diff --git a/web/static/css/c2.css b/web/static/css/c2.css index 2a473a4b..cd71cf82 100644 --- a/web/static/css/c2.css +++ b/web/static/css/c2.css @@ -1371,7 +1371,6 @@ Modal ============================================================================ */ -/* Toast 须高于模态遮罩 (10050),避免被 backdrop-filter 模糊 */ #c2-toast-container { z-index: 10100 !important; } @@ -1379,9 +1378,7 @@ .c2-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; - background: rgba(15, 23, 42, 0.5); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); + background: rgba(15, 23, 42, 0.52); display: flex; align-items: center; justify-content: center; @@ -1404,7 +1401,8 @@ overflow-y: auto; box-shadow: var(--c2-shadow-lg); border: 1px solid var(--c2-border); - animation: c2-slide-up 0.2s ease-out; + animation: c2-slide-up 0.18s ease-out; + contain: layout style paint; } @keyframes c2-slide-up { diff --git a/web/static/css/style.css b/web/static/css/style.css index 505ad666..05ea252a 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -3326,9 +3326,9 @@ header { top: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.6); + background-color: rgba(15, 23, 42, 0.52); overflow: auto; - animation: fadeIn 0.2s ease-in; + animation: fadeIn 0.15s ease-out; } .modal-content { @@ -3343,8 +3343,9 @@ header { flex-direction: column; box-shadow: var(--shadow-lg); border: 1px solid var(--border-color); - animation: slideDown 0.3s ease-out; + animation: slideDown 0.18s ease-out; overflow: hidden; + contain: layout style paint; } @keyframes slideDown { @@ -19374,6 +19375,8 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { cursor: pointer; transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); position: relative; + min-width: 0; + overflow: hidden; } .role-selection-item-main:hover { @@ -19436,6 +19439,10 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { margin: 0; transition: color 0.2s cubic-bezier(0.16, 1, 0.3, 1); letter-spacing: -0.01em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + overflow-wrap: anywhere; } .role-selection-item-main.selected .role-selection-item-name-main { @@ -19443,6 +19450,10 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { font-weight: 600; } +.role-selection-item-main.selected .role-selection-item-content-main { + padding-right: 24px; +} + .role-selection-item-description-main { font-size: 0.75rem; color: #666666; @@ -23777,10 +23788,8 @@ button.chat-files-dropdown-item:hover:not(:disabled) { justify-content: center; padding: 24px 16px; box-sizing: border-box; - background: rgba(15, 23, 42, 0.45); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - animation: projectsOverlayIn 0.2s ease-out; + background: rgba(15, 23, 42, 0.52); + animation: projectsOverlayIn 0.15s ease-out; } @keyframes projectsOverlayIn { from { opacity: 0; } @@ -23798,7 +23807,8 @@ button.chat-files-dropdown-item:hover:not(:disabled) { 0 24px 48px rgba(15, 23, 42, 0.18), 0 0 0 1px rgba(15, 23, 42, 0.06); overflow: hidden; - animation: projectsDialogIn 0.25s cubic-bezier(0.22, 1, 0.36, 1); + animation: projectsDialogIn 0.18s cubic-bezier(0.22, 1, 0.36, 1); + contain: layout style paint; } .projects-modal-dialog--wide { max-width: 640px; @@ -23859,6 +23869,13 @@ button.chat-files-dropdown-item:hover:not(:disabled) { padding: 10px 12px; font-size: 0.875rem; transition: border-color 0.15s, box-shadow 0.15s; + width: 100%; + min-width: 0; + box-sizing: border-box; +} +#project-modal-name { + overflow: hidden; + text-overflow: ellipsis; } .projects-modal-body .form-input:focus { outline: none; @@ -23882,7 +23899,8 @@ button.chat-files-dropdown-item:hover:not(:disabled) { .projects-modal-footer .btn-primary { min-width: 100px; } -body.projects-modal-open { +body.projects-modal-open, +body.app-modal-open { overflow: hidden; } .fact-detail-prev-wrap { @@ -24030,8 +24048,11 @@ body.projects-modal-open { /* 对话区项目选择器(与角色/代理模式共用 role-selector-*) */ .project-selector-wrapper .role-selector-text { max-width: 108px; + min-width: 0; + flex-shrink: 1; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; } .chat-project-panel { width: 280px; @@ -24051,6 +24072,7 @@ body.projects-modal-open { padding-right: 0; margin: 0; width: 100%; + overflow-x: hidden; } .chat-project-panel .role-selection-item-main { width: 100%; diff --git a/web/static/js/agents.js b/web/static/js/agents.js index bb0d759c..27fbf1f9 100644 --- a/web/static/js/agents.js +++ b/web/static/js/agents.js @@ -105,45 +105,48 @@ function showAddMarkdownAgentModal() { document.getElementById('agent-md-bind-role').value = ''; document.getElementById('agent-md-max-iter').value = '0'; document.getElementById('agent-md-instruction').value = ''; - if (modal) modal.style.display = 'flex'; + openAppModal('agent-md-modal'); } async function editMarkdownAgent(filename) { if (!filename) return; - const modal = document.getElementById('agent-md-modal'); const title = document.getElementById('agent-md-modal-title'); const row = document.getElementById('agent-md-filename-row'); markdownAgentsEditingFilename = null; markdownAgentsEditingIsOrchestrator = false; if (title) title.textContent = _agentsT('agentsPage.editTitle'); if (row) row.style.display = 'none'; + document.getElementById('agent-md-instruction').value = ''; + openAppModal('agent-md-modal', { focus: false }); try { const r = await apiFetch('/api/multi-agent/markdown-agents/' + encodeURIComponent(filename)); const data = await r.json(); if (!r.ok) throw new Error(data.error || r.statusText); markdownAgentsEditingFilename = data.filename || filename; markdownAgentsEditingIsOrchestrator = !!data.is_orchestrator; - document.getElementById('agent-md-filename-current').value = data.filename || filename; - document.getElementById('agent-md-filename').value = data.filename || filename; - document.getElementById('agent-md-filename').disabled = true; - var roleEl2 = document.getElementById('agent-md-role'); - if (roleEl2) roleEl2.value = data.is_orchestrator ? 'orchestrator' : 'sub'; - document.getElementById('agent-md-id').value = data.id || ''; - document.getElementById('agent-md-name').value = data.name || ''; - document.getElementById('agent-md-description').value = data.description || ''; - document.getElementById('agent-md-tools').value = Array.isArray(data.tools) ? data.tools.join(', ') : ''; - document.getElementById('agent-md-bind-role').value = data.bind_role || ''; - document.getElementById('agent-md-max-iter').value = String(data.max_iterations != null ? data.max_iterations : 0); - document.getElementById('agent-md-instruction').value = data.instruction || ''; - if (modal) modal.style.display = 'flex'; + deferModalContent(function () { + document.getElementById('agent-md-filename-current').value = data.filename || filename; + document.getElementById('agent-md-filename').value = data.filename || filename; + document.getElementById('agent-md-filename').disabled = true; + var roleEl2 = document.getElementById('agent-md-role'); + if (roleEl2) roleEl2.value = data.is_orchestrator ? 'orchestrator' : 'sub'; + document.getElementById('agent-md-id').value = data.id || ''; + document.getElementById('agent-md-name').value = data.name || ''; + document.getElementById('agent-md-description').value = data.description || ''; + document.getElementById('agent-md-tools').value = Array.isArray(data.tools) ? data.tools.join(', ') : ''; + document.getElementById('agent-md-bind-role').value = data.bind_role || ''; + document.getElementById('agent-md-max-iter').value = String(data.max_iterations != null ? data.max_iterations : 0); + document.getElementById('agent-md-instruction').value = data.instruction || ''; + document.getElementById('agent-md-name')?.focus(); + }); } catch (e) { + closeMarkdownAgentModal(); showNotification(_agentsT('agentsPage.loadOneFailed') + ': ' + e.message, 'error'); } } function closeMarkdownAgentModal() { - const modal = document.getElementById('agent-md-modal'); - if (modal) modal.style.display = 'none'; + closeAppModal('agent-md-modal'); markdownAgentsEditingFilename = null; markdownAgentsEditingIsOrchestrator = false; } diff --git a/web/static/js/audit.js b/web/static/js/audit.js index 84ec4bc0..30572951 100644 --- a/web/static/js/audit.js +++ b/web/static/js/audit.js @@ -533,56 +533,61 @@ async function exportAuditLogsCsv() { } function closeAuditDetailModal() { + closeAppModal('audit-detail-modal'); const el = document.getElementById('audit-detail-modal'); if (el) el.remove(); + syncAppModalBodyLock(); } async function showAuditLogDetail(id) { if (!id || typeof apiFetch !== 'function') return; const esc = typeof escapeHtml === 'function' ? escapeHtml : function (s) { return String(s || ''); }; try { + closeAuditDetailModal(); + const overlay = document.createElement('div'); + overlay.id = 'audit-detail-modal'; + overlay.className = 'modal'; + document.body.appendChild(overlay); + openAppModal(overlay, { focus: false }); const r = await apiFetch('/api/audit/logs/' + encodeURIComponent(id)); if (!r.ok) throw new Error('not found'); const data = await r.json(); const log = data.log || {}; const detail = log.detail ? JSON.stringify(log.detail, null, 2) : ''; - closeAuditDetailModal(); - const overlay = document.createElement('div'); - overlay.id = 'audit-detail-modal'; - overlay.className = 'modal'; - overlay.style.display = 'block'; const catAction = esc(auditCategoryLabel(log.category || '')) + ' / ' + esc(auditActionLabel(log.action || '')); - overlay.innerHTML = - ''; - document.body.appendChild(overlay); - const chatBtn = overlay.querySelector('.audit-open-chat-btn'); - if (chatBtn) { - chatBtn.addEventListener('click', function () { - auditOpenConversationChat(chatBtn.getAttribute('data-conversation-id')); + deferModalContent(function () { + overlay.innerHTML = + ''; + const chatBtn = overlay.querySelector('.audit-open-chat-btn'); + if (chatBtn) { + chatBtn.addEventListener('click', function () { + auditOpenConversationChat(chatBtn.getAttribute('data-conversation-id')); + }); + } + overlay.addEventListener('click', function (ev) { + if (ev.target === overlay) closeAuditDetailModal(); }); - } - overlay.addEventListener('click', function (ev) { - if (ev.target === overlay) closeAuditDetailModal(); }); } catch (e) { + closeAuditDetailModal(); if (typeof showToast === 'function') { showToast(e.message || String(e), 'error'); } diff --git a/web/static/js/auth.js b/web/static/js/auth.js index 5f3721e4..ed443351 100644 --- a/web/static/js/auth.js +++ b/web/static/js/auth.js @@ -72,7 +72,7 @@ function showLoginOverlay(message = '') { if (!overlay) { return; } - overlay.style.display = 'flex'; + openAppModal('login-overlay', { focus: false }); if (errorBox) { if (message) { errorBox.textContent = message; @@ -82,7 +82,7 @@ function showLoginOverlay(message = '') { errorBox.style.display = 'none'; } } - setTimeout(() => { + setTimeout(function () { if (passwordInput) { passwordInput.focus(); } @@ -93,9 +93,7 @@ function hideLoginOverlay() { const overlay = document.getElementById('login-overlay'); const errorBox = document.getElementById('login-error'); const passwordInput = document.getElementById('login-password'); - if (overlay) { - overlay.style.display = 'none'; - } + closeAppModal('login-overlay'); if (errorBox) { errorBox.textContent = ''; errorBox.style.display = 'none'; diff --git a/web/static/js/c2.js b/web/static/js/c2.js index 7ce93681..c7849af6 100644 --- a/web/static/js/c2.js +++ b/web/static/js/c2.js @@ -478,7 +478,7 @@ const content = document.getElementById('c2-modal-content'); if (!content || !modal) return; - modal.style.display = 'flex'; + openAppModal(modal); content.innerHTML = `

