feat(studio): hub maintenance panel with reset action (v0.45.0)
Some checks are pending
Security / Security check (push) Waiting to run
Some checks are pending
Security / Security check (push) Waiting to run
UI parity with the platform CLI: `fai reset` (v0.10.93) is now reachable from Settings. The panel shows the reset blurb plus two checkboxes mirroring the CLI flags (Keep modules / Keep audit log + saved flows), then a destructive-styled "Reset hub state" button. Click goes through a confirm dialog before SystemActions.faiReset is invoked. After the CLI returns, Studio bounces its gRPC channel against the same endpoint (the daemon restarts on the same channel + port, so no Settings change needed) and reloads every panel: channel status, system AI, MCP clients, n8n endpoints, registry token. Output (success or failure) is shown in a copyable error box so any unexpected stderr lands somewhere the operator can paste from. Sits at the bottom of the dialog under "HUB MAINTENANCE", separated from the everyday config so a destructive button doesn't crowd the day-to-day controls. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
9944a7ac8a
commit
3db7db7330
8 changed files with 358 additions and 1 deletions
|
|
@ -125,6 +125,25 @@ class SystemActions {
|
||||||
return _runFai(['daemon', 'disable', '--channel', channel]);
|
return _runFai(['daemon', 'disable', '--channel', channel]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run `fai reset --yes`. Wipes operator state with the same
|
||||||
|
/// safety net as the CLI: stops every daemon (including off-
|
||||||
|
/// channel orphans), atomically backs `~/.fai/` up, recreates
|
||||||
|
/// it with `bin/`, `channels/`, `config.yaml`, `current-channel`,
|
||||||
|
/// and `registry-token` preserved. The daemon restarts on the
|
||||||
|
/// active channel before the call returns.
|
||||||
|
///
|
||||||
|
/// Studio's gRPC channel goes down for ~1s while the daemon
|
||||||
|
/// restarts — caller should follow up with a reconnect.
|
||||||
|
static Future<({bool ok, String stdout, String stderr})> faiReset({
|
||||||
|
bool keepModules = false,
|
||||||
|
bool keepData = false,
|
||||||
|
}) async {
|
||||||
|
final args = <String>['reset', '--yes'];
|
||||||
|
if (keepModules) args.add('--keep-modules');
|
||||||
|
if (keepData) args.add('--keep-data');
|
||||||
|
return _runFai(args);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<({bool ok, String stdout, String stderr})> _runFai(
|
static Future<({bool ok, String stdout, String stderr})> _runFai(
|
||||||
List<String> args,
|
List<String> args,
|
||||||
) async {
|
) async {
|
||||||
|
|
|
||||||
|
|
@ -449,6 +449,17 @@
|
||||||
"@registryTokenSaveFailedToast": {
|
"@registryTokenSaveFailedToast": {
|
||||||
"placeholders": { "error": { "type": "String" } }
|
"placeholders": { "error": { "type": "String" } }
|
||||||
},
|
},
|
||||||
|
"maintenanceHeader": "HUB-WARTUNG",
|
||||||
|
"maintenanceResetBlurb": "Operator-Zustand zurücksetzen und mit einem sauberen Hub neu starten. Stoppt jeden laufenden Daemon, sichert ~/.fai/ atomar in ein Backup, legt es dann neu an — Binary, Channel-Pointer, MCP-/n8n-/System-AI-Konfiguration und Registry-Token bleiben erhalten. Wiederherstellen durch Zurückverschieben des Backup-Ordners.",
|
||||||
|
"maintenanceKeepModulesTitle": "Installierte Module behalten",
|
||||||
|
"maintenanceKeepModulesSubtitle": "Standardverhalten löscht ~/.fai/modules/, damit eine frische Installation den Modul-Bezugspfad end-to-end verifizieren kann.",
|
||||||
|
"maintenanceKeepDataTitle": "Protokoll + gespeicherte Flows behalten",
|
||||||
|
"maintenanceKeepDataSubtitle": "Standardverhalten löscht ~/.fai/data/, damit die Audit-Kette mit einer neuen Genesis startet und die mitgelieferten Sample-Flows beim ersten Daemon-Start neu importiert werden.",
|
||||||
|
"maintenanceResetButton": "Hub zurücksetzen",
|
||||||
|
"maintenanceResetInProgress": "Zurücksetzen…",
|
||||||
|
"maintenanceResetConfirmTitle": "Hub zurücksetzen?",
|
||||||
|
"maintenanceResetConfirmBody": "Stoppt jeden Daemon, verschiebt ~/.fai/ in ein Backup mit Zeitstempel und startet sauber neu. Das Backup ist vollständig wiederherstellbar. Fortfahren?",
|
||||||
|
"maintenanceResetConfirmButton": "Zurücksetzen",
|
||||||
"welcomeChecklistAllSetTitle": "Du bist eingerichtet.",
|
"welcomeChecklistAllSetTitle": "Du bist eingerichtet.",
|
||||||
"welcomeChecklistAllSetBody": "Drei Stränge, an denen du als nächstes ziehen kannst:",
|
"welcomeChecklistAllSetBody": "Drei Stränge, an denen du als nächstes ziehen kannst:",
|
||||||
"welcomeChecklistNextAuditTitle": "Audit-Log lesen",
|
"welcomeChecklistNextAuditTitle": "Audit-Log lesen",
|
||||||
|
|
|
||||||
|
|
@ -450,6 +450,17 @@
|
||||||
"@registryTokenSaveFailedToast": {
|
"@registryTokenSaveFailedToast": {
|
||||||
"placeholders": { "error": { "type": "String" } }
|
"placeholders": { "error": { "type": "String" } }
|
||||||
},
|
},
|
||||||
|
"maintenanceHeader": "HUB MAINTENANCE",
|
||||||
|
"maintenanceResetBlurb": "Wipe operator state and start over with a clean hub. Stops every running daemon, atomically backs ~/.fai/ up, then recreates it with the binary, channel state, MCP / n8n / system-AI config, and registry token preserved. Restore by moving the backup directory back.",
|
||||||
|
"maintenanceKeepModulesTitle": "Keep installed modules",
|
||||||
|
"maintenanceKeepModulesSubtitle": "Default behaviour wipes ~/.fai/modules/ so a fresh install can verify the module-fetch path end-to-end.",
|
||||||
|
"maintenanceKeepDataTitle": "Keep audit log + saved flows",
|
||||||
|
"maintenanceKeepDataSubtitle": "Default behaviour wipes ~/.fai/data/ so the audit chain restarts from a fresh genesis and the bundled sample flows re-import on first daemon start.",
|
||||||
|
"maintenanceResetButton": "Reset hub state",
|
||||||
|
"maintenanceResetInProgress": "Resetting…",
|
||||||
|
"maintenanceResetConfirmTitle": "Reset hub state?",
|
||||||
|
"maintenanceResetConfirmBody": "This stops every daemon, moves ~/.fai/ to a timestamped backup, and starts fresh. The backup is fully restorable. Continue?",
|
||||||
|
"maintenanceResetConfirmButton": "Reset",
|
||||||
"welcomeChecklistAllSetTitle": "You're set up.",
|
"welcomeChecklistAllSetTitle": "You're set up.",
|
||||||
"welcomeChecklistAllSetBody": "Three threads to pull on next:",
|
"welcomeChecklistAllSetBody": "Three threads to pull on next:",
|
||||||
"welcomeChecklistNextAuditTitle": "Read the audit log",
|
"welcomeChecklistNextAuditTitle": "Read the audit log",
|
||||||
|
|
|
||||||
|
|
@ -2042,6 +2042,72 @@ abstract class AppLocalizations {
|
||||||
/// **'Could not save: {error}'**
|
/// **'Could not save: {error}'**
|
||||||
String registryTokenSaveFailedToast(String error);
|
String registryTokenSaveFailedToast(String error);
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceHeader.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'HUB MAINTENANCE'**
|
||||||
|
String get maintenanceHeader;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceResetBlurb.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Wipe operator state and start over with a clean hub. Stops every running daemon, atomically backs ~/.fai/ up, then recreates it with the binary, channel state, MCP / n8n / system-AI config, and registry token preserved. Restore by moving the backup directory back.'**
|
||||||
|
String get maintenanceResetBlurb;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceKeepModulesTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Keep installed modules'**
|
||||||
|
String get maintenanceKeepModulesTitle;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceKeepModulesSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Default behaviour wipes ~/.fai/modules/ so a fresh install can verify the module-fetch path end-to-end.'**
|
||||||
|
String get maintenanceKeepModulesSubtitle;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceKeepDataTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Keep audit log + saved flows'**
|
||||||
|
String get maintenanceKeepDataTitle;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceKeepDataSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Default behaviour wipes ~/.fai/data/ so the audit chain restarts from a fresh genesis and the bundled sample flows re-import on first daemon start.'**
|
||||||
|
String get maintenanceKeepDataSubtitle;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceResetButton.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Reset hub state'**
|
||||||
|
String get maintenanceResetButton;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceResetInProgress.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Resetting…'**
|
||||||
|
String get maintenanceResetInProgress;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceResetConfirmTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Reset hub state?'**
|
||||||
|
String get maintenanceResetConfirmTitle;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceResetConfirmBody.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This stops every daemon, moves ~/.fai/ to a timestamped backup, and starts fresh. The backup is fully restorable. Continue?'**
|
||||||
|
String get maintenanceResetConfirmBody;
|
||||||
|
|
||||||
|
/// No description provided for @maintenanceResetConfirmButton.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Reset'**
|
||||||
|
String get maintenanceResetConfirmButton;
|
||||||
|
|
||||||
/// No description provided for @welcomeChecklistAllSetTitle.
|
/// No description provided for @welcomeChecklistAllSetTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -1159,6 +1159,44 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
return 'Speichern fehlgeschlagen: $error';
|
return 'Speichern fehlgeschlagen: $error';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceHeader => 'HUB-WARTUNG';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetBlurb =>
|
||||||
|
'Operator-Zustand zurücksetzen und mit einem sauberen Hub neu starten. Stoppt jeden laufenden Daemon, sichert ~/.fai/ atomar in ein Backup, legt es dann neu an — Binary, Channel-Pointer, MCP-/n8n-/System-AI-Konfiguration und Registry-Token bleiben erhalten. Wiederherstellen durch Zurückverschieben des Backup-Ordners.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepModulesTitle => 'Installierte Module behalten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepModulesSubtitle =>
|
||||||
|
'Standardverhalten löscht ~/.fai/modules/, damit eine frische Installation den Modul-Bezugspfad end-to-end verifizieren kann.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepDataTitle =>
|
||||||
|
'Protokoll + gespeicherte Flows behalten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepDataSubtitle =>
|
||||||
|
'Standardverhalten löscht ~/.fai/data/, damit die Audit-Kette mit einer neuen Genesis startet und die mitgelieferten Sample-Flows beim ersten Daemon-Start neu importiert werden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetButton => 'Hub zurücksetzen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetInProgress => 'Zurücksetzen…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetConfirmTitle => 'Hub zurücksetzen?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetConfirmBody =>
|
||||||
|
'Stoppt jeden Daemon, verschiebt ~/.fai/ in ein Backup mit Zeitstempel und startet sauber neu. Das Backup ist vollständig wiederherstellbar. Fortfahren?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetConfirmButton => 'Zurücksetzen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get welcomeChecklistAllSetTitle => 'Du bist eingerichtet.';
|
String get welcomeChecklistAllSetTitle => 'Du bist eingerichtet.';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1174,6 +1174,43 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
return 'Could not save: $error';
|
return 'Could not save: $error';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceHeader => 'HUB MAINTENANCE';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetBlurb =>
|
||||||
|
'Wipe operator state and start over with a clean hub. Stops every running daemon, atomically backs ~/.fai/ up, then recreates it with the binary, channel state, MCP / n8n / system-AI config, and registry token preserved. Restore by moving the backup directory back.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepModulesTitle => 'Keep installed modules';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepModulesSubtitle =>
|
||||||
|
'Default behaviour wipes ~/.fai/modules/ so a fresh install can verify the module-fetch path end-to-end.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepDataTitle => 'Keep audit log + saved flows';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceKeepDataSubtitle =>
|
||||||
|
'Default behaviour wipes ~/.fai/data/ so the audit chain restarts from a fresh genesis and the bundled sample flows re-import on first daemon start.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetButton => 'Reset hub state';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetInProgress => 'Resetting…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetConfirmTitle => 'Reset hub state?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetConfirmBody =>
|
||||||
|
'This stops every daemon, moves ~/.fai/ to a timestamped backup, and starts fresh. The backup is fully restorable. Continue?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get maintenanceResetConfirmButton => 'Reset';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get welcomeChecklistAllSetTitle => 'You\'re set up.';
|
String get welcomeChecklistAllSetTitle => 'You\'re set up.';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,24 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
onSave: _saveRegistryToken,
|
onSave: _saveRegistryToken,
|
||||||
onClear: _clearRegistryToken,
|
onClear: _clearRegistryToken,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: FaiSpace.lg),
|
||||||
|
_MaintenancePanel(
|
||||||
|
onResetDone: () async {
|
||||||
|
// Daemon just restarted under the same channel +
|
||||||
|
// port. Bouncing the gRPC connection picks up the
|
||||||
|
// fresh state without the operator having to
|
||||||
|
// close+reopen Settings.
|
||||||
|
await HubService.instance.reconnect(
|
||||||
|
HubService.instance.currentEndpoint,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
await _loadChannels();
|
||||||
|
await _loadAiStatus();
|
||||||
|
await _loadMcpClients();
|
||||||
|
await _loadN8nEndpoints();
|
||||||
|
await _loadRegistryToken();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1437,6 +1455,163 @@ class _AddN8nEndpointDialogState extends State<_AddN8nEndpointDialog> {
|
||||||
/// metadata; nothing is auto-spawned. Trust model:
|
/// metadata; nothing is auto-spawned. Trust model:
|
||||||
/// referencing public projects with their canonical install
|
/// referencing public projects with their canonical install
|
||||||
/// command is the same legal / security shape as a package
|
/// command is the same legal / security shape as a package
|
||||||
|
/// Hub maintenance panel — currently one action: reset operator
|
||||||
|
/// state via `fai reset --yes`. Lives at the bottom of Settings
|
||||||
|
/// so the everyday config controls aren't crowded by a destructive
|
||||||
|
/// button. The reset itself is gated by an interactive confirm
|
||||||
|
/// dialog before the CLI is invoked.
|
||||||
|
class _MaintenancePanel extends StatefulWidget {
|
||||||
|
/// Called after a successful reset so the parent Settings dialog
|
||||||
|
/// can bounce its gRPC channel and reload its panels.
|
||||||
|
final Future<void> Function() onResetDone;
|
||||||
|
|
||||||
|
const _MaintenancePanel({required this.onResetDone});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_MaintenancePanel> createState() => _MaintenancePanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MaintenancePanelState extends State<_MaintenancePanel> {
|
||||||
|
bool _keepModules = false;
|
||||||
|
bool _keepData = false;
|
||||||
|
bool _resetting = false;
|
||||||
|
String? _resultText;
|
||||||
|
bool _resultOk = false;
|
||||||
|
|
||||||
|
Future<void> _confirmAndReset() async {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: Text(l.maintenanceResetConfirmTitle),
|
||||||
|
content: Text(l.maintenanceResetConfirmBody),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, false),
|
||||||
|
child: Text(l.buttonCancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(ctx).colorScheme.error,
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
|
child: Text(l.maintenanceResetConfirmButton),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (confirmed != true || !mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_resetting = true;
|
||||||
|
_resultText = null;
|
||||||
|
});
|
||||||
|
final r = await SystemActions.faiReset(
|
||||||
|
keepModules: _keepModules,
|
||||||
|
keepData: _keepData,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_resetting = false;
|
||||||
|
_resultOk = r.ok;
|
||||||
|
_resultText = r.ok
|
||||||
|
? r.stdout.trim()
|
||||||
|
: (r.stderr.isEmpty ? r.stdout : r.stderr).trim();
|
||||||
|
});
|
||||||
|
if (r.ok) await widget.onResetDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
l.maintenanceHeader,
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
l.maintenanceResetBlurb,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: FaiSpace.sm),
|
||||||
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
dense: true,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
value: _keepModules,
|
||||||
|
onChanged: _resetting
|
||||||
|
? null
|
||||||
|
: (v) => setState(() => _keepModules = v ?? false),
|
||||||
|
title: Text(l.maintenanceKeepModulesTitle),
|
||||||
|
subtitle: Text(
|
||||||
|
l.maintenanceKeepModulesSubtitle,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
dense: true,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
value: _keepData,
|
||||||
|
onChanged: _resetting
|
||||||
|
? null
|
||||||
|
: (v) => setState(() => _keepData = v ?? false),
|
||||||
|
title: Text(l.maintenanceKeepDataTitle),
|
||||||
|
subtitle: Text(
|
||||||
|
l.maintenanceKeepDataSubtitle,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: FaiSpace.sm),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
FilledButton.icon(
|
||||||
|
icon: _resetting
|
||||||
|
? const SizedBox(
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.restart_alt, size: 16),
|
||||||
|
label: Text(
|
||||||
|
_resetting
|
||||||
|
? l.maintenanceResetInProgress
|
||||||
|
: l.maintenanceResetButton,
|
||||||
|
),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: theme.colorScheme.errorContainer,
|
||||||
|
foregroundColor: theme.colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
onPressed: _resetting ? null : _confirmAndReset,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_resultText != null) ...[
|
||||||
|
const SizedBox(height: FaiSpace.sm),
|
||||||
|
FaiErrorBox(
|
||||||
|
text: _resultText!,
|
||||||
|
isError: !_resultOk,
|
||||||
|
maxHeight: 240,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Credentials panel for the operator's registry auth token.
|
/// Credentials panel for the operator's registry auth token.
|
||||||
/// The hub reads the same token via `~/.fai/registry-token`
|
/// The hub reads the same token via `~/.fai/registry-token`
|
||||||
/// (or the `FAI_REGISTRY_TOKEN` env var, which still wins)
|
/// (or the `FAI_REGISTRY_TOKEN` env var, which still wins)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: fai_studio
|
name: fai_studio
|
||||||
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.44.0
|
version: 0.45.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.0-200.1.beta
|
sdk: ^3.11.0-200.1.beta
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue