Files
JKTV-online/web/templates/admin/import_match.html
T

97 lines
6.4 KiB
HTML

{% extends "admin/base.html" %}
{% block admin_title %}比赛导入{% endblock %}
{% block page_title %}比赛导入{% endblock %}
{% block page_subtitle %}校验网络抓包并启动可回滚的 L1 → L2 → L3 流水线{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
<section class="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div class="flex items-start justify-between gap-4">
<div>
<h2 class="text-xl font-black text-slate-900 dark:text-white">上传 iframe_network.json</h2>
<p class="mt-1 text-sm text-slate-500">系统自动提取比赛 ID、计算 SHA256、检查必要接口并拒绝重复数据。</p>
</div>
<span class="rounded-full bg-yrtv-50 px-3 py-1 text-xs font-black text-yrtv-700">Max {{ '%.0f MB'|format(config.MAX_CONTENT_LENGTH / 1024 / 1024) }}</span>
</div>
<form method="POST" enctype="multipart/form-data" class="mt-6 space-y-5">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<label id="drop-zone" class="group flex min-h-[260px] cursor-pointer flex-col items-center justify-center rounded-2xl border-2 border-dashed border-slate-300 bg-slate-50 px-6 text-center transition hover:border-yrtv-400 hover:bg-yrtv-50/40 dark:border-slate-700 dark:bg-slate-950/40">
<input id="capture-input" type="file" name="capture" accept=".json,application/json" required class="sr-only">
<div class="flex h-16 w-16 items-center justify-center rounded-2xl bg-white text-3xl shadow-sm transition group-hover:scale-105 dark:bg-slate-800"></div>
<div id="file-name" class="mt-4 font-black text-slate-800 dark:text-white">点击选择 JSON 文件</div>
<div class="mt-2 text-sm text-slate-400">或将 iframe_network.json 拖到这里</div>
<div class="mt-4 rounded-full bg-white px-3 py-1 text-[10px] font-bold uppercase tracking-wider text-slate-400 shadow-sm dark:bg-slate-800">UTF-8 · One Match · Network Capture</div>
</label>
<div class="rounded-xl border border-amber-200 bg-amber-50 p-4 dark:border-amber-900/50 dark:bg-amber-950/20">
<label class="flex items-start gap-3">
<input type="checkbox" name="replace" value="1" class="mt-0.5 rounded border-amber-300 text-amber-600">
<span>
<span class="block text-sm font-black text-amber-800 dark:text-amber-300">允许替换内容不同的已有比赛</span>
<span class="mt-1 block text-xs leading-5 text-amber-700/80 dark:text-amber-400">仅在确认原始抓包需要修复时使用。相同内容永远拒绝重复导入。</span>
</span>
</label>
</div>
<div class="flex flex-col gap-3 sm:flex-row">
<button type="submit" class="flex-1 rounded-xl bg-yrtv-600 px-5 py-3 text-sm font-black text-white shadow-lg shadow-yrtv-200 hover:bg-yrtv-500">校验并开始导入</button>
<a href="{{ url_for('admin.jobs') }}" class="rounded-xl border border-slate-200 px-5 py-3 text-center text-sm font-bold text-slate-600 hover:bg-slate-50 dark:border-slate-700 dark:text-slate-300">查看作业中心</a>
</div>
</form>
</section>
<aside class="space-y-6">
<section class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<h2 class="font-black text-slate-900 dark:text-white">导入流程</h2>
<ol class="mt-4 space-y-3">
{% for title, detail in [
('结构校验', 'JSON、比赛 ID、match/round 接口'),
('内容去重', 'SHA256 与 L1、待处理文件比对'),
('安全备份', '保存 L1/L2/L3 回滚快照'),
('分层构建', '串行执行 L1 → L2 → L3'),
('质量验收', '玩家数、回合与自动完整性规则')
] %}
<li class="flex gap-3">
<span class="flex h-6 w-6 flex-none items-center justify-center rounded-full bg-yrtv-100 text-xs font-black text-yrtv-700">{{ loop.index }}</span>
<span><b class="block text-sm text-slate-700 dark:text-slate-200">{{ title }}</b><span class="text-xs text-slate-400">{{ detail }}</span></span>
</li>
{% endfor %}
</ol>
</section>
<section class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div class="flex items-center justify-between border-b border-slate-100 px-5 py-4 dark:border-slate-800">
<h2 class="font-black text-slate-900 dark:text-white">最近导入作业</h2>
<a href="{{ url_for('admin.jobs') }}" class="text-xs font-bold text-yrtv-600">全部</a>
</div>
<div class="divide-y divide-slate-100 dark:divide-slate-800">
{% for job in jobs %}
<a href="{{ url_for('admin.jobs', job_id=job.id) }}" class="flex items-center justify-between px-5 py-3 hover:bg-slate-50 dark:hover:bg-slate-800">
<div class="min-w-0">
<div class="truncate text-sm font-bold text-slate-700 dark:text-slate-200">#{{ job.id }} {{ job.match_id or job.job_type }}</div>
<div class="text-xs text-slate-400">{{ job.current_stage or job.status }}</div>
</div>
<span class="ml-3 rounded-full px-2 py-1 text-[10px] font-black uppercase {% if job.status == 'succeeded' %}bg-emerald-100 text-emerald-700{% elif job.status == 'failed' %}bg-red-100 text-red-700{% else %}bg-amber-100 text-amber-700{% endif %}">{{ job.status }}</span>
</a>
{% else %}
<div class="px-5 py-8 text-center text-sm text-slate-400">暂无作业</div>
{% endfor %}
</div>
</section>
</aside>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
const input = document.getElementById('capture-input');
const fileName = document.getElementById('file-name');
input.addEventListener('change', () => {
fileName.textContent = input.files.length ? input.files[0].name : '点击选择 JSON 文件';
});
</script>
{% endblock %}