${escapeHtml(c2t('c2.listeners.modalCreateTitle'))}

@@ -635,7 +635,7 @@ const content = document.getElementById('c2-modal-content'); if (!content || !modal) return; - modal.style.display = 'flex'; + openAppModal(modal); content.innerHTML = `

${escapeHtml(c2t('c2.listeners.editTitle'))}

@@ -2376,7 +2376,7 @@
`; - modal.style.display = 'flex'; + openAppModal(modal); }; const local = C2.tasks.find(x => x.id === id); @@ -2920,7 +2920,7 @@
`; - modal.style.display = 'flex'; + openAppModal(modal); }; C2.createProfile = function() { @@ -2981,10 +2981,10 @@ C2.closeModal = function() { const modal = document.getElementById('c2-modal'); if (modal) { - modal.style.display = 'none'; const modalBox = modal.querySelector('.c2-modal'); if (modalBox) modalBox.classList.remove('c2-modal--wide'); } + closeAppModal('c2-modal'); }; // ============================================================================ diff --git a/web/static/js/chat-files.js b/web/static/js/chat-files.js index 1c843733..5c71999d 100644 --- a/web/static/js/chat-files.js +++ b/web/static/js/chat-files.js @@ -1002,7 +1002,7 @@ async function openChatFilesEdit(relativePath) { const modal = document.getElementById('chat-files-edit-modal'); if (pathEl) pathEl.textContent = relativePath; if (ta) ta.value = ''; - if (modal) modal.style.display = 'block'; + openAppModal('chat-files-edit-modal', { focus: false }); try { const res = await apiFetch('/api/chat-uploads/content?path=' + encodeURIComponent(relativePath)); @@ -1017,16 +1017,19 @@ async function openChatFilesEdit(relativePath) { throw new Error(errText || res.status); } const data = await res.json(); - if (ta) ta.value = data.content != null ? String(data.content) : ''; + const content = data.content != null ? String(data.content) : ''; + deferModalContent(() => { + if (ta) ta.value = content; + ta?.focus(); + }); } catch (e) { - if (modal) modal.style.display = 'none'; + closeAppModal('chat-files-edit-modal'); alert(chatFilesAlertMessage(e && e.message)); } } function closeChatFilesEditModal() { - const modal = document.getElementById('chat-files-edit-modal'); - if (modal) modal.style.display = 'none'; + closeAppModal('chat-files-edit-modal'); chatFilesEditRelativePath = ''; } @@ -1060,7 +1063,7 @@ function openChatFilesRename(relativePath, currentName) { input.value = currentName || ''; input.select(); } - if (modal) modal.style.display = 'flex'; + if (modal) openAppModal(modal); if (modal && typeof window.applyTranslations === 'function') { window.applyTranslations(modal); } @@ -1068,8 +1071,7 @@ function openChatFilesRename(relativePath, currentName) { } function closeChatFilesRenameModal() { - const modal = document.getElementById('chat-files-rename-modal'); - if (modal) modal.style.display = 'none'; + closeAppModal('chat-files-rename-modal'); const hint = document.getElementById('chat-files-rename-path-hint'); if (hint) hint.textContent = ''; chatFilesRenameRelativePath = ''; @@ -1106,7 +1108,7 @@ function openChatFilesMkdirModal() { const p = chatFilesBrowsePath.join('/'); if (hint) hint.textContent = p ? ('chat_uploads/' + p) : 'chat_uploads'; if (input) input.value = ''; - if (modal) modal.style.display = 'flex'; + if (modal) openAppModal(modal); if (modal && typeof window.applyTranslations === 'function') { window.applyTranslations(modal); } @@ -1116,8 +1118,7 @@ function openChatFilesMkdirModal() { } function closeChatFilesMkdirModal() { - const modal = document.getElementById('chat-files-mkdir-modal'); - if (modal) modal.style.display = 'none'; + closeAppModal('chat-files-mkdir-modal'); const input = document.getElementById('chat-files-mkdir-input'); if (input) input.value = ''; } diff --git a/web/static/js/chat.js b/web/static/js/chat.js index e88037a3..fe48e3f4 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -2535,10 +2535,17 @@ async function batchUpdateButtonToolNames(buttonsContainer, executionIds) { // 显示MCP调用详情 async function showMCPDetail(executionId) { try { + openAppModal('mcp-detail-modal', { focus: false }); const response = await apiFetch(`/api/monitor/execution/${executionId}`); const exec = await response.json(); - - if (response.ok) { + + if (!response.ok) { + closeMCPDetail(); + alert((typeof window.t === 'function' ? window.t('mcpDetailModal.getDetailFailed') : '获取详情失败') + ': ' + (exec.error || (typeof window.t === 'function' ? window.t('mcpDetailModal.unknown') : '未知错误'))); + return; + } + + deferModalContent(function () { // 填充模态框内容 document.getElementById('detail-tool-name').textContent = exec.toolName || (typeof window.t === 'function' ? window.t('mcpDetailModal.unknown') : 'Unknown'); document.getElementById('detail-execution-id').textContent = exec.id || 'N/A'; @@ -2645,20 +2652,16 @@ async function showMCPDetail(executionId) { delete abortBtn.dataset.execId; } } - - // 显示模态框 - document.getElementById('mcp-detail-modal').style.display = 'block'; - } else { - alert((typeof window.t === 'function' ? window.t('mcpDetailModal.getDetailFailed') : '获取详情失败') + ': ' + (exec.error || (typeof window.t === 'function' ? window.t('mcpDetailModal.unknown') : '未知错误'))); - } + }); } catch (error) { + closeMCPDetail(); alert((typeof window.t === 'function' ? window.t('mcpDetailModal.getDetailFailed') : '获取详情失败') + ': ' + error.message); } } // 关闭MCP详情模态框 function closeMCPDetail() { - document.getElementById('mcp-detail-modal').style.display = 'none'; + closeAppModal('mcp-detail-modal'); } /** 从详情模态框触发:取消当前进行中的 MCP 工具调用 */ @@ -2682,18 +2685,12 @@ function openMcpToolAbortModal(executionId, options = {}) { if (ta) { ta.value = ''; } - const m = document.getElementById('mcp-tool-abort-modal'); - if (m) { - m.style.display = 'block'; - } + openAppModal('mcp-tool-abort-modal'); } function closeMcpToolAbortModal() { window.__mcpToolAbortContext = null; - const m = document.getElementById('mcp-tool-abort-modal'); - if (m) { - m.style.display = 'none'; - } + closeAppModal('mcp-tool-abort-modal'); } async function submitMcpToolAbortModal() { @@ -3125,7 +3122,7 @@ async function loadConversation(conversationId) { // 如果攻击链模态框打开且显示的不是当前对话,关闭它 const attackChainModal = document.getElementById('attack-chain-modal'); - if (attackChainModal && attackChainModal.style.display === 'block') { + if (attackChainModal && isAppModalOpen('attack-chain-modal')) { if (currentAttackChainConversationId !== conversationId) { closeAttackChainModal(); } @@ -3415,7 +3412,7 @@ async function deleteConversation(conversationId, skipConfirm = false) { // 批量管理弹窗打开时,同步刷新弹窗内列表 const batchModal = document.getElementById('batch-manage-modal'); - if (batchModal && batchModal.style.display === 'flex') { + if (batchModal && isAppModalOpen('batch-manage-modal')) { allConversationsForBatch = allConversationsForBatch.filter(c => c.id !== conversationId); updateBatchManageTitle(allConversationsForBatch.length); const searchInput = document.getElementById('batch-search-input'); @@ -3522,7 +3519,7 @@ async function showAttackChain(conversationId) { if (isAttackChainLoading(conversationId) && currentAttackChainConversationId === conversationId) { // 如果模态框已经打开且显示的是同一个对话,不重复打开 const modal = document.getElementById('attack-chain-modal'); - if (modal && modal.style.display === 'block') { + if (modal && isAppModalOpen('attack-chain-modal')) { console.log('攻击链正在加载中,模态框已打开'); return; } @@ -3535,8 +3532,7 @@ async function showAttackChain(conversationId) { return; } - modal.style.display = 'block'; - // 打开时立即按当前语言刷新统计(避免红框内仍显示硬编码中文) + openAppModal('attack-chain-modal', { focus: false }); updateAttackChainStats({ nodes: [], edges: [] }); // 清空容器 @@ -4668,10 +4664,7 @@ function closeNodeDetails() { // 关闭攻击链模态框 function closeAttackChainModal() { - const modal = document.getElementById('attack-chain-modal'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('attack-chain-modal'); // 关闭节点详情 closeNodeDetails(); @@ -7214,19 +7207,14 @@ async function showBatchManageModal() { updateBatchManageTitle(allConversationsForBatch.length); renderBatchConversations(); - if (modal) { - modal.style.display = 'flex'; - } + openAppModal('batch-manage-modal'); } catch (error) { console.error('加载对话列表失败:', error); // 错误时使用空数组,不显示错误提示(更友好的用户体验) allConversationsForBatch = []; - const modal = document.getElementById('batch-manage-modal'); updateBatchManageTitle(0); - if (modal) { - renderBatchConversations(); - modal.style.display = 'flex'; - } + renderBatchConversations(); + openAppModal('batch-manage-modal'); } } @@ -7381,10 +7369,7 @@ async function deleteSelectedConversations() { // 关闭批量管理模态框 function closeBatchManageModal() { - const modal = document.getElementById('batch-manage-modal'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('batch-manage-modal'); const selectAll = document.getElementById('batch-select-all'); if (selectAll) { selectAll.checked = false; @@ -7424,8 +7409,7 @@ function refreshChatPanelI18n() { }); } - const mcpModal = document.getElementById('mcp-detail-modal'); - if (mcpModal && mcpModal.style.display === 'block') { + if (isAppModalOpen('mcp-detail-modal')) { const detailTimeEl = document.getElementById('detail-time'); if (detailTimeEl && detailTimeEl.dataset.detailTimeIso) { try { @@ -7447,7 +7431,7 @@ document.addEventListener('languagechange', function () { refreshSystemReadyMessageBubbles(); refreshChatPanelI18n(); const modal = document.getElementById('batch-manage-modal'); - if (modal && modal.style.display === 'flex') { + if (isAppModalOpen('batch-manage-modal')) { updateBatchManageTitle(allConversationsForBatch.length); } // 侧边栏最近对话等列表的时间戳会随语言变化(24h/12h 等),重新拉列表以统一格式 @@ -7482,20 +7466,14 @@ function showCreateGroupModal(andMoveConversation = false) { iconPicker.style.display = 'none'; } if (modal) { - modal.style.display = 'flex'; + openAppModal('create-group-modal', { focusEl: input }); modal.dataset.moveConversation = andMoveConversation ? 'true' : 'false'; - if (input) { - setTimeout(() => input.focus(), 100); - } } } // 关闭创建分组模态框 function closeCreateGroupModal() { - const modal = document.getElementById('create-group-modal'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('create-group-modal'); const input = document.getElementById('create-group-name-input'); if (input) { input.value = ''; diff --git a/web/static/js/info-collect.js b/web/static/js/info-collect.js index fe3301ab..4c9824ce 100644 --- a/web/static/js/info-collect.js +++ b/web/static/js/info-collect.js @@ -344,7 +344,9 @@ function showFofaParseModal(nlText, parsed) { const modal = document.createElement('div'); modal.id = 'fofa-parse-modal'; modal.className = 'modal'; - modal.style.display = 'block'; + document.body.appendChild(modal); + openAppModal(modal, { focus: false }); + deferModalContent(function () { modal.innerHTML = ` `; - - modal.style.display = 'block'; + }); } // 关闭检索日志详情模态框 function closeRetrievalLogDetailsModal() { - const modal = document.getElementById('retrieval-log-details-modal'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('retrieval-log-details-modal'); } // 点击模态框外部关闭 @@ -2118,7 +2121,8 @@ function showToastNotification(message, type = 'info') { font-size: 0.875rem; line-height: 1.45; word-wrap: break-word; - backdrop-filter: blur(8px); + backdrop-filter: none; + -webkit-backdrop-filter: none; `; toast.innerHTML = ` diff --git a/web/static/js/modal.js b/web/static/js/modal.js new file mode 100644 index 00000000..f882100e --- /dev/null +++ b/web/static/js/modal.js @@ -0,0 +1,92 @@ +/** + * 统一弹窗:先显示遮罩、下一帧再填大段内容,避免与 backdrop 绘制抢主线程。 + */ +(function () { + const BODY_LOCK = 'app-modal-open'; + const LEGACY_BODY_LOCK = 'projects-modal-open'; + const OVERLAY_SELECTOR = + '.projects-modal-overlay, .c2-modal-overlay, .modal, .info-collect-cell-modal, #login-overlay'; + + const FLEX_MODAL_IDS = new Set([ + 'role-modal', + 'skill-modal', + 'agent-md-modal', + 'batch-manage-modal', + 'create-group-modal', + 'login-overlay', + ]); + + function resolveEl(idOrEl) { + if (!idOrEl) return null; + return typeof idOrEl === 'string' ? document.getElementById(idOrEl) : idOrEl; + } + + function isElVisible(el) { + if (!el) return false; + const s = window.getComputedStyle(el); + return s.display !== 'none' && s.visibility !== 'hidden'; + } + + function defaultDisplay(el) { + if (el.classList.contains('projects-modal-overlay') || el.classList.contains('c2-modal-overlay')) { + return 'flex'; + } + if (el.classList.contains('info-collect-cell-modal')) { + return 'flex'; + } + if (FLEX_MODAL_IDS.has(el.id)) { + return 'flex'; + } + return 'block'; + } + + function syncBodyLock() { + const anyOpen = Array.from(document.querySelectorAll(OVERLAY_SELECTOR)).some(isElVisible); + document.body.classList.toggle(BODY_LOCK, anyOpen); + const projectsOpen = Array.from(document.querySelectorAll('.projects-modal-overlay')).some(isElVisible); + document.body.classList.toggle(LEGACY_BODY_LOCK, projectsOpen); + } + + function openAppModal(idOrEl, opts) { + opts = opts || {}; + const el = resolveEl(idOrEl); + if (!el) return null; + el.style.display = opts.display || defaultDisplay(el); + syncBodyLock(); + if (opts.focus === false) return el; + const sel = + opts.focusSelector || + 'input.form-input, textarea.form-input, select.form-input, input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled])'; + const focusTarget = opts.focusEl || el.querySelector(sel); + if (focusTarget) { + requestAnimationFrame(function () { + focusTarget.focus(); + }); + } + return el; + } + + function closeAppModal(idOrEl) { + const el = resolveEl(idOrEl); + if (el) el.style.display = 'none'; + syncBodyLock(); + return el; + } + + function isAppModalOpen(idOrEl) { + return isElVisible(resolveEl(idOrEl)); + } + + /** 双 rAF:等遮罩绘制完成后再写入大段 DOM / 表单 */ + function deferModalContent(fn) { + requestAnimationFrame(function () { + requestAnimationFrame(fn); + }); + } + + window.openAppModal = openAppModal; + window.closeAppModal = closeAppModal; + window.isAppModalOpen = isAppModalOpen; + window.deferModalContent = deferModalContent; + window.syncAppModalBodyLock = syncBodyLock; +})(); diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 3e939197..e10a1c5c 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -944,18 +944,12 @@ function openUserInterruptModal(progressId, conversationId) { if (ta) { ta.value = ''; } - const m = document.getElementById('user-interrupt-modal'); - if (m) { - m.style.display = 'block'; - } + openAppModal('user-interrupt-modal'); } function closeUserInterruptModal() { userInterruptModalPending = null; - const m = document.getElementById('user-interrupt-modal'); - if (m) { - m.style.display = 'none'; - } + closeAppModal('user-interrupt-modal'); } async function submitUserInterruptContinue() { diff --git a/web/static/js/projects.js b/web/static/js/projects.js index 57985dc6..8c1c174a 100644 --- a/web/static/js/projects.js +++ b/web/static/js/projects.js @@ -12,6 +12,7 @@ let _projectsFetchPromise = null; const PROJECT_ACTIVE_KEY = 'cyberstrike.activeProjectId'; const PROJECT_DESCRIPTION_MAX_LENGTH = 4000; +const PROJECT_NAME_MAX_LENGTH = 200; function tp(key, opts) { if (typeof window.t === 'function') return window.t(key, opts); @@ -878,38 +879,52 @@ let _factDetailFact = null; let _projectFactsFilterDebounce = null; async function viewProjectFactBody(factKey) { - const res = await apiFetch(`/api/projects/${currentProjectId}/facts?fact_key=${encodeURIComponent(factKey)}`); - if (!res.ok) return alert(tp('common.loadFailed')); - const f = await res.json(); - _factDetailKey = f.fact_key; - _factDetailFact = f; - document.getElementById('fact-detail-title').textContent = `[${f.fact_key}]`; - const metaParts = [ - tpFmt('projects.factMetaCategory', `Category: ${f.category}`, { value: f.category }), - tpFmt('projects.factMetaConfidence', `Confidence: ${f.confidence}`, { value: f.confidence }), - tpFmt('projects.factMetaUpdated', `Updated: ${formatProjectTime(f.updated_at, f.created_at)}`, { - time: formatProjectTime(f.updated_at, f.created_at), - }), - ]; - if (f.related_vulnerability_id) metaParts.push(tpFmt('projects.factMetaRelatedVuln', `Related vulnerability: ${f.related_vulnerability_id}`, { value: f.related_vulnerability_id })); - if (f.source_conversation_id) metaParts.push(tpFmt('projects.factMetaSourceConversation', `Source conversation: ${f.source_conversation_id}`, { value: f.source_conversation_id })); - document.getElementById('fact-detail-meta').textContent = metaParts.join(' · '); - document.getElementById('fact-detail-body').textContent = f.body || tp('projects.emptyBody'); + document.getElementById('fact-detail-title').textContent = factKey; + document.getElementById('fact-detail-meta').textContent = '…'; + document.getElementById('fact-detail-body').textContent = ''; const warnEl = document.getElementById('fact-detail-sparse-warn'); if (warnEl) { - if (isSparseFactBody(f.category, f.fact_key, f.body)) { - warnEl.hidden = false; - warnEl.textContent = tp('projects.factSparseWarn'); - } else { - warnEl.hidden = true; - warnEl.textContent = ''; - } + warnEl.hidden = true; + warnEl.textContent = ''; } const linkBtn = document.getElementById('fact-detail-link-vuln-btn'); const createBtn = document.getElementById('fact-detail-create-vuln-btn'); - if (linkBtn) linkBtn.hidden = false; - if (createBtn) createBtn.hidden = false; - openProjectsOverlay('fact-detail-modal'); + if (linkBtn) linkBtn.hidden = true; + if (createBtn) createBtn.hidden = true; + openProjectsOverlay('fact-detail-modal', { focus: false }); + const res = await apiFetch(`/api/projects/${currentProjectId}/facts?fact_key=${encodeURIComponent(factKey)}`); + if (!res.ok) { + closeFactDetailModal(); + return alert(tp('common.loadFailed')); + } + const f = await res.json(); + _factDetailKey = f.fact_key; + _factDetailFact = f; + deferModalContent(() => { + document.getElementById('fact-detail-title').textContent = `[${f.fact_key}]`; + const metaParts = [ + tpFmt('projects.factMetaCategory', `Category: ${f.category}`, { value: f.category }), + tpFmt('projects.factMetaConfidence', `Confidence: ${f.confidence}`, { value: f.confidence }), + tpFmt('projects.factMetaUpdated', `Updated: ${formatProjectTime(f.updated_at, f.created_at)}`, { + time: formatProjectTime(f.updated_at, f.created_at), + }), + ]; + if (f.related_vulnerability_id) metaParts.push(tpFmt('projects.factMetaRelatedVuln', `Related vulnerability: ${f.related_vulnerability_id}`, { value: f.related_vulnerability_id })); + if (f.source_conversation_id) metaParts.push(tpFmt('projects.factMetaSourceConversation', `Source conversation: ${f.source_conversation_id}`, { value: f.source_conversation_id })); + document.getElementById('fact-detail-meta').textContent = metaParts.join(' · '); + document.getElementById('fact-detail-body').textContent = f.body || tp('projects.emptyBody'); + if (warnEl) { + if (isSparseFactBody(f.category, f.fact_key, f.body)) { + warnEl.hidden = false; + warnEl.textContent = tp('projects.factSparseWarn'); + } else { + warnEl.hidden = true; + warnEl.textContent = ''; + } + } + if (linkBtn) linkBtn.hidden = false; + if (createBtn) createBtn.hidden = false; + }); } function editFactFromDetail() { @@ -1164,41 +1179,16 @@ async function viewFactsForVulnerability(vulnId) { else loadProjectFacts(); } -function openProjectsOverlay(id) { - const el = document.getElementById(id); - if (!el) return; - el.style.display = 'flex'; - syncProjectsModalBodyLock(); - const focusTarget = el.querySelector('input.form-input, textarea.form-input, select.form-input'); - if (focusTarget) { - setTimeout(() => focusTarget.focus(), 80); - } +function openProjectsOverlay(id, opts) { + openAppModal(id, opts); } function isProjectsOverlayVisible(id) { - const el = document.getElementById(id); - if (!el) return false; - const style = window.getComputedStyle(el); - return style.display !== 'none' && style.visibility !== 'hidden'; -} - -function hasVisibleProjectsOverlay() { - const overlays = document.querySelectorAll('.projects-modal-overlay'); - return Array.from(overlays).some((el) => { - const style = window.getComputedStyle(el); - return style.display !== 'none' && style.visibility !== 'hidden'; - }); -} - -function syncProjectsModalBodyLock() { - if (hasVisibleProjectsOverlay()) document.body.classList.add('projects-modal-open'); - else document.body.classList.remove('projects-modal-open'); + return isAppModalOpen(id); } function closeProjectsOverlay(id) { - const el = document.getElementById(id); - if (el) el.style.display = 'none'; - syncProjectsModalBodyLock(); + closeAppModal(id); } function showNewProjectModal() { @@ -1222,6 +1212,11 @@ async function showEditProjectModal(projectId) { if (sub) sub.textContent = tp('projects.modalEditSubtitle'); const submitBtn = document.getElementById('project-modal-submit-btn'); if (submitBtn) submitBtn.textContent = tp('projects.saveChanges'); + const nameEl = document.getElementById('project-modal-name'); + const descEl = document.getElementById('project-modal-description'); + if (nameEl) nameEl.value = ''; + if (descEl) descEl.value = ''; + openProjectsOverlay('project-modal', { focus: false }); let p = findProjectById(projectId); if (!p) { try { @@ -1229,15 +1224,19 @@ async function showEditProjectModal(projectId) { if (!res.ok) throw new Error(tp('projects.projectNotFound')); p = await res.json(); } catch (e) { + closeProjectModal(); alert(e.message || tp('projects.projectNotFound')); window._projectModalEditId = null; return; } } - document.getElementById('project-modal-name').value = p.name || ''; - document.getElementById('project-modal-description').value = p.description || ''; - openProjectsOverlay('project-modal'); - setTimeout(() => document.getElementById('project-modal-name')?.focus(), 0); + const name = (p.name || '').slice(0, PROJECT_NAME_MAX_LENGTH); + const description = clampProjectDescription(p.description || ''); + deferModalContent(() => { + if (nameEl) nameEl.value = name; + if (descEl) descEl.value = description; + nameEl?.focus(); + }); } /** 从对话区「选择项目」面板打开新建项目,创建成功后自动绑定当前对话 */ @@ -1248,7 +1247,7 @@ function showNewProjectModalFromChat() { } async function saveProjectModal() { - const name = document.getElementById('project-modal-name').value.trim(); + const name = document.getElementById('project-modal-name').value.trim().slice(0, PROJECT_NAME_MAX_LENGTH); if (!name) return alert(tp('projects.enterProjectName')); const body = { name, @@ -1541,14 +1540,20 @@ function showAddFactModal() { async function showEditFactModal(factKey) { if (!currentProjectId) return alert(tp('projects.selectProjectFirst')); + resetFactModalForm(); + openProjectsOverlay('fact-modal', { focus: false }); const res = await apiFetch( `/api/projects/${currentProjectId}/facts?fact_key=${encodeURIComponent(factKey)}`, ); - if (!res.ok) return alert(tp('projects.loadFactFailed')); + if (!res.ok) { + closeFactModal(); + return alert(tp('projects.loadFactFailed')); + } const f = await res.json(); - resetFactModalForm(); - fillFactModalForm(f); - openProjectsOverlay('fact-modal'); + deferModalContent(() => { + fillFactModalForm(f); + document.getElementById('fact-modal-key')?.focus(); + }); } function closeFactModal() { diff --git a/web/static/js/roles.js b/web/static/js/roles.js index 7b8506b1..f606a3cb 100644 --- a/web/static/js/roles.js +++ b/web/static/js/roles.js @@ -1112,7 +1112,7 @@ async function showAddRoleModal() { // 确保统计信息正确更新(显示0/108) updateRoleToolsStats(); - modal.style.display = 'flex'; + openAppModal('role-modal'); } // 编辑角色 @@ -1274,15 +1274,16 @@ async function editRole(roleName) { } } - modal.style.display = 'flex'; + openAppModal('role-modal'); } // 关闭角色模态框 function closeRoleModal() { - const modal = document.getElementById('role-modal'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('role-modal'); +} + +function closeRoleSelectModal() { + closeAppModal('role-select-modal'); } // 获取所有选中的工具(包括未在MCP管理中启用的工具) @@ -1634,6 +1635,7 @@ if (typeof window !== 'undefined') { window.getCurrentRole = getCurrentRole; window.toggleRoleSelectionPanel = toggleRoleSelectionPanel; window.closeRoleSelectionPanel = closeRoleSelectionPanel; + window.closeRoleSelectModal = closeRoleSelectModal; window.filterRoleToolsByStatus = filterRoleToolsByStatus; window.currentSelectedRole = getCurrentRole(); diff --git a/web/static/js/settings.js b/web/static/js/settings.js index 22b23cdb..74a52ebd 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -2096,47 +2096,42 @@ function showAddExternalMCPModal() { document.getElementById('external-mcp-json-error').style.display = 'none'; document.getElementById('external-mcp-json-error').textContent = ''; document.getElementById('external-mcp-json').classList.remove('error'); - document.getElementById('external-mcp-modal').style.display = 'block'; + openAppModal('external-mcp-modal'); } // 关闭外部MCP模态框 function closeExternalMCPModal() { - document.getElementById('external-mcp-modal').style.display = 'none'; + closeAppModal('external-mcp-modal'); currentEditingMCPName = null; } // 编辑外部MCP async function editExternalMCP(name) { try { + currentEditingMCPName = name; + document.getElementById('external-mcp-modal-title').textContent = (typeof window.t === 'function' ? window.t('mcp.editExternalMCP') : '编辑外部MCP'); + document.getElementById('external-mcp-json').value = ''; + document.getElementById('external-mcp-json-error').style.display = 'none'; + document.getElementById('external-mcp-json-error').textContent = ''; + document.getElementById('external-mcp-json').classList.remove('error'); + openAppModal('external-mcp-modal', { focus: false }); const response = await apiFetch(`/api/external-mcp/${encodeURIComponent(name)}`); if (!response.ok) { throw new Error(typeof window.t === 'function' ? window.t('mcp.getConfigFailed') : '获取外部MCP配置失败'); } - const server = await response.json(); - currentEditingMCPName = name; - - document.getElementById('external-mcp-modal-title').textContent = (typeof window.t === 'function' ? window.t('mcp.editExternalMCP') : '编辑外部MCP'); - - // 将配置转换为对象格式(key为名称) const config = { ...server.config }; - // 移除tool_count、external_mcp_enable等前端字段,但保留enabled/disabled用于向后兼容 delete config.tool_count; delete config.external_mcp_enable; - - // 包装成对象格式:{ "name": { config } } const configObj = {}; configObj[name] = config; - - // 格式化JSON const jsonStr = JSON.stringify(configObj, null, 2); - document.getElementById('external-mcp-json').value = jsonStr; - document.getElementById('external-mcp-json-error').style.display = 'none'; - document.getElementById('external-mcp-json-error').textContent = ''; - document.getElementById('external-mcp-json').classList.remove('error'); - - document.getElementById('external-mcp-modal').style.display = 'block'; + deferModalContent(() => { + document.getElementById('external-mcp-json').value = jsonStr; + document.getElementById('external-mcp-json')?.focus(); + }); } catch (error) { + closeExternalMCPModal(); console.error('编辑外部MCP失败:', error); alert((typeof window.t === 'function' ? window.t('mcp.operationFailed') : '编辑失败') + ': ' + error.message); } diff --git a/web/static/js/skills.js b/web/static/js/skills.js index 3fd95a83..580b1014 100644 --- a/web/static/js/skills.js +++ b/web/static/js/skills.js @@ -40,7 +40,7 @@ function shouldSkipSkillsAutoRefresh() { } const modal = document.getElementById('skill-modal'); - if (modal && modal.style.display === 'flex') { + if (modal && isAppModalOpen('skill-modal')) { return true; } @@ -465,7 +465,7 @@ function showAddSkillModal() { const addTa = document.getElementById('skill-content-add'); if (addTa) addTa.value = ''; - modal.style.display = 'flex'; + openAppModal('skill-modal'); } function skillPackagePathDepth(path) { @@ -555,6 +555,22 @@ async function selectSkillPackageFile(skillId, path, opts) { // 编辑skill async function editSkill(skillId) { wireSkillModalOnce(); + const modal = document.getElementById('skill-modal'); + if (!modal) return; + skillModalAddMode = false; + skillFileDirty = false; + skillActivePath = 'SKILL.md'; + const pkg = document.getElementById('skill-package-editor'); + const addEd = document.getElementById('skill-add-editor'); + if (pkg) pkg.style.display = 'block'; + if (addEd) addEd.style.display = 'none'; + document.getElementById('skill-modal-title').textContent = _t('skills.editSkill'); + document.getElementById('skill-name').value = ''; + document.getElementById('skill-name').disabled = true; + document.getElementById('skill-description').value = ''; + const ta = document.getElementById('skill-content'); + if (ta) ta.value = ''; + openAppModal('skill-modal', { focus: false }); try { const [detailRes, filesRes] = await Promise.all([ apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=full`), @@ -565,39 +581,24 @@ async function editSkill(skillId) { } const data = await detailRes.json(); const skill = data.skill; - - const modal = document.getElementById('skill-modal'); - if (!modal) return; - - skillModalAddMode = false; - skillFileDirty = false; - skillActivePath = 'SKILL.md'; - const pkg = document.getElementById('skill-package-editor'); - const addEd = document.getElementById('skill-add-editor'); - if (pkg) pkg.style.display = 'block'; - if (addEd) addEd.style.display = 'none'; - - document.getElementById('skill-modal-title').textContent = _t('skills.editSkill'); - document.getElementById('skill-name').value = skill.id || skillId; - document.getElementById('skill-name').disabled = true; - document.getElementById('skill-description').value = skill.description || ''; - + let files = []; if (filesRes.ok) { const fd = await filesRes.json(); - skillPackageFiles = fd.files || []; - } else { - skillPackageFiles = []; + files = fd.files || []; } - renderSkillPackageTree(); - - const ta = document.getElementById('skill-content'); - if (ta) ta.value = skill.content || ''; - const hint = document.getElementById('skill-body-hint-edit'); - if (hint) hint.style.display = 'block'; - currentEditingSkillName = skillId; - modal.style.display = 'flex'; + deferModalContent(() => { + document.getElementById('skill-name').value = skill.id || skillId; + document.getElementById('skill-description').value = skill.description || ''; + skillPackageFiles = files; + renderSkillPackageTree(); + if (ta) ta.value = skill.content || ''; + const hint = document.getElementById('skill-body-hint-edit'); + if (hint) hint.style.display = 'block'; + document.getElementById('skill-name')?.focus(); + }); } catch (error) { + closeSkillModal(); console.error('加载skill详情失败:', error); showNotification(_t('skills.loadDetailFailed') + ': ' + error.message, 'error'); } @@ -659,7 +660,7 @@ async function viewSkill(skillId) { `; document.body.appendChild(modal); - modal.style.display = 'flex'; + openAppModal(modal); const close = () => closeSkillViewModal(); modal.querySelectorAll('[data-skill-view-close]').forEach(el => el.addEventListener('click', close)); @@ -691,23 +692,22 @@ async function viewSkill(skillId) { // 关闭查看模态框 function closeSkillViewModal() { + closeAppModal('skill-view-modal'); const modal = document.getElementById('skill-view-modal'); if (modal) { modal.remove(); + syncAppModalBodyLock(); } } // 关闭skill模态框 function closeSkillModal() { - const modal = document.getElementById('skill-modal'); - if (modal) { - modal.style.display = 'none'; - currentEditingSkillName = null; - skillModalAddMode = true; - skillFileDirty = false; - skillPackageFiles = []; - skillActivePath = 'SKILL.md'; - } + closeAppModal('skill-modal'); + currentEditingSkillName = null; + skillModalAddMode = true; + skillFileDirty = false; + skillPackageFiles = []; + skillActivePath = 'SKILL.md'; } // 保存skill diff --git a/web/static/js/tasks.js b/web/static/js/tasks.js index 22a38f26..eb8b97a3 100644 --- a/web/static/js/tasks.js +++ b/web/static/js/tasks.js @@ -914,18 +914,14 @@ async function showBatchImportModal() { } } await refreshBatchProjectSelectOptions(); - - modal.style.display = 'block'; - input.focus(); + + openAppModal('batch-import-modal', { focusEl: input }); } } // 关闭新建任务模态框 function closeBatchImportModal() { - const modal = document.getElementById('batch-import-modal'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('batch-import-modal'); } function handleBatchScheduleModeChange() { @@ -1350,7 +1346,13 @@ async function showBatchQueueDetail(queueId) { const addTaskBtn = document.getElementById('batch-queue-add-task-btn'); if (!modal || !content) return; - + + const alreadyOpen = isAppModalOpen('batch-queue-detail-modal'); + if (!alreadyOpen) { + if (content) content.innerHTML = '

'; + openAppModal('batch-queue-detail-modal', { focus: false }); + } + try { // 加载角色列表(如果还未加载) let loadedRoles = []; @@ -1459,6 +1461,7 @@ async function showBatchQueueDetail(queueId) { const sameQueueAsBefore = prevDetailFor === queue.id; const savedTechDetailsOpen = sameQueueAsBefore && !!(prevTechDetails && prevTechDetails.open); + deferModalContent(function () { content.innerHTML = `
@@ -1529,8 +1532,7 @@ async function showBatchQueueDetail(queueId) { if (newTechDetails && savedTechDetailsOpen) { newTechDetails.open = true; } - - modal.style.display = 'block'; + }); // 仅运行中定时拉取详情;其它状态应停止,避免 innerHTML 重绘把
等 UI 打回默认态 if (queue.status === 'running') { @@ -1540,6 +1542,7 @@ async function showBatchQueueDetail(queueId) { } } catch (error) { console.error('获取队列详情失败:', error); + closeBatchQueueDetailModal(); alert(_t('tasks.getQueueDetailFailed') + ': ' + error.message); } } @@ -1708,10 +1711,7 @@ async function deleteBatchQueueFromList(queueId) { // 关闭批量任务队列详情模态框 function closeBatchQueueDetailModal() { - const modal = document.getElementById('batch-queue-detail-modal'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('batch-queue-detail-modal'); batchQueuesState.currentQueueId = null; stopBatchQueueRefresh(); } @@ -1730,7 +1730,7 @@ function startBatchQueueRefresh(queueId) { content.querySelector('.bq-inline-edit-controls') || content.querySelector('.batch-task-inline-edit') ); - if ((addModal && addModal.style.display === 'block') || hasInlineEdit) { + if ((addModal && isAppModalOpen('add-batch-task-modal')) || hasInlineEdit) { return; } if (batchQueuesState._bqDetailRefreshing) { @@ -1891,12 +1891,7 @@ function showAddBatchTaskModal() { } messageInput.value = ''; - modal.style.display = 'block'; - - // 聚焦到输入框 - setTimeout(() => { - messageInput.focus(); - }, 100); + openAppModal('add-batch-task-modal', { focusEl: messageInput }); // 清理旧的事件监听器 if (showAddBatchTaskModal._escHandler) { @@ -1940,9 +1935,7 @@ function closeAddBatchTaskModal() { } const modal = document.getElementById('add-batch-task-modal'); const messageInput = document.getElementById('add-task-message'); - if (modal) { - modal.style.display = 'none'; - } + closeAppModal('add-batch-task-modal'); if (messageInput) { messageInput.value = ''; } @@ -2462,7 +2455,7 @@ document.addEventListener('languagechange', function () { const detailModal = document.getElementById('batch-queue-detail-modal'); if ( detailModal && - detailModal.style.display === 'block' && + isAppModalOpen('batch-queue-detail-modal') && batchQueuesState.currentQueueId ) { showBatchQueueDetail(batchQueuesState.currentQueueId); diff --git a/web/static/js/vulnerability.js b/web/static/js/vulnerability.js index 0543711d..06b37ba7 100644 --- a/web/static/js/vulnerability.js +++ b/web/static/js/vulnerability.js @@ -1090,37 +1090,36 @@ async function showAddVulnerabilityModal() { document.getElementById('vulnerability-impact').value = ''; document.getElementById('vulnerability-recommendation').value = ''; - document.getElementById('vulnerability-modal').style.display = 'block'; + openAppModal('vulnerability-modal'); } // 编辑漏洞 async function editVulnerability(id) { try { - const response = await apiFetch(`/api/vulnerabilities/${id}`); - if (!response.ok) throw new Error(vulnT('vulnerabilityPage.fetchFailed')); - - const vuln = await response.json(); currentVulnerabilityId = id; document.getElementById('vulnerability-modal-title').textContent = vulnT('vulnerability.editVuln'); - - // 填充表单 - document.getElementById('vulnerability-conversation-id').value = vuln.conversation_id || ''; - document.getElementById('vulnerability-conversation-tag').value = vuln.conversation_tag || ''; - document.getElementById('vulnerability-task-tag').value = vuln.task_tag || ''; - document.getElementById('vulnerability-title').value = vuln.title || ''; - document.getElementById('vulnerability-description').value = vuln.description || ''; - document.getElementById('vulnerability-severity').value = vuln.severity || ''; - document.getElementById('vulnerability-status').value = vuln.status || 'open'; - document.getElementById('vulnerability-type').value = vuln.type || ''; - document.getElementById('vulnerability-target').value = vuln.target || ''; - document.getElementById('vulnerability-proof').value = vuln.proof || ''; - document.getElementById('vulnerability-impact').value = vuln.impact || ''; - document.getElementById('vulnerability-recommendation').value = vuln.recommendation || ''; - - await populateVulnerabilityModalProjectSelect(vuln.project_id || ''); - - document.getElementById('vulnerability-modal').style.display = 'block'; + openAppModal('vulnerability-modal', { focus: false }); + const response = await apiFetch(`/api/vulnerabilities/${id}`); + if (!response.ok) throw new Error(vulnT('vulnerabilityPage.fetchFailed')); + const vuln = await response.json(); + deferModalContent(async () => { + document.getElementById('vulnerability-conversation-id').value = vuln.conversation_id || ''; + document.getElementById('vulnerability-conversation-tag').value = vuln.conversation_tag || ''; + document.getElementById('vulnerability-task-tag').value = vuln.task_tag || ''; + document.getElementById('vulnerability-title').value = vuln.title || ''; + document.getElementById('vulnerability-description').value = vuln.description || ''; + document.getElementById('vulnerability-severity').value = vuln.severity || ''; + document.getElementById('vulnerability-status').value = vuln.status || 'open'; + document.getElementById('vulnerability-type').value = vuln.type || ''; + document.getElementById('vulnerability-target').value = vuln.target || ''; + document.getElementById('vulnerability-proof').value = vuln.proof || ''; + document.getElementById('vulnerability-impact').value = vuln.impact || ''; + document.getElementById('vulnerability-recommendation').value = vuln.recommendation || ''; + await populateVulnerabilityModalProjectSelect(vuln.project_id || ''); + document.getElementById('vulnerability-title')?.focus(); + }); } catch (error) { + closeVulnerabilityModal(); console.error('加载漏洞失败:', error); alert(vulnT('vulnerability.loadFailed') + ': ' + error.message); } @@ -1233,7 +1232,7 @@ async function deleteVulnerability(id) { // 关闭漏洞模态框 function closeVulnerabilityModal() { - document.getElementById('vulnerability-modal').style.display = 'none'; + closeAppModal('vulnerability-modal'); currentVulnerabilityId = null; } @@ -1749,7 +1748,7 @@ async function refreshVulnerabilityProjectFilter() { sel.innerHTML = html; if (cur) sel.value = cur; const modalSel = document.getElementById('vulnerability-project-id'); - if (modalSel && document.getElementById('vulnerability-modal')?.style.display === 'block') { + if (modalSel && isAppModalOpen('vulnerability-modal')) { const modalCur = modalSel.value || ''; modalSel.innerHTML = buildVulnerabilityProjectOptionsHtml(modalCur); modalSel.value = modalCur; diff --git a/web/static/js/webshell.js b/web/static/js/webshell.js index 151da66b..c06f82a0 100644 --- a/web/static/js/webshell.js +++ b/web/static/js/webshell.js @@ -2301,10 +2301,14 @@ function selectWebshell(id, stateReady) { function setDbProfileModalVisible(visible, mode) { if (!dbProfileModalEl) return; - dbProfileModalEl.style.display = visible ? 'block' : 'none'; - if (dbProfileModalTitleEl) { - if (mode === 'add') dbProfileModalTitleEl.textContent = wsT('webshell.dbAddProfile') || '新增连接'; - else dbProfileModalTitleEl.textContent = wsT('webshell.editConnectionTitle') || '编辑连接'; + if (visible) { + if (dbProfileModalTitleEl) { + if (mode === 'add') dbProfileModalTitleEl.textContent = wsT('webshell.dbAddProfile') || '新增连接'; + else dbProfileModalTitleEl.textContent = wsT('webshell.editConnectionTitle') || '编辑连接'; + } + openAppModal(dbProfileModalEl); + } else { + closeAppModal(dbProfileModalEl); } } @@ -4369,37 +4373,38 @@ function showAddWebshellModal() { var titleEl = document.getElementById('webshell-modal-title'); if (titleEl) titleEl.textContent = wsT('webshell.addConnection'); var modal = document.getElementById('webshell-modal'); - if (modal) modal.style.display = 'block'; + if (modal) openAppModal(modal); } // 打开编辑连接弹窗(预填当前连接信息) function showEditWebshellModal(connId) { var conn = webshellConnections.find(function (c) { return c.id === connId; }); if (!conn) return; - var editIdEl = document.getElementById('webshell-edit-id'); - if (editIdEl) editIdEl.value = conn.id; - document.getElementById('webshell-url').value = conn.url || ''; - document.getElementById('webshell-password').value = conn.password || ''; - document.getElementById('webshell-type').value = conn.type || 'php'; - document.getElementById('webshell-method').value = (conn.method || 'post').toLowerCase(); - document.getElementById('webshell-cmd-param').value = conn.cmdParam || ''; - var osEditEl = document.getElementById('webshell-os'); - if (osEditEl) osEditEl.value = normalizeWebshellOS(conn.os); - var encEditEl = document.getElementById('webshell-encoding'); - if (encEditEl) encEditEl.value = normalizeWebshellEncoding(conn.encoding); - document.getElementById('webshell-remark').value = conn.remark || ''; var titleEl = document.getElementById('webshell-modal-title'); if (titleEl) titleEl.textContent = wsT('webshell.editConnectionTitle'); - var modal = document.getElementById('webshell-modal'); - if (modal) modal.style.display = 'block'; + openAppModal('webshell-modal', { focus: false }); + deferModalContent(function () { + var editIdEl = document.getElementById('webshell-edit-id'); + if (editIdEl) editIdEl.value = conn.id; + document.getElementById('webshell-url').value = conn.url || ''; + document.getElementById('webshell-password').value = conn.password || ''; + document.getElementById('webshell-type').value = conn.type || 'php'; + document.getElementById('webshell-method').value = (conn.method || 'post').toLowerCase(); + document.getElementById('webshell-cmd-param').value = conn.cmdParam || ''; + var osEditEl = document.getElementById('webshell-os'); + if (osEditEl) osEditEl.value = normalizeWebshellOS(conn.os); + var encEditEl = document.getElementById('webshell-encoding'); + if (encEditEl) encEditEl.value = normalizeWebshellEncoding(conn.encoding); + document.getElementById('webshell-remark').value = conn.remark || ''; + document.getElementById('webshell-url')?.focus(); + }); } // 关闭弹窗 function closeWebshellModal() { var editIdEl = document.getElementById('webshell-edit-id'); if (editIdEl) editIdEl.value = ''; - var modal = document.getElementById('webshell-modal'); - if (modal) modal.style.display = 'none'; + closeAppModal('webshell-modal'); } // 语言切换时刷新 WebShell 页面内所有由 JS 生成的文案(不重建终端) @@ -4571,7 +4576,7 @@ function refreshWebshellUIOnLanguageChange() { } var modal = document.getElementById('webshell-modal'); - if (modal && modal.style.display === 'block') { + if (modal && isAppModalOpen('webshell-modal')) { var titleEl = document.getElementById('webshell-modal-title'); var editIdEl = document.getElementById('webshell-edit-id'); if (titleEl) { diff --git a/web/templates/index.html b/web/templates/index.html index 8fb59359..bb9f6f71 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -4172,7 +4172,7 @@
- +
@@ -4290,6 +4290,7 @@ +