// C2 模块前端逻辑 - 完整实现 // 支持: xterm 终端、文件管理、监听器/会话/任务/事件/Payload/Profile 管理 (function() { 'use strict'; // C2 模块命名空间 const C2 = { currentPage: '', listeners: [], sessions: [], tasks: [], tasksPage: 1, tasksPageSize: 10, tasksTotal: 0, tasksPendingQueuedCount: null, events: [], eventsPage: 1, eventsPageSize: 10, eventsTotal: 0, profiles: [], selectedSessionId: null, selectedListenerId: null, eventSource: null, // xterm 相关 terminalInstance: null, terminalFitAddon: null, terminalResizeObserver: null, terminalContainer: null, terminalSessionId: 'main', // 文件管理 currentPath: '/', fileList: [], // 任务轮询 taskPollInterval: null, }; // API 基础路径 const API_BASE = '/api/c2'; window.__c2DownloadPayload = function(filename) { const url = `${API_BASE}/payloads/${filename}/download`; const fetchFn = (typeof apiFetch === 'function') ? apiFetch : fetch; fetchFn(url).then(resp => { if (!resp.ok) throw new Error('download failed: ' + resp.status); return resp.blob(); }).then(blob => { const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href); }).catch(err => { if (window.showToast) window.showToast(err.message, 'error'); }); }; function c2t(key, opts) { try { if (typeof window.t === 'function') return window.t(key, opts || {}); } catch (e) {} return key; } function listenerTypeLabel(type) { if (!type) return ''; const k = 'c2.listeners.typeLabels.' + String(type).toLowerCase(); const tr = c2t(k); if (tr !== k) return tr; return String(type).replace(/_/g, ' '); } function sessionStatusLabel(status) { const s = String(status || '').toLowerCase(); if (!s) return ''; const k = 'c2.sessions.' + s; const tr = c2t(k); if (tr !== k) return tr; return status; } function taskStatusLabel(status) { const s = String(status || '').toLowerCase(); if (!s) return ''; const k = 'c2.tasks.' + s; const tr = c2t(k); if (tr !== k) return tr; return status; } // ============================================================================ // 工具函数 // ============================================================================ function apiRequest(method, url, data) { const options = { method: method, headers: { 'Content-Type': 'application/json' } }; if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE')) { options.body = JSON.stringify(data); } if (typeof apiFetch === 'function') { return apiFetch(url, options).then(r => r.json()); } return fetch(url, options).then(r => r.json()); } function showToast(message, type = 'info') { if (window.showToast) { window.showToast(message, type); return; } const container = document.getElementById('c2-toast-container') || (() => { const div = document.createElement('div'); div.id = 'c2-toast-container'; div.style.cssText = 'position:fixed;top:20px;right:20px;z-index:10000;display:flex;flex-direction:column;gap:8px;'; document.body.appendChild(div); return div; })(); const toast = document.createElement('div'); const colors = { error: '#e53e3e', success: '#38a169', info: '#3182ce', warn: '#d69e2e' }; toast.style.cssText = `background:${colors[type] || colors.info};color:#fff;padding:10px 18px;border-radius:6px;font-size:0.875rem;box-shadow:0 4px 12px rgba(0,0,0,0.2);opacity:0;transition:opacity .3s;max-width:400px;word-break:break-word;`; toast.textContent = message; container.appendChild(toast); requestAnimationFrame(() => { toast.style.opacity = '1'; }); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 3500); } function formatTime(dateStr) { if (!dateStr) return '-'; return new Date(dateStr).toLocaleString(); } function formatDuration(ms) { if (!ms || ms <= 0) return '-'; if (ms < 1000) return c2t('c2.fmt.durationMs', { n: ms }); if (ms < 60000) return c2t('c2.fmt.durationSec', { n: (ms / 1000).toFixed(1) }); return c2t('c2.fmt.durationMin', { n: (ms / 60000).toFixed(1) }); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** 监听器表单:Malleable Profile 下拉选项 HTML(value / 文本已转义) */ function listenerProfileSelectHtml(selectedProfileId) { const sel = selectedProfileId ? String(selectedProfileId) : ''; let opts = ``; for (const p of (C2.profiles || [])) { if (!p) continue; const pid = p.id || p.ID; if (!pid) continue; const idEsc = escapeHtml(String(pid)); const nameEsc = escapeHtml(p.name || pid); const selected = sel && String(pid) === sel ? ' selected' : ''; opts += ``; } return opts; } function listenerResolvedProfileId(l) { if (!l) return ''; const v = l.profileId != null && l.profileId !== '' ? l.profileId : l.profile_id; return v != null ? String(v).trim() : ''; } /** 监听器卡片展示用 Profile 名称(依赖 C2.profiles,由 loadListeners 一并拉取) */ function listenerProfileDisplayName(l) { const pid = listenerResolvedProfileId(l); if (!pid) return ''; const list = C2.profiles || []; for (let i = 0; i < list.length; i++) { const p = list[i]; if (p && (p.id === pid || p.ID === pid)) return String(p.name || p.id || pid).trim() || pid; } return pid.length > 18 ? pid.substring(0, 16) + '…' : pid; } function listenerTypeVisualClass(type) { const t = String(type || '').toLowerCase(); if (t === 'https_beacon') return 'c2-ltype-mark--https'; if (t === 'http_beacon') return 'c2-ltype-mark--http'; if (t === 'tcp_reverse') return 'c2-ltype-mark--tcp'; if (t === 'websocket') return 'c2-ltype-mark--ws'; return 'c2-ltype-mark--def'; } function listenerTypeShortLabel(type) { const t = String(type || '').toLowerCase(); if (t === 'https_beacon') return 'HTTPS'; if (t === 'http_beacon') return 'HTTP'; if (t === 'tcp_reverse') return 'TCP'; if (t === 'websocket') return 'WS'; return '?'; } function listenerCardStatusPillLabel(status) { const s = String(status || '').toLowerCase(); if (s === 'running') return c2t('c2.listeners.running'); if (s === 'stopped') return c2t('c2.listeners.stopped'); if (s === 'error') return c2t('c2.listeners.statusError'); return c2t('c2.listeners.stopped'); } /** 避免 i18n 插值把日期里的「/」转成 /,与 formatTime 拼接后整体转义 */ function formatListenerStartedHtml(dateStr) { if (!dateStr) return ''; const prefix = c2t('c2.listeners.startedAtPrefix'); const time = formatTime(dateStr); return '
' + escapeHtml(prefix) + ' ' + escapeHtml(time) + '
'; } function copyToClipboard(text) { if (navigator.clipboard) { navigator.clipboard.writeText(text).then(() => showToast(c2t('c2.clipboardCopied'), 'success')); } else { const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); showToast(c2t('c2.clipboardCopied'), 'success'); } } // ============================================================================ // 页面初始化 // ============================================================================ C2.init = function() { const pageId = window.currentPageId || ''; if (pageId.startsWith('c2')) { C2.connectEventStream(); } switch(pageId) { case 'c2': case 'c2-listeners': C2.loadListeners(); break; case 'c2-sessions': C2.loadSessions(); break; case 'c2-tasks': C2.loadTasks(); break; case 'c2-payloads': C2.loadListenersForPayload(); break; case 'c2-events': C2.loadEvents(); break; case 'c2-profiles': C2.loadProfiles(); break; } }; // ============================================================================ // 监听器管理 // ============================================================================ C2.loadListeners = function() { Promise.all([ apiRequest('GET', `${API_BASE}/listeners`), apiRequest('GET', `${API_BASE}/profiles`).catch(function() { return {}; }) ]).then(function(results) { var ldata = results[0]; var pdata = results[1]; C2.listeners = (ldata && ldata.listeners) || []; if (pdata && pdata.profiles && !pdata.error) { C2.profiles = pdata.profiles; } C2.renderListeners(); C2.updateDashboardStats(); }); }; /** 拉取 Profile 列表(监听器表单用);失败时置空列表不阻断弹窗 */ C2.ensureProfilesLoaded = function() { return apiRequest('GET', `${API_BASE}/profiles`).then(data => { if (data && data.error) { C2.profiles = []; return C2.profiles; } C2.profiles = (data && data.profiles) || []; return C2.profiles; }); }; C2.renderListeners = function() { const container = document.getElementById('c2-listener-grid'); if (!container) return; if (C2.listeners.length === 0) { container.innerHTML = `

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

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

