diff --git a/frontend/src/pages/AnnouncementPage.tsx b/frontend/src/pages/AnnouncementPage.tsx index 2200293..14c8b30 100644 --- a/frontend/src/pages/AnnouncementPage.tsx +++ b/frontend/src/pages/AnnouncementPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import request from '@/api/request'; import { Megaphone, Plus, Edit2, Trash2, Eye, Clock, CheckCircle, X, Save } from 'lucide-react'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; /* ── types ── */ interface Announcement { @@ -56,6 +57,7 @@ export default function AnnouncementPage() { /* view preview */ const [viewAnn, setViewAnn] = useState(null); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); const fetchList = useCallback(async () => { try { @@ -118,13 +120,8 @@ export default function AnnouncementPage() { }; const handleDelete = async (id: number) => { - if (!confirm('确定删除此公告?')) return; - try { - await request.delete(`/announcements/${id}`); - fetchList(); - } catch { - /* ignore */ - } + setDeleteConfirm({msg: '确定删除此公告?', onConfirm: async () => { try { await request.delete(`/announcements/${id}`); fetchList(); } catch {} }}); + return; }; /* ── render ── */ @@ -344,6 +341,14 @@ export default function AnnouncementPage() { )} + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); } diff --git a/frontend/src/pages/InstanceDetailPage.tsx b/frontend/src/pages/InstanceDetailPage.tsx index a98a31d..cf47092 100644 --- a/frontend/src/pages/InstanceDetailPage.tsx +++ b/frontend/src/pages/InstanceDetailPage.tsx @@ -4,6 +4,7 @@ import { ArrowLeft, Database, Table2, Users, Cpu, Play, RefreshCw } from 'lucide import { instanceDetailApi } from '@/api/instanceDetail'; import { instanceApi } from '@/api/instance'; import { queryApi } from '@/api/query'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; interface InstanceInfo { id: number; name: string; db_type: string; host: string; port: number; @@ -27,6 +28,7 @@ export default function InstanceDetailPage() { var [sqlText, setSqlText] = useState(''); var [queryResult, setQueryResult] = useState(null); var [loading, setLoading] = useState(false); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); var tabs: { key: TabKey; label: string; icon: React.ElementType }[] = [ { key: 'overview', label: '概览', icon: Database }, @@ -81,12 +83,17 @@ export default function InstanceDetailPage() { }; var handleKill = function (pid: number) { - if (!confirm('确定 Kill 进程 ' + pid + '?')) return; - instanceDetailApi.killProcess(instanceId, pid).then(function () { - instanceDetailApi.getProcesslist(instanceId).then(function (res: any) { - setProcesses(res.processes || res || []); - }); - }).catch(function () {}); + setDeleteConfirm({ + msg: '确定 Kill 进程 ' + pid + '?', + onConfirm: () => { + instanceDetailApi.killProcess(instanceId, pid).then(() => { + instanceDetailApi.getProcesslist(instanceId).then((res: any) => { + setProcesses(res.processes || res || []); + }); + }); + } + }); + return; }; if (!instance) return
加载中...
; @@ -255,6 +262,15 @@ export default function InstanceDetailPage() { {processes.length === 0 &&
暂无进程
} )} + + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); } diff --git a/frontend/src/pages/InstanceGroupPage.tsx b/frontend/src/pages/InstanceGroupPage.tsx index f88dd77..cf3fe70 100644 --- a/frontend/src/pages/InstanceGroupPage.tsx +++ b/frontend/src/pages/InstanceGroupPage.tsx @@ -8,6 +8,7 @@ import { instanceGroupApi, type InstanceGroup } from '@/api/instanceGroup'; import { instanceApi } from '@/api/instance'; import { userApi } from '@/api/user'; import { useActionPermission } from '@/hooks/useAuth'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; /* ───────── Constants ───────── */ @@ -109,6 +110,7 @@ export default function InstanceGroupPage() { const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showUserModal, setShowUserModal] = useState(false); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); const canManage = useActionPermission('instance-group.manage'); // Form state — shared by create & edit @@ -215,10 +217,11 @@ export default function InstanceGroupPage() { /* ── Delete ── */ const handleDelete = async (id: number) => { - if (!confirm('确定删除该权限组?')) return; - await instanceGroupApi.delete(id); - if (selectedId === id) setSelectedId(null); - fetchGroups(); + setDeleteConfirm({msg: '确定删除该权限组?', onConfirm: async () => { + await instanceGroupApi.delete(id); + if (selectedId === id) setSelectedId(null); + fetchGroups(); + }}); }; /* ── Assign Users ── */ @@ -439,6 +442,15 @@ export default function InstanceGroupPage() { + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> + {/* User Modal */} {showUserModal && (
diff --git a/frontend/src/pages/InstancePage.tsx b/frontend/src/pages/InstancePage.tsx index b53ca64..ee92abf 100644 --- a/frontend/src/pages/InstancePage.tsx +++ b/frontend/src/pages/InstancePage.tsx @@ -12,6 +12,7 @@ import PageHeader from '@/components/PageHeader'; import EmptyState from '@/components/EmptyState'; import { instanceApi } from '@/api/instance'; import EditInstanceDialog from '@/components/EditInstanceDialog'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; /* ------------------------------------------------------------------ */ /* Types */ @@ -189,29 +190,28 @@ export default function InstancePage() { }, [filtered]); const handleDelete = useCallback(async (id: number) => { - if (!confirm('确定删除该实例?')) return; - try { - await instanceApi.delete(id); - fetchInstances(); - } catch { - /* ignore */ - } + setDeleteConfirm({ + msg: '确定删除该实例?', + onConfirm: async () => { + try { await instanceApi.delete(id); fetchInstances(); } catch {} + }, + }); }, [fetchInstances]); const handleBatchDelete = useCallback(async () => { if (selectedIds.size === 0) return; - if (!confirm(`确定删除选中的 ${selectedIds.size} 个实例?此操作不可恢复。`)) return; - setBatchDeleting(true); - try { - const ids = Array.from(selectedIds); - await Promise.all(ids.map(id => instanceApi.delete(id))); - setSelectedIds(new Set()); - fetchInstances(); - } catch { - /* ignore */ - } finally { - setBatchDeleting(false); - } + setDeleteConfirm({ + msg: `确定删除选中的 ${selectedIds.size} 个实例?此操作不可恢复。`, + onConfirm: async () => { + setBatchDeleting(true); + try { + const ids = Array.from(selectedIds); + await Promise.all(ids.map(i => instanceApi.delete(i))); + setSelectedIds(new Set()); + fetchInstances(); + } finally { setBatchDeleting(false); } + }, + }); }, [selectedIds, fetchInstances]); const toggleSelect = useCallback((id: number) => { @@ -994,6 +994,7 @@ function CreateInstanceForm({ defaultType, onCancel, onCreated }: { const [error, setError] = useState(''); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string; version?: string } | null>(null); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); const isRedis = dbType === 'redis'; const needUsername = !isRedis; @@ -1447,6 +1448,14 @@ function CreateInstanceForm({ defaultType, onCancel, onCreated }: {
+ { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); } diff --git a/frontend/src/pages/NotificationPage.tsx b/frontend/src/pages/NotificationPage.tsx index d67fb94..a14b606 100644 --- a/frontend/src/pages/NotificationPage.tsx +++ b/frontend/src/pages/NotificationPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import request from '@/api/request'; import { Plus, Trash2, Send, Bell, Mail, Settings, X } from 'lucide-react'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; /* ── types ── */ type ChannelType = 'dingtalk' | 'webhook_feishu' | 'email'; @@ -62,6 +63,7 @@ export default function NotificationPage() { /* test */ const [testingId, setTestingId] = useState(null); const [testResult, setTestResult] = useState>({}); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); const fetchList = useCallback(async () => { try { @@ -128,13 +130,8 @@ export default function NotificationPage() { }; const handleDelete = async (id: number) => { - if (!confirm('确定删除此通知渠道?')) return; - try { - await request.delete(`/notifications/${id}`); - fetchList(); - } catch { - /* ignore */ - } + setDeleteConfirm({msg: '确定删除此通知渠道?', onConfirm: async () => { try { await request.delete(`/notifications/${id}`); fetchList(); } catch {} }}); + return; }; const handleTest = async (id: number) => { @@ -451,6 +448,14 @@ export default function NotificationPage() { )} + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); } diff --git a/frontend/src/pages/NotificationsPage.tsx b/frontend/src/pages/NotificationsPage.tsx index addd678..4e787e8 100644 --- a/frontend/src/pages/NotificationsPage.tsx +++ b/frontend/src/pages/NotificationsPage.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { Bell, Webhook, Check, Settings, TestTube, ToggleLeft, ToggleRight, Loader2, AlertCircle, Plus, Trash2 } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { notificationApi } from '@/api/notification'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; interface NotificationChannel { id: number; @@ -25,6 +26,7 @@ export default function NotificationsPage() { const [error, setError] = useState(null); const [testingId, setTestingId] = useState(null); const [testResult, setTestResult] = useState<{ id: number; success: boolean; message: string } | null>(null); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); const fetchChannels = useCallback(async () => { try { @@ -67,11 +69,8 @@ export default function NotificationsPage() { }, []); const handleDelete = useCallback(async (id: number) => { - if (!confirm('确认删除该通知渠道?')) return; - try { - await notificationApi.delete(id); - setChannels(prev => prev.filter(c => c.id !== id)); - } catch {} + setDeleteConfirm({msg: '确认删除该通知渠道?', onConfirm: async () => { try { await notificationApi.delete(id); setChannels(prev => prev.filter(c => c.id !== id)); } catch {} }}); + return; }, []); if (loading) { @@ -175,6 +174,14 @@ export default function NotificationsPage() { )} + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); } diff --git a/frontend/src/pages/RedisDetailPage.tsx b/frontend/src/pages/RedisDetailPage.tsx index f9ec823..e500da0 100644 --- a/frontend/src/pages/RedisDetailPage.tsx +++ b/frontend/src/pages/RedisDetailPage.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { ArrowLeft, Key, Activity, Terminal, Gauge, Plus, RefreshCw, Search } from 'lucide-react'; import { redisApi } from '@/api/redis'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; type TabKey = 'keys' | 'monitor' | 'cli' | 'perf'; @@ -24,6 +25,7 @@ export default function RedisDetailPage() { var [slowlog, setSlowlog] = useState([]); var [clients, setClients] = useState([]); var [loading, setLoading] = useState(false); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); var tabs: { key: TabKey; label: string; icon: React.ElementType }[] = [ { key: 'keys', label: '键管理', icon: Key }, @@ -54,8 +56,10 @@ export default function RedisDetailPage() { }; var handleDeleteKey = function (keyName: string) { - if (!confirm('确定删除键 ' + keyName + '?')) return; - redisApi.deleteKey(instanceId, keyName, selectedDb).then(function () { fetchKeys(); }).catch(function () {}); + setDeleteConfirm({msg: '确定删除该键?', onConfirm: async () => { + redisApi.deleteKey(instanceId, keyName, selectedDb).then(function () { fetchKeys(); }).catch(function () {}); + }}); + return; }; var handleCommand = function () { @@ -304,6 +308,14 @@ export default function RedisDetailPage() { )} + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); } diff --git a/frontend/src/pages/UsersPage.tsx b/frontend/src/pages/UsersPage.tsx index 7f5973a..c3ef98f 100644 --- a/frontend/src/pages/UsersPage.tsx +++ b/frontend/src/pages/UsersPage.tsx @@ -10,6 +10,7 @@ import { userApi, ldapApi } from '@/api/user'; import { instanceGroupApi, type InstanceGroup } from '@/api/instanceGroup'; import { nameToUsername } from '@/utils/pinyin'; import { toast } from '@/hooks/useToast'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; /* ───────── Types ───────── */ @@ -134,6 +135,7 @@ export default function UsersPage() { const [selectedIds, setSelectedIds] = useState>(new Set()); const [showBatchMenu, setShowBatchMenu] = useState(false); const [showImport, setShowImport] = useState(false); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); const fetchUsers = useCallback(async () => { setLoading(true); @@ -188,13 +190,15 @@ export default function UsersPage() { }; const handleDelete = async (id: number) => { - if (!confirm('确定删除该用户?此操作不可恢复。')) return; - try { - await userApi.delete(id); - setSelectedIds(prev => { const n = new Set(prev); n.delete(id); return n; }); - toast('success', '用户已删除'); - fetchUsers(); - } catch { toast('error', '删除失败'); } + setDeleteConfirm({msg: '确定删除该用户?此操作不可恢复。', onConfirm: async () => { + try { + await userApi.delete(id); + setSelectedIds(prev => { const n = new Set(prev); n.delete(id); return n; }); + toast('success', '用户已删除'); + fetchUsers(); + } catch { toast('error', '删除失败'); } + }}); + return; }; const handleBatchAction = async (action: string) => { @@ -521,6 +525,14 @@ export default function UsersPage() { {showImport && ( setShowImport(false)} onImported={() => { setShowImport(false); fetchUsers(); }} /> )} + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); } @@ -888,6 +900,7 @@ function BatchImportModal({ onClose, onImported }: { onClose: () => void; onImpo const [newOuName, setNewOuName] = useState(''); const [newOuDesc, setNewOuDesc] = useState(''); const [creatingOu, setCreatingOu] = useState(false); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); // Check LDAP readiness useEffect(() => { diff --git a/frontend/src/pages/WorkflowPage.tsx b/frontend/src/pages/WorkflowPage.tsx index 0c22b86..23ba242 100644 --- a/frontend/src/pages/WorkflowPage.tsx +++ b/frontend/src/pages/WorkflowPage.tsx @@ -7,6 +7,7 @@ import { import { Card, CardContent } from '@/components/ui/card'; import { workflowApi } from '@/api/workflow'; import { userApi } from '@/api/user'; +import { ConfirmDialog } from '@/components/ConfirmDialog'; /* ------------------------------------------------------------------ */ /* Types */ @@ -470,6 +471,7 @@ export default function WorkflowPage() { const [expandedId, setExpandedId] = useState(null); const [previewWorkflow, setPreviewWorkflow] = useState(null); const [editorWorkflow, setEditorWorkflow] = useState(undefined); + const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null); const fetchWorkflows = useCallback(async () => { try { @@ -506,11 +508,13 @@ export default function WorkflowPage() { }, []); const handleDelete = useCallback(async (id: number) => { - if (!confirm('确认删除该审批流程?')) return; - try { - await workflowApi.delete(id); - setItems((prev) => prev.filter((w) => w.id !== id)); - } catch {} + setDeleteConfirm({msg: '确认删除该审批流程?', onConfirm: async () => { + try { + await workflowApi.delete(id); + setItems((prev) => prev.filter((w) => w.id !== id)); + } catch {} + }}); + return; }, []); const setDefault = useCallback(async (id: number) => { @@ -807,6 +811,14 @@ export default function WorkflowPage() { onSaved={() => { setEditorWorkflow(undefined); fetchWorkflows(); }} /> )} + { deleteConfirm?.onConfirm(); }} + onCancel={() => setDeleteConfirm(null)} + /> ); }