SACC / templates /admin.html
cacode's picture
Deploy updated SCU course catcher
e28c9e4 verified
{% extends "base.html" %}
{% block title %}管理员后台{% endblock %}
{% block header_actions %}
<span class="status-pill status-live">{{ admin.role }} · {{ admin.username }}</span>
<form method="post" action="{{ url_for('admin_logout') }}">
<button type="submit" class="button button-ghost">退出后台</button>
</form>
{% endblock %}
{% block content %}
<section class="hero-card">
<div class="hero-copy-block">
<div class="eyebrow">管理员后台</div>
<h1>统一管理用户、并发调度与全局实时日志。</h1>
<p class="hero-copy">
用户提交的课程配置会集中展示在这里。管理员可以手动录入用户信息、代为启动任务,并根据 Hugging Face Space 资源调整并行数。
</p>
</div>
<div class="stat-grid admin-stat-grid">
<div class="stat-card">
<span>用户数</span>
<strong id="admin-users">{{ summary.users }}</strong>
</div>
<div class="stat-card">
<span>运行中任务</span>
<strong id="admin-running">{{ summary.running_tasks }}</strong>
</div>
<div class="stat-card">
<span>排队任务</span>
<strong id="admin-queued">{{ summary.queued_tasks }}</strong>
</div>
<div class="stat-card">
<span>并行数</span>
<strong id="admin-parallelism">{{ summary.parallelism }}</strong>
</div>
</div>
</section>
<section class="content-grid content-grid-admin">
<article class="panel">
<div class="panel-head">
<div>
<div class="section-kicker">系统设置</div>
<h2>并行调度</h2>
</div>
<span class="status-pill status-pending">资源敏感</span>
</div>
<form method="post" action="{{ url_for('update_parallelism') }}" class="form-grid form-grid-inline">
<label class="field">
<span>最大并行任务数</span>
<input type="number" name="parallelism" min="1" max="8" value="{{ summary.parallelism }}" required>
</label>
<button type="submit" class="button button-primary">保存并行数</button>
</form>
<p class="muted-note">建议根据 Space 资源从 1 开始逐步增加。每个任务都会独立启动一个无头浏览器实例。</p>
</article>
<article class="panel">
<div class="panel-head">
<div>
<div class="section-kicker">用户录入</div>
<h2>手动添加或更新用户</h2>
</div>
<span class="status-pill status-live">管理员可写</span>
</div>
<form method="post" action="{{ url_for('create_or_update_user_from_admin') }}" class="form-grid">
<label class="field">
<span>学号</span>
<input type="text" name="student_id" inputmode="numeric" placeholder="学生学号" required>
</label>
<label class="field">
<span>密码</span>
<input type="password" name="password" placeholder="学生密码" required>
</label>
<label class="field">
<span>初始课程号</span>
<input type="text" name="course_id" inputmode="numeric" placeholder="可选">
</label>
<label class="field">
<span>初始课序号</span>
<input type="text" name="course_index" inputmode="numeric" maxlength="2" placeholder="可选">
</label>
<button type="submit" class="button button-primary">保存用户</button>
</form>
</article>
{% if admin.role == "superadmin" %}
<article class="panel">
<div class="panel-head">
<div>
<div class="section-kicker">权限管理</div>
<h2>创建普通管理员</h2>
</div>
<span class="status-pill status-live">仅超级管理员</span>
</div>
<form method="post" action="{{ url_for('create_admin_account') }}" class="form-grid form-grid-inline">
<label class="field">
<span>管理员账号</span>
<input type="text" name="username" placeholder="admin-ops" required>
</label>
<label class="field">
<span>管理员密码</span>
<input type="password" name="password" placeholder="管理员密码" required>
</label>
<button type="submit" class="button button-ghost">创建管理员</button>
</form>
<div class="chip-wrap">
{% for item in admins %}
<span class="feature-pill">{{ item.username }} · {{ item.role }}</span>
{% endfor %}
</div>
</article>
{% endif %}
</section>
<section class="panel">
<div class="panel-head">
<div>
<div class="section-kicker">用户列表</div>
<h2>多用户管理</h2>
</div>
<span class="status-pill status-live">管理员全量可见</span>
</div>
<div class="user-grid">
{% if users %}
{% for item in users %}
<article class="user-card">
<div class="user-card-head">
<div>
<div class="user-code">{{ item.student_id }}</div>
<div class="user-meta-text">已选 {{ item.selected_count }}/{{ item.course_count }} · 最近登录 {{ item.last_login_at or "尚未登录" }}</div>
</div>
<span class="status-pill status-{{ item.active_task_status or 'idle' }}">{{ item.active_task_status or "idle" }}</span>
</div>
<div class="button-row compact">
<form method="post" action="{{ url_for('start_user_task_from_admin', user_id=item.id) }}">
<button type="submit" class="button button-primary button-small">启动</button>
</form>
<form method="post" action="{{ url_for('stop_user_task_from_admin', user_id=item.id) }}">
<button type="submit" class="button button-secondary button-small">停止</button>
</form>
</div>
<form method="post" action="{{ url_for('update_user_password_from_admin', user_id=item.id) }}" class="form-grid">
<label class="field">
<span>重置密码</span>
<input type="password" name="password" placeholder="输入新密码" required>
</label>
<button type="submit" class="button button-ghost button-small">更新</button>
</form>
<form method="post" action="{{ url_for('add_user_course_from_admin', user_id=item.id) }}" class="form-grid form-grid-inline">
<label class="field">
<span>课程号</span>
<input type="text" name="course_id" inputmode="numeric" placeholder="课程号" required>
</label>
<label class="field">
<span>课序号</span>
<input type="text" name="course_index" inputmode="numeric" maxlength="2" placeholder="01" required>
</label>
<button type="submit" class="button button-primary button-small">新增课程</button>
</form>
<div class="course-chip-grid">
{% if item.courses %}
{% for course in item.courses %}
<div class="course-chip course-chip-{{ course.status }}">
<div>
<strong>{{ course.course_id }}_{{ course.course_index }}</strong>
<p>{{ course.last_result or "等待执行" }}</p>
</div>
<form method="post" action="{{ url_for('delete_user_course_from_admin', user_id=item.id, course_record_id=course.id) }}">
<button type="submit" class="button button-danger button-small"></button>
</form>
</div>
{% endfor %}
{% else %}
<div class="empty-state small">这个用户还没有课程记录。</div>
{% endif %}
</div>
</article>
{% endfor %}
{% else %}
<div class="empty-state">当前还没有任何用户,先在上方录入用户信息。</div>
{% endif %}
</div>
</section>
<section class="panel console-panel">
<div class="panel-head">
<div>
<div class="section-kicker">全局日志</div>
<h2>管理员实时控制台</h2>
</div>
<span class="status-pill status-live">Live</span>
</div>
<div
class="log-console"
id="admin-log-stream"
data-last-id="{{ logs[-1].id if logs else 0 }}"
data-empty="当前还没有系统日志。"
>
{% if logs %}
{% for log in logs %}
<div class="log-line log-{{ log.level|lower }}">
<span class="log-time">{{ log.created_at }}</span>
<span class="log-actor">{{ log.student_id or log.actor }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
{% endfor %}
{% else %}
<div class="log-placeholder">当前还没有系统日志。</div>
{% endif %}
</div>
</section>
<script>
window.addEventListener("DOMContentLoaded", () => {
SCUApp.initLogStream("admin-log-stream", "{{ url_for('admin_log_stream') }}");
SCUApp.initAdminStatus("{{ url_for('admin_status') }}");
});
</script>
{% endblock %}