feat(studio): live audit feed via streamEvents (poll stays as safety net)
Some checks failed
Security / Security check (push) Failing after 1s
Some checks failed
Security / Security check (push) Failing after 1s
The Audit page subscribes to streamEvents and nudges an immediate (debounced) refresh on each new event, so the feed updates instantly instead of waiting up to 2 s. Reuses the proven _refresh() path, so list management + filtering + hash-chain ordering are unchanged. The 2 s poll remains the safety net: on a persistent stream error the page is still fresh within the interval; a clean stream close re-subscribes once after 3 s. Subscription + debounce cancelled on dispose. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
f5073b397e
commit
9bd5ddf8d2
1 changed files with 40 additions and 0 deletions
|
|
@ -26,19 +26,59 @@ class _AuditPageState extends State<AuditPage> {
|
||||||
bool _initialLoaded = false;
|
bool _initialLoaded = false;
|
||||||
Timer? _poller;
|
Timer? _poller;
|
||||||
|
|
||||||
|
/// Live event feed. New events nudge an immediate refresh so the
|
||||||
|
/// page updates instantly instead of waiting up to 2 s. The poll
|
||||||
|
/// stays as the safety net: if the stream errors or the hub drops,
|
||||||
|
/// the page is still fresh within the poll interval. Reusing the
|
||||||
|
/// proven `_refresh()` (re-fetch latest 100) keeps list management,
|
||||||
|
/// filtering, and the hash-chain ordering exactly as before.
|
||||||
|
StreamSubscription<AuditEvent>? _eventSub;
|
||||||
|
Timer? _nudgeDebounce;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_refresh();
|
_refresh();
|
||||||
_poller = Timer.periodic(const Duration(seconds: 2), (_) => _refresh());
|
_poller = Timer.periodic(const Duration(seconds: 2), (_) => _refresh());
|
||||||
|
_subscribeLive();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_poller?.cancel();
|
_poller?.cancel();
|
||||||
|
_nudgeDebounce?.cancel();
|
||||||
|
_eventSub?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _subscribeLive() {
|
||||||
|
_eventSub?.cancel();
|
||||||
|
_eventSub = HubService.instance.streamEvents(backfill: 0).listen(
|
||||||
|
(_) => _nudge(),
|
||||||
|
// On a persistent error the 2 s poll keeps the page fresh, so we
|
||||||
|
// do nothing. On a clean close (hub restart / stream end) try to
|
||||||
|
// re-establish the live feed once after a short delay.
|
||||||
|
onError: (_) {},
|
||||||
|
onDone: () {
|
||||||
|
_eventSub = null;
|
||||||
|
if (mounted) {
|
||||||
|
Timer(const Duration(seconds: 3), () {
|
||||||
|
if (mounted && _eventSub == null) _subscribeLive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coalesce a burst of events into a single refresh.
|
||||||
|
void _nudge() {
|
||||||
|
if (_nudgeDebounce?.isActive ?? false) return;
|
||||||
|
_nudgeDebounce = Timer(const Duration(milliseconds: 300), () {
|
||||||
|
if (mounted) _refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onClearPressed() async {
|
Future<void> _onClearPressed() async {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final outcome = await _ClearAuditDialog.show(context);
|
final outcome = await _ClearAuditDialog.show(context);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue