From 435a2ed5b2b65b6fe5703ee067a081468cad0867 Mon Sep 17 00:00:00 2001 From: naturallaw77 Date: Tue, 31 Mar 2026 14:45:05 -0500 Subject: [PATCH] new visulation in Hub --- app/sovran_systemsos_hub/application.py | 113 +----------------- app/sovran_systemsos_hub/service_tile.py | 21 +++- app/style.css | 3 +- modules/core/sovran-hub.nix | 141 ++++------------------- 4 files changed, 45 insertions(+), 233 deletions(-) diff --git a/app/sovran_systemsos_hub/application.py b/app/sovran_systemsos_hub/application.py index 42e04a2..c140335 100644 --- a/app/sovran_systemsos_hub/application.py +++ b/app/sovran_systemsos_hub/application.py @@ -1,87 +1,3 @@ -"""Sovran_SystemsOS_Hub — Main GTK4 Application.""" - -from __future__ import annotations - -import os - -import gi - -gi.require_version("Gtk", "4.0") -gi.require_version("Adw", "1") - -from gi.repository import Adw, Gdk, Gio, GLib, Gtk - -from .config import load_config -from .service_tile import ServiceTile - -APP_ID = "com.sovransystems.hub" - -# Initialize libadwaita BEFORE any widget creation -Adw.init() - - -class SovranHubWindow(Adw.ApplicationWindow): - - def __init__(self, app, config): - super().__init__( - application=app, - title="Sovran_SystemsOS Hub", - default_width=680, - default_height=700, - ) - self._config = config - self._tiles = [] - - css_path = os.environ.get("SOVRAN_HUB_CSS", "") - if css_path and os.path.isfile(css_path): - provider = Gtk.CssProvider() - provider.load_from_path(css_path) - Gtk.StyleContext.add_provider_for_display( - Gdk.Display.get_default(), provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, - ) - - header = Adw.HeaderBar() - refresh_btn = Gtk.Button( - icon_name="view-refresh-symbolic", - tooltip_text="Refresh now", - ) - refresh_btn.connect("clicked", lambda _b: self._refresh_all()) - header.pack_end(refresh_btn) - - self._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=16, - margin_bottom=16, - margin_start=16, - margin_end=16, - halign=Gtk.Align.CENTER, - valign=Gtk.Align.START, - ) - - scrolled = Gtk.ScrolledWindow( - hscrollbar_policy=Gtk.PolicyType.NEVER, - vscrollbar_policy=Gtk.PolicyType.AUTOMATIC, - vexpand=True, - child=self._flowbox, - ) - - toolbar_view = Adw.ToolbarView() - toolbar_view.add_top_bar(header) - toolbar_view.set_content(scrolled) - self.set_content(toolbar_view) - - self._build_tiles() - - interval = config.get("refresh_interval", 5) - if interval and interval > 0: - GLib.timeout_add_seconds(interval, self._auto_refresh) - def _build_tiles(self): method = self._config.get("command_method", "systemctl") for entry in self._config.get("services", []): @@ -91,34 +7,9 @@ class SovranHubWindow(Adw.ApplicationWindow): scope=entry.get("type", "system"), method=method, icon_name=entry.get("icon", ""), + enabled=entry.get("enabled", True), ) self._flowbox.append(tile) self._tiles.append(tile) - # Defer first status poll so the window renders immediately - GLib.idle_add(self._refresh_all) - - def _refresh_all(self): - for t in self._tiles: - t.refresh() - return False - - def _auto_refresh(self): - self._refresh_all() - return True - - -class SovranHubApp(Adw.Application): - - def __init__(self): - super().__init__( - application_id=APP_ID, - flags=Gio.ApplicationFlags.DEFAULT_FLAGS, - ) - self._config = load_config() - - def do_activate(self): - win = self.get_active_window() - if not win: - win = SovranHubWindow(self, self._config) - win.present() \ No newline at end of file + GLib.idle_add(self._refresh_all) \ No newline at end of file diff --git a/app/sovran_systemsos_hub/service_tile.py b/app/sovran_systemsos_hub/service_tile.py index 5966f03..e32eca8 100644 --- a/app/sovran_systemsos_hub/service_tile.py +++ b/app/sovran_systemsos_hub/service_tile.py @@ -22,7 +22,8 @@ ICON_EXTENSIONS = [".svg", ".png"] class ServiceTile(Gtk.Box): - def __init__(self, name, unit, scope="system", method="systemctl", icon_name="", **kw): + def __init__(self, name, unit, scope="system", method="systemctl", + icon_name="", enabled=True, **kw): super().__init__( orientation=Gtk.Orientation.VERTICAL, spacing=6, @@ -37,6 +38,7 @@ class ServiceTile(Gtk.Box): self._unit = unit self._scope = scope self._method = method + self._enabled = enabled self._logo = Gtk.Image(pixel_size=48, margin_top=12, halign=Gtk.Align.CENTER) self._set_logo(icon_name) @@ -70,7 +72,14 @@ class ServiceTile(Gtk.Box): controls.append(restart_btn) self.append(controls) - # No self.refresh() here — the application calls it via GLib.idle_add + # If the feature is disabled in custom.nix, lock the tile immediately + if not self._enabled: + self._switch.set_active(False) + self._switch.set_sensitive(False) + self._status_label.set_label("○ disabled") + self._status_label.set_css_classes(["caption", "disabled-label"]) + self._logo.set_opacity(0.35) + self.set_tooltip_text(f"{name} is not enabled in custom.nix") def _set_logo(self, icon_name): if icon_name and ICON_DIR: @@ -87,6 +96,10 @@ class ServiceTile(Gtk.Box): self._logo.set_from_icon_name("system-run-symbolic") def refresh(self): + # Don't poll systemctl for disabled features + if not self._enabled: + return + active = systemctl.is_active(self._unit, self._scope) is_on = active == "active" is_loading = active in LOADING_STATES @@ -111,10 +124,14 @@ class ServiceTile(Gtk.Box): self._status_label.set_css_classes(["caption", "dim-label"]) def _on_toggled(self, switch, state): + if not self._enabled: + return True # block the toggle systemctl.run_action("start" if state else "stop", self._unit, self._scope, self._method) GLib.timeout_add(1500, self.refresh) return False def _on_restart(self, _btn): + if not self._enabled: + return systemctl.run_action("restart", self._unit, self._scope, self._method) GLib.timeout_add(1500, self.refresh) \ No newline at end of file diff --git a/app/style.css b/app/style.css index 1daa90c..d958fd9 100644 --- a/app/style.css +++ b/app/style.css @@ -9,4 +9,5 @@ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); } .success { color: #2ec27e; } -.warning { color: #e5a50a; } \ No newline at end of file +.warning { color: #e5a50a; } +.disabled-label { color: #888888; font-style: italic; } \ No newline at end of file diff --git a/modules/core/sovran-hub.nix b/modules/core/sovran-hub.nix index 57d2bb6..fc222f7 100644 --- a/modules/core/sovran-hub.nix +++ b/modules/core/sovran-hub.nix @@ -1,124 +1,27 @@ -{ config, lib, pkgs, ... }: - -let - cfg = config.sovran_systemsOS; - monitoredServices = + # ── Always-on infrastructure ─────────────────────────────── [ - { name = "Caddy"; unit = "caddy.service"; type = "system"; icon = "caddy"; } - { name = "Tor"; unit = "tor.service"; type = "system"; icon = "tor"; } + { name = "Caddy"; unit = "caddy.service"; type = "system"; icon = "caddy"; enabled = true; } + { name = "Tor"; unit = "tor.service"; type = "system"; icon = "tor"; enabled = true; } ] - ++ lib.optionals cfg.services.bitcoin [ - { name = "Bitcoind"; unit = "bitcoind.service"; type = "system"; icon = "bitcoind"; } - { name = "Electrs"; unit = "electrs.service"; type = "system"; icon = "electrs"; } - { name = "LND"; unit = "lnd.service"; type = "system"; icon = "lnd"; } - { name = "Ride The Lightning"; unit = "rtl.service"; type = "system"; icon = "rtl"; } - { name = "BTCPayserver"; unit = "btcpayserver.service"; type = "system"; icon = "btcpayserver"; } + # ── Bitcoin ecosystem ────────────────────────────────────── + ++ [ + { name = "Bitcoind"; unit = "bitcoind.service"; type = "system"; icon = "bitcoind"; enabled = cfg.services.bitcoin; } + { name = "Electrs"; unit = "electrs.service"; type = "system"; icon = "electrs"; enabled = cfg.services.bitcoin; } + { name = "LND"; unit = "lnd.service"; type = "system"; icon = "lnd"; enabled = cfg.services.bitcoin; } + { name = "Ride The Lightning"; unit = "rtl.service"; type = "system"; icon = "rtl"; enabled = cfg.services.bitcoin; } + { name = "BTCPayserver"; unit = "btcpayserver.service"; type = "system"; icon = "btcpayserver"; enabled = cfg.services.bitcoin; } ] - ++ lib.optionals cfg.services.synapse [ - { name = "Matrix-Synapse"; unit = "matrix-synapse.service"; type = "system"; icon = "synapse"; } + # ── Other services ───────────────────────────────────────── + ++ [ + { name = "Matrix-Synapse"; unit = "matrix-synapse.service"; type = "system"; icon = "synapse"; enabled = cfg.services.synapse; } + { name = "VaultWarden"; unit = "vaultwarden.service"; type = "system"; icon = "vaultwarden"; enabled = cfg.services.vaultwarden; } + { 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; } ] - ++ lib.optionals cfg.services.vaultwarden [ - { name = "VaultWarden"; unit = "vaultwarden.service"; type = "system"; icon = "vaultwarden"; } - ] - ++ lib.optionals cfg.services.nextcloud [ - { name = "Nextcloud"; unit = "phpfpm-nextcloud.service"; type = "system"; icon = "nextcloud"; } - ] - ++ lib.optionals cfg.services.wordpress [ - { name = "WordPress"; unit = "phpfpm-wordpress.service"; type = "system"; icon = "wordpress"; } - ] - ++ lib.optionals cfg.features.haven [ - { name = "Haven Relay"; unit = "haven-relay.service"; type = "system"; icon = "haven"; } - ] - ++ lib.optionals cfg.features.mempool [ - { name = "Mempool"; unit = "mempool.service"; type = "system"; icon = "mempool"; } - ] - ++ lib.optionals cfg.features.element-calling [ - { name = "Element-Call"; unit = "livekit.service"; type = "system"; icon = "livekit"; } - ]; - - generatedConfig = pkgs.writeText "sovran-hub-config.json" - (builtins.toJSON { - refresh_interval = 5; - command_method = "systemctl"; - services = monitoredServices; - }); - - sovran-hub = pkgs.python3Packages.buildPythonApplication { - pname = "sovran-systemsos-hub"; - version = "1.0.0"; - format = "other"; - - src = ../../app; - - nativeBuildInputs = with pkgs; [ - wrapGAppsHook4 - gobject-introspection - ]; - - buildInputs = with pkgs; [ - gtk4 - libadwaita - gdk-pixbuf - librsvg - ]; - - propagatedBuildInputs = with pkgs.python3Packages; [ - pygobject3 - ]; - - dontBuild = true; - - installPhase = '' - runHook preInstall - - install -d $out/lib/sovran-hub - cp -r sovran_systemsos_hub $out/lib/sovran-hub/ - cp style.css $out/lib/sovran-hub/style.css - cp ${generatedConfig} $out/lib/sovran-hub/config.json - - install -d $out/share/sovran-hub/icons - cp icons/* $out/share/sovran-hub/icons/ 2>/dev/null || true - - install -d $out/bin - cat > $out/bin/sovran-hub < $out/share/applications/Sovran_SystemsOS_Hub.desktop <