55309cfd86
- lib.common.revert_to_applied(): restore a config file from its
_last_applied_config snapshot (stamped hash); no baseline -> skip with
reason, file untouched
- firewall config_apply now stamps the applied baseline like the other
subsystems; GET /firewall/config and the state collector strip the
internal _last_applied_* keys
- POST /status/cancel-all + /api/status/cancel-all: revert pending
subsystems, {cancelled, skipped, errors}, partial-failure safe
- dashboard: "Cancel All Changes" button with confirm modal
(CancelConfirm, reuses the pending-changes modal rows); the pending
changes card is hidden entirely when nothing is pending
- tests: revert_to_applied, status_cancel_all, firewall stamping/meta
stripping, /api/status/cancel-all route, node tests for CancelConfirm;
firewall _config_apply tests no longer write the real repo config
- docs: api.md, state-model.md, config.md, hoover.md
110 lines
2.8 KiB
Python
110 lines
2.8 KiB
Python
"""Aggregate status API blueprint.
|
|
|
|
Exposed at /api/status/* and delegates all operations to vacuum-walld.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from flask import Blueprint, request
|
|
|
|
from daemon.client import get, post
|
|
from daemon.iface import (
|
|
GET_STATUS_PENDING,
|
|
GET_SYSTEM_METRICS,
|
|
POST_STATUS_APPLY_ALL,
|
|
POST_STATUS_CANCEL_ALL,
|
|
POST_STATUS_REFRESH,
|
|
)
|
|
from webui.api.common import _error, _ok
|
|
|
|
logger = logging.getLogger(__name__)
|
|
bp = Blueprint("status", __name__)
|
|
|
|
|
|
@bp.route("/pending", methods=["GET"])
|
|
def pending():
|
|
"""Retrieve aggregate pending changes across all subsystems.
|
|
|
|
Endpoint:
|
|
GET /api/status/pending
|
|
|
|
Returns:
|
|
JSON response with per-subsystem pending status and total change count.
|
|
"""
|
|
try:
|
|
return _ok(get(GET_STATUS_PENDING))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to get pending status: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/apply-all", methods=["POST"])
|
|
def apply_all():
|
|
"""Apply pending changes for all subsystems in dependency order.
|
|
|
|
Endpoint:
|
|
POST /api/status/apply-all
|
|
|
|
Returns:
|
|
JSON response with applied subsystems list and any errors encountered.
|
|
"""
|
|
try:
|
|
return _ok(post(POST_STATUS_APPLY_ALL))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to apply all pending changes: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/cancel-all", methods=["POST"])
|
|
def cancel_all():
|
|
"""Revert pending changes for all subsystems to the last applied config.
|
|
|
|
Endpoint:
|
|
POST /api/status/cancel-all
|
|
|
|
Returns:
|
|
JSON response with the reverted subsystems, skipped subsystems
|
|
(label -> reason), and any errors encountered.
|
|
"""
|
|
try:
|
|
return _ok(post(POST_STATUS_CANCEL_ALL))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to cancel all pending changes: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/refresh", methods=["POST"])
|
|
def refresh():
|
|
"""Re-collect state from the daemon, optionally filtered by subsystem.
|
|
|
|
Endpoint:
|
|
POST /api/status/refresh
|
|
Body:
|
|
{"subsystems": ["firewall"]} or {} for all.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
try:
|
|
return _ok(post(POST_STATUS_REFRESH, body))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to refresh state: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/system-metrics", methods=["GET"])
|
|
def system_metrics():
|
|
"""Retrieve system-wide metrics.
|
|
|
|
Endpoint:
|
|
GET /api/status/system-metrics
|
|
|
|
Returns:
|
|
JSON response with CPU load, memory usage, and network traffic stats.
|
|
"""
|
|
try:
|
|
return _ok(get(GET_SYSTEM_METRICS))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to get system metrics: %s", exc)
|
|
return _error(str(exc), 500)
|