Files
xk-hy-transit-go/internal/logweb/web/upload.html
2026-05-28 16:39:51 +08:00

165 lines
6.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>文件上传测试</title>
<link rel="stylesheet" href="/static/static.css">
<style>
.token-panel h4 { margin: 0.5rem 0 0.25rem; font-size: 0.9rem; color: var(--accent); }
.token-panel pre { margin: 0 0 0.75rem; max-height: 8rem; overflow: auto; font-size: 0.85rem; }
.muted-note { color: var(--muted); font-size: 0.88rem; margin: 0.35rem 0 0.75rem; }
label.chk { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; margin-right: 0.75rem; }
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>监管文件上传测试</h1>
<nav class="nav" id="main-nav"></nav>
</header>
<p class="muted-note">§2.3.3 生产 token<strong>scope=仅 bucket 名</strong>。上传为 PDF 二进制体(<code>Content-Type: application/pdf</code>),不用 multipart。403 且 body 为 <code>404</code> 表示 token 验签失败。</p>
<div class="toolbar">
<label>PDF 文件 <input type="file" id="file" accept=".pdf,application/pdf"></label>
<button class="primary" id="upload">上传</button>
<a href="/config">互医配置</a>
<a href="/fileauth">上传凭证调试</a>
</div>
<div id="status-default" class="panel">
<h3>A. Runner 默认 tokenscope=仅 bucket</h3>
<p id="runner-status">加载中…</p>
</div>
<div id="status-upload" class="panel token-panel">
<h3>B. 本次上传凭证§2.3.3</h3>
<p id="upload-token-placeholder" class="muted-note">上传成功或失败后显示本次请求使用的 token 信息。</p>
<div id="upload-token-detail" style="display:none">
<p>filename: <code id="t-filename"></code> · scope: <code id="t-scope"></code> · algorithm: <code id="t-algo"></code> · deadline: <code id="t-deadline"></code></p>
<h4>signInputHMAC 输入 = encodedPutPolicy</h4>
<pre id="t-sign-input"></pre>
<h4>putPolicy JSON</h4>
<pre id="t-policy"></pre>
<h4>encodedPutPolicy</h4>
<pre id="t-encoded-policy"></pre>
<h4>encodedSign</h4>
<pre id="t-encoded-sign"></pre>
<h4>uploadToken</h4>
<pre id="t-upload-token"></pre>
<label class="chk"><input type="checkbox" id="show-full"> 显示完整 uploadToken</label>
<button type="button" id="btn-copy">复制完整 token</button>
</div>
</div>
<div class="panel">
<h3>上传结果</h3>
<pre id="result" style="min-height:3rem"></pre>
</div>
</div>
<script src="/static/app.js"></script>
<script>
if (typeof renderMainNav === 'function') renderMainNav('upload');
let lastTokenInfo = null;
async function showRunnerStatus() {
try {
const v = await api('/api/config/view');
const eff = v.effective || {};
const el = document.getElementById('runner-status');
el.innerHTML = `uploadToken: ${eff.uploadTokenSet ? '<span class="badge ok">有效</span> ' + esc(eff.uploadTokenPreview) + ' <span class="badge info">' + esc(eff.uploadTokenSource || 'local-ver233-upload') + '</span>' : '<span class="badge err">空</span>'}
· fileBucket: <code>${esc(eff.fileBucket || '-')}</code>
· 上传地址: <code>${esc(eff.fileUploadURL)}</code>`;
} catch (e) {
showInlineError(document.getElementById('runner-status'), e.message);
}
}
function tokenDisplay(info) {
if (!info) return '—';
const show = document.getElementById('show-full').checked;
return show ? (info.uploadToken || '—') : (info.uploadTokenMasked || info.uploadToken || '—');
}
function renderUploadTokenInfo(data) {
const info = data && data.tokenInfo;
const ph = document.getElementById('upload-token-placeholder');
const detail = document.getElementById('upload-token-detail');
if (!info || !info.uploadToken) {
ph.style.display = '';
detail.style.display = 'none';
lastTokenInfo = null;
return;
}
lastTokenInfo = info;
ph.style.display = 'none';
detail.style.display = '';
document.getElementById('t-filename').textContent = data.filename || '—';
document.getElementById('t-scope').textContent = data.scope || '—';
document.getElementById('t-algo').textContent = info.algorithm || 'ver233';
document.getElementById('t-deadline').textContent = info.deadlineUnix != null ? String(info.deadlineUnix) : '—';
document.getElementById('t-sign-input').textContent = info.signInput || '—';
document.getElementById('t-policy').textContent = info.policyJson || '—';
document.getElementById('t-encoded-policy').textContent = info.encodedPutPolicy || '—';
document.getElementById('t-encoded-sign').textContent = info.encodedSign || '—';
document.getElementById('t-upload-token').textContent = tokenDisplay(info);
}
document.getElementById('show-full').onchange = () => {
if (lastTokenInfo) {
document.getElementById('t-upload-token').textContent = tokenDisplay(lastTokenInfo);
}
};
document.getElementById('btn-copy').onclick = async () => {
if (!lastTokenInfo || !lastTokenInfo.uploadToken) {
showToast('请先上传以生成 token', 'warn');
return;
}
try {
await navigator.clipboard.writeText(lastTokenInfo.uploadToken);
showToast('已复制完整 uploadToken', 'ok');
} catch {
showToast('复制失败', 'err');
}
};
document.getElementById('upload').onclick = async () => {
const input = document.getElementById('file');
const resultEl = document.getElementById('result');
const btn = document.getElementById('upload');
if (!input.files || !input.files[0]) {
showToast('请选择 PDF', 'warn');
return;
}
const fd = new FormData();
fd.append('file', input.files[0]);
btn.disabled = true;
resultEl.textContent = '上传中…';
try {
const r = await fetch('/api/upload/test', { method: 'POST', body: fd });
const j = await r.json().catch(() => ({}));
renderUploadTokenInfo(j);
if (!r.ok) {
let text = j.detail || j.error || r.statusText;
if (j.uploadUrl) text += '\n\nuploadUrl: ' + j.uploadUrl;
resultEl.textContent = text;
showToast(firstLine(j.error || text), 'err');
return;
}
resultEl.textContent = JSON.stringify(j, null, 2);
showToast('上传成功 fileId=' + (j.fileId || ''), 'ok');
} catch (e) {
resultEl.textContent = e.message;
showToast(e.message, 'err');
} finally {
btn.disabled = false;
}
};
showRunnerStatus();
</script>
</body>
</html>