mirror of
https://github.com/mskatoni/ni-mail.git
synced 2026-08-29 12:07:24 +08:00
2226 lines
85 KiB
HTML
2226 lines
85 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html lang="zh-CN">
|
||
|
||
|
||
|
||
<head>
|
||
|
||
<meta charset="UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
<title>Outlook 邮件管理</title>
|
||
|
||
<link rel="icon" type="image/png" href="/img/ico.png">
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.8/dist/purify.min.js"></script>
|
||
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}?v={{ APP_VERSION }}-resp">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
<!-- 版本更新提示横幅(默认隐藏) -->
|
||
|
||
<div id="versionUpdateBanner" class="version-update-banner d-none">
|
||
|
||
<span id="versionUpdateMsg"></span>
|
||
|
||
<button id="btnTriggerUpdate" class="btn btn-sm btn-warning ms-3" onclick="triggerUpdate()">立即更新</button>
|
||
|
||
<button class="btn btn-sm btn-link ms-1" onclick="dismissVersionBanner()">忽略</button>
|
||
|
||
</div>
|
||
|
||
<div id="app">
|
||
|
||
<!-- ===== Sidebar ===== -->
|
||
|
||
<aside class="sidebar" id="sidebar">
|
||
|
||
<div class="sidebar-logo">
|
||
|
||
<div class="logo-mark">📬</div>
|
||
|
||
<div>
|
||
|
||
<span>Outlook 邮件</span>
|
||
|
||
<small>v{{ APP_VERSION }}</small>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<nav class="sidebar-nav">
|
||
|
||
<div class="nav-section">概览</div>
|
||
|
||
<button class="nav-item active" data-page="dashboard" onclick="navigate('dashboard')">
|
||
|
||
<span class="nav-icon">🏠</span><span>数据概览</span>
|
||
|
||
</button>
|
||
|
||
|
||
|
||
<div class="nav-section">邮箱管理</div>
|
||
|
||
<button class="nav-item" data-page="mailbox" onclick="navigate('mailbox')">
|
||
|
||
<span class="nav-icon">📬</span><span>账号管理</span>
|
||
|
||
</button>
|
||
|
||
<button class="nav-item" data-page="pool-admin" onclick="navigate('pool-admin')">
|
||
|
||
<span class="nav-icon">🎱</span><span>号池管理</span>
|
||
|
||
</button>
|
||
|
||
<button class="nav-item" data-page="temp-emails" onclick="navigate('temp-emails')">
|
||
|
||
<span class="nav-icon">⚡</span><span>临时邮箱</span>
|
||
|
||
</button>
|
||
|
||
|
||
|
||
<div class="nav-section">Token</div>
|
||
|
||
{% if OAUTH_TOOL_ENABLED %}
|
||
|
||
<button class="nav-item" onclick="window.open('/token-tool', 'token-tool', 'width=720,height=860,scrollbars=yes')" title="OAuth Token 工具">
|
||
|
||
<span class="nav-icon">🔑</span><span>Token 工具</span>
|
||
|
||
</button>
|
||
|
||
{% endif %}
|
||
|
||
<button class="nav-item" data-page="refresh-log" onclick="navigate('refresh-log')">
|
||
|
||
<span class="nav-icon">🔄</span><span>刷新日志</span>
|
||
|
||
</button>
|
||
|
||
|
||
|
||
<div class="nav-section">系统</div>
|
||
|
||
<button class="nav-item" data-page="settings" onclick="navigate('settings')">
|
||
|
||
<span class="nav-icon">⚙️</span><span>系统设置</span>
|
||
|
||
</button>
|
||
|
||
<button class="nav-item" data-page="audit" onclick="navigate('audit')">
|
||
|
||
<span class="nav-icon">📋</span><span>审计日志</span>
|
||
|
||
</button>
|
||
|
||
</nav>
|
||
|
||
|
||
|
||
<div class="sidebar-bottom">
|
||
|
||
<div class="user-chip">
|
||
|
||
<div class="user-avatar">管</div>
|
||
|
||
<div class="user-chip-info">
|
||
|
||
<div class="user-chip-name">管理员</div>
|
||
|
||
<div class="user-chip-role">Outlook 管理</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<a href="https://github.com/ZeroPointSix/outlookEmailPlus" target="_blank" class="btn-github" title="访问 GitHub 仓库">
|
||
|
||
<svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor">
|
||
|
||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||
|
||
</svg>
|
||
|
||
<span>GitHub</span>
|
||
|
||
</a>
|
||
|
||
<button class="btn-theme" id="themeToggleBtn" onclick="toggleTheme()">☀ 浅色模式</button>
|
||
|
||
<button class="btn-logout" onclick="logout()">退出登录</button>
|
||
|
||
</div>
|
||
|
||
</aside>
|
||
|
||
|
||
|
||
<!-- Mobile sidebar backdrop -->
|
||
|
||
<div class="sidebar-backdrop" id="sidebarBackdrop" onclick="closeSidebar()"></div>
|
||
|
||
|
||
|
||
<!-- ===== Content ===== -->
|
||
|
||
<div class="content">
|
||
|
||
<!-- Topbar -->
|
||
|
||
<header class="topbar" id="topbar">
|
||
|
||
<div class="topbar-left">
|
||
|
||
<button class="hamburger-btn" onclick="toggleSidebar()">☰</button>
|
||
|
||
<div>
|
||
|
||
<div class="topbar-title" id="topbarTitle">数据概览</div>
|
||
|
||
<div class="topbar-subtitle" id="topbarSubtitle">运营数据大盘</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="topbar-actions"></div>
|
||
|
||
</header>
|
||
|
||
|
||
|
||
<!-- Refresh progress bar (hidden by default) -->
|
||
|
||
<div class="refresh-progress-bar" id="refreshProgressBar" style="display:none;">
|
||
|
||
<span>🔄</span>
|
||
|
||
<span class="progress-text" id="refreshProgressText">正在刷新 Token…</span>
|
||
|
||
<div class="progress-track">
|
||
|
||
<div class="progress-fill" id="refreshProgressFill" style="width:0%"></div>
|
||
|
||
</div>
|
||
|
||
<span class="progress-text" id="refreshProgressCount">0 / 0</span>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="stopRefresh()">停止</button>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- ===== Page: Dashboard ===== -->
|
||
|
||
<div class="page" id="page-dashboard">
|
||
|
||
<div style="display:none;">
|
||
|
||
<div id="statTotalAccounts"></div>
|
||
|
||
<div id="statValidTokens"></div>
|
||
|
||
<div id="statExpiredTokens"></div>
|
||
|
||
<div id="statTempEmails"></div>
|
||
|
||
<ul id="dashboardGroupList"></ul>
|
||
|
||
</div>
|
||
|
||
<div class="overview-tab-shell ov-apple-shell">
|
||
|
||
<div class="ov-page-header">
|
||
|
||
<div class="ov-page-heading">
|
||
|
||
<div class="ov-page-eyebrow" id="ov-page-eyebrow">玻璃态概览面板</div>
|
||
|
||
<div class="ov-page-title-row">
|
||
|
||
<div class="ov-page-title" id="ov-page-title">📊 数据概览</div>
|
||
|
||
<span class="ov-page-badge" id="ov-page-badge">细腻卡片视图</span>
|
||
|
||
</div>
|
||
|
||
<div class="ov-page-subtitle" id="ov-page-subtitle">账号、验证码、对外 API、邮箱池与系统活动统一看板</div>
|
||
|
||
</div>
|
||
|
||
<div class="ov-page-actions">
|
||
|
||
<span class="ov-refresh-hint"><span id="ov-refresh-label">最近刷新:</span><strong id="ov-last-refresh">--</strong></span>
|
||
|
||
<button class="btn btn-sm btn-ghost ov-refresh-btn" id="ov-refresh-btn">⟳ 刷新</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="settings-tab-nav ov-tab-nav">
|
||
|
||
<button class="settings-tab ov-tab active" data-tab="summary">📊 <span class="ov-tab-label">总览</span></button>
|
||
|
||
<button class="settings-tab ov-tab" data-tab="verification">🔑 <span class="ov-tab-label">验证码提取</span></button>
|
||
|
||
<button class="settings-tab ov-tab" data-tab="external-api">🌐 <span class="ov-tab-label">对外 API</span></button>
|
||
|
||
<button class="settings-tab ov-tab" data-tab="pool">🎱 <span class="ov-tab-label">邮箱池</span></button>
|
||
|
||
<button class="settings-tab ov-tab" data-tab="activity">📋 <span class="ov-tab-label">系统活动</span></button>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="settings-tab-pane ov-tab-pane active" data-tab="summary">
|
||
|
||
<div id="ov-summary-body" class="ov-pane-body">
|
||
|
||
<div class="ov-empty">加载中…</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="settings-tab-pane ov-tab-pane" data-tab="verification">
|
||
|
||
<div id="ov-verification-body" class="ov-pane-body">
|
||
|
||
<div class="ov-empty">点击后加载</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="settings-tab-pane ov-tab-pane" data-tab="external-api">
|
||
|
||
<div id="ov-external-api-body" class="ov-pane-body">
|
||
|
||
<div class="ov-empty">点击后加载</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="settings-tab-pane ov-tab-pane" data-tab="pool">
|
||
|
||
<div id="ov-pool-body" class="ov-pane-body">
|
||
|
||
<div class="ov-empty">点击后加载</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="settings-tab-pane ov-tab-pane" data-tab="activity">
|
||
|
||
<div id="ov-activity-body" class="ov-pane-body">
|
||
|
||
<div class="ov-empty">点击后加载</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- ===== Page: Mailbox (3-column) ===== -->
|
||
|
||
<div class="page page-hidden page-no-padding page-workspace" id="page-mailbox">
|
||
|
||
<template id="mailboxViewModeSwitcherTemplate">
|
||
|
||
<div id="mailboxViewModeSwitcher" class="mailbox-view-switcher mode-switch">
|
||
|
||
<button class="mode-pill active" id="mailboxStandardModeBtn" onclick="switchMailboxViewMode('standard')">标准模式</button>
|
||
|
||
<button class="mode-pill" id="mailboxCompactModeBtn" onclick="switchMailboxViewMode('compact')">简洁模式</button>
|
||
|
||
</div>
|
||
|
||
</template>
|
||
|
||
|
||
|
||
<div class="workspace workspace-mailbox" id="mailboxStandardLayout" data-workspace="mailbox">
|
||
|
||
<!-- Groups Column -->
|
||
|
||
<div class="workspace-panel groups-column" id="groupPanel">
|
||
|
||
<div class="column-header">
|
||
|
||
<span class="column-title">分组</span>
|
||
|
||
<button class="btn-icon" onclick="showAddGroupModal()" title="添加分组">+</button>
|
||
|
||
</div>
|
||
|
||
<div class="column-search">
|
||
|
||
<input type="text" id="groupSearchInput" placeholder="搜索分组…" oninput="filterGroups(this.value)">
|
||
|
||
</div>
|
||
|
||
<div class="column-body" id="groupList">
|
||
|
||
<div class="loading-overlay"><span class="spinner"></span> 加载中…</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Resize Handle 1 -->
|
||
|
||
<div class="workspace-resizer" id="resizeHandle1" data-left="groupPanel" data-right="accountPanel" data-resize-role="groups-to-accounts"></div>
|
||
|
||
|
||
|
||
<!-- Accounts Column -->
|
||
|
||
<div class="workspace-panel accounts-column" id="accountPanel">
|
||
|
||
<div class="column-header">
|
||
|
||
<span class="column-title">
|
||
|
||
<button class="btn-icon btn-toggle-groups" id="btnToggleGroups" onclick="toggleGroupsColumn()" title="展开分组">
|
||
|
||
☰
|
||
|
||
</button>
|
||
|
||
<span class="group-color-dot" id="currentGroupColor" style="background-color:#666;"></span>
|
||
|
||
<span id="currentGroupName">选择分组</span>
|
||
|
||
</span>
|
||
|
||
<div style="display:flex;gap:4px;align-items:center;">
|
||
|
||
<button class="btn-icon" onclick="showTagManagementModal()" title="管理标签">🏷️</button>
|
||
|
||
<button class="btn-icon" onclick="showAddAccountModal()" title="导入账号">+</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="column-search">
|
||
|
||
<input type="text" id="globalSearch" placeholder="搜索邮箱地址…" autocomplete="off">
|
||
|
||
</div>
|
||
|
||
<div style="padding:6px 12px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:8px;font-size:0.78rem;flex-shrink:0;">
|
||
|
||
<label style="color:var(--text-muted);font-weight:500;">排序:</label>
|
||
|
||
<button class="btn btn-sm sort-btn active" data-sort="refresh_time" onclick="sortAccounts('refresh_time')">🕐 刷新时间</button>
|
||
|
||
<button class="btn btn-sm sort-btn" data-sort="email" onclick="sortAccounts('email')">📧 邮箱名</button>
|
||
|
||
<div style="margin-left:auto;display:flex;align-items:center;gap:4px;">
|
||
|
||
<input type="checkbox" id="selectAllCheckbox" onclick="toggleSelectAll()" style="cursor:pointer;">
|
||
|
||
<label for="selectAllCheckbox" style="font-size:0.72rem;color:var(--text-muted);cursor:pointer;">全选</label>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="tagFilterContainer" style="padding:6px 12px;border-bottom:1px solid var(--border-light);display:none;flex-wrap:wrap;gap:6px;"></div>
|
||
|
||
<div class="column-body" id="accountList">
|
||
|
||
<div class="empty-state">
|
||
|
||
<span class="empty-icon">📁</span>
|
||
|
||
<p>请从左侧选择一个分组</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="batchActionBar" class="batch-action-bar" style="display:none;">
|
||
|
||
<span id="selectedCount" style="font-size:0.78rem;color:var(--text-muted);">已选 0 项</span>
|
||
|
||
<div style="flex:1;"></div>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="showBatchTagModal('add')">打标签</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="showBatchTagModal('remove')">去标签</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="showBatchMoveGroupModal()">移动分组</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="showBatchFetchConfirm()">批量拉取邮件</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="batchNotificationToggle(true)">开启通知</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="batchNotificationToggle(false)">关闭通知</button>
|
||
|
||
<button class="btn btn-sm btn-primary" onclick="showBatchRefreshConfirm()">🔄 刷新 Token</button>
|
||
|
||
<button class="btn btn-sm btn-danger" onclick="showBatchDeleteConfirm()">删除</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Resize Handle 2 -->
|
||
|
||
<div class="workspace-resizer" id="resizeHandle2" data-left="accountPanel" data-right="emailListPanel" data-resize-role="accounts-to-emails"></div>
|
||
|
||
|
||
|
||
<!-- Emails Column -->
|
||
|
||
<div class="workspace-panel emails-column" id="emailListPanel">
|
||
|
||
<div class="column-header">
|
||
|
||
<div style="display:flex;align-items:center;gap:8px;">
|
||
|
||
<div class="email-tabs" id="folderTabs" style="display:none;border:none;">
|
||
|
||
<button class="email-tab active" data-folder="inbox" onclick="switchFolder('inbox')">📨 收件箱</button>
|
||
|
||
<button class="email-tab" data-folder="junkemail" onclick="switchFolder('junkemail')">⚠️ 垃圾邮件</button>
|
||
|
||
</div>
|
||
|
||
<span style="font-size:0.82rem;color:var(--text-muted);" id="emailCount"></span>
|
||
|
||
<span id="methodTag" style="display:none;" class="badge badge-gray"></span>
|
||
|
||
</div>
|
||
|
||
<div style="display:flex;gap:4px;">
|
||
|
||
<button class="btn btn-sm btn-primary" onclick="refreshEmails()">获取邮件</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Current account info bar -->
|
||
|
||
<div id="currentAccountBar" style="display:none;padding:0.5rem 1rem;background:var(--bg-card);border-bottom:1px solid var(--border-light);font-size:0.82rem;">
|
||
|
||
<span id="currentAccount" onclick="copyCurrentEmail()" title="点击复制邮箱地址" style="cursor:pointer;">
|
||
|
||
当前邮箱:<strong id="currentAccountEmail"></strong>
|
||
|
||
</span>
|
||
|
||
</div>
|
||
|
||
<div class="column-body" id="emailList">
|
||
|
||
<div class="empty-state">
|
||
|
||
<span class="empty-icon">📬</span>
|
||
|
||
<p>请从左侧选择一个邮箱账号</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="emailBatchActionBar" class="batch-action-bar" style="display:none;">
|
||
|
||
<span id="emailSelectedCount" style="font-size:0.78rem;color:var(--text-muted);">已选 0 项</span>
|
||
|
||
<div style="flex:1;"></div>
|
||
|
||
<button class="btn btn-sm btn-danger" onclick="confirmBatchDeleteEmails()">🗑️ 删除</button>
|
||
|
||
</div>
|
||
|
||
<div id="emailDetailSection" style="display:none;border-top:1px solid var(--border);">
|
||
|
||
<div class="email-detail-toolbar" id="emailDetailToolbar">
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="showEmailList()">← 返回</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="openFullscreenEmail()">🔍 全屏查看</button>
|
||
|
||
<button class="btn btn-sm btn-accent" onclick="extractVerificationFromDetail(this)" title="从邮件正文提取验证码/链接" style="font-size:0.72rem;padding:2px 8px;">🔑 验证码</button>
|
||
|
||
<label style="display:flex;align-items:center;gap:4px;font-size:0.78rem;margin-left:8px;cursor:pointer;">
|
||
|
||
<input type="checkbox" id="trustEmailCheckbox" onchange="toggleTrustMode(this)">
|
||
|
||
<span style="user-select:none;">信任此邮件</span>
|
||
|
||
</label>
|
||
|
||
<div style="flex:1;"></div>
|
||
|
||
<button class="btn btn-sm btn-danger" onclick="confirmDeleteCurrentEmail()">🗑️ 删除</button>
|
||
|
||
</div>
|
||
|
||
<div class="email-detail-container" id="emailDetail">
|
||
|
||
<div class="empty-state">
|
||
|
||
<span class="empty-icon">📄</span>
|
||
|
||
<p>选择一封邮件查看详情</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div id="mailboxCompactLayout" class="mailbox-compact-layout" style="display:none;">
|
||
|
||
<div class="compact-board">
|
||
|
||
<div class="board-toolbar">
|
||
|
||
<div class="toolbar-left">
|
||
|
||
<span id="compactModeSummary" class="toolbar-meta compact-mode-summary">请选择分组</span>
|
||
|
||
</div>
|
||
|
||
<div class="toolbar-right">
|
||
|
||
<button class="btn-inline ghost" onclick="showTagManagementModal()">管理标签</button>
|
||
|
||
<details class="action-menu">
|
||
|
||
<summary class="btn-inline ghost" aria-label="账号操作" title="账号操作">账号操作</summary>
|
||
|
||
<div class="menu-panel">
|
||
|
||
<button class="menu-item" onclick="event.preventDefault(); event.stopPropagation(); closeCompactMenu(this); showAddAccountModal()">添加账号</button>
|
||
|
||
<button class="menu-item" onclick="event.preventDefault(); event.stopPropagation(); closeCompactMenu(this); showExportModal()">导出账号</button>
|
||
|
||
<button class="menu-item" onclick="event.preventDefault(); event.stopPropagation(); closeCompactMenu(this); showRefreshModal()">全量刷新 Token</button>
|
||
|
||
</div>
|
||
|
||
</details>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div id="compactGroupStrip" class="group-strip"></div>
|
||
|
||
|
||
|
||
<div class="mail-list-wrap">
|
||
|
||
<div class="mail-head">
|
||
|
||
<div class="compact-header-cell compact-header-cell-selection">
|
||
|
||
<input type="checkbox" id="compactSelectAllCheckbox" onclick="toggleSelectAll()">
|
||
|
||
</div>
|
||
|
||
<div class="compact-header-cell">邮箱</div>
|
||
|
||
<div class="compact-header-cell">验证码</div>
|
||
|
||
<div class="compact-header-cell">最新邮件</div>
|
||
|
||
<div class="compact-header-cell">标签</div>
|
||
|
||
<div class="compact-header-cell compact-header-cell-actions">操作</div>
|
||
|
||
</div>
|
||
|
||
<div id="compactAccountList" class="mail-rows"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div id="compactBatchActionBar" class="batch-bar" style="display:none;">
|
||
|
||
<span id="compactSelectedCount" class="batch-info">已选 0 项</span>
|
||
|
||
<div class="batch-actions">
|
||
|
||
<button class="btn-inline ghost" onclick="showBatchTagModal('add')">打标签</button>
|
||
|
||
<button class="btn-inline ghost" onclick="showBatchTagModal('remove')">去标签</button>
|
||
|
||
<button class="btn-inline" onclick="showBatchMoveGroupModal()">移动分组</button>
|
||
|
||
<button class="btn-inline" onclick="showBatchRefreshConfirm()">🔄 刷新 Token</button>
|
||
|
||
<button class="btn-inline primary" onclick="showBatchDeleteConfirm()">删除</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- ===== Page: Temp Emails ===== -->
|
||
|
||
<div class="page page-hidden page-no-padding page-workspace" id="page-temp-emails">
|
||
|
||
<div class="workspace workspace-temp-emails" data-workspace="temp-emails">
|
||
|
||
<!-- Temp Email List Column -->
|
||
|
||
<div class="workspace-panel accounts-column" id="tempEmailPanel" style="width:300px;">
|
||
|
||
<div class="column-header">
|
||
|
||
<span class="column-title">⚡ 临时邮箱</span>
|
||
|
||
<button class="btn btn-sm btn-primary" onclick="generateTempEmail()">+ 创建</button>
|
||
|
||
</div>
|
||
|
||
<div style="padding:12px;border-bottom:1px solid var(--border-light);display:flex;flex-direction:column;gap:8px;">
|
||
|
||
<select class="form-input" id="tempEmailProviderSelect" onchange="onTempEmailProviderChange(this.value)" title="选择创建邮箱的 Provider">
|
||
|
||
<option value="legacy_bridge">🔧 GPTMail (Legacy Bridge)</option>
|
||
|
||
<option value="cloudflare_temp_mail">☁ Cloudflare Temp Mail</option>
|
||
|
||
</select>
|
||
|
||
<input type="text" class="form-input" id="tempEmailPrefixInput" placeholder="前缀(可选)" maxlength="64">
|
||
|
||
<select class="form-input" id="tempEmailDomainSelect" disabled>
|
||
|
||
<option value="">自动分配域名</option>
|
||
|
||
</select>
|
||
|
||
<div id="tempEmailOptionsHint" class="form-hint" style="margin:0;">支持自定义前缀和多域名创建。</div>
|
||
|
||
<div id="tempEmailOptionsStatus" class="form-error" style="display:none;margin:0;"></div>
|
||
|
||
</div>
|
||
|
||
<div class="column-body" id="tempEmailContainer">
|
||
|
||
<div class="empty-state">
|
||
|
||
<span class="empty-icon">📭</span>
|
||
|
||
<p>暂无临时邮箱</p>
|
||
|
||
<button class="btn btn-primary" onclick="generateTempEmail()">创建第一个临时邮箱</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Temp Email Messages Column -->
|
||
|
||
<div class="workspace-panel emails-column" id="tempEmailMessagePanel">
|
||
|
||
<div class="column-header">
|
||
|
||
<span class="column-title" id="tempEmailCurrentName" onclick="copyTempEmailCurrent()" title="点击复制邮箱地址" style="cursor:pointer;">选择一个临时邮箱</span>
|
||
|
||
<div style="display:flex;gap:4px;">
|
||
|
||
<button class="btn btn-sm btn-ghost" id="tempEmailRefreshBtn" onclick="if(currentAccount && isTempEmailGroup) loadTempEmailMessages(currentAccount)" style="display:none;">🔄 获取邮件</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="column-body" id="tempEmailMessageList">
|
||
|
||
<div class="empty-state">
|
||
|
||
<span class="empty-icon">📬</span>
|
||
|
||
<p>选择一个临时邮箱查看邮件</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="workspace-panel emails-column" id="tempEmailDetailSection" style="display:none;border-left:1px solid var(--border);">
|
||
|
||
<div class="column-header">
|
||
|
||
<span class="column-title">邮件详情</span>
|
||
|
||
</div>
|
||
|
||
<div class="email-detail-toolbar" id="tempEmailDetailToolbar" style="display:none;">
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="showEmailList()">← 返回</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="openFullscreenEmail()">🔍 全屏查看</button>
|
||
|
||
<button class="btn btn-sm btn-accent" onclick="extractVerificationFromDetail(this)" title="从邮件正文提取验证码/链接" style="font-size:0.72rem;padding:2px 8px;">🔑 验证码</button>
|
||
|
||
<label style="display:flex;align-items:center;gap:4px;font-size:0.78rem;margin-left:8px;cursor:pointer;">
|
||
|
||
<input type="checkbox" id="tempEmailTrustCheckbox" onchange="toggleTrustMode(this)">
|
||
|
||
<span style="user-select:none;">信任此邮件</span>
|
||
|
||
</label>
|
||
|
||
<div style="flex:1;"></div>
|
||
|
||
<button class="btn btn-sm btn-danger" onclick="confirmDeleteCurrentEmail()">🗑️ 删除</button>
|
||
|
||
</div>
|
||
|
||
<div class="email-detail-container" id="tempEmailDetail">
|
||
|
||
<div class="empty-state">
|
||
|
||
<span class="empty-icon">📄</span>
|
||
|
||
<p>选择一封邮件查看详情</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- ===== Page: Refresh Log ===== -->
|
||
|
||
<div class="page page-hidden" id="page-refresh-log">
|
||
|
||
<div class="card">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">🔄 刷新日志</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" id="refreshLogContainer">
|
||
|
||
<div class="loading-overlay"><span class="spinner"></span> 加载中…</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- ===== Page: Settings ===== -->
|
||
|
||
<div class="page page-hidden" id="page-settings" style="padding: 1.5rem;">
|
||
|
||
<!-- 整体 Tab 容器(Tab 导航 + 内容区共用一个卡片外壳) -->
|
||
|
||
<div class="settings-card-shell">
|
||
|
||
|
||
|
||
<!-- Tab 导航栏 -->
|
||
|
||
<div class="settings-tab-nav" id="settingsTabNav">
|
||
|
||
<button class="settings-tab active" data-tab="basic" onclick="switchSettingsTab('basic')">基础</button>
|
||
|
||
<button class="settings-tab" data-tab="temp-mail" onclick="switchSettingsTab('temp-mail')">临时邮箱</button>
|
||
|
||
<button class="settings-tab" data-tab="api-security" onclick="switchSettingsTab('api-security')">API 安全</button>
|
||
|
||
<button class="settings-tab" data-tab="automation" onclick="switchSettingsTab('automation')">自动化</button>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Tab 内容区 -->
|
||
|
||
<div class="settings-tab-content">
|
||
|
||
|
||
|
||
<!-- Tab 1: 基础 -->
|
||
|
||
<div class="settings-tab-pane active" id="settings-tab-basic">
|
||
|
||
<div class="card">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">⚙️ 基础设置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">登录密码</label>
|
||
|
||
<input type="password" class="form-input" id="settingsPassword" placeholder="输入新密码(留空则不修改)">
|
||
|
||
<div class="form-hint">用于登录系统的密码</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div style="border-top: 1px solid var(--border-light); margin-top: 1rem; padding-top: 1rem;">
|
||
|
||
<div class="card-title" style="font-size: 0.95rem; margin-bottom: 0.8rem;">🤖 验证码 AI 增强</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
|
||
|
||
<input type="checkbox" id="settingsVerificationAiEnabled">
|
||
|
||
<span class="form-label" style="margin:0;">启用验证码 AI 增强(系统级)</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">规则提取优先;仅在规则不足时触发 AI 回退。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">AI Base URL</label>
|
||
|
||
<input type="text" class="form-input" id="settingsVerificationAiBaseUrl" placeholder="https://api.openai.com/v1/chat/completions">
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">AI API Key</label>
|
||
|
||
<input type="password" class="form-input" id="settingsVerificationAiApiKey" placeholder="输入 AI API Key" autocomplete="off">
|
||
|
||
<div class="form-hint" id="verificationAiApiKeyHint">未设置</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">AI 模型 ID</label>
|
||
|
||
<input type="text" class="form-input" id="settingsVerificationAiModel" placeholder="例如:gpt-4.1-mini">
|
||
|
||
</div>
|
||
|
||
<div class="form-group" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||
|
||
<button class="btn btn-outline" type="button" onclick="testVerificationAiConfig()" id="btnTestVerificationAi" style="font-size: 0.85rem;">🤖 测试 AI 配置</button>
|
||
|
||
<span class="form-hint" id="verificationAiTestResult">建议先保存配置再测试。</span>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- 保存按钮 -->
|
||
|
||
<div style="margin-top: 1.5rem;">
|
||
|
||
<button class="btn btn-primary" onclick="saveSettings()">💾 保存设置</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Tab 2: 临时邮箱 -->
|
||
|
||
<div class="settings-tab-pane" id="settings-tab-temp-mail">
|
||
|
||
<!-- Provider 单选按钮组 -->
|
||
|
||
<div class="card" style="margin-bottom: 1rem;">
|
||
|
||
<div class="card-body" style="padding: 1rem;">
|
||
|
||
<div class="form-group" style="margin-bottom: 0;">
|
||
|
||
<label class="form-label">全局临时邮箱 Provider</label>
|
||
|
||
<div class="provider-radio-group">
|
||
|
||
<label class="provider-radio">
|
||
|
||
<input type="radio" name="tempMailProvider" value="legacy_bridge" onchange="onTempMailProviderChange('legacy_bridge')">
|
||
|
||
<span class="provider-radio-label">
|
||
|
||
<span class="provider-name">🔧 GPTMail (Legacy Bridge)</span>
|
||
|
||
<span class="provider-desc">自建域名临时邮箱服务</span>
|
||
|
||
</span>
|
||
|
||
</label>
|
||
|
||
<label class="provider-radio">
|
||
|
||
<input type="radio" name="tempMailProvider" value="cloudflare_temp_mail" onchange="onTempMailProviderChange('cloudflare_temp_mail')">
|
||
|
||
<span class="provider-radio-label">
|
||
|
||
<span class="provider-name">☁ Cloudflare Temp Mail</span>
|
||
|
||
<span class="provider-desc">CF Worker 部署的临时邮箱</span>
|
||
|
||
</span>
|
||
|
||
</label>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- GPTMail 配置面板 -->
|
||
|
||
<div class="card" id="gptmailConfigPanel">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">🔧 GPTMail 配置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">临时邮箱 API Base URL</label>
|
||
|
||
<input type="text" class="form-input" id="settingsTempMailApiBaseUrl" placeholder="https://mail.example.com">
|
||
|
||
<div class="form-hint">上游临时邮箱服务地址,留空则使用默认配置。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">临时邮箱 API Key</label>
|
||
|
||
<input type="text" class="form-input" id="settingsTempMailApiKey" placeholder="输入临时邮箱 API Key">
|
||
|
||
<div class="form-hint">用于临时邮箱能力。旧版临时邮箱 API Key 字段仅保留兼容读取与迁移,不再作为正式配置字段。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">可用域名(JSON)</label>
|
||
|
||
<textarea class="form-input" id="settingsTempMailDomains" rows="4" placeholder='[
|
||
|
||
{"name": "mail.example.com", "enabled": true},
|
||
|
||
{"name": "temp.example.net", "enabled": true}
|
||
|
||
]' style="font-family: monospace; font-size: 0.84rem;"></textarea>
|
||
|
||
<div class="form-hint">支持字符串数组或 `{name, enabled}` 对象数组。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">默认域名</label>
|
||
|
||
<input type="text" class="form-input" id="settingsTempMailDefaultDomain" placeholder="mail.example.com">
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">前缀规则(JSON)</label>
|
||
|
||
<textarea class="form-input" id="settingsTempMailPrefixRules" rows="4" placeholder='{
|
||
|
||
"min_length": 1,
|
||
|
||
"max_length": 32,
|
||
|
||
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
||
|
||
}' style="font-family: monospace; font-size: 0.84rem;"></textarea>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- CF Worker 配置面板 -->
|
||
|
||
<div class="card" id="cfWorkerConfigPanel" style="display: none;">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">☁ Cloudflare Temp Email Worker 配置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<p style="font-size: 0.82rem; color: var(--text-muted); margin-bottom: 1rem;">与 GPTMail 完全独立的配置。部署 dreamhunter2333/cloudflare_temp_email 后填写。</p>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">CF Worker 部署地址</label>
|
||
|
||
<input type="text" class="form-input" id="settingsCfWorkerBaseUrl" placeholder="https://mail.yourname.workers.dev">
|
||
|
||
<div class="form-hint">Cloudflare Worker 的部署 URL,不含尾部斜杠。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">CF Worker Admin 密码</label>
|
||
|
||
<input type="password" class="form-input" id="settingsCfWorkerAdminKey" placeholder="CF Worker 的 ADMIN_PASSWORDS 值" autocomplete="off">
|
||
|
||
<div class="form-hint">对应 CF Worker 环境变量 ADMIN_PASSWORDS 中的密码值。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group" style="margin-top: 0.75rem;">
|
||
|
||
<button type="button" class="btn btn-secondary" id="btnSyncCfWorkerDomains" onclick="syncCfWorkerDomains()" style="font-size: 0.85rem;">
|
||
|
||
☁ 从 CF Worker 同步域名
|
||
|
||
</button>
|
||
|
||
<div class="form-hint" id="cfWorkerSyncTime" style="margin-top: 0.4rem;">
|
||
|
||
自动读取 CF Worker 的域名配置,同步至下方域名字段。
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">可用域名(只读 · 通过同步按钮更新)</label>
|
||
|
||
<textarea class="form-input readonly-field" id="settingsCfWorkerDomains" rows="4" readonly placeholder="尚未同步,请点击上方按钮同步" style="font-family: monospace; font-size: 0.84rem;"></textarea>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">默认域名(只读 · 通过同步按钮更新)</label>
|
||
|
||
<input type="text" class="form-input readonly-field" id="settingsCfWorkerDefaultDomain" readonly placeholder="尚未同步">
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">前缀规则(JSON)</label>
|
||
|
||
<textarea class="form-input" id="settingsCfWorkerPrefixRules" rows="4" placeholder='{
|
||
|
||
"min_length": 1,
|
||
|
||
"max_length": 32,
|
||
|
||
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
||
|
||
}' style="font-family: monospace; font-size: 0.84rem;"></textarea>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- 插件 Provider 配置面板 -->
|
||
|
||
<div class="card" id="pluginProviderConfigPanel" style="display:none;margin-top:1rem;">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title" id="pluginProviderConfigTitle">🧩 插件 Provider 配置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" id="pluginProviderConfigBody" style="padding: 1.5rem;">
|
||
|
||
<div class="form-hint">请选择一个已安装插件 Provider。</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- 插件管理 -->
|
||
|
||
<div class="card" id="pluginManagerCard" style="margin-top:1rem;">
|
||
|
||
<div class="card-header" onclick="PluginManager.toggleCard()" style="cursor:pointer;user-select:none;">
|
||
|
||
<div style="display:flex;align-items:center;gap:0.75rem;">
|
||
|
||
<div style="width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--clr-primary),var(--clr-accent));border-radius:8px;color:white;font-size:0.9rem;">🧩</div>
|
||
|
||
<div>
|
||
|
||
<div class="card-title">插件管理</div>
|
||
|
||
<div style="font-size:0.75rem;color:var(--text-muted);margin-top:0.1rem;">安装、卸载并应用第三方 Provider 插件;运行时配置请在上方 Provider 设置区完成</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div style="display:flex;align-items:center;gap:0.6rem;">
|
||
|
||
<span id="pluginManagerBadge" class="badge" style="display:none;"></span>
|
||
|
||
<span class="toggle-icon" id="pluginManagerToggleIcon">▼</span>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" id="pluginManagerBody" style="display:none;">
|
||
|
||
<div id="pluginManagerContent">
|
||
|
||
<div style="text-align:center;padding:1rem;color:var(--text-muted);font-size:0.85rem;">点击上方标题展开插件管理…</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Tab 3: API 安全 -->
|
||
|
||
<div class="settings-tab-pane" id="settings-tab-api-security">
|
||
|
||
<div class="card">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">🔐 API 安全设置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">对外开放 API Key</label>
|
||
|
||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||
|
||
<input type="password" class="form-input" id="settingsExternalApiKey" placeholder="用于 /api/external/* 的 X-API-Key" autocomplete="off" style="flex:1;min-width:260px;">
|
||
|
||
<button type="button" class="btn btn-outline" id="btnGenerateExternalApiKey" onclick="generateExternalApiKey()" style="font-size:0.85rem;">随机生成</button>
|
||
|
||
<button type="button" class="btn btn-outline" id="btnCopyExternalApiKey" onclick="copyExternalApiKey()" style="font-size:0.85rem;">复制</button>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint" id="externalApiKeyHint">
|
||
|
||
用于对外开放接口鉴权(请求头:X-API-Key)。如需禁用对外开放接口,可清空后保存。
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">对外开放 API 多 Key 配置(JSON)</label>
|
||
|
||
<textarea class="form-input" id="settingsExternalApiKeysJson" rows="10" placeholder='[
|
||
|
||
{
|
||
|
||
"name": "partner-a",
|
||
|
||
"api_key": "your-key",
|
||
|
||
"enabled": true,
|
||
|
||
"allowed_emails": ["user@example.com"]
|
||
|
||
}
|
||
|
||
]' style="font-family: monospace; font-size: 0.84rem;"></textarea>
|
||
|
||
<div class="form-hint" id="externalApiKeysJsonHint">
|
||
|
||
用于按调用方维护多个 Key、邮箱范围授权和启停状态。保留已有脱敏 `api_key` 表示不修改该 Key;清空后保存表示清空全部多 Key。
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- P1:对外 API 公网安全配置 -->
|
||
|
||
<div id="externalApiAdvancedSettings" style="margin-top: 0.75rem; padding: 1rem; background: var(--bg-secondary); border-radius: 8px; border: 1px solid var(--border-light);">
|
||
|
||
<h5 style="font-size: 0.85rem; font-weight: 600; margin-bottom: 0.75rem; color: var(--text-muted);">🛡️ 公网安全配置(P1)</h5>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="externalApiPublicMode">
|
||
|
||
<span class="form-label" style="margin: 0;">启用公网模式</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">关闭时(默认)仅做 API Key 鉴权;开启后额外启用 IP 白名单、限流、高风险端点禁用等安全策略。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">IP 白名单 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(仅公网模式生效,留空则不限制)</span></label>
|
||
|
||
<textarea class="form-input" id="externalApiIpWhitelist" rows="2" placeholder='每行一个 IP 或 CIDR,如 192.168.1.0/24' style="font-family: monospace; font-size: 0.85rem;"></textarea>
|
||
|
||
<div class="form-hint">支持精确 IP 和 CIDR 格式。每行一个,保存时自动转为 JSON 数组。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">每分钟每 IP 限流 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(仅公网模式生效)</span></label>
|
||
|
||
<input type="number" class="form-input" id="externalApiRateLimit" min="1" max="10000" value="60" style="width: 120px;">
|
||
|
||
<div class="form-hint">每个 IP 每分钟最大请求数(默认 60)。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="externalApiDisableRaw">
|
||
|
||
<span class="form-label" style="margin: 0;">禁用 raw(原始内容)端点</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">公网模式下建议禁用,防止泄露完整邮件原文。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="externalApiDisableWait">
|
||
|
||
<span class="form-label" style="margin: 0;">禁用 wait-message(等待新邮件)端点</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">公网模式下建议禁用,防止长连接资源耗尽(Slowloris 风险)。</div>
|
||
|
||
</div>
|
||
|
||
<div style="border-top: 1px solid var(--border-light); margin-top: 1rem; padding-top: 1rem;">
|
||
|
||
<h6 style="font-size: 0.82rem; font-weight: 600; margin-bottom: 0.75rem; color: var(--text-muted);">📦 External Pool</h6>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="poolExternalEnabled">
|
||
|
||
<span class="form-label" style="margin: 0;">启用 external pool 端点</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">开启后才允许调用 `/api/external/pool/*`。仅设置对外 API Key 不会自动开启邮箱池对外接口。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="externalApiDisablePoolClaimRandom">
|
||
|
||
<span class="form-label" style="margin: 0;">禁用 pool claim-random</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">关闭后可供外部调用随机领取邮箱池账号。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="externalApiDisablePoolClaimRelease">
|
||
|
||
<span class="form-label" style="margin: 0;">禁用 pool claim-release</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">关闭后可供外部调用释放已领取账号。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="externalApiDisablePoolClaimComplete">
|
||
|
||
<span class="form-label" style="margin: 0;">禁用 pool claim-complete</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">关闭后可供外部调用领取完成/回写结果。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="externalApiDisablePoolStats">
|
||
|
||
<span class="form-label" style="margin: 0;">禁用 pool stats</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">关闭后可供外部读取邮箱池统计信息。</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Tab 4: 自动化 -->
|
||
|
||
<div class="settings-tab-pane" id="settings-tab-automation">
|
||
|
||
<!-- Token 刷新设置 -->
|
||
|
||
<div class="card" style="margin-bottom: 1rem;">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">🔄 Token 刷新设置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="enableScheduledRefresh" checked>
|
||
|
||
<span class="form-label" style="margin: 0;">启用定时刷新</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">关闭后将不会自动执行定时刷新任务</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">刷新策略</label>
|
||
|
||
<div style="display: flex; gap: 16px; margin-bottom: 12px;">
|
||
|
||
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||
|
||
<input type="radio" name="refreshStrategy" value="days" checked onchange="toggleRefreshStrategy()">
|
||
|
||
<span>按天数</span>
|
||
|
||
</label>
|
||
|
||
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||
|
||
<input type="radio" name="refreshStrategy" value="cron" onchange="toggleRefreshStrategy()">
|
||
|
||
<span>Cron 表达式</span>
|
||
|
||
</label>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="daysStrategyContainer">
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">定时刷新周期 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(每隔 X 天自动刷新所有账号)</span></label>
|
||
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
|
||
<input type="number" class="form-input" id="refreshIntervalDays" min="1" max="90" value="30" placeholder="30" style="max-width: 120px;">
|
||
|
||
<span style="font-size: 0.88rem; color: var(--text-muted); font-weight: 500;">天</span>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">建议设置为 30 天,防止 Token 因 90 天不使用而过期</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="cronStrategyContainer" style="display: none;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">Cron 表达式</label>
|
||
|
||
<input type="text" class="form-input" id="refreshCron" placeholder="0 2 * * *" value="0 2 * * *">
|
||
|
||
<div class="form-hint">格式:分 时 日 月 星期(例如:0 2 * * * 表示每天凌晨 2:00)</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">常用样例</label>
|
||
|
||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 8px;">
|
||
|
||
<button class="btn btn-sm btn-secondary" onclick="selectCronExample('0 2 * * *')">每天凌晨 2:00</button>
|
||
|
||
<button class="btn btn-sm btn-secondary" onclick="selectCronExample('0 2 * * 1')">每周一凌晨 2:00</button>
|
||
|
||
<button class="btn btn-sm btn-secondary" onclick="selectCronExample('0 2 1 * *')">每月 1 号凌晨 2:00</button>
|
||
|
||
<button class="btn btn-sm btn-secondary" onclick="selectCronExample('0 2 */3 * *')">每 3 天凌晨 2:00</button>
|
||
|
||
<button class="btn btn-sm btn-secondary" onclick="selectCronExample('0 */12 * * *')">每 12 小时</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<button class="btn btn-secondary" onclick="validateCronExpression()">验证表达式</button>
|
||
|
||
<div id="cronValidationResult" style="margin-top: 8px; font-size: 0.82rem;"></div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">邮箱间刷新间隔 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(每个邮箱刷新之间的等待时间)</span></label>
|
||
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
|
||
<input type="number" class="form-input" id="refreshDelaySeconds" min="0" max="60" value="5" placeholder="5" style="max-width: 120px;">
|
||
|
||
<span style="font-size: 0.88rem; color: var(--text-muted); font-weight: 500;">秒</span>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">建议设置为 5-10 秒,避免频繁请求触发 API 限流</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- 自动轮询设置 -->
|
||
|
||
<div class="card" style="margin-bottom: 1rem;">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">🔔 自动轮询设置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="enableAutoPolling">
|
||
|
||
<span class="form-label" style="margin: 0;">启用自动轮询</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">开启后自动检查当前账号是否有新邮件</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">轮询间隔 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(检查新邮件的时间间隔)</span></label>
|
||
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
|
||
<input type="number" class="form-input" id="pollingInterval" min="3" max="300" value="10" placeholder="10" style="max-width: 120px;">
|
||
|
||
<span style="font-size: 0.88rem; color: var(--text-muted); font-weight: 500;">秒</span>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">范围:3-300 秒,建议设置为 5-30 秒</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">轮询次数 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(最多轮询多少次后停止)</span></label>
|
||
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
|
||
<input type="number" class="form-input" id="pollingCount" min="0" max="100" value="5" placeholder="5" style="max-width: 120px;">
|
||
|
||
<span style="font-size: 0.88rem; color: var(--text-muted); font-weight: 500;">次</span>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">范围:0-100 次,设置为 0 表示持续轮询</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- 邮件通知设置 -->
|
||
|
||
<div class="card" style="margin-bottom: 1rem;">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">✉️ Email 通知</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="emailNotificationEnabled">
|
||
|
||
<span class="form-label" style="margin: 0;">启用 Email 通知</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">这里只配置 Email 通知通道。普通邮箱需在账号列表开启通知后才会通过 Email 发送;临时邮箱按当前通知规则处理。启用后仅从新到达的邮件开始通知。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">接收通知邮箱</label>
|
||
|
||
<input type="email" class="form-input" id="emailNotificationRecipient" placeholder="输入接收通知的邮箱地址">
|
||
|
||
<div class="form-hint">这里只配置 Email 渠道的接收邮箱,不会让所有普通邮箱自动发送。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group" style="margin-top: 0.5rem;">
|
||
|
||
<button class="btn btn-outline" onclick="testEmailNotification()" id="btnTestEmailNotification" style="font-size: 0.85rem;">📨 发送测试邮件</button>
|
||
|
||
<div class="form-hint">按"先保存,再测试"处理;成功语义为"请求已提交,请检查收件箱"。</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Telegram 推送设置 -->
|
||
|
||
<div class="card">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">📬 Telegram 通知</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">Bot Token <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(从 @BotFather 获取)</span></label>
|
||
|
||
<input type="password" class="form-input" id="telegramBotToken" placeholder="输入 Bot Token" autocomplete="off">
|
||
|
||
<div class="form-hint">这里只配置 Telegram 通知通道。普通邮箱需在账号列表开启通知后才会通过 Telegram 发送;临时邮箱按当前通知规则处理。</div>
|
||
|
||
<div class="form-hint">格式:1234567890:AAxxxxxx(留空则禁用推送)</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">Chat ID <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(接收推送的用户/群组 ID)</span></label>
|
||
|
||
<input type="text" class="form-input" id="telegramChatId" placeholder="输入 Chat ID" style="max-width: 300px;">
|
||
|
||
<div class="form-hint">可用 @userinfobot 获取你的 Chat ID</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">轮询间隔 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(检查新邮件的时间间隔)</span></label>
|
||
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
|
||
<input type="number" class="form-input" id="telegramPollInterval" min="10" max="86400" value="600" placeholder="600" style="max-width: 120px;">
|
||
|
||
<span style="font-size: 0.88rem; color: var(--text-muted); font-weight: 500;">秒</span>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">范围:10-86400 秒,默认 600 秒(10 分钟)</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">代理地址 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(可选,访问 Telegram API 时使用)</span></label>
|
||
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
|
||
<input type="text" class="form-input" id="telegramProxyUrl" placeholder="socks5://user:pass@host:port 或 http://host:port" style="max-width: 400px;">
|
||
|
||
<button class="btn btn-outline" onclick="testTelegramProxy()" id="btnTestTelegramProxy" style="font-size: 0.85rem; white-space: nowrap;">🔗 测试连通性</button>
|
||
|
||
<span id="telegramProxyTestResult" style="font-size: 0.85rem;"></span>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">留空则直连。格式:socks5://host:port 或 http://host:port</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group" style="margin-top: 0.5rem;">
|
||
|
||
<button class="btn btn-outline" onclick="testTelegramPush()" id="btnTestTelegram" style="font-size: 0.85rem;">📨 发送测试消息</button>
|
||
|
||
<div class="form-hint">验证当前 Telegram 通知通道是否配置正确</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Webhook 通知设置 -->
|
||
|
||
<div class="card" style="margin-top: 1rem;">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">📡 Webhook 通知</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<div class="form-group">
|
||
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
|
||
<input type="checkbox" id="webhookNotificationEnabled">
|
||
|
||
<span class="form-label" style="margin: 0;">启用 Webhook 通知</span>
|
||
|
||
</label>
|
||
|
||
<div class="form-hint">全局单 URL 通道;普通邮箱和临时邮箱都遵循当前通知参与规则。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">Webhook URL</label>
|
||
|
||
<input type="text" class="form-input" id="webhookNotificationUrl" placeholder="https://example.com/webhook">
|
||
|
||
<div class="form-hint">仅支持 http:// 或 https://,发送格式为 text/plain。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">Webhook Token(可选)</label>
|
||
|
||
<input type="password" class="form-input" id="webhookNotificationToken" placeholder="输入可选 Token" autocomplete="off">
|
||
|
||
<div class="form-hint">仅当有值时会附带请求头 X-Webhook-Token。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group" style="margin-top: 0.5rem;">
|
||
|
||
<button class="btn btn-outline" onclick="testWebhookNotification()" id="btnTestWebhookNotification" style="font-size: 0.85rem;">测试 Webhook</button>
|
||
|
||
<div class="form-hint">按“先保存,再测试”处理;测试仅使用已保存配置。</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Watchtower 一键更新设置 -->
|
||
|
||
<div class="card" style="margin-top: 1rem;">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">🔄 一键更新配置</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" style="padding: 1.5rem;">
|
||
|
||
<!-- 更新方式选择 -->
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">更新方式</label>
|
||
|
||
<div style="display: flex; gap: 20px; margin-top: 8px;">
|
||
|
||
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||
|
||
<input type="radio" name="updateMethod" value="watchtower" checked>
|
||
|
||
<span>Watchtower(推荐)</span>
|
||
|
||
</label>
|
||
|
||
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||
|
||
<input type="radio" name="updateMethod" value="docker_api">
|
||
|
||
<span>Docker API(高级)</span>
|
||
|
||
</label>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">
|
||
|
||
<strong>Watchtower:</strong> 使用外部 Watchtower 容器管理更新(推荐,更安全)<br>
|
||
|
||
<strong>Docker API:</strong> 容器直接通过 Docker API 自更新(需挂载 docker.sock,存在安全风险)
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Docker API 安全警告 -->
|
||
|
||
<div class="form-hint" id="dockerApiWarning" style="background: var(--bg-danger, #f8d7da); padding: 12px; border-radius: 6px; margin-bottom: 1rem; border-left: 3px solid var(--danger, #dc3545); display: none;" data-i18n-html="docker-api-warning">
|
||
|
||
<strong>⚠️ Docker API 模式安全警告:</strong><br>
|
||
|
||
1. 需要在 docker-compose.yml 中挂载 <code>/var/run/docker.sock</code><br>
|
||
|
||
2. 需要在 .env 中设置 <code>DOCKER_SELF_UPDATE_ALLOW=true</code><br>
|
||
|
||
3. ⚠️ 此操作授予容器完全的 Docker API 访问权限,请谨慎使用<br>
|
||
|
||
4. 建议仅在测试环境或可信网络中启用
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- 部署信息警告容器(动态加载,所有更新方式均显示) -->
|
||
|
||
<div id="deploymentWarnings" style="margin-bottom: 1rem;"></div>
|
||
|
||
|
||
|
||
<!-- 手动触发更新按钮 -->
|
||
|
||
<div class="form-group" style="margin-bottom: 1.5rem; padding: 1rem; background: var(--bg-card, #f8f9fa); border-radius: 8px; border: 1px solid var(--border, #dee2e6);">
|
||
|
||
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px;">
|
||
|
||
<div>
|
||
|
||
<div style="font-weight: 600; font-size: 0.95rem; color: var(--text);">🚀 触发容器更新</div>
|
||
|
||
<div class="form-hint" style="margin-top: 4px;">拉取最新镜像并重启容器,使用上方选择的更新方式</div>
|
||
|
||
</div>
|
||
|
||
<button class="btn btn-primary" id="btnManualTriggerUpdate" onclick="manualTriggerUpdate()" style="white-space: nowrap;">
|
||
|
||
立即更新
|
||
|
||
</button>
|
||
|
||
</div>
|
||
|
||
<div id="manualUpdateResult" style="margin-top: 8px; font-size: 0.85rem; display: none;"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Watchtower 配置区域 -->
|
||
|
||
<div id="watchtowerConfigArea">
|
||
|
||
<!-- 首次配置引导 -->
|
||
|
||
<div class="form-hint" style="background: var(--bg-accent, #fff3cd); padding: 12px; border-radius: 6px; margin-bottom: 1rem; border-left: 3px solid var(--warning, #ffc107);" data-i18n-html="watchtower-setup-guide">
|
||
|
||
<strong>📌 首次配置指南:</strong><br>
|
||
|
||
1. 在 <code>.env</code> 文件中设置 <code>WATCHTOWER_HTTP_API_TOKEN</code>(使用 <code>python -c "import secrets; print(secrets.token_hex(32))"</code> 生成)<br>
|
||
|
||
2. 使用 <code>docker-compose up -d</code> 重启容器以应用 Token<br>
|
||
|
||
3. 在下方配置相同的 Token 并保存设置<br>
|
||
|
||
4. 点击"测试连通性"验证配置是否正确
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">Watchtower API 地址 <span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 400;">(Docker 内网地址)</span></label>
|
||
|
||
<input type="text" class="form-input" id="watchtowerUrl" placeholder="http://watchtower:8080" style="max-width: 400px;">
|
||
|
||
<div class="form-hint">默认 http://watchtower:8080,仅 Docker 部署模式下可用</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
|
||
<label class="form-label">Watchtower API Token</label>
|
||
|
||
<input type="password" class="form-input" id="watchtowerToken" placeholder="输入 Watchtower HTTP API Token" autocomplete="off" style="max-width: 400px;">
|
||
|
||
<div class="form-hint">与 docker-compose 中 WATCHTOWER_HTTP_API_TOKEN 保持一致。留空则读取环境变量。</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group" style="margin-top: 0.5rem;">
|
||
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
|
||
<button class="btn btn-outline" onclick="testWatchtower()" id="btnTestWatchtower" style="font-size: 0.85rem;">🔗 测试连通性</button>
|
||
|
||
<span id="watchtowerTestResult" style="font-size: 0.85rem;"></span>
|
||
|
||
</div>
|
||
|
||
<div class="form-hint">验证 Watchtower 服务是否可达且 Token 正确</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- 更新方式切换(Watchtower/Docker API)的显隐逻辑已迁移到 static/js/main.js -->
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div><!-- /settings-card-shell -->
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- ===== Page: Pool Admin ===== -->
|
||
|
||
<div class="page page-hidden" id="page-pool-admin">
|
||
|
||
<div class="card">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">🎱 号池管理</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" id="poolAdminContainer">
|
||
|
||
<!-- 筛选栏:横向紧凑排列 -->
|
||
|
||
<div class="pool-admin-toolbar" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:0.8rem;">
|
||
|
||
<select id="poolAdminInPoolFilter" class="form-select" style="width:auto;min-width:90px;" onchange="loadPoolAdmin(true)">
|
||
|
||
<option value="all">全部</option>
|
||
|
||
<option value="true">池内</option>
|
||
|
||
<option value="false">池外</option>
|
||
|
||
</select>
|
||
|
||
<select id="poolAdminStatusFilter" class="form-select" style="width:auto;min-width:110px;" onchange="loadPoolAdmin(true)">
|
||
|
||
<option value="">所有状态</option>
|
||
|
||
<option value="available">可用</option>
|
||
|
||
<option value="claimed">占用中</option>
|
||
|
||
<option value="cooldown">冷却中</option>
|
||
|
||
<option value="used">已使用</option>
|
||
|
||
<option value="frozen">冻结</option>
|
||
|
||
<option value="retired">退休</option>
|
||
|
||
</select>
|
||
|
||
<select id="poolAdminProviderFilter" class="form-select" style="width:auto;min-width:100px;" onchange="loadPoolAdmin(true)">
|
||
|
||
<option value="">所有类型</option>
|
||
|
||
<option value="outlook">Outlook</option>
|
||
|
||
<option value="imap">IMAP</option>
|
||
|
||
<option value="custom">Custom</option>
|
||
|
||
<option value="cloudflare_temp_mail">CF Temp Mail</option>
|
||
|
||
</select>
|
||
|
||
<select id="poolAdminGroupFilter" class="form-select" style="width:auto;min-width:110px;" onchange="loadPoolAdmin(true)">
|
||
|
||
<option value="">所有分组</option>
|
||
|
||
</select>
|
||
|
||
<input type="text" id="poolAdminSearch" class="form-input" placeholder="搜索邮箱…" style="width:180px;min-width:140px;" oninput="debouncePoolAdminSearch()">
|
||
|
||
<div style="flex:1;"></div>
|
||
|
||
<!-- 批量操作区 -->
|
||
|
||
<div id="poolAdminBatchBar" style="display:none;align-items:center;gap:8px;">
|
||
|
||
<span id="poolAdminBatchCount" style="font-size:0.78rem;color:var(--text-muted);"></span>
|
||
|
||
<button class="btn btn-sm btn-primary" onclick="batchPoolAdminAction('move_into_pool')">批量移入</button>
|
||
|
||
<button class="btn btn-sm btn-ghost" onclick="batchPoolAdminAction('move_out_of_pool')">批量移出</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="poolAdminTableWrapper">
|
||
|
||
<div class="loading-overlay"><span class="spinner"></span> 加载中…</div>
|
||
|
||
</div>
|
||
|
||
<div class="pool-admin-pagination" id="poolAdminPagination" style="margin-top:0.8rem;"></div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
<!-- ===== Page: Audit ===== -->
|
||
|
||
<div class="page page-hidden" id="page-audit">
|
||
|
||
<div class="card">
|
||
|
||
<div class="card-header">
|
||
|
||
<div class="card-title">📋 审计日志</div>
|
||
|
||
</div>
|
||
|
||
<div class="card-body" id="auditLogContainer">
|
||
|
||
<div class="loading-overlay"><span class="spinner"></span> 加载中…</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Toast container -->
|
||
|
||
<div id="toast-container" aria-live="polite"></div>
|
||
|
||
|
||
|
||
{% include 'partials/modals.html' %}
|
||
|
||
|
||
|
||
{% include 'partials/scripts.html' %}
|
||
|
||
</body>
|
||
|
||
|
||
|
||
</html>
|
||
|