feat: codex-register with Sub2API增强 + Playwright引擎
Some checks are pending
Docker Image CI / build-and-push-image (push) Waiting to run

This commit is contained in:
2026-03-22 00:24:16 +08:00
commit 0f9948ffc3
91 changed files with 29942 additions and 0 deletions

283
templates/accounts.html Normal file
View File

@@ -0,0 +1,283 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>账号管理 - OpenAI 注册系统</title>
<link rel="stylesheet" href="/static/css/style.css?v={{ static_version }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📋</text></svg>">
<style>
.password-cell {
font-family: var(--font-mono);
font-size: 0.75rem;
}
.password-hidden {
filter: blur(4px);
cursor: pointer;
transition: filter 0.2s;
}
.password-hidden:hover {
filter: blur(0);
}
.token-status {
display: flex;
align-items: center;
gap: 4px;
}
.token-status .dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.token-status .dot.healthy { background: var(--success-color); }
.token-status .dot.warning { background: var(--warning-color); }
.token-status .dot.expired { background: var(--danger-color); }
.cpa-status {
display: flex;
align-items: center;
justify-content: center;
}
.cpa-status .badge {
font-size: 0.7rem;
padding: 2px 6px;
border-radius: 10px;
}
.cpa-status .badge.uploaded { background: var(--success-color); color: white; }
.cpa-status .badge.pending { background: var(--border-color); color: var(--text-secondary); }
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-brand">
<h1>OpenAI 注册系统</h1>
</div>
<div class="nav-links">
<a href="/" class="nav-link">注册</a>
<a href="/accounts" class="nav-link active">账号管理</a>
<a href="/email-services" class="nav-link">邮箱服务</a>
<a href="/payment" class="nav-link">支付</a>
<a href="/settings" class="nav-link">设置</a>
<a href="/logout" class="nav-link">退出</a>
</div>
<button class="theme-toggle" onclick="theme.toggle()" title="切换主题">
🌙
</button>
</nav>
<!-- 主内容区 -->
<main class="main-content">
<div class="page-header">
<h2>账号管理</h2>
<p class="subtitle">查看和管理已注册的 OpenAI 账号</p>
</div>
<!-- 统计卡片 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="total-accounts">0</div>
<div class="stat-label">总账号数</div>
</div>
<div class="stat-card success">
<div class="stat-value" id="active-accounts">0</div>
<div class="stat-label">活跃账号</div>
</div>
<div class="stat-card warning">
<div class="stat-value" id="expired-accounts">0</div>
<div class="stat-label">过期账号</div>
</div>
<div class="stat-card danger">
<div class="stat-value" id="failed-accounts">0</div>
<div class="stat-label">失败账号</div>
</div>
</div>
<!-- 工具栏 -->
<div class="card toolbar-card">
<div class="card-body toolbar">
<div class="toolbar-left">
<select id="filter-status" class="form-select">
<option value="">全部状态</option>
<option value="active">活跃</option>
<option value="expired">过期</option>
<option value="banned">封禁</option>
<option value="failed">失败</option>
</select>
<select id="filter-service" class="form-select">
<option value="">全部邮箱服务</option>
<option value="tempmail">Tempmail</option>
<option value="outlook">Outlook</option>
<option value="moe_mail">MoeMail</option>
</select>
<input type="text" id="search-input" class="form-input" placeholder="🔍 搜索邮箱..." style="min-width: 200px;">
</div>
<div class="toolbar-right">
<button class="btn btn-ghost" id="refresh-btn" title="刷新">
🔄 刷新
</button>
<button class="btn btn-warning" id="batch-refresh-btn" disabled title="批量刷新Token">
🔄 刷新Token
</button>
<button class="btn btn-info" id="batch-validate-btn" disabled title="批量验证Token">
✅ 验证Token
</button>
<button class="btn btn-info" id="batch-check-sub-btn" disabled title="批量检测订阅状态">
🔍 检测订阅
</button>
<div class="dropdown">
<button class="btn btn-success" id="batch-upload-btn" disabled>
☁️ 上传
</button>
<div class="dropdown-menu" id="upload-menu">
<a href="#" class="dropdown-item" id="batch-upload-cpa-item">☁️ 上传到 CPA</a>
<a href="#" class="dropdown-item" id="batch-upload-sub2api-item">🔗 上传到 Sub2API</a>
<a href="#" class="dropdown-item" id="batch-upload-tm-item">🚀 上传到 Team Manager</a>
</div>
</div>
<button class="btn btn-danger" id="batch-delete-btn" disabled>
🗑️ 批量删除
</button>
<div class="dropdown">
<button class="btn btn-primary" id="export-btn">
📥 导出
</button>
<div class="dropdown-menu" id="export-menu">
<a href="#" class="dropdown-item" data-format="json">导出 JSON</a>
<a href="#" class="dropdown-item" data-format="csv">导出 CSV</a>
<a href="#" class="dropdown-item" data-format="cpa">导出 CPA 格式</a>
<a href="#" class="dropdown-item" data-format="sub2api">导出 Sub2Api 格式</a>
</div>
</div>
</div>
</div>
</div>
<!-- 账号列表 -->
<div class="card">
<div class="card-body" style="padding: 0;">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width: 40px;"><input type="checkbox" id="select-all"></th>
<th style="width: 60px;">ID</th>
<th>邮箱</th>
<th style="width: 100px;">密码</th>
<th style="width: 120px;">邮箱服务</th>
<th style="width: 80px;">状态</th>
<th style="width: 80px;">CPA</th>
<th style="width: 80px;">订阅</th>
<th style="width: 140px;">最后刷新</th>
<th style="width: 160px;">操作</th>
</tr>
</thead>
<tbody id="accounts-table">
<tr>
<td colspan="11">
<div class="empty-state">
<div class="skeleton skeleton-text" style="width: 60%;"></div>
<div class="skeleton skeleton-text" style="width: 80%;"></div>
<div class="skeleton skeleton-text" style="width: 40%;"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination" id="pagination">
<button class="btn btn-secondary btn-sm" id="prev-page" disabled>
← 上一页
</button>
<span id="page-info">第 1 页 / 共 1 页</span>
<button class="btn btn-secondary btn-sm" id="next-page">
下一页 →
</button>
</div>
</div>
</div>
</main>
</div>
<!-- 详情模态框 -->
<div class="modal" id="detail-modal">
<div class="modal-content">
<div class="modal-header">
<h3>📋 账号详情</h3>
<button class="modal-close" id="close-modal">&times;</button>
</div>
<div class="modal-body" id="modal-body">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- CPA 服务选择模态框 -->
<div class="modal" id="cpa-service-modal">
<div class="modal-content" style="max-width: 480px;">
<div class="modal-header">
<h3>☁️ 选择 CPA 服务</h3>
<button class="modal-close" id="close-cpa-modal">&times;</button>
</div>
<div class="modal-body">
<p style="color: var(--text-muted); margin-bottom: 12px; font-size: 0.9rem;">选择要上传到的 CPA 服务,或使用全局配置。</p>
<div id="cpa-service-list" style="display: flex; flex-direction: column; gap: 8px; max-height: 300px; overflow-y: auto;">
<div style="text-align: center; color: var(--text-muted);">加载中...</div>
</div>
</div>
<div class="modal-footer" style="padding: 12px 20px; border-top: 1px solid var(--border); display: flex; gap: 8px; justify-content: flex-end;">
<button class="btn btn-secondary" id="cpa-use-global-btn">使用全局配置</button>
<button class="btn btn-secondary" id="cancel-cpa-modal-btn">取消</button>
</div>
</div>
</div>
<!-- Team Manager 服务选择模态框 -->
<div class="modal" id="tm-service-modal">
<div class="modal-content" style="max-width: 480px;">
<div class="modal-header">
<h3>🚀 选择 Team Manager 服务</h3>
<button class="modal-close" id="close-tm-modal">&times;</button>
</div>
<div class="modal-body">
<p style="color: var(--text-muted); margin-bottom: 12px; font-size: 0.9rem;">选择要上传到的 Team Manager 服务,或自动选择第一个启用的服务。</p>
<div id="tm-service-list" style="display: flex; flex-direction: column; gap: 8px; max-height: 300px; overflow-y: auto;">
<div style="text-align: center; color: var(--text-muted);">加载中...</div>
</div>
</div>
<div class="modal-footer" style="padding: 12px 20px; border-top: 1px solid var(--border); display: flex; gap: 8px; justify-content: flex-end;">
<button class="btn btn-secondary" id="tm-use-auto-btn">自动选择</button>
<button class="btn btn-secondary" id="cancel-tm-modal-btn">取消</button>
</div>
</div>
</div>
<!-- Sub2API 服务选择模态框 -->
<div class="modal" id="sub2api-service-modal">
<div class="modal-content" style="max-width: 480px;">
<div class="modal-header">
<h3>🔗 选择 Sub2API 服务</h3>
<button class="modal-close" id="close-sub2api-modal">&times;</button>
</div>
<div class="modal-body">
<p style="color: var(--text-muted); margin-bottom: 12px; font-size: 0.9rem;">选择要上传到的 Sub2API 服务,或自动选择第一个启用的服务。</p>
<div id="sub2api-service-list" style="display: flex; flex-direction: column; gap: 8px; max-height: 300px; overflow-y: auto;">
<div style="text-align: center; color: var(--text-muted);">加载中...</div>
</div>
</div>
<div class="modal-footer" style="padding: 12px 20px; border-top: 1px solid var(--border); display: flex; gap: 8px; justify-content: flex-end;">
<button class="btn btn-secondary" id="sub2api-use-auto-btn">自动选择</button>
<button class="btn btn-secondary" id="cancel-sub2api-modal-btn">取消</button>
</div>
</div>
</div>
<script src="/static/js/utils.js?v={{ static_version }}"></script>
<script src="/static/js/accounts.js?v={{ static_version }}"></script>
</body>
</html>

