diff --git a/webui/api/certs.py b/webui/api/certs.py index 36db799..cb5fb1a 100644 --- a/webui/api/certs.py +++ b/webui/api/certs.py @@ -8,6 +8,16 @@ import logging from flask import Blueprint, request from daemon.client import BadRequest, NotFound, delete, get, post +from daemon.iface import ( + DELETE_ACME_REMOVE, + GET_ACME_INFO, + GET_ACME_ISSUE_STATUS, + GET_ACME_LIST, + POST_ACME_EMAIL, + POST_ACME_ISSUE, + POST_ACME_RENEW, + POST_ACME_VALIDATE, +) from webui.api.common import _error, _ok logger = logging.getLogger(__name__) @@ -22,7 +32,7 @@ def list_certs_bp(): Response containing the list of certificates or an error message. """ try: - return _ok(get("/acme/list")) + return _ok(get(GET_ACME_LIST)) except RuntimeError as exc: logger.error("Failed to list certificates: %s", exc) return _error(str(exc), 500) @@ -39,7 +49,7 @@ def cert_details(domain: str): Response containing certificate info or an error message. """ try: - return _ok(get("/acme/info", {"domain": domain})) + return _ok(get(GET_ACME_INFO, {"domain": domain})) except NotFound as exc: logger.info("Cert for '%s' not found: %s", domain, exc) return _error(str(exc), 404) @@ -62,7 +72,7 @@ def validate(): if not domain: return _error("'domain' is required", 400) try: - result = post("/acme/validate", {"domain": domain}) + result = post(POST_ACME_VALIDATE, {"domain": domain}) return _ok(result) except BadRequest as exc: logger.info("Validation rejected: %s", exc) @@ -90,7 +100,7 @@ def issue_start(): try: logger.info("Certificate issuance requested for '%s' via API", domain) result = post( - "/acme/issue", {"domain": domain, "webroot": webroot, "email": email} + POST_ACME_ISSUE, {"domain": domain, "webroot": webroot, "email": email} ) logger.info( "Certificate issuance started for '%s' (id=%s)", @@ -117,7 +127,7 @@ def issue_status(request_id: str): Response containing issuance status or an error message. """ try: - result = get("/acme/issue/status", {"id": request_id}) + result = get(GET_ACME_ISSUE_STATUS, {"id": request_id}) return _ok(result) except NotFound as exc: logger.info("Issuance request '%s' not found: %s", request_id, exc) @@ -139,7 +149,7 @@ def renew_bp(domain: str): """ try: logger.info("Certificate renewal requested for '%s' via API", domain) - post("/acme/renew", {"domain": domain}) + post(POST_ACME_RENEW, {"domain": domain}) logger.info("Certificate renewed for '%s'", domain) return _ok(None) except BadRequest as exc: @@ -161,7 +171,7 @@ def remove_bp(domain: str): Response confirming removal or an error message. """ try: - delete("/acme/remove", {"domain": domain}) + delete(DELETE_ACME_REMOVE, {"domain": domain}) logger.info("Certificate removed for '%s' via API", domain) return _ok(None) except NotFound as exc: @@ -186,7 +196,7 @@ def set_email_bp(): if not email: return _error("'email' is required", 400) try: - post("/acme/email", {"email": email}) + post(POST_ACME_EMAIL, {"email": email}) logger.info("ACME email set via API: %s", email) return _ok({"email": email}) except BadRequest as exc: diff --git a/webui/api/dhcp.py b/webui/api/dhcp.py index bcc3b3b..f72f548 100644 --- a/webui/api/dhcp.py +++ b/webui/api/dhcp.py @@ -8,6 +8,20 @@ import logging from flask import Blueprint, request from daemon.client import BadRequest, NotFound, delete, get, patch, post +from daemon.iface import ( + DELETE_DNSMASQ_DNS_RECORD_REMOVE, + DELETE_DNSMASQ_RANGES_REMOVE, + DELETE_DNSMASQ_STATIC_LEASE_REMOVE, + GET_DNSMASQ_CONFIG, + GET_DNSMASQ_LEASES, + GET_DNSMASQ_STATUS, + PATCH_DNSMASQ_CONFIG, + POST_DNSMASQ_APPLY, + POST_DNSMASQ_CONFIG, + POST_DNSMASQ_DNS_RECORD_ADD, + POST_DNSMASQ_RANGES_ADD, + POST_DNSMASQ_STATIC_LEASE_ADD, +) from webui.api.common import _error, _ok logger = logging.getLogger(__name__) @@ -27,7 +41,7 @@ def get_config_bp(): JSON response with the config or an error. """ try: - return _ok(get("/dnsmasq/config")) + return _ok(get(GET_DNSMASQ_CONFIG)) except RuntimeError as exc: logger.error("Failed to read DHCP config: %s", exc) return _error(str(exc), 500) @@ -47,7 +61,7 @@ def post_config(): if not isinstance(body, dict): return _error("Request body must be a JSON object", 400) try: - post("/dnsmasq/config", body) + post(POST_DNSMASQ_CONFIG, body) return _ok(None) except BadRequest as exc: logger.info("DHCP config save rejected: %s", exc) @@ -71,7 +85,7 @@ def patch_config(): if not isinstance(body, dict): return _error("Request body must be a JSON object", 400) try: - patch("/dnsmasq/config", body) + patch(PATCH_DNSMASQ_CONFIG, body) return _ok(None) except BadRequest as exc: logger.info("DHCP config patch rejected: %s", exc) @@ -86,7 +100,7 @@ def apply_bp(): """POST /api/dhcp/apply — Apply the current dnsmasq configuration to the running service.""" try: - post("/dnsmasq/apply") + post(POST_DNSMASQ_APPLY) logger.info("dnsmasq config applied via API") return _ok(None) except RuntimeError as exc: @@ -104,7 +118,7 @@ def status_bp(): """GET /api/dhcp/status — Retrieve dnsmasq service status.""" try: - return _ok(get("/dnsmasq/status")) + return _ok(get(GET_DNSMASQ_STATUS)) except RuntimeError as exc: logger.error("Failed to get DHCP status: %s", exc) return _error(str(exc), 500) @@ -134,7 +148,7 @@ def add_range_bp(): return _error("'start' and 'end' are required", 400) try: post( - "/dnsmasq/ranges/add", + POST_DNSMASQ_RANGES_ADD, { "interface": iface or "", "start": start, @@ -170,7 +184,8 @@ def remove_range_bp(): return _error("'start' and 'end' are required", 400) try: delete( - "/dnsmasq/ranges/remove", {"interface": iface, "start": start, "end": end} + DELETE_DNSMASQ_RANGES_REMOVE, + {"interface": iface, "start": start, "end": end}, ) logger.info("DHCP range removed via API: %s-%s", start, end) return _ok(None) @@ -192,7 +207,7 @@ def leases_bp(): """GET /api/dhcp/leases — Retrieve the current DHCP lease table.""" try: - return _ok(get("/dnsmasq/leases")) + return _ok(get(GET_DNSMASQ_LEASES)) except RuntimeError as exc: logger.error("Failed to read lease table: %s", exc) return _error(str(exc), 500) @@ -220,7 +235,9 @@ def add_static_lease_bp(): if not mac or not ip: return _error("'mac' and 'ip' are required", 400) try: - post("/dnsmasq/static-lease/add", {"mac": mac, "ip": ip, "hostname": hostname}) + post( + POST_DNSMASQ_STATIC_LEASE_ADD, {"mac": mac, "ip": ip, "hostname": hostname} + ) logger.info("Static lease added via API: %s -> %s", mac, ip) return _ok({"mac": mac, "ip": ip, "hostname": hostname}) except BadRequest as exc: @@ -242,7 +259,7 @@ def remove_static_lease_bp(mac): JSON response with success status or an error. """ try: - delete("/dnsmasq/static-lease/remove", {"mac": mac}) + delete(DELETE_DNSMASQ_STATIC_LEASE_REMOVE, {"mac": mac}) logger.info("Static lease removed via API: %s", mac) return _ok(None) except NotFound as exc: @@ -276,7 +293,7 @@ def add_dns_record_bp(): return _error("'name' and 'address' are required", 400) try: post( - "/dnsmasq/dns-record/add", + POST_DNSMASQ_DNS_RECORD_ADD, {"name": name, "address": address, "hostname": hostname}, ) logger.info("DNS record added via API: %s -> %s", name, address) @@ -300,7 +317,7 @@ def remove_dns_record_bp(name): JSON response with success status or an error. """ try: - delete("/dnsmasq/dns-record/remove", {"name": name}) + delete(DELETE_DNSMASQ_DNS_RECORD_REMOVE, {"name": name}) logger.info("DNS record removed via API: %s", name) return _ok(None) except NotFound as exc: diff --git a/webui/api/firewall.py b/webui/api/firewall.py index 1adbcba..0754ee7 100644 --- a/webui/api/firewall.py +++ b/webui/api/firewall.py @@ -8,6 +8,27 @@ import logging from flask import Blueprint, request from daemon.client import BadRequest, NotFound, delete, get, patch, post +from daemon.iface import ( + DELETE_FIREWALL_FORWARD_PORT_REMOVE, + DELETE_FIREWALL_RICH_RULES_REMOVE, + DELETE_FIREWALL_ZONES_DELETE, + GET_FIREWALL_CONFIG, + GET_FIREWALL_CONFIG_PENDING, + GET_FIREWALL_INTERFACES, + GET_FIREWALL_RICH_RULES, + GET_FIREWALL_SERVICES, + GET_FIREWALL_ZONES, + GET_FIREWALL_ZONES_INFO, + PATCH_FIREWALL_CONFIG, + POST_FIREWALL_CONFIG, + POST_FIREWALL_CONFIG_APPLY, + POST_FIREWALL_FORWARD_PORT_ADD, + POST_FIREWALL_MASQUERADE, + POST_FIREWALL_RICH_RULES_ADD, + POST_FIREWALL_ZONES_CREATE, + POST_FIREWALL_ZONES_INTERFACES, + POST_FIREWALL_ZONES_SERVICES, +) from webui.api.common import _error, _ok logger = logging.getLogger(__name__) @@ -32,7 +53,7 @@ def config_list(): JSON response with the config data or an error message. """ try: - return _ok(get("/firewall/config")) + return _ok(get(GET_FIREWALL_CONFIG)) except RuntimeError as exc: logger.error("Failed to read firewall config: %s", exc) return _error(str(exc), 500) @@ -60,9 +81,9 @@ def config_save(): if not isinstance(body["zones"], dict): return _error("'zones' must be a dict", 400) try: - post("/firewall/config", body) + post(POST_FIREWALL_CONFIG, body) try: - pending = get("/firewall/config/pending") + pending = get(GET_FIREWALL_CONFIG_PENDING) pending_data = { "pending": pending.get("pending", []), "needs_apply": pending.get("needs_apply", False), @@ -106,9 +127,9 @@ def patch_config(): if not isinstance(body, dict): return _error("Request body must be a JSON object", 400) try: - patch("/firewall/config", body) + patch(PATCH_FIREWALL_CONFIG, body) try: - pending = get("/firewall/config/pending") + pending = get(GET_FIREWALL_CONFIG_PENDING) pending_data = { "pending": pending.get("pending", []), "needs_apply": pending.get("needs_apply", False), @@ -145,7 +166,7 @@ def config_apply_bp(): JSON with ``applied_zones`` list or an error message. """ try: - result = post("/firewall/config/apply") + result = post(POST_FIREWALL_CONFIG_APPLY) logger.info("Firewall config applied: %s", result.get("applied_zones", [])) return _ok(result) except RuntimeError as exc: @@ -167,7 +188,7 @@ def config_pending_bp(): JSON with pending changes and apply status. """ try: - return _ok(get("/firewall/config/pending")) + return _ok(get(GET_FIREWALL_CONFIG_PENDING)) except RuntimeError as exc: logger.error("Failed to check pending config: %s", exc) return _error(str(exc), 500) @@ -189,7 +210,7 @@ def list_zones(): JSON with ``active`` zones dict and ``available`` zones list. """ try: - data = get("/firewall/zones") + data = get(GET_FIREWALL_ZONES) return _ok( {"active": data.get("active", {}), "available": data.get("available", [])} ) @@ -212,7 +233,7 @@ def zone_details(name: str): JSON with zone configuration details or 404 error. """ try: - info = get("/firewall/zones/info", {"zone": name}) + info = get(GET_FIREWALL_ZONES_INFO, {"zone": name}) return _ok(info) except NotFound as exc: logger.info("Zone '%s' not found: %s", name, exc) @@ -241,7 +262,7 @@ def create_zone_bp(): if not zone_name: return _error("Zone name is required", 400) try: - post("/firewall/zones/create", {"name": zone_name, "target": target}) + post(POST_FIREWALL_ZONES_CREATE, {"name": zone_name, "target": target}) logger.info("Zone '%s' created via API", zone_name) return _ok(None) except BadRequest as exc: @@ -266,7 +287,7 @@ def delete_zone_bp(name: str): JSON confirmation or 404 if the zone does not exist. """ try: - delete("/firewall/zones/delete", {"zone": name}) + delete(DELETE_FIREWALL_ZONES_DELETE, {"zone": name}) logger.info("Zone '%s' deleted via API", name) return _ok(None) except NotFound as exc: @@ -303,7 +324,7 @@ def set_zone_interfaces_bp(name: str): if not isinstance(interfaces, list): return _error("'interfaces' must be a list", 400) try: - post("/firewall/zones/interfaces", {"zone": name, "interfaces": interfaces}) + post(POST_FIREWALL_ZONES_INTERFACES, {"zone": name, "interfaces": interfaces}) logger.info("Zone '%s' interfaces updated: %s", name, interfaces) return _ok({"zone": name, "interfaces": interfaces}) except BadRequest as exc: @@ -343,7 +364,7 @@ def set_zone_services_bp(name: str): if not isinstance(services, list): return _error("'services' must be a list", 400) try: - post("/firewall/zones/services", {"zone": name, "services": services}) + post(POST_FIREWALL_ZONES_SERVICES, {"zone": name, "services": services}) return _ok({"zone": name, "services": services}) except BadRequest as exc: logger.info("Set services for zone '%s' rejected: %s", name, exc) @@ -372,7 +393,7 @@ def list_services(): JSON with the list of available service names. """ try: - return _ok(get("/firewall/services")) + return _ok(get(GET_FIREWALL_SERVICES)) except RuntimeError as exc: logger.error("Failed to list services: %s", exc) return _error(str(exc), 500) @@ -389,7 +410,7 @@ def list_interfaces(): JSON with the list of available interface names. """ try: - return _ok(get("/firewall/interfaces")) + return _ok(get(GET_FIREWALL_INTERFACES)) except RuntimeError as exc: logger.error("Failed to list interfaces: %s", exc) return _error(str(exc), 500) @@ -419,7 +440,7 @@ def add_rich_rule_bp(): if not zone or not rule: return _error("Both 'zone' and 'rule' are required", 400) try: - entry = post("/firewall/rich-rules/add", {"zone": zone, "rule": rule}) + entry = post(POST_FIREWALL_RICH_RULES_ADD, {"zone": zone, "rule": rule}) logger.info("Rich rule added to zone '%s': %s", zone, rule[:80]) return _ok({"zone": zone, "id": entry["id"], "rule": rule}) except BadRequest as exc: @@ -444,7 +465,7 @@ def list_rich_rules(zone: str): JSON with list of rich rule entries for the zone. """ try: - return _ok(get("/firewall/rich-rules", {"zone": zone})) + return _ok(get(GET_FIREWALL_RICH_RULES, {"zone": zone})) except RuntimeError as exc: logger.error("Failed to get rich rules for zone '%s': %s", zone, exc) return _error(str(exc), 500) @@ -465,7 +486,7 @@ def remove_rich_rule_bp(zone: str, rule_id: str): JSON confirmation or 404 if the rule does not exist. """ try: - delete("/firewall/rich-rules/remove", {"zone": zone, "id": rule_id}) + delete(DELETE_FIREWALL_RICH_RULES_REMOVE, {"zone": zone, "id": rule_id}) logger.info("Rich rule '%s' removed from zone '%s'", rule_id, zone) return _ok({"zone": zone, "id": rule_id}) except NotFound as exc: @@ -500,7 +521,7 @@ def set_masquerade_bp(): if not zone or enable is None: return _error("'zone' and 'enable' (bool) are required", 400) try: - post("/firewall/masquerade", {"zone": zone, "enable": bool(enable)}) + post(POST_FIREWALL_MASQUERADE, {"zone": zone, "enable": bool(enable)}) logger.info( "Masquerade %s on zone '%s' via API", "enabled" if enable else "disabled", @@ -555,7 +576,7 @@ def add_forward_port_bp(): toaddr_str = str(toaddr) if toaddr else None try: entry = post( - "/firewall/forward-port/add", + POST_FIREWALL_FORWARD_PORT_ADD, { "zone": zone, "port": port_int, @@ -590,7 +611,7 @@ def remove_forward_port_bp(zone: str, port: int, proto: str): """ try: delete( - "/firewall/forward-port/remove", + DELETE_FIREWALL_FORWARD_PORT_REMOVE, {"zone": zone, "port": port, "proto": proto}, ) logger.info("Forward port %s/%s removed from zone '%s'", port, proto, zone) diff --git a/webui/api/logs.py b/webui/api/logs.py index 97b2a02..fb11203 100644 --- a/webui/api/logs.py +++ b/webui/api/logs.py @@ -1,116 +1,72 @@ """Log viewing API blueprint. -Serves log content to the /logs page via HTMX endpoints through vacuum-walld. +Wraps raw log text in the standard JSON response contract. """ import logging -from flask import Blueprint, render_template_string +from flask import Blueprint -from daemon.client import get +from daemon.client import NotFound, get +from daemon.iface import ( + GET_LOGS_APP, + GET_LOGS_DNSMASQ, + GET_LOGS_JOURNAL, + GET_LOGS_NGINX_ACCESS, + GET_LOGS_NGINX_ERROR, +) +from webui.api.common import _error, _ok logger = logging.getLogger(__name__) bp = Blueprint("logs", __name__) -_LOG_LINE_TEMPLATE = """\ -{% for line in lines %} -
{{ line | e }}
-{% endfor %}""" - - -def _render_log_lines(text: str) -> str: - """Render raw log text into styled HTML log-line divs. - - Args: - text: Raw log content with newline-separated lines. - - Returns: - HTML string with color-coded log-line elements. - """ - lines = text.rstrip("\n").split("\n") if text.strip() else [] - return render_template_string(_LOG_LINE_TEMPLATE, lines=lines) - @bp.route("/journal") def journal(): - """GET /api/logs/journal — Return systemd journal log lines. - - Fetches the daemon's journal log content via vacuum-walld and - renders it as styled HTML log-line elements. - - Returns: - HTML string containing rendered journal log lines. - """ + """GET /api/logs/journal — Return systemd journal log lines.""" try: - text = get("/logs/journal") - return _render_log_lines(text) + return _ok(get(GET_LOGS_JOURNAL)) except RuntimeError: - return _render_log_lines("(error reading journal)\n") + return _error("error reading journal", 500) @bp.route("/nginx/access") def nginx_access(): - """GET /api/logs/nginx/access — Return nginx access log lines. - - Fetches the nginx access log content via vacuum-walld and - renders it as styled HTML log-line elements. - - Returns: - HTML string containing rendered access log lines. - """ + """GET /api/logs/nginx/access — Return nginx access log lines.""" try: - text = get("/logs/nginx/access") - return _render_log_lines(text) + return _ok(get(GET_LOGS_NGINX_ACCESS)) + except NotFound: + return _error("log file not found", 404) except RuntimeError: - return _render_log_lines("(log file not found)\n") + return _error("error reading log", 500) @bp.route("/nginx/error") def nginx_error(): - """GET /api/logs/nginx/error — Return nginx error log lines. - - Fetches the nginx error log content via vacuum-walld and - renders it as styled HTML log-line elements. - - Returns: - HTML string containing rendered error log lines. - """ + """GET /api/logs/nginx/error — Return nginx error log lines.""" try: - text = get("/logs/nginx/error") - return _render_log_lines(text) + return _ok(get(GET_LOGS_NGINX_ERROR)) + except NotFound: + return _error("log file not found", 404) except RuntimeError: - return _render_log_lines("(log file not found)\n") + return _error("error reading log", 500) @bp.route("/dnsmasq") def dnsmasq(): - """GET /api/logs/dnsmasq — Return dnsmasq log lines. - - Fetches the dnsmasq log content via vacuum-walld and - renders it as styled HTML log-line elements. - - Returns: - HTML string containing rendered dnsmasq log lines. - """ + """GET /api/logs/dnsmasq — Return dnsmasq log lines.""" try: - text = get("/logs/dnsmasq") - return _render_log_lines(text) + return _ok(get(GET_LOGS_DNSMASQ)) except RuntimeError: - return _render_log_lines("(error reading journal)\n") + return _error("error reading journal", 500) @bp.route("/app") def app_log(): - """GET /api/logs/app — Return application log lines. - - Fetches the application log content via vacuum-walld and - renders it as styled HTML log-line elements. - - Returns: - HTML string containing rendered application log lines. - """ + """GET /api/logs/app — Return application log lines.""" try: - text = get("/logs/app") - return _render_log_lines(text) + return _ok(get(GET_LOGS_APP)) + except NotFound: + return _error("log file not found", 404) except RuntimeError: - return _render_log_lines("(log file not found)\n") + return _error("error reading log", 500) diff --git a/webui/api/network.py b/webui/api/network.py index 68094de..d65c660 100644 --- a/webui/api/network.py +++ b/webui/api/network.py @@ -9,6 +9,16 @@ import logging from flask import Blueprint, request from daemon.client import NotFound, get, post +from daemon.iface import ( + GET_NETWORK_INFER_DHCP_RANGES, + GET_NETWORK_INFER_ZONES, + GET_NETWORK_INTERFACE_NAME, + GET_NETWORK_INTERFACES, + POST_NETWORK_APPLY, + POST_NETWORK_INTERFACE_NAME, + POST_NETWORK_INTERFACE_RELOAD, +) +from lib.common import validate_interface_name from webui.api.common import _error, _ok logger = logging.getLogger(__name__) @@ -26,7 +36,7 @@ def list_interfaces(): JSON with interface config + runtime state. """ try: - return _ok(get("/network/interfaces")) + return _ok(get(GET_NETWORK_INTERFACES)) except RuntimeError as exc: logger.error("Failed to list network interfaces: %s", exc) return _error(str(exc), 500) @@ -43,7 +53,10 @@ def get_interface(name: str): JSON with interface config and runtime state. """ try: - return _ok(get("/network/interfaces/" + name, {"name": name})) + validate_interface_name(name) + return _ok(get(GET_NETWORK_INTERFACE_NAME, {"name": name})) + except ValueError as exc: + return _error(str(exc), 400) except NotFound as exc: logger.info("Interface '%s' not found: %s", name, exc) return _error(str(exc), 404) @@ -65,11 +78,14 @@ def save_interface(name: str): Returns: JSON confirmation. """ - body = request.get_json(silent=True) or {} + body = {**(request.get_json(silent=True) or {}), "name": name} try: - post("/network/interfaces/" + name, body) + validate_interface_name(name) + post(POST_NETWORK_INTERFACE_NAME, body) logger.info("Interface '%s' config saved", name) return _ok({"name": name, "applied": True}) + except ValueError as exc: + return _error(str(exc), 400) except NotFound as exc: return _error(str(exc), 404) except RuntimeError as exc: @@ -88,9 +104,12 @@ def reload_interface(name: str): JSON confirmation. """ try: - post("/network/interfaces/" + name + "/reload", {"name": name}) + validate_interface_name(name) + post(POST_NETWORK_INTERFACE_RELOAD, {"name": name}) logger.info("Interface '%s' reloaded", name) return _ok({"name": name, "reloaded": True}) + except ValueError as exc: + return _error(str(exc), 400) except RuntimeError as exc: logger.error("Failed to reload interface '%s': %s", name, exc) return _error(str(exc), 500) @@ -107,7 +126,7 @@ def apply_all(): JSON with number of interfaces applied. """ try: - result = post("/network/apply", {}) + result = post(POST_NETWORK_APPLY, {}) logger.info("Network config applied: %d interfaces", result.get("applied", 0)) return _ok(result) except RuntimeError as exc: @@ -126,7 +145,7 @@ def infer_dhcp_ranges(): JSON with per-interface suggested DHCP ranges. """ try: - return _ok(get("/network/infer-dhcp-ranges")) + return _ok(get(GET_NETWORK_INFER_DHCP_RANGES)) except RuntimeError as exc: logger.error("Failed to infer DHCP ranges: %s", exc) return _error(str(exc), 500) @@ -143,7 +162,7 @@ def infer_zones(): JSON with per-interface suggested zone names. """ try: - return _ok(get("/network/infer-zones")) + return _ok(get(GET_NETWORK_INFER_ZONES)) except RuntimeError as exc: logger.error("Failed to infer zones: %s", exc) return _error(str(exc), 500) diff --git a/webui/api/proxy.py b/webui/api/proxy.py index 0dec32c..9e2c5ac 100644 --- a/webui/api/proxy.py +++ b/webui/api/proxy.py @@ -8,6 +8,19 @@ import logging from flask import Blueprint, request from daemon.client import BadRequest, NotFound, delete, get, patch, post +from daemon.iface import ( + DELETE_NGINX_DOMAINS_REMOVE, + GET_NGINX_CONFIG, + GET_NGINX_DOMAINS, + PATCH_NGINX_CONFIG, + POST_NGINX_APPLY, + POST_NGINX_CONFIG, + POST_NGINX_DOMAINS_ADD, + POST_NGINX_DOMAINS_UPDATE, + POST_NGINX_MANAGEMENT, + POST_NGINX_SSL_APPLY, + POST_NGINX_TEST, +) from webui.api.common import _error, _ok logger = logging.getLogger(__name__) @@ -27,7 +40,7 @@ def ssl_apply_bp(): RuntimeError: If nginx SSL snippet write fails. """ try: - post("/nginx/ssl-apply") + post(POST_NGINX_SSL_APPLY) logger.info("SSL snippet written via API") return _ok(None) except RuntimeError as exc: @@ -45,7 +58,7 @@ def get_config_bp(): Current config dict from the daemon. """ try: - return _ok(get("/nginx/config")) + return _ok(get(GET_NGINX_CONFIG)) except RuntimeError as exc: logger.error("Failed to read proxy config: %s", exc) return _error(str(exc), 500) @@ -67,7 +80,7 @@ def post_config(): if not isinstance(body, dict): return _error("Request body must be a JSON object", 400) try: - post("/nginx/config", body) + post(POST_NGINX_CONFIG, body) logger.info("Proxy config saved: %s", sorted(body.keys())) return _ok(None) except BadRequest as exc: @@ -94,7 +107,7 @@ def patch_config(): if not isinstance(body, dict): return _error("Request body must be a JSON object", 400) try: - patch("/nginx/config", body) + patch(PATCH_NGINX_CONFIG, body) logger.info("Proxy config patched: %s", sorted(body.keys())) return _ok(None) except BadRequest as exc: @@ -115,7 +128,7 @@ def list_domains(): List of domain dicts from the daemon. """ try: - return _ok(get("/nginx/domains")) + return _ok(get(GET_NGINX_DOMAINS)) except RuntimeError as exc: logger.error("Failed to list proxy domains: %s", exc) return _error(str(exc), 500) @@ -153,7 +166,7 @@ def add_domain_bp(): return _error("'backend_port' is required", 400) try: post( - "/nginx/domains/add", + POST_NGINX_DOMAINS_ADD, { "domain": domain, "backend_host": backend_host, @@ -189,7 +202,7 @@ def update_domain_bp(domain): if not body: return _error("Request body must be a JSON object with fields to update", 400) try: - post("/nginx/domains/update", {"domain": domain, **body}) + post(POST_NGINX_DOMAINS_UPDATE, {"domain": domain, **body}) logger.info("Proxy domain '%s' updated via API", domain) return _ok({"domain": domain}) except BadRequest as exc: @@ -213,7 +226,7 @@ def remove_domain_bp(domain): ``{"domain": ...}`` on success. """ try: - delete("/nginx/domains/remove", {"domain": domain}) + delete(DELETE_NGINX_DOMAINS_REMOVE, {"domain": domain}) logger.info("Proxy domain removed via API: %s", domain) return _ok({"domain": domain}) except NotFound as exc: @@ -234,7 +247,7 @@ def apply_bp(): ``{"ok": true}`` on success. """ try: - post("/nginx/apply") + post(POST_NGINX_APPLY) logger.info("nginx config applied via API") return _ok(None) except RuntimeError as exc: @@ -252,7 +265,7 @@ def test_bp(): ``{"valid": true, "output": ...}`` on success. Returns 400 if test fails. """ try: - result = post("/nginx/test") + result = post(POST_NGINX_TEST) if result.get("valid"): return _ok({"valid": True, "output": result.get("output", "")}) return _error(result.get("output", "unknown error"), 400) @@ -287,7 +300,7 @@ def management_bp(): auth_pass = body.get("auth_pass") try: post( - "/nginx/management", + POST_NGINX_MANAGEMENT, { "domain": domain, "flask_host": flask_host, diff --git a/webui/api/wireguard.py b/webui/api/wireguard.py index 61e83e8..c616190 100644 --- a/webui/api/wireguard.py +++ b/webui/api/wireguard.py @@ -8,6 +8,20 @@ import logging from flask import Blueprint, request from daemon.client import BadRequest, NotFound, delete, get, patch, post +from daemon.iface import ( + DELETE_WIREGUARD_PEERS_REMOVE, + GET_WIREGUARD_CONFIG, + GET_WIREGUARD_PEER_STATUS, + GET_WIREGUARD_PEERS, + GET_WIREGUARD_STATUS, + PATCH_WIREGUARD_CONFIG, + POST_WIREGUARD_APPLY, + POST_WIREGUARD_CONFIG, + POST_WIREGUARD_DOWN, + POST_WIREGUARD_GENERATE_CLIENT, + POST_WIREGUARD_INITIALIZE, + POST_WIREGUARD_PEERS_ADD, +) from webui.api.common import _error, _ok logger = logging.getLogger(__name__) @@ -25,7 +39,7 @@ def get_config_bp(): response on failure. """ try: - return _ok(get("/wireguard/config")) + return _ok(get(GET_WIREGUARD_CONFIG)) except RuntimeError as exc: logger.error("Failed to read WireGuard config: %s", exc) return _error(str(exc), 500) @@ -53,7 +67,7 @@ def post_config(): body = dict(body) body["interface"] = dict(body["interface"]) body["interface"].pop("private_key", None) - post("/wireguard/config", body) + post(POST_WIREGUARD_CONFIG, body) return _ok(None) except BadRequest as exc: logger.info("WireGuard config save rejected: %s", exc) @@ -85,7 +99,7 @@ def patch_config(): body = dict(body) body["interface"] = dict(body["interface"]) body["interface"].pop("private_key", None) - patch("/wireguard/config", body) + patch(PATCH_WIREGUARD_CONFIG, body) logger.info("WireGuard config patched: %s", sorted(body.keys())) return _ok(None) except BadRequest as exc: @@ -106,7 +120,7 @@ def apply_bp(): Success response on acceptance, or 500 on server error. """ try: - post("/wireguard/apply") + post(POST_WIREGUARD_APPLY) logger.info("WireGuard tunnel applied via API") return _ok(None) except RuntimeError as exc: @@ -124,7 +138,7 @@ def up_bp(): Success response on acceptance, or 500 on server error. """ try: - post("/wireguard/apply") + post(POST_WIREGUARD_APPLY) logger.info("WireGuard tunnel started via API") return _ok(None) except RuntimeError as exc: @@ -142,7 +156,7 @@ def down_bp(): Success response on acceptance, or 500 on server error. """ try: - post("/wireguard/down") + post(POST_WIREGUARD_DOWN) logger.info("WireGuard tunnel brought down via API") return _ok(None) except RuntimeError as exc: @@ -161,7 +175,7 @@ def status_bp(): response on failure. """ try: - return _ok(get("/wireguard/status")) + return _ok(get(GET_WIREGUARD_STATUS)) except RuntimeError as exc: logger.error("Failed to get WireGuard status: %s", exc) return _error(str(exc), 500) @@ -177,7 +191,7 @@ def initialize_bp(): Success response on acceptance, or 500 on server error. """ try: - post("/wireguard/initialize") + post(POST_WIREGUARD_INITIALIZE) logger.info("WireGuard initialized via API") return _ok(None) except RuntimeError as exc: @@ -208,7 +222,7 @@ def add_peer_bp(): return _error("'name' is required", 400) try: peer = post( - "/wireguard/peers/add", + POST_WIREGUARD_PEERS_ADD, { "name": name, "endpoint": body.get("endpoint"), @@ -241,7 +255,7 @@ def remove_peer_bp(name): or 500 on server error. """ try: - delete("/wireguard/peers/remove", {"name": name}) + delete(DELETE_WIREGUARD_PEERS_REMOVE, {"name": name}) logger.info("WireGuard peer '%s' removed via API", name) return _ok({"name": name}) except NotFound as exc: @@ -263,7 +277,7 @@ def peers_bp(): on failure. """ try: - return _ok(get("/wireguard/peers")) + return _ok(get(GET_WIREGUARD_PEERS)) except RuntimeError as exc: logger.error("Failed to list WireGuard peers: %s", exc) return _error(str(exc), 500) @@ -280,7 +294,7 @@ def peer_status_bp(): on failure. """ try: - return _ok(get("/wireguard/peer-status")) + return _ok(get(GET_WIREGUARD_PEER_STATUS)) except RuntimeError as exc: logger.error("Failed to get WireGuard peer status: %s", exc) return _error(str(exc), 500) @@ -309,7 +323,7 @@ def generate_client_bp(): return _error("Field 'server_endpoint' is required", 400) try: result = post( - "/wireguard/generate-client", + POST_WIREGUARD_GENERATE_CLIENT, { "name": name, "server_endpoint": server_endpoint,