Bigger update modal, error report to Downloads, reboot button

This commit is contained in:
2026-03-31 17:03:52 -05:00
parent 68c24f6ec1
commit d93f5b9eda

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
import os import os
import subprocess import subprocess
import threading import threading
from datetime import datetime
import gi import gi
@@ -42,12 +43,20 @@ AUTOSTART_DIR = os.path.join(
USER_AUTOSTART_FILE = os.path.join(AUTOSTART_DIR, "sovran-hub.desktop") USER_AUTOSTART_FILE = os.path.join(AUTOSTART_DIR, "sovran-hub.desktop")
SYSTEM_AUTOSTART_FILE = "/etc/xdg/autostart/sovran-hub.desktop" SYSTEM_AUTOSTART_FILE = "/etc/xdg/autostart/sovran-hub.desktop"
DOWNLOADS_DIR = os.path.join(os.path.expanduser("~"), "Downloads")
UPDATE_COMMAND = [ UPDATE_COMMAND = [
"ssh", "-o", "StrictHostKeyChecking=no", "-o", "BatchMode=yes", "ssh", "-o", "StrictHostKeyChecking=no", "-o", "BatchMode=yes",
"root@localhost", "root@localhost",
"cd /etc/nixos && nix flake update && nixos-rebuild switch && flatpak update -y", "cd /etc/nixos && nix flake update && nixos-rebuild switch && flatpak update -y",
] ]
REBOOT_COMMAND = [
"ssh", "-o", "StrictHostKeyChecking=no", "-o", "BatchMode=yes",
"root@localhost",
"reboot",
]
def get_autostart_enabled() -> bool: def get_autostart_enabled() -> bool:
if os.path.isfile(USER_AUTOSTART_FILE): if os.path.isfile(USER_AUTOSTART_FILE):
@@ -85,20 +94,42 @@ class UpdateDialog(Adw.Window):
def __init__(self, parent): def __init__(self, parent):
super().__init__( super().__init__(
title="Sovran_SystemsOS Update", title="Sovran_SystemsOS Update",
default_width=700, default_width=900,
default_height=500, default_height=700,
modal=True, modal=True,
transient_for=parent, transient_for=parent,
) )
self._process = None self._process = None
self._full_log = ""
header = Adw.HeaderBar() header = Adw.HeaderBar()
# ── Close button (disabled during update) ────────────────
self._close_btn = Gtk.Button(label="Close", sensitive=False) self._close_btn = Gtk.Button(label="Close", sensitive=False)
self._close_btn.connect("clicked", lambda _b: self.close()) self._close_btn.connect("clicked", lambda _b: self.close())
header.pack_end(self._close_btn) header.pack_end(self._close_btn)
# ── Reboot button (hidden until update succeeds) ─────────
self._reboot_btn = Gtk.Button(
label="Reboot",
css_classes=["destructive-action"],
tooltip_text="Reboot the system now",
visible=False,
)
self._reboot_btn.connect("clicked", self._on_reboot_clicked)
header.pack_end(self._reboot_btn)
# ── Save error report button (hidden until failure) ──────
self._save_btn = Gtk.Button(
label="Save Error Report",
css_classes=["warning"],
tooltip_text="Save full log to ~/Downloads",
visible=False,
)
self._save_btn.connect("clicked", self._on_save_report)
header.pack_start(self._save_btn)
self._spinner = Gtk.Spinner(spinning=True) self._spinner = Gtk.Spinner(spinning=True)
header.pack_start(self._spinner) header.pack_start(self._spinner)
@@ -106,8 +137,8 @@ class UpdateDialog(Adw.Window):
label="Updating…", label="Updating…",
css_classes=["title-4"], css_classes=["title-4"],
halign=Gtk.Align.CENTER, halign=Gtk.Align.CENTER,
margin_top=8, margin_top=12,
margin_bottom=4, margin_bottom=8,
) )
self._textview = Gtk.TextView( self._textview = Gtk.TextView(
@@ -144,15 +175,18 @@ class UpdateDialog(Adw.Window):
self._start_update() self._start_update()
def _append_text(self, text): def _append_text(self, text):
self._full_log += text
end_iter = self._buffer.get_end_iter() end_iter = self._buffer.get_end_iter()
self._buffer.insert(end_iter, text) self._buffer.insert(end_iter, text)
# Auto-scroll to bottom
mark = self._buffer.create_mark(None, self._buffer.get_end_iter(), False) mark = self._buffer.create_mark(None, self._buffer.get_end_iter(), False)
self._textview.scroll_mark_onscreen(mark) self._textview.scroll_mark_onscreen(mark)
self._buffer.delete_mark(mark) self._buffer.delete_mark(mark)
def _start_update(self): def _start_update(self):
self._append_text("$ ssh root@localhost 'cd /etc/nixos && nix flake update && nixos-rebuild switch && flatpak update -y'\n\n") self._append_text(
"$ ssh root@localhost 'cd /etc/nixos && nix flake update "
"&& nixos-rebuild switch && flatpak update -y'\n\n"
)
thread = threading.Thread(target=self._run_update, daemon=True) thread = threading.Thread(target=self._run_update, daemon=True)
thread.start() thread.start()
@@ -187,11 +221,51 @@ class UpdateDialog(Adw.Window):
if success: if success:
self._status_label.set_label("" + message) self._status_label.set_label("" + message)
self._status_label.set_css_classes(["title-4", "success"]) self._status_label.set_css_classes(["title-4", "success"])
self._reboot_btn.set_visible(True)
else: else:
self._status_label.set_label("" + message) self._status_label.set_label("" + message)
self._status_label.set_css_classes(["title-4", "error"]) self._status_label.set_css_classes(["title-4", "error"])
self._save_btn.set_visible(True)
self._append_text(f"\n{'' * 50}\n{message}\n") self._append_text(f"\n{'' * 60}\n{message}\n")
def _on_save_report(self, _btn):
os.makedirs(DOWNLOADS_DIR, exist_ok=True)
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"sovran-update-error-{timestamp}.log"
filepath = os.path.join(DOWNLOADS_DIR, filename)
try:
with open(filepath, "w") as f:
f.write(f"Sovran_SystemsOS Update Error Report\n")
f.write(f"Date: {datetime.now().isoformat()}\n")
f.write(f"{'' * 60}\n\n")
f.write(self._full_log)
self._save_btn.set_label(f"Saved: {filename}")
self._save_btn.set_sensitive(False)
self._append_text(f"\n✓ Error report saved to ~/Downloads/{filename}\n")
except Exception as e:
self._append_text(f"\n✗ Failed to save report: {e}\n")
def _on_reboot_clicked(self, _btn):
dialog = Adw.MessageDialog(
transient_for=self,
heading="Reboot Now?",
body="The system will restart immediately. Save any open work first.",
)
dialog.add_response("cancel", "Cancel")
dialog.add_response("reboot", "Reboot")
dialog.set_response_appearance("reboot", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_default_response("cancel")
dialog.set_close_response("cancel")
dialog.connect("response", self._on_reboot_confirmed)
dialog.present()
def _on_reboot_confirmed(self, dialog, response):
if response == "reboot":
try:
subprocess.Popen(REBOOT_COMMAND)
except Exception as e:
self._append_text(f"\n✗ Reboot failed: {e}\n")
class SovranHubWindow(Adw.ApplicationWindow): class SovranHubWindow(Adw.ApplicationWindow):