| (function () {
|
| const statusLabels = {
|
| pending: "排队中",
|
| running: "执行中",
|
| cancel_requested: "停止中",
|
| completed: "已完成",
|
| stopped: "已停止",
|
| failed: "失败",
|
| idle: "未启动"
|
| };
|
|
|
| function renderLogLine(log) {
|
| const line = document.createElement("div");
|
| const level = (log.level || "INFO").toLowerCase();
|
| line.className = `log-line level-${level}`;
|
|
|
| const meta = document.createElement("span");
|
| meta.className = "log-meta";
|
| const owner = log.student_id || "system";
|
| meta.textContent = `${log.created_at} | ${owner} | ${log.scope} | ${log.level}`;
|
|
|
| const message = document.createElement("span");
|
| message.textContent = log.message || "";
|
|
|
| line.append(meta, message);
|
| return line;
|
| }
|
|
|
| function bindLogStream() {
|
| const shell = document.querySelector("[data-log-stream-url]");
|
| const consoleNode = document.getElementById("log-console");
|
| if (!shell || !consoleNode || !window.EventSource) {
|
| return;
|
| }
|
|
|
| const streamUrl = shell.getAttribute("data-log-stream-url");
|
| const eventSource = new EventSource(streamUrl);
|
|
|
| eventSource.onmessage = function (event) {
|
| try {
|
| const payload = JSON.parse(event.data);
|
| if (consoleNode.querySelector(".muted")) {
|
| consoleNode.innerHTML = "";
|
| }
|
| consoleNode.appendChild(renderLogLine(payload));
|
| while (consoleNode.children.length > 360) {
|
| consoleNode.removeChild(consoleNode.firstChild);
|
| }
|
| consoleNode.scrollTop = consoleNode.scrollHeight;
|
| } catch (_error) {
|
|
|
| }
|
| };
|
| }
|
|
|
| function updateUserStatus(data) {
|
| const taskText = document.getElementById("task-status-text");
|
| const taskPill = document.getElementById("task-status-pill");
|
| const courseCount = document.getElementById("course-count");
|
| const taskAttempts = document.getElementById("task-attempts");
|
| const taskAttemptsInline = document.getElementById("task-attempts-inline");
|
| const taskErrors = document.getElementById("task-errors");
|
| const taskErrorsInline = document.getElementById("task-errors-inline");
|
| const taskLastError = document.getElementById("task-last-error");
|
| const taskUpdatedAt = document.getElementById("task-updated-at");
|
| const refreshInterval = document.getElementById("refresh-interval");
|
| const task = data.task || null;
|
| const status = (task && task.status) || "idle";
|
|
|
| if (taskText) {
|
| taskText.textContent = statusLabels[status] || status;
|
| }
|
| if (taskPill) {
|
| taskPill.className = `status-pill status-${status}`;
|
| taskPill.textContent = statusLabels[status] || status;
|
| }
|
| if (courseCount && Array.isArray(data.courses)) {
|
| courseCount.textContent = String(data.courses.length);
|
| }
|
| if (taskAttempts) {
|
| taskAttempts.textContent = String((task && task.total_attempts) || 0);
|
| }
|
| if (taskAttemptsInline) {
|
| taskAttemptsInline.textContent = String((task && task.total_attempts) || 0);
|
| }
|
| if (taskErrors) {
|
| taskErrors.textContent = String((task && task.total_errors) || 0);
|
| }
|
| if (taskErrorsInline) {
|
| taskErrorsInline.textContent = String((task && task.total_errors) || 0);
|
| }
|
| if (taskLastError) {
|
| taskLastError.textContent = (task && task.last_error) || "当前没有错误提示";
|
| }
|
| if (taskUpdatedAt) {
|
| taskUpdatedAt.textContent = `最近更新时间:${(task && task.updated_at) || "暂无"}`;
|
| }
|
| if (refreshInterval && data.user && data.user.refresh_interval_seconds) {
|
| refreshInterval.textContent = `${data.user.refresh_interval_seconds} 秒`;
|
| }
|
| }
|
|
|
| function updateAdminStatus(data) {
|
| const users = document.getElementById("stat-users");
|
| const running = document.getElementById("stat-running");
|
| const pending = document.getElementById("stat-pending");
|
| const parallel = document.getElementById("parallel-limit-input");
|
| if (users) {
|
| users.textContent = String(data.stats.users_count || 0);
|
| }
|
| if (running) {
|
| running.textContent = String(data.stats.running_count || 0);
|
| }
|
| if (pending) {
|
| pending.textContent = String(data.stats.pending_count || 0);
|
| }
|
| if (parallel) {
|
| parallel.value = String(data.parallel_limit || parallel.value);
|
| }
|
| }
|
|
|
| function bindStatusPolling() {
|
| const shell = document.querySelector("[data-status-url]");
|
| if (!shell) {
|
| return;
|
| }
|
|
|
| const statusUrl = shell.getAttribute("data-status-url");
|
| const isAdmin = shell.classList.contains("admin-dashboard");
|
|
|
| async function refresh() {
|
| try {
|
| const response = await fetch(statusUrl, { headers: { "X-Requested-With": "fetch" } });
|
| if (!response.ok) {
|
| return;
|
| }
|
| const payload = await response.json();
|
| if (!payload.ok) {
|
| return;
|
| }
|
| if (isAdmin) {
|
| updateAdminStatus(payload);
|
| } else {
|
| updateUserStatus(payload);
|
| }
|
| } catch (_error) {
|
|
|
| }
|
| }
|
|
|
| refresh();
|
| window.setInterval(refresh, 7000);
|
| }
|
|
|
| bindLogStream();
|
| bindStatusPolling();
|
| })();
|
|
|