SACC-release / templates /admin_dashboard.html
cacode's picture
Deploy updated SCU course catcher
e28c9e4 verified
{% extends "base.html" %}
{% block title %}管理后台 | SCU 选课控制台{% endblock %}
{% block body_class %}admin-theme{% endblock %}
{% block content %}
<section class="dashboard-shell admin-dashboard" data-log-stream-url="{{ url_for('stream_admin_logs', last_id=recent_logs[-1].id if recent_logs else 0) }}" data-status-url="{{ url_for('admin_status') }}">
<header class="topbar reveal-up">
<div>
<span class="eyebrow">Admin Console</span>
<h1>管理员后台</h1>
<p>当前管理员:{{ admin_identity.username }}{% if is_super_admin %} · 超级管理员{% endif %}</p>
</div>
<form method="post" action="{{ url_for('admin_logout') }}">
<button type="submit" class="btn btn-ghost">退出后台</button>
</form>
</header>
<section class="metric-grid reveal-up delay-1">
<article class="metric-card">
<span>用户数</span>
<strong id="stat-users">{{ stats.users_count }}</strong>
<small>已录入的学生账号</small>
</article>
<article class="metric-card">
<span>运行中任务</span>
<strong id="stat-running">{{ stats.running_count }}</strong>
<small>排队中:<span id="stat-pending">{{ stats.pending_count }}</span></small>
</article>
<article class="metric-card">
<span>总课程目标</span>
<strong>{{ stats.courses_count }}</strong>
<small>管理员可见全部课程号与课序号</small>
</article>
<article class="metric-card">
<span>管理员总数</span>
<strong>{{ stats.admins_count }}</strong>
<small>包含 1 位超级管理员</small>
</article>
</section>
<section class="content-grid admin-grid">
<article class="card reveal-up delay-2">
<div class="card-head">
<span class="kicker">调度设置</span>
<h2>并行数</h2>
<p>建议根据 Hugging Face Space 的 CPU 与内存情况控制在较低范围。</p>
</div>
<form method="post" action="{{ url_for('update_parallel_limit') }}" class="form-grid form-grid-compact">
<label class="field">
<span>当前并行数</span>
<input type="number" id="parallel-limit-input" name="parallel_limit" min="1" max="8" value="{{ parallel_limit }}" required>
</label>
<button type="submit" class="btn btn-primary">更新并行数</button>
</form>
</article>
<article class="card reveal-up delay-2">
<div class="card-head">
<span class="kicker">新增用户</span>
<h2>手动录入用户信息</h2>
<p>管理员可以直接创建学生账号,普通用户随后即可用学号和密码登录。</p>
</div>
<form method="post" action="{{ url_for('create_user') }}" class="form-grid form-grid-compact">
<label class="field">
<span>学号</span>
<input type="text" name="student_id" inputmode="numeric" placeholder="13 位学号" required>
</label>
<label class="field">
<span>显示名称</span>
<input type="text" name="display_name" placeholder="可选备注">
</label>
<label class="field span-2">
<span>密码</span>
<input type="password" name="password" placeholder="教务系统密码" required>
</label>
<button type="submit" class="btn btn-secondary">创建用户</button>
</form>
</article>
{% if is_super_admin %}
<article class="card reveal-up delay-2">
<div class="card-head">
<span class="kicker">管理员管理</span>
<h2>新增管理员</h2>
<p>只有超级管理员可以继续创建普通管理员。</p>
</div>
<form method="post" action="{{ url_for('create_admin') }}" class="form-grid form-grid-compact">
<label class="field">
<span>管理员账号</span>
<input type="text" name="username" placeholder="输入管理员账号" required>
</label>
<label class="field">
<span>管理员密码</span>
<input type="password" name="password" placeholder="输入管理员密码" required>
</label>
<button type="submit" class="btn btn-ghost">创建管理员</button>
</form>
<div class="chip-row">
<span class="chip highlight">超级管理员:{{ admin_identity.username }}</span>
{% for admin in admins %}
<span class="chip">{{ admin.username }}</span>
{% endfor %}
</div>
</article>
{% endif %}
<article class="card reveal-up delay-3 span-2">
<div class="card-head split">
<div>
<span class="kicker">任务总览</span>
<h2>最近任务</h2>
<p>用于快速确认任务是否正在排队、执行、停止或失败。</p>
</div>
<span class="status-pill status-running">实时刷新</span>
</div>
<div class="course-table-wrap">
<table class="data-table">
<thead>
<tr>
<th>任务</th>
<th>学号</th>
<th>状态</th>
<th>触发者</th>
<th>更新时间</th>
</tr>
</thead>
<tbody>
{% if recent_tasks %}
{% for task in recent_tasks %}
<tr>
<td>#{{ task.id }}</td>
<td>{{ task.student_id }}</td>
<td><span class="status-pill status-{{ task.status }}">{{ task_labels.get(task.status, task.status) }}</span></td>
<td>{{ task.requested_by_role }}:{{ task.requested_by }}</td>
<td>{{ task.updated_at }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="5" class="empty-cell">还没有任务记录。</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</article>
<article class="card reveal-up delay-3 span-2">
<div class="card-head split">
<div>
<span class="kicker">全局日志</span>
<h2>所有用户的运行日志</h2>
<p>日志会持续流入,便于管理员确认浏览器登录、查课、提交结果与错误信息。</p>
</div>
<span class="live-dot">LIVE</span>
</div>
<div class="log-console" id="log-console">
{% if recent_logs %}
{% for log in recent_logs %}
<div class="log-line level-{{ log.level|lower }}">
<span class="log-meta">{{ log.created_at }} · {{ log.student_id or 'system' }} · {{ log.scope }} · {{ log.level }}</span>
<span>{{ log.message }}</span>
</div>
{% endfor %}
{% else %}
<div class="log-line level-info muted">暂无日志,用户启动任务后这里会自动刷新。</div>
{% endif %}
</div>
</article>
<article class="card reveal-up delay-4 span-2">
<div class="card-head">
<span class="kicker">用户清单</span>
<h2>所有用户与课程详情</h2>
<p>可以直接修改用户信息、增减课程,或代替用户启动和停止任务。</p>
</div>
<div class="user-card-grid">
{% for user in users %}
<section class="user-card">
<div class="user-card-head">
<div>
<h3>{{ user.display_name or user.student_id }}</h3>
<p>{{ user.student_id }}</p>
</div>
<span class="status-pill status-{{ user.latest_task.status if user.latest_task else 'idle' }}">
{{ task_labels.get(user.latest_task.status, '未启动') if user.latest_task else '未启动' }}
</span>
</div>
<div class="chip-row tight">
<span class="chip {% if user.is_active %}highlight{% endif %}">{{ '启用中' if user.is_active else '已禁用' }}</span>
<span class="chip">课程 {{ user.course_count }}</span>
<span class="chip">最近任务 {{ user.latest_task.id if user.latest_task else '--' }}</span>
</div>
<form method="post" action="{{ url_for('update_user', user_id=user.id) }}" class="form-grid form-grid-compact slim-form">
<label class="field span-2">
<span>显示名称</span>
<input type="text" name="display_name" value="{{ user.display_name }}" placeholder="备注名称">
</label>
<label class="field span-2">
<span>重置密码</span>
<input type="password" name="password" placeholder="留空表示不修改">
</label>
<button type="submit" class="btn btn-ghost">保存用户</button>
</form>
<div class="button-row wrap-row">
<form method="post" action="{{ url_for('toggle_user', user_id=user.id) }}">
<button type="submit" class="btn btn-ghost {% if not user.is_active %}danger{% endif %}">{{ '禁用' if user.is_active else '启用' }}</button>
</form>
<form method="post" action="{{ url_for('admin_start_user_task', user_id=user.id) }}">
<button type="submit" class="btn btn-primary">代启动任务</button>
</form>
<form method="post" action="{{ url_for('admin_stop_user_task', user_id=user.id) }}">
<button type="submit" class="btn btn-ghost danger">代停止任务</button>
</form>
</div>
<form method="post" action="{{ url_for('admin_add_course', user_id=user.id) }}" class="form-grid form-grid-compact slim-form">
<label class="field">
<span>类型</span>
<select name="category">
<option value="free">自由选课</option>
<option value="plan">方案选课</option>
</select>
</label>
<label class="field">
<span>课程号</span>
<input type="text" name="course_id" placeholder="课程号" required>
</label>
<label class="field">
<span>课序号</span>
<input type="text" name="course_index" placeholder="01" maxlength="2" required>
</label>
<button type="submit" class="btn btn-secondary">为该用户加课</button>
</form>
<div class="course-list">
{% if user.courses %}
{% for course in user.courses %}
<div class="course-chip-row">
<span>{{ category_labels.get(course.category, course.category) }} · {{ course.course_id }}_{{ course.course_index }}</span>
<form method="post" action="{{ url_for('admin_delete_course', course_target_id=course.id) }}">
<button type="submit" class="inline-action">删除</button>
</form>
</div>
{% endfor %}
{% else %}
<div class="empty-mini">当前没有课程目标。</div>
{% endif %}
</div>
</section>
{% else %}
<div class="empty-state-card">
还没有录入任何用户,请先通过上方表单创建。
</div>
{% endfor %}
</div>
</article>
</section>
</section>
{% endblock %}