Compare commits
3 Commits
b21d9bef87
...
6e133b6b59
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e133b6b59 | ||
|
|
01e3e02a62 | ||
|
|
85af70e2ee |
@@ -398,6 +398,27 @@ SERVICE_DESCRIPTIONS: dict[str, str] = {
|
|||||||
"Your system account credentials. These are the keys to your Sovran_SystemsOS machine — "
|
"Your system account credentials. These are the keys to your Sovran_SystemsOS machine — "
|
||||||
"root access, user accounts, and SSH passphrases. Keep them safe."
|
"root access, user accounts, and SSH passphrases. Keep them safe."
|
||||||
),
|
),
|
||||||
|
"sparrow-autoconnect.service": (
|
||||||
|
"Sparrow Wallet is a privacy-focused Bitcoin desktop wallet for sending, receiving, "
|
||||||
|
"and managing your Bitcoin. Sovran_SystemsOS automatically connects it to your local "
|
||||||
|
"Electrs server on first boot — your address lookups, balances, and transactions "
|
||||||
|
"never touch a third-party server. Full privacy, zero configuration."
|
||||||
|
),
|
||||||
|
"bisq-autoconnect.service": (
|
||||||
|
"Bisq is a decentralized, peer-to-peer Bitcoin exchange — buy and sell Bitcoin "
|
||||||
|
"with no KYC and no middleman. Sovran_SystemsOS automatically connects it to your "
|
||||||
|
"local Bitcoin node on first boot, routing all traffic through Tor. Your trades are "
|
||||||
|
"verified by your own node, keeping you fully sovereign."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVICE_DESKTOP_LINKS: dict[str, list[dict[str, str]]] = {
|
||||||
|
"sparrow-autoconnect.service": [
|
||||||
|
{"label": "Open Sparrow Wallet", "desktop_file": "sparrow-desktop.desktop"},
|
||||||
|
],
|
||||||
|
"bisq-autoconnect.service": [
|
||||||
|
{"label": "Open Bisq", "desktop_file": "Bisq.desktop"},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── App setup ────────────────────────────────────────────────────
|
# ── App setup ────────────────────────────────────────────────────
|
||||||
@@ -2151,9 +2172,36 @@ async def api_service_detail(unit: str, icon: str | None = None):
|
|||||||
btc_ver = _format_bitcoin_version(raw_ver, icon=icon)
|
btc_ver = _format_bitcoin_version(raw_ver, icon=icon)
|
||||||
service_detail["bitcoin_version"] = btc_ver # backwards compat
|
service_detail["bitcoin_version"] = btc_ver # backwards compat
|
||||||
service_detail["version"] = btc_ver
|
service_detail["version"] = btc_ver
|
||||||
|
desktop_links = SERVICE_DESKTOP_LINKS.get(unit, [])
|
||||||
|
if desktop_links:
|
||||||
|
service_detail["desktop_links"] = desktop_links
|
||||||
return service_detail
|
return service_detail
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/desktop/launch/{desktop_file}")
|
||||||
|
async def api_desktop_launch(desktop_file: str):
|
||||||
|
"""Launch a desktop application via gtk-launch on the local GNOME session."""
|
||||||
|
import re as _re
|
||||||
|
if not _re.match(r'^[a-zA-Z0-9_.-]+\.desktop$', desktop_file):
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid desktop file name")
|
||||||
|
|
||||||
|
try:
|
||||||
|
env = dict(os.environ)
|
||||||
|
env["DISPLAY"] = ":0"
|
||||||
|
result = subprocess.run(
|
||||||
|
["gtk-launch", desktop_file],
|
||||||
|
capture_output=True, text=True, timeout=10, env=env,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to launch: {result.stderr.strip()}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise HTTPException(status_code=500, detail="gtk-launch not found on this system")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
raise HTTPException(status_code=500, detail="Launch command timed out")
|
||||||
|
|
||||||
|
return {"ok": True, "launched": desktop_file}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/network")
|
@app.get("/api/network")
|
||||||
async def api_network():
|
async def api_network():
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|||||||
@@ -343,3 +343,18 @@
|
|||||||
color: var(--yellow);
|
color: var(--yellow);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Desktop launch buttons ──────────────────────────────────────── */
|
||||||
|
|
||||||
|
.svc-detail-launch-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svc-detail-launch-btn {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 8px 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,21 @@ async function openServiceDetailModal(unit, name, icon) {
|
|||||||
'</div>';
|
'</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Section: Desktop Launch (only for services with desktop apps)
|
||||||
|
if (data.desktop_links && data.desktop_links.length > 0) {
|
||||||
|
var launchBtns = '';
|
||||||
|
data.desktop_links.forEach(function(link) {
|
||||||
|
launchBtns += '<button class="btn btn-primary svc-detail-launch-btn" data-desktop="' + escHtml(link.desktop_file) + '">' +
|
||||||
|
'🖥️ ' + escHtml(link.label) +
|
||||||
|
'</button>';
|
||||||
|
});
|
||||||
|
html += '<div class="svc-detail-section">' +
|
||||||
|
'<div class="svc-detail-section-title">Open on Desktop</div>' +
|
||||||
|
'<p class="svc-detail-desc" style="margin-bottom:12px">If you are accessing this machine locally on the GNOME desktop, click below to launch the app directly.</p>' +
|
||||||
|
'<div class="svc-detail-launch-row">' + launchBtns + '</div>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// Section B: Status
|
// Section B: Status
|
||||||
// When a feature override is present, use the feature's enabled state so the
|
// When a feature override is present, use the feature's enabled state so the
|
||||||
// modal matches what the dashboard tile shows (feature toggle is authoritative).
|
// modal matches what the dashboard tile shows (feature toggle is authoritative).
|
||||||
@@ -312,6 +327,25 @@ async function openServiceDetailModal(unit, name, icon) {
|
|||||||
$credsBody.innerHTML = html;
|
$credsBody.innerHTML = html;
|
||||||
_attachCopyHandlers($credsBody);
|
_attachCopyHandlers($credsBody);
|
||||||
|
|
||||||
|
// Desktop launch button handlers
|
||||||
|
$credsBody.querySelectorAll(".svc-detail-launch-btn").forEach(function(btn) {
|
||||||
|
btn.addEventListener("click", async function() {
|
||||||
|
var desktopFile = btn.dataset.desktop;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = "Launching…";
|
||||||
|
try {
|
||||||
|
await apiFetch("/api/desktop/launch/" + encodeURIComponent(desktopFile), {
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
btn.textContent = "✓ Launched!";
|
||||||
|
setTimeout(function() { btn.textContent = "🖥️ " + btn.textContent; btn.disabled = false; }, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
btn.textContent = "❌ Failed";
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (unit === "matrix-synapse.service") {
|
if (unit === "matrix-synapse.service") {
|
||||||
var addBtn = document.getElementById("matrix-add-user-btn");
|
var addBtn = document.getElementById("matrix-add-user-btn");
|
||||||
var changePwBtn = document.getElementById("matrix-change-pw-btn");
|
var changePwBtn = document.getElementById("matrix-change-pw-btn");
|
||||||
|
|||||||
Reference in New Issue
Block a user