`; return; } container.innerHTML = C2.listeners.map(function(l) { const st = String(l.status || 'stopped').toLowerCase(); const stUi = st === 'running' || st === 'stopped' || st === 'error' ? st : 'stopped'; const profilePid = listenerResolvedProfileId(l); const profileName = listenerProfileDisplayName(l); const profileBadge = profilePid ? '
' + escapeHtml(profileName) + '
' : ''; const cb = C2.getListenerCallbackHost(l); const cbRow = cb ? '
' + escapeHtml(c2t('c2.listeners.callbackShort')) + '' + escapeHtml(cb) + '
' : ''; const remarkRow = l.remark ? '
' + escapeHtml(l.remark) + '
' : ''; const startedHtml = formatListenerStartedHtml(l.startedAt); const pillLabel = escapeHtml(listenerCardStatusPillLabel(st)); const typeMark = escapeHtml(listenerTypeShortLabel(l.type)); const typeVis = listenerTypeVisualClass(l.type); const fullType = escapeHtml(listenerTypeLabel(l.type)); const bindVal = escapeHtml(String(l.bindHost)) + ':' + escapeHtml(String(l.bindPort)); return `
${typeMark}

${escapeHtml(l.name)}

${pillLabel}
${escapeHtml(l.id)}
${escapeHtml(c2t('c2.listeners.bindEndpoint'))} ${bindVal}
${cbRow} ${profileBadge} ${remarkRow} ${startedHtml}
${l.status === 'stopped' ? `` : `` }
`; }).join(''); }; C2.getListenerCallbackHost = function(l) { if (!l) return ''; try { var raw = l.configJson != null ? l.configJson : '{}'; var j = typeof raw === 'string' ? JSON.parse(raw || '{}') : (raw || {}); return String(j.callback_host || '').trim(); } catch (e) { return ''; } }; C2.showCreateListenerModal = function() { const modal = document.getElementById('c2-modal'); const content = document.getElementById('c2-modal-content'); if (!content || !modal) return; modal.style.display = 'flex'; content.innerHTML = `

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

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

`; C2.ensureProfilesLoaded().then(() => { const profileOpts = listenerProfileSelectHtml(''); const emptyProfHintCreate = (C2.profiles && C2.profiles.length > 0) ? '' : `
${escapeHtml(c2t('c2.listeners.malleableProfileEmptyListHint'))}
`; content.innerHTML = `

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

${escapeHtml(c2t('c2.listeners.bindHintExternal'))}
${emptyProfHintCreate}
${escapeHtml(c2t('c2.listeners.malleableProfileHint'))}
${escapeHtml(c2t('c2.listeners.callbackHostHint'))}
`; C2.syncListenerProfileRowForType(); }).catch(() => { showToast(c2t('c2.listeners.toastProfilesLoadFailed'), 'error'); C2.closeModal(); }); }; /** 非 HTTP/HTTPS Beacon 时隐藏 Profile 行(避免误以为 TCP 等也会用) */ C2.syncListenerProfileRowForType = function() { const typeEl = document.getElementById('c2-listener-type'); const row = document.getElementById('c2-listener-profile-group'); if (!typeEl || !row) return; const t = String(typeEl.value || '').toLowerCase(); const show = t === 'http_beacon' || t === 'https_beacon'; row.style.display = show ? '' : 'none'; if (!show) { const sel = document.getElementById('c2-listener-profile-id'); if (sel) sel.value = ''; } }; C2.createListener = function() { const name = document.getElementById('c2-listener-name')?.value.trim(); const type = document.getElementById('c2-listener-type')?.value; const bindHost = document.getElementById('c2-listener-host')?.value || '127.0.0.1'; const bindPort = parseInt(document.getElementById('c2-listener-port')?.value); const callbackHost = document.getElementById('c2-listener-callback-host')?.value?.trim() || ''; const remark = document.getElementById('c2-listener-remark')?.value; if (!name || !type || !bindPort) { showToast(c2t('c2.listeners.toastFillRequired'), 'error'); return; } const profileId = (document.getElementById('c2-listener-profile-id')?.value || '').trim(); apiRequest('POST', `${API_BASE}/listeners`, { name, type, bind_host: bindHost, bind_port: bindPort, remark, callback_host: callbackHost, profile_id: profileId }).then(data => { if (data.error) { showToast(data.error, 'error'); } else { showToast(c2t('c2.listeners.toastCreated'), 'success'); C2.closeModal(); C2.loadListeners(); } }); }; C2.startListener = function(id) { apiRequest('POST', `${API_BASE}/listeners/${id}/start`, {}).then(data => { if (data.error) showToast(data.error, 'error'); else { showToast(c2t('c2.listeners.toastStarted'), 'success'); C2.loadListeners(); } }); }; C2.stopListener = function(id) { apiRequest('POST', `${API_BASE}/listeners/${id}/stop`, {}).then(data => { if (data.error) showToast(data.error, 'error'); else { showToast(c2t('c2.listeners.toastStopped'), 'success'); C2.loadListeners(); } }); }; C2.deleteListener = function(id) { if (!confirm(c2t('c2.listeners.confirmDelete'))) return; apiRequest('DELETE', `${API_BASE}/listeners/${id}`, {}).then(data => { showToast(c2t('c2.listeners.toastDeleted'), 'success'); C2.loadListeners(); }); }; C2.editListener = function(id) { const l = C2.listeners.find(x => x.id === id); if (!l) return; const cbHost = C2.getListenerCallbackHost(l); const modal = document.getElementById('c2-modal'); const content = document.getElementById('c2-modal-content'); if (!content || !modal) return; modal.style.display = 'flex'; content.innerHTML = `

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

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

