FluxIP/app/static/index.html

426 lines
46 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#f3f5f1">
<title>FluxIP</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="ambient-grid" aria-hidden="true"></div>
<main id="boot-screen" class="boot-screen" aria-live="polite">
<div class="brand-mark brand-mark-large"><span></span><span></span></div>
<div class="boot-line"><span></span></div>
</main>
<main id="auth-screen" class="auth-screen hidden">
<section class="auth-panel glass-surface" aria-labelledby="auth-title">
<div class="auth-brand">
<div class="brand-mark"><span></span><span></span></div>
<span>FluxIP</span>
</div>
<div class="auth-copy">
<p id="auth-kicker" class="eyebrow">PRIVATE CONTROL PLANE</p>
<h1 id="auth-title">创建管理员</h1>
<p id="auth-subtitle">完成初始化后进入控制台</p>
</div>
<form id="auth-form" class="form-stack" novalidate>
<label class="field">
<span>用户名</span>
<span class="input-shell"><i data-lucide="user-round"></i><input id="auth-username" name="username" autocomplete="username" maxlength="40" aria-describedby="auth-username-error" required></span>
<small id="auth-username-error" class="field-error" data-error-for="username" role="alert"></small>
</label>
<label class="field">
<span>密码</span>
<span class="input-shell"><i data-lucide="key-round"></i><input id="auth-password" name="password" type="password" autocomplete="new-password" maxlength="128" aria-describedby="auth-password-error" required></span>
<small id="auth-password-error" class="field-error" data-error-for="password" role="alert"></small>
</label>
<label id="bootstrap-token-field" class="field hidden">
<span>初始化令牌</span>
<span class="input-shell"><i data-lucide="shield-check"></i><input id="auth-bootstrap-token" name="bootstrap_token" type="password" autocomplete="off" maxlength="512" aria-describedby="auth-bootstrap-token-error"></span>
<small id="auth-bootstrap-token-error" class="field-error" data-error-for="bootstrap_token" role="alert"></small>
</label>
<button id="auth-submit" class="button button-primary button-wide" type="submit"><span>创建并进入</span><i data-lucide="arrow-right"></i></button>
</form>
<div class="auth-footer"><span class="status-dot status-dot-ok"></span><span>密钥本地加密存储</span></div>
</section>
</main>
<div id="app-shell" class="app-shell hidden">
<aside class="sidebar glass-surface">
<div class="sidebar-brand">
<div class="brand-mark"><span></span><span></span></div>
<div><strong>FluxIP</strong><small>CONTROL</small></div>
</div>
<nav class="primary-nav" aria-label="主导航">
<button class="nav-item active" data-view="overview" type="button"><i data-lucide="layout-dashboard"></i><span>概览</span></button>
<button class="nav-item" data-view="instances" type="button"><i data-lucide="server"></i><span>实例</span></button>
<button class="nav-item" data-view="groups" type="button"><i data-lucide="boxes"></i><span>分组</span></button>
<button class="nav-item" data-view="accounts" type="button"><i data-lucide="key-round"></i><span>账号</span></button>
<button class="nav-item" data-view="history" type="button"><i data-lucide="history"></i><span>记录</span></button>
</nav>
<div class="sidebar-foot">
<button id="account-button" class="account-button" type="button">
<span class="avatar">A</span>
<span><strong id="sidebar-username">admin</strong><small>管理员</small></span>
<i data-lucide="chevron-right"></i>
</button>
</div>
</aside>
<header class="mobile-header glass-surface">
<div class="sidebar-brand"><div class="brand-mark"><span></span><span></span></div><strong>FluxIP</strong></div>
<div class="mobile-header-actions">
<button id="mobile-account" class="icon-button" type="button" aria-label="管理员账户" title="管理员账户"><i data-lucide="user-round"></i></button>
<button id="mobile-refresh" class="icon-button" type="button" aria-label="刷新当前页面" title="刷新当前页面"><i data-lucide="refresh-cw"></i></button>
</div>
</header>
<div class="content-frame">
<section id="view-overview" class="view active-view" data-view-panel="overview">
<header class="page-header">
<div><p id="greeting" class="eyebrow">早上好</p><h1>轮换概览</h1></div>
<div class="header-actions">
<button id="refresh-overview" class="icon-button" type="button" aria-label="刷新概览" title="刷新概览"><i data-lucide="refresh-cw"></i></button>
<button class="button button-dark" data-create-instance type="button" aria-label="新增实例" title="新增实例"><i data-lucide="plus"></i><span>新增实例</span></button>
</div>
</header>
<div id="overview-alert" class="alert-banner hidden" role="status"><i data-lucide="triangle-alert"></i><span></span><button type="button" data-view="instances">查看实例</button></div>
<div id="overview-loading" class="overview-loading">
<div class="skeleton skeleton-status"></div>
<div class="skeleton-grid"><div class="skeleton skeleton-panel"></div><div class="skeleton skeleton-panel"></div></div>
</div>
<div id="overview-content" class="hidden">
<section class="status-band fleet-status-band glass-surface" aria-label="实例总体状态">
<div class="status-primary">
<div id="fleet-status-icon" class="status-symbol status-symbol-idle"><i data-lucide="radio-tower"></i></div>
<div><span class="metric-label">已启用实例</span><strong id="fleet-health" class="fleet-display">0 / 0</strong><span id="fleet-status-text" class="inline-status"><span class="status-dot"></span>等待配置</span></div>
</div>
<div class="status-metric"><span class="metric-label">运行中任务</span><strong id="fleet-active-runs">0</strong><small>实例轮换</small></div>
<div class="status-metric"><span class="metric-label">实例组</span><strong id="fleet-groups">0 / 0</strong><small>已启用 / 全部</small></div>
<div class="status-metric status-metric-last"><span class="metric-label">最近下次轮换</span><strong id="fleet-next-run">已暂停</strong><small id="fleet-next-group">未启用计划</small></div>
</section>
<div class="overview-grid">
<section class="operations-panel glass-surface">
<div class="section-heading"><div><span class="eyebrow">ACTIVE ROTATION</span><h2>当前任务</h2></div><span id="active-run-badge" class="badge badge-neutral">空闲</span></div>
<div id="active-run-empty" class="compact-empty"><i data-lucide="check-circle-2"></i><strong>当前没有运行中的任务</strong><span>队列状态正常</span></div>
<div id="active-run-panel" class="active-operation hidden">
<div class="active-operation-main"><span class="service-glyph service-glyph-proxy"><i data-lucide="replace"></i></span><div><small id="active-run-scope">实例轮换</small><strong id="active-run-name">--</strong><span id="active-run-stage">正在预检</span></div><b id="active-run-count">0 / 0</b></div>
<div class="progress-bar"><span id="active-progress-bar"></span></div>
<button id="open-active-run" class="text-button" type="button">查看任务详情<i data-lucide="arrow-up-right"></i></button>
</div>
</section>
<aside class="control-panel next-run-panel glass-surface">
<div class="section-heading"><div><span class="eyebrow">NEXT GROUP</span><h2>下次轮换</h2></div><i data-lucide="calendar-clock"></i></div>
<div class="countdown-block"><span id="next-group-name">暂无计划</span><strong id="countdown">--:--:--</strong></div>
<div class="control-meta"><span><i data-lucide="repeat-2"></i><span>轮换周期</span><strong id="next-group-interval">--</strong></span><span><i data-lucide="server"></i><span>组内实例</span><strong id="next-group-members">--</strong></span></div>
<button id="overview-open-groups" class="button button-secondary button-wide" type="button"><i data-lucide="boxes"></i><span>管理实例组</span></button>
</aside>
</div>
<section class="activity-section">
<div class="section-heading"><div><span class="eyebrow">GROUP STATUS</span><h2>实例组</h2></div><button class="text-button" type="button" data-view="groups">查看全部<i data-lucide="arrow-up-right"></i></button></div>
<div class="table-wrap glass-surface resource-table-wrap">
<table class="group-overview-table"><thead><tr><th>实例组</th><th>成员</th><th>轮换周期</th><th>下次执行</th><th>状态</th><th><span class="sr-only">操作</span></th></tr></thead><tbody id="overview-groups"></tbody></table>
<div id="overview-groups-empty" class="empty-state hidden"><i data-lucide="boxes"></i><strong>还没有实例组</strong><span>创建分组后统一安排轮换</span><button class="button button-secondary" data-create-group type="button"><i data-lucide="plus"></i><span>新增分组</span></button></div>
</div>
</section>
<section class="activity-section">
<div class="section-heading"><div><span class="eyebrow">RECENT ACTIVITY</span><h2>最近轮换</h2></div><button class="text-button" type="button" data-view="history">查看全部<i data-lucide="arrow-up-right"></i></button></div>
<div class="table-wrap glass-surface resource-table-wrap">
<table class="recent-table"><thead><tr><th>时间</th><th>目标</th><th>触发</th><th>IP 变化</th><th>状态</th><th><span class="sr-only">操作</span></th></tr></thead><tbody id="recent-runs"></tbody></table>
<div id="recent-empty" class="empty-state hidden"><i data-lucide="route"></i><strong>还没有轮换记录</strong><span>实例准备完成后即可开始</span></div>
</div>
</section>
</div>
</section>
<section id="view-instances" class="view" data-view-panel="instances">
<header class="page-header">
<div><p class="eyebrow">ROUTES</p><h1>实例</h1></div>
<button class="button button-primary" data-create-instance type="button" aria-label="新增实例" title="新增实例"><i data-lucide="plus"></i><span>新增实例</span></button>
</header>
<div class="resource-toolbar" aria-label="实例筛选">
<label class="input-shell search-shell"><i data-lucide="search"></i><input id="instance-search" type="search" placeholder="搜索名称、实例或域名" aria-label="搜索实例"></label>
<div class="custom-select custom-select-compact" data-custom-select>
<input id="instance-group-filter" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="instance-group-filter-options" aria-label="按实例组筛选"><i data-lucide="boxes"></i><span class="custom-select-value" data-custom-select-label>全部分组</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="instance-group-filter-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-label="按实例组筛选" hidden></div>
</div>
<div class="custom-select custom-select-compact" data-custom-select>
<input id="instance-status-filter" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="instance-status-filter-options" aria-label="按状态筛选"><i data-lucide="activity"></i><span class="custom-select-value" data-custom-select-label>全部状态</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="instance-status-filter-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-label="按状态筛选" hidden></div>
</div>
<button id="refresh-instances" class="icon-button" type="button" aria-label="刷新实例" title="刷新实例"><i data-lucide="refresh-cw"></i></button>
</div>
<div id="instances-loading" class="resource-loading hidden"><div class="skeleton skeleton-list"></div></div>
<div id="instances-table-wrap" class="table-wrap glass-surface resource-table-wrap">
<table class="instances-table"><thead><tr><th>实例</th><th>实例组</th><th>Cloudflare 路由</th><th>当前 Static IP</th><th>状态</th><th>最近检查</th><th><span class="sr-only">操作</span></th></tr></thead><tbody id="instance-rows"></tbody></table>
<div id="instances-empty" class="empty-state hidden"><i data-lucide="server"></i><strong>没有匹配的实例</strong><span id="instances-empty-copy">新增第一条 Lightsail 路由</span><button class="button button-secondary" data-create-instance type="button"><i data-lucide="plus"></i><span>新增实例</span></button></div>
</div>
<div id="instances-pagination" class="pagination hidden"><button id="instances-prev" class="icon-button" type="button" aria-label="上一页" title="上一页"><i data-lucide="chevron-left"></i></button><span id="instances-page">第 1 页</span><button id="instances-next" class="icon-button" type="button" aria-label="下一页" title="下一页"><i data-lucide="chevron-right"></i></button></div>
</section>
<section id="view-groups" class="view" data-view-panel="groups">
<header class="page-header">
<div><p class="eyebrow">AUTOMATION</p><h1>实例组</h1></div>
<button class="button button-primary" data-create-group type="button" aria-label="新增分组" title="新增分组"><i data-lucide="plus"></i><span>新增分组</span></button>
</header>
<div class="resource-toolbar group-toolbar" aria-label="实例组筛选">
<label class="input-shell search-shell"><i data-lucide="search"></i><input id="group-search" type="search" placeholder="搜索实例组" aria-label="搜索实例组"></label>
<div class="custom-select custom-select-compact" data-custom-select>
<input id="group-status-filter" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="group-status-filter-options" aria-label="按计划状态筛选"><i data-lucide="timer-reset"></i><span class="custom-select-value" data-custom-select-label>全部状态</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="group-status-filter-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-label="按计划状态筛选" hidden></div>
</div>
<button id="refresh-groups" class="icon-button" type="button" aria-label="刷新分组" title="刷新分组"><i data-lucide="refresh-cw"></i></button>
</div>
<div id="groups-loading" class="resource-loading hidden"><div class="skeleton skeleton-list"></div></div>
<div id="groups-table-wrap" class="table-wrap glass-surface resource-table-wrap">
<table class="groups-table"><thead><tr><th>实例组</th><th>成员</th><th>轮换周期</th><th>下次执行</th><th>上次执行</th><th>状态</th><th><span class="sr-only">操作</span></th></tr></thead><tbody id="group-rows"></tbody></table>
<div id="groups-empty" class="empty-state hidden"><i data-lucide="boxes"></i><strong>没有匹配的实例组</strong><span id="groups-empty-copy">将多个实例放入同一轮换计划</span><button class="button button-secondary" data-create-group type="button"><i data-lucide="plus"></i><span>新增分组</span></button></div>
</div>
</section>
<section id="view-accounts" class="view" data-view-panel="accounts">
<header class="page-header">
<div><p class="eyebrow">CREDENTIAL VAULT</p><h1>账号</h1></div>
<button id="refresh-accounts" class="icon-button" type="button" aria-label="刷新账号" title="刷新账号"><i data-lucide="refresh-cw"></i></button>
</header>
<div id="accounts-loading" class="account-vault-loading hidden"><div class="skeleton skeleton-account"></div><div class="skeleton skeleton-account"></div></div>
<div id="accounts-content" class="account-vault-grid">
<section class="account-collection" aria-labelledby="aws-accounts-title">
<div class="account-collection-head">
<div><span class="service-glyph service-glyph-aws"><i data-lucide="cloud"></i></span><span><small>AMAZON WEB SERVICES</small><strong id="aws-accounts-title">AWS 账号</strong></span></div>
<button class="icon-button" data-create-account="aws" type="button" aria-label="新增 AWS 账号" title="新增 AWS 账号"><i data-lucide="plus"></i></button>
</div>
<div id="aws-account-list" class="credential-list"></div>
<div id="aws-accounts-empty" class="credential-empty hidden"><span class="empty-glyph"><i data-lucide="key-round"></i></span><strong>还没有 AWS 账号</strong><span>添加后即可绑定 Lightsail 实例</span><button class="button button-secondary" data-create-account="aws" type="button"><i data-lucide="plus"></i><span>新增 AWS 账号</span></button></div>
</section>
<section class="account-collection" aria-labelledby="cf-accounts-title">
<div class="account-collection-head">
<div><span class="service-glyph service-glyph-cf"><i data-lucide="cloud"></i></span><span><small>CLOUDFLARE</small><strong id="cf-accounts-title">Cloudflare 账号</strong></span></div>
<button class="icon-button" data-create-account="cloudflare" type="button" aria-label="新增 Cloudflare 账号" title="新增 Cloudflare 账号"><i data-lucide="plus"></i></button>
</div>
<div id="cloudflare-account-list" class="credential-list"></div>
<div id="cloudflare-accounts-empty" class="credential-empty hidden"><span class="empty-glyph"><i data-lucide="shield-keyhole"></i></span><strong>还没有 Cloudflare 账号</strong><span>添加后即可自动同步 DNS</span><button class="button button-secondary" data-create-account="cloudflare" type="button"><i data-lucide="plus"></i><span>新增 Cloudflare 账号</span></button></div>
</section>
</div>
</section>
<section id="view-history" class="view" data-view-panel="history">
<header class="page-header"><div><p class="eyebrow">AUDIT TRAIL</p><h1>轮换记录</h1></div><button id="refresh-history" class="icon-button" type="button" aria-label="刷新记录" title="刷新记录"><i data-lucide="refresh-cw"></i></button></header>
<div class="resource-toolbar history-toolbar" aria-label="记录筛选">
<div class="custom-select custom-select-compact" data-custom-select>
<input id="history-instance-filter" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="history-instance-filter-options" aria-label="按实例筛选"><i data-lucide="server"></i><span class="custom-select-value" data-custom-select-label>全部实例</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="history-instance-filter-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-label="按实例筛选" hidden></div>
</div>
<div class="custom-select custom-select-compact" data-custom-select>
<input id="history-group-filter" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="history-group-filter-options" aria-label="按实例组筛选"><i data-lucide="boxes"></i><span class="custom-select-value" data-custom-select-label>全部分组</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="history-group-filter-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-label="按实例组筛选" hidden></div>
</div>
<div class="custom-select custom-select-compact" data-custom-select>
<input id="history-status-filter" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="history-status-filter-options" aria-label="按结果筛选"><i data-lucide="activity"></i><span class="custom-select-value" data-custom-select-label>全部结果</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="history-status-filter-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-label="按结果筛选" hidden></div>
</div>
</div>
<div id="history-loading" class="resource-loading hidden"><div class="skeleton skeleton-list"></div></div>
<section class="history-summary" aria-label="记录摘要"><div><span>全部任务</span><strong id="history-total">0</strong></div><div><span>成功率</span><strong id="history-rate">--</strong></div><div><span>最近成功</span><strong id="history-latest">--</strong></div></section>
<div class="table-wrap history-table glass-surface resource-table-wrap"><table><thead><tr><th>开始时间</th><th>目标</th><th>触发</th><th>完成进度 / IP 变化</th><th>结果</th><th><span class="sr-only">详情</span></th></tr></thead><tbody id="history-runs"></tbody></table><div id="history-empty" class="empty-state hidden"><i data-lucide="history"></i><strong>暂无记录</strong><span>调整筛选或开始一次轮换</span></div></div>
<div class="pagination"><button id="history-prev" class="icon-button" type="button" aria-label="上一页" title="上一页"><i data-lucide="chevron-left"></i></button><span id="history-page">第 1 页</span><button id="history-next" class="icon-button" type="button" aria-label="下一页" title="下一页"><i data-lucide="chevron-right"></i></button></div>
</section>
</div>
<nav class="mobile-nav glass-surface" aria-label="移动端导航">
<button class="nav-item active" data-view="overview" type="button"><i data-lucide="layout-dashboard"></i><span>概览</span></button>
<button class="nav-item" data-view="instances" type="button"><i data-lucide="server"></i><span>实例</span></button>
<button class="nav-item" data-view="groups" type="button"><i data-lucide="boxes"></i><span>分组</span></button>
<button class="nav-item" data-view="accounts" type="button"><i data-lucide="key-round"></i><span>账号</span></button>
<button class="nav-item" data-view="history" type="button"><i data-lucide="history"></i><span>记录</span></button>
</nav>
</div>
<dialog id="instance-dialog" class="modal-dialog editor-dialog" aria-labelledby="instance-dialog-title">
<form id="instance-form" novalidate>
<div class="dialog-header sticky-dialog-head"><div><span class="eyebrow">INSTANCE</span><h2 id="instance-dialog-title">新增实例</h2></div><button class="icon-button" data-close-dialog type="button" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button></div>
<section class="editor-section">
<div class="editor-section-title"><span class="service-glyph service-glyph-proxy"><i data-lucide="route"></i></span><div><span class="eyebrow">IDENTITY</span><h3>路由身份</h3></div></div>
<div class="form-grid form-grid-2">
<label class="field"><span>显示名称</span><span class="input-shell"><i data-lucide="tag"></i><input name="display_name" maxlength="80" placeholder="新加坡代理 01" required></span><small class="field-error" data-error-for="display_name"></small></label>
<div class="field">
<span id="instance-group-label">所属实例组</span>
<div id="instance-group-select" class="custom-select custom-select-rich" data-custom-select>
<input name="group_id" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="instance-group-options" aria-labelledby="instance-group-label instance-group-select-value">
<i data-lucide="boxes"></i>
<span id="instance-group-select-value" class="custom-select-value" data-custom-select-label>未分组</span>
<i class="custom-select-chevron" data-lucide="chevron-down"></i>
</button>
<div id="instance-group-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-labelledby="instance-group-label" hidden></div>
</div>
<small class="field-error" data-error-for="group_id"></small>
</div>
</div>
<label class="toggle-row"><span><strong>启用此实例</strong><small>停用后不参与组计划</small></span><input name="enabled" type="checkbox" checked><span class="toggle" aria-hidden="true"></span></label>
</section>
<section class="editor-section">
<div class="editor-section-title"><span class="service-glyph service-glyph-aws"><i data-lucide="server"></i></span><div><span class="eyebrow">AMAZON LIGHTSAIL</span><h3>目标实例</h3></div></div>
<div class="form-grid form-grid-2">
<div class="field">
<span id="instance-aws-account-label">AWS 账号</span>
<div id="instance-aws-account-select" class="custom-select custom-select-rich" data-custom-select>
<input name="aws_account_id" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="instance-aws-account-options" aria-labelledby="instance-aws-account-label instance-aws-account-value" aria-describedby="instance-aws-account-error" aria-required="true"><i data-lucide="key-round"></i><span id="instance-aws-account-value" class="custom-select-value" data-custom-select-label>选择 AWS 账号</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="instance-aws-account-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-labelledby="instance-aws-account-label" hidden></div>
</div>
<small id="instance-aws-account-error" class="field-error" data-error-for="aws_account_id" role="alert"></small>
</div>
<div class="field">
<span id="instance-region-label">AWS Region</span>
<div id="instance-region-select" class="custom-select custom-select-rich custom-select-region" data-custom-select>
<input name="aws_region" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="instance-region-options" aria-labelledby="instance-region-label instance-region-value" aria-describedby="instance-region-error" aria-required="true"><span class="custom-select-trigger-symbol" data-custom-select-symbol aria-hidden="true"><i data-lucide="map-pin"></i></span><span id="instance-region-value" class="custom-select-value" data-custom-select-label>选择区域</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="instance-region-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-labelledby="instance-region-label" hidden></div>
</div>
<small id="instance-region-error" class="field-error" data-error-for="aws_region" role="alert"></small>
</div>
<label class="field field-span-2"><span>Lightsail 实例名</span><span class="input-shell"><i data-lucide="box"></i><input name="lightsail_instance_name" maxlength="255" placeholder="socks-gateway" required></span><small class="field-error" data-error-for="lightsail_instance_name"></small></label>
</div>
</section>
<section class="editor-section">
<div class="editor-section-title"><span class="service-glyph service-glyph-cf"><i data-lucide="cloud"></i></span><div><span class="eyebrow">CLOUDFLARE DNS</span><h3>域名路由</h3></div></div>
<div class="form-grid form-grid-2">
<div class="field field-span-2">
<span id="instance-cf-account-label">Cloudflare 账号</span>
<div id="instance-cf-account-select" class="custom-select custom-select-rich" data-custom-select>
<input name="cloudflare_account_id" type="hidden" value="">
<button class="custom-select-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="instance-cf-account-options" aria-labelledby="instance-cf-account-label instance-cf-account-value" aria-describedby="instance-cf-account-error" aria-required="true"><i data-lucide="shield-keyhole"></i><span id="instance-cf-account-value" class="custom-select-value" data-custom-select-label>选择 Cloudflare 账号</span><i class="custom-select-chevron" data-lucide="chevron-down"></i></button>
<div id="instance-cf-account-options" class="custom-select-menu" data-custom-select-menu role="listbox" aria-labelledby="instance-cf-account-label" hidden></div>
</div>
<small id="instance-cf-account-error" class="field-error" data-error-for="cloudflare_account_id" role="alert"></small>
</div>
<label class="field"><span>Zone 根域名</span><span class="input-shell"><i data-lucide="globe-2"></i><input name="cloudflare_zone_name" maxlength="253" placeholder="example.com" required></span><small class="field-error" data-error-for="cloudflare_zone_name"></small></label>
<label class="field"><span>Zone ID <small>可选</small></span><span class="input-shell"><i data-lucide="scan-line"></i><input name="cloudflare_zone_id" maxlength="64" placeholder="留空自动查找"></span><small class="field-error" data-error-for="cloudflare_zone_id"></small></label>
<label class="field field-span-2"><span>A 记录完整域名</span><span class="input-shell"><i data-lucide="at-sign"></i><input name="cloudflare_record_name" maxlength="253" placeholder="proxy.example.com" required></span><small class="field-error" data-error-for="cloudflare_record_name"></small></label>
</div>
<label class="toggle-row cloudflare-proxy-toggle"><span><strong>Cloudflare 代理(橙云)</strong><small>普通 SOCKS5/HTTP CONNECT 请保持关闭</small></span><input name="cloudflare_proxied" type="checkbox"><span class="toggle" aria-hidden="true"></span></label>
<div id="instance-account-callout" class="credential-callout hidden"><span><i data-lucide="key-round"></i></span><div><strong>缺少可用账号</strong><small id="instance-account-callout-copy">请先添加 AWS 与 Cloudflare 账号</small></div><button class="button button-secondary" data-manage-accounts type="button"><span>前往账号</span><i data-lucide="arrow-up-right"></i></button></div>
<div id="cloudflare-mode-note" class="compatibility-note" role="status"><span class="note-icon"><i data-lucide="cloud-off"></i></span><div><strong>DNS-only · TTL 60 秒</strong><span>灰云直接解析到当前 Static IP</span></div></div>
</section>
<section class="editor-section">
<div class="editor-section-title"><span class="service-glyph service-glyph-health"><i data-lucide="activity"></i></span><div><span class="eyebrow">AVAILABILITY</span><h3>切换检查</h3></div></div>
<label class="field proxy-url-field"><span>代理测活链接 <small>可选</small></span><span class="input-shell proxy-url-shell"><i data-lucide="link-2"></i><input name="proxy_url" type="password" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="4096" placeholder="socks5://用户名:密码@域名:端口" aria-describedby="proxy-url-status proxy-url-error"><button id="toggle-proxy-url-visibility" class="inline-icon-button" type="button" aria-label="显示代理链接" title="显示代理链接"><i data-lucide="eye"></i></button></span><small id="proxy-url-error" class="field-error" data-error-for="proxy_url" role="alert"></small></label>
<input name="proxy_protocol" type="hidden" value="socks5">
<div id="proxy-url-status" class="proxy-parse-state" role="status"><span class="proxy-parse-icon"><i data-lucide="clipboard-paste"></i></span><div><strong>可直接粘贴 SOCKS5 或 HTTP 链接</strong><small>支持 user:pass@host:port 和 host:port@user:pass</small></div></div>
<label id="clear-proxy-credentials-row" class="check-row proxy-clear-row hidden"><input name="clear_proxy_credentials" type="checkbox"><span class="check-box"><i data-lucide="check"></i></span><span><strong>清除已保存的代理认证</strong><small>保存后删除用户名和密码,不会在页面回显密文</small></span></label>
<div class="form-grid form-grid-3">
<label class="field"><span>代理端口</span><span class="input-shell"><i data-lucide="network"></i><input name="socks_port" type="number" min="1" max="65535" value="1080" required></span></label>
<label class="field"><span>检查超时</span><span class="input-shell"><i data-lucide="clock-3"></i><input name="health_timeout_seconds" type="number" min="10" max="600" value="120" required><em></em></span></label>
<label class="field"><span>释放等待</span><span class="input-shell"><i data-lucide="hourglass"></i><input name="release_grace_seconds" type="number" min="60" max="600" value="75" required><em></em></span></label>
</div>
<label class="toggle-row"><span><strong>DNS 切换前检查代理</strong><small>新 Static IP 的协议与认证通过后再更新域名</small></span><input name="proxy_health_check" type="checkbox" checked><span class="toggle" aria-hidden="true"></span></label>
</section>
<div class="form-footer sticky-dialog-foot"><button id="delete-instance" class="button button-danger-soft hidden" type="button"><i data-lucide="trash-2"></i><span>删除</span></button><span id="instance-save-state"></span><button id="test-instance-dialog" class="button button-secondary hidden" type="button"><i data-lucide="plug-zap"></i><span>测试</span></button><button id="save-instance" class="button button-primary" type="submit"><i data-lucide="save"></i><span>保存实例</span></button></div>
</form>
</dialog>
<dialog id="group-dialog" class="modal-dialog editor-dialog group-editor-dialog" aria-labelledby="group-dialog-title">
<form id="group-form" novalidate>
<div class="dialog-header sticky-dialog-head"><div><span class="eyebrow">INSTANCE GROUP</span><h2 id="group-dialog-title">新增分组</h2></div><button class="icon-button" data-close-dialog type="button" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button></div>
<section class="editor-section group-identity-section">
<div class="form-grid form-grid-2 group-title-grid">
<label class="field"><span>分组名称</span><span class="input-shell"><i data-lucide="boxes"></i><input name="name" maxlength="80" placeholder="亚太代理池" required></span><small class="field-error" data-error-for="name"></small></label>
<label class="standalone-toggle group-enabled-toggle"><input name="enabled" type="checkbox"><span class="toggle" aria-hidden="true"></span><span data-group-enabled-label>计划已暂停</span></label>
</div>
</section>
<section class="editor-section schedule-editor">
<div class="editor-section-title"><span class="service-glyph service-glyph-schedule"><i data-lucide="timer-reset"></i></span><div><span class="eyebrow">SCHEDULE</span><h3>轮换周期</h3></div></div>
<div class="schedule-config group-schedule-config">
<div><span class="field-label">常用周期</span><div id="group-interval-presets" class="preset-group"><button type="button" data-group-minutes="30">30 分钟</button><button type="button" data-group-minutes="60" class="active">1 小时</button><button type="button" data-group-minutes="360">6 小时</button><button type="button" data-group-minutes="1440">24 小时</button></div></div>
<label class="field interval-custom"><span>自定义分钟</span><span class="input-shell"><i data-lucide="sliders-horizontal"></i><input name="interval_minutes" type="number" min="5" max="10080" value="60" required><em>分钟</em></span><small class="field-error" data-error-for="interval_minutes"></small></label>
</div>
</section>
<section class="editor-section member-editor-section">
<div class="editor-section-title member-heading"><span class="service-glyph service-glyph-members"><i data-lucide="server"></i></span><div><span class="eyebrow">MEMBERS</span><h3>组内实例</h3></div><span id="selected-member-count" class="badge badge-neutral">0 个</span></div>
<label class="input-shell member-search"><i data-lucide="search"></i><input id="group-member-search" type="search" placeholder="搜索实例或域名" aria-label="搜索可选实例"></label>
<div id="group-member-list" class="member-list" role="group" aria-label="选择实例"></div>
<div id="group-members-empty" class="compact-empty hidden"><i data-lucide="server-off"></i><strong>没有可选实例</strong><span>先创建一个实例</span></div>
</section>
<div class="form-footer sticky-dialog-foot"><button id="delete-group" class="button button-danger-soft hidden" type="button"><i data-lucide="trash-2"></i><span>删除</span></button><span id="group-save-state"></span><button id="save-group" class="button button-primary" type="submit"><i data-lucide="save"></i><span>保存分组</span></button></div>
</form>
</dialog>
<dialog id="credential-dialog" class="modal-dialog editor-dialog credential-dialog" aria-labelledby="credential-dialog-title">
<form id="credential-form" novalidate>
<div class="dialog-header sticky-dialog-head"><div><span id="credential-dialog-kicker" class="eyebrow">AMAZON WEB SERVICES</span><h2 id="credential-dialog-title">新增 AWS 账号</h2></div><button class="icon-button" data-close-dialog type="button" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button></div>
<section class="editor-section credential-identity-section">
<div class="editor-section-title"><span id="credential-provider-glyph" class="service-glyph service-glyph-aws"><i data-lucide="key-round"></i></span><div><span class="eyebrow">ACCOUNT PROFILE</span><h3>账号标识</h3></div><span id="credential-config-badge" class="badge badge-neutral">新账号</span></div>
<input name="provider" type="hidden" value="aws">
<label class="field"><span>账号名称</span><span class="input-shell"><i data-lucide="tag"></i><input name="name" maxlength="80" placeholder="生产环境 AWS" required></span><small class="field-error" data-error-for="name"></small></label>
</section>
<section id="credential-aws-fields" class="editor-section">
<div class="editor-section-title compact-section-title"><span class="service-glyph service-glyph-aws"><i data-lucide="fingerprint"></i></span><div><span class="eyebrow">AUTHENTICATION</span><h3>AWS 认证方式</h3></div></div>
<div class="segmented-control credential-mode-control" aria-label="AWS 凭据方式"><button type="button" data-credential-aws-mode="access">访问密钥</button><button type="button" data-credential-aws-mode="default">默认凭据链</button></div>
<input type="hidden" name="use_default_aws_credentials" value="false">
<div id="credential-aws-secret-fields" class="form-grid form-grid-2 secret-grid">
<label class="field"><span>Access Key ID</span><span class="input-shell"><i data-lucide="fingerprint"></i><input name="aws_access_key_id" autocomplete="off" maxlength="128"></span><small class="field-error" data-error-for="aws_access_key_id"></small></label>
<label class="field"><span>Secret Access Key</span><span class="input-shell"><i data-lucide="key-round"></i><input name="aws_secret_access_key" type="password" autocomplete="new-password" maxlength="256"></span><small class="field-error" data-error-for="aws_secret_access_key"></small></label>
<label class="field field-span-2"><span>Session Token <small>可选</small></span><span class="input-shell"><i data-lucide="ticket-check"></i><input name="aws_session_token" type="password" autocomplete="new-password" maxlength="4096"></span><small class="field-error" data-error-for="aws_session_token"></small></label>
</div>
</section>
<section id="credential-cf-fields" class="editor-section hidden">
<div class="editor-section-title compact-section-title"><span class="service-glyph service-glyph-cf"><i data-lucide="shield-keyhole"></i></span><div><span class="eyebrow">AUTHENTICATION</span><h3>Cloudflare API Token</h3></div></div>
<label class="field"><span>API Token</span><span class="input-shell"><i data-lucide="shield-check"></i><input name="cloudflare_api_token" type="password" autocomplete="new-password" maxlength="2048"></span><small class="field-error" data-error-for="cloudflare_api_token"></small></label>
</section>
<div class="form-footer sticky-dialog-foot"><button id="delete-credential" class="button button-danger-soft hidden" type="button"><i data-lucide="trash-2"></i><span>删除</span></button><span id="credential-save-state"></span><button id="save-credential" class="button button-primary" type="submit"><i data-lucide="save"></i><span>保存账号</span></button></div>
</form>
</dialog>
<dialog id="rotation-confirm-dialog" class="modal-dialog" aria-labelledby="rotation-confirm-title">
<div class="modal-icon modal-icon-warn"><i data-lucide="replace"></i></div><h2 id="rotation-confirm-title">立即轮换 Static IP</h2><p id="rotation-confirm-copy">实例不会关机,切换地址时现有 SOCKS5 连接仍会中断。新 IP 与 DNS 验证成功后,系统将释放旧 Static IP。</p>
<div id="rotation-confirm-meta" class="confirm-meta"></div>
<div class="modal-actions"><button class="button button-secondary" data-close-dialog type="button">取消</button><button id="confirm-rotation" class="button button-dark" type="button"><i data-lucide="rotate-cw"></i><span>确认轮换</span></button></div>
</dialog>
<dialog id="delete-confirm-dialog" class="modal-dialog" aria-labelledby="delete-confirm-title">
<div class="modal-icon modal-icon-danger"><i data-lucide="trash-2"></i></div><h2 id="delete-confirm-title">删除配置?</h2><p id="delete-confirm-copy">该配置将从控制台移除,当前附加的 Static IP 不会被主动释放。</p>
<div class="modal-actions"><button class="button button-secondary" data-close-dialog type="button">取消</button><button id="confirm-delete" class="button button-danger" type="button"><i data-lucide="trash-2"></i><span>确认删除</span></button></div>
</dialog>
<dialog id="run-dialog" class="modal-dialog run-dialog" aria-labelledby="run-dialog-title">
<div class="dialog-header"><div><span class="eyebrow">RUN DETAILS</span><h2 id="run-dialog-title">任务详情</h2></div><button class="icon-button" data-close-dialog type="button" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button></div>
<div id="run-dialog-summary" class="run-dialog-summary"></div>
<div id="run-items-wrap" class="run-items-wrap hidden"><div class="run-subheading"><span>实例进度</span><strong id="run-items-count">0 / 0</strong></div><div id="run-items" class="run-items"></div></div>
<div class="run-subheading"><span>任务事件</span></div><ol id="run-events" class="event-timeline"></ol>
<div id="resume-run-wrap" class="modal-actions run-recovery-actions hidden"><button id="cancel-run" class="button button-danger" type="button"><i data-lucide="octagon-x"></i><span>放弃任务</span></button><button id="resume-run" class="button button-primary" type="button"><i data-lucide="play"></i><span>继续处理</span></button></div>
</dialog>
<dialog id="cancel-run-dialog" class="modal-dialog" aria-labelledby="cancel-run-title">
<div class="modal-icon modal-icon-warn"><i data-lucide="triangle-alert"></i></div><h2 id="cancel-run-title">放弃当前任务?</h2><p>系统将停止自动恢复。请在完成后核对实例、Cloudflare DNS 和旧 Static IP 的释放状态。</p>
<div class="modal-actions"><button class="button button-secondary" data-close-dialog type="button">返回</button><button id="confirm-cancel-run" class="button button-danger" type="button"><i data-lucide="octagon-x"></i><span>确认放弃</span></button></div>
</dialog>
<dialog id="account-dialog" class="modal-dialog account-dialog" aria-labelledby="account-dialog-title">
<div class="dialog-header"><div><span class="eyebrow">ACCOUNT</span><h2 id="account-dialog-title">管理员账户</h2></div><button class="icon-button" data-close-dialog type="button" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button></div>
<form id="password-form" class="form-stack" novalidate><label class="field"><span>当前密码</span><span class="input-shell"><i data-lucide="key-round"></i><input name="current_password" type="password" autocomplete="current-password" required></span></label><label class="field"><span>新密码</span><span class="input-shell"><i data-lucide="shield-check"></i><input name="new_password" type="password" autocomplete="new-password" minlength="10" required></span></label><button class="button button-primary button-wide" type="submit"><i data-lucide="save"></i><span>更新密码</span></button></form>
<div class="account-divider"></div><button id="logout-button" class="button button-secondary button-wide" type="button"><i data-lucide="log-out"></i><span>退出登录</span></button>
</dialog>
<div id="toast-region" class="toast-region" aria-live="polite" aria-atomic="true"></div>
<script src="/static/vendor/lucide.min.js"></script>
<script src="/static/app.js"></script>
</body>
</html>