(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;
});
})();