From d30d732c70d2f3f2f8f9588ab453b5a39597ccaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Fri, 10 Jul 2026 13:28:47 -0600 Subject: [PATCH] fix[frontend](): added missing confirm modal and language keys --- .../pages/AlertingRulesPage.tsx | 25 +++++- .../src/features/alerts/pages/AlertsPage.tsx | 30 ++++++- .../alerts/pages/TaggingRulesPage.tsx | 30 ++++++- .../components/incident-alerts-tab.tsx | 85 +++++++++++-------- .../hooks/use-incident-alerts-tab.ts | 45 ++++++---- frontend/src/shared/i18n/locales/de.json | 7 +- frontend/src/shared/i18n/locales/en.json | 7 +- frontend/src/shared/i18n/locales/es.json | 7 +- frontend/src/shared/i18n/locales/fr.json | 7 +- frontend/src/shared/i18n/locales/it.json | 7 +- frontend/src/shared/i18n/locales/pt.json | 7 +- frontend/src/shared/i18n/locales/ru.json | 7 +- 12 files changed, 186 insertions(+), 78 deletions(-) diff --git a/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx b/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx index 0ebb08e44..175fc65d7 100644 --- a/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx +++ b/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx @@ -25,6 +25,7 @@ import { cn } from '@/shared/lib/utils' import { Button } from '@/shared/components/ui/button' import { Input } from '@/shared/components/ui/input' import { InfiniteScrollSentinel } from '@/shared/components/ui/infinite-scroll' +import { ConfirmDialog } from '@/shared/components/ui/confirm-dialog' import { YamlCodeEditor } from '@/shared/components/YamlCodeEditor' import { useDateFormat } from '@/shared/lib/datetime' import { @@ -79,6 +80,8 @@ export function AlertingRulesPage() { const [dataTypeOptions, setDataTypeOptions] = useState([]) const [open, setOpen] = useState(null) const [creating, setCreating] = useState(false) + const [pendingDelete, setPendingDelete] = useState(null) + const [deleting, setDeleting] = useState(false) const fileInputRef = useRef(null) const [importBusy, setImportBusy] = useState(false) @@ -162,15 +165,21 @@ export function AlertingRulesPage() { } } - const remove = async (r: CorrelationRule) => { - if (!confirm(t('alertingRules.deleteConfirm', { name: r.name }))) return + const remove = (r: CorrelationRule) => setPendingDelete(r) + + const confirmDelete = async () => { + if (!pendingDelete) return + setDeleting(true) try { - await svc.remove(r.relPath) + await svc.remove(pendingDelete.relPath) toast.success(t('alertingRules.toast.deleted')) setOpen(null) refresh() } catch (e) { toast.error(e instanceof AlertingRulesHttpError ? e.message : t('alertingRules.toast.deleteError')) + } finally { + setPendingDelete(null) + setDeleting(false) } } @@ -259,6 +268,16 @@ export function AlertingRulesPage() { {open && setOpen(null)} onToggle={toggleActive} onDelete={remove} onSaved={() => { setOpen(null); refresh() }} t={t} />} {creating && setCreating(false)} onSaved={() => { setCreating(false); refresh() }} t={t} />} {importResults && setImportResults(null)} t={t} />} + !deleting && setPendingDelete(null)} + onConfirm={confirmDelete} + /> ) } diff --git a/frontend/src/features/alerts/pages/AlertsPage.tsx b/frontend/src/features/alerts/pages/AlertsPage.tsx index 6e4f8675a..1a853f731 100644 --- a/frontend/src/features/alerts/pages/AlertsPage.tsx +++ b/frontend/src/features/alerts/pages/AlertsPage.tsx @@ -4,6 +4,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Button } from '@/shared/components/ui/button' import { InfiniteScrollSentinel } from '@/shared/components/ui/infinite-scroll' +import { ConfirmDialog } from '@/shared/components/ui/confirm-dialog' import { presetRange, type TimeRange } from '@/shared/components/ui/time-range-picker' import { FILTER_OPS, TS } from '../lib/alert-meta' import { alertToRuleConditions } from '../lib/tagging-rule-meta' @@ -65,6 +66,8 @@ export function AlertsPage() { | { kind: 'create'; tags?: AlertTag[]; conditions?: FilterType[] } | null >(null) + const [pendingDelete, setPendingDelete] = useState(null) + const [deleting, setDeleting] = useState(false) const toggleEchoes = (id: string) => setExpandedEchoes((prev) => { @@ -212,10 +215,18 @@ export function AlertsPage() { if (ok) setRuleDrawer(null) } - const removeRule = async (r: TaggingRule) => { - if (!confirm(t('taggingRules.deleteConfirm', { name: r.name }))) return - const ok = await deleteRule(r.id, r.name) - if (ok) setRuleDrawer(null) + const removeRule = async (r: TaggingRule) => setPendingDelete(r) + + const confirmDelete = async () => { + if (!pendingDelete) return + setDeleting(true) + try { + const ok = await deleteRule(pendingDelete.id, pendingDelete.name) + if (ok) setRuleDrawer(null) + } finally { + setDeleting(false) + setPendingDelete(null) + } } const selectedAlerts = useMemo(() => alerts.filter((a) => selected.has(a.id)), [alerts, selected]) @@ -440,6 +451,17 @@ export function AlertsPage() { onCreateTag={createTag} /> )} + + !deleting && setPendingDelete(null)} + onConfirm={confirmDelete} + /> ) } diff --git a/frontend/src/features/alerts/pages/TaggingRulesPage.tsx b/frontend/src/features/alerts/pages/TaggingRulesPage.tsx index 14c297ec8..cdffe427d 100644 --- a/frontend/src/features/alerts/pages/TaggingRulesPage.tsx +++ b/frontend/src/features/alerts/pages/TaggingRulesPage.tsx @@ -5,6 +5,7 @@ import { cn } from '@/shared/lib/utils' import { Button } from '@/shared/components/ui/button' import { Input } from '@/shared/components/ui/input' import { InfiniteScrollSentinel } from '@/shared/components/ui/infinite-scroll' +import { ConfirmDialog } from '@/shared/components/ui/confirm-dialog' import { useTaggingRulesList } from '../hooks/use-tagging-rules-list' import { useTaggingRuleMutations } from '../hooks/use-tagging-rule-mutations' import { useAlertTagCatalog } from '../hooks/use-alert-tag-catalog' @@ -24,6 +25,8 @@ export function TaggingRulesPage() { const [open, setOpen] = useState(null) const [creating, setCreating] = useState(false) + const [pendingDelete, setPendingDelete] = useState(null) + const [deleting, setDeleting] = useState(false) // Debounce the search box. useEffect(() => { @@ -59,10 +62,18 @@ export function TaggingRulesPage() { } } - const remove = async (r: TaggingRule) => { - if (!confirm(t('taggingRules.deleteConfirm', { name: r.name }))) return - const ok = await deleteRule(r.id, r.name) - if (ok) setOpen(null) + const remove = async (r: TaggingRule) => setPendingDelete(r) + + const confirmDelete = async () => { + if (!pendingDelete) return + setDeleting(true) + try { + const ok = await deleteRule(pendingDelete.id, pendingDelete.name) + if (ok) setOpen(null) + } finally { + setDeleting(false) + setPendingDelete(null) + } } return ( @@ -158,6 +169,17 @@ export function TaggingRulesPage() { onCreateTag={createTag} /> )} + + !deleting && setPendingDelete(null)} + onConfirm={confirmDelete} + /> ) } diff --git a/frontend/src/features/incidents/components/incident-alerts-tab.tsx b/frontend/src/features/incidents/components/incident-alerts-tab.tsx index 65a2bc1e1..3851d6242 100644 --- a/frontend/src/features/incidents/components/incident-alerts-tab.tsx +++ b/frontend/src/features/incidents/components/incident-alerts-tab.tsx @@ -2,53 +2,66 @@ import { Trash2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' import { cn } from '@/shared/lib/utils' +import { ConfirmDialog } from '@/shared/components/ui/confirm-dialog' import { sevKey } from '../lib/incident-meta' import { useIncidentAlertsTab } from '../hooks/use-incident-alerts-tab' import { TabEmpty, TabError, TabLoader } from './ui-primitives' export function IncidentAlertsTab({ incidentId, onChanged }: { incidentId: number; onChanged: () => void }) { const { t } = useTranslation() - const { rows, error, reload, remove } = useIncidentAlertsTab(incidentId, onChanged) + const { rows, error, reload, remove, pendingDelete, deleting, cancelDelete, confirmDelete } = useIncidentAlertsTab(incidentId, onChanged) if (error) return if (rows === null) return if (rows.length === 0) return {t('incidents.alerts.empty')} return ( -
- {rows.map((a) => ( - - - - {a.alertName} - - - {t(`incidents.sev.${sevKey(a.alertSeverity)}`)} - - - - ))} -
+ + + {a.alertName} + + + {t(`incidents.sev.${sevKey(a.alertSeverity)}`)} + + + + ))} + + !deleting && cancelDelete()} + onConfirm={confirmDelete} + /> + ) } diff --git a/frontend/src/features/incidents/hooks/use-incident-alerts-tab.ts b/frontend/src/features/incidents/hooks/use-incident-alerts-tab.ts index 087437d6f..383879f20 100644 --- a/frontend/src/features/incidents/hooks/use-incident-alerts-tab.ts +++ b/frontend/src/features/incidents/hooks/use-incident-alerts-tab.ts @@ -8,7 +8,11 @@ export interface UseIncidentAlertsTabResult { rows: IncidentAlert[] | null error: boolean reload: () => Promise - remove: (a: IncidentAlert) => Promise + remove: (a: IncidentAlert) => void + pendingDelete: IncidentAlert | null + deleting: boolean + cancelDelete: () => void + confirmDelete: () => Promise } /** Data + remove-alert mutation for the incident drawer's "Alerts" tab. */ @@ -16,6 +20,8 @@ export function useIncidentAlertsTab(incidentId: number, onChanged: () => void): const { t } = useTranslation() const [rows, setRows] = useState(null) const [error, setError] = useState(false) + const [pendingDelete, setPendingDelete] = useState(null) + const [deleting, setDeleting] = useState(false) const load = useCallback(async () => { setError(false) @@ -31,20 +37,25 @@ export function useIncidentAlertsTab(incidentId: number, onChanged: () => void): void load() }, [load]) - const remove = useCallback( - async (a: IncidentAlert) => { - if (!confirm(t('incidents.alerts.removeConfirm', { name: a.alertName }))) return - try { - await svc.removeAlert(a.id) - toast.success(t('incidents.alerts.removed')) - await load() - onChanged() - } catch (e) { - toast.error(e instanceof IncidentsHttpError ? e.message : t('incidents.alerts.removeError')) - } - }, - [load, onChanged, t] - ) - - return { rows, error, reload: load, remove } + const remove = useCallback((a: IncidentAlert) => setPendingDelete(a), []) + + const cancelDelete = useCallback(() => setPendingDelete(null), []) + + const confirmDelete = useCallback(async () => { + if (!pendingDelete) return + setDeleting(true) + try { + await svc.removeAlert(pendingDelete.id) + toast.success(t('incidents.alerts.removed')) + await load() + onChanged() + } catch (e) { + toast.error(e instanceof IncidentsHttpError ? e.message : t('incidents.alerts.removeError')) + } finally { + setDeleting(false) + setPendingDelete(null) + } + }, [pendingDelete, load, onChanged, t]) + + return { rows, error, reload: load, remove, pendingDelete, deleting, cancelDelete, confirmDelete } } diff --git a/frontend/src/shared/i18n/locales/de.json b/frontend/src/shared/i18n/locales/de.json index 0ce042950..c0853389b 100644 --- a/frontend/src/shared/i18n/locales/de.json +++ b/frontend/src/shared/i18n/locales/de.json @@ -18,7 +18,9 @@ "save": "Speichern", "cancel": "Abbrechen", "retry": "Wiederholen", - "refresh": "Aktualisieren" + "refresh": "Aktualisieren", + "delete": "Löschen", + "remove": "Entfernen" }, "confirm": { "delete": "Löschen", @@ -5032,6 +5034,7 @@ "createError": "Regel konnte nicht erstellt werden.", "updateError": "Regel konnte nicht aktualisiert werden.", "deleteError": "Regel konnte nicht gelöscht werden." - } + }, + "deleteTitle": "Tagging-Regel löschen" } } diff --git a/frontend/src/shared/i18n/locales/en.json b/frontend/src/shared/i18n/locales/en.json index 922b5460b..bc7b71341 100644 --- a/frontend/src/shared/i18n/locales/en.json +++ b/frontend/src/shared/i18n/locales/en.json @@ -18,7 +18,9 @@ "save": "Save", "cancel": "Cancel", "retry": "Retry", - "refresh": "Refresh" + "refresh": "Refresh", + "delete": "Delete", + "remove": "Remove" }, "confirm": { "delete": "Delete", @@ -4248,7 +4250,8 @@ "createError": "Failed to create rule.", "updateError": "Failed to update rule.", "deleteError": "Failed to delete rule." - } + }, + "deleteTitle": "Delete tagging rule" }, "incidents": { "title": "Incidents", diff --git a/frontend/src/shared/i18n/locales/es.json b/frontend/src/shared/i18n/locales/es.json index c947a3444..ec8d67b03 100644 --- a/frontend/src/shared/i18n/locales/es.json +++ b/frontend/src/shared/i18n/locales/es.json @@ -18,7 +18,9 @@ "save": "Guardar", "cancel": "Cancelar", "retry": "Reintentar", - "refresh": "Actualizar" + "refresh": "Actualizar", + "delete": "Eliminar", + "remove": "Quitar" }, "confirm": { "delete": "Eliminar", @@ -4994,6 +4996,7 @@ "createError": "No se pudo crear la regla.", "updateError": "No se pudo actualizar la regla.", "deleteError": "No se pudo eliminar la regla." - } + }, + "deleteTitle": "Eliminar regla de etiquetado" } } diff --git a/frontend/src/shared/i18n/locales/fr.json b/frontend/src/shared/i18n/locales/fr.json index a1809fc02..9e12b6a28 100644 --- a/frontend/src/shared/i18n/locales/fr.json +++ b/frontend/src/shared/i18n/locales/fr.json @@ -18,7 +18,9 @@ "save": "Enregistrer", "cancel": "Annuler", "retry": "Réessayer", - "refresh": "Actualiser" + "refresh": "Actualiser", + "delete": "Supprimer", + "remove": "Retirer" }, "confirm": { "delete": "Supprimer", @@ -5032,6 +5034,7 @@ "createError": "Échec de la création de la règle.", "updateError": "Échec de la mise à jour de la règle.", "deleteError": "Échec de la suppression de la règle." - } + }, + "deleteTitle": "Supprimer la règle d'étiquetage" } } diff --git a/frontend/src/shared/i18n/locales/it.json b/frontend/src/shared/i18n/locales/it.json index 9e8d85d5b..dfb777b63 100644 --- a/frontend/src/shared/i18n/locales/it.json +++ b/frontend/src/shared/i18n/locales/it.json @@ -18,7 +18,9 @@ "save": "Salva", "cancel": "Annulla", "retry": "Riprova", - "refresh": "Aggiorna" + "refresh": "Aggiorna", + "delete": "Elimina", + "remove": "Rimuovi" }, "confirm": { "delete": "Elimina", @@ -5032,6 +5034,7 @@ "createError": "Impossibile creare la regola.", "updateError": "Impossibile aggiornare la regola.", "deleteError": "Impossibile eliminare la regola." - } + }, + "deleteTitle": "Elimina regola di tagging" } } diff --git a/frontend/src/shared/i18n/locales/pt.json b/frontend/src/shared/i18n/locales/pt.json index ddcb8c892..3680383c0 100644 --- a/frontend/src/shared/i18n/locales/pt.json +++ b/frontend/src/shared/i18n/locales/pt.json @@ -18,7 +18,9 @@ "save": "Salvar", "cancel": "Cancelar", "retry": "Tentar novamente", - "refresh": "Atualizar" + "refresh": "Atualizar", + "delete": "Excluir", + "remove": "Remover" }, "confirm": { "delete": "Excluir", @@ -4994,6 +4996,7 @@ "createError": "Falha ao criar a regra.", "updateError": "Falha ao atualizar a regra.", "deleteError": "Falha ao excluir a regra." - } + }, + "deleteTitle": "Excluir regra de marcação" } } diff --git a/frontend/src/shared/i18n/locales/ru.json b/frontend/src/shared/i18n/locales/ru.json index 3344d94db..8546a533e 100644 --- a/frontend/src/shared/i18n/locales/ru.json +++ b/frontend/src/shared/i18n/locales/ru.json @@ -18,7 +18,9 @@ "save": "Сохранить", "cancel": "Отмена", "retry": "Повторить", - "refresh": "Обновить" + "refresh": "Обновить", + "delete": "Удалить", + "remove": "Убрать" }, "confirm": { "delete": "Удалить", @@ -5058,6 +5060,7 @@ "createError": "Не удалось создать правило.", "updateError": "Не удалось обновить правило.", "deleteError": "Не удалось удалить правило." - } + }, + "deleteTitle": "Удалить правило тегирования" } }