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();
})();