(function () { const state = { meta: null, logs: [], currentRel: null, currentDetail: null, loadSource: 'outbound', bodyBase64: '', }; function $(id) { return document.getElementById(id); } async function api(path, opts) { const r = await fetch(path, opts); const t = await r.text(); let j; try { j = JSON.parse(t); } catch { j = { error: t }; } if (!r.ok) throw new Error(j.error || r.statusText); return j; } function esc(s) { const d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; } async function loadMeta() { state.meta = await api('/t/api/meta'); $('meta-hint').innerHTML = '28212: ' + esc(state.meta.superviseTarget) + '
' + '28211: ' + esc(state.meta.fileTarget) + ''; } async function loadLogs() { state.logs = await api('/t/api/logs'); const ul = $('log-list'); ul.innerHTML = ''; if (!state.logs.length) { ul.innerHTML = '
  • 暂无 file 日志
  • '; return; } state.logs.forEach((e) => { const li = document.createElement('li'); li.dataset.rel = e.rel; li.innerHTML = '
    ' + esc(e.name) + '
    ' + '
    ' + esc(e.hourDir) + ' · ' + esc(e.modified) + '
    '; li.onclick = () => selectLog(e.rel, li); ul.appendChild(li); }); } async function selectLog(rel, liEl) { state.currentRel = rel; document.querySelectorAll('.log-list li').forEach((el) => el.classList.remove('active')); if (liEl) liEl.classList.add('active'); state.currentDetail = await api('/t/api/logs/detail?rel=' + encodeURIComponent(rel)); loadSection(state.loadSource === 'inbound' ? state.currentDetail.inbound : state.currentDetail.outbound); showLogResponse(); $('logmeta-view').textContent = JSON.stringify({ meta: state.currentDetail.meta, parseWarnings: state.currentDetail.parseWarnings, outboundAbsent: state.currentDetail.outboundAbsent, }, null, 2); } function showLogResponse() { const r = state.currentDetail.response; let text = ''; if (r.bodyJSON) text = r.bodyJSON; else if (r.body) text = r.body; else text = '(日志块3 无响应体)'; if (r.status) text = 'HTTP ' + r.status + '\n\n' + text; $('response-view').textContent = text; } function loadSection(sec) { if (!sec) return; $('url').value = sec.url || ''; renderHeaders(sec.headers || {}); const info = []; if (sec.bodyLen) info.push('body_len=' + sec.bodyLen); if (sec.bodyPlain) info.push('明文'); else if (sec.bodyBase64) info.push('base64'); $('body-info').textContent = info.join(' · ') || ''; state.bodyBase64 = sec.bodyBase64 || ''; if (sec.bodyPlain && sec.bodyRaw) { $('body-raw').value = sec.bodyRaw; } else if (sec.bodyBase64) { try { $('body-raw').value = '[binary ' + sec.bodyLen + ' bytes — 发送时将使用 bodyBase64 回放]'; } catch (e) { $('body-raw').value = ''; } } else { $('body-raw').value = sec.bodyRaw || ''; } $('body-base64').value = state.bodyBase64; } function renderHeaders(hdrs) { const tbody = $('headers-table').querySelector('tbody'); tbody.innerHTML = ''; Object.entries(hdrs).forEach(([k, v]) => addHeaderRow(k, v)); if (!tbody.children.length) addHeaderRow('', ''); } function addHeaderRow(k, v) { const tbody = $('headers-table').querySelector('tbody'); const tr = document.createElement('tr'); tr.innerHTML = '' + '' + ''; tr.querySelector('.del').onclick = () => tr.remove(); tbody.appendChild(tr); } function collectHeaders() { const h = {}; $('headers-table').querySelectorAll('tbody tr').forEach((tr) => { const k = tr.querySelector('.hk').value.trim(); const v = tr.querySelector('.hv').value.trim(); if (k) h[k] = v; }); return h; } async function runConnect(channel) { const box = $('connect-results'); box.innerHTML = '测试中…'; try { const res = await api('/t/api/test/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel }), }); box.innerHTML = res.results.map((r) => { const cls = r.ok ? 'ok' : 'err'; const detail = r.ok ? 'http=' + r.httpStatus + ' ' + r.elapsedMs + 'ms' : (r.phase || '') + ' ' + (r.error || ''); return '' + esc(r.channel) + ' ' + esc(detail) + ''; }).join(''); } catch (e) { box.innerHTML = '' + esc(e.message) + ''; } } async function sendRequest() { const payload = { url: $('url').value.trim(), headers: collectHeaders(), useOutbound: state.loadSource === 'outbound', }; if (state.currentRel) payload.rel = state.currentRel; const b64 = $('body-base64').value.trim(); if (b64 && !$('body-raw').value.startsWith('[binary')) { payload.bodyBase64 = b64; } else if (b64) { payload.bodyBase64 = b64; } else { const raw = $('body-raw').value; if (raw && !raw.startsWith('[binary')) { payload.bodyBase64 = btoa(unescape(encodeURIComponent(raw))); } } $('response-view').textContent = '请求中…'; try { const res = await api('/t/api/test/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); let text = 'HTTP ' + res.httpStatus + ' (' + res.elapsedMs + 'ms)\n\n'; if (res.error) text += 'Error: ' + res.error; else if (res.bodyJSON) text += res.bodyJSON; else text += res.body || ''; $('response-view').textContent = text; } catch (e) { $('response-view').textContent = '失败: ' + e.message; } } function exportLog(source) { if (!state.currentRel) { alert('请先选择日志'); return; } window.location.href = '/t/api/logs/export?rel=' + encodeURIComponent(state.currentRel) + '&source=' + (source === 'inbound' ? 'inbound' : 'outbound'); } function copyCurl() { const url = $('url').value.trim(); const hdrs = collectHeaders(); let parts = ['curl -X POST "' + url + '"']; Object.entries(hdrs).forEach(([k, v]) => { parts.push('-H "' + k + ': ' + v.replace(/"/g, '\\"') + '"'); }); const b64 = $('body-base64').value.trim(); if (b64) { parts.push('--data-binary @<(echo ' + b64 + ' | base64 -d)'); } else { const raw = $('body-raw').value; if (raw && !raw.startsWith('[binary')) { parts.push("-d '" + raw.replace(/'/g, "'\\''") + "'"); } } navigator.clipboard.writeText(parts.join(' \\\n ')).then( () => alert('已复制 cURL(multipart 二进制请用导出 ApiPost)'), () => alert('复制失败') ); } document.querySelectorAll('.tab').forEach((tab) => { tab.onclick = () => { document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active')); document.querySelectorAll('.panel').forEach((p) => p.classList.remove('active')); tab.classList.add('active'); $('panel-' + tab.dataset.tab).classList.add('active'); }; }); document.querySelectorAll('[data-connect]').forEach((btn) => { btn.onclick = () => runConnect(btn.dataset.connect); }); $('btn-refresh-logs').onclick = loadLogs; $('btn-load-outbound').onclick = () => { state.loadSource = 'outbound'; if (state.currentDetail) loadSection(state.currentDetail.outbound); }; $('btn-load-inbound').onclick = () => { state.loadSource = 'inbound'; if (state.currentDetail) loadSection(state.currentDetail.inbound); }; $('btn-export-outbound').onclick = () => exportLog('outbound'); $('btn-export-inbound').onclick = () => exportLog('inbound'); $('btn-curl').onclick = copyCurl; $('btn-send').onclick = sendRequest; $('btn-add-header').onclick = () => addHeaderRow('', ''); loadMeta().then(loadLogs).catch((e) => { $('meta-hint').textContent = '加载失败: ' + e.message; }); })();