2.0.0-rc2: Achievements Refactored & Admin improved.

This commit is contained in:
2026-08-09 01:34:20 +08:00
parent 3874bf57a9
commit 63a0751aba
29 changed files with 2143 additions and 355 deletions
+88 -67
View File
@@ -1,84 +1,105 @@
{% extends "base.html" %}
{% extends "admin/base.html" %}
{% block title %}数据完整性 - YRTV{% endblock %}
{% block admin_title %}数据质量{% endblock %}
{% block page_title %}数据质量中心{% endblock %}
{% block page_subtitle %}数据库健康、引用完整性、Mart 覆盖与运行治理{% endblock %}
{% block content %}
{% block admin_content %}
{% set status_styles = {
'pass': 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300',
'warn': 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300',
'fail': 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300'
'pass': 'bg-emerald-100 text-emerald-700 border-emerald-200',
'warn': 'bg-amber-100 text-amber-700 border-amber-200',
'fail': 'bg-red-100 text-red-700 border-red-200'
} %}
<div class="space-y-6 px-4 sm:px-0">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<div class="flex items-center gap-3">
<h1 class="text-2xl font-bold text-slate-900 dark:text-white">数据完整性中心</h1>
<span class="rounded-full px-3 py-1 text-xs font-semibold uppercase {{ status_styles[report.overall_status] }}">
{{ report.overall_status }}
</span>
<div class="space-y-6">
<section class="overflow-hidden rounded-2xl bg-gradient-to-r {% if report.overall_status == 'pass' %}from-emerald-600 to-teal-700{% elif report.overall_status == 'warn' %}from-amber-500 to-orange-600{% else %}from-red-600 to-rose-700{% endif %} p-6 text-white shadow-xl">
<div class="flex flex-col gap-5 sm:flex-row sm:items-center sm:justify-between">
<div>
<div class="text-xs font-black uppercase tracking-[0.2em] text-white/70">Integrity Report</div>
<div class="mt-2 flex items-baseline gap-3">
<div class="text-5xl font-black">{{ report.totals.pass }}</div>
<div class="text-lg font-bold">项检查通过</div>
</div>
<div class="mt-2 text-sm text-white/70">生成于 {{ report.generated_at }}</div>
</div>
<div class="flex gap-3">
<div class="rounded-xl bg-white/10 px-5 py-3 text-center"><div class="text-2xl font-black">{{ report.totals.warn }}</div><div class="text-[10px] uppercase text-white/70">Warnings</div></div>
<div class="rounded-xl bg-white/10 px-5 py-3 text-center"><div class="text-2xl font-black">{{ report.totals.fail }}</div><div class="text-[10px] uppercase text-white/70">Failures</div></div>
</div>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
校验时间:{{ report.generated_at }}
</p>
</div>
<div class="flex gap-2">
<a href="{{ url_for('admin.data_integrity', format='json') }}"
class="rounded-lg border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50 dark:border-slate-600 dark:text-slate-200 dark:hover:bg-slate-800">
JSON
</a>
<a href="{{ url_for('admin.data_integrity') }}"
class="rounded-lg bg-yrtv-600 px-4 py-2 text-sm font-medium text-white hover:bg-yrtv-500">
重新校验
</a>
<div class="mt-6 flex gap-3">
<a href="{{ url_for('admin.data_integrity') }}" class="rounded-xl bg-white px-4 py-2 text-sm font-black text-slate-900">重新检查</a>
<a href="{{ url_for('admin.data_integrity', format='json') }}" target="_blank" class="rounded-xl border border-white/30 px-4 py-2 text-sm font-bold text-white">JSON 报告</a>
</div>
</div>
</section>
<div class="grid grid-cols-3 gap-3">
{% for status, label in [('pass', '通过'), ('warn', '警告'), ('fail', '失败')] %}
<div class="rounded-xl bg-white p-4 shadow dark:bg-slate-800">
<div class="text-sm text-slate-500 dark:text-slate-400">{{ label }}</div>
<div class="mt-1 text-3xl font-bold {% if status == 'pass' %}text-emerald-600{% elif status == 'warn' %}text-amber-600{% else %}text-red-600{% endif %}">
{{ report.totals[status] }}
</div>
<section class="grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-6">
{% for label, value in [
('Matches', report.counts.matches),
('Players', report.counts.players),
('Rounds', report.counts.rounds),
('Events', report.counts.events),
('Reports', report.counts.l3_match_reports),
('Awards', report.counts.l3_awards)
] %}
<div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div class="text-[10px] font-black uppercase tracking-wider text-slate-400">{{ label }}</div>
<div class="mt-1 text-2xl font-black text-slate-900 dark:text-white">{{ '{:,}'.format(value or 0) }}</div>
</div>
{% endfor %}
</div>
</section>
<div class="rounded-xl bg-white p-5 shadow dark:bg-slate-800">
<h2 class="mb-4 text-lg font-semibold text-slate-900 dark:text-white">数据规模</h2>
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
{% for key, value in report.counts.items() %}
<div class="rounded-lg bg-slate-50 p-3 dark:bg-slate-900/60">
<div class="truncate text-xs uppercase tracking-wide text-slate-500">{{ key|replace('_', ' ') }}</div>
<div class="mt-1 text-xl font-semibold text-slate-900 dark:text-white">{{ "{:,}".format(value) }}</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_300px]">
<section class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div class="border-b border-slate-100 px-5 py-4 dark:border-slate-800">
<h2 class="font-black text-slate-900 dark:text-white">检查明细</h2>
<p class="text-xs text-slate-400">失败优先,其次警告,最后通过项</p>
</div>
{% endfor %}
</div>
</div>
<div class="overflow-hidden rounded-xl bg-white shadow dark:bg-slate-800">
<div class="border-b border-slate-200 px-5 py-4 dark:border-slate-700">
<h2 class="text-lg font-semibold text-slate-900 dark:text-white">校验项目</h2>
</div>
<div class="divide-y divide-slate-100 dark:divide-slate-700">
{% for check in report.checks %}
<div class="flex flex-col gap-2 px-5 py-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<div class="font-medium text-slate-900 dark:text-white">{{ check.name }}</div>
<div class="mt-1 text-sm text-slate-500 dark:text-slate-400">{{ check.detail }}</div>
</div>
<span class="self-start rounded-full px-3 py-1 text-xs font-semibold uppercase sm:self-center {{ status_styles[check.status] }}">
{{ check.status }}
</span>
<div class="divide-y divide-slate-100 dark:divide-slate-800">
{% for target_status in ['fail', 'warn', 'pass'] %}
{% for check in report.checks if check.status == target_status %}
<div class="flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center sm:justify-between">
<div class="min-w-0">
<div class="font-bold text-slate-800 dark:text-slate-200">{{ check.name }}</div>
<div class="mt-1 text-xs leading-5 text-slate-400">{{ check.detail }}</div>
</div>
<span class="self-start rounded-full border px-2.5 py-1 text-[10px] font-black uppercase sm:self-center {{ status_styles[check.status] }}">{{ check.status }}</span>
</div>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
</div>
</div>
</section>
<div>
<a href="{{ url_for('admin.dashboard') }}" class="text-sm font-medium text-yrtv-600 hover:text-yrtv-500">
返回管理后台
</a>
<aside class="space-y-4">
{% for title, entries in [
('Roster & Team', [
('Active roster', report.counts.active_roster),
('Roster versions', report.counts.roster_versions),
('Duo marts', report.counts.l3_duos),
('Lineup marts', report.counts.l3_lineups)
]),
('Profile Marts', [
('History', report.counts.l3_history),
('Periods', report.counts.l3_periods),
('Records', report.counts.l3_records),
('Record events', report.counts.l3_record_events)
]),
('Operations', [
('ETL jobs', report.counts.etl_jobs),
('Imports', report.counts.match_imports),
('Backups', report.counts.backup_sets),
('Web schema', report.counts.web_schema_version)
])
] %}
<section class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<h3 class="text-xs font-black uppercase tracking-wider text-slate-400">{{ title }}</h3>
<dl class="mt-3 space-y-2">
{% for label, value in entries %}
<div class="flex items-center justify-between"><dt class="text-sm text-slate-500">{{ label }}</dt><dd class="font-mono text-sm font-black text-slate-800 dark:text-slate-200">{{ '{:,}'.format(value or 0) }}</dd></div>
{% endfor %}
</dl>
</section>
{% endfor %}
</aside>
</div>
</div>
{% endblock %}