initial: blog-app snapshot
This commit is contained in:
119
templates/analyze.html
Normal file
119
templates/analyze.html
Normal file
@@ -0,0 +1,119 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}AI 分析 - AI Agent 项目追踪{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="mb-4">AI 分析</h1>
|
||||
|
||||
<!-- Control panel -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<!-- Scope selector -->
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">分析范围</label>
|
||||
<select class="form-select" id="scopeSelect">
|
||||
<option value="home">全站概览</option>
|
||||
<option value="projects">所有项目</option>
|
||||
<!-- 项目选项由 JS 从 /api/projects 动态填充, 与 DB 保持一致 -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Preset buttons -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">预设分析</label>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button type="button" class="btn btn-outline-primary preset-btn" data-preset="activity">
|
||||
📊 活跃度评估
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary preset-btn" data-preset="evolution">
|
||||
🚀 版本演进
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary preset-btn" data-preset="issues_hot">
|
||||
🔥 Issue 热点
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Start button -->
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-success w-100" id="startBtn">
|
||||
开始分析
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom question input -->
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-12">
|
||||
<label class="form-label">自定义问题 (可选)</label>
|
||||
<input type="text" class="form-control" id="questionInput"
|
||||
placeholder="例如: 这个项目的核心功能是什么? / 最近有什么重要更新?">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Output area with tabs -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" id="outputTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="observation-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#observation" type="button" role="tab">
|
||||
数据观察
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="prompt-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#prompt" type="button" role="tab">
|
||||
Prompt
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="reasoning-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#reasoning" type="button" role="tab">
|
||||
LLM 推理
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="conclusion-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#conclusion" type="button" role="tab">
|
||||
结论
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content" id="outputTabContent">
|
||||
<div class="tab-pane fade show active" id="observation" role="tabpanel">
|
||||
<div class="output-content" id="observationContent">
|
||||
<div class="text-muted">等待分析开始...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="prompt" role="tabpanel">
|
||||
<div class="output-content" id="promptContent">
|
||||
<div class="text-muted">等待分析开始...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="reasoning" role="tabpanel">
|
||||
<div class="output-content" id="reasoningContent">
|
||||
<div class="text-muted">等待分析开始...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="conclusion" role="tabpanel">
|
||||
<div class="output-content" id="conclusionContent">
|
||||
<div class="text-muted">等待分析开始...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="module">
|
||||
import { initAnalyzePage } from "{{ url_for('static', filename='js/pages/analyze.js') }}";
|
||||
|
||||
initAnalyzePage();
|
||||
</script>
|
||||
{% endblock %}
|
||||
72
templates/base.html
Normal file
72
templates/base.html
Normal file
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}AI Agent 项目追踪{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">AI Agent 追踪</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="mainNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item"><a class="nav-link" href="/">首页</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/projects">项目列表</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/search">全局搜索</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/watchdog" title="每天 9:00 自动生成的 AI Agent 项目升级日报">日报</a></li>
|
||||
</ul>
|
||||
<form class="d-flex" action="/search" method="get" id="navSearchForm">
|
||||
<input class="form-control me-2 nav-search-input" type="search"
|
||||
name="q" placeholder="搜索项目 / release / issue" aria-label="Search">
|
||||
<button class="btn btn-outline-light" type="submit">搜索</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-4 flex-grow-1">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category | default('info') }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-light text-center py-3 mt-auto">
|
||||
<div class="container">
|
||||
AI Agent 项目追踪 - Powered by Flask
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked@15/marked.min.js"
|
||||
integrity="sha384-948ahk4ZmxYVYOc+rxN1H2gM1EJ2Duhp7uHtZ4WSLkV4Vtx5MUqnV+l7u9B+jFv+"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"
|
||||
integrity="sha384-ZuC+DIACqSIZTsp+7YF57cR5Y+6qXa7YFbEKdA/EHA/R0T+41dtorqucYl71Zp+t"
|
||||
crossorigin="anonymous"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
<!-- 全站浮动 AI 分析面板 -->
|
||||
<button id="ai-fab" type="button" title="AI 分析">🤖</button>
|
||||
<script type="module" src="{{ url_for('static', filename='js/ai-panel.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
57
templates/index.html
Normal file
57
templates/index.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}首页 - AI Agent 项目追踪{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="mb-4">项目总览</h1>
|
||||
|
||||
<!-- Stats cards -->
|
||||
<div class="row g-3 mb-5" id="statsRow">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card stat-card text-center">
|
||||
<div class="card-body">
|
||||
<div class="stat-number text-primary" id="statProjects">-</div>
|
||||
<div class="stat-label">项目数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card stat-card text-center">
|
||||
<div class="card-body">
|
||||
<div class="stat-number text-success" id="statReleases">-</div>
|
||||
<div class="stat-label">Release 数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card stat-card text-center">
|
||||
<div class="card-body">
|
||||
<div class="stat-number text-warning" id="statIssues">-</div>
|
||||
<div class="stat-label">Issue 数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card stat-card text-center">
|
||||
<div class="card-body">
|
||||
<div class="stat-number text-danger" id="statStars">-</div>
|
||||
<div class="stat-label">总 Stars</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="mb-3">项目 Dashboard</h2>
|
||||
<div class="row g-3" id="projectCards">
|
||||
<div class="empty-hint">加载中…</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="module">
|
||||
import { renderProjectCard, bindProjectCardClicks } from "{{ url_for('static', filename='js/components/project-card.js') }}";
|
||||
import { initIndexPage } from "{{ url_for('static', filename='js/pages/index.js') }}";
|
||||
|
||||
bindProjectCardClicks();
|
||||
initIndexPage(renderProjectCard);
|
||||
</script>
|
||||
{% endblock %}
|
||||
45
templates/project_detail.html
Normal file
45
templates/project_detail.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}项目详情 - AI Agent 项目追踪{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="projectHeader">
|
||||
<div class="empty-hint">加载项目信息…</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mt-2">
|
||||
<div class="col-lg-6">
|
||||
<h2 class="h4 mb-3">Release 时间线</h2>
|
||||
<div class="timeline">
|
||||
<ul class="list-group" id="releaseTimeline">
|
||||
<li class="list-group-item empty-hint">加载中…</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<h2 class="h4 mb-3">最近 Issues</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>标题</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th>用户</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="issueTableBody">
|
||||
<tr><td colspan="4" class="empty-hint">加载中…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="module">
|
||||
import { initProjectDetailPage } from "{{ url_for('static', filename='js/pages/project-detail.js') }}";
|
||||
|
||||
initProjectDetailPage({{ project_id | tojson }});
|
||||
</script>
|
||||
{% endblock %}
|
||||
33
templates/projects.html
Normal file
33
templates/projects.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}项目列表 - AI Agent 项目追踪{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4 gap-2">
|
||||
<h1 class="mb-0">项目列表</h1>
|
||||
<div class="d-flex gap-2">
|
||||
<div class="btn-group" role="group" aria-label="语言过滤">
|
||||
<button type="button" class="btn btn-outline-primary active" data-lang="all">All</button>
|
||||
<button type="button" class="btn btn-outline-primary" data-lang="Python">Python</button>
|
||||
<button type="button" class="btn btn-outline-primary" data-lang="TypeScript">TypeScript</button>
|
||||
</div>
|
||||
<select class="form-select w-auto" id="sortSelect">
|
||||
<option value="stars">按 Stars 排序</option>
|
||||
<option value="updated_at">按更新时间排序</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3" id="projectList">
|
||||
<div class="empty-hint">加载中…</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="module">
|
||||
import { renderProjectCard, bindProjectCardClicks } from "{{ url_for('static', filename='js/components/project-card.js') }}";
|
||||
import { initProjectsPage } from "{{ url_for('static', filename='js/pages/projects.js') }}";
|
||||
|
||||
bindProjectCardClicks();
|
||||
initProjectsPage(renderProjectCard);
|
||||
</script>
|
||||
{% endblock %}
|
||||
36
templates/search.html
Normal file
36
templates/search.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}全局搜索 - AI Agent 项目追踪{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="mb-4">全局搜索</h1>
|
||||
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-md-8">
|
||||
<input type="search" class="form-control form-control-lg search-input"
|
||||
id="searchInput" placeholder="输入关键词搜索项目 / release / issue…">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="searchHint" class="empty-hint">输入关键词开始搜索</div>
|
||||
|
||||
<div id="searchResults" class="d-none">
|
||||
<h2 class="h4 mt-3">匹配的项目 <span class="badge bg-primary" id="projCount">0</span></h2>
|
||||
<div class="row g-3 mb-4" id="resultProjects"></div>
|
||||
|
||||
<h2 class="h4">匹配的 Releases <span class="badge bg-success" id="relCount">0</span></h2>
|
||||
<ul class="list-group mb-4" id="resultReleases"></ul>
|
||||
|
||||
<h2 class="h4">匹配的 Issues <span class="badge bg-warning text-dark" id="issueCount">0</span></h2>
|
||||
<ul class="list-group mb-4" id="resultIssues"></ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="module">
|
||||
import { renderProjectCard, bindProjectCardClicks } from "{{ url_for('static', filename='js/components/project-card.js') }}";
|
||||
import { initSearchPage } from "{{ url_for('static', filename='js/pages/search.js') }}";
|
||||
|
||||
bindProjectCardClicks();
|
||||
initSearchPage(renderProjectCard);
|
||||
</script>
|
||||
{% endblock %}
|
||||
159
templates/watchdog.html
Normal file
159
templates/watchdog.html
Normal file
@@ -0,0 +1,159 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}日报 - AI Agent 项目追踪{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4 gap-2">
|
||||
<div>
|
||||
<h1 class="mb-1">升级迭代日报</h1>
|
||||
<small class="text-muted">每天 09:00 自动更新 · 最新报告: {{ reports[0].date if reports else "无" }}</small>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<select class="form-select form-select-sm w-auto" onchange="location.search='?days='+this.value" aria-label="历史天数">
|
||||
{% for d in [7, 14, 30, 90] %}
|
||||
<option value="{{ d }}" {{ 'selected' if days == d }}>最近 {{ d }} 天</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<form method="post" action="/watchdog/run" id="run-form" class="m-0">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-primary" id="run-btn">立即运行</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wd-pending" class="alert alert-warning d-none mt-2 mb-3">
|
||||
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||
报告生成中, 通常 1-2 分钟, 完成后页面自动刷新…
|
||||
</div>
|
||||
|
||||
{% if not reports %}
|
||||
<div class="alert alert-info">
|
||||
暂无报告, 点击立即运行生成第一份日报
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if reports and reports[0].meta %}
|
||||
{% set m = reports[0].meta %}
|
||||
{% set projs = m.get("projects", {}) %}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-6 col-md-3"><div class="card stat-card text-center"><div class="card-body">
|
||||
<div class="stat-number text-primary">{{ projs|length }}</div>
|
||||
<div class="stat-label">监控项目</div></div></div></div>
|
||||
<div class="col-6 col-md-3"><div class="card stat-card text-center"><div class="card-body">
|
||||
<div class="stat-number text-success">{{ projs.values()|selectattr('status','equalto','ok')|list|length }}</div>
|
||||
<div class="stat-label">✓ 正常</div></div></div></div>
|
||||
<div class="col-6 col-md-3"><div class="card stat-card text-center"><div class="card-body">
|
||||
<div class="stat-number text-warning">{{ projs.values()|selectattr('status','equalto','partial')|list|length }}</div>
|
||||
<div class="stat-label">⚠ 部分</div></div></div></div>
|
||||
<div class="col-6 col-md-3"><div class="card stat-card text-center"><div class="card-body">
|
||||
<div class="stat-number text-danger">{{ m.get("sources_failed", [])|length }}</div>
|
||||
<div class="stat-label">✗ 失败数据源</div></div></div></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for r in reports %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div>
|
||||
<strong>{{ r.date }}</strong>
|
||||
<span class="text-muted small ms-2">生成于 {{ r.created_at[:19] }}</span>
|
||||
</div>
|
||||
{% set prev = reports[loop.index] if not loop.last else none %}
|
||||
{% if prev and r.meta and prev.meta %}
|
||||
{% set cur_failed = r.meta.get("sources_failed", []) %}
|
||||
{% set prev_failed = prev.meta.get("sources_failed", []) %}
|
||||
{% set fixed = prev_failed|reject('in', cur_failed)|list %}
|
||||
{% set broke = cur_failed|reject('in', prev_failed)|list %}
|
||||
<small class="text-muted">
|
||||
{% if fixed %}<span class="badge bg-success-subtle text-success-emphasis">✅ 恢复: {{ fixed|join(", ") }}</span>{% endif %}
|
||||
{% if broke %}<span class="badge bg-danger-subtle text-danger-emphasis">⚠ 新增失败: {{ broke|join(", ") }}</span>{% endif %}
|
||||
{% if not fixed and not broke %}<span class="badge bg-secondary-subtle text-secondary-emphasis">与昨日相同</span>{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if r.meta %}
|
||||
<div class="d-flex flex-wrap gap-1 mb-2">
|
||||
{% for name, info in r.meta.get("projects", {}).items() %}
|
||||
{% set st = info.get('status', 'unknown') %}
|
||||
{% set is_starred = name in starred %}
|
||||
<span class="badge {{ 'badge-state-open' if st=='ok' else ('badge-state-partial' if st=='partial' else 'bg-danger') }}"
|
||||
title="{{ name }}: {{ st }}{{ ' (已装)' if is_starred else '' }}">
|
||||
{{ name }}{% if is_starred %} ★{% endif %}
|
||||
{% if st=='ok' %} ✓{% elif st=='partial' %} ⚠{% else %} ✗{% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% for s in r.meta.get("sources_failed", []) %}
|
||||
<span class="badge bg-danger" title="数据源失败: {{ s }}">{{ s }} ✗</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<details {% if loop.first %}open{% endif %}>
|
||||
<summary class="text-primary">查看报告内容</summary>
|
||||
<div class="watchdog-md mb-0 mt-2" data-md="{{ r.markdown | e }}"></div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.querySelectorAll('.watchdog-md[data-md]').forEach(function(el){
|
||||
var raw = el.getAttribute('data-md') || '';
|
||||
el.innerHTML = DOMPurify.sanitize(marked.parse(raw));
|
||||
el.querySelectorAll('a[href^="http"]').forEach(function(a){
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var KEY = 'wd_pending_since';
|
||||
var form = document.getElementById('run-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(){
|
||||
var b = document.getElementById('run-btn');
|
||||
b.disabled = true;
|
||||
b.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>已触发…';
|
||||
sessionStorage.setItem(KEY, String(Date.now()));
|
||||
});
|
||||
}
|
||||
var since = parseInt(sessionStorage.getItem(KEY) || '0', 10);
|
||||
if (!since) return;
|
||||
var ageMin = (Date.now() - since) / 60000;
|
||||
var pending = document.getElementById('wd-pending');
|
||||
if (ageMin >= 5) {
|
||||
sessionStorage.removeItem(KEY);
|
||||
if (pending) {
|
||||
pending.classList.remove('d-none', 'alert-warning');
|
||||
pending.classList.add('alert-danger');
|
||||
pending.textContent = '生成超时, 请检查 logs/daily-watchdog-run.log 后重试';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pending) pending.classList.remove('d-none');
|
||||
var knownTop = (document.querySelector('.card-header .text-muted.small') || {}).textContent || '';
|
||||
var timer = setInterval(function(){
|
||||
fetch(location.href, {credentials: 'same-origin'}).then(function(r){ return r.text(); }).then(function(html){
|
||||
var m = html.match(/生成于\s*([0-9:\-\s]+)/);
|
||||
var top = m ? m[1].trim() : '';
|
||||
if (top && top !== knownTop.trim()) {
|
||||
sessionStorage.removeItem(KEY);
|
||||
clearInterval(timer);
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
if ((Date.now() - since) / 60000 > 5) {
|
||||
clearInterval(timer);
|
||||
sessionStorage.removeItem(KEY);
|
||||
if (pending) {
|
||||
pending.classList.remove('alert-warning');
|
||||
pending.classList.add('alert-danger');
|
||||
pending.textContent = '生成超时, 请检查 logs/daily-watchdog-run.log 后重试';
|
||||
}
|
||||
}
|
||||
}).catch(function(){ /* 网络抖动, 下一轮再试 */ });
|
||||
}, 15000);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user