`; C2.ensureProfilesLoaded().then(() => { const resolvedPid = listenerResolvedProfileId(l); const profileOpts = listenerProfileSelectHtml(resolvedPid); const lt = String(l.type || '').toLowerCase(); const httpHint = (lt === 'http_beacon' || lt === 'https_beacon') ? '' : `
${escapeHtml(c2t('c2.listeners.malleableProfileNonHttpHint'))}
`; const emptyProfHint = (C2.profiles && C2.profiles.length > 0) ? '' : `
${escapeHtml(c2t('c2.listeners.malleableProfileEmptyListHint'))}
`; content.innerHTML = `

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

${httpHint}${emptyProfHint}
${escapeHtml(c2t('c2.listeners.malleableProfileHint'))}
${escapeHtml(c2t('c2.listeners.callbackHostHint'))}
`; }).catch(() => { showToast(c2t('c2.listeners.toastProfilesLoadFailed'), 'error'); C2.closeModal(); }); }; C2.saveListener = function(id) { const name = document.getElementById('c2-listener-name')?.value.trim(); const bindHost = document.getElementById('c2-listener-host')?.value; const bindPort = parseInt(document.getElementById('c2-listener-port')?.value); const callbackHost = document.getElementById('c2-listener-callback-host')?.value?.trim() ?? ''; const remark = document.getElementById('c2-listener-remark')?.value; const profileEl = document.getElementById('c2-listener-profile-id'); const profileId = profileEl ? String(profileEl.value || '').trim() : ''; apiRequest('PUT', `${API_BASE}/listeners/${id}`, { name, bind_host: bindHost, bind_port: bindPort, remark, callback_host: callbackHost, profile_id: profileId }).then(data => { if (data.error) showToast(data.error, 'error'); else { showToast(c2t('c2.listeners.toastUpdated'), 'success'); C2.closeModal(); C2.loadListeners(); } }); }; // ============================================================================ // 会话管理 // ============================================================================ C2.loadSessions = function() { return apiRequest('GET', `${API_BASE}/sessions`).then(data => { C2.sessions = data.sessions || []; C2.renderSessions(); C2.updateDashboardStats(); }); }; C2.renderSessions = function() { const list = document.getElementById('c2-session-list'); const main = document.getElementById('c2-session-main'); if (!list) return; if (C2.sessions.length === 0) { list.innerHTML = `

${escapeHtml(c2t('c2.sessions.emptyTitle'))}

${escapeHtml(c2t('c2.sessions.emptyHint'))}

`; if (main) main.innerHTML = ''; return; } list.innerHTML = C2.sessions.map(s => `
${escapeHtml(s.hostname || c2t('c2.sessions.unknownHost'))} ${escapeHtml(sessionStatusLabel(s.status))}
${escapeHtml(s.username)} · ${s.os}/${s.arch} ${s.isAdmin ? '' + escapeHtml(c2t('c2.sessions.rootBadge')) + '' : ''}
${s.internalIp || '-'} · PID ${s.pid}
`).join(''); if (C2.selectedSessionId && !C2.sessions.find(s => s.id === C2.selectedSessionId)) { C2.selectedSessionId = null; } if (!C2.selectedSessionId && C2.sessions.length > 0) { C2.selectSession(C2.sessions[0].id); } }; C2.selectSession = function(id) { C2.selectedSessionId = id; C2.renderSessions(); C2.renderSessionDetail(id); C2.initTerminal(); }; C2.renderSessionDetail = function(id) { const container = document.getElementById('c2-session-main'); if (!container) return; const s = C2.sessions.find(x => x.id === id); if (!s) return; const adminVal = s.isAdmin ? c2t('c2.sessions.adminYes') : c2t('c2.sessions.adminNo'); const sleepLine = c2t('c2.sessions.infoSleepLine', { sec: s.sleepSeconds, jitter: s.jitterPercent }); container.innerHTML = `

${escapeHtml(s.hostname)} ${escapeHtml(sessionStatusLabel(s.status))}

