soraapi / src /static /admin /index.html
anycallzhf's picture
Initial commit for Hugging Face Space deployment
b064311
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sora API - 管理控制台</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="/static/admin/css/style.css">
</head>
<body>
<!-- 登录页面 -->
<div id="login-container" style="display: flex;" class="login-container">
<div class="card shadow-lg">
<div class="card-header bg-primary text-white text-center py-3">
<h4>Sora API 管理控制台</h4>
</div>
<div class="card-body p-4">
<div class="text-center mb-4">
<i class="bi bi-shield-lock" style="font-size: 3rem;"></i>
<h5 class="mt-2">管理员登录</h5>
</div>
<form id="login-form">
<div class="mb-3">
<label for="admin-key-input" class="form-label">管理员密钥</label>
<input type="password" class="form-control" id="admin-key-input" required>
<div class="form-text">请输入管理员密钥以访问管理控制台</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">登录</button>
</div>
</form>
<div id="login-error" class="alert alert-danger mt-3" style="display: none;"></div>
</div>
</div>
</div>
<!-- 管理控制台 -->
<div id="admin-panel" style="display: none;" class="wrapper">
<!-- 侧边栏导航 -->
<nav id="sidebar">
<div class="sidebar-header d-flex align-items-center">
<h3 class="mb-0">Sora API</h3>
</div>
<ul class="list-unstyled components">
<li class="active">
<a href="#" data-page="dashboard"><i class="bi bi-speedometer2 me-2"></i>仪表盘</a>
</li>
<li>
<a href="#" data-page="keys"><i class="bi bi-key-fill me-2"></i>API密钥管理</a>
</li>
<li>
<a href="#" data-page="stats"><i class="bi bi-graph-up me-2"></i>使用统计</a>
</li>
<li>
<a href="#" data-page="settings"><i class="bi bi-gear-fill me-2"></i>系统设置</a>
</li>
<li class="sidebar-footer">
<a href="#" id="logout-btn"><i class="bi bi-box-arrow-right me-2"></i>退出登录</a>
</li>
</ul>
</nav>
<!-- 页面内容区 -->
<div id="content">
<!-- 顶部导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light shadow-sm mb-4">
<div class="container-fluid">
<button type="button" id="sidebarCollapse" class="btn btn-primary">
<i class="bi bi-list"></i>
</button>
<div class="ms-3 d-none d-md-block">
<h5 class="mb-0" id="current-page-title">仪表盘</h5>
</div>
<div class="ms-auto d-flex align-items-center">
<span class="badge bg-primary me-3" id="admin-key-display"></span>
<span class="text-muted d-none d-md-block">欢迎回来,管理员</span>
</div>
</div>
</nav>
<!-- 仪表盘页面 -->
<div id="dashboard-page" class="content-page active container-fluid">
<div class="row mb-4">
<div class="col-12">
<h4 class="mb-4">系统概览</h4>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-primary text-white h-100">
<div class="card-body">
<h5 class="card-title">总API密钥</h5>
<h3 id="total-keys" class="card-text">--</h3>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-success text-white h-100">
<div class="card-body">
<h5 class="card-title">激活密钥</h5>
<h3 id="active-keys" class="card-text">--</h3>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-danger text-white h-100">
<div class="card-body">
<h5 class="card-title">禁用密钥</h5>
<h3 id="disabled-keys" class="card-text">--</h3>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-info text-white h-100">
<div class="card-body">
<h5 class="card-title">今日请求</h5>
<h3 id="today-requests" class="card-text">--</h3>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="card-title mb-0">近期请求趋势</h5>
</div>
<div class="card-body">
<canvas id="dashboard-requests-chart"></canvas>
</div>
</div>
</div>
<div class="col-lg-4 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="card-title mb-0">最新API密钥</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush" id="recent-keys-list">
<!-- JS动态加载 -->
</div>
</div>
<div class="card-footer text-end">
<a href="#" class="btn btn-sm btn-primary" data-page="keys">查看全部</a>
</div>
</div>
</div>
</div>
</div>
<!-- API密钥管理页面 -->
<div id="keys-page" class="content-page container-fluid">
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between flex-wrap mb-3">
<div class="mb-2">
<button id="add-key-btn" class="btn btn-primary me-2">
<i class="bi bi-plus-circle me-1"></i> 添加密钥
</button>
<div class="btn-group">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
批量操作
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item batch-action" data-action="enable" href="#">启用选中</a></li>
<li><a class="dropdown-item batch-action" data-action="disable" href="#">禁用选中</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item batch-action text-danger" data-action="delete" href="#">删除选中</a></li>
</ul>
</div>
<button id="import-keys-btn" class="btn btn-outline-primary ms-2">
<i class="bi bi-upload me-1"></i> 批量导入
</button>
</div>
<div class="mb-2">
<input type="text" id="search-keys" class="form-control" placeholder="搜索密钥...">
</div>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>名称</th>
<th>密钥值</th>
<th>状态</th>
<th>权重</th>
<th>速率限制</th>
<th>创建时间</th>
<th>最后使用</th>
<th>操作</th>
</tr>
</thead>
<tbody id="keys-table-body">
<!-- JS动态加载 -->
</tbody>
</table>
</div>
<div id="pagination" class="d-flex justify-content-center mt-4">
<!-- JS动态加载 -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 使用统计页面 -->
<div id="stats-page" class="content-page container-fluid">
<div class="row">
<div class="col-12">
<h4 class="mb-4">使用统计</h4>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-primary text-white">
<div class="card-body">
<h5 class="card-title">总请求次数</h5>
<h3 id="total-requests" class="card-text">--</h3>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">成功请求</h5>
<h3 id="successful-requests" class="card-text">--</h3>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-danger text-white">
<div class="card-body">
<h5 class="card-title">失败请求</h5>
<h3 id="failed-requests" class="card-text">--</h3>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">成功率</h5>
<h3 id="success-rate" class="card-text">--</h3>
</div>
</div>
</div>
</div>
<div class="row">
<!-- 请求趋势图 -->
<div class="col-lg-8 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">请求趋势</h5>
</div>
<div class="card-body">
<canvas id="requests-chart"></canvas>
</div>
</div>
</div>
<!-- 密钥使用分布 -->
<div class="col-lg-4 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">密钥使用分布</h5>
</div>
<div class="card-body">
<canvas id="keys-usage-chart"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- 设置页面 -->
<div id="settings-page" class="content-page container-fluid">
<div class="row">
<div class="col-12">
<h4 class="mb-4">系统设置</h4>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">管理员密钥</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="admin-key" class="form-label">管理员密钥</label>
<div class="input-group">
<input type="password" class="form-control" id="admin-key" readonly>
<button class="btn btn-outline-secondary" type="button" id="show-admin-key">
<i class="bi bi-eye"></i>
</button>
<button class="btn btn-outline-secondary" type="button" id="copy-admin-key">
<i class="bi bi-clipboard"></i>
</button>
</div>
<div class="form-text">此密钥用于访问管理界面,请妥善保管</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">系统配置</h5>
</div>
<div class="card-body">
<form id="settings-form">
<div class="mb-3">
<label for="proxy-host" class="form-label">代理主机</label>
<input type="text" class="form-control" id="proxy-host">
</div>
<div class="mb-3">
<label for="proxy-port" class="form-label">代理端口</label>
<input type="text" class="form-control" id="proxy-port">
</div>
<div class="mb-3">
<label for="proxy-user" class="form-label">代理用户名</label>
<input type="text" class="form-control" id="proxy-user">
</div>
<div class="mb-3">
<label for="proxy-pass" class="form-label">代理密码</label>
<input type="password" class="form-control" id="proxy-pass">
<div class="form-text">如果代理服务器需要认证,请填写用户名和密码</div>
</div>
<hr>
<h6 class="mb-3">图片本地化设置</h6>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="image-localization">
<label class="form-check-label" for="image-localization">启用图片本地化</label>
<div class="form-text">启用后,Sora生成的图片将被下载并保存到本地服务器,避免客户端无法访问外部链接</div>
</div>
<div class="mb-3">
<label for="base-url" class="form-label">Base URL</label>
<input type="text" class="form-control" id="base-url" placeholder="http://example.com">
<div class="form-text">设置访问本地化图片的基础URL,例如:http://example.com 或 https://your-domain.com</div>
</div>
<div class="mb-3">
<label for="image-save-dir" class="form-label">图片保存目录</label>
<input type="text" class="form-control" id="image-save-dir" placeholder="src/static/images">
<div class="form-text">相对于工作目录的路径,必须确保目录存在且有写入权限</div>
</div>
<button type="submit" class="btn btn-primary">保存设置</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 添加/编辑密钥对话框 -->
<div class="modal fade" id="keyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="keyModalLabel">添加新密钥</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="key-form">
<input type="hidden" id="key-id">
<div class="mb-3">
<label for="key-name" class="form-label">密钥名称</label>
<input type="text" class="form-control" id="key-name" required>
</div>
<div class="mb-3">
<label for="key-value" class="form-label">密钥值 (Bearer Token)</label>
<input type="text" class="form-control" id="key-value" required>
<div class="form-text">输入OpenAI格式的API密钥,无需添加"Bearer "前缀,系统会自动处理</div>
</div>
<div class="mb-3">
<label for="key-weight" class="form-label">权重 (1-10)</label>
<input type="number" class="form-control" id="key-weight" min="1" max="10" value="1">
<div class="form-text">权重越高,被选中的概率越大</div>
</div>
<div class="mb-3">
<label for="key-rate-limit" class="form-label">速率限制 (每分钟请求数)</label>
<input type="number" class="form-control" id="key-rate-limit" min="1" value="60">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="key-enabled" checked>
<label class="form-check-label" for="key-enabled">启用</label>
</div>
<div class="mb-3">
<label for="key-notes" class="form-label">备注</label>
<textarea class="form-control" id="key-notes" rows="2"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="test-key-btn">测试连接</button>
<button type="button" class="btn btn-success" id="save-key-btn">保存</button>
</div>
</div>
</div>
</div>
<!-- 确认对话框 -->
<div class="modal fade" id="confirmModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmModalLabel">确认操作</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="confirm-message">
确定要执行此操作吗?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-danger" id="confirm-btn">确认</button>
</div>
</div>
</div>
</div>
<!-- 批量导入密钥对话框 -->
<div class="modal fade" id="importKeysModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">批量导入API密钥</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<ul class="nav nav-tabs" id="importTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="paste-tab" data-bs-toggle="tab" data-bs-target="#paste-content" type="button" role="tab">粘贴内容</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="file-tab" data-bs-toggle="tab" data-bs-target="#file-upload" type="button" role="tab">上传文件</button>
</li>
</ul>
<div class="tab-content mt-3" id="importTabsContent">
<div class="tab-pane fade show active" id="paste-content" role="tabpanel">
<div class="mb-3">
<label for="keys-text" class="form-label">API密钥列表</label>
<textarea class="form-control" id="keys-text" rows="10" placeholder="支持两种格式:&#10;1. 每行一个密钥&#10;2. 每行格式:[名称],[API密钥],[权重(可选)],[速率限制(可选)]&#10;&#10;例如:&#10;sk-abcdefg123456&#10;我的密钥2,sk-hijklmn789012,5,100"></textarea>
</div>
</div>
<div class="tab-pane fade" id="file-upload" role="tabpanel">
<div class="mb-3">
<label for="keys-file" class="form-label">选择文件</label>
<input class="form-control" type="file" id="keys-file" accept=".txt,.csv">
<div class="form-text">支持TXT或CSV文件,每行格式同上</div>
</div>
</div>
</div>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="auto-enable-keys" checked>
<label class="form-check-label" for="auto-enable-keys">
自动启用导入的密钥
</label>
</div>
<div class="alert alert-info mt-3">
<i class="bi bi-info-circle me-2"></i> 批量导入将跳过重复的密钥值。如果名称重复但密钥值不同,将作为新密钥添加。
</div>
<div id="import-preview" class="mt-3" style="display: none;">
<h6>导入预览 (<span id="preview-count">0</span>个密钥)</h6>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>名称</th>
<th>密钥</th>
<th>权重</th>
<th>速率限制</th>
</tr>
</thead>
<tbody id="preview-table-body">
</tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="preview-import-btn">预览</button>
<button type="button" class="btn btn-success" id="confirm-import-btn" disabled>导入</button>
</div>
</div>
</div>
</div>
<!-- Toast通知 -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 5">
<div id="toast-container"></div>
</div>
<!-- JavaScript库 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="/static/admin/js/main.js"></script>
</body>
</html>