feat: sync runtime multi-user web and login updates

This commit is contained in:
Rixuan Shao
2026-08-17 23:16:12 +08:00
parent 846ce88daf
commit ee441ff5a1
23 changed files with 1879 additions and 198 deletions
+11 -3
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark light">
<title>{% block title %}续火花{% endblock %}</title>
<link rel="stylesheet" href="/static/app.css?v=20260710">
<link rel="stylesheet" href="/static/app.css?v=20260817">
</head>
<body data-page="{% block page_key %}dashboard{% endblock %}">
<header class="mobile-topbar">
@@ -49,9 +49,11 @@
<a class="nav-item {% if current_nav|trim == 'send_console' %}active{% endif %}" href="/ops/send-console">
<i data-lucide="send"></i><span>发送控制台</span>
</a>
{% if is_admin %}
<a class="nav-item {% if current_nav|trim == 'logs' %}active{% endif %}" href="/ops/logs">
<i data-lucide="scroll-text"></i><span>发送记录</span>
</a>
{% endif %}
</div>
</section>
@@ -64,15 +66,21 @@
<a class="nav-item" href="/#interactive-login-section">
<i data-lucide="scan-line"></i><span>登录抖音账号</span>
</a>
{% if is_admin %}
<a class="nav-item" href="/#config-panel">
<i data-lucide="sliders-horizontal"></i><span>运行配置</span>
</a>
{% endif %}
{% if is_admin %}
<a class="nav-item" href="/#ops-panel">
<i data-lucide="wrench"></i><span>运维操作</span>
</a>
{% endif %}
{% if is_admin %}
<a class="nav-item" href="/#settings-panel">
<i data-lucide="settings"></i><span>系统设置</span>
</a>
{% endif %}
</div>
</section>
</nav>
@@ -82,7 +90,7 @@
<span class="user-avatar">{{ (current_user or "A")[:1] }}</span>
<span class="user-copy">
<strong>{{ current_user or "未登录" }}</strong>
<small>管理员</small>
<small>{% if is_admin %}管理员{% else %}普通用户{% endif %}</small>
</span>
</div>
<form method="post" action="/logout">
@@ -131,7 +139,7 @@
</dialog>
<script defer src="/static/lucide.min.js?v=20260710"></script>
<script defer src="/static/app.js?v=20260710"></script>
<script defer src="/static/app.js?v=20260817"></script>
{% block scripts %}{% endblock %}
</body>
</html>
+112 -7
View File
@@ -226,21 +226,33 @@
</div>
</div>
<div class="button-row">
<button class="button button-primary login-desktop-open" type="button">
<i data-lucide="external-link"></i><span>打开登录工作区</span>
<button class="button button-primary login-desktop-open" type="button" data-login-mode="add">
<i data-lucide="user-plus"></i><span>添加新的抖音账号</span>
</button>
<button class="button button-soft login-desktop-save" type="button" data-relogin-unique-id="">
<i data-lucide="save"></i><span>保存当前登录</span>
<button class="button button-soft login-desktop-save" type="button" data-relogin-unique-id="" data-login-mode="add">
<i data-lucide="save"></i><span>保存新账号登录</span>
</button>
<button
class="button button-danger login-desktop-close"
type="button"
data-confirm-title="关闭登录界面"
data-confirm="当前未保存的登录过程会被关闭,登录界面会恢复初始状态。确认继续吗?"
data-confirm-accept="确认关闭"
>
<i data-lucide="x-circle"></i><span>关闭登录界面</span>
</button>
{% if is_admin %}
<button
class="button button-danger login-desktop-reset"
type="button"
data-confirm-title="重置登录工作区"
data-confirm="登录浏览器会被重置,当前未保存的登录过程将丢失。确认继续吗?"
data-confirm-title="强制重置登录工作区"
data-confirm="会清理当前登录环境并切换队列中的下一位用户。确认继续吗?"
data-confirm-accept="确认重置"
>
<i data-lucide="rotate-ccw"></i><span>重置工作区</span>
<i data-lucide="rotate-ccw"></i><span>强制重置工作区</span>
</button>
{% endif %}
<p class="muted compact login-workspace-hint">已有抖音账号请从对应账号卡片点击“重新登录”;“添加新的抖音账号”会将新账号自动绑定到当前用户。</p>
<button class="button button-quiet" type="button" data-copy-login-url>
<i data-lucide="copy"></i><span>复制地址</span>
</button>
@@ -258,6 +270,76 @@
</div>
</details>
{% if is_admin %}
<section class="dashboard-section" id="user-management">
<div class="section-header">
<div>
<h2 class="section-title">用户管理</h2>
<p class="muted compact">创建普通用户并分配可管理的抖音账号。管理员始终可以查看全部账号。</p>
</div>
<span class="pill">{{ web_users|length }} 个普通用户</span>
</div>
<details class="collapsible-section" open>
<summary><span class="summary-copy"><i data-lucide="user-plus"></i><span><strong>创建用户</strong><small>账号绑定可后续调整</small></span></span><i class="summary-chevron" data-lucide="chevron-down"></i></summary>
<div class="collapsible-body">
<form method="post" action="/admin/users/create" class="stack-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-grid">
<label><span>用户名</span><input type="text" name="username" pattern="[A-Za-z0-9_.-]+" required></label>
<label><span>初始密码</span><input type="password" name="password" autocomplete="new-password" required></label>
</div>
<label class="check-row"><input type="checkbox" name="enabled" checked><span>允许登录</span></label>
<fieldset class="stack-form account-assignment-picker">
<legend>分配账号</legend>
<p class="muted compact">可多选;如果暂时不分配账号,保持全部未选即可。</p>
{% for account in all_accounts %}
<label class="check-row">
<input type="checkbox" name="account_refs" value="{{ account.account_ref }}">
<span>{{ account.username }}{{ account.unique_id }}</span>
</label>
{% else %}
<p class="muted compact">当前没有可分配的抖音账号。</p>
{% endfor %}
</fieldset>
<button class="button button-primary" type="submit"><i data-lucide="user-plus"></i><span>创建普通用户</span></button>
</form>
</div>
</details>
{% for web_user in web_users %}
<details class="collapsible-section">
<summary><span class="summary-copy"><span class="account-avatar">{{ (web_user.username or "U")[:1] }}</span><span><strong>{{ web_user.username }}</strong><small>{{ web_user.account_refs|length }} 个绑定账号</small></span></span><span class="status-chip {% if web_user.enabled %}success{% else %}warning{% endif %}">{% if web_user.enabled %}可登录{% else %}已停用{% endif %}</span><i class="summary-chevron" data-lucide="chevron-down"></i></summary>
<div class="collapsible-body">
<form method="post" action="/admin/users/{{ web_user.username }}/update" class="stack-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-grid">
<label><span>用户名</span><input type="text" name="new_username" value="{{ web_user.username }}" pattern="[A-Za-z0-9_.-]+" required></label>
<label><span>重置密码(留空不修改)</span><input type="password" name="password" autocomplete="new-password"></label>
</div>
<label class="check-row"><input type="checkbox" name="enabled" {% if web_user.enabled %}checked{% endif %}><span>允许登录</span></label>
<fieldset class="stack-form account-assignment-picker">
<legend>绑定账号</legend>
<p class="muted compact">可多选;全部未选表示解除该用户的所有抖音账号绑定。</p>
{% for account in all_accounts %}
<label class="check-row">
<input type="checkbox" name="account_refs" value="{{ account.account_ref }}" {% if account.account_ref in web_user.account_refs %}checked{% endif %}>
<span>{{ account.username }}{{ account.unique_id }}</span>
</label>
{% else %}
<p class="muted compact">当前没有可分配的抖音账号。</p>
{% endfor %}
</fieldset>
<div class="form-actions"><button class="button button-primary" type="submit"><i data-lucide="save"></i><span>保存用户</span></button></div>
</form>
<form method="post" action="/admin/users/{{ web_user.username }}/delete" data-confirm-title="删除 Web 用户" data-confirm="只删除 Web 用户并解除账号绑定,不删除抖音账号数据。确认继续吗?" data-confirm-accept="确认删除">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="button button-danger" type="submit"><i data-lucide="trash-2"></i><span>删除用户</span></button>
</form>
</div>
</details>
{% endfor %}
</section>
{% endif %}
<section class="dashboard-section" id="account-management">
<div class="section-header">
<div>
@@ -355,6 +437,7 @@
<span>{% if account.enabled|default(true) %}停用自动发送{% else %}启用自动发送{% endif %}</span>
</button>
</form>
{% if is_admin %}
<form
method="post"
action="/accounts/{{ account.unique_id }}/delete"
@@ -367,6 +450,7 @@
<i data-lucide="trash-2"></i><span>删除账号</span>
</button>
</form>
{% endif %}
</div>
</div>
</details>
@@ -377,6 +461,7 @@
</section>
<section class="advanced-grid">
{% if is_admin %}
<details class="collapsible-section" id="config-panel">
<summary>
<span class="summary-copy">
@@ -494,6 +579,26 @@
</form>
</div>
</details>
{% endif %}
{% if not is_admin %}
<details class="collapsible-section" id="password-panel">
<summary>
<span class="summary-copy"><i data-lucide="key-round"></i><span><strong>修改登录密码</strong><small>修改当前 Web 用户密码</small></span></span>
<i class="summary-chevron" data-lucide="chevron-down"></i>
</summary>
<div class="collapsible-body">
<form method="post" action="/account/password" class="stack-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-grid">
<label><span>新密码</span><input type="password" name="new_password" autocomplete="new-password" required></label>
<label><span>确认新密码</span><input type="password" name="confirm_password" autocomplete="new-password" required></label>
</div>
<button class="button button-primary" type="submit"><i data-lucide="save"></i><span>保存密码</span></button>
</form>
</div>
</details>
{% endif %}
</section>
</div>
{% endblock %}