${s.id} | ${escapeHtml(s.username)}@${s.os}/${s.arch}
${escapeHtml(c2t('c2.sessions.terminal'))}
${escapeHtml(c2t('c2.sessions.files'))}
${escapeHtml(c2t('c2.sessions.tasks'))}
${escapeHtml(c2t('c2.sessions.info'))}
${escapeHtml(c2t('c2.sessions.termStatusReady'))}
`; var isCurlBeacon = s.implantUuid && s.implantUuid.startsWith('curl_'); if (isCurlBeacon) { var termContainer = container.querySelector('#c2-terminal-container'); if (termContainer) { termContainer.innerHTML = '
' + '
📡
' + '
' + escapeHtml(c2t('c2.sessions.curlBeaconTitle')) + '
' + '
' + c2t('c2.sessions.curlBeaconBody').split('\n').map(function (ln) { return escapeHtml(ln); }).join('
') + '
' + '
'; } } setTimeout(() => { if (!isCurlBeacon) C2.initTerminal(); C2.loadFileList(s.id, '.'); C2.loadSessionTasks(s.id); }, 50); }; C2.switchTab = function(tab) { document.querySelectorAll('.c2-session-tab').forEach(el => el.classList.remove('active')); document.querySelectorAll('.c2-tab-panel').forEach(el => el.style.display = 'none'); const tabEl = document.querySelector(`.c2-session-tab[data-tab="${tab}"]`); if (tabEl) tabEl.classList.add('active'); const panel = document.getElementById(`c2-tab-${tab}`); if (panel) panel.style.display = 'block'; if (tab === 'terminal') { setTimeout(() => C2.fitTerminal(), 50); } }; C2.setSessionSleep = function(id) { const sleep = prompt(c2t('c2.sessions.promptSleepSeconds'), '5'); if (!sleep) return; const jitter = prompt(c2t('c2.sessions.promptJitterPercent'), '0') || '0'; apiRequest('PUT', `${API_BASE}/sessions/${id}/sleep`, { sleep_seconds: parseInt(sleep), jitter_percent: parseInt(jitter) }).then(data => { if (data.error) showToast(data.error, 'error'); else showToast(c2t('c2.sessions.toastSleepUpdated'), 'success'); }); }; C2.killSession = function(id) { if (!confirm(c2t('c2.sessions.confirmExitSession'))) return; apiRequest('POST', `${API_BASE}/tasks`, { session_id: id, task_type: 'exit', payload: {} }).then(data => { showToast(c2t('c2.sessions.toastExitSent'), 'success'); }); }; C2.deleteSessionRecord = function(id) { if (!confirm(c2t('c2.sessions.confirmDeleteSession'))) return; apiRequest('DELETE', `${API_BASE}/sessions/${id}`, {}).then(data => { if (data.error) { showToast(data.error, 'error'); return; } showToast(c2t('c2.sessions.toastSessionDeleted'), 'success'); if (C2.selectedSessionId === id) C2.selectedSessionId = null; C2.loadSessions(); }); }; // ============================================================================ // xterm 终端 // ============================================================================ C2.initTerminal = function() { const container = document.getElementById('c2-terminal-container'); if (!container || typeof Terminal === 'undefined') return; if (C2.terminalInstance) { C2.terminalInstance.dispose(); } const term = new Terminal({ cursorBlink: true, cursorStyle: 'block', fontSize: 14, fontFamily: 'Menlo, Monaco, "Courier New", monospace', lineHeight: 1.3, scrollback: 5000, theme: { background: '#0d1117', foreground: '#e6edf3', cursor: '#58a6ff', selection: 'rgba(88, 166, 255, 0.3)' } }); if (typeof FitAddon !== 'undefined') { const FitCtor = FitAddon.FitAddon || FitAddon; C2.terminalFitAddon = new FitCtor(); term.loadAddon(C2.terminalFitAddon); } term.open(container); try { if (C2.terminalFitAddon) C2.terminalFitAddon.fit(); } catch (e) {} let lineBuffer = ''; const prompt = '$ '; term.writeln('\x1b[36m' + c2t('c2.sessions.terminalWelcome') + '\x1b[0m'); term.writeln(''); term.write(prompt); term.onData(e => { const code = e.charCodeAt(0); if (code === 13) { // Enter term.writeln(''); const cmd = lineBuffer.trim(); lineBuffer = ''; if (cmd) { C2.executeInTerminal(cmd, term); } else { term.write(prompt); } } else if (code === 127) { // Backspace if (lineBuffer.length > 0) { lineBuffer = lineBuffer.slice(0, -1); term.write('\b \b'); } } else if (code >= 32) { // Printable lineBuffer += e; term.write(e); } }); C2.terminalInstance = term; // Resize observer if (C2.terminalResizeObserver) { C2.terminalResizeObserver.disconnect(); } C2.terminalResizeObserver = new ResizeObserver(() => { C2.fitTerminal(); }); C2.terminalResizeObserver.observe(container); }; C2.fitTerminal = function() { if (C2.terminalFitAddon && C2.terminalInstance) { try { C2.terminalFitAddon.fit(); } catch (e) {} } }; C2.executeInTerminal = function(cmd, term) { if (!C2.selectedSessionId) { term.writeln('\x1b[31m' + c2t('c2.sessions.termNoSession') + '\x1b[0m'); term.write('$ '); return; } const statusEl = document.getElementById('c2-terminal-status'); if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusExec'); apiRequest('POST', `${API_BASE}/tasks`, { session_id: C2.selectedSessionId, task_type: 'shell', payload: { command: cmd, timeout_seconds: 60 } }).then(data => { if (data.error) { term.writeln(`\x1b[31mError: ${data.error}\x1b[0m`); term.write('$ '); if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusErr'); } else { C2.waitForTaskResult(data.task?.id || data.task_id, term); } }); }; C2.waitForTaskResult = function(taskId, term) { let attempts = 0; const maxAttempts = 60; let delay = 500; const maxDelay = 5000; const check = () => { if (++attempts > maxAttempts) { term.writeln('\x1b[33m' + c2t('c2.sessions.termWaitTimeout') + '\x1b[0m'); term.write('$ '); const statusEl = document.getElementById('c2-terminal-status'); if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusTimeout'); return; } apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(data => { const task = data.task; if (task && (task.status === 'success' || task.status === 'failed')) { if (task.resultText) { const lines = task.resultText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n'); lines.forEach(line => term.writeln(line)); } if (task.error) { term.writeln(`\x1b[31m${task.error}\x1b[0m`); } term.write('$ '); const statusEl = document.getElementById('c2-terminal-status'); if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusReady'); } else { delay = Math.min(delay * 1.5, maxDelay); setTimeout(check, delay); } }); }; check(); }; C2.clearTerminal = function() { if (C2.terminalInstance) { C2.terminalInstance.clear(); C2.terminalInstance.writeln('\x1b[36m' + c2t('c2.sessions.termCleared') + '\x1b[0m'); C2.terminalInstance.write('$ '); } }; C2.copyTerminal = function() { if (!C2.terminalInstance) return; const text = C2.terminalInstance.getSelection(); if (text) copyToClipboard(text); else showToast(c2t('c2.sessions.termNoSelection'), 'warning'); }; // ============================================================================ // 文件管理 // ============================================================================ C2.loadFileList = function(sessionId, path) { if (!sessionId) sessionId = C2.selectedSessionId; if (!sessionId) return; if (!path) path = C2.currentPath || '.'; const container = document.getElementById('c2-file-list'); const breadcrumb = document.getElementById('c2-current-path'); if (container) container.innerHTML = '
' + escapeHtml(c2t('c2.files.loading')) + '
'; apiRequest('POST', `${API_BASE}/tasks`, { session_id: sessionId, task_type: 'ls', payload: { path: path } }).then(data => { if (data.error) { if (container) container.innerHTML = `
${data.error}
`; return; } C2.waitForFileList(data.task?.id || data.task_id, sessionId, path); }); }; C2.waitForFileList = function(taskId, sessionId, path) { let attempts = 0; const container = document.getElementById('c2-file-list'); const check = () => { if (++attempts > 60) { if (container) container.innerHTML = '
' + escapeHtml(c2t('c2.files.timeout')) + '
'; return; } apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(data => { const task = data.task; if (task && task.status === 'success') { C2.currentPath = path; const breadcrumb = document.getElementById('c2-current-path'); if (breadcrumb) breadcrumb.textContent = path; C2.renderFileList(task.resultText || ''); } else if (task && task.status === 'failed') { if (container) container.innerHTML = `
${escapeHtml(task.error || c2t('c2.files.failed'))}
`; } else { setTimeout(check, 500); } }); }; check(); }; C2.renderFileList = function(output) { const container = document.getElementById('c2-file-list'); if (!container) return; const lines = output.split('\n').filter(l => l.trim()); if (lines.length === 0) { container.innerHTML = '
' + escapeHtml(c2t('c2.files.emptyDir')) + '
'; return; } container.innerHTML = ` ${lines.map(line => { const parts = line.split(/\s+/); const name = parts[parts.length - 1] || line; const isDir = line.startsWith('d') || parts[0]?.startsWith?.('d'); return ` `; }).join('')}
${escapeHtml(c2t('c2.files.colName'))} ${escapeHtml(c2t('c2.files.colSize'))} ${escapeHtml(c2t('c2.files.colMode'))} ${escapeHtml(c2t('c2.files.colActions'))}
${isDir ? '📁' : '📄'} ${escapeHtml(name)} ${parts[parts.length - 5] || '-'} ${parts[parts.length - 4] || '-'} ${isDir ? `` : `` }
`; }; C2.refreshFiles = function() { C2.loadFileList(null, C2.currentPath); }; C2.downloadFile = function(filename) { if (!C2.selectedSessionId) return; const remotePath = C2.currentPath === '/' ? '/' + filename : C2.currentPath + '/' + filename; apiRequest('POST', `${API_BASE}/tasks`, { session_id: C2.selectedSessionId, task_type: 'download', payload: { remote_path: remotePath } }).then(data => { if (data.error) showToast(data.error, 'error'); else showToast(c2t('c2.payloads.toastDownloadQueued'), 'success'); }); }; // ============================================================================ // 任务管理 // ============================================================================ C2.loadTasks = function(page) { const p = page != null ? page : (C2.tasksPage || 1); C2.tasksPage = p; const ps = C2.tasksPageSize || 10; apiRequest('GET', `${API_BASE}/tasks?page=${encodeURIComponent(String(p))}&page_size=${encodeURIComponent(String(ps))}`).then(data => { if (data.error) { showToast(String(data.error), 'error'); return; } C2.tasks = data.tasks || []; C2.tasksTotal = typeof data.total === 'number' ? data.total : (C2.tasks.length || 0); if (typeof data.pending_queued_count === 'number') { C2.tasksPendingQueuedCount = data.pending_queued_count; } const maxPage = Math.max(1, Math.ceil(C2.tasksTotal / ps)); if (p > maxPage) { C2.loadTasks(maxPage); return; } C2.renderTasks(); C2.renderTasksPagination(); C2.syncTasksToolbar(); C2.updateDashboardStats(); }).catch(err => { showToast(err.message || String(err), 'error'); }); }; C2.goTasksPage = function(targetPage) { const totalPages = Math.max(1, Math.ceil((C2.tasksTotal || 0) / (C2.tasksPageSize || 10))); if (targetPage < 1 || targetPage > totalPages) return; C2.loadTasks(targetPage); const list = document.getElementById('c2-task-list'); if (list) list.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; C2.changeTasksPageSize = function() { const sel = document.getElementById('c2-tasks-page-size-pagination'); if (!sel) return; const n = parseInt(sel.value, 10); if (n > 0) { C2.tasksPageSize = n; C2.loadTasks(1); } }; C2.renderTasksPagination = function() { const paginationContainer = document.getElementById('c2-tasks-pagination'); if (!paginationContainer) return; const total = C2.tasksTotal || 0; const currentPage = C2.tasksPage || 1; const pageSize = C2.tasksPageSize || 10; const totalPages = Math.max(1, Math.ceil(total / pageSize)); if (total === 0) { paginationContainer.innerHTML = ''; return; } const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1; const end = Math.min(currentPage * pageSize, total); let html = '
'; html += `
${escapeHtml(c2t('c2.tasks.paginationShow', { start, end, total }))}
${escapeHtml(c2t('c2.tasks.paginationPage', { current: currentPage, total: totalPages }))}
`; html += '
'; paginationContainer.innerHTML = html; if (typeof applyTranslations === 'function') applyTranslations(paginationContainer); }; C2.collectCheckedTaskIds = function() { return Array.from(document.querySelectorAll('.c2-task-row-check:checked')).map(cb => cb.getAttribute('data-id')).filter(Boolean); }; C2.syncTasksToolbar = function() { const batchBtn = document.getElementById('c2-tasks-batch-delete'); const ids = C2.collectCheckedTaskIds(); if (batchBtn) batchBtn.disabled = ids.length === 0; const all = document.querySelectorAll('.c2-task-row-check'); const selAll = document.getElementById('c2-tasks-select-all'); if (selAll && all.length) { const nChecked = document.querySelectorAll('.c2-task-row-check:checked').length; selAll.checked = nChecked === all.length; selAll.indeterminate = nChecked > 0 && nChecked < all.length; } else if (selAll) { selAll.checked = false; selAll.indeterminate = false; } }; C2.onTasksSelectAll = function(checked) { document.querySelectorAll('.c2-task-row-check').forEach(cb => { cb.checked = checked; }); C2.syncTasksToolbar(); }; C2.deleteTaskById = function(id) { if (!id) return; if (!confirm(c2t('c2.tasks.confirmDeleteOne'))) return; apiRequest('DELETE', `${API_BASE}/tasks`, { ids: [id] }).then(data => { if (data.error) { showToast(String(data.error), 'error'); return; } showToast(c2t('c2.tasks.toastDeleted', { n: data.deleted != null ? data.deleted : 1 }), 'success'); C2.loadTasks(C2.tasksPage || 1); }).catch(err => showToast(err.message || String(err), 'error')); }; C2.deleteSelectedTasks = function() { const ids = C2.collectCheckedTaskIds(); if (!ids.length) { showToast(c2t('c2.tasks.toastSelectFirst'), 'warn'); return; } if (!confirm(c2t('c2.tasks.confirmBatchDelete', { n: ids.length }))) return; apiRequest('DELETE', `${API_BASE}/tasks`, { ids }).then(data => { if (data.error) { showToast(String(data.error), 'error'); return; } const deleted = data.deleted != null ? data.deleted : ids.length; showToast(c2t('c2.tasks.toastDeleted', { n: deleted }), 'success'); C2.loadTasks(C2.tasksPage || 1); }).catch(err => showToast(err.message || String(err), 'error')); }; C2.loadSessionTasks = function(sessionId) { apiRequest('GET', `${API_BASE}/tasks?session_id=${encodeURIComponent(sessionId)}&limit=50`).then(data => { const container = document.getElementById('c2-session-tasks-list'); const tasks = data.tasks || []; if (typeof data.pending_queued_count === 'number') { C2.tasksPendingQueuedCount = data.pending_queued_count; C2.updateDashboardStats(); } if (!container) return; if (tasks.length === 0) { container.innerHTML = '
' + escapeHtml(c2t('c2.tasks.emptySession')) + '
'; return; } container.innerHTML = tasks.map(t => `
${t.taskType} ${escapeHtml(taskStatusLabel(t.status))} | ${formatDuration(t.durationMs)}
`).join(''); }); }; C2.renderTasks = function() { const container = document.getElementById('c2-task-list'); if (!container) return; const selAll = document.getElementById('c2-tasks-select-all'); if (selAll) { selAll.checked = false; selAll.indeterminate = false; } if (C2.tasks.length === 0) { container.innerHTML = '
' + escapeHtml(c2t('c2.tasks.emptyAll')) + '
'; if (selAll) selAll.disabled = true; C2.syncTasksToolbar(); return; } if (selAll) selAll.disabled = false; const delTitle = escapeHtml(c2t('c2.tasks.deleteOne')); container.innerHTML = ` ${C2.tasks.map(t => { const rawId = t.id || ''; const idJson = JSON.stringify(rawId); const shortTaskId = rawId.length > 14 ? escapeHtml(rawId.substring(0, 12)) + '\u2026' : escapeHtml(rawId); const sid = t.sessionId ? escapeHtml(String(t.sessionId).substring(0, 8)) + '\u2026' : '-'; return ` `; }).join('')}
${escapeHtml(c2t('c2.tasks.colTask'))} ${escapeHtml(c2t('c2.tasks.colSession'))} ${escapeHtml(c2t('c2.tasks.colType'))} ${escapeHtml(c2t('c2.tasks.colStatus'))} ${escapeHtml(c2t('c2.tasks.colDuration'))} ${escapeHtml(c2t('c2.tasks.colCreated'))} ${escapeHtml(c2t('c2.tasks.colActions'))}
${shortTaskId} ${sid} ${escapeHtml(t.taskType || '')} ${escapeHtml(taskStatusLabel(t.status))} ${formatDuration(t.durationMs)} ${formatTime(t.createdAt)} ${t.status === 'queued' || t.status === 'sent' ? `` : ''}
`; C2.syncTasksToolbar(); if (typeof applyTranslations === 'function') applyTranslations(container); }; C2.viewTask = function(id) { const modal = document.getElementById('c2-modal'); const content = document.getElementById('c2-modal-content'); if (!content) return; const renderTaskModal = function(t) { if (!t || !modal) return; content.innerHTML = `

${escapeHtml(c2t('c2.tasks.modalTitle'))}

${escapeHtml(c2t('c2.tasks.labelId'))}: ${t.id}
${escapeHtml(c2t('c2.tasks.labelSession'))}: ${t.sessionId}
${escapeHtml(c2t('c2.tasks.labelType'))}: ${t.taskType}
${escapeHtml(c2t('c2.tasks.labelStatus'))}: ${escapeHtml(taskStatusLabel(t.status))}
${escapeHtml(c2t('c2.tasks.labelCreated'))}: ${formatTime(t.createdAt)}
${escapeHtml(c2t('c2.tasks.labelSent'))}: ${formatTime(t.sentAt)}
${escapeHtml(c2t('c2.tasks.labelCompleted'))}: ${formatTime(t.completedAt)}
${escapeHtml(c2t('c2.tasks.labelDuration'))}: ${formatDuration(t.durationMs)}
${t.error ? `
${escapeHtml(c2t('c2.tasks.labelError'))}: ${escapeHtml(t.error)}
` : ''} ${t.resultText ? `
${escapeHtml(c2t('c2.tasks.labelResult'))}:
${escapeHtml(t.resultText)}
` : ''}
`; modal.style.display = 'flex'; }; const local = C2.tasks.find(x => x.id === id); if (local) { renderTaskModal(local); return; } apiRequest('GET', `${API_BASE}/tasks/${id}`).then(data => { if (data.task) renderTaskModal(data.task); }); }; C2.cancelTask = function(id) { apiRequest('POST', `${API_BASE}/tasks/${id}/cancel`, {}).then(data => { if (data.error) showToast(data.error, 'error'); else { showToast(c2t('c2.tasks.toastCancelled'), 'success'); C2.loadTasks(C2.tasksPage || 1); } }); }; // ============================================================================ // Payload 生成 // ============================================================================ C2.loadListenersForPayload = function() { apiRequest('GET', `${API_BASE}/listeners`).then(data => { if (data.error) { showToast(data.error, 'error'); return; } C2.listeners = data.listeners || []; C2.renderPayloadPage(); }).catch(err => { showToast(c2t('c2.payloads.toastLoadListenersFail', { msg: err.message || '' }), 'error'); }); }; var onelinerKindsByListenerType = { 'tcp_reverse': [ { value: 'bash', label: 'Bash (/dev/tcp)' }, { value: 'nc', label: 'Netcat (-e)' }, { value: 'nc_mkfifo', label: 'Netcat (mkfifo)' }, { value: 'python', label: 'Python' }, { value: 'perl', label: 'Perl' }, { value: 'powershell', label: 'PowerShell' } ], 'http_beacon': [ { value: 'curl_beacon', label: 'Curl Beacon (HTTP)' } ], 'https_beacon': [ { value: 'curl_beacon', label: 'Curl Beacon (HTTP)' } ], 'websocket': [ { value: 'curl_beacon', label: 'Curl Beacon (HTTP)' } ] }; C2.updateOnelinerKinds = function() { var listenerSelect = document.getElementById('c2-payload-listener'); var kindSelect = document.getElementById('c2-payload-kind'); if (!listenerSelect || !kindSelect) return; var listenerId = listenerSelect.value; var listener = (C2.listeners || []).find(function(l) { return l.id === listenerId; }); var ltype = listener ? listener.type : ''; var kinds = onelinerKindsByListenerType[ltype] || []; if (kinds.length === 0) { kindSelect.innerHTML = ''; } else { kindSelect.innerHTML = kinds.map(function(k) { return ''; }).join(''); } }; C2.updateLoopbackBuildHint = function() { const sel = document.getElementById('c2-build-listener'); const hint = document.getElementById('c2-build-loopback-hint'); if (!hint) return; const override = document.getElementById('c2-build-host') && String(document.getElementById('c2-build-host').value || '').trim(); if (override) { hint.style.display = 'none'; return; } const id = sel && sel.value; if (!id) { hint.style.display = 'none'; return; } const l = (C2.listeners || []).find(function(x) { return x.id === id; }); const h = (l && l.bindHost ? String(l.bindHost) : '').toLowerCase().trim(); if (h === '127.0.0.1' || h === 'localhost' || h === '::1') { hint.textContent = c2t('c2.payloads.loopbackBeaconWarning'); hint.style.display = 'block'; } else { hint.style.display = 'none'; } }; C2.renderPayloadPage = function() { const optionsHtml = C2.listeners.length > 0 ? C2.listeners.map(l => `` ).join('') : ''; const listenerSelect = document.getElementById('c2-payload-listener'); if (listenerSelect) { listenerSelect.innerHTML = optionsHtml; listenerSelect.removeEventListener('change', C2.updateOnelinerKinds); listenerSelect.addEventListener('change', C2.updateOnelinerKinds); } const buildSelect = document.getElementById('c2-build-listener'); if (buildSelect) { const listeners = C2.listeners || []; let buildOptionsHtml; if (listeners.length > 0) { buildOptionsHtml = listeners.map(l => `` ).join(''); } else { buildOptionsHtml = ''; } buildSelect.innerHTML = buildOptionsHtml; buildSelect.removeEventListener('change', C2.updateLoopbackBuildHint); buildSelect.addEventListener('change', C2.updateLoopbackBuildHint); C2.updateLoopbackBuildHint(); } const buildHostInput = document.getElementById('c2-build-host'); if (buildHostInput) { buildHostInput.removeEventListener('input', C2.updateLoopbackBuildHint); buildHostInput.addEventListener('input', C2.updateLoopbackBuildHint); } C2.updateOnelinerKinds(); const buildBtn = document.getElementById('c2-build-btn'); if (buildBtn && !buildBtn.disabled) buildBtn.textContent = c2t('c2.payloads.buildBeaconBtn'); const genBtn = document.getElementById('c2-generate-oneliner-btn'); if (genBtn) genBtn.textContent = c2t('c2.payloads.generateOnelinerBtn'); }; C2.generateOneliner = function() { const listenerId = document.getElementById('c2-payload-listener')?.value; const kind = document.getElementById('c2-payload-kind')?.value || 'bash'; const host = document.getElementById('c2-payload-host')?.value; if (!listenerId) { showToast(c2t('c2.payloads.toastPickListener'), 'error'); return; } apiRequest('POST', `${API_BASE}/payloads/oneliner`, { listener_id: listenerId, kind: kind, host: host }).then(data => { if (data.error) { showToast(data.error, 'error'); } else { const output = document.getElementById('c2-oneliner-output'); if (output) { output.textContent = data.oneliner; output.style.display = 'block'; } } }).catch(err => { showToast(c2t('c2.payloads.toastOnelinerFail', { msg: err.message || '' }), 'error'); }); }; C2.copyOneliner = function() { const el = document.getElementById('c2-oneliner-output'); if (el && el.textContent) copyToClipboard(el.textContent); }; C2.buildBeacon = function() { const listenerId = document.getElementById('c2-build-listener')?.value; const os = document.getElementById('c2-build-os')?.value || 'linux'; const arch = document.getElementById('c2-build-arch')?.value || 'amd64'; const host = document.getElementById('c2-build-host')?.value; if (!listenerId) { showToast(c2t('c2.payloads.toastPickListener'), 'error'); return; } const btn = document.getElementById('c2-build-btn'); if (btn) { btn.disabled = true; btn.textContent = c2t('c2.payloads.building'); } apiRequest('POST', `${API_BASE}/payloads/build`, { listener_id: listenerId, os: os, arch: arch, host: host }).then(data => { if (btn) { btn.disabled = false; btn.textContent = c2t('c2.payloads.buildBeaconBtn'); } if (data.error) { showToast(data.error, 'error'); } else { showToast(c2t('c2.payloads.toastBuildSuccess', { bytes: data.payload?.size_bytes }), 'success'); const result = document.getElementById('c2-build-result'); if (result) { result.innerHTML = `
✓ ${escapeHtml(c2t('c2.payloads.buildSuccessTitle'))}
${escapeHtml(c2t('c2.payloads.buildMetaOsArch', { os: data.payload?.os, arch: data.payload?.arch }))}
${escapeHtml(c2t('c2.payloads.buildSize', { bytes: data.payload?.size_bytes }))}
`; } } }).catch(err => { if (btn) { btn.disabled = false; btn.textContent = c2t('c2.payloads.buildBeaconBtn'); } showToast(c2t('c2.payloads.toastBuildFail', { msg: err.message || '' }), 'error'); }); }; // ============================================================================ // 事件审计 // ============================================================================ C2.loadEvents = function(page) { const p = page != null ? page : (C2.eventsPage || 1); C2.eventsPage = p; const ps = C2.eventsPageSize || 10; apiRequest('GET', `${API_BASE}/events?page=${encodeURIComponent(String(p))}&page_size=${encodeURIComponent(String(ps))}`).then(data => { if (data.error) { showToast(String(data.error), 'error'); return; } C2.events = data.events || []; C2.eventsTotal = typeof data.total === 'number' ? data.total : (C2.events.length || 0); const maxPage = Math.max(1, Math.ceil(C2.eventsTotal / ps)); if (p > maxPage) { C2.loadEvents(maxPage); return; } C2.renderEvents(); C2.renderEventsPagination(); C2.syncEventsToolbar(); }).catch(err => { showToast(err.message || String(err), 'error'); }); }; C2.goEventsPage = function(targetPage) { const totalPages = Math.max(1, Math.ceil((C2.eventsTotal || 0) / (C2.eventsPageSize || 10))); if (targetPage < 1 || targetPage > totalPages) return; C2.loadEvents(targetPage); const list = document.getElementById('c2-event-list'); if (list) list.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; C2.changeEventsPageSize = function() { const sel = document.getElementById('c2-events-page-size-pagination'); if (!sel) return; const n = parseInt(sel.value, 10); if (n > 0) { C2.eventsPageSize = n; C2.loadEvents(1); } }; C2.renderEventsPagination = function() { const paginationContainer = document.getElementById('c2-events-pagination'); if (!paginationContainer) return; const total = C2.eventsTotal || 0; const currentPage = C2.eventsPage || 1; const pageSize = C2.eventsPageSize || 10; const totalPages = Math.max(1, Math.ceil(total / pageSize)); if (total === 0) { paginationContainer.innerHTML = ''; return; } const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1; const end = Math.min(currentPage * pageSize, total); let html = '
'; html += `
${escapeHtml(c2t('c2.events.paginationShow', { start, end, total }))}
${escapeHtml(c2t('c2.events.paginationPage', { current: currentPage, total: totalPages }))}
`; html += '
'; paginationContainer.innerHTML = html; if (typeof applyTranslations === 'function') applyTranslations(paginationContainer); }; C2.collectCheckedEventIds = function() { return Array.from(document.querySelectorAll('.c2-event-check:checked')).map(cb => cb.getAttribute('data-id')).filter(Boolean); }; C2.syncEventsToolbar = function() { const batchBtn = document.getElementById('c2-events-batch-delete'); const ids = C2.collectCheckedEventIds(); if (batchBtn) batchBtn.disabled = ids.length === 0; const all = document.querySelectorAll('.c2-event-check'); const selAll = document.getElementById('c2-events-select-all'); if (selAll && all.length) { const nChecked = document.querySelectorAll('.c2-event-check:checked').length; selAll.checked = nChecked === all.length; selAll.indeterminate = nChecked > 0 && nChecked < all.length; } else if (selAll) { selAll.checked = false; selAll.indeterminate = false; } }; C2.onEventsSelectAll = function(checked) { document.querySelectorAll('.c2-event-check').forEach(cb => { cb.checked = checked; }); C2.syncEventsToolbar(); }; C2.deleteEventById = function(id) { if (!id) return; if (!confirm(c2t('c2.events.confirmDeleteOne'))) return; apiRequest('DELETE', `${API_BASE}/events`, { ids: [id] }).then(data => { if (data.error) { showToast(String(data.error), 'error'); return; } showToast(c2t('c2.events.toastDeleted', { n: data.deleted != null ? data.deleted : 1 }), 'success'); C2.loadEvents(C2.eventsPage || 1); }).catch(err => showToast(err.message || String(err), 'error')); }; C2.deleteSelectedEvents = function() { const ids = C2.collectCheckedEventIds(); if (!ids.length) { showToast(c2t('c2.events.toastSelectFirst'), 'warn'); return; } if (!confirm(c2t('c2.events.confirmBatchDelete', { n: ids.length }))) return; apiRequest('DELETE', `${API_BASE}/events`, { ids }).then(data => { if (data.error) { showToast(String(data.error), 'error'); return; } const deleted = data.deleted != null ? data.deleted : ids.length; showToast(c2t('c2.events.toastDeleted', { n: deleted }), 'success'); C2.loadEvents(C2.eventsPage || 1); }).catch(err => showToast(err.message || String(err), 'error')); }; C2.renderEvents = function() { const container = document.getElementById('c2-event-list'); if (!container) return; const selAll = document.getElementById('c2-events-select-all'); if (selAll) { selAll.checked = false; selAll.indeterminate = false; } if (C2.events.length === 0) { container.innerHTML = '
' + escapeHtml(c2t('c2.events.empty')) + '
'; if (selAll) selAll.disabled = true; C2.syncEventsToolbar(); return; } if (selAll) selAll.disabled = false; const delTitle = escapeHtml(c2t('c2.events.deleteOne')); container.innerHTML = C2.events.map(e => { const eid = escapeHtml(e.id || ''); return `
${escapeHtml(e.message)}
${formatTime(e.createdAt)} · ${escapeHtml(e.category || '')}${e.sessionId ? ' · ' + escapeHtml(String(e.sessionId).substring(0, 8)) : ''}
`; }).join(''); C2.syncEventsToolbar(); if (typeof applyTranslations === 'function') applyTranslations(container); }; C2.connectEventStream = function() { if (C2.eventSource) C2.eventSource.close(); let streamUrl = `${API_BASE}/events/stream`; if (typeof authToken !== 'undefined' && authToken) { streamUrl += `?token=${encodeURIComponent(authToken)}`; } C2.eventSource = new EventSource(streamUrl); C2.eventSource.onmessage = (e) => { try { const event = JSON.parse(e.data); C2.onEvent(event); } catch (err) {} }; C2.eventSource.onerror = () => { setTimeout(() => C2.connectEventStream(), 5000); }; }; C2.onEvent = function(event) { if (window.currentPageId === 'c2-events' && (C2.eventsPage || 1) === 1) { C2.loadEvents(1); } const msg = event.message || ''; const sessionOnline = event.category === 'session' && ( msg.includes('上线') || msg.includes('新会话') || /new session/i.test(msg) ); if (event.level === 'critical' || sessionOnline) { showToast(`[${event.category}] ${event.message}`, event.level === 'critical' ? 'error' : 'info'); } C2.updateDashboardStats(); }; // ============================================================================ // Profile 管理 // ============================================================================ C2.loadProfiles = function() { apiRequest('GET', `${API_BASE}/profiles`).then(data => { C2.profiles = data.profiles || []; C2.renderProfiles(); }); }; C2.renderProfiles = function() { const container = document.getElementById('c2-profile-list'); if (!container) return; if (C2.profiles.length === 0) { container.innerHTML = '
' + escapeHtml(c2t('c2.profiles.empty')) + '
'; return; } const defVal = c2t('c2.profiles.defaultValue'); container.innerHTML = C2.profiles.map(p => `

${escapeHtml(p.name)}

UA: ${escapeHtml(p.userAgent || defVal)}
URIs: ${escapeHtml((p.uris || []).join(', ') || defVal)}
Jitter: ${p.jitterMinMs || 0}ms – ${p.jitterMaxMs || 0}ms
`).join(''); }; C2.showCreateProfileModal = function() { const modal = document.getElementById('c2-modal'); const content = document.getElementById('c2-modal-content'); if (!content) return; content.innerHTML = `

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

${escapeHtml(c2t('c2.profiles.hintUa'))}
${escapeHtml(c2t('c2.profiles.hintUris'))}
${escapeHtml(c2t('c2.profiles.hintHeaders'))}
`; modal.style.display = 'flex'; }; C2.createProfile = function() { const name = document.getElementById('c2-profile-name')?.value.trim(); if (!name) { showToast(c2t('c2.profiles.toastNameRequired'), 'error'); return; } const userAgent = document.getElementById('c2-profile-ua')?.value.trim() || ''; const urisRaw = document.getElementById('c2-profile-uris')?.value.trim() || ''; const uris = urisRaw.split('\n').map(u => u.trim()).filter(u => u); const jitterMinMs = parseInt(document.getElementById('c2-profile-jmin')?.value) || 100; const jitterMaxMs = parseInt(document.getElementById('c2-profile-jmax')?.value) || 500; let responseHeaders = {}; const headersRaw = document.getElementById('c2-profile-headers')?.value.trim(); if (headersRaw) { try { responseHeaders = JSON.parse(headersRaw); } catch (e) { showToast(c2t('c2.profiles.toastInvalidHeadersJson'), 'error'); return; } } apiRequest('POST', `${API_BASE}/profiles`, { name, user_agent: userAgent, uris, jitter_min_ms: jitterMinMs, jitter_max_ms: jitterMaxMs, response_headers: responseHeaders }).then(data => { if (data.error) { showToast(data.error, 'error'); } else { showToast(c2t('c2.profiles.toastCreated'), 'success'); C2.closeModal(); C2.loadProfiles(); } }); }; C2.deleteProfile = function(id) { if (!confirm(c2t('c2.profiles.confirmDelete'))) return; apiRequest('DELETE', `${API_BASE}/profiles/${id}`, {}).then(data => { showToast(c2t('c2.profiles.toastDeleted'), 'success'); C2.loadProfiles(); }); }; // ============================================================================ // 仪表盘 // ============================================================================ C2.updateDashboardStats = function() { const runningListeners = C2.listeners.filter(l => l.status === 'running').length; const activeSessions = C2.sessions.filter(s => s.status === 'active').length; const pendingTasks = typeof C2.tasksPendingQueuedCount === 'number' ? C2.tasksPendingQueuedCount : C2.tasks.filter(t => t.status === 'queued' || t.status === 'pending').length; const elListeners = document.getElementById('c2-stat-listeners'); const elSessions = document.getElementById('c2-stat-sessions'); const elPending = document.getElementById('c2-stat-pending'); if (elListeners) elListeners.textContent = runningListeners; if (elSessions) elSessions.textContent = activeSessions; if (elPending) elPending.textContent = pendingTasks; }; // ============================================================================ // 模态框 // ============================================================================ C2.closeModal = function() { const modal = document.getElementById('c2-modal'); if (modal) modal.style.display = 'none'; }; // ============================================================================ // 暴露到全局 // ============================================================================ window.C2 = C2; // 页面切换监听 window.addEventListener('pageChanged', function(e) { if (e.detail?.pageId?.startsWith('c2')) { C2.init(); } }); // DOM 加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { if (window.currentPageId?.startsWith('c2')) C2.init(); }); } else { if (window.currentPageId?.startsWith('c2')) C2.init(); } document.addEventListener('languagechange', function () { try { if (!window.currentPageId || !String(window.currentPageId).startsWith('c2')) return; if (typeof applyTranslations === 'function') applyTranslations(document); C2.init(); if (C2.selectedSessionId && (window.currentPageId === 'c2-sessions')) { C2.renderSessions(); C2.renderSessionDetail(C2.selectedSessionId); } } catch (e) { console.warn('languagechange C2 refresh failed', e); } }); })();