Initial commit of AthonetTools
This commit is contained in:
754
app.py
Normal file
754
app.py
Normal 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)
|
||||
Reference in New Issue
Block a user