File size: 4,452 Bytes
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
(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 status = (data.task && data.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);
        }
    }

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