mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-30 17:55:32 +02:00
Add files via upload
This commit is contained in:
@@ -2042,6 +2042,12 @@ header {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.hitl-logs-retention-hint {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.hitl-logs-empty-hint {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -736,6 +736,20 @@
|
||||
"saveFailed": "Save failed",
|
||||
"deleteConfirm": "Delete this audit log?",
|
||||
"deleteFailed": "Delete failed",
|
||||
"retentionHint": "Audit logs are kept for {{days}} days, then purged automatically.",
|
||||
"selectedCount": "{{count}} selected",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all",
|
||||
"batchDelete": "Batch delete",
|
||||
"batchDeleteConfirm": "Delete the selected {{count}} audit log(s)? This cannot be undone.",
|
||||
"batchDeleteSuccess": "Successfully deleted {{count}} audit log(s)",
|
||||
"batchDeleteFailed": "Batch delete failed",
|
||||
"clearAll": "Clear all",
|
||||
"clearAllConfirm": "Clear all {{count}} audit log(s) matching the current filters? This cannot be undone.",
|
||||
"clearAllConfirmNoFilter": "No filters are set. This will clear all {{count}} audit log(s). This cannot be undone. Continue?",
|
||||
"clearAllSuccess": "Cleared {{count}} audit log(s)",
|
||||
"clearAllFailed": "Clear failed",
|
||||
"selectLogsFirst": "Select audit logs to delete first",
|
||||
"loading": "Loading...",
|
||||
"emptyState": "No pending approvals",
|
||||
"dismiss": "Dismiss",
|
||||
|
||||
@@ -724,6 +724,20 @@
|
||||
"saveFailed": "保存失败",
|
||||
"deleteConfirm": "确定删除这条审计日志?",
|
||||
"deleteFailed": "删除失败",
|
||||
"retentionHint": "审计日志保留 {{days}} 天,超期自动清理",
|
||||
"selectedCount": "已选择 {{count}} 项",
|
||||
"selectAll": "全选",
|
||||
"deselectAll": "取消全选",
|
||||
"batchDelete": "批量删除",
|
||||
"batchDeleteConfirm": "确定删除选中的 {{count}} 条审计日志?此操作不可恢复。",
|
||||
"batchDeleteSuccess": "成功删除 {{count}} 条审计日志",
|
||||
"batchDeleteFailed": "批量删除失败",
|
||||
"clearAll": "清空",
|
||||
"clearAllConfirm": "确定清空当前筛选条件下的全部 {{count}} 条审计日志?此操作不可恢复。",
|
||||
"clearAllConfirmNoFilter": "未设置筛选条件,将清空全部 {{count}} 条审计日志。此操作不可恢复,是否继续?",
|
||||
"clearAllSuccess": "已清空 {{count}} 条审计日志",
|
||||
"clearAllFailed": "清空失败",
|
||||
"selectLogsFirst": "请先选择要删除的审计日志",
|
||||
"loading": "加载中...",
|
||||
"emptyState": "暂无待审批项",
|
||||
"dismiss": "忽略",
|
||||
|
||||
+199
-8
@@ -786,6 +786,8 @@ let hitlLogsPageSize = 20;
|
||||
let hitlLogsTotal = 0;
|
||||
let hitlLogsCache = [];
|
||||
let hitlLogsLoaded = false;
|
||||
let hitlLogsRetentionDays = 0;
|
||||
const hitlSelectedLogs = new Set();
|
||||
let hitlPendingPage = 1;
|
||||
let hitlPendingPageSize = 20;
|
||||
let hitlPendingTotal = 0;
|
||||
@@ -983,6 +985,182 @@ function hitlFormatTime(v) {
|
||||
}
|
||||
}
|
||||
|
||||
function hitlLogsHasActiveFilters() {
|
||||
const qEl = document.getElementById('hitl-logs-search');
|
||||
const decEl = document.getElementById('hitl-logs-decision-filter');
|
||||
const byEl = document.getElementById('hitl-logs-decidedby-filter');
|
||||
return Boolean(
|
||||
(qEl && qEl.value.trim()) ||
|
||||
(decEl && decEl.value && decEl.value !== 'all') ||
|
||||
(byEl && byEl.value && byEl.value !== 'all')
|
||||
);
|
||||
}
|
||||
|
||||
function hitlLogsFilterParams() {
|
||||
const params = new URLSearchParams();
|
||||
const qEl = document.getElementById('hitl-logs-search');
|
||||
const decEl = document.getElementById('hitl-logs-decision-filter');
|
||||
const byEl = document.getElementById('hitl-logs-decidedby-filter');
|
||||
if (qEl && qEl.value.trim()) params.set('q', qEl.value.trim());
|
||||
if (decEl && decEl.value && decEl.value !== 'all') params.set('decision', decEl.value);
|
||||
if (byEl && byEl.value && byEl.value !== 'all') params.set('decidedBy', byEl.value);
|
||||
return params;
|
||||
}
|
||||
|
||||
function updateHitlLogsRetentionHint() {
|
||||
const el = document.getElementById('hitl-logs-retention-hint');
|
||||
if (!el) return;
|
||||
if (typeof hitlLogsRetentionDays === 'number' && hitlLogsRetentionDays > 0) {
|
||||
el.textContent = hitlT('retentionHint', 'Audit logs are kept for {{days}} days, then purged automatically.', { days: hitlLogsRetentionDays });
|
||||
el.hidden = false;
|
||||
} else {
|
||||
el.textContent = '';
|
||||
el.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function updateHitlLogsBatchActionsState() {
|
||||
const selectedCount = hitlSelectedLogs.size;
|
||||
const batchActions = document.getElementById('hitl-logs-batch-actions');
|
||||
const selectedCountSpan = document.getElementById('hitl-logs-selected-count');
|
||||
if (batchActions) {
|
||||
batchActions.style.display = selectedCount > 0 ? 'flex' : 'none';
|
||||
}
|
||||
if (selectedCountSpan) {
|
||||
selectedCountSpan.textContent = hitlT('selectedCount', '{{count}} selected', { count: selectedCount });
|
||||
}
|
||||
const selectAllCheckbox = document.getElementById('hitl-logs-select-all');
|
||||
if (selectAllCheckbox) {
|
||||
const allCheckboxes = document.querySelectorAll('.hitl-log-checkbox');
|
||||
if (allCheckboxes.length === 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
} else {
|
||||
const checkedOnPage = Array.from(allCheckboxes).filter(function (cb) {
|
||||
return hitlSelectedLogs.has(cb.value);
|
||||
}).length;
|
||||
selectAllCheckbox.checked = checkedOnPage === allCheckboxes.length;
|
||||
selectAllCheckbox.indeterminate = checkedOnPage > 0 && checkedOnPage < allCheckboxes.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleHitlLogSelection(id, checked) {
|
||||
if (!id) return;
|
||||
if (checked) {
|
||||
hitlSelectedLogs.add(id);
|
||||
} else {
|
||||
hitlSelectedLogs.delete(id);
|
||||
}
|
||||
updateHitlLogsBatchActionsState();
|
||||
}
|
||||
|
||||
function toggleHitlLogsSelectAll(checkbox) {
|
||||
const checkboxes = document.querySelectorAll('.hitl-log-checkbox');
|
||||
checkboxes.forEach(function (cb) {
|
||||
cb.checked = checkbox.checked;
|
||||
if (checkbox.checked) {
|
||||
hitlSelectedLogs.add(cb.value);
|
||||
} else {
|
||||
hitlSelectedLogs.delete(cb.value);
|
||||
}
|
||||
});
|
||||
updateHitlLogsBatchActionsState();
|
||||
}
|
||||
|
||||
function selectAllHitlLogs() {
|
||||
const checkboxes = document.querySelectorAll('.hitl-log-checkbox');
|
||||
checkboxes.forEach(function (cb) {
|
||||
cb.checked = true;
|
||||
hitlSelectedLogs.add(cb.value);
|
||||
});
|
||||
const selectAllCheckbox = document.getElementById('hitl-logs-select-all');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = true;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
}
|
||||
updateHitlLogsBatchActionsState();
|
||||
}
|
||||
|
||||
function deselectAllHitlLogs() {
|
||||
const checkboxes = document.querySelectorAll('.hitl-log-checkbox');
|
||||
checkboxes.forEach(function (cb) {
|
||||
cb.checked = false;
|
||||
});
|
||||
hitlSelectedLogs.clear();
|
||||
const selectAllCheckbox = document.getElementById('hitl-logs-select-all');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
}
|
||||
updateHitlLogsBatchActionsState();
|
||||
}
|
||||
|
||||
async function batchDeleteHitlLogs() {
|
||||
const ids = Array.from(hitlSelectedLogs);
|
||||
if (!ids.length) {
|
||||
alert(hitlT('selectLogsFirst', 'Select audit logs to delete first'));
|
||||
return;
|
||||
}
|
||||
const count = ids.length;
|
||||
if (!confirm(hitlT('batchDeleteConfirm', 'Delete the selected {{count}} audit log(s)? This cannot be undone.', { count: count }))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const resp = await hitlApiFetch('/api/hitl/logs', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ ids: ids })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(function () { return {}; });
|
||||
throw new Error(err.error || hitlT('batchDeleteFailed', 'Batch delete failed'));
|
||||
}
|
||||
const result = await resp.json().catch(function () { return {}; });
|
||||
const deletedCount = typeof result.deleted === 'number' ? result.deleted : count;
|
||||
ids.forEach(function (id) { hitlSelectedLogs.delete(id); });
|
||||
await refreshHitlLogs();
|
||||
alert(hitlT('batchDeleteSuccess', 'Successfully deleted {{count}} audit log(s)', { count: deletedCount }));
|
||||
} catch (e) {
|
||||
console.error('batchDeleteHitlLogs', e);
|
||||
alert(hitlT('batchDeleteFailed', 'Batch delete failed') + ': ' + (e && e.message ? e.message : String(e)));
|
||||
}
|
||||
}
|
||||
|
||||
async function clearHitlLogs() {
|
||||
const count = hitlLogsTotal || 0;
|
||||
if (count <= 0) {
|
||||
return;
|
||||
}
|
||||
const confirmKey = hitlLogsHasActiveFilters() ? 'clearAllConfirm' : 'clearAllConfirmNoFilter';
|
||||
if (!confirm(hitlT(confirmKey, 'Clear all {{count}} audit log(s)? This cannot be undone.', { count: count }))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const params = hitlLogsFilterParams();
|
||||
const resp = await hitlApiFetch('/api/hitl/logs' + (params.toString() ? '?' + params.toString() : ''), {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ all: true })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(function () { return {}; });
|
||||
throw new Error(err.error || hitlT('clearAllFailed', 'Clear failed'));
|
||||
}
|
||||
const result = await resp.json().catch(function () { return {}; });
|
||||
const deletedCount = typeof result.deleted === 'number' ? result.deleted : count;
|
||||
hitlSelectedLogs.clear();
|
||||
hitlLogsPage = 1;
|
||||
await refreshHitlLogs();
|
||||
alert(hitlT('clearAllSuccess', 'Cleared {{count}} audit log(s)', { count: deletedCount }));
|
||||
} catch (e) {
|
||||
console.error('clearHitlLogs', e);
|
||||
alert(hitlT('clearAllFailed', 'Clear failed') + ': ' + (e && e.message ? e.message : String(e)));
|
||||
}
|
||||
}
|
||||
|
||||
function renderHitlLogsTable(items) {
|
||||
const wrap = document.getElementById('hitl-logs-table-wrap');
|
||||
if (!wrap) return;
|
||||
@@ -993,18 +1171,24 @@ function renderHitlLogsTable(items) {
|
||||
'<p>' + escapeHtml(hitlT('logsEmpty', 'No audit logs')) + '</p>' +
|
||||
'<p class="hitl-logs-empty-hint">' + escapeHtml(hitlT('logsEmptyHint', 'Records appear here after HITL decisions.')) + '</p>' +
|
||||
'</div>';
|
||||
const batchActions = document.getElementById('hitl-logs-batch-actions');
|
||||
if (batchActions) batchActions.style.display = 'none';
|
||||
renderHitlLogsPagination();
|
||||
return;
|
||||
}
|
||||
const rows = list.map(function (item) {
|
||||
const id = escapeHtml(String(item.id || ''));
|
||||
const qId = JSON.stringify(String(item.id || '')).replace(/"/g, '"');
|
||||
const rawId = String(item.id || '');
|
||||
const id = escapeHtml(rawId);
|
||||
const jsId = rawId.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
||||
const qId = JSON.stringify(rawId).replace(/"/g, '"');
|
||||
const isSelected = hitlSelectedLogs.has(rawId);
|
||||
const payloadObj = hitlParsePayloadObject(item.payload || '');
|
||||
const decision = String(item.decision || '-');
|
||||
const decisionCls = decision === 'approve' ? 'hitl-decision--approve' : (decision === 'reject' ? 'hitl-decision--reject' : '');
|
||||
const summary = hitlPayloadSummary(payloadObj);
|
||||
return (
|
||||
'<tr>' +
|
||||
'<td><input type="checkbox" class="hitl-log-checkbox" value="' + id + '" ' + (isSelected ? 'checked' : '') + ' onchange="toggleHitlLogSelection(\'' + jsId + '\', this.checked)" /></td>' +
|
||||
'<td class="hitl-logs-cell-mono">' + id + '</td>' +
|
||||
'<td>' + escapeHtml(String(item.toolName || '-')) + '</td>' +
|
||||
'<td class="hitl-logs-cell-mono">' + escapeHtml(String(item.conversationId || '-')) + '</td>' +
|
||||
@@ -1021,6 +1205,7 @@ function renderHitlLogsTable(items) {
|
||||
wrap.innerHTML =
|
||||
'<table class="hitl-logs-table">' +
|
||||
'<thead><tr>' +
|
||||
'<th><input type="checkbox" id="hitl-logs-select-all" onchange="toggleHitlLogsSelectAll(this)" aria-label="select all" /></th>' +
|
||||
'<th>' + escapeHtml(hitlT('colId', 'ID')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colTool', 'Tool')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colConversation', 'Conversation')) + '</th>' +
|
||||
@@ -1030,6 +1215,7 @@ function renderHitlLogsTable(items) {
|
||||
'<th>' + escapeHtml(hitlT('colTime', 'Time')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colActions', 'Actions')) + '</th>' +
|
||||
'</tr></thead><tbody>' + rows + '</tbody></table>';
|
||||
updateHitlLogsBatchActionsState();
|
||||
renderHitlLogsPagination();
|
||||
}
|
||||
|
||||
@@ -1042,17 +1228,15 @@ async function refreshHitlLogs() {
|
||||
page: String(hitlLogsPage),
|
||||
pageSize: String(hitlLogsPageSize)
|
||||
});
|
||||
const qEl = document.getElementById('hitl-logs-search');
|
||||
const decEl = document.getElementById('hitl-logs-decision-filter');
|
||||
const byEl = document.getElementById('hitl-logs-decidedby-filter');
|
||||
if (qEl && qEl.value.trim()) params.set('q', qEl.value.trim());
|
||||
if (decEl && decEl.value && decEl.value !== 'all') params.set('decision', decEl.value);
|
||||
if (byEl && byEl.value && byEl.value !== 'all') params.set('decidedBy', byEl.value);
|
||||
const filterParams = hitlLogsFilterParams();
|
||||
filterParams.forEach(function (value, key) { params.set(key, value); });
|
||||
const resp = await hitlApiFetch('/api/hitl/logs?' + params.toString(), { credentials: 'same-origin' });
|
||||
if (!resp.ok) throw new Error('request failed');
|
||||
const data = await resp.json();
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
hitlLogsTotal = typeof data.total === 'number' ? data.total : items.length;
|
||||
hitlLogsRetentionDays = typeof data.retentionDays === 'number' ? data.retentionDays : 0;
|
||||
updateHitlLogsRetentionHint();
|
||||
const maxPage = Math.max(1, Math.ceil(hitlLogsTotal / hitlLogsPageSize));
|
||||
if (hitlLogsPage > maxPage) {
|
||||
hitlLogsPage = maxPage;
|
||||
@@ -1076,6 +1260,7 @@ function filterHitlLogs() {
|
||||
|
||||
function refreshHitlLogsI18n() {
|
||||
if (!document.getElementById('hitl-logs-table-wrap') || !hitlLogsLoaded) return;
|
||||
updateHitlLogsRetentionHint();
|
||||
renderHitlLogsTable(hitlLogsCache);
|
||||
}
|
||||
|
||||
@@ -1246,6 +1431,12 @@ window.closeHitlLogModal = closeHitlLogModal;
|
||||
window.hitlLogsGoPage = hitlLogsGoPage;
|
||||
window.hitlPendingGoPage = hitlPendingGoPage;
|
||||
window.filterHitlLogs = filterHitlLogs;
|
||||
window.batchDeleteHitlLogs = batchDeleteHitlLogs;
|
||||
window.clearHitlLogs = clearHitlLogs;
|
||||
window.selectAllHitlLogs = selectAllHitlLogs;
|
||||
window.deselectAllHitlLogs = deselectAllHitlLogs;
|
||||
window.toggleHitlLogSelection = toggleHitlLogSelection;
|
||||
window.toggleHitlLogsSelectAll = toggleHitlLogsSelectAll;
|
||||
window.filterHitlPending = filterHitlPending;
|
||||
window.onHitlLogsPageSizeChange = onHitlLogsPageSizeChange;
|
||||
window.onHitlPendingPageSizeChange = onHitlPendingPageSizeChange;
|
||||
|
||||
@@ -1244,6 +1244,18 @@
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="btn-secondary" onclick="filterHitlLogs()" data-i18n="hitl.searchApply">搜索</button>
|
||||
<button type="button" class="btn-secondary btn-delete" onclick="clearHitlLogs()" data-i18n="hitl.clearAll">清空</button>
|
||||
</div>
|
||||
<p id="hitl-logs-retention-hint" class="hitl-logs-retention-hint" hidden></p>
|
||||
<div id="hitl-logs-batch-actions" class="monitor-batch-actions" style="display: none;">
|
||||
<div class="batch-actions-info">
|
||||
<span id="hitl-logs-selected-count" data-i18n="hitl.selectedCount" data-i18n-params='{"count":0}'>已选择 0 项</span>
|
||||
</div>
|
||||
<div class="batch-actions-buttons">
|
||||
<button type="button" class="btn-secondary" onclick="selectAllHitlLogs()" data-i18n="hitl.selectAll">全选</button>
|
||||
<button type="button" class="btn-secondary" onclick="deselectAllHitlLogs()" data-i18n="hitl.deselectAll">取消全选</button>
|
||||
<button type="button" class="btn-secondary btn-delete" onclick="batchDeleteHitlLogs()" data-i18n="hitl.batchDelete">批量删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hitl-logs-table-wrap" class="hitl-logs-table-wrap">
|
||||
<div class="loading-spinner" data-i18n="hitl.loading">加载中...</div>
|
||||
|
||||
Reference in New Issue
Block a user