"""Network management API blueprint. Exposes /api/network/* and delegates to vacuum-walld for interface IP configuration via systemd-networkd. """ import logging from flask import Blueprint, request from daemon.client import NotFound, get, post from webui.api.common import _error, _ok logger = logging.getLogger(__name__) bp = Blueprint("network", __name__) @bp.route("/interfaces", methods=["GET"]) def list_interfaces(): """List all interfaces with their network config and runtime state. Endpoint: GET /api/network/interfaces Returns: JSON with interface config + runtime state. """ try: return _ok(get("/network/interfaces")) except RuntimeError as exc: logger.error("Failed to list network interfaces: %s", exc) return _error(str(exc), 500) @bp.route("/interfaces/", methods=["GET"]) def get_interface(name: str): """Get config + runtime state for a specific interface. Endpoint: GET /api/network/interfaces/ Returns: JSON with interface config and runtime state. """ try: return _ok(get("/network/interfaces/" + name, {"name": name})) except NotFound as exc: logger.info("Interface '%s' not found: %s", name, exc) return _error(str(exc), 404) except RuntimeError as exc: logger.error("Failed to get interface '%s': %s", name, exc) return _error(str(exc), 500) @bp.route("/interfaces/", methods=["POST"]) def save_interface(name: str): """Save and apply network config for an interface. Endpoint: POST /api/network/interfaces/ Args: body: JSON with addresses, gateway, dns, routes. Returns: JSON confirmation. """ body = request.get_json(silent=True) or {} try: post("/network/interfaces/" + name, body) logger.info("Interface '%s' config saved", name) return _ok({"name": name, "applied": True}) except NotFound as exc: return _error(str(exc), 404) except RuntimeError as exc: logger.error("Failed to save interface '%s': %s", name, exc) return _error(str(exc), 500) @bp.route("/interfaces//reload", methods=["POST"]) def reload_interface(name: str): """Reload networkd for a single interface. Endpoint: POST /api/network/interfaces//reload Returns: JSON confirmation. """ try: post("/network/interfaces/" + name + "/reload", {"name": name}) logger.info("Interface '%s' reloaded", name) return _ok({"name": name, "reloaded": True}) except RuntimeError as exc: logger.error("Failed to reload interface '%s': %s", name, exc) return _error(str(exc), 500) @bp.route("/apply", methods=["POST"]) def apply_all(): """Apply network config for ALL interfaces (full sync). Endpoint: POST /api/network/apply Returns: JSON with number of interfaces applied. """ try: result = post("/network/apply", {}) logger.info("Network config applied: %d interfaces", result.get("applied", 0)) return _ok(result) except RuntimeError as exc: logger.error("Failed to apply network config: %s", exc) return _error(str(exc), 500) @bp.route("/infer-dhcp-ranges", methods=["GET"]) def infer_dhcp_ranges(): """Suggest candidate DHCP ranges based on static interface IPs. Endpoint: GET /api/network/infer-dhcp-ranges Returns: JSON with per-interface suggested DHCP ranges. """ try: return _ok(get("/network/infer-dhcp-ranges")) except RuntimeError as exc: logger.error("Failed to infer DHCP ranges: %s", exc) return _error(str(exc), 500) @bp.route("/infer-zones", methods=["GET"]) def infer_zones(): """Suggest firewalld zone assignments for configured interfaces. Endpoint: GET /api/network/infer-zones Returns: JSON with per-interface suggested zone names. """ try: return _ok(get("/network/infer-zones")) except RuntimeError as exc: logger.error("Failed to infer zones: %s", exc) return _error(str(exc), 500)