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 =
- '
' +
- '' +
- '
' +
- '
' + esc(auditT('settingsAudit.detailTime', null, '时间')) + ': ' + esc(formatAuditTime(log.createdAt)) + '
' +
- '
' + esc(auditT('settingsAudit.detailCategory', null, '类别')) + ': ' + catAction + '
' +
- '
' + esc(auditT('settingsAudit.detailResult', null, '结果')) + ': ' + esc(log.result || '') + '
' +
- '
' + esc(auditT('settingsAudit.detailMessage', null, '说明')) + ': ' + esc(log.message || '') + '
' +
- (log.clientIp ? '
IP: ' + esc(log.clientIp) + '
' : '') +
- (log.sessionHint ? '
' + esc(auditT('settingsAudit.detailSession', null, '会话')) + ': ' + esc(log.sessionHint) + '
' : '') +
- (log.userAgent ? '
UA: ' + esc(log.userAgent) + '
' : '') +
- auditResourceMeta(log) +
- (detail ? '
' + esc(detail) + '
' : '') +
- '
' +
- '' +
- '
';
- 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 =
+ '' +
+ '' +
+ '
' +
+ '
' + esc(auditT('settingsAudit.detailTime', null, '时间')) + ': ' + esc(formatAuditTime(log.createdAt)) + '
' +
+ '
' + esc(auditT('settingsAudit.detailCategory', null, '类别')) + ': ' + catAction + '
' +
+ '
' + esc(auditT('settingsAudit.detailResult', null, '结果')) + ': ' + esc(log.result || '') + '
' +
+ '
' + esc(auditT('settingsAudit.detailMessage', null, '说明')) + ': ' + esc(log.message || '') + '
' +
+ (log.clientIp ? '
IP: ' + esc(log.clientIp) + '
' : '') +
+ (log.sessionHint ? '
' + esc(auditT('settingsAudit.detailSession', null, '会话')) + ': ' + esc(log.sessionHint) + '
' : '') +
+ (log.userAgent ? '
UA: ' + esc(log.userAgent) + '
' : '') +
+ auditResourceMeta(log) +
+ (detail ? '
' + esc(detail) + '
' : '') +
+ '
' +
+ '' +
+ '
';
+ 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 = `
`;
- 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 = `
`;
- document.body.appendChild(modal);
-
const queryTextarea = document.getElementById('fofa-parse-query');
if (queryTextarea) {
queryTextarea.value = (parsed?.query || '').trim();
- setTimeout(() => {
- try { queryTextarea.focus(); } catch (e) { /* ignore */ }
- }, 0);
+ queryTextarea.focus();
}
- const close = () => modal.remove();
- modal.addEventListener('click', (e) => {
+ const close = function () {
+ closeAppModal(modal);
+ modal.remove();
+ syncAppModalBodyLock();
+ };
+ modal.addEventListener('click', function (e) {
if (e.target === modal) close();
});
document.getElementById('fofa-parse-modal-close')?.addEventListener('click', close);
document.getElementById('fofa-parse-cancel')?.addEventListener('click', close);
- const applyToQuery = (run) => {
+ const applyToQuery = function (run) {
const els = getFofaFormElements();
const q = (queryTextarea?.value || '').trim();
if (!q) {
@@ -435,6 +437,7 @@ function showFofaParseModal(nlText, parsed) {
}
};
document.addEventListener('keydown', onKey);
+ });
}
function setFofaMeta(text) {
@@ -1091,8 +1094,13 @@ function showCellDetailModal(field, fullText) {
`;
document.body.appendChild(modal);
+ openAppModal(modal);
- const close = () => modal.remove();
+ const close = function () {
+ closeAppModal(modal);
+ modal.remove();
+ syncAppModalBodyLock();
+ };
modal.addEventListener('click', (e) => {
if (e.target === modal) close();
});
diff --git a/web/static/js/knowledge.js b/web/static/js/knowledge.js
index 8325fc53..37db2add 100644
--- a/web/static/js/knowledge.js
+++ b/web/static/js/knowledge.js
@@ -905,25 +905,32 @@ function showAddKnowledgeItemModal() {
document.getElementById('knowledge-item-category').value = '';
document.getElementById('knowledge-item-title').value = '';
document.getElementById('knowledge-item-content').value = '';
- document.getElementById('knowledge-item-modal').style.display = 'block';
+ openAppModal('knowledge-item-modal');
}
// 编辑知识项
async function editKnowledgeItem(id) {
try {
+ currentEditingItemId = id;
+ document.getElementById('knowledge-item-modal-title').textContent = '编辑知识';
+ document.getElementById('knowledge-item-category').value = '';
+ document.getElementById('knowledge-item-title').value = '';
+ document.getElementById('knowledge-item-content').value = '';
+ openAppModal('knowledge-item-modal', { focus: false });
const response = await apiFetch(`/api/knowledge/items/${id}`);
if (!response.ok) {
throw new Error('获取知识项失败');
}
const item = await response.json();
-
- currentEditingItemId = id;
- document.getElementById('knowledge-item-modal-title').textContent = '编辑知识';
- document.getElementById('knowledge-item-category').value = item.category;
- document.getElementById('knowledge-item-title').value = item.title;
- document.getElementById('knowledge-item-content').value = item.content;
- document.getElementById('knowledge-item-modal').style.display = 'block';
+ deferModalContent(() => {
+ document.getElementById('knowledge-item-category').value = item.category;
+ document.getElementById('knowledge-item-title').value = item.title;
+ document.getElementById('knowledge-item-content').value = item.content;
+ document.getElementById('knowledge-item-title')?.focus();
+ });
} catch (error) {
+ closeAppModal('knowledge-item-modal');
+ currentEditingItemId = null;
console.error('编辑知识项失败:', error);
showNotification('编辑知识项失败: ' + error.message, 'error');
}
@@ -1232,10 +1239,7 @@ function updateKnowledgeStatsAfterDelete() {
// 关闭知识项模态框
function closeKnowledgeItemModal() {
- const modal = document.getElementById('knowledge-item-modal');
- if (modal) {
- modal.style.display = 'none';
- }
+ closeAppModal('knowledge-item-modal');
// 重置编辑状态
currentEditingItemId = null;
@@ -1786,8 +1790,11 @@ function showRetrievalLogDetailsModal(log, retrievedItems) {
document.body.appendChild(modal);
}
- // 填充内容
const content = document.getElementById('retrieval-log-details-content');
+ if (content) content.innerHTML = '
…
';
+ openAppModal(modal, { focus: false });
+
+ deferModalContent(() => {
const timeAgo = getTimeAgo(log.createdAt);
const fullTime = formatTime(log.createdAt);
@@ -1880,16 +1887,12 @@ function showRetrievalLogDetailsModal(log, retrievedItems) {
`;
-
- 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 @@