Skip to content

Commit 93a7fef

Browse files
authored
Merge pull request #42 from m4r1k/fix/tui-events-tiered-priority
fix(tui): tier events priority + decouple --time from events
2 parents c1290f4 + 7cdd1e7 commit 93a7fef

7 files changed

Lines changed: 470 additions & 290 deletions

File tree

docs/changelog.md

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## [5.2.2] - 2026-04-28
1111

12-
TUI usability fixes plus one safety-critical re-arm fix on the shutdown
13-
state machine. Drop-in upgrade.
12+
Bug-fix release. Drop-in upgrade.
1413

1514
### Fixed
16-
- **Shutdown trigger never re-armed after `POWER_RESTORED`** when the
17-
local-shutdown command failed to actually halt the host (custom
18-
no-op command, sandboxed environment, dummy-UPS test rig — bug #4).
19-
Subsequent on-battery transitions silently no-op'd at the flag-file
20-
gate. The handler now clears the flag on the OB/FSD → OL transition,
21-
and `MultiUPSCoordinator` resets its in-memory lock + global flag in
22-
the same hook so multi-UPS deployments re-arm too. Gated re-triggers
23-
also log a warning so the no-op is no longer silent.
24-
- **`eneru tui --graph voltage` silently ignored in interactive mode.**
25-
The CLI flag now seeds the initial graph metric, and `--time` seeds
26-
the initial window; `<G>` / `<T>` cycle from there.
27-
- **A single phantom 0 V sample squashed the voltage band into a
28-
one-pixel strip at the top of the graph.** Writer drops on-line
29-
`input.voltage <= 0` rows at sample time (real outages — `OB`/`FSD`
30-
— still record the legitimate dip to 0 V); graph panel auto-scales
31-
unbounded metrics from the 5th/95th percentile so any leftover
32-
outliers don't dictate the band.
15+
- Shutdown trigger never re-armed after `POWER_RESTORED` when the
16+
local-shutdown command didn't actually halt the host (bug #4).
17+
Single-UPS and multi-UPS coordinator paths both fixed. Gated
18+
re-triggers now log a warning instead of returning silently.
19+
- `eneru tui --graph voltage` silently ignored in interactive mode.
20+
- Phantom 0 V samples squashed the voltage graph into a one-row strip
21+
at the top. Writer drops on-line `input.voltage <= 0` (real
22+
outages still record the dip); graph uses 5th/95th percentile bounds.
23+
- Events panel showed daemon-lifecycle chatter instead of power
24+
events. Priority filter is now tiered: power events always survive
25+
the cap; daemon events fill remaining slots.
26+
- `eneru tui --once --events-only` silently fell back to log parsing
27+
because the default 1 h window was empty for sparse events. Events
28+
no longer use a time window at all.
3329

3430
### Added
35-
- **`--verbose` / `-v`** on `eneru tui` (live + `--once`): widens the
36-
events filter to include low-priority chatter alongside the priority
37-
defaults. Live TUI gains a `<V>` keybind to toggle in-session.
38-
- **`--full-history`** for `eneru tui --once`: ignore the `--time`
39-
window and query the events table from the beginning. Rejects with
40-
`error: --full-history requires --once` (exit 2) when combined with
31+
- `--verbose` / `-v`: include low-priority events. `<V>` toggles in
4132
the live TUI.
33+
- `--length N`: cap events output (default 30, `0` = no cap).
4234

4335
### Changed
44-
- **Events panel defaults to priority-only** in both live TUI and
45-
`eneru tui --once --events-only`. Daemon lifecycle, shutdown
46-
triggers, and power transitions surface; per-condition chatter
47-
is hidden. Scripts that grep `--once --events-only` for
48-
low-priority event types must now pass `--verbose`.
49-
- Live TUI events panel default cap raised from 8 to 20 rows so a few
50-
voltage transitions don't push the daemon-level history off-screen.
36+
- `--time` and `<T>` apply to the graph only. Use `--length` for events.
37+
- Events panel defaults to priority-only.
38+
- Live TUI events cap raised (now `min(30, visible_panel_rows)` so
39+
power events stay inside the visible window on smaller terminals;
40+
`<M>` still expands to 500 rows for full scrollable history).
5141

5242
### Migration notes
53-
None for the package install. Scripts parsing `eneru tui --once
54-
--events-only` output: add `--verbose` if you rely on seeing
55-
low-priority event types (voltage flaps, etc.).
43+
- Scripts grepping `--events-only` output for low-priority event
44+
types: add `--verbose`.
45+
- Scripts using `--time` to size events: switch to `--length`.
5646

5747
---
5848

docs/configuration.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,16 +431,16 @@ Common flags:
431431
| `monitor` | `--once` | Print one status snapshot |
432432
| `monitor` | `--interval` | TUI refresh interval |
433433
| `monitor` | `--graph {charge,load,voltage,runtime}` | Initial graph metric (interactive: cycle with `<G>`) |
434-
| `monitor` | `--time {1h,6h,24h,7d,30d}` | Initial graph / event window (interactive seeds the cycle, still toggle with `<T>`) |
434+
| `monitor` | `--time {1h,6h,24h,7d,30d}` | **Graph** time range (interactive: cycle with `<T>`). Does NOT affect the events list — events have no time window |
435435
| `monitor` | `--events-only` | Print recent events only |
436436
| `monitor` | `--verbose`, `-v` | Show low-priority events too (default: priority only); `<V>` toggles in-session |
437-
| `monitor` | `--full-history` | Ignore `--time`, query events from the beginning (`--once` only) |
437+
| `monitor` | `--length N` | With `--once`: max events to print (default 30, 0 = no cap). Power events always preserved within the cap |
438438

439439
Example package commands:
440440

441441
```bash
442442
sudo eneru validate --config /etc/ups-monitor/config.yaml
443443
sudo eneru run --dry-run --config /etc/ups-monitor/config.yaml
444444
sudo eneru monitor --once --events-only --config /etc/ups-monitor/config.yaml
445-
sudo /opt/ups-monitor/eneru.py monitor --once --events-only --verbose --full-history --config /etc/ups-monitor/config.yaml
445+
sudo eneru monitor --once --events-only --verbose --length 100 --config /etc/ups-monitor/config.yaml
446446
```

src/eneru/cli.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@
1818
apprise = None
1919

2020

21+
def _non_negative_int(value: str) -> int:
22+
"""argparse type for ``--length``: int >= 0 (0 = no cap)."""
23+
try:
24+
n = int(value)
25+
except (TypeError, ValueError):
26+
raise argparse.ArgumentTypeError(
27+
f"--length must be a non-negative integer, got {value!r}"
28+
)
29+
if n < 0:
30+
raise argparse.ArgumentTypeError(
31+
f"--length must be >= 0 (0 = no cap), got {n}"
32+
)
33+
return n
34+
35+
2136
def _load_config(args):
2237
"""Load configuration from the --config path."""
2338
return ConfigLoader.load(getattr(args, 'config', None))
@@ -368,31 +383,9 @@ def _cmd_deliver_stop(args):
368383

369384
def _cmd_monitor(args):
370385
"""Launch the TUI dashboard."""
371-
# Validate flag combinations BEFORE _load_config so the rejection
372-
# isn't preceded by a "Configuration loaded from: ..." message --
373-
# that ordering looked like the validate-then-fail had succeeded.
374-
# argparse can't easily express "this flag requires --once" so we
375-
# do it here, exit with the same code argparse would use, and
376-
# mimic its ``error: ...`` stderr format so log scrapers keep
377-
# working.
378-
if getattr(args, "full_history", False) and not args.once:
379-
# Compose the prefix from the actual invocation so users running
380-
# ``eneru monitor`` don't get an "eneru tui: ..." message and
381-
# vice versa. ``args.command`` is set by argparse via the
382-
# subparsers' ``dest="command"``; fall back to ``argv[1]`` if
383-
# that's somehow missing (defensive: monitor / tui are aliases).
384-
sub = getattr(args, "command", None) or (
385-
sys.argv[1] if len(sys.argv) > 1 else "monitor"
386-
)
387-
sys.stderr.write(
388-
f"eneru {sub}: error: --full-history requires --once "
389-
"(interactive TUI is real-time)\n"
390-
)
391-
sys.exit(2)
392-
393386
config = _load_config(args)
394387

395-
from eneru.tui import run_tui, run_once
388+
from eneru.tui import run_tui, run_once, EVENTS_MAX_ROWS_NORMAL
396389

397390
if args.once:
398391
run_once(
@@ -401,7 +394,7 @@ def _cmd_monitor(args):
401394
time_range=getattr(args, "time", "1h"),
402395
events_only=getattr(args, "events_only", False),
403396
verbose=getattr(args, "verbose", False),
404-
full_history=getattr(args, "full_history", False),
397+
length=getattr(args, "length", EVENTS_MAX_ROWS_NORMAL),
405398
)
406399
else:
407400
run_tui(
@@ -465,19 +458,26 @@ def _add_monitor_args(p):
465458
choices=["charge", "load", "voltage", "runtime"],
466459
help="Initial graph metric. With --once renders a Braille snapshot; "
467460
"in interactive TUI pre-selects the metric (still cycle with <G>)")
461+
# IMPORTANT: --time is GRAPH-ONLY in 5.2.2+. It must NOT be
462+
# threaded into the events query. Events are sparse and a fixed
463+
# window made the panel silently empty for normal homelab usage
464+
# (the events panel then fell back to log parsing without the
465+
# operator noticing). Use --length to size the events list.
468466
p.add_argument("--time", default="1h",
469-
help="Initial graph time range (1h/6h/24h/7d/30d). Applies to "
470-
"--once snapshots and to the interactive TUI's initial view")
467+
help="Graph time range (1h/6h/24h/7d/30d). Applies only to the "
468+
"graph -- the events list is independent (use --length to size it)")
471469
p.add_argument("--events-only", action="store_true",
472470
help="With --once: print only the events list (SQLite, log-tail fallback)")
473471
p.add_argument("--verbose", "-v", action="store_true",
474472
help="Show low-priority events alongside the priority defaults "
475473
"(daemon lifecycle, shutdown triggers, power transitions). "
476474
"Applies to both --once and the interactive TUI; toggle "
477475
"in-session with <V>")
478-
p.add_argument("--full-history", action="store_true",
479-
help="Ignore --time and query the events table from the beginning. "
480-
"Only valid with --once")
476+
p.add_argument("--length", type=_non_negative_int, default=30,
477+
metavar="N",
478+
help="With --once: max events to print (default: 30, 0 = no cap). "
479+
"Power events are always preserved within the cap; daemon-"
480+
"lifecycle events fill remaining slots")
481481
p.set_defaults(func=_cmd_monitor)
482482

483483
mon_parser = subparsers.add_parser("monitor", help="Launch real-time TUI dashboard")

0 commit comments

Comments
 (0)