diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 09e8b88..7b1b842 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -2963,6 +2963,35 @@ async def api_ping(): return {"ok": True} +@app.post("/api/service/{unit}/restart") +async def api_service_restart(unit: str): + cfg = load_config() + services = cfg.get("services", []) + allowed_units = { + str(s.get("unit", "")).strip() + for s in services + if s.get("unit") + } + if unit not in allowed_units: + raise HTTPException(status_code=404, detail="Service not found") + + try: + proc = await asyncio.create_subprocess_exec( + "systemctl", "restart", unit, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + _, stderr = await proc.communicate() + except Exception as exc: + raise HTTPException(status_code=500, detail=f"Failed to restart service: {exc}") + + if proc.returncode != 0: + detail = stderr.decode(errors="ignore").strip() or "systemctl restart failed" + raise HTTPException(status_code=500, detail=detail) + + return {"ok": True} + + @app.post("/api/reboot") async def api_reboot(): try: diff --git a/app/sovran_systemsos_web/static/css/tiles.css b/app/sovran_systemsos_web/static/css/tiles.css index efc8d77..32f2278 100644 --- a/app/sovran_systemsos_web/static/css/tiles.css +++ b/app/sovran_systemsos_web/static/css/tiles.css @@ -353,6 +353,47 @@ font-weight: 600; } +.btn-warning { + background-color: #d97706; + color: #fff; +} + +.btn-warning:hover:not(:disabled) { + background-color: #b45309; +} + +.svc-detail-restart-section { + border-top: 1px solid var(--border-color); + padding-top: 16px; +} + +.svc-detail-restart-btn { + margin-top: 8px; +} + +.svc-detail-restart-result { + margin-top: 12px; + padding: 12px 16px; + border-radius: 8px; + font-size: 0.88rem; + line-height: 1.5; + display: none; +} + +.svc-detail-restart-result.success { + background-color: rgba(109, 191, 139, 0.12); + border: 1px solid var(--green); + color: var(--green); + display: block; +} + +.svc-detail-restart-result.error { + background-color: rgba(239, 68, 68, 0.12); + border: 1px solid #ef4444; + color: #f87171; + display: block; +} + /* ── Desktop launch buttons ──────────────────────────────────────── */ diff --git a/app/sovran_systemsos_web/static/js/service-detail.js b/app/sovran_systemsos_web/static/js/service-detail.js index 4d8effc..0320556 100644 --- a/app/sovran_systemsos_web/static/js/service-detail.js +++ b/app/sovran_systemsos_web/static/js/service-detail.js @@ -270,6 +270,15 @@ async function openServiceDetailModal(unit, name, icon) { ''; } + if (effectiveEnabled || data.enabled) { + html += '
' + + '
Troubleshooting
' + + '

If you\'re experiencing issues with this service, try restarting it.

' + + '' + + '
' + + '
'; + } + $credsBody.innerHTML = html; _attachCopyHandlers($credsBody); @@ -296,6 +305,33 @@ async function openServiceDetailModal(unit, name, icon) { } } + var restartBtn = document.getElementById("svc-detail-restart-btn"); + var restartResult = document.getElementById("svc-detail-restart-result"); + if (restartBtn && restartResult) { + restartBtn.addEventListener("click", async function() { + restartBtn.disabled = true; + restartBtn.textContent = "Restarting…"; + restartResult.className = "svc-detail-restart-result"; + restartResult.textContent = ""; + + try { + await apiFetch("/api/service/" + encodeURIComponent(unit) + "/restart", { method: "POST" }); + restartResult.classList.add("success"); + restartResult.textContent = "✅ Service restarted successfully."; + restartBtn.disabled = false; + restartBtn.textContent = "🔄 Restart Service"; + setTimeout(function() { + openServiceDetailModal(unit, name, icon); + }, 3000); + } catch (e) { + restartResult.classList.add("error"); + restartResult.textContent = e && e.message ? e.message : "Failed to restart service."; + restartBtn.disabled = false; + restartBtn.textContent = "🔄 Restart Service"; + } + }); + } + // Configure / Reconfigure Domain buttons (for non-feature services that need a domain) var configDomainBtn = document.getElementById("svc-detail-config-domain-btn"); var reconfigDomainBtn = document.getElementById("svc-detail-reconfig-domain-btn");