diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index d228f68..bb3e412 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -5,7 +5,6 @@ from __future__ import annotations import asyncio import base64 import hashlib -import io import json import os import re @@ -317,7 +316,6 @@ async def api_config(): @app.get("/api/services") async def api_services(): cfg = load_config() - method = cfg.get("command_method", "systemctl") services = cfg.get("services", []) loop = asyncio.get_event_loop() @@ -382,56 +380,6 @@ async def api_credentials(unit: str): } -def _get_allowed_units() -> set[str]: - cfg = load_config() - return {s.get("unit", "") for s in cfg.get("services", []) if s.get("unit")} - - -@app.post("/api/services/{unit}/start") -async def service_start(unit: str): - if unit not in _get_allowed_units(): - raise HTTPException(status_code=403, detail=f"Unit {unit!r} is not in the allowed service list") - cfg = load_config() - method = cfg.get("command_method", "systemctl") - loop = asyncio.get_event_loop() - ok = await loop.run_in_executor( - None, lambda: sysctl.run_action("start", unit, "system", method) - ) - if not ok: - raise HTTPException(status_code=500, detail=f"Failed to start {unit}") - return {"ok": True} - - -@app.post("/api/services/{unit}/stop") -async def service_stop(unit: str): - if unit not in _get_allowed_units(): - raise HTTPException(status_code=403, detail=f"Unit {unit!r} is not in the allowed service list") - cfg = load_config() - method = cfg.get("command_method", "systemctl") - loop = asyncio.get_event_loop() - ok = await loop.run_in_executor( - None, lambda: sysctl.run_action("stop", unit, "system", method) - ) - if not ok: - raise HTTPException(status_code=500, detail=f"Failed to stop {unit}") - return {"ok": True} - - -@app.post("/api/services/{unit}/restart") -async def service_restart(unit: str): - if unit not in _get_allowed_units(): - raise HTTPException(status_code=403, detail=f"Unit {unit!r} is not in the allowed service list") - cfg = load_config() - method = cfg.get("command_method", "systemctl") - loop = asyncio.get_event_loop() - ok = await loop.run_in_executor( - None, lambda: sysctl.run_action("restart", unit, "system", method) - ) - if not ok: - raise HTTPException(status_code=500, detail=f"Failed to restart {unit}") - return {"ok": True} - - @app.get("/api/network") async def api_network(): loop = asyncio.get_event_loop() diff --git a/app/sovran_systemsos_web/static/app.js b/app/sovran_systemsos_web/static/app.js index 616cef6..7cce369 100644 --- a/app/sovran_systemsos_web/static/app.js +++ b/app/sovran_systemsos_web/static/app.js @@ -1,9 +1,9 @@ -/* Sovran_SystemsOS Hub — Vanilla JS Frontend */ +/* Sovran_SystemsOS Hub — Vanilla JS Frontend + v6 — Status-only dashboard (no start/stop/restart controls) */ "use strict"; const POLL_INTERVAL_SERVICES = 5000; // 5 s const POLL_INTERVAL_UPDATES = 1800000; // 30 min -const ACTION_REFRESH_DELAY = 1500; // 1.5 s after start/stop/restart const UPDATE_POLL_INTERVAL = 2000; // 2 s while update is running const REBOOT_CHECK_INTERVAL = 5000; // 5 s between reconnect attempts @@ -153,8 +153,7 @@ function buildTile(svc) { const sc = statusClass(svc.status); const st = statusText(svc.status, svc.enabled); const dis = !svc.enabled; - const isOn = svc.status === "active"; - const hasCreds = svc.has_credentials; + const hasCreds = svc.has_credentials && svc.enabled; const tile = document.createElement("div"); tile.className = "service-tile" + (dis ? " disabled" : ""); @@ -162,7 +161,7 @@ function buildTile(svc) { tile.dataset.tileId = tileId(svc); if (dis) tile.title = `${svc.name} is not enabled in custom.nix`; - // Info button (only if service has credentials) + // Info button (only if service has credentials and is enabled) const infoBtn = hasCreds ? `` : ""; @@ -179,16 +178,6 @@ function buildTile(svc) { ${escHtml(st)} -
-
- - -
`; // Info button click handler @@ -200,29 +189,6 @@ function buildTile(svc) { }); } - const chk = tile.querySelector(".tile-toggle"); - if (!dis) { - chk.addEventListener("change", async (e) => { - const action = e.target.checked ? "start" : "stop"; - chk.disabled = true; - try { - await apiFetch(`/api/services/${encodeURIComponent(svc.unit)}/${action}`, { method: "POST" }); - } catch (_) {} - setTimeout(() => refreshServices(), ACTION_REFRESH_DELAY); - }); - } - - const restartBtn = tile.querySelector(".tile-restart-btn"); - if (!dis) { - restartBtn.addEventListener("click", async () => { - restartBtn.disabled = true; - try { - await apiFetch(`/api/services/${encodeURIComponent(svc.unit)}/restart`, { method: "POST" }); - } catch (_) {} - setTimeout(() => refreshServices(), ACTION_REFRESH_DELAY); - }); - } - return tile; } @@ -241,13 +207,9 @@ function updateTiles(services) { const dot = tile.querySelector(".status-dot"); const text = tile.querySelector(".status-text"); - const chk = tile.querySelector(".tile-toggle"); if (dot) { dot.className = `status-dot ${sc}`; } if (text) { text.textContent = st; } - if (chk && !chk.disabled) { - chk.checked = svc.status === "active"; - } } } @@ -269,7 +231,7 @@ async function refreshServices() { } } -// ── Network IPs ──────────────────────────────────��──────────────── +// ── Network IPs ─────────────────────────────────────────────────── async function loadNetwork() { try { diff --git a/app/sovran_systemsos_web/static/style.css b/app/sovran_systemsos_web/static/style.css index e9cf6d9..a2dbec6 100644 --- a/app/sovran_systemsos_web/static/style.css +++ b/app/sovran_systemsos_web/static/style.css @@ -1,6 +1,6 @@ /* Sovran_SystemsOS Hub — Web UI Stylesheet Dark theme matching the Adwaita dark aesthetic - v5 — QR code support in credentials modal */ + v6 — Status-only tiles (no controls) */ *, *::before, *::after { box-sizing: border-box; @@ -222,11 +222,11 @@ button:disabled { gap: 14px; } -/* ── Service tile card ──────────────────────────────────────────── */ +/* ── Service tile card (status-only) ─────────────────────────────── */ .service-tile { - width: 180px; - min-height: 210px; + width: 160px; + min-height: 150px; background-color: var(--card-color); border: 1px solid var(--border-color); border-radius: var(--radius-card); @@ -234,7 +234,8 @@ button:disabled { display: flex; flex-direction: column; align-items: center; - padding: 18px 12px 14px; + justify-content: center; + padding: 20px 12px 18px; gap: 0; transition: box-shadow 0.2s, border-color 0.2s; position: relative; @@ -279,7 +280,7 @@ button:disabled { width: 48px; height: 48px; object-fit: contain; - margin-bottom: 8px; + margin-bottom: 10px; } .tile-icon-fallback { @@ -292,7 +293,7 @@ button:disabled { border-radius: 12px; color: var(--text-dim); font-size: 1.5rem; - margin-bottom: 8px; + margin-bottom: 10px; } .tile-name { @@ -301,10 +302,10 @@ button:disabled { text-align: center; color: var(--text-primary); line-height: 1.3; - max-width: 156px; + max-width: 140px; word-break: break-word; hyphens: auto; - min-height: 2.6em; + min-height: 1.3em; display: flex; align-items: center; justify-content: center; @@ -312,7 +313,7 @@ button:disabled { .tile-status { font-size: 0.75rem; - margin-top: 6px; + margin-top: 8px; display: flex; align-items: center; gap: 5px; @@ -333,86 +334,7 @@ button:disabled { .status-dot.failed { background-color: var(--red); } .status-dot.disabled { background-color: var(--grey); } -.tile-spacer { - flex: 1; -} - -/* ── Tile controls ──────────────────────────────────────────────── */ - -.tile-controls { - display: flex; - align-items: center; - gap: 10px; - margin-top: 10px; -} - -.toggle-label { - display: flex; - align-items: center; - cursor: pointer; -} - -.toggle-label input[type="checkbox"] { - display: none; -} - -.toggle-track { - width: 40px; - height: 22px; - background-color: var(--border-color); - border-radius: 11px; - position: relative; - transition: background-color 0.2s; -} - -.toggle-label input:checked + .toggle-track { - background-color: var(--green); -} - -.toggle-label.disabled-toggle { - cursor: not-allowed; - opacity: 0.5; -} - -.toggle-thumb { - position: absolute; - top: 3px; - left: 3px; - width: 16px; - height: 16px; - background-color: #fff; - border-radius: 50%; - transition: transform 0.2s; - box-shadow: 0 1px 3px rgba(0,0,0,0.4); -} - -.toggle-label input:checked + .toggle-track .toggle-thumb { - transform: translateX(18px); -} - -.tile-restart-btn { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - padding: 4px 6px; - border-radius: 50%; - font-size: 0.95rem; - line-height: 1; - transition: background-color 0.15s, color 0.15s; -} - -.tile-restart-btn:hover:not(:disabled) { - background-color: var(--border-color); - color: var(--text-primary); -} - -.tile-restart-btn:disabled { - opacity: 0.35; - cursor: default; -} - -/* ── Update modal ───────────────────────────────────────────────── */ +/* ── Update modal ─────────────────────────────────���─────────────── */ .modal-overlay { display: none; @@ -530,7 +452,7 @@ button.btn-reboot:hover:not(:disabled) { background-color: #5a5c72; } -/* ── Credentials info modal ──────────────────────────────────────��─ */ +/* ── Credentials info modal ──────────────────────────────────────── */ .creds-dialog { background-color: var(--surface-color); @@ -680,16 +602,6 @@ button.btn-reboot:hover:not(:disabled) { margin-bottom: 10px; } -/* ── QR code in credentials modal ────────────────────────────────── */ - -.creds-qr-wrap { - display: flex; - flex-direction: column; - align-items: center; - padding: 20px 0; - margin-bottom: 10px; -} - .creds-qr-img { width: 240px; height: 240px; @@ -830,8 +742,8 @@ button.btn-reboot:hover:not(:disabled) { justify-content: center; } .service-tile { - width: 160px; - min-height: 200px; + width: 140px; + min-height: 130px; } .reboot-card { padding: 36px 28px; @@ -844,4 +756,4 @@ button.btn-reboot:hover:not(:disabled) { width: 200px; height: 200px; } -} +} \ No newline at end of file