Initial commit of AthonetTools

This commit is contained in:
2025-08-21 12:59:43 +00:00
commit cd932b8fcb
2483 changed files with 433999 additions and 0 deletions

754
app.py Normal file
View File

@@ -0,0 +1,754 @@
from flask import Flask, render_template, request, jsonify, Response
import core_functions
import auth_utils
import logging
import os
import urllib3; urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import requests
from requests.exceptions import HTTPError, RequestException
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
from services.state import set_target_ip, get_target_ip, set_mgmt_info, get_mgmt_info
from services.combocore import login as cc_login, get_routes as cc_get_routes, extract_eth0_cidr_gw
from services.local_net import get_eth0_dhcp_snapshot
from services.remote_admin import (
validate_ipv4,
service_action,
perform_service_sequence,
)
from services.yaml_writer import STAGING, render_to_file
from pathlib import Path
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"
}
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
def _format_ipv6(host: str) -> str:
return f"[{host}]" if ":" in host and not host.startswith("[") else host
def _fetch_eth0_from_remote(host: str) -> dict:
fip = _format_ipv6(host)
login_url = f"https://{fip}/core/pls/api/1/auth/login"
routes_url = f"https://{fip}/core/ncm/api/1/status/routes"
# 1) get token
r = requests.post(
login_url,
json={"username": "admin", "password": "Super4dmin!"},
timeout=10, verify=False
)
r.raise_for_status()
token = r.json().get("access_token")
if not token:
raise RuntimeError("No access_token in login response")
# 2) get routes
r2 = requests.get(
routes_url,
headers={"Authorization": f"Bearer {token}"},
timeout=10, verify=False
)
r2.raise_for_status()
routes = r2.json() # list of dicts
gw = None
cidr = None
# default route via eth0 -> gateway
for item in routes:
if item.get("family") == "inet" and item.get("dst") == "default" and item.get("dev") == "eth0":
gw = item.get("gateway")
break
# derive IP/ prefix from eth0 kernel link + subnet route
prefsrc = None
mask = None
for item in routes:
if item.get("family") == "inet" and item.get("dev") == "eth0" and item.get("type") == "unicast":
if "prefsrc" in item and item["prefsrc"]:
prefsrc = item["prefsrc"]
if item.get("dst") and "/" in str(item["dst"]):
mask = item["dst"].split("/", 1)[1]
if prefsrc and mask:
cidr = f"{prefsrc}/{mask}"
if not gw or not cidr:
raise RuntimeError(f"Could not parse eth0 params gw={gw} cidr={cidr}")
return {"cidr": cidr, "gw": gw}
def fetch_oam_from_target(host: str) -> dict:
"""
Log in to the target 5GC and read the IPv4 OAM info from /core/ncm/api/1/status/routes.
Returns dict like {"cidr":"192.168.86.54/24", "gw":"192.168.86.1"} or {"error": "..."}.
"""
base = f"https://{host}"
login_url = f"{base}/core/pls/api/1/auth/login"
routes_url = f"{base}/core/ncm/api/1/status/routes"
# --- Authenticate ---
try:
r = requests.post(
login_url,
json={"username": "admin", "password": "Super4dmin!"},
timeout=10,
verify=False,
)
r.raise_for_status()
j = r.json()
token = j.get("access_token") or j.get("token")
if not token:
return {"error": "login ok but no access_token in response"}
except Exception as e:
return {"error": f"login failed: {e}"}
render_to_file("aio_networking.yaml.j2", ctx, aio_networking_path)
# --- Fetch routes ---
try:
r2 = requests.get(
routes_url,
headers={"Authorization": f"Bearer {token}"},
timeout=10,
verify=False,
)
r2.raise_for_status()
routes = r2.json() # expected: list of route dicts
if not isinstance(routes, list):
return {"error": "unexpected routes payload (not a list)"}
except Exception as e:
return {"error": f"routes fetch failed: {e}"}
# --- Parse gw (default v4 route) ---
gw = None
for rt in routes:
if rt.get("family") == "inet" and rt.get("type") == "unicast" and rt.get("dst") == "default" and rt.get("dev") == "eth0":
gw = rt.get("gateway")
if gw:
break
# --- Parse prefix length from the on-link route and combine with prefsrc ---
ip_addr = None
prefix_len = None
for rt in routes:
if rt.get("family") == "inet" and rt.get("dev") == "eth0":
# kernel on-link route looks like 192.168.86.0/24 on eth0
dst = rt.get("dst", "")
if "/" in dst:
try:
prefix_len = int(dst.split("/", 1)[1])
except Exception:
pass
# dhcp/kernel entries usually include prefsrc with the assigned IP
ip_addr = ip_addr or rt.get("prefsrc")
if ip_addr and prefix_len is not None:
break
if not ip_addr or prefix_len is None:
return {"error": "could not derive ip/prefix from routes"}
cidr = f"{ip_addr}/{prefix_len}"
# persist for later rendering
try:
from services.state import set_mgmt_info # avoid circular import issues
set_mgmt_info(cidr, gw or "")
except Exception:
pass
return {"cidr": cidr, "gw": gw}
# --- Host target state (Stage 1) ---
@app.post("/api/host/target")
def api_set_target():
ip = request.json.get("ip")
try:
ip = validate_ipv4(ip)
set_target_ip(ip)
return jsonify({"ok": True, "ip": ip})
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 400
@app.post("/api/local/eth0/capture")
def api_local_eth0_capture():
data = request.json or {}
host = data.get('host')
try:
# Use get_eth0_dhcp_snapshot or _fetch_eth0_from_remote depending on your setup
if host:
result = _fetch_eth0_from_remote(host)
else:
result = get_eth0_dhcp_snapshot()
return jsonify({"ok": True, **result})
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 500
@app.get("/api/local/eth0")
def api_local_eth0():
try:
host = request.args.get("host") or get_target_ip()
if not host:
return jsonify({"ok": False, "error": "host not provided and no target set"}), 400
snap = _fetch_eth0_from_remote(host)
return jsonify({"ok": True, **snap})
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 500
@app.get("/api/host/target")
def api_get_target():
return jsonify({"ip": get_target_ip()})
@app.get("/api/routes")
def api_routes():
rules = []
for r in app.url_map.iter_rules():
rules.append({
"rule": str(r),
"methods": sorted(m for m in r.methods if m not in {"HEAD","OPTIONS"}),
"endpoint": r.endpoint,
})
return jsonify({"ok": True, "routes": rules})
@app.post("/api/host/capture_oam")
def api_host_capture_oam():
"""
Use the saved target host (or JSON body 'host') to:
1) login to ComboCore
2) read routes
3) extract eth0 cidr/gw
4) persist via set_mgmt_info
"""
body = request.get_json(silent=True) or {}
host = (body.get("host") or get_target_ip() or "").strip()
if not host:
return jsonify({"ok": False, "error": "No target host set. Set it via /api/host/target or include 'host' in body."}), 400
try:
token = cc_login(host)
routes = cc_get_routes(host, token)
cidr, gw = extract_eth0_cidr_gw(routes)
set_mgmt_info(cidr, gw)
return jsonify({"ok": True, "host": host, "cidr": cidr, "gw": gw})
except HTTPError as e:
return jsonify({"ok": False, "host": host, "error": f"HTTP {e.response.status_code}: {e.response.text}"}), 502
except Exception as e:
return jsonify({"ok": False, "host": host, "error": str(e)}), 500
# Enable SSH + Webconsole: enable → enable-autostart → start (serial)
@app.post("/api/host/bootstrap_access")
def api_bootstrap_access():
ip = get_target_ip()
if not ip:
return jsonify({"ok": False, "error": "Target IP not set"}), 400
try:
perform_service_sequence(ip, "ssh", API_USER, API_PASS)
perform_service_sequence(ip, "webconsole", API_USER, API_PASS)
return jsonify({"ok": True})
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 502
@app.get("/api/ping")
def api_ping():
return jsonify({"ok": True, "msg": "pong"})
def _ensure_dir(p: Path):
p.mkdir(parents=True, exist_ok=True)
def _clean_dir(p: Path):
if not p.exists():
return
for item in sorted(p.rglob("*"), reverse=True):
if item.is_file():
item.unlink()
elif item.is_dir():
try: item.rmdir()
except OSError: pass
@app.post("/api/ansible/render")
def api_ansible_render():
body = request.get_json(force=True)
def _first(v): return (v or "").strip()
# --- Basic normalization ---
plmn = _first(body.get("plmn"))
mcc, mnc = (plmn.split("-")[0], plmn.split("-")[1]) if "-" in plmn else ("315", "010")
ran_cidr = _first(body.get("ran", {}).get("cidr"))
ran_ip = ran_cidr.split("/")[0] if "/" in ran_cidr else ran_cidr
vpn_ip = _first(body.get("ansible_host_ip")) or _first(get_target_ip())
mgmt_in = body.get("mgmt", {}) or {}
mgmt_cidr = _first(mgmt_in.get("cidr"))
mgmt_gw = _first(mgmt_in.get("gw"))
# Always use values from payload, do not override with cached/remote values
print(f"[DEBUG] Rendering YAML with mgmt_cidr={mgmt_cidr}, mgmt_gw={mgmt_gw}")
app.logger.info(f"[DEBUG] Rendering YAML with mgmt_cidr={mgmt_cidr}, mgmt_gw={mgmt_gw}")
if not (mgmt_cidr and mgmt_gw):
return jsonify({"ok": False, "error": "Cannot determine OAM (eth0) CIDR/gateway run Stage 1 capture first."}), 400
inventory_host = _first(body.get("inventory_host") or "GBP08-AIO-1")
esxi_host = _first(body.get("esxi_host") or "ESXI-1")
ctx = {
"hostname": _first(body.get("hostname") or "AIO-1"),
"network_name": _first(body.get("network_name") or "Network"),
"plmn": plmn, "mcc": mcc, "mnc": mnc,
"dns": body.get("dns", ["8.8.8.8"]),
"ntp": body.get("ntp", ["0.pool.ntp.org", "1.pool.ntp.org"]),
"ran": {"cidr": ran_cidr, "gw": _first(body.get("ran", {}).get("gw")), "ip": ran_ip},
"mgmt": {
"mode": "static",
"cidr": mgmt_cidr,
"gw": mgmt_gw,
},
"dn": {
"cidr": _first(body.get("dn", {}).get("cidr")),
"gw": _first(body.get("dn", {}).get("gw")),
"vlan": body.get("dn", {}).get("vlan"),
"ue_pool": _first(body.get("dn", {}).get("ue_pool")),
"dnn": _first(body.get("dn", {}).get("dnn") or "internet"),
},
"inventory_host": inventory_host,
"ansible_host_ip": vpn_ip or "127.0.0.1", # final fallback
"esxi_host": esxi_host,
"version": _first(body.get("version") or "25.1"),
"ova_file": _first(body.get("ova_file") or "/home/mjensen/OVA/HPE_ANW_P5G_Core-1.25.1.1-qemux86-64.ova"),
"report_services": bool(body.get("report_services", False)),
}
# --- Write directly under staging/ (no scenario folder) ---
base = STAGING
_clean_dir(base) # wipe staging each run (simple & predictable)
# Ensure folders exist
aio_host_dir = base / "host_vars" / inventory_host
esxi_host_dir = base / "host_vars" / esxi_host
_ensure_dir(aio_host_dir)
_ensure_dir(esxi_host_dir)
# --- Force re-render of YAML files with latest values ---
render_to_file("hosts.yaml.j2", ctx, base / "hosts.yaml")
render_to_file("aio_deploy.yaml.j2", ctx, aio_host_dir / "aio_deploy.yaml")
# Always render aio_networking.yaml with correct context
aio_networking_path = aio_host_dir / "aio_networking.yaml"
print(f"[DEBUG] Rendering {aio_networking_path} with context: {ctx}")
render_to_file("aio_networking.yaml.j2", ctx, aio_networking_path)
render_to_file("aio_3gpp.yaml.j2", ctx, aio_host_dir / "aio_3gpp.yaml")
render_to_file("aio_provisioning.yaml.j2", ctx, aio_host_dir / "aio_provisioning.yaml")
render_to_file("esxi.yaml.j2", ctx, esxi_host_dir / "esxi.yaml")
# Always return success if no exception occurred
return jsonify({"ok": True, "staging": str(base)})
@app.post("/api/host/service/<service>/<action>")
def api_service_action(service, action):
service = service.lower()
action = action.lower()
ip = get_target_ip()
if not ip:
return jsonify({"ok": False, "error": "Target IP not set"}), 400
try:
# optional early guard (mirrors remote_admin.ALLOWED_*):
if service not in {"ssh","webconsole"} or action not in {"enable","enable-autostart","start"}:
return jsonify({"ok": False, "error": "Unsupported service/action"}), 400
service_action(ip, service, action, API_USER, API_PASS)
return jsonify({"ok": True})
except Exception as e:
app.logger.exception("service_action failed")
return jsonify({"ok": False, "error": str(e)}), 502
# --- Page Routes ---
@app.route("/")
def vpn_status_page():
return render_template("pages/vpn_status.html", active_page='vpn_status')
@app.route("/network-config")
def network_config_page():
return render_template("pages/network_config.html", active_page='network_config')
@app.route("/tenants")
def tenants_page():
return render_template("pages/tenants.html", active_page='tenants')
@app.route("/hnk")
def hnk_page():
return render_template("pages/hnk.html", active_page='hnk')
@app.route("/network-clients")
def network_clients_page():
return render_template("pages/network_clients.html", active_page='network_clients')
@app.route("/system-browser")
def system_browser_page():
return render_template("pages/system_browser.html", active_page='system_browser')
@app.route("/users")
def users_page():
return render_template("pages/users.html", active_page='users')
@app.route("/m2000-reset")
def m2000_reset_page():
return render_template("pages/m2000_config_reset.html", active_page='m2000_reset')
@app.route("/api/ansible/deploy", methods=["POST"])
def api_ansible_deploy():
import subprocess
staging_dir = "/home/mjensen/network_tool/ansible_workspace/staging"
try:
import subprocess
outputs = []
try:
import os
# Ensure PATH includes system binaries
os.environ["PATH"] = os.environ.get("PATH", "") + ":/usr/bin:/usr/local/bin:/bin"
env_info = f"USER: {os.environ.get('USER')}\nPATH: {os.environ.get('PATH')}\n"
result_docker = subprocess.run(['/usr/bin/docker', 'ps'], capture_output=True, text=True)
docker_output = result_docker.stdout + result_docker.stderr
result_script = subprocess.run(
'/usr/bin/script -q -c "/usr/local/bin/ath-gaf-cli --ova-path /OVA" /dev/null <<< "playbook-ngc-config"',
cwd=staging_dir,
shell=True,
capture_output=True,
text=True,
executable='/bin/bash'
)
output = env_info + docker_output + result_script.stdout + result_script.stderr
# Return as plain text if not valid JSON
from flask import Response
return Response(output, mimetype='text/plain')
except Exception as e:
return jsonify({"output": f"Error: {str(e)}"}), 500
except Exception as e:
return jsonify({"output": f"Error: {str(e)}"}), 500
@app.route("/vpn-switcher")
def vpn_switcher_page():
ip_from_url = request.args.get('ip', None)
return render_template("pages/vpn_switcher.html", active_page='vpn_switcher', ip_from_url=ip_from_url)
@app.route("/m2000psw")
def m2000_password_page():
serial_from_url = request.args.get('serial', None)
return render_template("pages/m2000_password.html", active_page='m2000_password', serial_from_url=serial_from_url)
@app.route("/gaf-desk")
def gaf_desk_page():
return render_template("pages/gaf_desk.html", active_page='gaf_desk')
# --- API Page Routes ---
@app.route("/api/m2000/list", methods=["POST"])
def api_list_m2000():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
devices = core_functions.list_m2000_vpns(base_url, token, session)
return jsonify(devices)
except Exception as e:
app.logger.error(f"Error in /api/m2000/list: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/network/get-config", methods=["POST"])
def api_get_network_config():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
config_data = core_functions.get_full_network_config(base_url, token, session)
return jsonify(config_data)
except Exception as e:
app.logger.error(f"Error in /api/network/get-config: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/tenants/list", methods=["POST"])
def api_list_tenants():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
tenants = core_functions.list_tenants(base_url, token, session)
return jsonify(tenants)
except Exception as e:
app.logger.error(f"Error listing tenants: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/plmns/list", methods=["POST"])
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:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
plmns = core_functions.list_plmns(base_url, token, session, tenant_id)
return jsonify(plmns)
except Exception as e:
app.logger.error(f"Error listing PLMNs for tenant {tenant_id}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route('/generate_yaml', methods=['POST'])
def generate_yaml():
data = request.json
# Use Jinja2 to render from .j2 templates
# Example:
rendered = render_template('my_template.j2', **data)
output_path = f"/path/to/output/{data['network_name']}.yaml"
with open(output_path, 'w') as f:
f.write(rendered)
return jsonify({"status": "success", "file": output_path})
@app.route("/api/vpn/get-config", methods=["POST"])
def api_get_vpn_config():
data = request.json
host_ip = data.get('host_ip')
if not host_ip:
return jsonify({"error": "Host IP is missing"}), 400
try:
combined_data = core_functions.get_vpn_config_and_details(host_ip)
return jsonify(combined_data)
except Exception as e:
app.logger.error(f"Error in VPN switcher for {host_ip}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/vpn/get-endpoint", methods=["POST"])
def api_get_vpn_endpoint():
data = request.json
host_ip = data.get('host_ip')
if not host_ip:
return jsonify({"error": "Host IP is missing"}), 400
try:
endpoint_info = core_functions.get_current_vpn_endpoint(host_ip)
return jsonify(endpoint_info)
except Exception as e:
app.logger.error(f"Error getting VPN endpoint for {host_ip}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/vpn/set-endpoint", methods=["POST"])
def api_set_vpn_endpoint():
data = request.json
host_ip = data.get('host_ip')
region = data.get('region')
if not all([host_ip, region]):
return jsonify({"error": "Missing required parameters"}), 400
try:
result = core_functions.set_vpn_endpoint(host_ip, region)
return jsonify(result)
except Exception as e:
app.logger.error(f"Error setting VPN endpoint for {host_ip}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/hnks/list/by-plmn", methods=["POST"])
def api_list_plmn_hnks():
data = request.json
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:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
hnks = core_functions.list_plmn_hnks(base_url, token, session, tenant_id, plmn_id)
return jsonify(hnks)
except Exception as e:
app.logger.error(f"Error listing HNKs for PLMN {plmn_id}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/m2000/get-password", methods=["POST"])
def api_get_m2000_password():
data = request.json
serial = data.get('serial')
if not serial:
return jsonify({"error": "Serial number is missing"}), 400
try:
password = core_functions.generate_m2000_password(serial)
return jsonify({"serial": serial, "password": password})
except Exception as e:
app.logger.error(f"Error generating password for serial {serial}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/m2000/reset-config", methods=["POST"])
def api_m2000_reset_config():
data = request.json
base_ip = data.get('base_ip')
if not base_ip:
return jsonify({"error": "Base IPv6 address is missing"}), 400
try:
reset_results = core_functions.reset_m2000_configuration(base_ip)
return jsonify(reset_results)
except Exception as e:
app.logger.error(f"Error in m2000 config reset for base IP {base_ip}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/hnk/list", methods=["POST"])
def api_list_hnk():
data = request.json
host_ip = data.get('host')
if not host_ip:
return jsonify({"error": "Host IP is missing"}), 400
try:
token = auth_utils.authenticate(host_ip)
hnk_data = core_functions.list_home_network_keys(host_ip, token)
return jsonify(hnk_data)
except Exception as e:
app.logger.error(f"Error in /api/hnk/list: {e}", exc_info=True)
return jsonify({"error": "An internal error occurred. Check server logs."}), 500
@app.route("/host/<host_ip>")
def host_details_page(host_ip):
details_from_browser = {
"customer_name": request.args.get('customer_name', 'N/A'),
"common_name": request.args.get('common_name', 'N/A'),
"public_ip": request.args.get('public_ip', 'N/A'),
"connected_since": request.args.get('connected_since', 'N/A')
}
try:
# Fetch the live, detailed information from the host
live_details = core_functions.get_host_details(host_ip)
# Combine both sets of data to pass to the template
live_details['browser_info'] = details_from_browser
return render_template("pages/host_details.html", details=live_details)
except Exception as e:
app.logger.error(f"Error getting details for host {host_ip}: {e}", exc_info=True)
# Pass the browser info even if the live fetch fails
error_details = {"browser_info": details_from_browser, "error": True}
return render_template("pages/host_details.html", details=error_details)
@app.route("/api/m2000/restart", methods=["POST"])
def api_restart_m2000():
data = request.json
serial = data.get('serial')
subnet = data.get('subnet')
try:
result = core_functions.restart_m2000_vpn(serial, subnet)
return jsonify(result)
except Exception as e:
app.logger.error(f"An exception occurred during VPN restart for serial {serial}:", exc_info=True)
return jsonify({"error": "An internal error occurred. Check server logs."}), 500
@app.route("/api/vpn/status", methods=["GET"])
def api_vpn_status():
try:
active_vpn = core_functions.get_active_vpn()
return jsonify({"active_vpn": active_vpn})
except Exception as e:
app.logger.error(f"Error getting VPN status: {e}", exc_info=True)
return jsonify({"error": "Failed to get VPN status"}), 500
@app.route("/api/vpn/toggle", methods=["POST"])
def api_vpn_toggle():
data = request.json
vpn_name = data.get('vpn_name')
turn_on = data.get('state', False)
try:
new_active_vpn = core_functions.toggle_vpn_connection(vpn_name, turn_on)
return jsonify({"status": "success", "active_vpn": new_active_vpn})
except Exception as e:
app.logger.error(f"Error toggling VPN {vpn_name}: {e}", exc_info=True)
return jsonify({"error": f"Failed to toggle VPN {vpn_name}"}), 500
@app.route("/api/network/update-radios", methods=["POST"])
def api_update_radios():
data = request.json
dashboard_name = data.get('dashboard')
network_id = data.get('network_id')
new_count = data.get('new_count')
operation = data.get('operation')
base_url = DASHBOARD_URLS.get(dashboard_name)
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)
return jsonify({"status": "success", "message": "Radio count updated successfully.", "details": result})
except Exception as e:
app.logger.error(f"Error updating radio count for network {network_id}: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/system-browser/data", methods=["POST"])
def api_get_system_browser_data():
try:
browser_data = core_functions.get_system_browser_data()
return jsonify(browser_data)
except Exception as e:
app.logger.error(f"Error getting system browser data: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/api/backup/create", methods=["POST"])
def api_create_backup():
data = request.json
host_ip = data.get('host')
if not host_ip:
return Response("Host IP is missing", status=400)
try:
token = auth_utils.authenticate(host_ip)
backup_response = core_functions.create_backup(host_ip, token)
return Response(
backup_response.iter_content(chunk_size=1024),
content_type=backup_response.headers.get('Content-Type'),
headers={"Content-Disposition": backup_response.headers.get('Content-Disposition')}
)
except Exception as e:
app.logger.error(f"Error creating backup for host {host_ip}: {e}", exc_info=True)
return Response(f"An error occurred on the server: {e}", status=500)
@app.route("/api/host/details", methods=["POST"])
def api_get_host_details():
data = request.json
host_ip = data.get('host')
if not host_ip:
return jsonify({"error": "Host IP is missing"}), 400
try:
token = auth_utils.authenticate(host_ip)
system_info = core_functions.get_system_info(host_ip, token)
site_info = core_functions.get_site_info(host_ip, token)
frontend_config = core_functions.get_frontend_config(host_ip, token)
# Combine all the data into a single response
combined_data = {
"system": system_info,
"site": site_info,
"services": frontend_config.get("services", [])
}
return jsonify(combined_data)
except Exception as e:
app.logger.error(f"Error getting host details for {host_ip}: {e}", exc_info=True)
return jsonify({"error": "An internal error occurred"}), 500
@app.route("/api/users/list", methods=["POST"])
def api_list_users():
data = request.json
dashboard_name = data.get('dashboard')
base_url = DASHBOARD_URLS.get(dashboard_name)
try:
token, session = auth_utils.get_vpn_dashboard_token(base_url)
users = core_functions.list_users(base_url, token, session)
return jsonify(users)
except Exception as e:
app.logger.error(f"Error listing users: {e}", exc_info=True)
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
app.run(debug=True)