ggload / app /static /admin /pages /token.html
f2d90b38's picture
Upload 120 files
8cdca00 verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Grok2API - Token 管理</title>
<link rel="icon" href="/static/common/img/favicon/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/geist@1.0.0/dist/fonts/geist-sans/style.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/geist@1.0.0/dist/fonts/geist-mono/style.css" rel="stylesheet">
<link href="/static/common/css/common.css?v=1.5.0" rel="stylesheet">
<link href="/static/admin/css/token.css?v=1.5.0" rel="stylesheet">
<link href="/static/common/css/toast.css" rel="stylesheet">
</head>
<body class="min-h-screen flex flex-col" style="background-color: var(--bg);">
<!-- Toast Container -->
<div id="toast-container" class="toast-container"></div>
<div id="app-header"></div>
<!-- Main Content -->
<main class="space-y-6 flex-1 container mx-auto max-w-5xl px-6 py-8 fade-in">
<div class="space-y-6">
<div class="flex justify-between items-center">
<div>
<h2 class="text-2xl font-semibold tracking-tight">Token 列表</h2>
<p class="text-[var(--accents-4)] mt-1 text-sm">管理 Grok2API 的 Token 服务号池。</p>
</div>
<div class="flex items-center gap-3">
<button onclick="openImportModal()" class="geist-button-outline gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
导入
</button>
<button onclick="addToken()" class="geist-button gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 5v14M5 12h14" />
</svg>
添加
</button>
</div>
</div>
<div class="h-px bg-[var(--border)] my-6"></div>
<!-- Statistics -->
<div class="grid grid-cols-4 gap-4 mb-6" id="stats-container">
<!-- Row 1 -->
<div class="stat-card">
<div class="stat-value" id="stat-total">-</div>
<div class="stat-label">Token 总数</div>
</div>
<div class="stat-card">
<div class="stat-value text-green-600" id="stat-active">-</div>
<div class="stat-label">Token 正常</div>
</div>
<div class="stat-card">
<div class="stat-value text-orange-500" id="stat-cooling">-</div>
<div class="stat-label">Token 限流</div>
</div>
<div class="stat-card">
<div class="stat-value text-red-600" id="stat-invalid">-</div>
<div class="stat-label">Token 失效</div>
</div>
<!-- Row 2 -->
<div class="stat-card">
<div class="stat-value" id="stat-chat-quota">-</div>
<div class="stat-label">Chat 剩余</div>
</div>
<div class="stat-card">
<div class="stat-value text-blue-600" id="stat-image-quota">-</div>
<div class="stat-label">Image 剩余</div>
</div>
<div class="stat-card">
<div class="stat-value text-gray-400" id="stat-video-quota">无法统计</div>
<div class="stat-label">Video 剩余</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-total-calls">-</div>
<div class="stat-label">总调用次数</div>
</div>
</div>
<!-- Tab Filter (with ARIA) -->
<div class="flex items-center gap-4 mb-4 border-b border-[var(--border)] overflow-x-auto hide-scrollbar"
role="tablist" aria-label="Token 状态筛选" id="status-tabs">
<button class="tab-item active" role="tab" aria-selected="true"
data-filter="all" onclick="filterByStatus('all')">
全部 <span class="badge" id="tab-count-all">0</span>
</button>
<button class="tab-item" role="tab" aria-selected="false"
data-filter="active" onclick="filterByStatus('active')">
正常 <span class="badge badge-green" id="tab-count-active">0</span>
</button>
<button class="tab-item" role="tab" aria-selected="false"
data-filter="cooling" onclick="filterByStatus('cooling')">
限流 <span class="badge badge-orange" id="tab-count-cooling">0</span>
</button>
<button class="tab-item" role="tab" aria-selected="false"
data-filter="expired" onclick="filterByStatus('expired')">
失效 <span class="badge badge-red" id="tab-count-expired">0</span>
</button>
<span class="tab-sep"></span>
<button class="tab-item" role="tab" aria-selected="false"
data-filter="nsfw" onclick="filterByStatus('nsfw')">
已开 NSFW <span class="badge badge-purple" id="tab-count-nsfw">0</span>
</button>
<button class="tab-item" role="tab" aria-selected="false"
data-filter="no-nsfw" onclick="filterByStatus('no-nsfw')">
未开 NSFW <span class="badge badge-gray" id="tab-count-no-nsfw">0</span>
</button>
</div>
<!-- Token Table -->
<div class="rounded-lg overflow-hidden bg-white mb-4 overflow-x-auto">
<table class="geist-table min-w-[800px]">
<thead>
<tr>
<th class="w-10"><input type="checkbox" id="select-all" class="checkbox" onclick="toggleSelectAll()"></th>
<th class="w-48 text-left">Token</th>
<th class="w-24">类型</th>
<th class="w-24">状态</th>
<th class="w-20">额度</th>
<th class="text-left">备注</th>
<th class="w-32 text-center">操作</th>
</tr>
</thead>
<tbody id="token-table-body">
<!-- Dynamic Rows -->
</tbody>
</table>
<div id="loading" class="text-center py-12 text-[var(--accents-4)]">加载中...</div>
<div id="empty-state" class="hidden table-empty">
暂无 Token,请点击右上角导入或添加。
</div>
</div>
<!-- Pagination -->
<div class="pagination-bar">
<div id="pagination-info" class="text-xs text-[var(--accents-5)]">第 0 / 0 页 · 共 0 条</div>
<div class="flex items-center gap-2">
<button class="geist-button-outline text-xs px-3" onclick="selectVisibleAll()">全选本页</button>
<button class="geist-button-outline text-xs px-3" onclick="selectAllFiltered()">全选全部</button>
<button class="geist-button-outline text-xs px-3" onclick="clearAllSelection()">取消全选</button>
<button id="page-prev" class="geist-button-outline text-xs px-3" onclick="goPrevPage()">上一页</button>
<button id="page-next" class="geist-button-outline text-xs px-3" onclick="goNextPage()">下一页</button>
<select id="page-size" class="geist-input h-[30px]" onchange="changePageSize()">
<option value="20">20 / 页</option>
<option value="50" selected>50 / 页</option>
<option value="100">100 / 页</option>
<option value="200">200 / 页</option>
</select>
</div>
</div>
<!-- Batch Actions Bar (Floating Bottom) -->
</div>
</main>
<div id="app-footer"></div>
<!-- Batch Actions Bar (Floating Bottom) -->
<div id="batch-actions"
class="fixed bottom-8 left-1/2 -translate-x-1/2 z-20 bg-white border border-[var(--border)] rounded-full px-3 py-2 flex items-center shadow-lg gap-3 cursor-move select-none active:cursor-grabbing whitespace-nowrap">
<div class="text-sm font-medium flex items-center gap-2">
<span class="text-[var(--accents-5)] text-xs">已选择</span>
<span class="bg-black text-white text-xs px-1.5 py-0.5 rounded-full" id="selected-count">0</span>
<span class="text-[var(--accents-5)] text-xs"></span>
</div>
<span class="toolbar-sep"></span>
<div class="flex items-center gap-1">
<button id="btn-batch-export" onclick="batchExport()"
class="geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100">
导出
</button>
<button id="btn-batch-update" onclick="batchUpdate()"
class="geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100 justify-center">
刷新
</button>
<button id="btn-batch-nsfw" onclick="batchEnableNSFW()"
class="geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100 justify-center" disabled>
NSFW
</button>
<button id="btn-batch-delete" onclick="batchDelete()" class="geist-button-danger text-xs px-3">
删除
</button>
</div>
<div id="batch-progress" class="hidden text-xs text-[var(--accents-5)] flex items-center gap-2">
<span class="toolbar-sep"></span>
<span id="batch-progress-text"></span>
<button id="btn-pause-action" type="button" class="batch-link hidden" onclick="toggleBatchPause()">暂停</button>
<button id="btn-stop-action" type="button" class="batch-link hidden" onclick="stopBatchRefresh()">终止</button>
</div>
</div>
<!-- Import Modal -->
<div id="import-modal" class="modal-overlay hidden">
<div class="modal-content modal-lg" id="import-modal-content">
<div class="modal-header">
<h3 class="modal-title">批量导入 Token</h3>
<button onclick="closeImportModal()" class="modal-close">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="space-y-4">
<div>
<label class="modal-label mb-1 block">目标 Pool</label>
<select id="import-pool" class="geist-input">
<option value="ssoBasic">ssoBasic</option>
<option value="ssoSuper">ssoSuper</option>
</select>
</div>
<div>
<label class="modal-label mb-1 block">Token 列表 (每行一个)</label>
<textarea id="import-text" class="geist-input font-mono h-48"
placeholder="粘贴 Token,一行一个..."></textarea>
</div>
<div class="flex justify-end gap-2 pt-2">
<button onclick="closeImportModal()" class="geist-button-outline text-xs px-3">取消</button>
<button onclick="submitImport()" class="geist-button text-xs px-3">开始导入</button>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div id="edit-modal" class="modal-overlay hidden">
<div class="modal-content modal-md" id="edit-modal-content">
<div class="modal-header">
<h3 class="modal-title">编辑 Token</h3>
<button onclick="closeEditModal()" class="modal-close">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="space-y-4">
<input type="hidden" id="edit-original-token">
<input type="hidden" id="edit-original-pool">
<div>
<label class="modal-label mb-1 block">Token</label>
<input type="text" id="edit-token-display" class="geist-input font-mono bg-gray-50 text-gray-500"
disabled>
</div>
<div>
<label class="modal-label mb-1 block">类型</label>
<select id="edit-pool" class="geist-input">
<option value="ssoBasic">ssoBasic</option>
<option value="ssoSuper">ssoSuper</option>
</select>
</div>
<div>
<label class="modal-label mb-1 block">额度</label>
<input type="number" id="edit-quota" class="geist-input" min="0">
</div>
<div>
<label class="modal-label mb-1 block">备注</label>
<input type="text" id="edit-note" class="geist-input" placeholder="可选备注" maxlength="50">
</div>
<div class="flex justify-end gap-2 pt-2">
<button onclick="closeEditModal()" class="geist-button-outline text-xs px-3">取消</button>
<button onclick="saveEdit()" class="geist-button text-xs px-3">保存</button>
</div>
</div>
</div>
</div>
<div id="confirm-dialog" class="modal-overlay confirm-dialog hidden">
<div class="modal-content modal-md">
<div class="confirm-dialog-body">
<div class="confirm-dialog-title">请确认</div>
<div id="confirm-message" class="confirm-dialog-message"></div>
<div class="confirm-dialog-actions">
<button id="confirm-cancel" type="button" class="geist-button-outline text-xs px-3">取消</button>
<button id="confirm-ok" type="button" class="geist-button-danger text-xs px-3">确定</button>
</div>
</div>
</div>
</div>
<script src="/static/common/js/draggable.js"></script>
<script src="/static/common/js/admin-auth.js?v=1.5.0"></script>
<script src="/static/common/js/batch-sse.js?v=1.5.0"></script>
<script src="/static/common/js/header.js?v=1.5.0"></script>
<script src="/static/common/js/footer.js?v=1.5.0"></script>
<script src="/static/common/js/toast.js"></script>
<script src="/static/admin/js/token.js?v=1.5.0"></script>
</body>
</html>