fix(studio): daemon-start probes health first + shows a readable error
Some checks failed
Security / Security check (push) Failing after 2s
Some checks failed
Security / Security check (push) Failing after 2s
'Tap to start' on an already-running hub reported 'daemon could not be started' (the start failed because it was already up) then flashed the error away as a SnackBar — confusing next to the sidebar's 'connected'. Now: on a failed start, probe the hub; if it answers, just connect (no false error). If it is genuinely down, show the daemon's stderr in a persistent, copyable dialog (showFaiProcessErrorDialog) instead of a SnackBar that vanishes before the operator can read or copy it. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
57dac3d999
commit
62ed3951e5
2 changed files with 59 additions and 23 deletions
|
|
@ -109,6 +109,27 @@ void showFaiProcessError(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like [showFaiProcessError] but as a persistent modal dialog. Use for
|
||||||
|
/// failures the operator needs time to read and copy (a daemon that
|
||||||
|
/// won't start) — a SnackBar flashes away before they can act on it.
|
||||||
|
Future<void> showFaiProcessErrorDialog(
|
||||||
|
BuildContext context,
|
||||||
|
String source,
|
||||||
|
String stdout,
|
||||||
|
String stderr, {
|
||||||
|
String? title,
|
||||||
|
}) {
|
||||||
|
final detail = [stderr.trim(), stdout.trim()]
|
||||||
|
.where((s) => s.isNotEmpty)
|
||||||
|
.join('\n\n');
|
||||||
|
return showFaiErrorDialog(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
detail.isEmpty ? 'process exited non-zero (no output)' : detail,
|
||||||
|
title: title,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Show [error] as a centered modal dialog. Use this when the
|
/// Show [error] as a centered modal dialog. Use this when the
|
||||||
/// failure blocks meaningful interaction (auth missing, hub
|
/// failure blocks meaningful interaction (auth missing, hub
|
||||||
/// unreachable) — a SnackBar would be too easy to dismiss.
|
/// unreachable) — a SnackBar would be too easy to dismiss.
|
||||||
|
|
|
||||||
|
|
@ -275,20 +275,29 @@ class StudioShellState extends State<StudioShell> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(l.daemonStartRequested)),
|
SnackBar(content: Text(l.daemonStartRequested)),
|
||||||
);
|
);
|
||||||
} else {
|
Future.delayed(const Duration(seconds: 1), _checkHealth);
|
||||||
// Surface the daemon's stderr/stdout as a copyable error — this
|
return;
|
||||||
// is the message the operator most often needs to paste back.
|
}
|
||||||
showFaiProcessError(
|
// The start command reported failure — but very often the daemon is
|
||||||
|
// simply already running (port in use). Probe before crying error,
|
||||||
|
// so "tap to start" on an already-up hub just connects.
|
||||||
|
final alive =
|
||||||
|
await HubService.instance.healthy().catchError((_) => false);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (alive) {
|
||||||
|
await _checkHealth();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Genuinely down: a persistent, copyable dialog — a SnackBar flashes
|
||||||
|
// away before the operator can read or copy the daemon's stderr.
|
||||||
|
await showFaiProcessErrorDialog(
|
||||||
context,
|
context,
|
||||||
'daemon.start',
|
'daemon.start',
|
||||||
r.stdout,
|
r.stdout,
|
||||||
r.stderr,
|
r.stderr,
|
||||||
title: l.daemonStartFailed(''),
|
title: l.daemonStartFailed(''),
|
||||||
);
|
);
|
||||||
}
|
if (mounted) Future.delayed(const Duration(seconds: 1), _checkHealth);
|
||||||
// Probe again shortly so the UI flips to "connected" without
|
|
||||||
// waiting for the next 5 s tick.
|
|
||||||
Future.delayed(const Duration(seconds: 1), _checkHealth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switch the sidebar's selected page by its registered id
|
/// Switch the sidebar's selected page by its registered id
|
||||||
|
|
@ -702,10 +711,17 @@ class _SidebarState extends State<_Sidebar>
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(l.daemonStartRequested)),
|
SnackBar(content: Text(l.daemonStartRequested)),
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
// Surface the daemon's stderr/stdout as a copyable error — this
|
}
|
||||||
// is the message the operator most often needs to paste back.
|
// The daemon may already be running (port in use) — probe before
|
||||||
showFaiProcessError(
|
// showing an error, so this just connects on an already-up hub.
|
||||||
|
final alive =
|
||||||
|
await HubService.instance.healthy().catchError((_) => false);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (alive) return;
|
||||||
|
// Genuinely down: persistent, copyable dialog (a SnackBar flashes
|
||||||
|
// away before the operator can read or copy the daemon's stderr).
|
||||||
|
await showFaiProcessErrorDialog(
|
||||||
context,
|
context,
|
||||||
'daemon.start',
|
'daemon.start',
|
||||||
r.stdout,
|
r.stdout,
|
||||||
|
|
@ -713,7 +729,6 @@ class _SidebarState extends State<_Sidebar>
|
||||||
title: l.daemonStartFailed(''),
|
title: l.daemonStartFailed(''),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue