Files
douyin-sparkflow/DouYinSparkFlow/webui/templates/send_console.html

299 lines
15 KiB
HTML

{% extends "base.html" %}
{% block nav_key %}send_console{% endblock %}
{% block page_key %}send-console{% endblock %}
{% block title %}发送控制台{% endblock %}
{% block page_title %}发送控制台{% endblock %}
{% block page_subtitle %}异常优先查看,健康账号默认收起{% endblock %}
{% block topbar_actions %}
{% set top_summary = ops.send_console.summary %}
<a class="button button-quiet" href="/">
<i data-lucide="arrow-left"></i><span>返回首页</span>
</a>
<form
method="post"
action="/ops/run-failed"
data-confirm-title="补发异常目标"
data-confirm="将补发 {{ top_summary.today_attention_targets }} 个待核验、失败或受账号异常影响的目标。确认继续吗?"
data-confirm-accept="开始补发"
data-confirm-tone="primary"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button
class="button button-soft"
type="submit"
data-action-count-source="attention"
{% if ops.task_lock.running or top_summary.today_attention_targets == 0 %}disabled{% endif %}
>
<i data-lucide="refresh-cw"></i>
<span>补发异常 <b data-action-count>{{ top_summary.today_attention_targets }}</b></span>
</button>
</form>
<form
method="post"
action="/ops/run-unsent"
data-confirm-title="补发今日待发送目标"
data-confirm="将立即补发 {{ top_summary.today_pending_targets + top_summary.today_unprocessed_targets }} 个尚未强确认且仍可重试的目标。确认继续吗?"
data-confirm-accept="开始补发"
data-confirm-tone="primary"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button
class="button button-primary"
type="submit"
data-action-count-source="pending"
{% if ops.task_lock.running or (top_summary.today_pending_targets + top_summary.today_unprocessed_targets) == 0 %}disabled{% endif %}
>
<i data-lucide="send"></i>
<span>补发待发送 <b data-action-count>{{ top_summary.today_pending_targets + top_summary.today_unprocessed_targets }}</b></span>
</button>
</form>
{% endblock %}
{% block content %}
{% set send_console = ops.send_console %}
{% set summary = send_console.summary %}
<div class="console-shell" data-overview-root>
<div
class="status-banner {% if ops.task_lock.running %}warning{% elif ops.task_lock.stale %}info{% else %}success{% endif %}"
data-task-banner
>
<strong>
<i data-lucide="{% if ops.task_lock.running %}loader-circle{% elif ops.task_lock.stale %}info{% else %}circle-check{% endif %}"></i>
<span data-task-text>
{% if ops.task_lock.running %}
发送任务运行中,已运行约 {{ ops.task_lock.ageSeconds }} 秒
{% elif ops.task_lock.stale %}
检测到过期任务锁,下次启动任务时会自动清理
{% else %}
当前没有发送任务运行
{% endif %}
</span>
</strong>
<span class="pill info" data-overview-live-state>实时</span>
</div>
<div class="status-banner info refresh-notice" data-refresh-notice>
<strong><i data-lucide="circle-check"></i><span>任务已结束,刷新后可查看最新目标级结果。</span></strong>
<button class="button button-quiet" type="button" data-refresh-page>
<i data-lucide="refresh-cw"></i><span>刷新详情</span>
</button>
</div>
<section class="metric-grid" aria-label="发送状态汇总">
<article class="metric-card info">
<div class="metric-label">总目标</div>
<div class="metric-value" data-overview-value="total">{{ summary.total_targets }}</div>
<div class="metric-help">{{ summary.enabled_accounts }} 个启用账号</div>
</article>
<article class="metric-card success">
<div class="metric-label">强确认成功</div>
<div class="metric-value" data-overview-value="confirmed">{{ summary.today_confirmed_targets }}</div>
<div class="metric-help">服务端回执或页面回显</div>
</article>
<article class="metric-card danger">
<div class="metric-label">需要处理</div>
<div class="metric-value" data-overview-value="attention">{{ summary.today_attention_targets }}</div>
<div class="metric-help">待核验、失败和账号阻断</div>
</article>
<article class="metric-card warning">
<div class="metric-label">待发送</div>
<div class="metric-value" data-overview-value="pending">
{{ summary.today_pending_targets + summary.today_unprocessed_targets }}
</div>
<div class="metric-help">等待调度或尚未处理</div>
</article>
</section>
<section class="dashboard-section">
<div class="section-header">
<div>
<h2 class="section-title">账号明细</h2>
<p class="muted compact">异常账号自动展开;成功、异常和待发送分开查看。</p>
</div>
<span class="pill">{{ send_console.nowDisplay }}</span>
</div>
<div class="account-console-list">
{% for account in send_console.accounts %}
{% set exception_count = account.unconfirmed_targets|length + account.failed_targets|length + account.account_blocked_targets|length %}
{% set success_count = account.confirmed_targets|length %}
{% set waiting_targets = account.pending_targets + account.unprocessed_targets %}
{% set default_segment = "exceptions" if exception_count else ("success" if success_count else "pending") %}
<details
class="account-console state-{{ account.state }}"
id="account-{{ account.unique_id }}"
data-segment-owner
data-account-overview="{{ account.unique_id }}"
{% if account.state in ["attention", "paused", "warning"] %}open{% endif %}
>
<summary>
<span class="account-ident">
<span class="account-avatar">{{ (account.username or "A")[:1] }}</span>
<span class="account-name">
<strong>{{ account.username }}</strong>
<small>{{ account.confirmed_targets|length }}/{{ account.total_targets }} 已强确认</small>
</span>
</span>
<span class="account-console-metrics">
<span class="status-chip success">已确认 <b data-account-confirmed>{{ success_count }}</b></span>
{% if exception_count %}<span class="status-chip danger">需处理 <b data-account-attention>{{ exception_count }}</b></span>{% endif %}
{% if waiting_targets|length %}<span class="status-chip warning">待发送 <b data-account-pending>{{ waiting_targets|length }}</b></span>{% endif %}
{% if account.account_paused %}<span class="status-chip danger">账号暂停</span>{% endif %}
</span>
<i class="summary-chevron" data-lucide="chevron-down"></i>
</summary>
<div class="account-console-body">
{% if account.warnings %}
<div class="warning-box">
{% for warning in account.warnings %}<div>{{ warning.message }}</div>{% endfor %}
</div>
{% endif %}
{% if account.account_failure %}
<div class="danger-box">
<strong>账号级异常:{{ account.account_failure.category or "-" }}</strong>
<div>今日尝试 {{ account.account_failure.attemptCount or 0 }}/{{ account.account_failure_pause_after }} · {{ account.account_failure.reason or "-" }}</div>
</div>
{% endif %}
<div class="account-meta-strip">
<span>
好友索引:
{% if account.friend_index_meta.lastScanAt %}
{{ "完整" if account.friend_index_meta.lastScanComplete else "未完整" }},记录 {{ account.friend_index_count }} 个
{% else %}
尚未扫描
{% endif %}
</span>
<span>最近强确认:{{ account.last_confirmed_display or "-" }}</span>
</div>
<div class="segmented-control" data-segment-group role="tablist" aria-label="{{ account.username }} 发送状态">
<button class="segment-button {% if default_segment == 'exceptions' %}active{% endif %}" type="button" data-segment-target="exceptions">
<i data-lucide="triangle-alert"></i><span>异常 {{ exception_count }}</span>
</button>
<button class="segment-button {% if default_segment == 'success' %}active{% endif %}" type="button" data-segment-target="success">
<i data-lucide="circle-check"></i><span>成功 {{ success_count }}</span>
</button>
<button class="segment-button {% if default_segment == 'pending' %}active{% endif %}" type="button" data-segment-target="pending">
<i data-lucide="clock-3"></i><span>待发送 {{ waiting_targets|length }}</span>
</button>
</div>
<div data-segment-panel="exceptions" {% if default_segment != "exceptions" %}hidden{% endif %}>
<div class="target-list">
{% for item in account.unconfirmed_targets %}
<article class="target-record">
<div class="target-record-main">
<strong>{{ item.target }}</strong>
<small>{{ item.categoryLabel }} · 已尝试 {{ item.attemptCount or 0 }} 次</small>
</div>
<div class="target-reason" title="{{ item.reason or item.confirmationDetail or '' }}">
{{ item.reason or item.confirmationDetail or "发送结果缺少强确认" }}
</div>
<span class="target-meta">{{ item.displayLastAttemptAt or item.displaySentAt or "-" }}</span>
<form method="post" action="/accounts/{{ account.unique_id }}/retry-target">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="target" value="{{ item.target }}">
<button class="button button-soft" type="submit"><i data-lucide="refresh-cw"></i><span>重试</span></button>
</form>
</article>
{% endfor %}
{% for item in account.failed_targets %}
<article class="target-record">
<div class="target-record-main">
<strong>{{ item.target }}</strong>
<small>{{ item.categoryLabel }} · 已尝试 {{ item.attemptCount or 0 }} 次</small>
</div>
<div class="target-reason" title="{{ item.reason or '' }}">{{ item.reason or "未记录失败原因" }}</div>
<span class="target-meta">{{ item.displayLastAttemptAt or "-" }}</span>
<form method="post" action="/accounts/{{ account.unique_id }}/retry-target">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="target" value="{{ item.target }}">
<button class="button button-soft" type="submit"><i data-lucide="refresh-cw"></i><span>重试</span></button>
</form>
</article>
{% endfor %}
{% for item in account.account_blocked_targets %}
<article class="target-record">
<div class="target-record-main">
<strong>{{ item.target }}</strong>
<small>受账号异常影响</small>
</div>
<div class="target-reason" title="{{ item.reason or '' }}">{{ item.reason or item.categoryLabel }}</div>
<span class="target-meta">{{ item.displayLastAttemptAt or "-" }}</span>
<form method="post" action="/accounts/{{ account.unique_id }}/retry-target">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="target" value="{{ item.target }}">
<button class="button button-soft" type="submit"><i data-lucide="refresh-cw"></i><span>重试</span></button>
</form>
</article>
{% endfor %}
{% if exception_count == 0 %}
<div class="empty-state">该账号今天没有待核验、失败或受阻断的目标。</div>
{% endif %}
</div>
</div>
<div data-segment-panel="success" {% if default_segment != "success" %}hidden{% endif %}>
<div class="target-list">
{% for item in account.confirmed_targets %}
<article class="target-record">
<div class="target-record-main">
<strong>{{ item.target }}</strong>
<small>{{ item.confirmationLabel }}</small>
</div>
<div class="target-message" title="{{ item.message or '' }}">{{ item.message or "未保存消息正文" }}</div>
<span class="target-meta" title="{{ item.sentAt or '' }}">{{ item.displaySentAt or "-" }}</span>
<form
method="post"
action="/accounts/{{ account.unique_id }}/mark-target-unconfirmed"
data-confirm-title="标记为待核验"
data-confirm="将把“{{ item.target }}”的今日强确认记录改为待核验并加入补发队列。确认继续吗?"
data-confirm-accept="标记待核验"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="target" value="{{ item.target }}">
<button class="button button-quiet" type="submit"><i data-lucide="flag"></i><span>待核验</span></button>
</form>
</article>
{% else %}
<div class="empty-state">该账号今天还没有强确认记录。</div>
{% endfor %}
</div>
</div>
<div data-segment-panel="pending" {% if default_segment != "pending" %}hidden{% endif %}>
<div class="target-list">
{% for item in waiting_targets %}
<article class="target-record">
<div class="target-record-main">
<strong>{{ item.target }}</strong>
<small>{% if item.status == "pending" %}等待计划时间{% else %}尚未处理{% endif %}</small>
</div>
<div class="target-message">好友索引:{% if item.friendIndex.seen %}已记录{% else %}尚未记录{% endif %}</div>
<span class="target-meta">{{ item.displayScheduledAt or "-" }}</span>
<span class="status-chip warning">待发送</span>
</article>
{% else %}
<div class="empty-state">该账号今天没有待发送目标。</div>
{% endfor %}
</div>
</div>
</div>
</details>
{% else %}
<div class="empty-state">当前没有启用账号。</div>
{% endfor %}
</div>
</section>
</div>
{% endblock %}