106 lines
6.1 KiB
HTML
106 lines
6.1 KiB
HTML
{% extends "admin/base.html" %}
|
|
|
|
{% block admin_title %}数据质量{% endblock %}
|
|
{% block page_title %}数据质量中心{% endblock %}
|
|
{% block page_subtitle %}数据库健康、引用完整性、Mart 覆盖与运行治理{% endblock %}
|
|
|
|
{% block admin_content %}
|
|
{% set status_styles = {
|
|
'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">
|
|
<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>
|
|
</div>
|
|
<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>
|
|
</section>
|
|
|
|
<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 %}
|
|
</section>
|
|
|
|
<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>
|
|
<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>
|
|
</section>
|
|
|
|
<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 %}
|