fix: 修复监控链路 api_key 未解码致 401、瞬时 401 误禁、前端表单多处问题 - monitor: 调度链路对 base64 存储的 api_key 解码(此前直接发送编码串导致所有账号 401) - fetcher: 401/403 先按重试次数确认再判定禁用,避免瞬时 401 误杀;POST 自动补 Content-Type: application/json - ui: val/err 兼容 # 前缀(保存无反应的根因);平台编辑回填 headers 反转义;监控页补添加账号入口 - test: 新增 monitor 解码回归测试,共 51 个用例
This commit is contained in:
+45
-9
@@ -54,6 +54,22 @@
|
||||
return s;
|
||||
}
|
||||
|
||||
/* 容错 JSON 解析:允许用户写 Python 风格的单引号;失败返回 null */
|
||||
function parseJsonInput(text, label, errorId) {
|
||||
let s = (text || "").trim();
|
||||
if (!s) return {};
|
||||
try {
|
||||
return JSON.parse(s);
|
||||
} catch (_) {
|
||||
try {
|
||||
return JSON.parse(s.replace(/'/g, '"'));
|
||||
} catch (_2) {
|
||||
err(errorId, `${label} 不是合法 JSON:键和值必须用双引号,如 {"key": "value"}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 品牌图标映射(@lobehub/icons 键 → 品牌色) ---------- */
|
||||
|
||||
const BRAND_COLORS = {
|
||||
@@ -193,7 +209,15 @@
|
||||
document.getElementById("stat-pending").textContent = stats.pending;
|
||||
|
||||
grid.innerHTML = accounts.map(accountCard).join("");
|
||||
document.getElementById("empty-hint").classList.toggle("hidden", accounts.length > 0);
|
||||
const hint = document.getElementById("empty-hint");
|
||||
if (accounts.length > 0) {
|
||||
hint.classList.add("hidden");
|
||||
} else {
|
||||
hint.classList.remove("hidden");
|
||||
hint.innerHTML = platforms.length === 0
|
||||
? "<p>还没有平台。先到「平台」页添加一个平台,再回来添加账号。</p>"
|
||||
: "<p>还没有账号。点击右上角「+ 添加账号」开始监控。</p>";
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 渲染:平台 ---------- */
|
||||
@@ -266,7 +290,11 @@
|
||||
function platformForm(p) {
|
||||
const isEdit = !!p;
|
||||
const v = p || {};
|
||||
const headersStr = Object.keys(v.headers || {}).length ? JSON.stringify(v.headers, null, 2) : '{\n "Authorization": "Bearer {{apiKey}}"\n}';
|
||||
let savedHeaders = v.headers || {};
|
||||
if (typeof savedHeaders === "string") {
|
||||
try { savedHeaders = JSON.parse(savedHeaders); } catch (_) { savedHeaders = {}; }
|
||||
}
|
||||
const headersStr = Object.keys(savedHeaders).length ? JSON.stringify(savedHeaders, null, 2) : '{\n "Authorization": "Bearer {{apiKey}}"\n}';
|
||||
openModal(`
|
||||
<div class="modal">
|
||||
<h3>${isEdit ? "编辑平台" : "添加平台"}</h3>
|
||||
@@ -290,7 +318,7 @@
|
||||
<div class="form-row"><label>超时秒数(留空用全局 ${settings ? settings.timeout_seconds : 10})</label><input id="pf-timeout" type="number" min="1" max="120" value="${v.timeout_seconds ?? ""}"></div>
|
||||
<div class="form-row full"><label>备注</label><input id="pf-note" value="${escapeHtml(v.note || "")}"></div>
|
||||
</div>
|
||||
<p class="form-hint">提示:apikey 在 URL / Header / Body 中统一用 {{apiKey}} 占位,添加账号时自动替换。</p>
|
||||
<p class="form-hint">提示:apikey 在 URL / Header / Body 中统一用 {{apiKey}} 占位,添加账号时自动替换。Headers/Body 需为 JSON,键值用双引号(单引号会自动兼容)。</p>
|
||||
<p class="modal-error" id="pf-error"></p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn" data-cancel>取消</button>
|
||||
@@ -306,13 +334,13 @@
|
||||
interval_seconds: numOrNull("#pf-interval"), retry_count: numOrNull("#pf-retry"),
|
||||
timeout_seconds: numOrNull("#pf-timeout"), enabled: p ? p.enabled : true,
|
||||
};
|
||||
let headers = {};
|
||||
try { headers = JSON.parse(val("#pf-headers") || "{}"); }
|
||||
catch (e) { err("#pf-error", "Headers 不是合法 JSON"); return; }
|
||||
let headers = parseJsonInput(val("#pf-headers"), "Headers", "#pf-error");
|
||||
if (headers === null) return;
|
||||
payload.headers = headers;
|
||||
if (!payload.name || !payload.url || !payload.balance_path) { err("#pf-error", "名称 / URL / 提取路径必填"); return; }
|
||||
if (payload.method === "POST") {
|
||||
try { JSON.parse(val("#pf-body") || "{}"); } catch (e) { err("#pf-error", "Body 不是合法 JSON"); return; }
|
||||
const bodyObj = parseJsonInput(val("#pf-body"), "Body", "#pf-error");
|
||||
if (bodyObj === null) return;
|
||||
}
|
||||
payload.body = val("#pf-body");
|
||||
try {
|
||||
@@ -420,9 +448,9 @@
|
||||
|
||||
/* ---------- 事件 ---------- */
|
||||
|
||||
function val(id) { return document.getElementById(id).value.trim(); }
|
||||
function val(id) { return document.getElementById(String(id).replace(/^#/, "")).value.trim(); }
|
||||
function numOrNull(id) { const s = val(id); return s === "" ? null : Number(s); }
|
||||
function err(id, msg) { document.getElementById(id).textContent = msg; }
|
||||
function err(id, msg) { document.getElementById(String(id).replace(/^#/, "")).textContent = msg; }
|
||||
|
||||
function bindEvents() {
|
||||
document.getElementById("login-btn").onclick = doLogin;
|
||||
@@ -430,6 +458,14 @@
|
||||
document.getElementById("logout-btn").onclick = logout;
|
||||
document.querySelectorAll(".tab").forEach((t) => t.addEventListener("click", () => switchView(t.dataset.view)));
|
||||
document.getElementById("add-platform-btn").onclick = () => platformForm(null);
|
||||
document.getElementById("add-account-btn").onclick = () => {
|
||||
if (platforms.length === 0) {
|
||||
toast("请先在「平台」页添加平台", "err");
|
||||
switchView("platforms");
|
||||
return;
|
||||
}
|
||||
accountForm(null);
|
||||
};
|
||||
document.getElementById("save-settings-btn").onclick = saveSettings;
|
||||
document.getElementById("change-pwd-btn").onclick = changePassword;
|
||||
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
|
||||
<!-- 监控视图 -->
|
||||
<section id="view-monitor" class="view">
|
||||
<div class="view-head">
|
||||
<h2>账号监控</h2>
|
||||
<button id="add-account-btn" class="btn primary">+ 添加账号</button>
|
||||
</div>
|
||||
<div class="stats-bar">
|
||||
<div class="stat"><span id="stat-total">0</span><label>账号</label></div>
|
||||
<div class="stat ok"><span id="stat-ok">0</span><label>正常</label></div>
|
||||
|
||||
Reference in New Issue
Block a user