Files
xk-hy-transit-go/internal/logweb/web/fileauth.html

162 lines
6.4 KiB
HTML
Raw Normal View History

2026-05-28 16:39:51 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
2026-05-28 17:03:08 +08:00
<title>上传凭证 FileAuth</title>
2026-05-28 16:39:51 +08:00
<link rel="stylesheet" href="/static/static.css">
<style>
.step { margin-bottom: 1rem; }
.step h4 { margin: 0 0 0.35rem; color: var(--accent); font-size: 0.95rem; }
.step pre { margin: 0; max-height: 12rem; overflow: auto; }
.formula { color: var(--muted); font-size: 0.9rem; margin-bottom: 1rem; }
.check-ok { color: var(--ok); }
.check-fail { color: var(--err); }
label.chk { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; }
</style>
</head>
<body>
<div class="wrap">
<header>
2026-05-28 17:03:08 +08:00
<h1>上传凭证(FileAuth.java)</h1>
2026-05-28 16:39:51 +08:00
<nav class="nav" id="main-nav"></nav>
</header>
<p class="formula">
2026-05-28 17:03:08 +08:00
与 <code>docs/FileAuth.java</code> 一致:
<code>sign = HMAC-SHA1(secret, policyJSON)</code> →
<code>uploadToken = AccessKey:encodedSign:policyB64</code>(policyB64 = urlsafe_base64(policyJSON))
2026-05-28 16:39:51 +08:00
</p>
<div class="toolbar">
<label>deadline(Unix 秒)<input type="text" id="deadline" placeholder="默认 now+3600" style="width:11rem"></label>
<label>bucket <input type="text" id="bucket" placeholder="Runner fileBucket" style="width:10rem"></label>
2026-05-28 17:03:08 +08:00
<button class="primary" id="btn-gen">生成</button>
2026-05-28 16:39:51 +08:00
<button id="btn-refresh">刷新 Runner 并对比</button>
<label class="chk"><input type="checkbox" id="show-full"> 显示完整 token</label>
<button type="button" id="btn-copy">复制完整 token</button>
</div>
<div id="err-box"></div>
2026-05-28 17:03:08 +08:00
<div class="panel" id="panel-upload">
<h3>生产 upload profile(scope=仅 bucket)</h3>
2026-05-28 16:39:51 +08:00
<div class="step">
2026-05-28 17:03:08 +08:00
<h4>1. putPolicy</h4>
2026-05-28 16:39:51 +08:00
<pre id="policy-json">—</pre>
</div>
<div class="step">
2026-05-28 17:03:08 +08:00
<h4>2. sign = HMAC-SHA1(secret, <strong>policyJSON</strong> UTF-8) → encodedSign</h4>
<p class="formula" style="margin:0 0 0.35rem">signInput:<code id="sign-input">—</code> · hex:<code id="sign-hex">—</code></p>
<pre id="encoded-sign">—</pre>
2026-05-28 16:39:51 +08:00
</div>
<div class="step">
2026-05-28 17:03:08 +08:00
<h4>3. policyB64 = urlsafe_base64(putPolicy)</h4>
<pre id="encoded-policy">—</pre>
2026-05-28 16:39:51 +08:00
</div>
<div class="step">
2026-05-28 17:03:08 +08:00
<h4>4. uploadToken = AccessKey + ':' + encodedSign + ':' + policyB64</h4>
2026-05-28 16:39:51 +08:00
<pre id="upload-token">—</pre>
</div>
2026-05-28 17:03:08 +08:00
<p>算法:<code id="algorithm">—</code> · B64:<code id="b64-mode">—</code> · AccessKey:<code id="access-key">—</code> · scope:<code id="scope-val">—</code> · deadline:<code id="deadline-val">—</code></p>
2026-05-28 16:39:51 +08:00
</div>
<div class="panel">
2026-05-28 17:03:08 +08:00
<h3>格式校验</h3>
2026-05-28 16:39:51 +08:00
<p id="format-summary">—</p>
<ul id="format-checks"></ul>
2026-05-28 17:03:08 +08:00
<p>与 Runner 当前 token:<span id="runner-match">—</span>(<code id="runner-preview">—</code>)</p>
2026-05-28 16:39:51 +08:00
</div>
</div>
<script src="/static/app.js"></script>
<script>
if (typeof renderMainNav === 'function') renderMainNav('fileauth');
let lastData = null;
function tokenDisplay(data) {
if (!data) return '—';
const show = document.getElementById('show-full').checked;
return show ? data.uploadToken : (data.uploadTokenMasked || data.uploadToken);
}
function renderChecks(checks) {
const ul = document.getElementById('format-checks');
ul.innerHTML = (checks || []).map(c => {
const cls = c.ok ? 'check-ok' : 'check-fail';
const mark = c.ok ? '✓' : '✗';
return `<li class="${cls}">${mark} <strong>${esc(c.id)}</strong> — ${esc(c.detail)}</li>`;
}).join('');
}
function render(data) {
lastData = data;
document.getElementById('err-box').innerHTML = '';
document.getElementById('policy-json').textContent = data.policyJson || '—';
document.getElementById('encoded-policy').textContent = data.encodedPutPolicy || '—';
document.getElementById('sign-input').textContent = data.signInput || '—';
document.getElementById('sign-hex').textContent = data.signHex || '—';
document.getElementById('encoded-sign').textContent = data.encodedSign || '—';
document.getElementById('upload-token').textContent = tokenDisplay(data);
document.getElementById('access-key').textContent = data.accessKey || '—';
document.getElementById('scope-val').textContent = data.scope || data.bucket || '—';
document.getElementById('b64-mode').textContent = data.b64Mode || '—';
document.getElementById('deadline-val').textContent = data.deadlineUnix != null ? String(data.deadlineUnix) : '—';
document.getElementById('algorithm').textContent = data.algorithm || '—';
const sum = document.getElementById('format-summary');
sum.innerHTML = data.formatOk
2026-05-28 17:03:08 +08:00
? '<span class="badge ok">格式符合 AccessKey:encodedSign:policyB64</span>'
2026-05-28 16:39:51 +08:00
: '<span class="badge err">格式校验未全部通过</span>';
renderChecks(data.formatChecks);
const matchEl = document.getElementById('runner-match');
if (data.runnerTokenMatch) {
matchEl.innerHTML = '<span class="badge ok">一致</span>';
} else {
matchEl.innerHTML = '<span class="badge warn">不一致</span>(自定义 deadline 或需重新拉取配置)';
}
document.getElementById('runner-preview').textContent = data.runnerTokenPreview || '—';
}
async function load(refresh) {
const errBox = document.getElementById('err-box');
errBox.innerHTML = '';
const params = new URLSearchParams();
const dl = document.getElementById('deadline').value.trim();
const bk = document.getElementById('bucket').value.trim();
if (dl) params.set('deadline', dl);
if (bk) params.set('bucket', bk);
if (refresh) params.set('refresh', '1');
try {
const data = await api('/api/fileauth/generate?' + params.toString());
render(data);
showToast('已生成', 'ok');
} catch (e) {
showInlineError(errBox, e.message || String(e));
showToast(firstLine(e.message) || '生成失败', 'err');
}
}
document.getElementById('btn-gen').onclick = () => load(false);
document.getElementById('btn-refresh').onclick = () => load(true);
document.getElementById('show-full').onchange = () => {
if (lastData) document.getElementById('upload-token').textContent = tokenDisplay(lastData);
};
document.getElementById('btn-copy').onclick = async () => {
if (!lastData || !lastData.uploadToken) {
showToast('请先生成 token', 'warn');
return;
}
try {
await navigator.clipboard.writeText(lastData.uploadToken);
showToast('已复制完整 uploadToken', 'ok');
} catch {
showToast('复制失败', 'err');
}
};
load(false);
</script>
</body>
</html>