From 70728cfec876d9acad2474f7a28f577d810afff2 Mon Sep 17 00:00:00 2001 From: Abhishekrajpurohit Date: Fri, 19 Jun 2026 18:51:23 +0530 Subject: [PATCH] feat(dashboard): add Kill Stuck button for failed sessions - Add Skull icon button on session cards with status 'failed' - Confirmation modal before force-killing - Call POST /sessions/:id/force-kill via new sessionApi.forceKill() - i18n keys in all 9 locales --- dashboard/src/i18n/locales/ar.json | 15 +++++++- dashboard/src/i18n/locales/en.json | 15 +++++++- dashboard/src/i18n/locales/es.json | 15 +++++++- dashboard/src/i18n/locales/fr.json | 15 +++++++- dashboard/src/i18n/locales/he.json | 15 +++++++- dashboard/src/i18n/locales/it.json | 15 +++++++- dashboard/src/i18n/locales/te.json | 15 +++++++- dashboard/src/i18n/locales/zh-CN.json | 15 +++++++- dashboard/src/i18n/locales/zh-HK.json | 15 +++++++- dashboard/src/pages/Sessions.tsx | 54 ++++++++++++++++++++++++++- dashboard/src/services/api.ts | 1 + 11 files changed, 171 insertions(+), 19 deletions(-) diff --git a/dashboard/src/i18n/locales/ar.json b/dashboard/src/i18n/locales/ar.json index dc085535..5aaae46b 100644 --- a/dashboard/src/i18n/locales/ar.json +++ b/dashboard/src/i18n/locales/ar.json @@ -226,7 +226,8 @@ "start": "بدء", "stop": "إيقاف", "reconnect": "إعادة الاتصال", - "delete": "حذف" + "delete": "حذف", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "الجلسة جاهزة", @@ -241,6 +242,16 @@ "sessionId": "معرّف الجلسة", "lastActive": "آخر نشاط", "error": "خطأ" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "فشل الحفظ" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/en.json b/dashboard/src/i18n/locales/en.json index a161c8b8..c7a36f37 100644 --- a/dashboard/src/i18n/locales/en.json +++ b/dashboard/src/i18n/locales/en.json @@ -226,7 +226,8 @@ "start": "Start", "stop": "Stop", "reconnect": "Reconnect", - "delete": "Delete" + "delete": "Delete", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "Session Ready", @@ -241,6 +242,16 @@ "sessionId": "Session ID", "lastActive": "Last Active", "error": "Error" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "Save Failed" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/es.json b/dashboard/src/i18n/locales/es.json index 5ad73384..b885ca5f 100644 --- a/dashboard/src/i18n/locales/es.json +++ b/dashboard/src/i18n/locales/es.json @@ -226,7 +226,8 @@ "start": "Iniciar", "stop": "Detener", "reconnect": "Reconectar", - "delete": "Eliminar" + "delete": "Eliminar", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "Sesión lista", @@ -241,6 +242,16 @@ "sessionId": "ID de sesión", "lastActive": "Última actividad", "error": "Error" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "Error al guardar" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/fr.json b/dashboard/src/i18n/locales/fr.json index b2ebe209..e8d82293 100644 --- a/dashboard/src/i18n/locales/fr.json +++ b/dashboard/src/i18n/locales/fr.json @@ -226,7 +226,8 @@ "start": "Démarrer", "stop": "Arrêter", "reconnect": "Reconnecter", - "delete": "Supprimer" + "delete": "Supprimer", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "Session prête", @@ -241,6 +242,16 @@ "sessionId": "ID de session", "lastActive": "Dernière activité", "error": "Erreur" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "Échec de l'enregistrement" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/he.json b/dashboard/src/i18n/locales/he.json index bde4b580..2ada3b55 100644 --- a/dashboard/src/i18n/locales/he.json +++ b/dashboard/src/i18n/locales/he.json @@ -226,7 +226,8 @@ "start": "הפעלה", "stop": "עצירה", "reconnect": "התחברות מחדש", - "delete": "מחיקה" + "delete": "מחיקה", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "ה-Session מוכן", @@ -241,6 +242,16 @@ "sessionId": "מזהה Session", "lastActive": "פעילות אחרונה", "error": "שגיאה" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "השמירה נכשלה" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/it.json b/dashboard/src/i18n/locales/it.json index 20b1c3ee..05b6f192 100644 --- a/dashboard/src/i18n/locales/it.json +++ b/dashboard/src/i18n/locales/it.json @@ -226,7 +226,8 @@ "start": "Avvia", "stop": "Ferma", "reconnect": "Riconnetti", - "delete": "Elimina" + "delete": "Elimina", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "Sessione Pronta", @@ -241,6 +242,16 @@ "sessionId": "ID Sessione", "lastActive": "Ultima Attività", "error": "Errore" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "Salvataggio non riuscito" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/te.json b/dashboard/src/i18n/locales/te.json index a1cd99a2..9c4ebe91 100644 --- a/dashboard/src/i18n/locales/te.json +++ b/dashboard/src/i18n/locales/te.json @@ -226,7 +226,8 @@ "start": "ప్రారంభించు", "stop": "ఆపివేయి", "reconnect": "మళ్లీ కనెక్ట్ చేయి", - "delete": "తొలగించు" + "delete": "తొలగించు", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "సెషన్ సిద్ధంగా ఉంది", @@ -241,6 +242,16 @@ "sessionId": "సెషన్ ID", "lastActive": "చివరిసారి యాక్టివ్", "error": "లోపం" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "సేవ్ చేయడం విఫలమైంది" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/zh-CN.json b/dashboard/src/i18n/locales/zh-CN.json index 75897bcb..a0de1dde 100644 --- a/dashboard/src/i18n/locales/zh-CN.json +++ b/dashboard/src/i18n/locales/zh-CN.json @@ -226,7 +226,8 @@ "start": "启动", "stop": "停止", "reconnect": "重新连接", - "delete": "删除" + "delete": "删除", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "会话就绪", @@ -241,6 +242,16 @@ "sessionId": "会话 ID", "lastActive": "上次活跃", "error": "错误" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "保存失败" } } -} +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/zh-HK.json b/dashboard/src/i18n/locales/zh-HK.json index 76794cea..cd176394 100644 --- a/dashboard/src/i18n/locales/zh-HK.json +++ b/dashboard/src/i18n/locales/zh-HK.json @@ -226,7 +226,8 @@ "start": "啟動", "stop": "停止", "reconnect": "重新連線", - "delete": "刪除" + "delete": "刪除", + "killStuck": "Kill Stuck" }, "toasts": { "readyTitle": "工作階段就緒", @@ -241,6 +242,16 @@ "sessionId": "工作階段 ID", "lastActive": "上次活躍", "error": "錯誤" + }, + "forceKill": { + "title": "Kill Stuck Session", + "message": "Are you sure you want to force-kill session \"{{name}}\"?", + "warning": "This will forcefully terminate the WhatsApp browser process. The session may need to be re-scanned on restart.", + "confirm": "Kill Session", + "successTitle": "Session Killed", + "success": "Session has been force-killed. You can restart it now.", + "failedTitle": "Force-Kill Failed", + "failed": "Failed to force-kill the session." } }, "webhooks": { @@ -605,4 +616,4 @@ "saveFailed": "儲存失敗" } } -} +} \ No newline at end of file diff --git a/dashboard/src/pages/Sessions.tsx b/dashboard/src/pages/Sessions.tsx index 122e74c9..f2df010f 100644 --- a/dashboard/src/pages/Sessions.tsx +++ b/dashboard/src/pages/Sessions.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { Plus, QrCode, RefreshCw, Trash2, Eye, Loader2, Play, Square, X, Search, Filter } from 'lucide-react'; +import { Plus, QrCode, RefreshCw, Trash2, Eye, Loader2, Play, Square, X, Search, Filter, Skull } from 'lucide-react'; import { sessionApi, type Session } from '../services/api'; import { useDocumentTitle } from '../hooks/useDocumentTitle'; import { useToast } from '../components/Toast'; @@ -25,6 +25,7 @@ export function Sessions() { const [statusFilter, setStatusFilter] = useState('all'); const [selectedSession, setSelectedSession] = useState(null); const [deleteConfirmId, setDeleteConfirmId] = useState(null); + const [killConfirmId, setKillConfirmId] = useState(null); const fetchSessions = useCallback(async (): Promise => { try { @@ -204,6 +205,20 @@ export function Sessions() { } }; + const handleForceKill = async (id: string) => { + try { + await sessionApi.forceKill(id); + setSessions(sessions.map(s => (s.id === id ? { ...s, status: 'disconnected' } : s))); + toast.success(t('sessions.forceKill.successTitle'), t('sessions.forceKill.success')); + } catch (err) { + console.error('Failed to force-kill:', err); + toast.error(t('sessions.forceKill.failedTitle'), t('sessions.forceKill.failed')); + fetchSessions(); + } finally { + setKillConfirmId(null); + } + }; + const formatLastActive = (date?: string) => { if (!date) return t('common.never'); const diff = Date.now() - new Date(date).getTime(); @@ -462,6 +477,37 @@ export function Sessions() { )} + {killConfirmId && ( +
setKillConfirmId(null)}> +
e.stopPropagation()}> +
+

{t('sessions.forceKill.title')}

+ +
+
+

+ s.id === killConfirmId)?.name }} + components={{ strong: }} + /> +

+

{t('sessions.forceKill.warning')}

+
+
+ + +
+
+
+ )} +
{filteredSessions.length === 0 ? (
@@ -542,6 +588,12 @@ export function Sessions() { {t('sessions.actions.delete')} )} + {canWrite && session.status === 'failed' && ( + + )}
)) diff --git a/dashboard/src/services/api.ts b/dashboard/src/services/api.ts index 34f8f8b5..b7bb1e40 100644 --- a/dashboard/src/services/api.ts +++ b/dashboard/src/services/api.ts @@ -317,6 +317,7 @@ export const sessionApi = { delete: (id: string) => request(`/sessions/${id}`, { method: 'DELETE' }), start: (id: string) => request(`/sessions/${id}/start`, { method: 'POST' }), stop: (id: string) => request(`/sessions/${id}/stop`, { method: 'POST' }), + forceKill: (id: string) => request(`/sessions/${id}/force-kill`, { method: 'POST' }), getQR: (id: string) => request<{ qrCode: string; status: string }>(`/sessions/${id}/qr`), getStats: () => request('/sessions/stats/overview'), getGroups: (id: string) =>