Adds operations/services.py: a fixed ManagedService allow-list (act-runner, act-runner-rust, vmware-host, ci-web) with find_service/service_states/control_service. control_service builds an argument list only (never shell=True), validates the action against ALLOWED_ACTIONS (ValueError->400), refuses stop on restart-only services (ci-web, allow_stop=False) without building a command, and defers ci-web's own restart via 'sudo -n systemd-run --on-active=2 systemctl restart ci-web.service' so the HTTP response returns first.
Route POST /services/{service_id}/{action} in web/routes.py mirrors vms_remove: require_user + require_csrf + two-step confirm gate + one audit entry per stage; unknown id / bad action / disallowed-stop short-circuit to 400 before any subprocess or audit; the vmware-host confirm step warns with the active-VM count. New /services read partial with state-aware Start/Stop/Restart buttons, polled every 15s.
deploy/sudoers.d/ci-web grants ci-runner exact-command NOPASSWD for exactly these nine systemctl lines plus the systemd-run deferral — no wildcards, no bare systemctl, no ci-web stop; setup-web-linux.sh installs it mode 0440 and validates with 'visudo -cf', rolling back on failure. Config gains [web].managed_services override; docs/WEB-UI.md documents the panel + security model.
Gates: 1044 tests pass (46 new security/unit tests), coverage 94.20%, ruff + mypy --strict clean. Not yet deployed: sudoers install + prod-venv reinstall + ci-web restart are a human privilege-boundary step.
19 lines
529 B
HTML
19 lines
529 B
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - ci-orchestrator{% endblock %}
|
|
|
|
{% block content %}
|
|
{{ super() }}
|
|
{% include "partials/status_header.html" %}
|
|
<div class="dashboard-grid">
|
|
{% include "partials/vms.html" %}
|
|
{% include "partials/cache.html" %}
|
|
{% include "partials/host.html" %}
|
|
{% include "partials/resources.html" %}
|
|
{% include "partials/vmware.html" %}
|
|
{% include "partials/templates.html" %}
|
|
{% include "partials/files.html" %}
|
|
{% include "partials/services.html" %}
|
|
</div>
|
|
{% endblock %}
|