diff --git a/crates/cordiale-ui/lang/de/LC_MESSAGES/cordiale-ui.po b/crates/cordiale-ui/lang/de/LC_MESSAGES/cordiale-ui.po index f35c3947..97875ed0 100644 --- a/crates/cordiale-ui/lang/de/LC_MESSAGES/cordiale-ui.po +++ b/crates/cordiale-ui/lang/de/LC_MESSAGES/cordiale-ui.po @@ -2704,6 +2704,10 @@ msgctxt "AppWindow" msgid "Admin actions need an administrator signed in with a full session. A client token can't do them." msgstr "Admin-Aktionen erfordern einen Administrator mit vollständiger Sitzung. Ein Client-Token kann sie nicht ausführen." +msgctxt "AppWindow" +msgid "Couldn't delete that visitor ({}). A visitor with a lot of history can time out on the server; try again, or ask the server admin." +msgstr "Der Besucher konnte nicht gelöscht werden ({}). Ein Besucher mit viel Verlauf kann auf dem Server in ein Timeout laufen: Versuche es erneut oder frage den Serveradministrator." + msgctxt "AppWindow" msgid "That network still has accounts bound to it. Unbind them on the Credentials tab first." msgstr "An dieses Netzwerk sind noch Konten gebunden. Löse sie zuerst im Tab „Zugangsdaten“." diff --git a/crates/cordiale-ui/lang/es/LC_MESSAGES/cordiale-ui.po b/crates/cordiale-ui/lang/es/LC_MESSAGES/cordiale-ui.po index 90ee4c96..8f69d93e 100644 --- a/crates/cordiale-ui/lang/es/LC_MESSAGES/cordiale-ui.po +++ b/crates/cordiale-ui/lang/es/LC_MESSAGES/cordiale-ui.po @@ -2704,6 +2704,10 @@ msgctxt "AppWindow" msgid "Admin actions need an administrator signed in with a full session. A client token can't do them." msgstr "Las acciones de administración requieren un administrador con una sesión completa. Un token de cliente no puede realizarlas." +msgctxt "AppWindow" +msgid "Couldn't delete that visitor ({}). A visitor with a lot of history can time out on the server; try again, or ask the server admin." +msgstr "No se pudo eliminar ese visitante ({}). Un visitante con mucho historial puede agotar el tiempo en el servidor: inténtalo de nuevo o consulta al administrador del servidor." + msgctxt "AppWindow" msgid "That network still has accounts bound to it. Unbind them on the Credentials tab first." msgstr "Esa red todavía tiene cuentas vinculadas. Desvincúlalas primero en la pestaña Credenciales." diff --git a/crates/cordiale-ui/lang/fr/LC_MESSAGES/cordiale-ui.po b/crates/cordiale-ui/lang/fr/LC_MESSAGES/cordiale-ui.po index 8e6087c5..5ab3c2b0 100644 --- a/crates/cordiale-ui/lang/fr/LC_MESSAGES/cordiale-ui.po +++ b/crates/cordiale-ui/lang/fr/LC_MESSAGES/cordiale-ui.po @@ -2704,6 +2704,10 @@ msgctxt "AppWindow" msgid "Admin actions need an administrator signed in with a full session. A client token can't do them." msgstr "Les actions d'administration nécessitent un administrateur connecté avec une session complète. Un jeton client ne peut pas les effectuer." +msgctxt "AppWindow" +msgid "Couldn't delete that visitor ({}). A visitor with a lot of history can time out on the server; try again, or ask the server admin." +msgstr "Impossible de supprimer ce visiteur ({}). Un visiteur avec beaucoup d'historique peut dépasser le délai côté serveur : réessaie ou contacte l'administrateur du serveur." + msgctxt "AppWindow" msgid "That network still has accounts bound to it. Unbind them on the Credentials tab first." msgstr "Des comptes sont encore associés à ce réseau. Dissocie-les d'abord dans l'onglet Identifiants." diff --git a/crates/cordiale-ui/lang/it/LC_MESSAGES/cordiale-ui.po b/crates/cordiale-ui/lang/it/LC_MESSAGES/cordiale-ui.po index 148ff24c..ce761abf 100644 --- a/crates/cordiale-ui/lang/it/LC_MESSAGES/cordiale-ui.po +++ b/crates/cordiale-ui/lang/it/LC_MESSAGES/cordiale-ui.po @@ -2704,6 +2704,10 @@ msgctxt "AppWindow" msgid "Admin actions need an administrator signed in with a full session. A client token can't do them." msgstr "Le azioni di amministrazione richiedono un amministratore con una sessione completa. Un token client non può eseguirle." +msgctxt "AppWindow" +msgid "Couldn't delete that visitor ({}). A visitor with a lot of history can time out on the server; try again, or ask the server admin." +msgstr "Impossibile eliminare quel visitor ({}). Un visitor con molta cronologia può andare in timeout sul server: riprova o contatta l'amministratore del server." + msgctxt "AppWindow" msgid "That network still has accounts bound to it. Unbind them on the Credentials tab first." msgstr "A quella rete sono ancora collegati degli account. Scollegali prima dalla scheda Credenziali." diff --git a/crates/cordiale-ui/src/admin_handlers.rs b/crates/cordiale-ui/src/admin_handlers.rs index 784a7dd1..79cb2067 100644 --- a/crates/cordiale-ui/src/admin_handlers.rs +++ b/crates/cordiale-ui/src/admin_handlers.rs @@ -11,6 +11,18 @@ pub(crate) fn admin_failure_kind(status: Option, network_delete: bool) -> & } } +/// The status line for a failed visitor delete. A server error or a dropped +/// connection is what a slow bulk delete looks like (Grappa's write pool can +/// time out on a visitor with a lot of scrollback), so it gets its own text +/// with a retry hint; anything else follows `admin_failure_kind`. +pub(crate) fn admin_visitor_delete_failure_kind(status: Option) -> &'static str { + match status { + None => "admin-visitor-delete-failed", + Some(code) if code >= 500 => "admin-visitor-delete-failed", + other => admin_failure_kind(other, false), + } +} + /// One `subject_search` row: `(type, id, network, nick)`, `network` empty /// for an account. pub(crate) fn admin_subject_row(row: &Value) -> Option<(String, String, String, String)> { @@ -104,6 +116,9 @@ pub(crate) async fn handle_admin_write( AdminWrite::DeleteNetwork(network_id) => { (client.delete_admin_network(token, network_id).await, "") } + AdminWrite::DeleteVisitor(visitor_id) => { + (client.delete_admin_visitor(token, visitor_id).await, "") + } AdminWrite::AddServer { network_id, host, @@ -267,10 +282,12 @@ pub(crate) async fn handle_admin_write( .map(|status| status.as_u16().to_string()) .unwrap_or_else(|| "network error".to_string()); persistence::log_line(&format!("admin write failed: {status}")); - let kind = admin_failure_kind( - err.status().map(|status| status.as_u16()), - matches!(write, AdminWrite::DeleteNetwork(_)), - ); + let code = err.status().map(|status| status.as_u16()); + let kind = if matches!(write, AdminWrite::DeleteVisitor(_)) { + admin_visitor_delete_failure_kind(code) + } else { + admin_failure_kind(code, matches!(write, AdminWrite::DeleteNetwork(_))) + }; let _ = ui.upgrade_in_event_loop(move |ui| { ui.set_status_command_hint(status.into()); ui.set_status_kind(kind.into()); diff --git a/crates/cordiale-ui/src/main.rs b/crates/cordiale-ui/src/main.rs index ad72a236..0acb0217 100644 --- a/crates/cordiale-ui/src/main.rs +++ b/crates/cordiale-ui/src/main.rs @@ -1969,10 +1969,8 @@ async fn run_worker( handle_admin_refresh(&state, &ui).await; } Some(WorkerCommand::AdminVisitorDelete(visitor_id)) => { - if let (Some(client), Some(token)) = (&state.conn.client, &state.conn.token) { - let _ = client.delete_admin_visitor(token, &visitor_id).await; - } - handle_admin_refresh(&state, &ui).await; + handle_admin_write(&state, &ui, AdminWrite::DeleteVisitor(visitor_id)) + .await; } Some(WorkerCommand::AdminNetworkResetCircuit(network_id)) => { if let (Some(client), Some(token)) = (&state.conn.client, &state.conn.token) { @@ -5708,6 +5706,7 @@ enum AdminWrite { password: String, }, CreateNetwork(String), + DeleteVisitor(String), UpdateNetwork { slug: String, settings: Value, diff --git a/crates/cordiale-ui/src/tests.rs b/crates/cordiale-ui/src/tests.rs index ad04e0cf..1ccdf422 100644 --- a/crates/cordiale-ui/src/tests.rs +++ b/crates/cordiale-ui/src/tests.rs @@ -4657,6 +4657,25 @@ fn a_refused_admin_write_is_told_apart() { assert_eq!(admin_failure_kind(None, true), "admin-action-failed"); } +#[test] +fn a_failed_visitor_delete_says_to_retry() { + // Timeouts and dropped connections look like a slow bulk delete. + for status in [Some(500), Some(503), None] { + assert_eq!( + admin_visitor_delete_failure_kind(status), + "admin-visitor-delete-failed" + ); + } + assert_eq!( + admin_visitor_delete_failure_kind(Some(403)), + "admin-forbidden" + ); + assert_eq!( + admin_visitor_delete_failure_kind(Some(404)), + "admin-action-failed" + ); +} + #[test] fn network_delete_needs_a_count_for_that_network() { let mut state = WorkerState::new(); diff --git a/crates/cordiale-ui/ui/appwindow.slint b/crates/cordiale-ui/ui/appwindow.slint index 3db9b235..fcc15e4e 100644 --- a/crates/cordiale-ui/ui/appwindow.slint +++ b/crates/cordiale-ui/ui/appwindow.slint @@ -2418,6 +2418,8 @@ export component AppWindow inherits Window { return @tr("The server refused that admin action ({}).", status-command-hint); } else if status-kind == "admin-forbidden" { return @tr("Admin actions need an administrator signed in with a full session. A client token can't do them."); + } else if status-kind == "admin-visitor-delete-failed" { + return @tr("Couldn't delete that visitor ({}). A visitor with a lot of history can time out on the server; try again, or ask the server admin.", status-command-hint); } else if status-kind == "admin-network-in-use" { return @tr("That network still has accounts bound to it. Unbind them on the Credentials tab first."); } else if status-kind == "admin-credential-stopped" {