Files
AthonetTools/app.py

754 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)