133 lines
5.0 KiB
JavaScript
133 lines
5.0 KiB
JavaScript
(function () {
|
||
const state = { bizType: 'pdf', items: [], currentRel: null };
|
||
|
||
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;
|
||
}
|
||
|
||
function showToast(msg, ok) {
|
||
const el = $('toast');
|
||
el.textContent = msg;
|
||
el.className = 'rx-toast ' + (ok ? 'ok' : 'err');
|
||
setTimeout(() => { el.textContent = ''; el.className = 'rx-toast'; }, 4000);
|
||
}
|
||
|
||
function statusBadge(rec) {
|
||
if (rec.ok) return '<span class="badge ok">成功</span>';
|
||
return '<span class="badge err">失败</span>';
|
||
}
|
||
|
||
function fmtTime(at) {
|
||
if (!at) return '-';
|
||
try {
|
||
return new Date(at).toLocaleString('zh-CN');
|
||
} catch {
|
||
return esc(at);
|
||
}
|
||
}
|
||
|
||
async function load() {
|
||
const data = await api('/t/api/prescription/records?bizType=' + encodeURIComponent(state.bizType) + '&limit=200');
|
||
state.items = data.items || [];
|
||
const tb = $('tbody');
|
||
if (!state.items.length) {
|
||
tb.innerHTML = '<tr><td colspan="9" style="text-align:center;color:var(--muted)">暂无记录(可先触发同步或点「重建索引」)</td></tr>';
|
||
$('hint').textContent = '索引文件:api-logs/prescription-audit.jsonl';
|
||
return;
|
||
}
|
||
tb.innerHTML = state.items.map((rec) => {
|
||
const hint = rec.fileId || rec.prescriptionHint || '-';
|
||
const rowClass = rec.ok ? '' : ' class="rx-fail"';
|
||
return '<tr' + rowClass + '>' +
|
||
'<td>' + fmtTime(rec.at) + '</td>' +
|
||
'<td><code>' + esc(rec.channel) + '</code></td>' +
|
||
'<td>' + esc(rec.httpStatus) + '</td>' +
|
||
'<td>' + esc(rec.msgCode != null ? rec.msgCode : '-') + '</td>' +
|
||
'<td><code class="trace">' + esc(rec.traceId || '-') + '</code></td>' +
|
||
'<td>' + esc(hint) + '</td>' +
|
||
'<td>' + statusBadge(rec) + (rec.errorSummary ? ' ' + esc(rec.errorSummary) : '') + '</td>' +
|
||
'<td>' + esc(rec.durationMs) + 'ms</td>' +
|
||
'<td class="rx-actions">' +
|
||
'<button type="button" class="btn sm" data-act="detail" data-rel="' + esc(rec.rel) + '">详情</button> ' +
|
||
'<button type="button" class="btn sm primary" data-act="retry" data-rel="' + esc(rec.rel) + '">重试</button>' +
|
||
'</td></tr>';
|
||
}).join('');
|
||
$('hint').textContent = '共 ' + state.items.length + ' 条 · 类型 ' + state.bizType;
|
||
tb.querySelectorAll('button[data-act]').forEach((btn) => {
|
||
btn.onclick = () => {
|
||
const rel = btn.dataset.rel;
|
||
if (btn.dataset.act === 'detail') openDetail(rel);
|
||
else retryRecord(rel);
|
||
};
|
||
});
|
||
}
|
||
|
||
async function openDetail(rel) {
|
||
state.currentRel = rel;
|
||
const d = await api('/t/api/logs/detail?rel=' + encodeURIComponent(rel));
|
||
$('detail-view').textContent = JSON.stringify(d, null, 2);
|
||
$('replay-view').style.display = 'none';
|
||
$('replay-view').textContent = '';
|
||
$('drawer').hidden = false;
|
||
}
|
||
|
||
async function retryRecord(rel) {
|
||
if (!confirm('向政务云回放该条出站请求(块2)?\n\n注意:仅内网运维重放,不会更新 transit/xk-api 状态。')) return;
|
||
state.currentRel = rel;
|
||
try {
|
||
const result = await api('/t/api/test/send', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ rel: rel, useOutbound: true }),
|
||
});
|
||
$('replay-view').style.display = 'block';
|
||
$('replay-view').textContent = '回放结果:\n' + JSON.stringify(result, null, 2);
|
||
$('drawer').hidden = false;
|
||
$('detail-view').textContent = '(重试完成,见下方回放结果;可点详情查看完整 api-log)';
|
||
showToast('回放完成 HTTP ' + (result.httpStatus || '-'), result.ok !== false);
|
||
} catch (e) {
|
||
showToast(e.message, false);
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('.rx-tabs .tab').forEach((tab) => {
|
||
tab.onclick = () => {
|
||
document.querySelectorAll('.rx-tabs .tab').forEach((t) => t.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
state.bizType = tab.dataset.biz;
|
||
load().catch((e) => showToast(e.message, false));
|
||
};
|
||
});
|
||
|
||
$('btn-refresh').onclick = () => load().catch((e) => showToast(e.message, false));
|
||
$('btn-rebuild').onclick = async () => {
|
||
if (!confirm('扫描 api-logs 全量重建 prescription-audit.jsonl?')) return;
|
||
try {
|
||
const r = await api('/t/api/prescription/rebuild', { method: 'POST' });
|
||
showToast('已索引 ' + r.count + ' 条', true);
|
||
await load();
|
||
} catch (e) {
|
||
showToast(e.message, false);
|
||
}
|
||
};
|
||
$('btn-close-drawer').onclick = () => { $('drawer').hidden = true; };
|
||
$('btn-retry').onclick = () => {
|
||
if (state.currentRel) retryRecord(state.currentRel);
|
||
};
|
||
|
||
load().catch((e) => showToast(e.message, false));
|
||
})();
|