mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-28 05:40:33 +02:00
Add files via upload
This commit is contained in:
+207
-10
@@ -1,6 +1,10 @@
|
||||
const AUTH_STORAGE_KEY = 'cyberstrike-auth';
|
||||
let authToken = null;
|
||||
let authTokenExpiry = null;
|
||||
let authUser = null;
|
||||
let authRoles = [];
|
||||
let authPermissions = new Set();
|
||||
let authScope = '';
|
||||
let authPromise = null;
|
||||
let authPromiseResolvers = [];
|
||||
let isAppInitialized = false;
|
||||
@@ -9,28 +13,42 @@ function isTokenValid() {
|
||||
return !!authToken && authTokenExpiry instanceof Date && authTokenExpiry.getTime() > Date.now();
|
||||
}
|
||||
|
||||
function saveAuth(token, expiresAt) {
|
||||
function saveAuth(token, expiresAt, meta = {}) {
|
||||
const expiry = expiresAt instanceof Date ? expiresAt : new Date(expiresAt);
|
||||
authToken = token;
|
||||
authTokenExpiry = expiry;
|
||||
authUser = meta.user || null;
|
||||
authRoles = Array.isArray(meta.roles) ? meta.roles : [];
|
||||
authPermissions = new Set(Array.isArray(meta.permissions) ? meta.permissions : []);
|
||||
authScope = meta.scope || '';
|
||||
try {
|
||||
localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify({
|
||||
token,
|
||||
expiresAt: expiry.toISOString(),
|
||||
user: authUser,
|
||||
roles: authRoles,
|
||||
permissions: Array.from(authPermissions),
|
||||
scope: authScope,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn('无法持久化认证信息:', error);
|
||||
}
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
|
||||
function clearAuthStorage() {
|
||||
authToken = null;
|
||||
authTokenExpiry = null;
|
||||
authUser = null;
|
||||
authRoles = [];
|
||||
authPermissions = new Set();
|
||||
authScope = '';
|
||||
try {
|
||||
localStorage.removeItem(AUTH_STORAGE_KEY);
|
||||
} catch (error) {
|
||||
console.warn('无法清除认证信息:', error);
|
||||
}
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
|
||||
function loadAuthFromStorage() {
|
||||
@@ -51,6 +69,10 @@ function loadAuthFromStorage() {
|
||||
}
|
||||
authToken = stored.token;
|
||||
authTokenExpiry = expiry;
|
||||
authUser = stored.user || null;
|
||||
authRoles = Array.isArray(stored.roles) ? stored.roles : [];
|
||||
authPermissions = new Set(Array.isArray(stored.permissions) ? stored.permissions : []);
|
||||
authScope = stored.scope || '';
|
||||
return isTokenValid();
|
||||
} catch (error) {
|
||||
console.error('读取认证信息失败:', error);
|
||||
@@ -68,6 +90,7 @@ function resolveAuthPromises(success) {
|
||||
function showLoginOverlay(message = '') {
|
||||
const overlay = document.getElementById('login-overlay');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const usernameInput = document.getElementById('login-username');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
if (!overlay) {
|
||||
return;
|
||||
@@ -83,7 +106,9 @@ function showLoginOverlay(message = '') {
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
if (passwordInput) {
|
||||
if (usernameInput && !usernameInput.value) {
|
||||
usernameInput.focus();
|
||||
} else if (passwordInput) {
|
||||
passwordInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
@@ -92,6 +117,7 @@ function showLoginOverlay(message = '') {
|
||||
function hideLoginOverlay() {
|
||||
const overlay = document.getElementById('login-overlay');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const usernameInput = document.getElementById('login-username');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
closeAppModal('login-overlay');
|
||||
if (errorBox) {
|
||||
@@ -101,6 +127,9 @@ function hideLoginOverlay() {
|
||||
if (passwordInput) {
|
||||
passwordInput.value = '';
|
||||
}
|
||||
if (usernameInput && !authUser) {
|
||||
usernameInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAuthPromise() {
|
||||
@@ -158,6 +187,13 @@ async function apiFetch(url, options = {}) {
|
||||
: '未授权访问';
|
||||
throw new Error(msg);
|
||||
}
|
||||
if (response.status === 403) {
|
||||
const result = await response.clone().json().catch(() => ({}));
|
||||
const msg = result.error || (typeof window !== 'undefined' && typeof window.t === 'function'
|
||||
? window.t('auth.forbidden')
|
||||
: '权限不足');
|
||||
throw new Error(msg);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -211,6 +247,7 @@ async function apiUploadWithProgress(url, formData, options = {}) {
|
||||
|
||||
async function submitLogin(event) {
|
||||
event.preventDefault();
|
||||
const usernameInput = document.getElementById('login-username');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const submitBtn = document.querySelector('.login-submit');
|
||||
@@ -219,6 +256,7 @@ async function submitLogin(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const username = usernameInput ? usernameInput.value.trim() : '';
|
||||
const password = passwordInput.value.trim();
|
||||
if (!password) {
|
||||
if (errorBox) {
|
||||
@@ -241,7 +279,7 @@ async function submitLogin(event) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ password }),
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !result.token) {
|
||||
@@ -255,8 +293,14 @@ async function submitLogin(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveAuth(result.token, result.expires_at);
|
||||
saveAuth(result.token, result.expires_at, {
|
||||
user: result.user,
|
||||
roles: result.roles,
|
||||
permissions: result.permissions,
|
||||
scope: result.scope,
|
||||
});
|
||||
hideLoginOverlay();
|
||||
applyRBACToUI();
|
||||
resolveAuthPromises(true);
|
||||
if (!isAppInitialized) {
|
||||
await bootstrapApp();
|
||||
@@ -306,9 +350,151 @@ async function bootstrapApp() {
|
||||
initializeChatUI();
|
||||
isAppInitialized = true;
|
||||
}
|
||||
applyRBACToUI();
|
||||
await refreshAppData();
|
||||
}
|
||||
|
||||
const PAGE_PERMISSION_MAP = {
|
||||
dashboard: 'dashboard:read',
|
||||
chat: 'chat:read',
|
||||
hitl: 'hitl:read',
|
||||
'info-collect': 'fofa:execute',
|
||||
tasks: 'tasks:read',
|
||||
workflows: 'workflow:read',
|
||||
projects: 'project:read',
|
||||
vulnerabilities: 'vulnerability:read',
|
||||
'chat-files': 'files:read',
|
||||
webshell: 'webshell:read',
|
||||
c2: 'c2:read',
|
||||
'c2-listeners': 'c2:read',
|
||||
'c2-sessions': 'c2:read',
|
||||
'c2-tasks': 'c2:read',
|
||||
'c2-payloads': 'c2:read',
|
||||
'c2-events': 'c2:read',
|
||||
'c2-profiles': 'c2:read',
|
||||
mcp: 'mcp:read',
|
||||
'mcp-monitor': 'monitor:read',
|
||||
'mcp-management': 'mcp:read',
|
||||
knowledge: 'knowledge:read',
|
||||
'knowledge-retrieval-logs': 'knowledge:read',
|
||||
'knowledge-management': 'knowledge:read',
|
||||
skills: 'skills:read',
|
||||
'skills-monitor': 'skills:read',
|
||||
'skills-management': 'skills:read',
|
||||
agents: 'agents:read',
|
||||
'agents-management': 'agents:read',
|
||||
roles: 'roles:read',
|
||||
'roles-management': 'roles:read',
|
||||
'platform-rbac': 'rbac:read',
|
||||
settings: 'config:read',
|
||||
};
|
||||
|
||||
function hasPermission(permission) {
|
||||
return !permission || authPermissions.has(permission);
|
||||
}
|
||||
|
||||
function applyRBACToUI() {
|
||||
document.querySelectorAll('[data-page]').forEach((el) => {
|
||||
const page = el.getAttribute('data-page');
|
||||
const permission = PAGE_PERMISSION_MAP[page];
|
||||
if (!permission) return;
|
||||
const allowed = hasPermission(permission);
|
||||
el.hidden = !allowed;
|
||||
el.setAttribute('aria-hidden', allowed ? 'false' : 'true');
|
||||
});
|
||||
const userAvatar = document.querySelector('.user-avatar-btn');
|
||||
if (userAvatar && authUser && authUser.username) {
|
||||
const displayName = getAuthDisplayName();
|
||||
userAvatar.setAttribute('title', displayName);
|
||||
userAvatar.setAttribute('aria-label', authT('header.userMenuFor', '用户菜单:{{name}}', { name: displayName }));
|
||||
}
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
|
||||
function authT(key, fallback, opts = {}) {
|
||||
if (typeof window !== 'undefined' && typeof window.t === 'function') {
|
||||
const translated = window.t(key, opts);
|
||||
if (translated && translated !== key) {
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
return fallback.replace(/\{\{\s*(\w+)\s*\}\}/g, function (_, name) {
|
||||
return Object.prototype.hasOwnProperty.call(opts, name) ? String(opts[name]) : '';
|
||||
});
|
||||
}
|
||||
|
||||
function getAuthDisplayName() {
|
||||
if (!authUser) {
|
||||
return authT('header.unknownUser', '未知用户');
|
||||
}
|
||||
return String(authUser.display_name || authUser.displayName || authUser.username || '').trim()
|
||||
|| authT('header.unknownUser', '未知用户');
|
||||
}
|
||||
|
||||
function getAuthUsername() {
|
||||
if (!authUser) {
|
||||
return '-';
|
||||
}
|
||||
const username = String(authUser.username || '').trim();
|
||||
return username ? `@${username}` : '-';
|
||||
}
|
||||
|
||||
function getScopeLabel(scope) {
|
||||
const normalized = String(scope || '').trim().toLowerCase();
|
||||
const keyMap = {
|
||||
all: 'header.scopeAll',
|
||||
assigned: 'header.scopeAssigned',
|
||||
own: 'header.scopeOwn',
|
||||
};
|
||||
const fallbackMap = {
|
||||
all: '全部资源',
|
||||
assigned: '指定资源',
|
||||
own: '自己的资源',
|
||||
};
|
||||
const key = keyMap[normalized] || 'header.scopeUnknown';
|
||||
const fallback = fallbackMap[normalized] || '资源范围未知';
|
||||
return authT(key, fallback);
|
||||
}
|
||||
|
||||
function renderUserMenuProfile() {
|
||||
const displayNameEl = document.getElementById('user-menu-display-name');
|
||||
const usernameEl = document.getElementById('user-menu-username');
|
||||
const scopeEl = document.getElementById('user-menu-scope');
|
||||
const rolesEl = document.getElementById('user-menu-roles');
|
||||
const permissionsEl = document.getElementById('user-menu-permissions');
|
||||
const avatarBtn = document.getElementById('user-avatar-btn') || document.querySelector('.user-avatar-btn');
|
||||
|
||||
const displayName = getAuthDisplayName();
|
||||
const roleCount = Array.isArray(authRoles) ? authRoles.length : 0;
|
||||
const permissionCount = authPermissions instanceof Set ? authPermissions.size : 0;
|
||||
|
||||
if (displayNameEl) displayNameEl.textContent = displayName;
|
||||
if (usernameEl) usernameEl.textContent = getAuthUsername();
|
||||
if (scopeEl) scopeEl.textContent = getScopeLabel(authScope);
|
||||
if (rolesEl) rolesEl.textContent = authT('header.rolesCount', '{{count}} 个角色', { count: roleCount });
|
||||
if (permissionsEl) permissionsEl.textContent = authT('header.permissionsCount', '{{count}} 项权限', { count: permissionCount });
|
||||
if (avatarBtn && authUser) {
|
||||
avatarBtn.setAttribute('title', displayName);
|
||||
avatarBtn.setAttribute('aria-label', authT('header.userMenuFor', '用户菜单:{{name}}', { name: displayName }));
|
||||
} else if (avatarBtn) {
|
||||
avatarBtn.setAttribute('aria-label', authT('header.userMenu', '用户菜单'));
|
||||
}
|
||||
}
|
||||
|
||||
function setUserMenuOpen(open) {
|
||||
const dropdown = document.getElementById('user-menu-dropdown');
|
||||
const avatarBtn = document.getElementById('user-avatar-btn') || document.querySelector('.user-avatar-btn');
|
||||
if (!dropdown) return;
|
||||
dropdown.style.display = open ? 'block' : 'none';
|
||||
if (avatarBtn) {
|
||||
avatarBtn.classList.toggle('active', open);
|
||||
avatarBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
}
|
||||
if (open) {
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
}
|
||||
|
||||
// 通用工具函数
|
||||
function getStatusText(status) {
|
||||
const s = (status && String(status).toLowerCase()) || '';
|
||||
@@ -366,7 +552,15 @@ async function initializeApp() {
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
const result = await response.json().catch(() => ({}));
|
||||
saveAuth(result.token || authToken, result.expires_at || authTokenExpiry, {
|
||||
user: result.user || authUser,
|
||||
roles: result.roles || authRoles,
|
||||
permissions: result.permissions || Array.from(authPermissions),
|
||||
scope: result.scope || authScope,
|
||||
});
|
||||
hideLoginOverlay();
|
||||
applyRBACToUI();
|
||||
resolveAuthPromises(true);
|
||||
await bootstrapApp();
|
||||
return;
|
||||
@@ -386,7 +580,7 @@ function toggleUserMenu() {
|
||||
if (!dropdown) return;
|
||||
|
||||
const isVisible = dropdown.style.display !== 'none';
|
||||
dropdown.style.display = isVisible ? 'none' : 'block';
|
||||
setUserMenuOpen(!isVisible);
|
||||
}
|
||||
|
||||
// 点击页面其他地方时关闭下拉菜单
|
||||
@@ -397,17 +591,18 @@ document.addEventListener('click', function(event) {
|
||||
if (dropdown && avatarBtn &&
|
||||
!dropdown.contains(event.target) &&
|
||||
!avatarBtn.contains(event.target)) {
|
||||
dropdown.style.display = 'none';
|
||||
setUserMenuOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('languagechange', function () {
|
||||
renderUserMenuProfile();
|
||||
});
|
||||
|
||||
// 退出登录
|
||||
async function logout() {
|
||||
// 关闭下拉菜单
|
||||
const dropdown = document.getElementById('user-menu-dropdown');
|
||||
if (dropdown) {
|
||||
dropdown.style.display = 'none';
|
||||
}
|
||||
setUserMenuOpen(false);
|
||||
|
||||
try {
|
||||
// 先尝试调用退出API(如果token有效)
|
||||
@@ -434,5 +629,7 @@ async function logout() {
|
||||
// 导出函数供HTML使用
|
||||
window.toggleUserMenu = toggleUserMenu;
|
||||
window.logout = logout;
|
||||
window.hasPermission = hasPermission;
|
||||
window.applyRBACToUI = applyRBACToUI;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initializeApp);
|
||||
|
||||
+118
-8
@@ -2458,6 +2458,32 @@ function syncProcessDetailButtonLabels(messageId, expanded) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 懒加载占位提示可点击,与工具栏「展开详情」行为一致 */
|
||||
function bindProcessDetailsLazyHint(hostEl, messageId) {
|
||||
if (!hostEl || !messageId) return;
|
||||
const emptyEl = hostEl.classList && hostEl.classList.contains('progress-timeline-empty')
|
||||
? hostEl
|
||||
: hostEl.querySelector('.progress-timeline-empty');
|
||||
if (!emptyEl || emptyEl.dataset.lazyHintBound === '1') return;
|
||||
emptyEl.dataset.lazyHintBound = '1';
|
||||
emptyEl.classList.add('progress-timeline-lazy-clickable');
|
||||
emptyEl.setAttribute('role', 'button');
|
||||
emptyEl.setAttribute('tabindex', '0');
|
||||
const activate = () => {
|
||||
if (typeof toggleProcessDetails === 'function') {
|
||||
toggleProcessDetails(null, messageId);
|
||||
}
|
||||
};
|
||||
emptyEl.addEventListener('click', activate);
|
||||
emptyEl.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
activate();
|
||||
}
|
||||
});
|
||||
}
|
||||
window.bindProcessDetailsLazyHint = bindProcessDetailsLazyHint;
|
||||
|
||||
// 渲染过程详情
|
||||
// options.append=true 时分页追加;options.markLoaded=false 时保留 lazy 标记(分页加载中)
|
||||
function renderProcessDetails(messageId, processDetails, options) {
|
||||
@@ -2465,6 +2491,13 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
const appendMode = !!renderOpts.append;
|
||||
const prependMode = !!renderOpts.prepend;
|
||||
const markLoaded = renderOpts.markLoaded !== false;
|
||||
const toolStatusByProcessDetailId = new Map();
|
||||
if (Array.isArray(renderOpts.toolExecutions)) {
|
||||
renderOpts.toolExecutions.forEach((execution) => {
|
||||
if (!execution || !execution.processDetailId) return;
|
||||
toolStatusByProcessDetailId.set(String(execution.processDetailId), String(execution.status || '').toLowerCase());
|
||||
});
|
||||
}
|
||||
const messageElement = document.getElementById(messageId);
|
||||
if (!messageElement) {
|
||||
return;
|
||||
@@ -2529,6 +2562,7 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||
let lazyHint = expandLabel + '(点击后加载迭代详情)';
|
||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + lazyHint + '</div>';
|
||||
bindProcessDetailsLazyHint(timeline, messageId);
|
||||
timeline.classList.remove('expanded');
|
||||
prefetchProcessDetailsSummaryHint(messageId, messageElement);
|
||||
return;
|
||||
@@ -2725,6 +2759,9 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
processDetailId: detail.id || '',
|
||||
createdAt: detail.createdAt
|
||||
};
|
||||
if (eventType === 'tool_call' && detail.id && toolStatusByProcessDetailId.has(String(detail.id))) {
|
||||
timelineOpts.toolStatus = toolStatusByProcessDetailId.get(String(detail.id));
|
||||
}
|
||||
if (eventType === 'tool_call' && data._mergedResult) {
|
||||
timelineOpts.mergedResult = data._mergedResult;
|
||||
}
|
||||
@@ -2781,6 +2818,7 @@ function finishProcessDetailsRender(messageElement, processDetails, isLazyNotLoa
|
||||
lazyHint.textContent = (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') +
|
||||
'(点击后加载完整过程详情)';
|
||||
timeline.appendChild(lazyHint);
|
||||
bindProcessDetailsLazyHint(lazyHint, messageElement.id);
|
||||
}
|
||||
|
||||
const hasPendingHitlInDetails = processDetails.some(d => d && d.eventType === 'hitl_interrupt');
|
||||
@@ -2841,6 +2879,7 @@ function prefetchProcessDetailsSummaryHint(messageId, messageElement) {
|
||||
const empty = timeline.querySelector('.progress-timeline-empty');
|
||||
if (empty) {
|
||||
empty.textContent = hint;
|
||||
bindProcessDetailsLazyHint(timeline, messageId);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -2999,16 +3038,42 @@ function normalizeToolExecutionSummaryForButton(raw) {
|
||||
};
|
||||
}
|
||||
|
||||
function setPendingToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return;
|
||||
function cacheToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return [];
|
||||
const normalized = summaries
|
||||
.map(normalizeToolExecutionSummaryForButton)
|
||||
.filter((item) => item.toolName || item.executionId || item.toolCallId);
|
||||
if (normalized.length > 0) {
|
||||
messageElement.dataset.toolExecutionSummaries = JSON.stringify(normalized);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function getCachedToolExecutionSummaries(messageElement) {
|
||||
if (!messageElement || !messageElement.dataset || !messageElement.dataset.toolExecutionSummaries) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(messageElement.dataset.toolExecutionSummaries);
|
||||
return Array.isArray(parsed) ? parsed.map(normalizeToolExecutionSummaryForButton) : [];
|
||||
} catch (e) {
|
||||
delete messageElement.dataset.toolExecutionSummaries;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function setPendingToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return;
|
||||
const normalized = cacheToolExecutionSummaries(messageElement, summaries);
|
||||
if (normalized.length > 0) {
|
||||
messageElement.dataset.pendingToolExecutionSummaries = JSON.stringify(normalized);
|
||||
} else {
|
||||
delete messageElement.dataset.pendingToolExecutionSummaries;
|
||||
}
|
||||
const renderedToolList = messageElement.querySelector('.mcp-tool-list');
|
||||
if (normalized.length > 0 && renderedToolList && renderedToolList.querySelector('.mcp-detail-btn[data-exec-id], .mcp-detail-btn[data-tool-summary]')) {
|
||||
appendMcpCallSummaryButtons(messageElement, normalized);
|
||||
delete messageElement.dataset.pendingToolExecutionSummaries;
|
||||
delete messageElement.dataset.pendingMcpExecutionIds;
|
||||
}
|
||||
if (typeof syncMcpToolsToggleButton === 'function') {
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
@@ -3202,6 +3267,34 @@ async function focusToolExecutionInProcessDetails(messageElement, summary, index
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
async function resolveToolExecutionSummaryForFocus(messageElement, executionId, index) {
|
||||
const wantedExecutionId = executionId == null ? '' : String(executionId).trim();
|
||||
let summaries = getCachedToolExecutionSummaries(messageElement);
|
||||
let item = wantedExecutionId
|
||||
? summaries.find((summary) => summary.executionId === wantedExecutionId)
|
||||
: summaries[index];
|
||||
if (item && (item.processDetailId || item.toolCallId)) return item;
|
||||
|
||||
const backendId = messageElement && messageElement.dataset
|
||||
? String(messageElement.dataset.backendMessageId || '').trim()
|
||||
: '';
|
||||
if (!backendId || typeof apiFetch !== 'function') return item || null;
|
||||
try {
|
||||
const res = await apiFetch('/api/messages/' + encodeURIComponent(backendId) + '/process-details?summary=1');
|
||||
const payload = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !payload.summary || !Array.isArray(payload.summary.toolExecutions)) {
|
||||
return item || null;
|
||||
}
|
||||
summaries = cacheToolExecutionSummaries(messageElement, payload.summary.toolExecutions);
|
||||
item = wantedExecutionId
|
||||
? summaries.find((summary) => summary.executionId === wantedExecutionId)
|
||||
: summaries[index];
|
||||
return item || null;
|
||||
} catch (e) {
|
||||
return item || null;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMcpToolList(assistantMessageId) {
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
if (!messageEl) return;
|
||||
@@ -3255,7 +3348,14 @@ function appendMcpCallButtons(messageElement, executionIds) {
|
||||
detailBtn.dataset.execId = execId;
|
||||
detailBtn.dataset.execIndex = String(index + 1);
|
||||
detailBtn.innerHTML = '<span>' + (typeof window.t === 'function' ? window.t('chat.callNumber', { n: index + 1 }) : '调用 #' + (index + 1)) + '</span>';
|
||||
detailBtn.onclick = () => showMCPDetail(execId);
|
||||
detailBtn.onclick = async () => {
|
||||
const summary = await resolveToolExecutionSummaryForFocus(messageElement, execId, index);
|
||||
if (summary && (summary.processDetailId || summary.toolCallId)) {
|
||||
await focusToolExecutionInProcessDetails(messageElement, summary, index);
|
||||
return;
|
||||
}
|
||||
showMCPDetail(execId);
|
||||
};
|
||||
toolList.appendChild(detailBtn);
|
||||
});
|
||||
batchUpdateButtonToolNames(toolList, executionIds);
|
||||
@@ -3273,10 +3373,16 @@ function appendMcpCallSummaryButtons(messageElement, summaries) {
|
||||
const item = normalizeToolExecutionSummaryForButton(raw);
|
||||
const key = item.executionId || item.toolCallId || `${item.toolName || 'tool'}-${index + 1}`;
|
||||
const selector = '.mcp-detail-btn[data-tool-summary="' + CSS.escape(String(key)) + '"]';
|
||||
if (toolList.querySelector(selector) || (item.executionId && toolList.querySelector('.mcp-detail-btn[data-exec-id="' + CSS.escape(String(item.executionId)) + '"]'))) {
|
||||
const existingSummaryBtn = toolList.querySelector(selector);
|
||||
const existingExecBtn = item.executionId
|
||||
? toolList.querySelector('.mcp-detail-btn[data-exec-id="' + CSS.escape(String(item.executionId)) + '"]')
|
||||
: null;
|
||||
if (existingSummaryBtn) {
|
||||
return;
|
||||
}
|
||||
const btn = document.createElement('button');
|
||||
// 历史会话可能先按 executionId 渲染旧按钮,随后摘要才异步到达。
|
||||
// 复用并升级该按钮,避免它永久保留“只看执行详情、不定位上下文”的旧处理器。
|
||||
const btn = existingExecBtn || document.createElement('button');
|
||||
btn.className = 'mcp-detail-btn';
|
||||
btn.dataset.toolSummary = key;
|
||||
if (item.executionId) {
|
||||
@@ -3294,7 +3400,9 @@ function appendMcpCallSummaryButtons(messageElement, summaries) {
|
||||
};
|
||||
}
|
||||
renderToolExecutionButtonContent(btn, item.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具'), String(index + 1), item.status);
|
||||
toolList.appendChild(btn);
|
||||
if (!existingExecBtn) {
|
||||
toolList.appendChild(btn);
|
||||
}
|
||||
});
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
@@ -3363,7 +3471,8 @@ function getToolExecutionStatusLabel(status) {
|
||||
failed: 'mcpMonitor.statusFailed',
|
||||
running: 'mcpMonitor.statusRunning',
|
||||
cancelled: 'mcpMonitor.statusCancelled',
|
||||
pending: 'mcpMonitor.statusPending'
|
||||
pending: 'mcpMonitor.statusPending',
|
||||
result_missing: 'timeline.resultMissing'
|
||||
};
|
||||
const key = keyMap[normalized];
|
||||
if (key) {
|
||||
@@ -3376,7 +3485,8 @@ function getToolExecutionStatusLabel(status) {
|
||||
failed: '失败',
|
||||
running: '运行中',
|
||||
cancelled: '已取消',
|
||||
pending: '等待中'
|
||||
pending: '等待中',
|
||||
result_missing: '结果记录缺失'
|
||||
};
|
||||
return fallback[normalized] || '';
|
||||
}
|
||||
|
||||
+109
-41
@@ -1357,6 +1357,10 @@ function integrateProgressToMCPSection(progressId, assistantMessageId, mcpExecut
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof window.bindProcessDetailsLazyHint === 'function') {
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) window.bindProcessDetailsLazyHint(tl, assistantMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||
@@ -1492,11 +1496,13 @@ async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId,
|
||||
throw new Error((j && j.error) ? j.error : String(res.status));
|
||||
}
|
||||
const details = (j && Array.isArray(j.processDetails)) ? j.processDetails : [];
|
||||
const toolExecutions = (j && Array.isArray(j.toolExecutions)) ? j.toolExecutions : [];
|
||||
const hasMore = !!(j && j.hasMore);
|
||||
renderProcessDetails(assistantMessageId, details, {
|
||||
append: !isFirst || opts.append,
|
||||
prepend: prepend,
|
||||
markLoaded: autoLoadAll ? !hasMore : true
|
||||
markLoaded: autoLoadAll ? !hasMore : true,
|
||||
toolExecutions: toolExecutions
|
||||
});
|
||||
const responseOffset = j && typeof j.offset === 'number' ? j.offset : offset;
|
||||
const total = j && typeof j.total === 'number' ? j.total : responseOffset + details.length;
|
||||
@@ -1525,6 +1531,81 @@ async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId,
|
||||
|
||||
window.loadProcessDetailsPaginated = loadProcessDetailsPaginated;
|
||||
|
||||
function resolveEventBackendMessageId(eventData) {
|
||||
if (!eventData || typeof eventData !== 'object') return '';
|
||||
const raw = eventData.messageId != null ? eventData.messageId : eventData.assistantMessageId;
|
||||
return raw != null ? String(raw).trim() : '';
|
||||
}
|
||||
|
||||
function triggerLazyProcessDetailsLoad(assistantMessageId, backendMessageId, detailsContainer) {
|
||||
if (!assistantMessageId || !backendMessageId || !detailsContainer) return false;
|
||||
if (detailsContainer.dataset.loading === '1') return false;
|
||||
const collapseT = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情';
|
||||
detailsContainer.dataset.loading = '1';
|
||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||
if (timeline) {
|
||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('common.loading') : '加载中…') + '</div>';
|
||||
}
|
||||
loadProcessDetailsPaginated(assistantMessageId, backendMessageId, { autoLoadAll: false })
|
||||
.catch((e) => {
|
||||
console.error('加载过程详情失败:', e);
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('chat.noProcessDetail') : '暂无过程详情(加载失败)') + '</div>';
|
||||
if (typeof window.bindProcessDetailsLazyHint === 'function') {
|
||||
window.bindProcessDetailsLazyHint(tl, assistantMessageId);
|
||||
}
|
||||
}
|
||||
detailsContainer.dataset.lazyNotLoaded = '1';
|
||||
detailsContainer.dataset.loaded = '0';
|
||||
})
|
||||
.finally(() => {
|
||||
detailsContainer.dataset.loading = '0';
|
||||
if (detailsContainer.dataset.userExpanded === '1') {
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.classList.add('expanded');
|
||||
}
|
||||
if (typeof syncProcessDetailButtonLabels === 'function') {
|
||||
syncProcessDetailButtonLabels(assistantMessageId, true);
|
||||
} else {
|
||||
document.querySelectorAll('#' + assistantMessageId + ' .process-detail-btn').forEach((btn) => {
|
||||
btn.innerHTML = '<span>' + collapseT + '</span>';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function maybeReloadLazyProcessDetails(assistantMessageId) {
|
||||
const detailsContainer = document.getElementById('process-details-' + assistantMessageId);
|
||||
if (!detailsContainer) return;
|
||||
const isLazy = detailsContainer.dataset.lazyNotLoaded === '1' && detailsContainer.dataset.loaded !== '1';
|
||||
if (!isLazy) return;
|
||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||
const wantsExpanded = detailsContainer.dataset.userExpanded === '1' ||
|
||||
!!(timeline && timeline.classList.contains('expanded'));
|
||||
if (!wantsExpanded) return;
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
const backendId = messageEl && messageEl.dataset ? String(messageEl.dataset.backendMessageId || '').trim() : '';
|
||||
if (!backendId) return;
|
||||
triggerLazyProcessDetailsLoad(assistantMessageId, backendId, detailsContainer);
|
||||
}
|
||||
|
||||
function scheduleProcessDetailsLoadWhenReady(assistantMessageId, detailsContainer, attempt) {
|
||||
if (!assistantMessageId || !detailsContainer) return;
|
||||
const tries = typeof attempt === 'number' ? attempt : 0;
|
||||
if (tries > 25) return;
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
const backendId = messageEl && messageEl.dataset ? String(messageEl.dataset.backendMessageId || '').trim() : '';
|
||||
if (backendId) {
|
||||
triggerLazyProcessDetailsLoad(assistantMessageId, backendId, detailsContainer);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => scheduleProcessDetailsLoadWhenReady(assistantMessageId, detailsContainer, tries + 1), 200);
|
||||
}
|
||||
|
||||
// 切换过程详情显示
|
||||
function toggleProcessDetails(progressId, assistantMessageId) {
|
||||
const detailsId = 'process-details-' + assistantMessageId;
|
||||
@@ -1539,42 +1620,10 @@ function toggleProcessDetails(progressId, assistantMessageId) {
|
||||
if (maybeLazy) {
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
const backendMessageId = messageEl && messageEl.dataset ? messageEl.dataset.backendMessageId : '';
|
||||
if (backendMessageId && typeof apiFetch === 'function' && typeof renderProcessDetails === 'function') {
|
||||
if (detailsContainer.dataset.loading === '1') {
|
||||
// 正在加载中,避免重复请求
|
||||
} else {
|
||||
detailsContainer.dataset.loading = '1';
|
||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||
if (timeline) {
|
||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('common.loading') : '加载中…') + '</div>';
|
||||
}
|
||||
loadProcessDetailsPaginated(assistantMessageId, backendMessageId, { autoLoadAll: false })
|
||||
.catch((e) => {
|
||||
console.error('加载过程详情失败:', e);
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('chat.noProcessDetail') : '暂无过程详情(加载失败)') + '</div>';
|
||||
}
|
||||
detailsContainer.dataset.lazyNotLoaded = '1';
|
||||
detailsContainer.dataset.loaded = '0';
|
||||
})
|
||||
.finally(() => {
|
||||
detailsContainer.dataset.loading = '0';
|
||||
if (detailsContainer.dataset.userExpanded === '1') {
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.classList.add('expanded');
|
||||
}
|
||||
if (typeof syncProcessDetailButtonLabels === 'function') {
|
||||
syncProcessDetailButtonLabels(assistantMessageId, true);
|
||||
} else {
|
||||
document.querySelectorAll('#' + assistantMessageId + ' .process-detail-btn').forEach((btn) => {
|
||||
btn.innerHTML = '<span>' + collapseT + '</span>';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (backendMessageId) {
|
||||
triggerLazyProcessDetailsLoad(assistantMessageId, backendMessageId, detailsContainer);
|
||||
} else {
|
||||
scheduleProcessDetailsLoadWhenReady(assistantMessageId, detailsContainer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1647,6 +1696,7 @@ function applyBackendMessageIdToAssistantDom(domAssistantId, backendMessageId) {
|
||||
if (typeof attachDeleteTurnButton === 'function') {
|
||||
attachDeleteTurnButton(el);
|
||||
}
|
||||
maybeReloadLazyProcessDetails(domAssistantId);
|
||||
}
|
||||
|
||||
/** 将后端用户消息 ID 绑定到最后一条尚未绑定 backendMessageId 的用户气泡 */
|
||||
@@ -2480,7 +2530,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
|
||||
// 复用已有助手消息(若有),避免终态事件重复插入消息
|
||||
{
|
||||
const preferredMessageId = event.data && event.data.messageId ? event.data.messageId : null;
|
||||
const preferredMessageId = resolveEventBackendMessageId(event.data) || null;
|
||||
const { assistantId, assistantElement } = upsertTerminalAssistantMessage(event.message, preferredMessageId);
|
||||
if (assistantId && preferredMessageId) {
|
||||
applyBackendMessageIdToAssistantDom(assistantId, preferredMessageId);
|
||||
@@ -2489,6 +2539,8 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
const detailsId = 'process-details-' + assistantId;
|
||||
if (!document.getElementById(detailsId)) {
|
||||
integrateProgressToMCPSection(progressId, assistantId, typeof getMcpIds === 'function' ? (getMcpIds() || []) : []);
|
||||
} else if (preferredMessageId) {
|
||||
maybeReloadLazyProcessDetails(assistantId);
|
||||
}
|
||||
setTimeout(() => {
|
||||
collapseAllProgressDetails(assistantId, progressId);
|
||||
@@ -2501,7 +2553,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
// Close any remaining running tool calls for this progress.
|
||||
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
|
||||
break;
|
||||
|
||||
|
||||
case 'response_start': {
|
||||
const responseTaskState = progressTaskState.get(progressId);
|
||||
const responseOriginalConversationId = responseTaskState?.conversationId;
|
||||
@@ -2721,7 +2773,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
|
||||
// 复用已有助手消息(若有),避免终态事件重复插入消息
|
||||
{
|
||||
const preferredMessageId = event.data && event.data.messageId ? event.data.messageId : null;
|
||||
const preferredMessageId = resolveEventBackendMessageId(event.data) || null;
|
||||
const { assistantId, assistantElement } = upsertTerminalAssistantMessage(event.message, preferredMessageId);
|
||||
if (assistantId && preferredMessageId) {
|
||||
applyBackendMessageIdToAssistantDom(assistantId, preferredMessageId);
|
||||
@@ -2730,6 +2782,8 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
const detailsId = 'process-details-' + assistantId;
|
||||
if (!document.getElementById(detailsId)) {
|
||||
integrateProgressToMCPSection(progressId, assistantId, typeof getMcpIds === 'function' ? (getMcpIds() || []) : []);
|
||||
} else if (preferredMessageId) {
|
||||
maybeReloadLazyProcessDetails(assistantId);
|
||||
}
|
||||
setTimeout(() => {
|
||||
collapseAllProgressDetails(assistantId, progressId);
|
||||
@@ -3961,6 +4015,7 @@ function addTimelineItem(timeline, type, options) {
|
||||
item.dataset.toolCallId = String(d.toolCallId).trim();
|
||||
}
|
||||
const merged = options.mergedResult || d._mergedResult;
|
||||
const terminalStatus = String(options.toolStatus || '').toLowerCase();
|
||||
if (merged) {
|
||||
item.dataset.toolResultMerged = '1';
|
||||
item.dataset.toolSuccess = merged.success !== false ? '1' : '0';
|
||||
@@ -3968,6 +4023,12 @@ function addTimelineItem(timeline, type, options) {
|
||||
if (d._mergedResultDetailId) {
|
||||
item.dataset.toolResultDetailId = String(d._mergedResultDetailId);
|
||||
}
|
||||
} else if (terminalStatus === 'completed' || terminalStatus === 'failed') {
|
||||
item.dataset.toolSuccess = terminalStatus === 'completed' ? '1' : '0';
|
||||
item.classList.add(terminalStatus === 'completed' ? 'tool-call-completed' : 'tool-call-failed');
|
||||
} else if (terminalStatus === 'result_missing') {
|
||||
item.classList.add('tool-call-incomplete');
|
||||
item.title = typeof window.t === 'function' ? window.t('timeline.resultMissing') : '结果记录缺失';
|
||||
}
|
||||
}
|
||||
if (type === 'hitl_interrupt' && options.data && options.data.interruptId != null && String(options.data.interruptId).trim() !== '') {
|
||||
@@ -4041,19 +4102,26 @@ function addTimelineItem(timeline, type, options) {
|
||||
const data = options.data;
|
||||
const args = parseToolCallArgsFromData(data);
|
||||
const merged = options.mergedResult || data._mergedResult;
|
||||
const terminalStatus = String(options.toolStatus || '').toLowerCase();
|
||||
const hasTerminalStatus = terminalStatus === 'completed' || terminalStatus === 'failed';
|
||||
const hasHistoricalStatus = hasTerminalStatus || terminalStatus === 'result_missing';
|
||||
if (merged) {
|
||||
if (merged.success !== false) {
|
||||
item.classList.add('tool-call-completed');
|
||||
} else {
|
||||
item.classList.add('tool-call-failed');
|
||||
}
|
||||
} else if (hasTerminalStatus) {
|
||||
item.classList.add(terminalStatus === 'completed' ? 'tool-call-completed' : 'tool-call-failed');
|
||||
} else if (terminalStatus === 'result_missing') {
|
||||
item.classList.add('tool-call-incomplete');
|
||||
} else if (!options.skipPendingResult) {
|
||||
item.classList.add('tool-call-running');
|
||||
}
|
||||
setToolCallDetailState(item, {
|
||||
args: args,
|
||||
resultData: merged || null,
|
||||
pending: !merged && !options.skipPendingResult,
|
||||
pending: !merged && !hasHistoricalStatus && !options.skipPendingResult,
|
||||
processDetailId: options.processDetailId || '',
|
||||
resultDetailId: data._mergedResultDetailId || '',
|
||||
payloadDeferred: data._payloadDeferred === true || (merged && merged._payloadDeferred === true),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,7 +81,7 @@ function initRouter() {
|
||||
const hashParts = hash.split('?');
|
||||
let pageId = hashParts[0];
|
||||
if (pageId === 'c2') pageId = 'c2-listeners';
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'workflows', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'platform-rbac', 'workflows', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
switchPage(pageId);
|
||||
if (pageId === 'chat') {
|
||||
scheduleChatConversationFromHash(500);
|
||||
@@ -472,6 +472,11 @@ async function initPage(pageId) {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'platform-rbac':
|
||||
if (typeof initPlatformRbacPage === 'function') {
|
||||
initPlatformRbacPage();
|
||||
}
|
||||
break;
|
||||
case 'workflows':
|
||||
if (typeof refreshWorkflows === 'function') {
|
||||
refreshWorkflows();
|
||||
@@ -538,7 +543,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
let pageId = hashParts[0];
|
||||
|
||||
if (pageId === 'c2') pageId = 'c2-listeners';
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'tasks', 'workflows', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'tasks', 'workflows', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'platform-rbac', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
switchPage(pageId);
|
||||
if (pageId === 'chat') {
|
||||
scheduleChatConversationFromHash(200);
|
||||
|
||||
@@ -509,6 +509,13 @@ function syncC2NavFromConfig(cfg) {
|
||||
|
||||
// 切换设置分类
|
||||
function switchSettingsSection(section) {
|
||||
if (section === 'rbac') {
|
||||
if (typeof switchPage === 'function') {
|
||||
switchPage('platform-rbac');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新导航项状态
|
||||
document.querySelectorAll('.settings-nav-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
@@ -3660,3 +3667,6 @@ document.addEventListener('languagechange', function () {
|
||||
console.warn('languagechange MCP refresh failed', e);
|
||||
}
|
||||
});
|
||||
|
||||
window.initSettingsCustomSelects = initSettingsCustomSelects;
|
||||
window.refreshSettingsCustomSelects = refreshSettingsCustomSelects;
|
||||
|
||||
@@ -1048,6 +1048,7 @@ function probeWebshellConnection(conn) {
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
connection_id: conn.id || '',
|
||||
command: buildWebshellProbeCommand(probeToken)
|
||||
})
|
||||
})
|
||||
@@ -3854,6 +3855,7 @@ function execWebshellCommand(conn, command) {
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
connection_id: conn.id || '',
|
||||
command: command
|
||||
})
|
||||
}).then(function (r) { return r.json(); })
|
||||
|
||||
Reference in New Issue
Block a user