SACC / static /app.js
cacode's picture
Deploy runtime refresh interval, counters, and curriculum callback check
774585c verified
(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();
})();