diff --git a/web/static/css/style.css b/web/static/css/style.css index 0b76f1df..ef5decad 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -4466,6 +4466,16 @@ header { font-size: 0.875rem; } +.audit-resource-removed { + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.audit-resource-meta code { + font-size: 0.8125rem; + word-break: break-all; +} + /* 系统设置 - 终端 */ .terminal-wrapper { border: 1px solid var(--border-color); diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 5beff619..0cf458e7 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -1862,6 +1862,9 @@ "markdown_delete": "Delete sub-agent" }, "openResource": "Open linked resource", + "openResourceChat": "Open linked resource (chat)", + "resourceIdLabel": "Resource ID", + "resourceRemoved": "(resource no longer exists)", "filterAll": "All", "filterBtn": "Filter", "resetBtn": "Reset", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 0883d42c..556bd3a7 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -1851,6 +1851,9 @@ "markdown_delete": "删除子代理" }, "openResource": "打开关联资源", + "openResourceChat": "打开关联资源(chat)", + "resourceIdLabel": "资源 ID", + "resourceRemoved": "(关联对象已删除)", "filterAll": "全部", "filterBtn": "筛选", "resetBtn": "重置", diff --git a/web/static/js/audit.js b/web/static/js/audit.js index f3a26f64..84ec4bc0 100644 --- a/web/static/js/audit.js +++ b/web/static/js/audit.js @@ -15,7 +15,7 @@ const AUDIT_ACTIONS_BY_CATEGORY = { 'session_delete', 'task_create', 'task_cancel', 'task_delete'], webshell: ['connection_create', 'connection_delete'], knowledge: ['item_delete', 'index_rebuild'], - conversation: ['delete', 'delete_turn'], + conversation: ['create', 'delete', 'delete_turn'], vulnerability: ['create', 'update', 'delete'], external_mcp: ['upsert', 'delete'], task: ['create_queue', 'start_queue', 'delete_queue', 'pause_queue', 'rerun_queue', 'delete_batch_task'], @@ -337,15 +337,84 @@ function resetAuditLogFilters() { filterAuditLogs(); } +/** 资源已被删除/移除的审计操作,不再提供「打开关联资源」 */ +const AUDIT_ACTIONS_RESOURCE_REMOVED = { + delete: true, + item_delete: true, + connection_delete: true, + listener_delete: true, + session_delete: true, + task_delete: true, + execution_delete: true, + execution_delete_batch: true, + delete_queue: true, + delete_batch_task: true, + markdown_delete: true +}; + +function auditResourceWasRemoved(log) { + if (!log || !log.action) return false; + return !!AUDIT_ACTIONS_RESOURCE_REMOVED[log.action]; +} + +/** 删除类操作,或关联资源已不存在(由详情 API resourceAvailable 判定) */ +function auditResourceUnavailable(log) { + if (!log) return false; + if (auditResourceWasRemoved(log)) return true; + return log.resourceAvailable === false; +} + +function auditResourceMeta(log) { + if (!log || !log.resourceId) return ''; + const esc = typeof escapeHtml === 'function' ? escapeHtml : function (s) { return String(s || ''); }; + const id = esc(log.resourceId); + if (auditResourceUnavailable(log)) { + const idLabel = esc(auditT('settingsAudit.resourceIdLabel', null, '资源 ID')); + const removed = esc(auditT('settingsAudit.resourceRemoved', null, '(关联对象已删除)')); + return '
'; + } + const link = auditResourceLink(log); + return link || ('ID: ' + id + '
'); +} + +async function auditOpenConversationChat(conversationId) { + const id = String(conversationId || '').trim(); + if (!id) return; + if (typeof apiFetch === 'function') { + try { + const r = await apiFetch('/api/conversations/' + encodeURIComponent(id)); + if (!r.ok) { + if (typeof showToast === 'function') { + showToast(auditT('settingsAudit.resourceRemoved', null, '(关联对象已删除)'), 'warning'); + } + return; + } + } catch (_) { + return; + } + } + closeAuditDetailModal(); + if (typeof switchPage === 'function') { + switchPage('chat'); + } + if (typeof loadConversation === 'function') { + void loadConversation(id); + } +} +window.auditOpenConversationChat = auditOpenConversationChat; + function auditResourceLink(log) { - if (!log) return ''; + if (!log || auditResourceUnavailable(log)) return ''; const type = log.resourceType || ''; const id = log.resourceId || ''; if (!id) return ''; const esc = typeof escapeHtml === 'function' ? escapeHtml : function (s) { return String(s || ''); }; const label = esc(auditT('settingsAudit.openResource', null, '打开关联资源')); if (type === 'conversation' || (type === '' && id.length > 8 && !id.startsWith('c2_'))) { - return ''; + const chatLabel = esc(auditT('settingsAudit.openResourceChat', null, '打开关联资源(chat)')); + return ''; } if (type === 'vulnerability' || type === 'batch_queue') { const page = type === 'batch_queue' ? 'tasks' : 'vulnerabilities'; @@ -370,7 +439,7 @@ function auditResourceLink(log) { if (type === 'role' || type === 'skill' || type === 'markdown_agent') { return ''; } - return id ? 'ID: ' + esc(id) + '
' : ''; + return ''; } function refreshAuditLogs() { @@ -497,13 +566,19 @@ async function showAuditLogDetail(id) { (log.clientIp ? 'IP: ' + esc(log.clientIp) + '
' : '') + (log.sessionHint ? '' + esc(auditT('settingsAudit.detailSession', null, '会话')) + ': ' + esc(log.sessionHint) + '
' : '') + (log.userAgent ? 'UA: ' + esc(log.userAgent) + '
' : '') + - auditResourceLink(log) + + 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')); + }); + } overlay.addEventListener('click', function (ev) { if (ev.target === overlay) closeAuditDetailModal(); });