xiaoyukkkk's picture
Upload index.html
7712fa8 verified
{% extends "base.html" %}
{% from "components/alerts.html" import api_key_status, error_alert, no_accounts_alert %}
{% from "components/account_table.html" import account_table %}
{% block title %}系统管理 - Gemini Business API{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="/static/css/admin.css">
{% endblock %}
{% block content %}
<div class="container">
<div class="header">
<div class="header-info">
<h1>Gemini-Business2api</h1>
<div class="subtitle">多账户代理面板</div>
</div>
<div class="header-actions">
<a href="/public/uptime/html" class="btn" target="_blank">📊 状态监控</a>
<a href="/public/log/html" class="btn" target="_blank">📄 公开日志</a>
<a href="/{{ admin_path_segment }}/log/html" class="btn" target="_blank">🔧 管理日志</a>
<button class="btn" onclick="document.getElementById('fileInput').click()">📥 批量上传</button>
<input type="file" id="fileInput" accept=".json" multiple style="display:none" onchange="handleFileUpload(event)">
<button class="btn" onclick="showEditConfig()" id="edit-btn">✏️ 编辑配置</button>
</div>
</div>
<!-- Tabs Navigation -->
<div class="tabs-nav">
<button class="tab-button active" onclick="switchTab('accounts')">📋 账户管理</button>
<button class="tab-button" onclick="switchTab('api')">📚 API文档</button>
<button class="tab-button" onclick="switchTab('config')">⚙️ 系统配置</button>
<button class="tab-button" onclick="switchTab('settings')">🔧 系统设置</button>
</div>
<!-- Tab 1: 账户管理 -->
<div id="tab-accounts" class="tab-content active">
{{ api_key_status(has_api_key) }}
{{ error_alert(error_count, admin_path_segment) }}
{% if multi_account_mgr.accounts|length == 0 %}
{{ no_accounts_alert() }}
{% endif %}
<div class="alert alert-primary">
<div class="alert-icon">🔗</div>
<div class="alert-content">
<strong>API 接口</strong>
<div style="margin-top: 6px; color: #86868b; font-size: 11px;">根据客户端选择对应接口</div>
<div style="margin-top: 10px; display: grid; gap: 10px;">
<div class="api-item">
<span class="api-item-label">基础端点</span>
<div class="api-item-content" style="display: flex; gap: 8px; flex: 1;">
<code class="api-item-code">{{ api_base_url }}</code>
<button class="btn-copy" onclick="navigator.clipboard.writeText('{{ api_base_url }}').then(() => { this.innerHTML = '✅'; setTimeout(() => this.innerHTML = '📋', 2000); })">📋</button>
</div>
</div>
<div class="api-item">
<span class="api-item-label">SDK 接口</span>
<div class="api-item-content" style="display: flex; gap: 8px; flex: 1;">
<code class="api-item-code">{{ api_base_v1 }}</code>
<button class="btn-copy" onclick="navigator.clipboard.writeText('{{ api_base_v1 }}').then(() => { this.innerHTML = '✅'; setTimeout(() => this.innerHTML = '📋', 2000); })">📋</button>
</div>
</div>
<div class="api-item">
<span class="api-item-label">完整接口</span>
<div class="api-item-content" style="display: flex; gap: 8px; flex: 1;">
<code class="api-item-code">{{ api_endpoint }}</code>
<button class="btn-copy" onclick="navigator.clipboard.writeText('{{ api_endpoint }}').then(() => { this.innerHTML = '✅'; setTimeout(() => this.innerHTML = '📋', 2000); })">📋</button>
</div>
</div>
<div class="api-item">
<span class="api-item-label">API 密钥</span>
<div class="api-item-content" style="display: flex; gap: 8px; flex: 1;">
<code class="api-item-code">{% if main.API_KEY %}{{ main.API_KEY }}{% else %}<span style="color: #ff9500;">未设置</span>{% endif %}</code>
{% if main.API_KEY %}
<button class="btn-copy" onclick="navigator.clipboard.writeText('{{ main.API_KEY }}').then(() => { this.innerHTML = '✅'; setTimeout(() => this.innerHTML = '📋', 2000); })">📋</button>
{% endif %}
</div>
</div>
</div>
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0,0,0,0.06);">
<div style="font-size: 11px; color: #6b6b6b; margin-bottom: 6px;">支持的模型</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
<span style="background: #f0f0f2; color: #1d1d1f; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;">gemini-auto</span>
<span style="background: #f0f0f2; color: #1d1d1f; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;">gemini-2.5-flash</span>
<span style="background: #f0f0f2; color: #1d1d1f; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;">gemini-2.5-pro</span>
<span style="background: #f0f0f2; color: #1d1d1f; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;">gemini-3-flash-preview</span>
<span style="background: #f0f0f2; color: #1d1d1f; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;">gemini-3-pro-preview</span>
</div>
<div style="margin-top: 8px; font-size: 11px; color: #86868b;">
📸 图片生成可在"系统设置"自定义配置
</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">账户状态 ({{ multi_account_mgr.accounts|length }} 个)</div>
<div style="color: #6b6b6b; font-size: 12px; margin-bottom: 12px; padding-left: 4px;">
默认过期时间12小时,注意北京时间 • 批量上传使用 <code style="font-size: 11px; background: rgba(0,0,0,0.05); padding: 2px 6px; border-radius: 4px;">script/download-config.js</code> 油猴脚本
</div>
{{ account_table(accounts_data) }}
</div>
</div>
<!-- Tab 2: API文档 -->
<div id="tab-api" class="tab-content">
<div class="section">
<div class="section-title">API 端点列表</div>
<div class="current-url-row">
<span style="font-size:12px; font-weight:600; color:#0071e3; margin-right:8px;">当前页面:</span>
<code style="background:none; padding:0; color:#1d1d1f;">{{ current_url }}</code>
</div>
<table class="ep-table">
<tr>
<td width="70"><span class="method m-post">POST</span></td>
<td><span class="ep-path">/{{ api_path_segment }}v1/chat/completions</span></td>
<td><span class="ep-desc">OpenAI 兼容对话接口</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/{{ api_path_segment }}v1/models</span></td>
<td><span class="ep-desc">获取模型列表</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/{{ admin_path_segment }}</span></td>
<td><span class="ep-desc">管理首页 (需登录)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/{{ admin_path_segment }}/health</span></td>
<td><span class="ep-desc">健康检查 (需登录)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/{{ admin_path_segment }}/accounts</span></td>
<td><span class="ep-desc">账户状态 JSON (需登录)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/{{ admin_path_segment }}/log</span></td>
<td><span class="ep-desc">获取日志 JSON (需登录)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/{{ admin_path_segment }}/log/html</span></td>
<td><span class="ep-desc">日志查看器 HTML (需登录)</span></td>
</tr>
<tr>
<td><span class="method m-del">DEL</span></td>
<td><span class="ep-path">/{{ admin_path_segment }}/log?confirm=yes</span></td>
<td><span class="ep-desc">清空系统日志 (需登录)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/public/stats</span></td>
<td><span class="ep-desc">公开统计数据</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/public/log</span></td>
<td><span class="ep-desc">公开日志 (JSON, 脱敏)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/public/log/html</span></td>
<td><span class="ep-desc">公开日志查看器 (HTML)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/public/uptime</span></td>
<td><span class="ep-desc">实时状态监控 (JSON)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/public/uptime/html</span></td>
<td><span class="ep-desc">实时状态监控页面 (HTML)</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/docs</span></td>
<td><span class="ep-desc">Swagger API 文档</span></td>
</tr>
<tr>
<td><span class="method m-get">GET</span></td>
<td><span class="ep-path">/redoc</span></td>
<td><span class="ep-desc">ReDoc API 文档</span></td>
</tr>
</table>
</div>
</div>
<!-- Tab 3: 系统配置 -->
<div id="tab-config" class="tab-content">
<div class="section">
<div class="section-title">当前配置状态</div>
<div class="grid-env">
<div class="stack-col">
<div class="card">
<h3>环境变量 <span class="badge badge-required">ENV</span></h3>
<div style="margin-top: 12px;">
<div class="env-var">
<div><div class="env-name">ADMIN_KEY</div><div class="env-desc">管理员密钥</div></div>
<div class="env-value">已设置</div>
</div>
<div class="env-var">
<div><div class="env-name">PATH_PREFIX</div><div class="env-desc">API路径前缀</div></div>
<div class="env-value">{{ main.PATH_PREFIX or '未设置' }}</div>
</div>
</div>
</div>
<div class="card">
<h3>基础配置 <span class="badge badge-optional">YAML</span></h3>
<div style="margin-top: 12px;">
<div class="env-var">
<div><div class="env-name">API_KEY</div><div class="env-desc">API访问密钥</div></div>
<div class="env-value">{% if main.API_KEY %}已设置{% else %}未设置(公开访问){% endif %}</div>
</div>
<div class="env-var">
<div><div class="env-name">BASE_URL</div><div class="env-desc">服务器URL</div></div>
<div class="env-value">{% if main.BASE_URL %}已设置{% else %}自动检测{% endif %}</div>
</div>
<div class="env-var">
<div><div class="env-name">PROXY</div><div class="env-desc">代理地址</div></div>
<div class="env-value">{% if main.PROXY %}已设置{% else %}未设置{% endif %}</div>
</div>
</div>
</div>
</div>
<div class="card">
<h3>重试策略 <span class="badge badge-optional">YAML</span></h3>
<div style="margin-top: 12px;">
<div class="env-var">
<div><div class="env-name">max_new_session_tries</div><div class="env-desc">新会话尝试数</div></div>
<div class="env-value">{{ main.MAX_NEW_SESSION_TRIES }}</div>
</div>
<div class="env-var">
<div><div class="env-name">max_request_retries</div><div class="env-desc">请求重试次数</div></div>
<div class="env-value">{{ main.MAX_REQUEST_RETRIES }}</div>
</div>
<div class="env-var">
<div><div class="env-name">max_account_switch_tries</div><div class="env-desc">账户切换次数</div></div>
<div class="env-value">{{ main.MAX_ACCOUNT_SWITCH_TRIES }}</div>
</div>
<div class="env-var">
<div><div class="env-name">account_failure_threshold</div><div class="env-desc">失败阈值</div></div>
<div class="env-value">{{ main.ACCOUNT_FAILURE_THRESHOLD }} 次</div>
</div>
<div class="env-var">
<div><div class="env-name">rate_limit_cooldown_seconds</div><div class="env-desc">429冷却时间</div></div>
<div class="env-value">{{ main.RATE_LIMIT_COOLDOWN_SECONDS }} 秒</div>
</div>
<div class="env-var">
<div><div class="env-name">session_cache_ttl_seconds</div><div class="env-desc">会话缓存时间</div></div>
<div class="env-value">{{ main.SESSION_CACHE_TTL_SECONDS }} 秒</div>
</div>
</div>
</div>
<div class="card">
<h3>公开展示 <span class="badge badge-optional">YAML</span></h3>
<div style="margin-top: 12px;">
<div class="env-var">
<div><div class="env-name">LOGO_URL</div><div class="env-desc">Logo图片</div></div>
<div class="env-value">{% if main.LOGO_URL %}已设置{% else %}未设置{% endif %}</div>
</div>
<div class="env-var">
<div><div class="env-name">CHAT_URL</div><div class="env-desc">对话链接</div></div>
<div class="env-value">{% if main.CHAT_URL %}已设置{% else %}未设置{% endif %}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tab 4: 系统设置 -->
<div id="tab-settings" class="tab-content">
<div class="section">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; gap: 16px; flex-wrap: wrap;">
<div style="flex: 1; min-width: 200px;">
<div class="section-title" style="margin-bottom: 4px;">系统设置</div>
<div style="color: #6b6b6b; font-size: 11px; padding-left: 4px;">
✅ 配置修改后立即生效,无需重启 • 📋 优先级:YAML > 环境变量 > 默认值
</div>
</div>
<div style="display: flex; gap: 10px; flex-shrink: 0;">
<button class="btn" onclick="loadSettings()">重置</button>
<button class="btn" onclick="saveSettings()">保存设置</button>
</div>
</div>
<div class="grid-env">
<div class="stack-col">
<!-- 基础配置 -->
<div class="card">
<h3>基础配置</h3>
<div style="margin-top: 12px;">
<div class="setting-item">
<label>API 访问密钥</label>
<input type="text" id="setting-api-key" placeholder="留空则公开访问" />
</div>
<div class="setting-item">
<label>服务器 URL</label>
<input type="text" id="setting-base-url" placeholder="留空则自动检测" />
</div>
<div class="setting-item">
<label>代理地址</label>
<input type="text" id="setting-proxy" placeholder="如 http://127.0.0.1:7890" />
</div>
</div>
</div>
<!-- 图片生成配置 -->
<div class="card">
<h3>📸 图片生成配置</h3>
<div style="margin-top: 12px;">
<div class="setting-item">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="setting-image-enabled" style="width: auto;" />
启用图片生成
</label>
</div>
<div class="setting-item">
<label>支持的模型</label>
<div id="setting-image-models" style="display: flex; flex-direction: column; gap: 6px; margin-top: 6px;">
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; font-size: 12px;">
<input type="checkbox" value="gemini-3-pro-preview" style="width: auto;" /> gemini-3-pro-preview
</label>
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; font-size: 12px;">
<input type="checkbox" value="gemini-2.5-pro" style="width: auto;" /> gemini-2.5-pro
</label>
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; font-size: 12px;">
<input type="checkbox" value="gemini-2.5-flash" style="width: auto;" /> gemini-2.5-flash
</label>
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; font-size: 12px;">
<input type="checkbox" value="gemini-3-flash-preview" style="width: auto;" /> gemini-3-flash-preview
</label>
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; font-size: 12px;">
<input type="checkbox" value="gemini-auto" style="width: auto;" /> gemini-auto
</label>
</div>
</div>
</div>
</div>
</div>
<div class="stack-col">
<!-- 重试策略配置 -->
<div class="card">
<h3>🔄 重试策略配置</h3>
<div style="margin-top: 12px;">
<div class="setting-item">
<label>新会话尝试账户数</label>
<input type="number" id="setting-max-new-session" min="1" max="20" />
</div>
<div class="setting-item">
<label>请求失败重试次数</label>
<input type="number" id="setting-max-retries" min="1" max="10" />
</div>
<div class="setting-item">
<label>账户切换尝试次数</label>
<input type="number" id="setting-max-switch" min="1" max="20" />
</div>
<div class="setting-item">
<label>账户失败阈值(次)</label>
<input type="number" id="setting-failure-threshold" min="1" max="10" />
</div>
<div class="setting-item">
<label>429 冷却时间(秒)</label>
<input type="number" id="setting-cooldown" min="60" max="3600" />
</div>
<div class="setting-item">
<label>会话缓存时间(秒)</label>
<input type="number" id="setting-cache-ttl" min="300" max="86400" />
</div>
</div>
</div>
<!-- 公开展示配置 -->
<div class="card">
<h3>🎨 公开展示配置</h3>
<div style="margin-top: 12px;">
<div class="setting-item">
<label>Logo URL</label>
<input type="text" id="setting-logo-url" placeholder="留空则不显示" />
</div>
<div class="setting-item">
<label>开始对话链接</label>
<input type="text" id="setting-chat-url" placeholder="留空则不显示" />
</div>
<div class="setting-item">
<label>Session 过期时间(小时)</label>
<input type="number" id="setting-session-hours" min="1" max="168" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- JSON 编辑器模态框 -->
<div id="jsonModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">编辑账户配置</div>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<textarea id="jsonEditor" class="json-editor" placeholder="在此编辑 JSON 配置..."></textarea>
<div id="jsonError" class="json-error"></div>
<div style="margin-top: 12px; font-size: 12px; color: #6b6b6b;">
<strong>提示:</strong>编辑完成后点击"保存"按钮。JSON 格式错误时无法保存。<br>
配置立即生效。重启后将从环境变量重新加载,建议同步更新 ACCOUNTS_CONFIG。
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="saveConfig()">保存配置</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Define global variables for admin.js
window.ADMIN_PATH = '{{ admin_path_segment }}';
</script>
<script src="/static/js/admin.js"></script>
{% endblock %}