Show trustworthy local-day activity totals (#90)
Add local-day activity summaries derived from UTC hour observations using the system timezone, with durable storage in a new local_days table. The collector derives local-day read/write totals after UTC aggregation; the TUI displays them with timezone, completeness state, and coverage. Schema: bump SCHEMA_VERSION to 3, add local_days table (migration 2→3 is pure addition, idempotent, preserves newer-schema refusal).
This commit is contained in:
@@ -1045,19 +1045,35 @@ class FenrisTuiApp(App):
|
||||
grid-rows: auto 10 auto 3;
|
||||
height: auto;
|
||||
}
|
||||
#main-grid.local-day {
|
||||
grid-size: 2 5;
|
||||
grid-rows: auto 10 auto auto 3;
|
||||
}
|
||||
#main-grid.paused {
|
||||
grid-size: 2 5;
|
||||
grid-rows: auto auto 10 auto 3;
|
||||
}
|
||||
#main-grid.paused.local-day {
|
||||
grid-size: 2 6;
|
||||
grid-rows: auto auto 10 auto auto 3;
|
||||
}
|
||||
#main-grid.constrained {
|
||||
grid-size: 1 4;
|
||||
grid-columns: 1fr;
|
||||
grid-rows: auto auto auto auto;
|
||||
}
|
||||
#main-grid.constrained.local-day {
|
||||
grid-size: 1 5;
|
||||
grid-rows: auto auto auto auto auto;
|
||||
}
|
||||
#main-grid.constrained.paused {
|
||||
grid-size: 1 5;
|
||||
grid-rows: auto auto auto auto auto;
|
||||
}
|
||||
#main-grid.constrained.paused.local-day {
|
||||
grid-size: 1 6;
|
||||
grid-rows: auto auto auto auto auto auto;
|
||||
}
|
||||
#main-grid.constrained #usage-history {
|
||||
display: none;
|
||||
}
|
||||
@@ -1071,6 +1087,14 @@ class FenrisTuiApp(App):
|
||||
height: auto;
|
||||
min-height: 3;
|
||||
}
|
||||
#local-day {
|
||||
column-span: 2;
|
||||
display: none;
|
||||
border: round #555555;
|
||||
padding: 0 1;
|
||||
height: auto;
|
||||
min-height: 3;
|
||||
}
|
||||
#dashboard-scroll { height: 1fr; }
|
||||
#headline-band { column-span: 2; height: auto; min-height: 8; }
|
||||
#paused-banner {
|
||||
@@ -1141,6 +1165,7 @@ class FenrisTuiApp(App):
|
||||
yield DailyBarGraph(id="usage-history", classes="pane")
|
||||
yield Static("", id="constrained-summary", classes="pane")
|
||||
yield Static("", id="drive-health", classes="pane")
|
||||
yield Static("", id="local-day", classes="pane")
|
||||
yield Static("", id="service-strip", classes="pane")
|
||||
yield Static("q Quit TUI", id="quit-rail")
|
||||
|
||||
@@ -1154,6 +1179,7 @@ class FenrisTuiApp(App):
|
||||
self.query_one("#usage-history").border_title = "Usage history"
|
||||
self.query_one("#constrained-summary").border_title = "Usage history"
|
||||
self.query_one("#drive-health").border_title = "Drive"
|
||||
self.query_one("#local-day").border_title = "Local day"
|
||||
self.query_one("#service-strip").border_title = "Monitoring and actions"
|
||||
self._refresh_timer = self.set_interval(
|
||||
self.refresh_interval_s, self.on_refresh_tick
|
||||
@@ -1277,6 +1303,11 @@ class FenrisTuiApp(App):
|
||||
self.query_one("#constrained-summary").update(
|
||||
"[dim]Graph needs ≥80×24[/dim]\n" + message
|
||||
)
|
||||
# Hide local-day widget when no data
|
||||
local_day_widget = self.query_one("#local-day")
|
||||
local_day_widget.styles.display = "none"
|
||||
main_grid = self.query_one("#main-grid")
|
||||
main_grid.remove_class("local-day")
|
||||
|
||||
def _render_all_regions(self, conn: sqlite3.Connection, comp: StatusComposition) -> None:
|
||||
"""Render all four regions from live store data."""
|
||||
@@ -1355,6 +1386,9 @@ class FenrisTuiApp(App):
|
||||
)
|
||||
self.query_one("#drive-health").update(health_text)
|
||||
|
||||
# --- Local-day totals (issue #90) ---
|
||||
self._render_local_day(conn)
|
||||
|
||||
def _render_paused_banner(self, comp) -> None:
|
||||
"""Show the high-contrast Deliberate disable block only when sanctioned.
|
||||
|
||||
@@ -1388,6 +1422,46 @@ class FenrisTuiApp(App):
|
||||
main_grid.remove_class("paused")
|
||||
main_grid.refresh(layout=True)
|
||||
|
||||
def _render_local_day(self, conn: sqlite3.Connection) -> None:
|
||||
"""Render the local-day activity totals widget (issue #90)."""
|
||||
try:
|
||||
from .tz_util import detect_system_tz
|
||||
from .local_day import query_current_local_day
|
||||
tz_name = detect_system_tz()
|
||||
local = query_current_local_day(conn, self._clock_now, tz_name)
|
||||
except Exception:
|
||||
local = None
|
||||
|
||||
widget = self.query_one("#local-day")
|
||||
main_grid = self.query_one("#main-grid")
|
||||
|
||||
if local is None:
|
||||
widget.styles.display = "none"
|
||||
main_grid.remove_class("local-day")
|
||||
return
|
||||
|
||||
main_grid.add_class("local-day")
|
||||
widget.styles.display = "block"
|
||||
|
||||
bw = local["bytes_written"]
|
||||
br = local["bytes_read"]
|
||||
partial = "" if local["complete"] else " · totals so far"
|
||||
tz_display = "%s %s" % (local["tz_name"], local["tz_offset"])
|
||||
|
||||
text = (
|
||||
"[bold]%s[/bold] %s%s\n"
|
||||
" W %.3f GB · R %.3f GB · %.0f%% coverage"
|
||||
% (
|
||||
local["local_date"],
|
||||
tz_display,
|
||||
partial,
|
||||
bw / 1e9,
|
||||
br / 1e9,
|
||||
local["coverage"] * 100,
|
||||
)
|
||||
)
|
||||
widget.update(text)
|
||||
|
||||
def _on_graph_drill(self, day: str) -> None:
|
||||
"""Load hourly data when the graph enters drill-down mode."""
|
||||
graph = self.query_one("#usage-history")
|
||||
|
||||
Reference in New Issue
Block a user