mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
/data and /tmp standarization
This commit is contained in:
@@ -1,30 +1,74 @@
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
|
||||
from flask import Flask, request, jsonify, Response
|
||||
from flask_cors import CORS
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH = "/app"
|
||||
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
from logger import mylog
|
||||
from helper import get_setting_value, timeNowTZ
|
||||
from helper import get_setting_value
|
||||
from db.db_helper import get_date_from_period
|
||||
from app_state import updateState
|
||||
|
||||
|
||||
from .graphql_endpoint import devicesSchema
|
||||
from .device_endpoint import get_device_data, set_device_data, delete_device, delete_device_events, reset_device_props, copy_device, update_device_column
|
||||
from .devices_endpoint import get_all_devices, delete_unknown_devices, delete_all_with_empty_macs, delete_devices, export_devices, import_csv, devices_totals, devices_by_status
|
||||
from .events_endpoint import delete_events, delete_events_older_than, get_events, create_event, get_events_totals
|
||||
from .history_endpoint import delete_online_history
|
||||
from .prometheus_endpoint import get_metric_stats
|
||||
from .sessions_endpoint import get_sessions, delete_session, create_session, get_sessions_calendar, get_device_sessions, get_session_events
|
||||
from .nettools_endpoint import wakeonlan, traceroute, speedtest, nslookup, nmap_scan, internet_info
|
||||
from .dbquery_endpoint import read_query, write_query, update_query, delete_query
|
||||
from .sync_endpoint import handle_sync_post, handle_sync_get
|
||||
from messaging.in_app import write_notification, mark_all_notifications_read, delete_notifications, get_unread_notifications, delete_notification, mark_notification_as_read
|
||||
from api_server.graphql_endpoint import devicesSchema
|
||||
from api_server.device_endpoint import (
|
||||
get_device_data,
|
||||
set_device_data,
|
||||
delete_device,
|
||||
delete_device_events,
|
||||
reset_device_props,
|
||||
copy_device,
|
||||
update_device_column,
|
||||
)
|
||||
from api_server.devices_endpoint import (
|
||||
get_all_devices,
|
||||
delete_unknown_devices,
|
||||
delete_all_with_empty_macs,
|
||||
delete_devices,
|
||||
export_devices,
|
||||
import_csv,
|
||||
devices_totals,
|
||||
devices_by_status,
|
||||
)
|
||||
from api_server.events_endpoint import (
|
||||
delete_events,
|
||||
delete_events_older_than,
|
||||
get_events,
|
||||
create_event,
|
||||
get_events_totals,
|
||||
)
|
||||
from api_server.history_endpoint import delete_online_history
|
||||
from api_server.prometheus_endpoint import get_metric_stats
|
||||
from api_server.sessions_endpoint import (
|
||||
get_sessions,
|
||||
delete_session,
|
||||
create_session,
|
||||
get_sessions_calendar,
|
||||
get_device_sessions,
|
||||
get_session_events,
|
||||
)
|
||||
from api_server.nettools_endpoint import (
|
||||
wakeonlan,
|
||||
traceroute,
|
||||
speedtest,
|
||||
nslookup,
|
||||
nmap_scan,
|
||||
internet_info,
|
||||
)
|
||||
from api_server.dbquery_endpoint import read_query, write_query, update_query, delete_query
|
||||
from api_server.sync_endpoint import handle_sync_post, handle_sync_get
|
||||
from messaging.in_app import (
|
||||
write_notification,
|
||||
mark_all_notifications_read,
|
||||
delete_notifications,
|
||||
get_unread_notifications,
|
||||
delete_notification,
|
||||
mark_notification_as_read,
|
||||
)
|
||||
|
||||
# Flask application
|
||||
app = Flask(__name__)
|
||||
@@ -40,34 +84,36 @@ CORS(
|
||||
r"/settings/*": {"origins": "*"},
|
||||
r"/dbquery/*": {"origins": "*"},
|
||||
r"/messaging/*": {"origins": "*"},
|
||||
r"/events/*": {"origins": "*"}
|
||||
r"/events/*": {"origins": "*"},
|
||||
},
|
||||
supports_credentials=True,
|
||||
allow_headers=["Authorization", "Content-Type"]
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
)
|
||||
|
||||
# --------------------------
|
||||
# GraphQL Endpoints
|
||||
# --------------------------
|
||||
|
||||
|
||||
# Endpoint used when accessed via browser
|
||||
@app.route("/graphql", methods=["GET"])
|
||||
def graphql_debug():
|
||||
# Handles GET requests
|
||||
return "NetAlertX GraphQL server running."
|
||||
|
||||
|
||||
# Endpoint for GraphQL queries
|
||||
@app.route("/graphql", methods=["POST"])
|
||||
def graphql_endpoint():
|
||||
# Check for API token in headers
|
||||
if not is_authorized():
|
||||
msg = '[graphql_server] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct.'
|
||||
mylog('verbose', [msg])
|
||||
msg = "[graphql_server] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct."
|
||||
mylog("verbose", [msg])
|
||||
return jsonify({"error": msg}), 401
|
||||
|
||||
# Retrieve and log request data
|
||||
data = request.get_json()
|
||||
mylog('verbose', [f'[graphql_server] data: {data}'])
|
||||
mylog("verbose", [f"[graphql_server] data: {data}"])
|
||||
|
||||
# Execute the GraphQL query
|
||||
result = devicesSchema.execute(data.get("query"), variables=data.get("variables"))
|
||||
@@ -82,10 +128,12 @@ def graphql_endpoint():
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Settings Endpoints
|
||||
# --------------------------
|
||||
|
||||
|
||||
@app.route("/settings/<setKey>", methods=["GET"])
|
||||
def api_get_setting(setKey):
|
||||
if not is_authorized():
|
||||
@@ -93,40 +141,47 @@ def api_get_setting(setKey):
|
||||
value = get_setting_value(setKey)
|
||||
return jsonify({"success": True, "value": value})
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Device Endpoints
|
||||
# --------------------------
|
||||
|
||||
|
||||
@app.route("/device/<mac>", methods=["GET"])
|
||||
def api_get_device(mac):
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return get_device_data(mac)
|
||||
|
||||
|
||||
@app.route("/device/<mac>", methods=["POST"])
|
||||
def api_set_device(mac):
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return set_device_data(mac, request.json)
|
||||
|
||||
|
||||
@app.route("/device/<mac>/delete", methods=["DELETE"])
|
||||
def api_delete_device(mac):
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return delete_device(mac)
|
||||
|
||||
|
||||
@app.route("/device/<mac>/events/delete", methods=["DELETE"])
|
||||
def api_delete_device_events(mac):
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return delete_device_events(mac)
|
||||
|
||||
|
||||
@app.route("/device/<mac>/reset-props", methods=["POST"])
|
||||
def api_reset_device_props(mac):
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return reset_device_props(mac, request.json)
|
||||
|
||||
|
||||
@app.route("/device/copy", methods=["POST"])
|
||||
def api_copy_device():
|
||||
if not is_authorized():
|
||||
@@ -137,10 +192,13 @@ def api_copy_device():
|
||||
mac_to = data.get("macTo")
|
||||
|
||||
if not mac_from or not mac_to:
|
||||
return jsonify({"success": False, "error": "macFrom and macTo are required"}), 400
|
||||
return jsonify(
|
||||
{"success": False, "error": "macFrom and macTo are required"}
|
||||
), 400
|
||||
|
||||
return copy_device(mac_from, mac_to)
|
||||
|
||||
|
||||
@app.route("/device/<mac>/update-column", methods=["POST"])
|
||||
def api_update_device_column(mac):
|
||||
if not is_authorized():
|
||||
@@ -151,35 +209,42 @@ def api_update_device_column(mac):
|
||||
column_value = data.get("columnValue")
|
||||
|
||||
if not column_name or not column_value:
|
||||
return jsonify({"success": False, "error": "columnName and columnValue are required"}), 400
|
||||
return jsonify(
|
||||
{"success": False, "error": "columnName and columnValue are required"}
|
||||
), 400
|
||||
|
||||
return update_device_column(mac, column_name, column_value)
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Devices Collections
|
||||
# --------------------------
|
||||
|
||||
|
||||
@app.route("/devices", methods=["GET"])
|
||||
def api_get_devices():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return get_all_devices()
|
||||
|
||||
|
||||
@app.route("/devices", methods=["DELETE"])
|
||||
def api_delete_devices():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
|
||||
|
||||
macs = request.json.get("macs") if request.is_json else None
|
||||
|
||||
return delete_devices(macs)
|
||||
|
||||
|
||||
@app.route("/devices/empty-macs", methods=["DELETE"])
|
||||
def api_delete_all_empty_macs():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return delete_all_with_empty_macs()
|
||||
|
||||
|
||||
@app.route("/devices/unknown", methods=["DELETE"])
|
||||
def api_delete_unknown_devices():
|
||||
if not is_authorized():
|
||||
@@ -196,18 +261,21 @@ def api_export_devices(format=None):
|
||||
export_format = (format or request.args.get("format", "csv")).lower()
|
||||
return export_devices(export_format)
|
||||
|
||||
|
||||
@app.route("/devices/import", methods=["POST"])
|
||||
def api_import_csv():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return import_csv(request.files.get("file"))
|
||||
|
||||
|
||||
@app.route("/devices/totals", methods=["GET"])
|
||||
def api_devices_totals():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return devices_totals()
|
||||
|
||||
|
||||
@app.route("/devices/by-status", methods=["GET"])
|
||||
def api_devices_by_status():
|
||||
if not is_authorized():
|
||||
@@ -217,6 +285,7 @@ def api_devices_by_status():
|
||||
|
||||
return devices_by_status(status)
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Net tools
|
||||
# --------------------------
|
||||
@@ -228,6 +297,7 @@ def api_wakeonlan():
|
||||
mac = request.json.get("devMac")
|
||||
return wakeonlan(mac)
|
||||
|
||||
|
||||
@app.route("/nettools/traceroute", methods=["POST"])
|
||||
def api_traceroute():
|
||||
if not is_authorized():
|
||||
@@ -235,12 +305,14 @@ def api_traceroute():
|
||||
ip = request.json.get("devLastIP")
|
||||
return traceroute(ip)
|
||||
|
||||
|
||||
@app.route("/nettools/speedtest", methods=["GET"])
|
||||
def api_speedtest():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return speedtest()
|
||||
|
||||
|
||||
@app.route("/nettools/nslookup", methods=["POST"])
|
||||
def api_nslookup():
|
||||
"""
|
||||
@@ -257,6 +329,7 @@ def api_nslookup():
|
||||
ip = data["devLastIP"]
|
||||
return nslookup(ip)
|
||||
|
||||
|
||||
@app.route("/nettools/nmap", methods=["POST"])
|
||||
def api_nmap():
|
||||
"""
|
||||
@@ -273,7 +346,7 @@ def api_nmap():
|
||||
ip = data["scan"]
|
||||
mode = data["mode"]
|
||||
return nmap_scan(ip, mode)
|
||||
|
||||
|
||||
|
||||
@app.route("/nettools/internetinfo", methods=["GET"])
|
||||
def api_internet_info():
|
||||
@@ -286,6 +359,7 @@ def api_internet_info():
|
||||
# DB query
|
||||
# --------------------------
|
||||
|
||||
|
||||
@app.route("/dbquery/read", methods=["POST"])
|
||||
def dbquery_read():
|
||||
if not is_authorized():
|
||||
@@ -296,9 +370,9 @@ def dbquery_read():
|
||||
|
||||
if not raw_sql_b64:
|
||||
return jsonify({"error": "rawSql is required"}), 400
|
||||
|
||||
|
||||
return read_query(raw_sql_b64)
|
||||
|
||||
|
||||
|
||||
@app.route("/dbquery/write", methods=["POST"])
|
||||
def dbquery_write():
|
||||
@@ -324,12 +398,12 @@ def dbquery_update():
|
||||
return jsonify({"error": "Missing required parameters"}), 400
|
||||
|
||||
return update_query(
|
||||
column_name=data["columnName"],
|
||||
ids=data["id"],
|
||||
dbtable=data["dbtable"],
|
||||
columns=data["columns"],
|
||||
values=data["values"],
|
||||
)
|
||||
column_name=data["columnName"],
|
||||
ids=data["id"],
|
||||
dbtable=data["dbtable"],
|
||||
columns=data["columns"],
|
||||
values=data["values"],
|
||||
)
|
||||
|
||||
|
||||
@app.route("/dbquery/delete", methods=["POST"])
|
||||
@@ -342,26 +416,30 @@ def dbquery_delete():
|
||||
if not all(data.get(k) for k in required):
|
||||
return jsonify({"error": "Missing required parameters"}), 400
|
||||
|
||||
return delete_query(
|
||||
column_name=data["columnName"],
|
||||
ids=data["id"],
|
||||
dbtable=data["dbtable"],
|
||||
)
|
||||
return delete_query(
|
||||
column_name=data["columnName"],
|
||||
ids=data["id"],
|
||||
dbtable=data["dbtable"],
|
||||
)
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Online history
|
||||
# --------------------------
|
||||
|
||||
|
||||
@app.route("/history", methods=["DELETE"])
|
||||
def api_delete_online_history():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return delete_online_history()
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Device Events
|
||||
# --------------------------
|
||||
|
||||
|
||||
@app.route("/events/create/<mac>", methods=["POST"])
|
||||
def api_create_event(mac):
|
||||
if not is_authorized():
|
||||
@@ -387,12 +465,14 @@ def api_events_by_mac(mac):
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return delete_device_events(mac)
|
||||
|
||||
|
||||
@app.route("/events", methods=["DELETE"])
|
||||
def api_delete_all_events():
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
return delete_events()
|
||||
|
||||
|
||||
@app.route("/events", methods=["GET"])
|
||||
def api_get_events():
|
||||
if not is_authorized():
|
||||
@@ -401,6 +481,7 @@ def api_get_events():
|
||||
mac = request.args.get("mac")
|
||||
return get_events(mac)
|
||||
|
||||
|
||||
@app.route("/events/<int:days>", methods=["DELETE"])
|
||||
def api_delete_old_events(days: int):
|
||||
"""
|
||||
@@ -409,9 +490,10 @@ def api_delete_old_events(days: int):
|
||||
"""
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
|
||||
|
||||
return delete_events_older_than(days)
|
||||
|
||||
|
||||
@app.route("/sessions/totals", methods=["GET"])
|
||||
def api_get_events_totals():
|
||||
if not is_authorized():
|
||||
@@ -420,10 +502,12 @@ def api_get_events_totals():
|
||||
period = get_date_from_period(request.args.get("period", "7 days"))
|
||||
return get_events_totals(period)
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Sessions
|
||||
# --------------------------
|
||||
|
||||
|
||||
@app.route("/sessions/create", methods=["POST"])
|
||||
def api_create_session():
|
||||
if not is_authorized():
|
||||
@@ -440,7 +524,9 @@ def api_create_session():
|
||||
if not mac or not ip or not start_time:
|
||||
return jsonify({"success": False, "error": "Missing required parameters"}), 400
|
||||
|
||||
return create_session(mac, ip, start_time, end_time, event_type_conn, event_type_disc)
|
||||
return create_session(
|
||||
mac, ip, start_time, end_time, event_type_conn, event_type_disc
|
||||
)
|
||||
|
||||
|
||||
@app.route("/sessions/delete", methods=["DELETE"])
|
||||
@@ -466,6 +552,7 @@ def api_get_sessions():
|
||||
|
||||
return get_sessions(mac, start_date, end_date)
|
||||
|
||||
|
||||
@app.route("/sessions/calendar", methods=["GET"])
|
||||
def api_get_sessions_calendar():
|
||||
if not is_authorized():
|
||||
@@ -477,6 +564,7 @@ def api_get_sessions_calendar():
|
||||
|
||||
return get_sessions_calendar(start_date, end_date)
|
||||
|
||||
|
||||
@app.route("/sessions/<mac>", methods=["GET"])
|
||||
def api_device_sessions(mac):
|
||||
if not is_authorized():
|
||||
@@ -485,6 +573,7 @@ def api_device_sessions(mac):
|
||||
period = request.args.get("period", "1 day")
|
||||
return get_device_sessions(mac, period)
|
||||
|
||||
|
||||
@app.route("/sessions/session-events", methods=["GET"])
|
||||
def api_get_session_events():
|
||||
if not is_authorized():
|
||||
@@ -494,6 +583,7 @@ def api_get_session_events():
|
||||
period = get_date_from_period(request.args.get("period", "7 days"))
|
||||
return get_session_events(session_event_type, period)
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Prometheus metrics endpoint
|
||||
# --------------------------
|
||||
@@ -503,7 +593,8 @@ def metrics():
|
||||
return jsonify({"error": "Forbidden"}), 403
|
||||
|
||||
# Return Prometheus metrics as plain text
|
||||
return Response(get_metric_stats(), mimetype="text/plain")
|
||||
return Response(get_metric_stats(), mimetype="text/plain")
|
||||
|
||||
|
||||
# --------------------------
|
||||
# In-app notifications
|
||||
@@ -519,10 +610,11 @@ def api_write_notification():
|
||||
|
||||
if not content:
|
||||
return jsonify({"success": False, "error": "Missing content"}), 400
|
||||
|
||||
|
||||
write_notification(content, level)
|
||||
return jsonify({"success": True})
|
||||
|
||||
|
||||
@app.route("/messaging/in-app/unread", methods=["GET"])
|
||||
def api_get_unread_notifications():
|
||||
if not is_authorized():
|
||||
@@ -530,6 +622,7 @@ def api_get_unread_notifications():
|
||||
|
||||
return get_unread_notifications()
|
||||
|
||||
|
||||
@app.route("/messaging/in-app/read/all", methods=["POST"])
|
||||
def api_mark_all_notifications_read():
|
||||
if not is_authorized():
|
||||
@@ -537,6 +630,7 @@ def api_mark_all_notifications_read():
|
||||
|
||||
return jsonify(mark_all_notifications_read())
|
||||
|
||||
|
||||
@app.route("/messaging/in-app/delete", methods=["DELETE"])
|
||||
def api_delete_all_notifications():
|
||||
if not is_authorized():
|
||||
@@ -544,6 +638,7 @@ def api_delete_all_notifications():
|
||||
|
||||
return delete_notifications()
|
||||
|
||||
|
||||
@app.route("/messaging/in-app/delete/<guid>", methods=["DELETE"])
|
||||
def api_delete_notification(guid):
|
||||
"""Delete a single notification by GUID."""
|
||||
@@ -556,6 +651,7 @@ def api_delete_notification(guid):
|
||||
else:
|
||||
return jsonify({"success": False, "error": result.get("error")}), 500
|
||||
|
||||
|
||||
@app.route("/messaging/in-app/read/<guid>", methods=["POST"])
|
||||
def api_mark_notification_read(guid):
|
||||
"""Mark a single notification as read by GUID."""
|
||||
@@ -567,7 +663,8 @@ def api_mark_notification_read(guid):
|
||||
return jsonify({"success": True})
|
||||
else:
|
||||
return jsonify({"success": False, "error": result.get("error")}), 500
|
||||
|
||||
|
||||
|
||||
# --------------------------
|
||||
# SYNC endpoint
|
||||
# --------------------------
|
||||
@@ -586,6 +683,7 @@ def sync_endpoint():
|
||||
mylog("verbose", [msg])
|
||||
return jsonify({"error": "Method Not Allowed"}), 405
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Background Server Start
|
||||
# --------------------------
|
||||
@@ -594,7 +692,7 @@ def is_authorized():
|
||||
is_authorized = token == f"Bearer {get_setting_value('API_TOKEN')}"
|
||||
|
||||
if not is_authorized:
|
||||
msg = f"[api] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct."
|
||||
msg = "[api] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct."
|
||||
write_notification(msg, "alert")
|
||||
mylog("verbose", [msg])
|
||||
|
||||
@@ -605,19 +703,15 @@ def start_server(graphql_port, app_state):
|
||||
"""Start the GraphQL server in a background thread."""
|
||||
|
||||
if app_state.graphQLServerStarted == 0:
|
||||
|
||||
mylog('verbose', [f'[graphql endpoint] Starting on port: {graphql_port}'])
|
||||
mylog("verbose", [f"[graphql endpoint] Starting on port: {graphql_port}"])
|
||||
|
||||
# Start Flask app in a separate thread
|
||||
thread = threading.Thread(
|
||||
target=lambda: app.run(
|
||||
host="0.0.0.0",
|
||||
port=graphql_port,
|
||||
debug=True,
|
||||
use_reloader=False
|
||||
host="0.0.0.0", port=graphql_port, debug=True, use_reloader=False
|
||||
)
|
||||
)
|
||||
thread.start()
|
||||
|
||||
# Update the state to indicate the server has started
|
||||
app_state = updateState("Process: Idle", None, None, None, 1)
|
||||
app_state = updateState("Process: Idle", None, None, None, 1)
|
||||
|
||||
Reference in New Issue
Block a user