165 lines
6.3 KiB
HTML
165 lines
6.3 KiB
HTML
<!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 默认 token(scope=仅 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>signInput(HMAC 输入 = 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>
|