Files
vacuum-wall/webui/api/network.py
T

169 lines
4.7 KiB
Python

"""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 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__)
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(GET_NETWORK_INTERFACES))
except RuntimeError as exc:
logger.error("Failed to list network interfaces: %s", exc)
return _error(str(exc), 500)
@bp.route("/interfaces/<name>", methods=["GET"])
def get_interface(name: str):
"""Get config + runtime state for a specific interface.
Endpoint:
GET /api/network/interfaces/<name>
Returns:
JSON with interface config and runtime state.
"""
try:
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)
except RuntimeError as exc:
logger.error("Failed to get interface '%s': %s", name, exc)
return _error(str(exc), 500)
@bp.route("/interfaces/<name>", methods=["POST"])
def save_interface(name: str):
"""Save and apply network config for an interface.
Endpoint:
POST /api/network/interfaces/<name>
Args:
body: JSON with addresses, gateway, dns, routes.
Returns:
JSON confirmation.
"""
body = {**(request.get_json(silent=True) or {}), "name": name}
try:
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:
logger.error("Failed to save interface '%s': %s", name, exc)
return _error(str(exc), 500)
@bp.route("/interfaces/<name>/reload", methods=["POST"])
def reload_interface(name: str):
"""Reload networkd for a single interface.
Endpoint:
POST /api/network/interfaces/<name>/reload
Returns:
JSON confirmation.
"""
try:
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)
@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(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(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(GET_NETWORK_INFER_ZONES))
except RuntimeError as exc:
logger.error("Failed to infer zones: %s", exc)
return _error(str(exc), 500)