租户配额与计费:按用户月度 USD 上限 + 建任务/分支/重试准入 402 + 管理/自查端点 + 控制台展示

This commit is contained in:
host committed 2026-10-27 05:18:37 +08:00
1 parent 2a7cdd582f
commit 970b181bab
5 files changed
+496 -1

No files matched your search

+22 -1
View File
@@ -87,6 +87,7 @@
<button id="clearKey">清除</button>
<span id="keyState" class="muted"></span>
</div>
<div><span id="quotaState" class="muted">未加载</span></div>
</section>
<section class="card">
@@ -292,6 +293,7 @@ async function api(path, method, body) {
var msg = (data && data.error) ? data.error : ("HTTP " + r.status);
if (r.status === 401) msg += "(key 未填或无效)";
if (r.status === 429) msg += "(触发限流,稍后再试)";
if (r.status === 402) msg += "(本月配额已用完,联系管理员调整)";
throw new Error(msg);
}
return data;
@@ -324,6 +326,23 @@ async function loadHealth() {
} catch (e) { $("#health").textContent = "加载失败:" + e.message; }
}
async function loadQuota() {
// 本月配额与用量:只读展示,管理员改配额走 API(/admin/quotas)。
var el = $("#quotaState");
if (!getKey()) { el.textContent = "未填 key,配额不显示"; return; }
try {
var d = await api("/users/me/quota", "GET");
var used = (d.used_usd_micros / 1000000).toFixed(2);
if (d.quota_usd_micros === null || d.quota_usd_micros === undefined) {
el.textContent = "本月用量 $" + used + "(不限额)";
} else {
var q = (d.quota_usd_micros / 1000000).toFixed(2);
var rem = (d.remaining_usd_micros / 1000000).toFixed(2);
el.textContent = "本月用量 $" + used + " / $" + q + "(剩余 $" + rem + ")";
}
} catch (e) { el.textContent = "配额加载失败:" + e.message; }
}
async function loadProjects() {
try {
var d = await api("/projects", "GET");
@@ -610,6 +629,7 @@ $("#saveKey").onclick = function () {
loadWorkers();
if ($("#projectSelect").value) { loadTasks(); loadEnvironments(); }
if (getKey()) loadRelayModels();
loadQuota();
};
$("#clearKey").onclick = function () { localStorage.removeItem(KEY_STORE); setKeyState(); };
$("#refreshHealth").onclick = loadHealth;
@@ -789,11 +809,12 @@ setKeyState();
loadHealth();
loadProjects();
loadWorkers();
loadQuota();
// 回访用户 key 已在 localStorage 里:初始化也要加载模型列表,
// 否则下拉永远只有默认项(此前只在点“保存”时调一次,
// 2026-10-27 实测:存过 key 后重开页面模型选不了)。
if (getKey()) loadRelayModels();
setInterval(function () { if ($("#projectSelect").value) { loadTasks(); loadEnvironments(); } loadHealth(); }, 10000);
setInterval(function () { if ($("#projectSelect").value) { loadTasks(); loadEnvironments(); } loadHealth(); loadQuota(); }, 10000);
</script>
</body>
</html>