File size: 13,905 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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
{% 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 %}