File size: 6,115 Bytes
774585c 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 | (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();
})();
|