File size: 8,935 Bytes
e28c9e4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | {% 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>
</script>
{% endblock %}
|