feat(tui): identify Fenris and explain polkit authentication

This commit is contained in:
xavierk
2026-09-10 13:28:05 +05:30
parent bb5bc9a72e
commit 7bbe5cede7
4 changed files with 96 additions and 13 deletions
+38 -13
View File
@@ -286,8 +286,8 @@ class DisclosuresScreen(ModalScreen[None]):
class FenrisTuiApp(App):
"""Fenris Panes TUI — keyboard-first, one dense screen (spec §7)."""
TITLE = "Fenris"
SUB_TITLE = "NVMe endurance monitor"
TITLE = "Fenris — NVMe endurance monitor"
SUB_TITLE = ""
CSS = """
#main-grid {
@@ -317,12 +317,15 @@ class FenrisTuiApp(App):
store_path: Optional[Path] = None,
config_path: Optional[Path] = None,
helper_path: Optional[str] = None,
refresh_interval_s: float = CADENCE_DEFAULT_S,
**kwargs,
) -> None:
super().__init__(**kwargs)
self.store_path = store_path or Path("/var/lib/fenris/observations.db")
self.config_path = config_path
self.helper_path = helper_path or "/usr/libexec/fenris/fenris-monitor"
self.refresh_interval_s = refresh_interval_s
self._show_auth_notice = True
self._conn: Optional[sqlite3.Connection] = None
self._clock_now = datetime.now(timezone.utc)
@@ -339,8 +342,30 @@ class FenrisTuiApp(App):
self.query_one("#usage-history").border_title = "usage history"
self.query_one("#drive-health").border_title = "drive"
self.query_one("#service-strip").border_title = "service + actions"
self._refresh_timer = self.set_interval(
self.refresh_interval_s, self.on_refresh_tick
)
self._refresh()
def on_refresh_tick(self) -> None:
"""Refresh dashboard and dismiss launch-only authentication guidance."""
self._show_auth_notice = False
self._refresh()
def _headline_prefix(self) -> str:
"""Render identity and any launch-only guidance above drive state."""
lines = ["[bold]Fenris — NVMe endurance monitor[/bold]"]
if self._show_auth_notice:
lines.append("[dim]privileged actions will prompt for authentication (polkit)[/dim]")
return "\n".join(lines)
def _render_headline(self, body: str = "") -> None:
"""Render full-width identity, guidance, and current drive state."""
text = self._headline_prefix()
if body:
text += "\n\n" + body
self.query_one("#headline-band").update(text)
def _open_store(self) -> Optional[sqlite3.Connection]:
"""Open store read-only, handling faults."""
try:
@@ -368,25 +393,27 @@ class FenrisTuiApp(App):
"""Render empty store greeting or store fault."""
if not self.store_path.exists():
# Empty store — greeting with enable hint (IN-3)
self.query_one("#headline-band").update(
self._render_headline(
"[bold]No observations yet[/bold]\n\n"
"Enable monitoring: fenris monitor resume"
)
self.query_one("#usage-history").update("")
self.query_one("#drive-health").update("")
self.query_one("#service-strip").update(
"boot: disabled · timer: inactive · last collect: unknown · freshness: empty\n"
"boot: disabled · timer: inactive · last collect: unknown · freshness: empty · "
"[dim]by Bongbetic[/dim]\n"
"p pause · r resume · c collect · d disclosures · q quit"
)
else:
# Store fault (FL-4)
self.query_one("#headline-band").update(
self._render_headline(
"[bold red]Observation store unreadable[/bold red]\n"
"Check journalctl -u fenris-collect.service"
)
self.query_one("#usage-history").update("")
self.query_one("#drive-health").update("")
self.query_one("#service-strip").update(
"[dim]by Bongbetic[/dim]\n"
"p pause · r resume · c collect · d disclosures · q quit"
)
@@ -398,13 +425,9 @@ class FenrisTuiApp(App):
headline = self._format_headline(proj)
confidence = self._format_confidence(proj)
scenario = self._format_scenario(proj)
self.query_one("#headline-band").update(
headline + "\n" + confidence + "\n" + scenario
)
self._render_headline(headline + "\n" + confidence + "\n" + scenario)
except Exception:
self.query_one("#headline-band").update(
"[bold]No projection available[/bold]"
)
self._render_headline("[bold]No projection available[/bold]")
# --- Usage-history pane (§7.2 left) ---
history = _query_usage_history(conn)
@@ -448,14 +471,16 @@ class FenrisTuiApp(App):
collect = "ok" if svc.get("last_collect_ok") else "FAILED"
freshness = svc.get("freshness", "unknown")
self.query_one("#service-strip").update(
"boot: %s · timer: %s · last collect: %s · freshness: %s\n"
"boot: %s · timer: %s · last collect: %s · freshness: %s · "
"[dim]by Bongbetic[/dim]\n"
"%s\n"
"p pause · r resume · c collect · d disclosures · q quit"
% (boot, activity, collect, freshness, svc.get("period", ""))
)
except Exception:
self.query_one("#service-strip").update(
"boot: unknown · timer: unknown · last collect: unknown · freshness: unknown\n"
"boot: unknown · timer: unknown · last collect: unknown · freshness: unknown · "
"[dim]by Bongbetic[/dim]\n"
"p pause · r resume · c collect · d disclosures · q quit"
)