This commit is contained in:
2026-05-07 12:30:51 -04:00
parent 2ed785e214
commit 59d8db92ca
32 changed files with 1796 additions and 290 deletions
+156 -17
View File
@@ -1,8 +1,9 @@
from flask import Flask, render_template, request, jsonify, Response
from flask import Flask, render_template, request, jsonify, Response, stream_with_context
import core_functions
import auth_utils
import logging
import os
import json
import urllib3; urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import requests
from requests.exceptions import HTTPError, RequestException
@@ -17,20 +18,60 @@ from services.remote_admin import (
)
from services.yaml_writer import STAGING, render_to_file
from pathlib import Path
from services import vpn_runtime
from services.log_stream import JournalctlStream, LogTarget
API_USER = os.getenv("CORE_API_USER", "admin")
API_PASS = os.getenv("CORE_API_PASS", "Super4dmin!") # consider moving to env/secret
DASHBOARD_URLS = {
"Triton": "https://dashboard.arubaedge-triton.athonetusa.com",
"Star": "https://dashboard.arubaedge-star.athonetusa.com",
"Bluebonnet": "https://dashboard.arubaedge-bluebonnet.athonetusa.com",
"Lonestar": "https://dashboard.arubaedge-lonestar.athonetusa.com",
"Production": "https://dashboard.us-east-2.p5g.athonet.cloud",
"Test (future)": "https://your-test-dashboard-url.com"
}
def _load_dashboard_urls() -> dict:
default = {
"Production": "https://dashboard.private5g.networking.hpe.com",
"Test": "https://your-test-dashboard-url.com",
}
raw = os.getenv("DASHBOARD_ENVIRONMENTS")
if not raw:
return default
try:
parsed = json.loads(raw)
cleaned = {str(k): str(v) for k, v in parsed.items() if str(v).strip()}
return cleaned or default
except json.JSONDecodeError as exc:
logging.warning("Failed to parse DASHBOARD_ENVIRONMENTS (%s); falling back to defaults", exc)
return default
DASHBOARD_URLS = _load_dashboard_urls()
def _resolve_dashboard_url(name: str) -> str:
if not name:
raise ValueError("Dashboard selection is required")
url = DASHBOARD_URLS.get(name)
if not url:
raise ValueError(f"Unknown dashboard '{name}'")
return url
def _get_available_vpn_configs() -> list:
try:
available = vpn_runtime.list_available_vpns()
except Exception:
available = []
configured = getattr(core_functions, "VPN_CONFIG_NAMES", [])
if not available:
return configured
ordered = [name for name in configured if name in available]
extras = [name for name in available if name not in ordered]
return ordered + extras
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET_KEY", "dev-secret")
@app.context_processor
def inject_global_options():
return {
"dashboard_names": list(DASHBOARD_URLS.keys()),
"available_vpn_configs": _get_available_vpn_configs(),
}
def _format_ipv6(host: str) -> str:
return f"[{host}]" if ":" in host and not host.startswith("[") else host
@@ -393,6 +434,23 @@ def hnk_page():
def network_clients_page():
return render_template("pages/network_clients.html", active_page='network_clients')
@app.post("/api/supis/list")
def api_list_supis():
data = request.get_json(silent=True) or {}
host = (data.get("host") or "").strip()
try:
limit = int(data.get("limit", 500))
except (TypeError, ValueError):
return jsonify({"error": "limit must be an integer"}), 400
if not host:
return jsonify({"error": "Host IP is missing"}), 400
try:
supis = core_functions.list_network_clients(host, limit=limit)
return jsonify({"count": len(supis), "supis": supis})
except Exception as e:
app.logger.error(f"Error listing SUPIs for host {host}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 502
@app.route("/system-browser")
def system_browser_page():
return render_template("pages/system_browser.html", active_page='system_browser')
@@ -457,7 +515,10 @@ def gaf_desk_page():
def api_list_m2000():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
base_url = _resolve_dashboard_url(dashboard_name)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
devices = core_functions.list_m2000_vpns(base_url, token, session)
@@ -470,7 +531,10 @@ def api_list_m2000():
def api_get_network_config():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
base_url = _resolve_dashboard_url(dashboard_name)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
config_data = core_functions.get_full_network_config(base_url, token, session)
@@ -483,7 +547,10 @@ def api_get_network_config():
def api_list_tenants():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
base_url = _resolve_dashboard_url(dashboard_name)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
tenants = core_functions.list_tenants(base_url, token, session)
@@ -497,7 +564,10 @@ def api_list_plmns():
data = request.json
dashboard_name = data.get('dashboard')
tenant_id = data.get('tenant_id')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
base_url = _resolve_dashboard_url(dashboard_name)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
plmns = core_functions.list_plmns(base_url, token, session, tenant_id)
@@ -563,7 +633,10 @@ def api_list_plmn_hnks():
dashboard_name = data.get('dashboard')
tenant_id = data.get('tenant_id')
plmn_id = data.get('plmn_id')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
base_url = _resolve_dashboard_url(dashboard_name)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
hnks = core_functions.list_plmn_hnks(base_url, token, session, tenant_id, plmn_id)
@@ -675,7 +748,10 @@ def api_update_radios():
network_id = data.get('network_id')
new_count = data.get('new_count')
operation = data.get('operation')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
base_url = _resolve_dashboard_url(dashboard_name)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
result = core_functions.update_radio_count(base_url, token, session, network_id, new_count, operation)
@@ -693,6 +769,66 @@ def api_get_system_browser_data():
app.logger.error(f"Error getting system browser data: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.post("/api/logs/processes")
def api_logs_processes():
data = request.get_json(force=True) or {}
hosts = data.get("hosts") or []
if not isinstance(hosts, list) or not hosts:
return jsonify({"error": "Provide a list of host IPs"}), 400
results = []
for raw_host in hosts:
host = (raw_host or "").strip()
if not host:
continue
try:
token = auth_utils.authenticate(host)
system_info = core_functions.get_system_info(host, token)
frontend = core_functions.get_frontend_config(host, token)
services = frontend.get("services", []) if isinstance(frontend, dict) else []
running = [svc for svc in services if svc.get("state") == "started"]
results.append({
"host": host,
"hostname": system_info.get("hostname"),
"services": running,
})
except Exception as exc:
app.logger.error(f"Failed to fetch services for {host}: {exc}", exc_info=True)
results.append({"host": host, "error": str(exc)})
return jsonify({"hosts": results})
@app.post("/api/logs/stream")
def api_logs_stream():
data = request.get_json(force=True) or {}
targets_in = data.get("targets") or []
if not isinstance(targets_in, list) or not targets_in:
return jsonify({"error": "No log targets supplied"}), 400
targets: list[LogTarget] = []
for item in targets_in:
host = (item.get("host") or "").strip()
processes = [p.strip() for p in item.get("processes", []) if p and p.strip()]
if host and processes:
targets.append(LogTarget(host=host, processes=processes, hostname=item.get("hostname")))
if not targets:
return jsonify({"error": "No valid hosts/processes to stream"}), 400
try:
streamer = JournalctlStream(targets)
except Exception as exc:
return jsonify({"error": str(exc)}), 500
def generate():
try:
for event in streamer.iter_events():
yield json.dumps(event) + "\n"
finally:
streamer.stop()
return Response(stream_with_context(generate()), mimetype="text/plain")
@app.route("/api/backup/create", methods=["POST"])
def api_create_backup():
data = request.json
@@ -741,7 +877,10 @@ def api_get_host_details():
def api_list_users():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
base_url = _resolve_dashboard_url(dashboard_name)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
users = core_functions.list_users(base_url, token, session)
@@ -751,4 +890,4 @@ def api_list_users():
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
app.run(debug=True)
app.run(debug=True)