169 lines
4.7 KiB
Python
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)
|