(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) { // Ignore malformed frames and keep the stream alive. } }; } 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) { // Leave the current UI state as-is if polling fails. } } refresh(); window.setInterval(refresh, 7000); } bindLogStream(); bindStatusPolling(); })();