| <!DOCTYPE html>
|
| <html lang="zh-CN">
|
| <head>
|
|
|
|
|
| <script>
|
|
|
| (function () {
|
| var KEY = "g2a_asset_reload_at";
|
| function reloadedRecently() {
|
| try { return (Date.now() - Number(sessionStorage.getItem(KEY) || 0)) < 60000; } catch (e) { return false; }
|
| }
|
| function markReload() {
|
| try { sessionStorage.setItem(KEY, String(Date.now())); } catch (e) {}
|
| }
|
| window.__g2aScriptFailed = function (el) {
|
| console.error("static script failed", el && el.src);
|
| if (!reloadedRecently()) {
|
| markReload();
|
| var u = new URL(location.href);
|
| u.searchParams.set("_g2a_cb", String(Date.now()));
|
| location.replace(u.pathname + u.search);
|
| return;
|
| }
|
| var d = document.getElementById("boot-desc") || document.getElementById("boot-hint");
|
| if (d) d.textContent = "静态资源加载失败,请点击重试";
|
| };
|
| window.__g2aBootLogin = function () {
|
| function show(msg) {
|
| try {
|
| var boot = document.getElementById("boot-view");
|
| var auth = document.getElementById("auth-view");
|
| if (boot) boot.classList.remove("hidden");
|
| if (auth) auth.classList.add("hidden");
|
| var d = document.getElementById("boot-desc");
|
| var h = document.getElementById("boot-hint");
|
| if (d) d.textContent = "页面脚本未就绪";
|
| if (h) h.innerHTML = (msg || "") +
|
| '<br><button type="button" class="g2a-btn g2a-btn-primary" style="margin-top:12px" id="g2a-reload-btn">重试</button>';
|
| var b = document.getElementById("g2a-reload-btn");
|
| if (b) b.onclick = function () {
|
| try { sessionStorage.removeItem(KEY); } catch (e) {}
|
| location.replace(location.pathname + "?_g2a_cb=" + Date.now());
|
| };
|
| } catch (e) {}
|
| }
|
|
|
| var tries = 0;
|
| function attempt() {
|
| tries += 1;
|
| if (window.G2A && typeof G2A.api === "function" && G2A.auth && typeof G2A.auth.initLoginPage === "function") {
|
| try { G2A.auth.initLoginPage(); } catch (e) { show(String(e && e.message || e)); }
|
| return;
|
| }
|
| if (tries < 20) { setTimeout(attempt, 50); return; }
|
| show("登录脚本加载不完整。点击重试。");
|
| }
|
| attempt();
|
| };
|
| })();
|
| </script>
|
|
|
|
|
|
|
| <script>
|
|
|
| (function () {
|
| try {
|
| var t = localStorage.getItem("g2a_theme");
|
| if (t !== "light" && t !== "dark") {
|
| t = (window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches) ? "light" : "dark";
|
| }
|
| document.documentElement.setAttribute("data-theme", t);
|
| document.documentElement.style.colorScheme = t;
|
| document.documentElement.style.background = t === "light" ? "#f5f7fb" : "#0a0a0f";
|
| var tok = localStorage.getItem("g2a_admin_token") || "";
|
| var ts = Number(localStorage.getItem("g2a_admin_token_ts") || 0) || 0;
|
| var grace = tok && ts && (Date.now() - ts) < 7 * 24 * 3600 * 1000;
|
| if (tok || grace) document.documentElement.classList.add("g2a-has-session");
|
| } catch (e) {}
|
|
|
| window.addEventListener("pageshow", function () {
|
| try { document.body && document.body.classList.remove("g2a-softnav-busy"); } catch (_) {}
|
| });
|
|
|
| setTimeout(function () {
|
| try { document.body && document.body.classList.remove("g2a-softnav-busy"); } catch (_) {}
|
| }, 12000);
|
| })();
|
| </script>
|
|
|
|
|
| <meta charset="utf-8" />
|
| <meta name="viewport" content="width=device-width, initial-scale=1" />
|
| <title>用量 · grokcli-2api</title>
|
| <link rel="preconnect" href="https://fonts.googleapis.com" />
|
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
| <link rel="stylesheet" href="/static/dist/admin-antd.1031c5bb2f.css" />
|
| <link rel="icon" href="/static/favicon.ico" type="image/x-icon" />
|
|
|
| </head>
|
| <body class="g2a-body" data-page="usage">
|
| <div id="toast" class="g2a-message"></div>
|
| <div class="g2a-layout" id="main-view">
|
| <aside class="g2a-sider">
|
| <div class="g2a-brand">
|
| <div class="g2a-logo">G</div>
|
| <div>
|
| <div class="g2a-brand-title">grokcli-2api</div>
|
| <div class="g2a-brand-sub"><span id="app-version">…</span> · Admin</div>
|
| </div>
|
| </div>
|
| <nav class="g2a-menu">
|
| <a class="g2a-menu-item " href="/admin" data-page="overview"><span class="g2a-menu-ico">◫</span><span>概览</span></a>
|
| <a class="g2a-menu-item " href="/admin/keys" data-page="keys"><span class="g2a-menu-ico">🔑</span><span>API Keys</span></a>
|
| <a class="g2a-menu-item " href="/admin/accounts" data-page="accounts"><span class="g2a-menu-ico">👥</span><span>账号 / 轮询</span></a>
|
| <a class="g2a-menu-item is-active" href="/admin/usage" data-page="usage"><span class="g2a-menu-ico">Σ</span><span>用量</span></a>
|
| <a class="g2a-menu-item " href="/admin/logs" data-page="logs"><span class="g2a-menu-ico">📜</span><span>任务日志</span></a>
|
| <a class="g2a-menu-item " href="/admin/models" data-page="models"><span class="g2a-menu-ico">▣</span><span>模型</span></a>
|
| <a class="g2a-menu-item " href="/admin/settings" data-page="settings"><span class="g2a-menu-ico">⚙</span><span>系统设置</span></a>
|
| <a class="g2a-menu-item " href="/admin/guide" data-page="guide"><span class="g2a-menu-ico">📘</span><span>接入指南</span></a>
|
| </nav>
|
| <div class="g2a-sider-foot">
|
| <a href="/docs" target="_blank" rel="noopener">Swagger</a>
|
| <a href="/health" target="_blank" rel="noopener">Health</a>
|
| <a href="/metrics" target="_blank" rel="noopener">Metrics</a>
|
| <button type="button" class="g2a-btn g2a-btn-default g2a-btn-block" id="btn-logout">退出登录</button>
|
| </div>
|
| </aside>
|
| <div class="g2a-main">
|
| <header class="g2a-header">
|
| <div>
|
| <h1 id="page-title">用量</h1>
|
| <p id="page-sub">Token 消耗与请求使用情况(今日 / 近 N 天 / 累计)</p>
|
| </div>
|
| <div class="g2a-header-actions">
|
| <span id="status-pill" class="g2a-tag">—</span>
|
| <button type="button" class="g2a-theme-toggle" data-theme-toggle aria-label="切换主题"><span class="ico">☀️</span><span class="label">白天</span></button>
|
| <button type="button" class="g2a-btn g2a-btn-default" id="btn-refresh">刷新</button>
|
| </div>
|
| </header>
|
| <div class="g2a-mobile-nav" id="mobile-nav"></div>
|
| <div class="g2a-content">
|
|
|
| <section class="g2a-card">
|
| <div class="g2a-card-head">
|
| <div>
|
| <h3>用量概览</h3>
|
| <p>代理侧统计:成功请求的输入 / 输出 token;失败只计请求数。数据来自 Redis 热桶 + PostgreSQL 日汇总。缓存命中率来自请求明细(cache_read / prompt)。</p>
|
| </div>
|
| <div class="g2a-actions">
|
| <div class="g2a-field" style="min-width:120px;margin:0">
|
| <label for="usage-days">窗口</label>
|
| <select id="usage-days" class="g2a-input">
|
| <option value="1">今日</option>
|
| <option value="7" selected>近 7 天</option>
|
| <option value="14">近 14 天</option>
|
| <option value="30">近 30 天</option>
|
| </select>
|
| </div>
|
| <button class="g2a-btn g2a-btn-primary" id="btn-usage-reload" style="align-self:end">刷新</button>
|
| </div>
|
| </div>
|
| <div id="usage-stats-grid" class="g2a-stat-grid"></div>
|
| <div id="usage-source" class="g2a-muted" style="margin-top:8px">—</div>
|
| </section>
|
|
|
| <section class="g2a-card">
|
| <div class="g2a-card-head">
|
| <div>
|
| <h3>按日趋势</h3>
|
| <p>每日 total tokens(柱高)与请求数。</p>
|
| </div>
|
| </div>
|
| <div id="usage-series" class="g2a-usage-bars" style="min-height:120px">—</div>
|
| </section>
|
|
|
| <div class="g2a-grid-2">
|
| <section class="g2a-card">
|
| <div class="g2a-card-head">
|
| <div>
|
| <h3>按 API Key</h3>
|
| <p>窗口内 Token 消耗 Top。</p>
|
| </div>
|
| </div>
|
| <div class="g2a-table-wrap">
|
| <table class="g2a-table">
|
| <thead>
|
| <tr>
|
| <th>Key</th>
|
| <th style="width:90px">请求</th>
|
| <th style="width:110px">Tokens</th>
|
| <th style="width:80px">成功率</th>
|
| </tr>
|
| </thead>
|
| <tbody id="usage-by-key-tbody"><tr><td colspan="4" class="g2a-muted">加载中…</td></tr></tbody>
|
| </table>
|
| </div>
|
| </section>
|
| <section class="g2a-card">
|
| <div class="g2a-card-head">
|
| <div>
|
| <h3>按模型</h3>
|
| <p>窗口内模型消耗 Top。</p>
|
| </div>
|
| </div>
|
| <div class="g2a-table-wrap">
|
| <table class="g2a-table">
|
| <thead>
|
| <tr>
|
| <th>模型</th>
|
| <th style="width:90px">请求</th>
|
| <th style="width:110px">Tokens</th>
|
| <th style="width:80px">成功率</th>
|
| </tr>
|
| </thead>
|
| <tbody id="usage-by-model-tbody"><tr><td colspan="4" class="g2a-muted">加载中…</td></tr></tbody>
|
| </table>
|
| </div>
|
| </section>
|
| </div>
|
|
|
| <section class="g2a-card">
|
| <div class="g2a-card-head">
|
| <div>
|
| <h3>按上游账号</h3>
|
| <p>窗口内 Grok 账号消耗 Top(account_id)。</p>
|
| </div>
|
| </div>
|
| <div class="g2a-table-wrap">
|
| <table class="g2a-table">
|
| <thead>
|
| <tr>
|
| <th>账号</th>
|
| <th style="width:90px">请求</th>
|
| <th style="width:90px">成功</th>
|
| <th style="width:90px">失败</th>
|
| <th style="width:120px">Tokens</th>
|
| <th style="width:90px">成功率</th>
|
| </tr>
|
| </thead>
|
| <tbody id="usage-by-account-tbody"><tr><td colspan="6" class="g2a-muted">加载中…</td></tr></tbody>
|
| </table>
|
| </div>
|
| </section>
|
|
|
| <section class="g2a-card">
|
| <div class="g2a-card-head">
|
| <div>
|
| <h3>使用明细</h3>
|
| <p>每条 API 请求的 Token 明细、首字/完成耗时、调用的 API Key、客户端 IP 与缓存命中(prompt cache)。</p>
|
| </div>
|
| <div class="g2a-actions">
|
| <button class="g2a-btn g2a-btn-default" id="btn-usage-events-reload">刷新明细</button>
|
| </div>
|
| </div>
|
| <div class="g2a-form-row" style="gap:10px;flex-wrap:wrap;margin-bottom:12px">
|
| <div class="g2a-field" style="min-width:220px;flex:1">
|
| <label for="usage-events-q">关键词</label>
|
| <input id="usage-events-q" class="g2a-input" placeholder="Key / 账号 / 模型 / 路径 / IP" />
|
| </div>
|
| <div class="g2a-field" style="min-width:140px">
|
| <label for="usage-events-protocol">协议</label>
|
| <select id="usage-events-protocol" class="g2a-input">
|
| <option value="all">全部</option>
|
| <option value="openai">openai</option>
|
| <option value="anthropic">anthropic</option>
|
| <option value="openai_responses">openai_responses</option>
|
| </select>
|
| </div>
|
| <div class="g2a-field" style="min-width:120px">
|
| <label for="usage-events-ok">结果</label>
|
| <select id="usage-events-ok" class="g2a-input">
|
| <option value="all">全部</option>
|
| <option value="true">成功</option>
|
| <option value="false">失败</option>
|
| </select>
|
| </div>
|
| <div class="g2a-field" style="min-width:120px">
|
| <label for="usage-events-page-size">每页</label>
|
| <select id="usage-events-page-size" class="g2a-input">
|
| <option value="20">20</option>
|
| <option value="50" selected>50</option>
|
| <option value="100">100</option>
|
| </select>
|
| </div>
|
| <div class="g2a-field" style="align-self:end">
|
| <button class="g2a-btn g2a-btn-primary" id="btn-usage-events-search">查询</button>
|
| </div>
|
| </div>
|
| <div id="usage-events-info" class="g2a-muted" style="margin-bottom:8px">—</div>
|
| <div class="g2a-table-wrap">
|
| <table class="g2a-table">
|
| <thead>
|
| <tr>
|
| <th style="width:150px">时间</th>
|
| <th style="width:120px">协议/路径</th>
|
| <th style="width:140px">API Key</th>
|
| <th style="width:110px">IP</th>
|
| <th>模型</th>
|
| <th style="width:100px">输入 token</th>
|
| <th style="width:100px">输出 token</th>
|
| <th style="width:100px">合计 token</th>
|
| <th style="width:100px">缓存 token</th>
|
| <th style="width:100px">推理 token</th>
|
| <th style="width:100px" title="reasoning_effort / thinking · low · medium · high · xhigh">思考强度</th>
|
| <th style="width:90px">首字</th>
|
| <th style="width:90px">完成</th>
|
| <th style="width:70px">结果</th>
|
| </tr>
|
| </thead>
|
| <tbody id="usage-events-tbody"><tr><td colspan="14" class="g2a-muted">加载中…</td></tr></tbody>
|
| </table>
|
| </div>
|
| <div class="g2a-actions" style="justify-content:space-between;margin-top:12px">
|
| <div id="usage-events-page-info" class="g2a-muted">—</div>
|
| <div class="g2a-actions">
|
| <button class="g2a-btn g2a-btn-default" id="usage-events-page-prev">上一页</button>
|
| <button class="g2a-btn g2a-btn-default" id="usage-events-page-next">下一页</button>
|
| </div>
|
| </div>
|
| <pre id="usage-events-detail" class="g2a-log is-empty hidden" style="margin-top:12px" hidden>—</pre>
|
| </section>
|
|
|
| </div>
|
| </div>
|
| </div>
|
| <script src="/static/dist/utils.360f09cd20.js" onerror="window.__g2aScriptFailed&&__g2aScriptFailed(this)"></script>
|
| <script src="/static/dist/api.c4edde3d16.js" onerror="window.__g2aScriptFailed&&__g2aScriptFailed(this)"></script>
|
| <script src="/static/dist/state.da9974e23b.js" onerror="window.__g2aScriptFailed&&__g2aScriptFailed(this)"></script>
|
| <script src="/static/dist/auth.fa7b1e0e4d.js" onerror="window.__g2aScriptFailed&&__g2aScriptFailed(this)"></script>
|
| <script src="/static/dist/core.63c43151f2.js" onerror="window.__g2aScriptFailed&&__g2aScriptFailed(this)"></script>
|
| </body>
|
| </html>
|
|
|