View File

@@ -0,0 +1,522 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮箱服务 - OpenAI 注册系统</title>
<link rel="stylesheet" href="/static/css/style.css?v={{ static_version }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📧</text></svg>">
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-brand">
<h1>OpenAI 注册系统</h1>
</div>
<div class="nav-links">
<a href="/" class="nav-link">注册</a>
<a href="/accounts" class="nav-link">账号管理</a>
<a href="/email-services" class="nav-link active">邮箱服务</a>
<a href="/payment" class="nav-link">支付</a>
<a href="/settings" class="nav-link">设置</a>
<a href="/logout" class="nav-link">退出</a>
</div>
<button class="theme-toggle" onclick="theme.toggle()" title="切换主题">
🌙
</button>
</nav>
<!-- 主内容区 -->
<main class="main-content">
<div class="page-header">
<h2>邮箱服务管理</h2>
<p class="subtitle">配置和管理注册所需的邮箱服务</p>
</div>
<!-- 统计卡片 -->
<div class="stats-grid">
<div class="stat-card info">
<div class="stat-value" id="outlook-count">0</div>
<div class="stat-label">Outlook 账户</div>
</div>
<div class="stat-card success">
<div class="stat-value" id="custom-count">0</div>
<div class="stat-label">自定义邮箱</div>
</div>
<div class="stat-card warning">
<div class="stat-value" id="tempmail-status">可用</div>
<div class="stat-label">临时邮箱</div>
</div>
<div class="stat-card">
<div class="stat-value" id="total-enabled">0</div>
<div class="stat-label">已启用服务</div>
</div>
</div>
<!-- Outlook 管理 -->
<div class="card">
<div class="card-header">
<h3>📥 Outlook 批量导入</h3>
<button class="btn btn-ghost btn-sm" id="toggle-outlook-import">展开</button>
</div>
<div class="card-body" id="outlook-import-body" style="display: none;">
<div class="import-info">
<p><strong>支持格式:</strong></p>
<ul>
<li><code>邮箱----密码</code> (密码认证)</li>
<li><code>邮箱----密码----client_id----refresh_token</code> XOAUTH2 认证,推荐)</li>
</ul>
<p>每行一个账户,使用四个连字符(----)分隔字段。以 # 开头的行将被忽略。</p>
</div>
<div class="form-group">
<label for="outlook-import-data">批量导入数据</label>
<textarea id="outlook-import-data" rows="8" placeholder="example@outlook.com----password123&#10;test@outlook.com----password456----client_id----refresh_token"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label>
<input type="checkbox" id="outlook-import-enabled" checked>
导入后启用
</label>
</div>
<div class="form-group">
<label for="outlook-import-priority">优先级</label>
<input type="number" id="outlook-import-priority" value="0" min="0">
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-primary" id="outlook-import-btn">📥 开始导入</button>
<button type="button" class="btn btn-secondary" id="clear-import-btn">清空</button>
</div>
<div id="import-result" style="display: none; margin-top: var(--spacing-md);"></div>
</div>
</div>
<!-- 自定义邮箱管理(含 MoeMail / TempMail / DuckMail -->
<div class="card">
<div class="card-header">
<h3>🔗 自定义邮箱服务</h3>
<button class="btn btn-primary btn-sm" id="add-custom-btn"> 添加服务</button>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width: 40px;"><input type="checkbox" id="select-all-custom"></th>
<th>名称</th>
<th style="width: 90px;">类型</th>
<th style="width: 200px;">地址</th>
<th style="width: 100px;">状态</th>
<th style="width: 80px;">优先级</th>
<th style="width: 160px;">最后使用</th>
<th style="width: 180px;">操作</th>
</tr>
</thead>
<tbody id="custom-services-table">
<tr>
<td colspan="8">
<div class="empty-state">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text" style="width: 80%;"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Outlook 账户列表 -->
<div class="card">
<div class="card-header">
<h3>📧 Outlook 账户列表</h3>
<button class="btn btn-danger btn-sm" id="batch-delete-outlook-btn" disabled>🗑️ 批量删除</button>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width: 40px;"><input type="checkbox" id="select-all-outlook"></th>
<th>邮箱</th>
<th style="width: 100px;">认证方式</th>
<th style="width: 100px;">状态</th>
<th style="width: 80px;">优先级</th>
<th style="width: 160px;">最后使用</th>
<th style="width: 140px;">操作</th>
</tr>
</thead>
<tbody id="outlook-accounts-table">
<tr>
<td colspan="7">
<div class="empty-state">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text" style="width: 80%;"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 临时邮箱配置 -->
<div class="card">
<div class="card-header">
<h3>🌐 临时邮箱配置</h3>
</div>
<div class="card-body">
<form id="tempmail-form">
<div class="form-group">
<label for="tempmail-api">Tempmail.lol API 地址</label>
<input type="text" id="tempmail-api" name="api_url" placeholder="https://tempmail.lol/api">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="tempmail-enabled" checked>
启用临时邮箱
</label>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存设置</button>
<button type="button" class="btn btn-secondary" id="test-tempmail-btn">🔌 测试连接</button>
</div>
</form>
</div>
</div>
</main>
</div>
<!-- 添加自定义邮箱服务模态框(含类型选择) -->
<div class="modal" id="add-custom-modal">
<div class="modal-content">
<div class="modal-header">
<h3> 添加自定义邮箱服务</h3>
<button class="modal-close" id="close-custom-modal">&times;</button>
</div>
<div class="modal-body">
<form id="add-custom-form">
<div class="form-group">
<label for="custom-name">服务名称</label>
<input type="text" id="custom-name" name="name" required placeholder="例如:我的域名邮箱">
</div>
<div class="form-group">
<label for="custom-sub-type">服务类型</label>
<select id="custom-sub-type" name="sub_type">
<option value="moemail">MoeMail自定义域名 API</option>
<option value="tempmail">TempMail自部署 Cloudflare Worker</option>
<option value="duckmail">DuckMailDuckMail API</option>
<option value="freemail">Freemail自部署 Cloudflare Worker</option>
<option value="imap">标准 IMAPGmail/QQ/163等</option>
</select>
</div>
<!-- MoeMail 字段 -->
<div id="add-moemail-fields">
<div class="form-group">
<label for="custom-api-url">API 地址</label>
<input type="text" id="custom-api-url" name="api_url" placeholder="https://api.example.com">
</div>
<div class="form-group">
<label for="custom-api-key">API 密钥 (可选)</label>
<input type="text" id="custom-api-key" name="api_key" placeholder="API Key">
</div>
<div class="form-group">
<label for="custom-domain">邮箱域名</label>
<input type="text" id="custom-domain" name="domain" placeholder="example.com">
</div>
</div>
<!-- TempMail 字段 -->
<div id="add-tempmail-fields" style="display:none;">
<div class="form-group">
<label for="custom-tm-base-url">Worker 地址</label>
<input type="text" id="custom-tm-base-url" name="tm_base_url" placeholder="https://mail.example.com">
</div>
<div class="form-group">
<label for="custom-tm-admin-password">Admin 密码</label>
<input type="password" id="custom-tm-admin-password" name="tm_admin_password" placeholder="x-admin-auth 密码">
</div>
<div class="form-group">
<label for="custom-tm-domain">邮箱域名</label>
<input type="text" id="custom-tm-domain" name="tm_domain" placeholder="example.com">
</div>
</div>
<!-- DuckMail 字段 -->
<div id="add-duckmail-fields" style="display:none;">
<div class="form-group">
<label for="custom-dm-base-url">API 地址</label>
<input type="text" id="custom-dm-base-url" name="dm_base_url" placeholder="https://api.duckmail.sbs">
</div>
<div class="form-group">
<label for="custom-dm-api-key">API Key可选</label>
<input type="text" id="custom-dm-api-key" name="dm_api_key" placeholder="dk_xxx">
</div>
<div class="form-group">
<label for="custom-dm-domain">默认域名</label>
<input type="text" id="custom-dm-domain" name="dm_domain" placeholder="example.com">
</div>
<div class="form-group">
<label for="custom-dm-password-length">随机密码长度</label>
<input type="number" id="custom-dm-password-length" name="dm_password_length" min="6" max="64" value="12">
</div>
</div>
<!-- Freemail 字段 -->
<div id="add-freemail-fields" style="display:none;">
<div class="form-group">
<label for="custom-fm-base-url">Worker 地址</label>
<input type="text" id="custom-fm-base-url" name="fm_base_url" placeholder="https://freemail.example.com">
</div>
<div class="form-group">
<label for="custom-fm-admin-token">Admin Token</label>
<input type="password" id="custom-fm-admin-token" name="fm_admin_token" placeholder="JWT_TOKEN 值">
</div>
<div class="form-group">
<label for="custom-fm-domain">邮箱域名 (可选)</label>
<input type="text" id="custom-fm-domain" name="fm_domain" placeholder="example.com">
</div>
</div>
<!-- IMAP 字段 -->
<div id="add-imap-fields" style="display:none;">
<div class="form-group">
<label for="custom-imap-host">IMAP 服务器</label>
<input type="text" id="custom-imap-host" name="imap_host" placeholder="imap.gmail.com / imap.qq.com / imap.163.com">
<small style="color: var(--text-muted);">Gmail: imap.gmail.com | QQ: imap.qq.com | 163: imap.163.com</small>
</div>
<div class="form-row">
<div class="form-group">
<label for="custom-imap-port">端口</label>
<input type="number" id="custom-imap-port" name="imap_port" value="993" min="1" max="65535">
</div>
<div class="form-group">
<label for="custom-imap-use-ssl">加密方式</label>
<select id="custom-imap-use-ssl" name="imap_use_ssl">
<option value="true">SSL端口 993</option>
<option value="false">STARTTLS端口 143</option>
</select>
</div>
</div>
<div class="form-group">
<label for="custom-imap-email">邮箱地址</label>
<input type="email" id="custom-imap-email" name="imap_email" placeholder="your@gmail.com">
</div>
<div class="form-group">
<label for="custom-imap-password">密码 / 授权码</label>
<input type="password" id="custom-imap-password" name="imap_password" placeholder="Gmail 需使用 App PasswordQQ/163 需使用授权码">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="custom-priority">优先级</label>
<input type="number" id="custom-priority" name="priority" value="0" min="0">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="custom-enabled" name="enabled" checked>
启用服务
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">添加</button>
<button type="button" class="btn btn-secondary" id="cancel-add-custom">取消</button>
</div>
</form>
</div>
</div>
</div>
<!-- 编辑自定义邮箱服务模态框(含类型选择) -->
<div class="modal" id="edit-custom-modal">
<div class="modal-content">
<div class="modal-header">
<h3>✏️ 编辑自定义邮箱服务</h3>
<button class="modal-close" id="close-edit-custom-modal">&times;</button>
</div>
<div class="modal-body">
<form id="edit-custom-form">
<input type="hidden" id="edit-custom-id" name="id">
<input type="hidden" id="edit-custom-sub-type-hidden" name="sub_type">
<div class="form-group">
<label for="edit-custom-name">服务名称</label>
<input type="text" id="edit-custom-name" name="name" required placeholder="例如:我的域名邮箱">
</div>
<div class="form-group">
<label>服务类型</label>
<div id="edit-custom-type-badge" style="padding: 6px 0; color: var(--text-muted); font-size: 0.875rem;"></div>
</div>
<!-- MoeMail 字段 -->
<div id="edit-moemail-fields">
<div class="form-group">
<label for="edit-custom-api-url">API 地址</label>
<input type="text" id="edit-custom-api-url" name="api_url" placeholder="https://api.example.com">
</div>
<div class="form-group">
<label for="edit-custom-api-key">API 密钥</label>
<input type="text" id="edit-custom-api-key" name="api_key" placeholder="API Key">
<small style="color: var(--text-muted);">留空则保持原值不变</small>
</div>
<div class="form-group">
<label for="edit-custom-domain">邮箱域名</label>
<input type="text" id="edit-custom-domain" name="domain" placeholder="example.com">
</div>
</div>
<!-- TempMail 字段 -->
<div id="edit-tempmail-fields" style="display:none;">
<div class="form-group">
<label for="edit-tm-base-url">Worker 地址</label>
<input type="text" id="edit-tm-base-url" name="tm_base_url" placeholder="https://mail.example.com">
</div>
<div class="form-group">
<label for="edit-tm-admin-password">Admin 密码</label>
<input type="password" id="edit-tm-admin-password" name="tm_admin_password" placeholder="留空则不修改">
<small style="color: var(--text-muted);">留空则保持原值不变</small>
</div>
<div class="form-group">
<label for="edit-tm-domain">邮箱域名</label>
<input type="text" id="edit-tm-domain" name="tm_domain" placeholder="example.com">
</div>
</div>
<!-- DuckMail 字段 -->
<div id="edit-duckmail-fields" style="display:none;">
<div class="form-group">
<label for="edit-dm-base-url">API 地址</label>
<input type="text" id="edit-dm-base-url" name="dm_base_url" placeholder="https://api.duckmail.sbs">
</div>
<div class="form-group">
<label for="edit-dm-api-key">API Key</label>
<input type="text" id="edit-dm-api-key" name="dm_api_key" placeholder="留空则不修改">
<small style="color: var(--text-muted);">留空则保持原值不变</small>
</div>
<div class="form-group">
<label for="edit-dm-domain">默认域名</label>
<input type="text" id="edit-dm-domain" name="dm_domain" placeholder="example.com">
</div>
<div class="form-group">
<label for="edit-dm-password-length">随机密码长度</label>
<input type="number" id="edit-dm-password-length" name="dm_password_length" min="6" max="64" value="12">
</div>
</div>
<!-- Freemail 字段 -->
<div id="edit-freemail-fields" style="display:none;">
<div class="form-group">
<label for="edit-fm-base-url">Worker 地址</label>
<input type="text" id="edit-fm-base-url" name="fm_base_url" placeholder="https://freemail.example.com">
</div>
<div class="form-group">
<label for="edit-fm-admin-token">Admin Token</label>
<input type="password" id="edit-fm-admin-token" name="fm_admin_token" placeholder="留空则不修改">
<small style="color: var(--text-muted);">留空则保持原值不变</small>
</div>
<div class="form-group">
<label for="edit-fm-domain">邮箱域名 (可选)</label>
<input type="text" id="edit-fm-domain" name="fm_domain" placeholder="example.com">
</div>
</div>
<!-- IMAP 字段 -->
<div id="edit-imap-fields" style="display:none;">
<div class="form-group">
<label for="edit-imap-host">IMAP 服务器</label>
<input type="text" id="edit-imap-host" name="imap_host" placeholder="imap.gmail.com">
</div>
<div class="form-row">
<div class="form-group">
<label for="edit-imap-port">端口</label>
<input type="number" id="edit-imap-port" name="imap_port" value="993" min="1" max="65535">
</div>
<div class="form-group">
<label for="edit-imap-use-ssl">加密方式</label>
<select id="edit-imap-use-ssl" name="imap_use_ssl">
<option value="true">SSL端口 993</option>
<option value="false">STARTTLS端口 143</option>
</select>
</div>
</div>
<div class="form-group">
<label for="edit-imap-email">邮箱地址</label>
<input type="email" id="edit-imap-email" name="imap_email" placeholder="your@gmail.com">
</div>
<div class="form-group">
<label for="edit-imap-password">密码 / 授权码</label>
<input type="password" id="edit-imap-password" name="imap_password" placeholder="留空则保持原值不变">
<small style="color: var(--text-muted);">留空则保持原值不变</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="edit-custom-priority">优先级</label>
<input type="number" id="edit-custom-priority" name="priority" value="0" min="0">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="edit-custom-enabled" name="enabled">
启用服务
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">保存</button>
<button type="button" class="btn btn-secondary" id="cancel-edit-custom">取消</button>
</div>
</form>
</div>
</div>
</div>
<!-- 编辑 Outlook 账户模态框 -->
<div class="modal" id="edit-outlook-modal">
<div class="modal-content">
<div class="modal-header">
<h3>✏️ 编辑 Outlook 账户</h3>
<button class="modal-close" id="close-edit-outlook-modal">&times;</button>
</div>
<div class="modal-body">
<form id="edit-outlook-form">
<input type="hidden" id="edit-outlook-id" name="id">
<div class="form-group">
<label for="edit-outlook-email">邮箱地址</label>
<input type="text" id="edit-outlook-email" name="email" required placeholder="example@outlook.com">
</div>
<div class="form-group">
<label for="edit-outlook-password">密码</label>
<input type="password" id="edit-outlook-password" name="password" placeholder="留空则保持原值不变">
<small style="color: var(--text-muted);">留空则保持原值不变</small>
</div>
<div class="form-group">
<label for="edit-outlook-client-id">OAuth Client ID (可选)</label>
<input type="text" id="edit-outlook-client-id" name="client_id" placeholder="用于 XOAUTH2 认证">
</div>
<div class="form-group">
<label for="edit-outlook-refresh-token">OAuth Refresh Token (可选)</label>
<input type="text" id="edit-outlook-refresh-token" name="refresh_token" placeholder="留空则保持原值不变">
<small style="color: var(--text-muted);">留空则保持原值不变</small>
</div>
<div class="form-row">
<div class="form-group">
<label for="edit-outlook-priority">优先级</label>
<input type="number" id="edit-outlook-priority" name="priority" value="0" min="0">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="edit-outlook-enabled" name="enabled">
启用账户
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">保存</button>
<button type="button" class="btn btn-secondary" id="cancel-edit-outlook">取消</button>
</div>
</form>
</div>
</div>
</div>
<script src="/static/js/utils.js?v={{ static_version }}"></script>
<script src="/static/js/email_services.js?v={{ static_version }}"></script>
</body>
</html>

448
templates/index.html Normal file
View File

@@ -0,0 +1,448 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>注册控制台 - OpenAI 注册系统</title>
<link rel="stylesheet" href="/static/css/style.css?v={{ static_version }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚀</text></svg>">
<style>
/* 两栏布局 */
.two-column-layout {
display: grid;
grid-template-columns: 30% 70%;
gap: var(--spacing-lg);
align-items: start;
}
.left-panel {
position: sticky;
top: calc(60px + var(--spacing-lg));
}
.right-panel {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
/* 任务状态行 */
.task-status-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--spacing-sm);
padding: var(--spacing-md);
background: var(--surface-hover);
border-radius: var(--radius);
margin-bottom: var(--spacing-md);
}
.task-status-item {
text-align: center;
}
.task-status-item .label {
font-size: 0.75rem;
color: var(--text-muted);
}
.task-status-item .value {
font-weight: 600;
color: var(--text-primary);
font-size: 0.875rem;
}
/* 已注册账号列表 */
.recent-accounts-table {
max-height: 300px;
overflow-y: auto;
}
.recent-accounts-table table {
width: 100%;
}
.recent-accounts-table th,
.recent-accounts-table td {
padding: var(--spacing-sm) var(--spacing-md);
font-size: 0.8125rem;
}
.password-cell {
font-family: var(--font-mono);
font-size: 0.75rem;
}
.password-hidden {
filter: blur(4px);
cursor: pointer;
transition: filter 0.2s;
}
.password-hidden:hover {
filter: blur(0);
}
/* 自定义多选下拉 */
.multi-select-dropdown {
position: relative;
width: 100%;
font-size: 0.85rem;
}
.msd-trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 10px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface);
color: var(--text-primary);
cursor: pointer;
user-select: none;
}
.msd-trigger:hover { border-color: var(--primary-color); }
.msd-arrow { font-size: 0.7rem; transition: transform 0.15s; }
.msd-dropdown.open .msd-arrow { transform: rotate(180deg); }
.msd-list {
display: none;
position: absolute;
top: calc(100% + 4px);
left: 0; right: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
z-index: 100;
max-height: 150px;
overflow-y: auto;
padding: 4px 0;
}
.msd-dropdown.open .msd-list { display: block; }
.msd-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
cursor: pointer;
}
.msd-item:hover { background: var(--surface-hover); }
.msd-item input[type=checkbox] { margin: 0; cursor: pointer; }
.msd-empty { padding: 8px 12px; color: var(--text-muted); font-size: 0.8rem; }
/* 响应式 */
@media (max-width: 1024px) {
.two-column-layout {
grid-template-columns: 1fr;
}
.left-panel {
position: static;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-brand">
<h1>OpenAI 注册系统</h1>
</div>
<div class="nav-links">
<a href="/" class="nav-link active">注册</a>
<a href="/accounts" class="nav-link">账号管理</a>
<a href="/email-services" class="nav-link">邮箱服务</a>
<a href="/payment" class="nav-link">支付</a>
<a href="/settings" class="nav-link">设置</a>
<a href="/logout" class="nav-link">退出</a>
</div>
<button class="theme-toggle" onclick="theme.toggle()" title="切换主题">
🌙
</button>
</nav>
<!-- 主内容区 - 两栏布局 -->
<main class="main-content">
<div class="two-column-layout">
<!-- 左侧面板 - 注册设置 -->
<div class="left-panel">
<div class="card">
<div class="card-header">
<h3>📝 注册设置</h3>
</div>
<div class="card-body">
<form id="registration-form">
<div class="form-group">
<label for="email-service">邮箱服务</label>
<select id="email-service" name="email_service" required>
<option value="tempmail:default">Tempmail.lol (临时邮箱)</option>
<option value="outlook">Outlook</option>
<option value="moe_mail">MoeMail</option>
<option value="temp_mail">Temp-Mail 自部署</option>
<option value="duck_mail">DuckMail</option>
<option value="imap_mail">IMAP 邮箱</option>
<option value="outlook_batch:all">Outlook 批量注册</option>
</select>
</div>
<!-- Outlook 批量注册区域 -->
<div id="outlook-batch-section" style="display: none;">
<div class="form-group">
<label>选择账户</label>
<div id="outlook-accounts-container" style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: var(--radius); padding: var(--spacing-sm);">
<div class="loading-placeholder" style="text-align: center; padding: var(--spacing-md); color: var(--text-muted);">
加载中...
</div>
</div>
<div style="margin-top: var(--spacing-sm); display: flex; gap: var(--spacing-xs); flex-wrap: wrap;">
<button type="button" class="btn btn-ghost btn-sm" onclick="selectAllOutlookAccounts()">全选</button>
<button type="button" class="btn btn-ghost btn-sm" onclick="selectUnregisteredOutlook()">只选未注册</button>
<button type="button" class="btn btn-ghost btn-sm" onclick="deselectAllOutlookAccounts()">取消全选</button>
</div>
</div>
<div class="form-group">
<label for="outlook-concurrency-mode">并发模式</label>
<select id="outlook-concurrency-mode" name="outlook_concurrency_mode">
<option value="pipeline">流水线Pipeline</option>
<option value="parallel">并行Parallel</option>
</select>
</div>
<div class="form-group">
<label for="outlook-concurrency-count">并发数 (1-50)</label>
<input type="number" id="outlook-concurrency-count" name="outlook_concurrency_count" min="1" max="50" value="3">
<small id="outlook-concurrency-hint" style="color: var(--text-muted); font-size: 0.75rem;">同时最多运行 N 个任务,每隔 interval 秒启动新任务</small>
</div>
<div id="outlook-interval-group">
<div class="form-group">
<label for="outlook-interval-min">最小间隔 (秒)</label>
<input type="number" id="outlook-interval-min" name="outlook_interval_min" min="0" max="300" value="5">
</div>
<div class="form-group">
<label for="outlook-interval-max">最大间隔 (秒)</label>
<input type="number" id="outlook-interval-max" name="outlook_interval_max" min="1" max="600" value="30">
</div>
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer;">
<input type="checkbox" id="outlook-skip-registered" checked>
<span>自动跳过已注册的邮箱</span>
</label>
</div>
</div>
<div class="form-group" id="reg-mode-group">
<label for="reg-mode">注册模式</label>
<select id="reg-mode" name="reg_mode">
<option value="single">单次注册</option>
<option value="batch">批量注册</option>
</select>
</div>
<div class="form-group" id="batch-count-group" style="display: none;">
<label for="batch-count">注册数量 (1-100)</label>
<input type="number" id="batch-count" name="batch_count" min="1" max="100" value="5">
</div>
<div id="batch-options" style="display: none;">
<div class="form-group">
<label for="concurrency-mode">并发模式</label>
<select id="concurrency-mode" name="concurrency_mode">
<option value="pipeline">流水线Pipeline</option>
<option value="parallel">并行Parallel</option>
</select>
</div>
<div class="form-group">
<label for="concurrency-count">并发数 (1-50)</label>
<input type="number" id="concurrency-count" name="concurrency_count" min="1" max="50" value="3">
<small id="concurrency-hint" style="color: var(--text-muted); font-size: 0.75rem;">同时最多运行 N 个任务,每隔 interval 秒启动新任务</small>
</div>
<div id="interval-group">
<div class="form-group">
<label for="interval-min">最小间隔 (秒)</label>
<input type="number" id="interval-min" name="interval_min" min="0" max="300" value="5">
</div>
<div class="form-group">
<label for="interval-max">最大间隔 (秒)</label>
<input type="number" id="interval-max" name="interval_max" min="1" max="600" value="30">
</div>
</div>
</div>
<div class="form-group" id="auto-upload-group">
<label style="font-weight: 500; margin-bottom: var(--spacing-xs); display: block;">注册后自动操作</label>
<!-- CPA -->
<label style="display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer;">
<input type="checkbox" id="auto-upload-cpa">
<span>上传到 CPA</span>
</label>
<div id="cpa-service-select-group" style="display:none; margin-top: 6px; margin-bottom: 8px; padding-left: 4px;">
<div class="multi-select-dropdown" id="cpa-service-select"></div>
</div>
<!-- Sub2API -->
<label style="display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer; margin-top: 6px;">
<input type="checkbox" id="auto-upload-sub2api">
<span>上传到 Sub2API</span>
</label>
<div id="sub2api-service-select-group" style="display:none; margin-top: 6px; margin-bottom: 8px; padding-left: 4px;">
<div class="multi-select-dropdown" id="sub2api-service-select"></div>
</div>
<!-- Sub2API 高级配置 -->
<div id="sub2api-advanced-group" style="display:none; margin-top: 8px; padding-left: 4px; border-left: 2px solid var(--border-color, #e0e0e0); padding-bottom: 8px;">
<!-- 分组选择 -->
<div style="margin-bottom: 8px;">
<label style="font-size: 0.85em; color: var(--text-secondary, #666); display: block; margin-bottom: 4px;">上传分组</label>
<select id="sub2api-group-select" multiple style="width: 100%; min-height: 60px; padding: 4px; border: 1px solid var(--border-color, #ddd); border-radius: 4px; font-size: 0.85em;">
<option value="" disabled>加载中...</option>
</select>
<p style="font-size: 0.75em; color: var(--text-secondary, #999); margin: 2px 0 0;">按住 Ctrl 多选,不选则不绑定分组</p>
</div>
<!-- 代理节点选择 -->
<div style="margin-bottom: 8px;">
<label style="font-size: 0.85em; color: var(--text-secondary, #666); display: block; margin-bottom: 4px;">代理节点</label>
<select id="sub2api-proxy-select" style="width: 100%; padding: 4px; border: 1px solid var(--border-color, #ddd); border-radius: 4px; font-size: 0.85em;">
<option value="">不指定(使用默认)</option>
</select>
</div>
<!-- 模型限制 -->
<div>
<label style="font-size: 0.85em; color: var(--text-secondary, #666); display: block; margin-bottom: 4px;">模型限制</label>
<div id="sub2api-model-checkboxes" style="display: flex; flex-wrap: wrap; gap: 4px 12px; margin-bottom: 6px;"></div>
<div style="display: flex; gap: 4px; align-items: center;">
<input type="text" id="sub2api-custom-models" placeholder="输入自定义模型名" style="flex: 1; padding: 4px 6px; border: 1px solid var(--border-color, #ddd); border-radius: 4px; font-size: 0.82em;">
<button type="button" id="sub2api-add-model-btn" style="padding: 4px 12px; border: 1px solid var(--primary-color, #4a90d9); background: var(--primary-color, #4a90d9); color: #fff; border-radius: 4px; font-size: 0.82em; cursor: pointer; white-space: nowrap;">添加</button>
</div>
<div id="sub2api-custom-model-tags" style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px;"></div>
<p style="font-size: 0.75em; color: var(--text-secondary, #999); margin: 2px 0 0;">勾选预置模型或输入自定义模型名点击添加,上传后账号仅允许使用已选模型</p>
</div>
</div>
<!-- Team Manager -->
<label style="display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer; margin-top: 6px;">
<input type="checkbox" id="auto-upload-tm">
<span>上传到 Team Manager</span>
</label>
<div id="tm-service-select-group" style="display:none; margin-top: 6px; padding-left: 4px;">
<div class="multi-select-dropdown" id="tm-service-select"></div>
</div>
</div>
<div class="form-actions" style="flex-direction: column;">
<button type="submit" class="btn btn-primary btn-lg" id="start-btn" style="width: 100%;">
🚀 开始注册
</button>
<button type="button" class="btn btn-secondary" id="cancel-btn" disabled style="width: 100%;">
取消任务
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 右侧面板 -->
<div class="right-panel">
<!-- 监控台 -->
<div class="card" id="console-card">
<div class="card-header">
<h3>💻 监控台</h3>
<div style="display: flex; gap: var(--spacing-sm); align-items: center;">
<span id="task-status-badge" class="status-badge pending" style="display: none;">等待中</span>
<button class="btn btn-ghost btn-sm" id="clear-log-btn">清空</button>
</div>
</div>
<div class="card-body" style="padding: 0;">
<!-- 任务状态行 -->
<div class="task-status-row" id="task-status-row" style="display: none;">
<div class="task-status-item">
<div class="label">任务 ID</div>
<div class="value" id="task-id">-</div>
</div>
<div class="task-status-item">
<div class="label">邮箱</div>
<div class="value" id="task-email">-</div>
</div>
<div class="task-status-item">
<div class="label">邮箱服务</div>
<div class="value" id="task-service">-</div>
</div>
<div class="task-status-item">
<div class="label">状态</div>
<div class="value" id="task-status">-</div>
</div>
</div>
<!-- 批量进度 -->
<div id="batch-progress-section" style="display: none; padding: var(--spacing-md); border-bottom: 1px solid var(--border-light);">
<div style="display: flex; justify-content: space-between; margin-bottom: var(--spacing-sm);">
<span id="batch-progress-text">0/0</span>
<span id="batch-progress-percent">0%</span>
</div>
<div class="progress-bar-container">
<div id="progress-bar" class="progress-bar" style="width: 0%"></div>
</div>
<div class="batch-stats" style="margin-top: var(--spacing-sm);">
<span><strong id="batch-success">0</strong></span>
<span><strong id="batch-failed">0</strong></span>
<span><strong id="batch-remaining">0</strong></span>
</div>
</div>
<!-- 控制台日志 -->
<div id="console-log" class="console-log">
<div class="log-line info">[系统] 准备就绪,等待开始注册...</div>
</div>
</div>
</div>
<!-- 已注册账号列表 -->
<div class="card">
<div class="card-header">
<h3>📋 已注册账号</h3>
<div style="display: flex; gap: var(--spacing-sm);">
<button class="btn btn-ghost btn-sm" id="refresh-accounts-btn">🔄 刷新</button>
<a href="/accounts" class="btn btn-secondary btn-sm">查看全部</a>
</div>
</div>
<div class="card-body" style="padding: 0;">
<div class="recent-accounts-table">
<table class="data-table">
<thead>
<tr>
<th style="width: 50px;">ID</th>
<th>邮箱</th>
<th style="width: 120px;">密码</th>
<th style="width: 80px;">状态</th>
</tr>
</thead>
<tbody id="recent-accounts-table">
<tr>
<td colspan="5">
<div class="empty-state" style="padding: var(--spacing-md);">
<div class="empty-state-icon">📭</div>
<div class="empty-state-title">暂无已注册账号</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="/static/js/utils.js?v={{ static_version }}"></script>
<script src="/static/js/app.js?v={{ static_version }}"></script>
</body>
</html>

62
templates/login.html Normal file
View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>访问验证 - OpenAI 注册系统</title>
<link rel="stylesheet" href="/static/css/style.css?v={{ static_version }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔒</text></svg>">
<style>
.login-wrap {
max-width: 420px;
margin: 12vh auto 0;
}
.login-card {
padding: var(--spacing-lg);
}
.login-title {
margin-bottom: var(--spacing-sm);
}
.login-subtitle {
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
}
.login-error {
background: var(--danger-light);
color: var(--danger-color);
border: 1px solid var(--danger-color);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius);
margin-bottom: var(--spacing-md);
font-size: 0.875rem;
}
</style>
</head>
<body>
<div class="container">
<div class="login-wrap">
<div class="card login-card">
<div class="card-header">
<h3 class="login-title">🔒 访问验证</h3>
<span class="hint">请输入访问密码继续</span>
</div>
<div class="card-body">
{% if error %}
<div class="login-error">{{ error }}</div>
{% endif %}
<form method="post" action="/login">
<input type="hidden" name="next" value="{{ next }}">
<div class="form-group">
<label for="password">访问密码</label>
<input type="password" id="password" name="password" autocomplete="current-password" required autofocus>
</div>
<div class="form-actions" style="margin-top: var(--spacing-lg);">
<button type="submit" class="btn btn-primary" style="width: 100%">验证进入</button>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>

172
templates/payment.html Normal file
View File

@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>支付升级 - OpenAI 注册系统</title>
<link rel="stylesheet" href="/static/css/style.css?v={{ static_version }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>💳</text></svg>">
<style>
.plan-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
.plan-card {
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 20px;
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s;
text-align: center;
}
.plan-card:hover {
border-color: var(--primary-color);
}
.plan-card.selected {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(99,102,241,0.15);
}
.plan-card h3 { margin: 0 0 8px; font-size: 1.2rem; }
.plan-card p { margin: 0; color: var(--text-secondary); font-size: 0.9rem; }
.team-options { display: none; }
.team-options.show { display: block; }
.link-box {
display: none;
margin-top: 16px;
}
.link-box.show { display: block; }
.link-text {
width: 100%;
font-family: var(--font-mono);
font-size: 0.8rem;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-secondary);
color: var(--text-primary);
resize: vertical;
min-height: 80px;
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-brand">
<h1>OpenAI 注册系统</h1>
</div>
<div class="nav-links">
<a href="/" class="nav-link">注册</a>
<a href="/accounts" class="nav-link">账号管理</a>
<a href="/email-services" class="nav-link">邮箱服务</a>
<a href="/payment" class="nav-link active">支付</a>
<a href="/settings" class="nav-link">设置</a>
<a href="/logout" class="nav-link">退出</a>
</div>
<button class="theme-toggle" onclick="theme.toggle()" title="切换主题">🌙</button>
</nav>
<!-- 主内容区 -->
<main class="main-content">
<div class="page-header">
<h2>支付升级</h2>
<p class="subtitle">为账号生成 Plus 或 Team 订阅支付链接</p>
</div>
<div class="card">
<div class="card-header">
<h3>选择套餐</h3>
</div>
<div class="card-body">
<!-- 套餐选择 -->
<div class="plan-cards">
<div class="plan-card selected" id="plan-plus" onclick="selectPlan('plus')">
<h3>Plus</h3>
<p>个人订阅,$20/月</p>
</div>
<div class="plan-card" id="plan-team" onclick="selectPlan('team')">
<h3>Team</h3>
<p>团队订阅,按座位计费</p>
</div>
</div>
<!-- 账号选择 -->
<div class="form-group">
<label for="account-select">选择账号</label>
<select id="account-select" style="width:100%">
<option value="">-- 加载中... --</option>
</select>
</div>
<!-- 国家选择 -->
<div class="form-row">
<div class="form-group">
<label for="country-select">计费国家</label>
<select id="country-select" onchange="onCountryChange()">
<option value="SG">新加坡 (SGD)</option>
<option value="US">美国 (USD)</option>
<option value="TR">土耳其 (TRY)</option>
<option value="JP">日本 (JPY)</option>
<option value="HK">香港 (HKD)</option>
<option value="GB">英国 (GBP)</option>
<option value="AU">澳大利亚 (AUD)</option>
<option value="CA">加拿大 (CAD)</option>
<option value="IN">印度 (INR)</option>
<option value="BR">巴西 (BRL)</option>
<option value="MX">墨西哥 (MXN)</option>
</select>
</div>
<div class="form-group">
<label>对应货币</label>
<input type="text" id="currency-display" value="SGD" readonly style="background:var(--surface-hover);cursor:default">
</div>
</div>
<!-- Team 额外参数 -->
<div class="team-options" id="team-options">
<div class="form-row">
<div class="form-group">
<label for="workspace-name">工作区名称</label>
<input type="text" id="workspace-name" value="MyTeam" placeholder="MyTeam">
</div>
<div class="form-group">
<label for="seat-quantity">座位数量</label>
<input type="number" id="seat-quantity" value="5" min="1" max="100">
</div>
<div class="form-group">
<label for="price-interval">计费周期</label>
<select id="price-interval">
<option value="month">月付</option>
<option value="year">年付</option>
</select>
</div>
</div>
</div>
<!-- 操作区 -->
<div class="form-actions">
<button class="btn btn-primary" onclick="generateLink()">生成支付链接</button>
</div>
<!-- 链接结果 -->
<div class="link-box" id="link-box">
<label>支付链接</label>
<textarea class="link-text" id="link-text" readonly></textarea>
<div class="form-actions" style="margin-top:10px">
<button class="btn btn-secondary" onclick="copyLink()">复制链接</button>
<button class="btn btn-primary" onclick="openIncognito()">无痕打开浏览器</button>
</div>
<p class="hint" id="open-status"></p>
</div>
</div>
</div>
</main>
</div>
<script src="/static/js/utils.js?v={{ static_version }}"></script>
<script src="/static/js/payment.js?v={{ static_version }}"></script>
</body>
</html>

596
templates/settings.html Normal file
View File

@@ -0,0 +1,596 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统设置 - OpenAI 注册系统</title>
<link rel="stylesheet" href="/static/css/style.css?v={{ static_version }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚙️</text></svg>">
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-brand">
<h1>OpenAI 注册系统</h1>
</div>
<div class="nav-links">
<a href="/" class="nav-link">注册</a>
<a href="/accounts" class="nav-link">账号管理</a>
<a href="/email-services" class="nav-link">邮箱服务</a>
<a href="/payment" class="nav-link">支付</a>
<a href="/settings" class="nav-link active">设置</a>
<a href="/logout" class="nav-link">退出</a>
</div>
<button class="theme-toggle" onclick="theme.toggle()" title="切换主题">
🌙
</button>
</nav>
<!-- 主内容区 -->
<main class="main-content">
<div class="page-header">
<h2>系统设置</h2>
<p class="subtitle">配置代理、邮箱服务和系统参数</p>
</div>
<!-- 设置标签页 -->
<div class="tabs">
<button class="tab-btn active" data-tab="proxy">🌐 代理设置</button>
<button class="tab-btn" data-tab="webui">🔒 访问控制</button>
<button class="tab-btn" data-tab="upload">☁️ 上传</button>
<button class="tab-btn" data-tab="outlook">📮 Outlook配置</button>
<button class="tab-btn" data-tab="registration">⚙️ 注册配置</button>
<button class="tab-btn" data-tab="email-code">📧 验证码配置</button>
<button class="tab-btn" data-tab="database">💾 数据库</button>
</div>
<!-- 代理设置 -->
<div class="tab-content active" id="proxy-tab">
<!-- 动态代理配置 -->
<div class="card" style="margin-top: var(--spacing-lg);">
<div class="card-header">
<h3>动态代理配置</h3>
<span class="hint">通过 API 每次获取新代理 IP优先级高于代理列表</span>
</div>
<div class="card-body">
<form id="dynamic-proxy-form">
<div class="form-group">
<label>
<input type="checkbox" id="dynamic-proxy-enabled" name="enabled">
启用动态代理
</label>
</div>
<div class="form-group">
<label for="dynamic-proxy-api-url">代理 API 地址</label>
<input type="text" id="dynamic-proxy-api-url" name="api_url" placeholder="http://api.example.com/get_proxy">
<small style="color: var(--text-muted);">每次注册任务启动时调用此 API 获取代理 URL</small>
</div>
<div class="form-row">
<div class="form-group">
<label for="dynamic-proxy-api-key">API 密钥 (可选)</label>
<input type="password" id="dynamic-proxy-api-key" name="api_key" placeholder="留空保持不变" autocomplete="new-password">
</div>
<div class="form-group">
<label for="dynamic-proxy-api-key-header">密钥请求头</label>
<input type="text" id="dynamic-proxy-api-key-header" name="api_key_header" value="X-API-Key">
</div>
</div>
<div class="form-group">
<label for="dynamic-proxy-result-field">JSON 字段路径 (可选)</label>
<input type="text" id="dynamic-proxy-result-field" name="result_field" placeholder="例如: data.proxy 或留空使用响应原文">
<small style="color: var(--text-muted);">若 API 返回 JSON填写点号分隔的字段路径提取代理 URL留空则将响应原文作为代理 URL</small>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存设置</button>
<button type="button" class="btn btn-secondary" id="test-dynamic-proxy-btn">🔌 测试动态代理</button>
</div>
</form>
</div>
</div>
<!-- 代理列表 -->
<div class="card" style="margin-top: var(--spacing-lg);">
<div class="card-header">
<h3>代理列表</h3>
<div style="display: flex; gap: var(--spacing-sm);">
<button class="btn btn-secondary btn-sm" id="test-all-proxies-btn">🔌 测试全部</button>
<button class="btn btn-primary btn-sm" id="add-proxy-btn"> 添加代理</button>
</div>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width: 50px;">ID</th>
<th>名称</th>
<th>类型</th>
<th>地址</th>
<th style="width: 60px;">默认</th>
<th style="width: 80px;">状态</th>
<th style="width: 120px;">最后使用</th>
<th style="width: 180px;">操作</th>
</tr>
</thead>
<tbody id="proxies-table">
<tr>
<td colspan="7">
<div class="empty-state">
<div class="empty-state-icon">🌐</div>
<div class="empty-state-title">暂无代理</div>
<div class="empty-state-description">点击"添加代理"按钮添加代理服务器</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 访问控制 -->
<div class="tab-content" id="webui-tab">
<div class="card">
<div class="card-header">
<h3>Web UI 访问密码</h3>
<span class="hint">用于访问页面的密码,留空表示不修改</span>
</div>
<div class="card-body">
<form id="webui-settings-form">
<div class="form-group">
<label for="webui-access-password">访问密码</label>
<input type="password" id="webui-access-password" name="access_password" placeholder="留空保持不变" autocomplete="new-password">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存设置</button>
</div>
</form>
</div>
</div>
</div>
<!-- 添加代理模态框 -->
<div class="modal" id="add-proxy-modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="proxy-modal-title">添加代理</h3>
<button class="modal-close" id="close-proxy-modal">&times;</button>
</div>
<div class="modal-body">
<form id="proxy-item-form">
<input type="hidden" id="proxy-item-id">
<div class="form-group">
<label for="proxy-item-name">名称</label>
<input type="text" id="proxy-item-name" name="name" required placeholder="例如:美国代理 1">
</div>
<div class="form-row">
<div class="form-group">
<label for="proxy-item-type">类型</label>
<select id="proxy-item-type" name="type">
<option value="http">HTTP</option>
<option value="socks5">SOCKS5</option>
</select>
</div>
<div class="form-group">
<label for="proxy-item-host">主机地址</label>
<input type="text" id="proxy-item-host" name="host" required placeholder="127.0.0.1">
</div>
<div class="form-group">
<label for="proxy-item-port">端口</label>
<input type="number" id="proxy-item-port" name="port" required placeholder="7890">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="proxy-item-username">用户名 (可选)</label>
<input type="text" id="proxy-item-username" name="username" autocomplete="off">
</div>
<div class="form-group">
<label for="proxy-item-password">密码 (可选)</label>
<input type="password" id="proxy-item-password" name="password" autocomplete="new-password">
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancel-proxy-btn">取消</button>
<button type="submit" class="btn btn-primary">💾 保存</button>
</div>
</form>
</div>
</div>
</div>
<!-- 上传服务设置CPA + Sub2API + Team Manager -->
<div class="tab-content" id="upload-tab">
<!-- CPA 服务管理 -->
<div class="card" style="margin-top: var(--spacing-lg);">
<div class="card-header">
<h3>☁️ CPA 服务</h3>
<button class="btn btn-primary btn-sm" id="add-cpa-service-btn">+ 添加服务</button>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width:150px;">名称</th>
<th>API URL</th>
<th style="width:90px;">代理写入</th>
<th style="width:80px;">状态</th>
<th style="width:60px;text-align:center;">优先级</th>
<th style="width:220px;">操作</th>
</tr>
</thead>
<tbody id="cpa-services-table">
<tr><td colspan="6" style="text-align:center;color:var(--text-muted);padding:20px;">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Sub2API 服务管理 -->
<div class="card" style="margin-top: var(--spacing-lg);">
<div class="card-header">
<h3>🔗 Sub2API 服务</h3>
<button class="btn btn-primary btn-sm" id="add-sub2api-service-btn">+ 添加服务</button>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width:150px;">名称</th>
<th>API URL</th>
<th style="width:80px;">状态</th>
<th style="width:60px;text-align:center;">优先级</th>
<th style="width:220px;">操作</th>
</tr>
</thead>
<tbody id="sub2api-services-table">
<tr><td colspan="5" style="text-align:center;color:var(--text-muted);padding:20px;">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Team Manager 服务管理 -->
<div class="card" style="margin-top: var(--spacing-lg);">
<div class="card-header">
<h3>🚀 Team Manager 服务</h3>
<button class="btn btn-primary btn-sm" id="add-tm-service-btn">+ 添加服务</button>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width:150px;">名称</th>
<th>API URL</th>
<th style="width:80px;">状态</th>
<th style="width:60px;text-align:center;">优先级</th>
<th style="width:220px;">操作</th>
</tr>
</thead>
<tbody id="tm-services-table">
<tr><td colspan="5" style="text-align:center;color:var(--text-muted);padding:20px;">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Team Manager 服务编辑模态框 -->
<div class="modal" id="tm-service-edit-modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3 id="tm-service-modal-title">添加 Team Manager 服务</h3>
<button class="modal-close" id="close-tm-service-modal">&times;</button>
</div>
<div class="modal-body">
<form id="tm-service-form">
<input type="hidden" id="tm-service-id">
<div class="form-group">
<label for="tm-service-name">名称 *</label>
<input type="text" id="tm-service-name" placeholder="例如: 主服务" required>
</div>
<div class="form-group">
<label for="tm-service-url">API URL *</label>
<input type="text" id="tm-service-url" placeholder="https://tm.example.com" required>
</div>
<div class="form-group">
<label for="tm-service-key">API Key</label>
<input type="password" id="tm-service-key" placeholder="编辑时留空则保持原值" autocomplete="new-password">
</div>
<div class="form-row">
<div class="form-group">
<label for="tm-service-priority">优先级</label>
<input type="number" id="tm-service-priority" value="0" min="0">
<p class="hint">数字越小优先级越高</p>
</div>
<div class="form-group">
<label>&nbsp;</label>
<label style="margin-top:10px;display:flex;align-items:center;gap:8px;">
<input type="checkbox" id="tm-service-enabled" checked> 启用
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存</button>
<button type="button" class="btn btn-secondary" id="test-tm-service-btn">🔌 测试连接</button>
<button type="button" class="btn btn-secondary" id="cancel-tm-service-btn">取消</button>
</div>
</form>
</div>
</div>
</div>
<!-- Sub2API 服务编辑模态框 -->
<div class="modal" id="sub2api-service-edit-modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3 id="sub2api-service-modal-title">添加 Sub2API 服务</h3>
<button class="modal-close" id="close-sub2api-service-modal">&times;</button>
</div>
<div class="modal-body">
<form id="sub2api-service-form">
<input type="hidden" id="sub2api-service-id">
<div class="form-group">
<label for="sub2api-service-name">名称 *</label>
<input type="text" id="sub2api-service-name" placeholder="例如: 主服务" required>
</div>
<div class="form-group">
<label for="sub2api-service-url">API URL *</label>
<input type="text" id="sub2api-service-url" placeholder="http://host" required>
</div>
<div class="form-group">
<label for="sub2api-service-key">API Key</label>
<input type="password" id="sub2api-service-key" placeholder="编辑时留空则保持原值" autocomplete="new-password">
</div>
<div class="form-row">
<div class="form-group">
<label for="sub2api-service-priority">优先级</label>
<input type="number" id="sub2api-service-priority" value="0" min="0">
<p class="hint">数字越小优先级越高</p>
</div>
<div class="form-group">
<label>&nbsp;</label>
<label style="margin-top:10px;display:flex;align-items:center;gap:8px;">
<input type="checkbox" id="sub2api-service-enabled" checked> 启用
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存</button>
<button type="button" class="btn btn-secondary" id="test-sub2api-service-btn">🔌 测试连接</button>
<button type="button" class="btn btn-secondary" id="cancel-sub2api-service-btn">取消</button>
</div>
</form>
</div>
</div>
</div>
<!-- CPA 服务编辑模态框 -->
<div class="modal" id="cpa-service-edit-modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3 id="cpa-service-modal-title">添加 CPA 服务</h3>
<button class="modal-close" id="close-cpa-service-modal">&times;</button>
</div>
<div class="modal-body">
<form id="cpa-service-form">
<input type="hidden" id="cpa-service-id">
<div class="form-group">
<label for="cpa-service-name">名称 *</label>
<input type="text" id="cpa-service-name" placeholder="例如: 主服务" required>
</div>
<div class="form-group">
<label for="cpa-service-url">API URL *</label>
<input type="text" id="cpa-service-url" placeholder="https://cpa.example.com" required>
<p class="hint">支持填写根地址、`/v0/management` 或完整的 `/v0/management/auth-files` 地址</p>
</div>
<div class="form-group">
<label for="cpa-service-token">API Token</label>
<input type="password" id="cpa-service-token" placeholder="编辑时留空则保持原值" autocomplete="new-password">
</div>
<div class="form-row">
<div class="form-group">
<label for="cpa-service-priority">优先级</label>
<input type="number" id="cpa-service-priority" value="0" min="0">
<p class="hint">数字越小优先级越高</p>
</div>
<div class="form-group">
<label>&nbsp;</label>
<label style="margin-top:10px;display:flex;align-items:center;gap:8px;">
<input type="checkbox" id="cpa-service-enabled" checked> 启用
</label>
<label style="margin-top:10px;display:flex;align-items:center;gap:8px;">
<input type="checkbox" id="cpa-service-include-proxy-url"> 写入 auth file 的 <code>proxy_url</code>
</label>
<p class="hint">开启后,若账号记录了实际使用代理(含动态代理),上传到 CPA 时会一并写入。</p>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存</button>
<button type="button" class="btn btn-secondary" id="test-cpa-service-btn">🔌 测试连接</button>
<button type="button" class="btn btn-secondary" id="cancel-cpa-service-btn">取消</button>
</div>
</form>
</div>
</div>
</div>
<!-- Outlook 配置 -->
<div class="tab-content" id="outlook-tab">
<div class="card">
<div class="card-header">
<h3>Outlook OAuth 配置</h3>
<span class="hint">配置 Outlook 邮箱 OAuth 认证参数</span>
</div>
<div class="card-body">
<form id="outlook-settings-form">
<div class="form-group">
<label for="outlook-default-client-id">默认 Client ID</label>
<input type="text" id="outlook-default-client-id" name="default_client_id"
placeholder="24d9a0ed-8787-4584-883c-2fd79308940a">
<p class="hint">Outlook OAuth 应用的 Client ID。导入账户时未填写 client_id 则使用此默认值。</p>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存设置</button>
</div>
</form>
</div>
</div>
</div>
<!-- 注册配置 -->
<div class="tab-content" id="registration-tab">
<div class="card">
<div class="card-header">
<h3>注册配置</h3>
</div>
<div class="card-body">
<form id="registration-settings-form">
<div class="form-row">
<div class="form-group">
<label for="max-retries">最大重试次数</label>
<input type="number" id="max-retries" name="max_retries" value="3" min="1" max="10">
</div>
<div class="form-group">
<label for="timeout">超时时间 (秒)</label>
<input type="number" id="timeout" name="timeout" value="120" min="30" max="600">
</div>
<div class="form-group">
<label for="password-length">密码长度</label>
<input type="number" id="password-length" name="default_password_length" value="12" min="8" max="32">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="sleep-min">最小等待时间 (秒)</label>
<input type="number" id="sleep-min" name="sleep_min" value="5" min="1" max="60">
</div>
<div class="form-group">
<label for="sleep-max">最大等待时间 (秒)</label>
<input type="number" id="sleep-max" name="sleep_max" value="30" min="5" max="120">
</div>
</div>
<div class="form-row" style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border-color, #e0e0e0);">
<div class="form-group">
<label for="registration-engine">注册引擎</label>
<select id="registration-engine" name="engine">
<option value="http">HTTP 引擎(默认)</option>
<option value="playwright">Playwright 浏览器引擎</option>
</select>
<p class="hint">Playwright 使用真实浏览器环境,解决 workspace Cookie 问题</p>
</div>
<div class="form-group">
<label for="playwright-pool-size">Playwright 池大小</label>
<input type="number" id="playwright-pool-size" name="playwright_pool_size" value="5" min="1" max="20">
<p class="hint">并发浏览器上下文数量</p>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存设置</button>
</div>
</form>
</div>
</div>
</div>
<!-- 验证码配置 -->
<div class="tab-content" id="email-code-tab">
<div class="card">
<div class="card-header">
<h3>验证码等待配置</h3>
<span class="hint">配置 Outlook 邮箱验证码获取的超时时间和轮询间隔</span>
</div>
<div class="card-body">
<form id="email-code-form">
<div class="form-row">
<div class="form-group">
<label for="email-code-timeout">等待超时 (秒)</label>
<input type="number" id="email-code-timeout" name="timeout" value="120" min="30" max="600">
<span class="hint">等待验证码的最大时间,建议 60-300 秒</span>
</div>
<div class="form-group">
<label for="email-code-poll-interval">轮询间隔 (秒)</label>
<input type="number" id="email-code-poll-interval" name="poll_interval" value="3" min="1" max="30">
<span class="hint">检查邮箱的时间间隔,建议 2-5 秒</span>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 保存设置</button>
</div>
</form>
</div>
</div>
<div class="card" style="margin-top: var(--spacing-lg);">
<div class="card-header">
<h3>验证码获取策略</h3>
</div>
<div class="card-body">
<ul class="info-list">
<li><strong>渐进式检查</strong>:前 3 次轮询只检查未读邮件,之后检查所有邮件</li>
<li><strong>时间戳过滤</strong>:自动跳过 OTP 发送前的旧邮件</li>
<li><strong>验证码去重</strong>:避免重复使用同一验证码</li>
<li><strong>多策略提取</strong>:主题优先 → 语义匹配 → 兜底匹配</li>
<li><strong>发件人验证</strong>:严格验证邮件来自 OpenAI 官方</li>
</ul>
</div>
</div>
</div>
<!-- 数据库 -->
<div class="tab-content" id="database-tab">
<div class="card">
<div class="card-header">
<h3>数据库信息</h3>
</div>
<div class="card-body">
<div class="info-grid" style="margin-bottom: var(--spacing-lg);">
<div class="info-item">
<span class="label">数据库大小</span>
<span id="db-size" class="value">-</span>
</div>
<div class="info-item">
<span class="label">账号数量</span>
<span id="db-accounts" class="value">-</span>
</div>
<div class="info-item">
<span class="label">邮箱服务数量</span>
<span id="db-services" class="value">-</span>
</div>
<div class="info-item">
<span class="label">任务记录数量</span>
<span id="db-tasks" class="value">-</span>
</div>
</div>
<div class="form-actions">
<button class="btn btn-secondary" id="backup-btn">💾 备份数据库</button>
<button class="btn btn-warning" id="cleanup-btn">🧹 清理过期数据</button>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="/static/js/utils.js?v={{ static_version }}"></script>
<script src="/static/js/settings.js?v={{ static_version }}"></script>
</body>
</html>