updated layout in hub
This commit is contained in:
@@ -19,6 +19,21 @@ APP_ID = "com.sovransystems.hub"
|
|||||||
# Initialize libadwaita BEFORE any widget creation
|
# Initialize libadwaita BEFORE any widget creation
|
||||||
Adw.init()
|
Adw.init()
|
||||||
|
|
||||||
|
# Category display order and labels
|
||||||
|
CATEGORY_ORDER = [
|
||||||
|
("infrastructure", "Infrastructure"),
|
||||||
|
("bitcoin", "Bitcoin"),
|
||||||
|
("communication", "Communication"),
|
||||||
|
("apps", "Self-Hosted Apps"),
|
||||||
|
("nostr", "Nostr"),
|
||||||
|
]
|
||||||
|
|
||||||
|
ROLE_LABELS = {
|
||||||
|
"server_plus_desktop": "Server + Desktop",
|
||||||
|
"desktop": "Desktop Only",
|
||||||
|
"node": "Bitcoin Node",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SovranHubWindow(Adw.ApplicationWindow):
|
class SovranHubWindow(Adw.ApplicationWindow):
|
||||||
|
|
||||||
@@ -27,7 +42,7 @@ class SovranHubWindow(Adw.ApplicationWindow):
|
|||||||
application=app,
|
application=app,
|
||||||
title="Sovran_SystemsOS Hub",
|
title="Sovran_SystemsOS Hub",
|
||||||
default_width=680,
|
default_width=680,
|
||||||
default_height=700,
|
default_height=780,
|
||||||
)
|
)
|
||||||
self._config = config
|
self._config = config
|
||||||
self._tiles = []
|
self._tiles = []
|
||||||
@@ -42,6 +57,18 @@ class SovranHubWindow(Adw.ApplicationWindow):
|
|||||||
)
|
)
|
||||||
|
|
||||||
header = Adw.HeaderBar()
|
header = Adw.HeaderBar()
|
||||||
|
|
||||||
|
# Show active role in header
|
||||||
|
role = config.get("role", "server_plus_desktop")
|
||||||
|
role_label = ROLE_LABELS.get(role, role)
|
||||||
|
role_tag = Gtk.Label(
|
||||||
|
label=role_label,
|
||||||
|
css_classes=["caption", "role-badge"],
|
||||||
|
)
|
||||||
|
header.set_title_widget(
|
||||||
|
self._build_title_box(role_label)
|
||||||
|
)
|
||||||
|
|
||||||
refresh_btn = Gtk.Button(
|
refresh_btn = Gtk.Button(
|
||||||
icon_name="view-refresh-symbolic",
|
icon_name="view-refresh-symbolic",
|
||||||
tooltip_text="Refresh now",
|
tooltip_text="Refresh now",
|
||||||
@@ -49,26 +76,17 @@ class SovranHubWindow(Adw.ApplicationWindow):
|
|||||||
refresh_btn.connect("clicked", lambda _b: self._refresh_all())
|
refresh_btn.connect("clicked", lambda _b: self._refresh_all())
|
||||||
header.pack_end(refresh_btn)
|
header.pack_end(refresh_btn)
|
||||||
|
|
||||||
self._flowbox = Gtk.FlowBox(
|
# Main vertical layout
|
||||||
max_children_per_line=4,
|
self._main_box = Gtk.Box(
|
||||||
min_children_per_line=2,
|
orientation=Gtk.Orientation.VERTICAL,
|
||||||
selection_mode=Gtk.SelectionMode.NONE,
|
spacing=0,
|
||||||
homogeneous=True,
|
|
||||||
row_spacing=12,
|
|
||||||
column_spacing=12,
|
|
||||||
margin_top=16,
|
|
||||||
margin_bottom=16,
|
|
||||||
margin_start=16,
|
|
||||||
margin_end=16,
|
|
||||||
halign=Gtk.Align.CENTER,
|
|
||||||
valign=Gtk.Align.START,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
scrolled = Gtk.ScrolledWindow(
|
scrolled = Gtk.ScrolledWindow(
|
||||||
hscrollbar_policy=Gtk.PolicyType.NEVER,
|
hscrollbar_policy=Gtk.PolicyType.NEVER,
|
||||||
vscrollbar_policy=Gtk.PolicyType.AUTOMATIC,
|
vscrollbar_policy=Gtk.PolicyType.AUTOMATIC,
|
||||||
vexpand=True,
|
vexpand=True,
|
||||||
child=self._flowbox,
|
child=self._main_box,
|
||||||
)
|
)
|
||||||
|
|
||||||
toolbar_view = Adw.ToolbarView()
|
toolbar_view = Adw.ToolbarView()
|
||||||
@@ -82,19 +100,85 @@ class SovranHubWindow(Adw.ApplicationWindow):
|
|||||||
if interval and interval > 0:
|
if interval and interval > 0:
|
||||||
GLib.timeout_add_seconds(interval, self._auto_refresh)
|
GLib.timeout_add_seconds(interval, self._auto_refresh)
|
||||||
|
|
||||||
|
def _build_title_box(self, role_label):
|
||||||
|
box = Gtk.Box(
|
||||||
|
orientation=Gtk.Orientation.VERTICAL,
|
||||||
|
halign=Gtk.Align.CENTER,
|
||||||
|
)
|
||||||
|
box.append(Gtk.Label(
|
||||||
|
label="Sovran_SystemsOS Hub",
|
||||||
|
css_classes=["title"],
|
||||||
|
))
|
||||||
|
box.append(Gtk.Label(
|
||||||
|
label=role_label,
|
||||||
|
css_classes=["caption", "dim-label"],
|
||||||
|
))
|
||||||
|
return box
|
||||||
|
|
||||||
def _build_tiles(self):
|
def _build_tiles(self):
|
||||||
method = self._config.get("command_method", "systemctl")
|
method = self._config.get("command_method", "systemctl")
|
||||||
for entry in self._config.get("services", []):
|
services = self._config.get("services", [])
|
||||||
tile = ServiceTile(
|
|
||||||
name=entry.get("name", entry["unit"]),
|
# Group services by category
|
||||||
unit=entry["unit"],
|
grouped = {}
|
||||||
scope=entry.get("type", "system"),
|
for entry in services:
|
||||||
method=method,
|
cat = entry.get("category", "other")
|
||||||
icon_name=entry.get("icon", ""),
|
grouped.setdefault(cat, []).append(entry)
|
||||||
enabled=entry.get("enabled", True),
|
|
||||||
|
for cat_key, cat_label in CATEGORY_ORDER:
|
||||||
|
entries = grouped.get(cat_key, [])
|
||||||
|
if not entries:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Section header
|
||||||
|
section_label = Gtk.Label(
|
||||||
|
label=cat_label,
|
||||||
|
css_classes=["title-4"],
|
||||||
|
halign=Gtk.Align.START,
|
||||||
|
margin_top=20,
|
||||||
|
margin_bottom=4,
|
||||||
|
margin_start=24,
|
||||||
)
|
)
|
||||||
self._flowbox.append(tile)
|
self._main_box.append(section_label)
|
||||||
self._tiles.append(tile)
|
|
||||||
|
# Separator
|
||||||
|
sep = Gtk.Separator(
|
||||||
|
orientation=Gtk.Orientation.HORIZONTAL,
|
||||||
|
margin_start=24,
|
||||||
|
margin_end=24,
|
||||||
|
margin_bottom=8,
|
||||||
|
)
|
||||||
|
self._main_box.append(sep)
|
||||||
|
|
||||||
|
# FlowBox for this category
|
||||||
|
flowbox = Gtk.FlowBox(
|
||||||
|
max_children_per_line=4,
|
||||||
|
min_children_per_line=2,
|
||||||
|
selection_mode=Gtk.SelectionMode.NONE,
|
||||||
|
homogeneous=True,
|
||||||
|
row_spacing=12,
|
||||||
|
column_spacing=12,
|
||||||
|
margin_top=4,
|
||||||
|
margin_bottom=8,
|
||||||
|
margin_start=16,
|
||||||
|
margin_end=16,
|
||||||
|
halign=Gtk.Align.CENTER,
|
||||||
|
valign=Gtk.Align.START,
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
tile = ServiceTile(
|
||||||
|
name=entry.get("name", entry["unit"]),
|
||||||
|
unit=entry["unit"],
|
||||||
|
scope=entry.get("type", "system"),
|
||||||
|
method=method,
|
||||||
|
icon_name=entry.get("icon", ""),
|
||||||
|
enabled=entry.get("enabled", True),
|
||||||
|
)
|
||||||
|
flowbox.append(tile)
|
||||||
|
self._tiles.append(tile)
|
||||||
|
|
||||||
|
self._main_box.append(flowbox)
|
||||||
|
|
||||||
# Defer first status poll so the window renders immediately
|
# Defer first status poll so the window renders immediately
|
||||||
GLib.idle_add(self._refresh_all)
|
GLib.idle_add(self._refresh_all)
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
.sovran-tile:hover {
|
.sovran-tile:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
.success { color: #2ec27e; }
|
.success { color: #2ec27e; }
|
||||||
.warning { color: #e5a50a; }
|
.warning { color: #e5a50a; }
|
||||||
|
.error { color: #e01b24; }
|
||||||
.disabled-label { color: #888888; font-style: italic; }
|
.disabled-label { color: #888888; font-style: italic; }
|
||||||
|
.role-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
@@ -3,38 +3,54 @@
|
|||||||
let
|
let
|
||||||
cfg = config.sovran_systemsOS;
|
cfg = config.sovran_systemsOS;
|
||||||
|
|
||||||
|
# ── Determine Bitcoin implementation label ───────────────────
|
||||||
|
bitcoinImplName =
|
||||||
|
if cfg.features.bitcoin-core then "Bitcoin Core"
|
||||||
|
else if cfg.features.bip110 then "Bitcoin Knots + BIP110"
|
||||||
|
else "Bitcoin Knots";
|
||||||
|
|
||||||
monitoredServices =
|
monitoredServices =
|
||||||
# ── Always-on infrastructure ───────────────────────────────
|
# ── Infrastructure (always present) ────────────────────────
|
||||||
[
|
[
|
||||||
{ name = "Caddy"; unit = "caddy.service"; type = "system"; icon = "caddy"; enabled = true; }
|
{ name = "Caddy"; unit = "caddy.service"; type = "system"; icon = "caddy"; enabled = true; category = "infrastructure"; }
|
||||||
{ name = "Tor"; unit = "tor.service"; type = "system"; icon = "tor"; enabled = true; }
|
{ name = "Tor"; unit = "tor.service"; type = "system"; icon = "tor"; enabled = true; category = "infrastructure"; }
|
||||||
]
|
]
|
||||||
# ── Bitcoin ecosystem ──────────────────────────────────────
|
# ── Bitcoin Ecosystem ──────────────────────────────────────
|
||||||
++ [
|
++ [
|
||||||
{ name = "Bitcoind"; unit = "bitcoind.service"; type = "system"; icon = "bitcoind"; enabled = cfg.services.bitcoin; }
|
{ name = bitcoinImplName; unit = "bitcoind.service"; type = "system"; icon = "bitcoind"; enabled = cfg.services.bitcoin; category = "bitcoin"; }
|
||||||
{ name = "Electrs"; unit = "electrs.service"; type = "system"; icon = "electrs"; enabled = cfg.services.bitcoin; }
|
{ name = "Electrs"; unit = "electrs.service"; type = "system"; icon = "electrs"; enabled = cfg.services.bitcoin; category = "bitcoin"; }
|
||||||
{ name = "LND"; unit = "lnd.service"; type = "system"; icon = "lnd"; enabled = cfg.services.bitcoin; }
|
{ name = "LND"; unit = "lnd.service"; type = "system"; icon = "lnd"; enabled = cfg.services.bitcoin; category = "bitcoin"; }
|
||||||
{ name = "Ride The Lightning"; unit = "rtl.service"; type = "system"; icon = "rtl"; enabled = cfg.services.bitcoin; }
|
{ name = "Ride The Lightning"; unit = "rtl.service"; type = "system"; icon = "rtl"; enabled = cfg.services.bitcoin; category = "bitcoin"; }
|
||||||
{ name = "BTCPayserver"; unit = "btcpayserver.service"; type = "system"; icon = "btcpayserver"; enabled = cfg.services.bitcoin; }
|
{ name = "BTCPayserver"; unit = "btcpayserver.service"; type = "system"; icon = "btcpayserver"; enabled = cfg.services.bitcoin; category = "bitcoin"; }
|
||||||
|
{ name = "Mempool"; unit = "mempool.service"; type = "system"; icon = "mempool"; enabled = cfg.features.mempool; category = "bitcoin"; }
|
||||||
]
|
]
|
||||||
# ── Other services ─────────────────────────────────────────
|
# ── Communication ──────────────────────────────────────────
|
||||||
++ [
|
++ [
|
||||||
{ name = "Matrix-Synapse"; unit = "matrix-synapse.service"; type = "system"; icon = "synapse"; enabled = cfg.services.synapse; }
|
{ name = "Matrix-Synapse"; unit = "matrix-synapse.service"; type = "system"; icon = "synapse"; enabled = cfg.services.synapse; category = "communication"; }
|
||||||
{ name = "VaultWarden"; unit = "vaultwarden.service"; type = "system"; icon = "vaultwarden"; enabled = cfg.services.vaultwarden; }
|
{ name = "Element-Call"; unit = "livekit.service"; type = "system"; icon = "livekit"; enabled = cfg.features.element-calling; category = "communication"; }
|
||||||
{ name = "Nextcloud"; unit = "phpfpm-nextcloud.service"; type = "system"; icon = "nextcloud"; enabled = cfg.services.nextcloud; }
|
|
||||||
{ name = "WordPress"; unit = "phpfpm-wordpress.service"; type = "system"; icon = "wordpress"; enabled = cfg.services.wordpress; }
|
|
||||||
]
|
]
|
||||||
# ── Optional features ──────────────────────────────────────
|
# ── Self-Hosted Apps ───────────────────────────────────────
|
||||||
++ [
|
++ [
|
||||||
{ name = "Haven Relay"; unit = "haven-relay.service"; type = "system"; icon = "haven"; enabled = cfg.features.haven; }
|
{ name = "VaultWarden"; unit = "vaultwarden.service"; type = "system"; icon = "vaultwarden"; enabled = cfg.services.vaultwarden; category = "apps"; }
|
||||||
{ name = "Mempool"; unit = "mempool.service"; type = "system"; icon = "mempool"; enabled = cfg.features.mempool; }
|
{ name = "Nextcloud"; unit = "phpfpm-nextcloud.service"; type = "system"; icon = "nextcloud"; enabled = cfg.services.nextcloud; category = "apps"; }
|
||||||
{ name = "Element-Call"; unit = "livekit.service"; type = "system"; icon = "livekit"; enabled = cfg.features.element-calling; }
|
{ name = "WordPress"; unit = "phpfpm-wordpress.service"; type = "system"; icon = "wordpress"; enabled = cfg.services.wordpress; category = "apps"; }
|
||||||
|
]
|
||||||
|
# ── Nostr / Relay ──────────────────────────────────────────
|
||||||
|
++ [
|
||||||
|
{ name = "Haven Relay"; unit = "haven-relay.service"; type = "system"; icon = "haven"; enabled = cfg.features.haven; category = "nostr"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# ── Determine active role name ───────────────────────────────
|
||||||
|
activeRole =
|
||||||
|
if cfg.roles.desktop then "desktop"
|
||||||
|
else if cfg.roles.node then "node"
|
||||||
|
else "server_plus_desktop";
|
||||||
|
|
||||||
generatedConfig = pkgs.writeText "sovran-hub-config.json"
|
generatedConfig = pkgs.writeText "sovran-hub-config.json"
|
||||||
(builtins.toJSON {
|
(builtins.toJSON {
|
||||||
refresh_interval = 5;
|
refresh_interval = 5;
|
||||||
command_method = "systemctl";
|
command_method = "systemctl";
|
||||||
|
role = activeRole;
|
||||||
services = monitoredServices;
|
services = monitoredServices;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,7 +82,7 @@ let
|
|||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
|
|
||||||
# ── Python source ───────────────────────────────<EFBFBD><EFBFBD><EFBFBD>─────────
|
# ── Python source ─────────────────────────────────────────
|
||||||
install -d $out/lib/sovran-hub
|
install -d $out/lib/sovran-hub
|
||||||
cp -r sovran_systemsos_hub $out/lib/sovran-hub/
|
cp -r sovran_systemsos_hub $out/lib/sovran-hub/
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user