SACC / static /app.js
cacodex's picture
Add proxy toggle and DDDDOCR captcha solver
abd380b verified
Raw
History Blame Contribute Delete
18.3 kB
(function () {
const statusLabels = {
pending: "排队中",
running: "执行中",
waiting_2fa: "等待短信验证码",
cancel_requested: "停止中",
completed: "已完成",
stopped: "已停止",
failed: "失败",
idle: "未启动",
};
const modeLabels = {
stable: "稳定版",
optimized: "优化版",
};
const loginModeLabels = {
unified: "统一认证",
zhjw_direct: "教务直连",
};
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 modeLabel(mode) {
return modeLabels[mode] || mode || "稳定版";
}
function loginModeLabel(mode) {
return loginModeLabels[mode] || mode || "统一认证";
}
function normalizeLogin2faCode(value) {
return String(value || "").replace(/\D/g, "").slice(0, 6);
}
function setFormMessage(node, message, level) {
if (!node) {
return;
}
node.className = `form-message ${level ? `level-${level}` : ""}`.trim();
node.textContent = message || "";
}
function secondsText(value) {
const seconds = Math.max(0, Number(value || 0));
return String(Math.ceil(seconds));
}
async function submitLogin2faCode(taskId, code, scope) {
const endpoint = scope === "admin"
? `/api/admin/tasks/${taskId}/login-2fa`
: `/api/user/tasks/${taskId}/login-2fa`;
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "fetch",
},
body: JSON.stringify({ code }),
});
let payload = {};
try {
payload = await response.json();
} catch (_error) {
payload = { ok: false, message: "提交失败,请稍后重试。" };
}
if (!response.ok || !payload.ok) {
throw new Error(payload.message || "提交失败,请稍后重试。");
}
return payload.message || "验证码已提交。";
}
function updateUserLogin2fa(challenge) {
const panel = document.getElementById("user-login-2fa-panel");
if (!panel) {
return;
}
const phone = document.getElementById("user-login-2fa-phone");
const remaining = document.getElementById("user-login-2fa-remaining");
const form = document.getElementById("user-login-2fa-form");
const message = document.getElementById("user-login-2fa-message");
const input = form ? form.querySelector("input[name='code']") : null;
const button = form ? form.querySelector("button[type='submit']") : null;
const nextTaskId = challenge ? String(challenge.task_id) : "";
if (!challenge) {
panel.hidden = true;
panel.dataset.taskId = "";
if (input) {
input.value = "";
input.disabled = false;
}
if (button) {
button.disabled = false;
}
setFormMessage(message, "", "");
return;
}
if (panel.dataset.taskId !== nextTaskId) {
if (input) {
input.value = "";
input.disabled = false;
}
if (button) {
button.disabled = false;
}
setFormMessage(message, "", "");
}
panel.dataset.taskId = nextTaskId;
panel.hidden = false;
if (phone) {
phone.textContent = challenge.phone_mask || "绑定手机";
}
if (remaining) {
remaining.textContent = secondsText(challenge.remaining_seconds);
}
}
function createAdminLogin2faItem(taskId) {
const item = document.createElement("section");
item.className = "login-2fa-item";
item.dataset.taskId = String(taskId);
const info = document.createElement("div");
const title = document.createElement("h3");
const desc = document.createElement("p");
const meta = document.createElement("div");
meta.className = "login-2fa-meta";
info.append(title, desc, meta);
const form = document.createElement("form");
form.className = "login-2fa-form";
form.dataset.login2faTaskId = String(taskId);
const input = document.createElement("input");
input.type = "text";
input.name = "code";
input.inputMode = "numeric";
input.autocomplete = "one-time-code";
input.maxLength = 6;
input.pattern = "[0-9]{6}";
input.placeholder = "6 位验证码";
input.required = true;
const button = document.createElement("button");
button.type = "submit";
button.className = "btn btn-secondary";
button.textContent = "提交验证码";
const message = document.createElement("p");
message.className = "form-message";
form.append(input, button, message);
item.append(info, form);
return item;
}
function updateAdminLogin2fa(challenges) {
const panel = document.getElementById("admin-login-2fa-panel");
const list = document.getElementById("admin-login-2fa-list");
if (!panel || !list) {
return;
}
const activeChallenges = Array.isArray(challenges) ? challenges : [];
panel.hidden = activeChallenges.length === 0;
const activeTaskIds = new Set(activeChallenges.map((challenge) => String(challenge.task_id)));
for (const item of Array.from(list.querySelectorAll(".login-2fa-item"))) {
if (!activeTaskIds.has(item.dataset.taskId || "")) {
item.remove();
}
}
for (const challenge of activeChallenges) {
const taskId = String(challenge.task_id);
let item = Array.from(list.querySelectorAll(".login-2fa-item"))
.find((candidate) => candidate.dataset.taskId === taskId);
if (!item) {
item = createAdminLogin2faItem(taskId);
list.appendChild(item);
}
const title = item.querySelector("h3");
const desc = item.querySelector("p");
const meta = item.querySelector(".login-2fa-meta");
if (title) {
title.textContent = `${challenge.display_name || challenge.student_id || "用户"} 的登录认证`;
}
if (desc) {
desc.textContent = `验证码已发送至 ${challenge.phone_mask || "绑定手机"},剩余 ${secondsText(challenge.remaining_seconds)} 秒。`;
}
if (meta) {
meta.innerHTML = "";
for (const text of [`任务 #${challenge.task_id}`, `学号 ${challenge.student_id || "--"}`]) {
const chip = document.createElement("span");
chip.className = "chip";
chip.textContent = text;
meta.appendChild(chip);
}
}
}
}
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 requestedMode = document.getElementById("task-requested-mode");
const requestedModeInline = document.getElementById("task-requested-mode-inline");
const effectiveMode = document.getElementById("task-effective-mode");
const effectiveModeInline = document.getElementById("task-effective-mode-inline");
const loginModeInline = document.getElementById("task-login-mode-inline");
const proxyInline = document.getElementById("task-proxy-inline");
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 (requestedMode) {
const proxyText = task && Number(task.use_proxy || 0) ? "启用" : "关闭";
requestedMode.textContent = `请求模式:${modeLabel(task && task.requested_mode)};登录方式:${loginModeLabel(task && task.login_mode)};代理:${proxyText}`;
}
if (requestedModeInline) {
requestedModeInline.textContent = modeLabel(task && task.requested_mode);
}
if (effectiveMode) {
effectiveMode.textContent = modeLabel(task && task.effective_mode);
}
if (effectiveModeInline) {
effectiveModeInline.textContent = modeLabel(task && task.effective_mode);
}
if (loginModeInline) {
loginModeInline.textContent = loginModeLabel(task && task.login_mode);
}
if (proxyInline) {
proxyInline.textContent = task && Number(task.use_proxy || 0) ? "启用" : "关闭";
}
if (refreshInterval && data.user && data.user.refresh_interval_seconds) {
refreshInterval.textContent = `${data.user.refresh_interval_seconds} 秒`;
}
updateUserLogin2fa(data.login_2fa_challenge || null);
}
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);
}
updateAdminLogin2fa(data.login_2fa_challenges || []);
}
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);
}
function bindLogin2faInteractions() {
document.addEventListener("input", function (event) {
const target = event.target;
if (!(target instanceof HTMLInputElement) || target.name !== "code") {
return;
}
const normalized = normalizeLogin2faCode(target.value);
if (target.value !== normalized) {
target.value = normalized;
}
});
const userForm = document.getElementById("user-login-2fa-form");
if (userForm) {
userForm.addEventListener("submit", async function (event) {
event.preventDefault();
const panel = document.getElementById("user-login-2fa-panel");
const message = document.getElementById("user-login-2fa-message");
const input = userForm.querySelector("input[name='code']");
const button = userForm.querySelector("button[type='submit']");
const code = normalizeLogin2faCode(input && input.value);
const taskId = panel && panel.dataset.taskId;
if (!taskId || code.length !== 6) {
setFormMessage(message, "请输入 6 位短信验证码。", "warning");
return;
}
if (button) {
button.disabled = true;
}
let submitted = false;
try {
await submitLogin2faCode(taskId, code, "user");
submitted = true;
setFormMessage(message, "已提交,等待后台继续登录。", "success");
if (input) {
input.value = "";
input.disabled = true;
}
} catch (error) {
setFormMessage(message, error.message || "提交失败,请稍后重试。", "error");
} finally {
if (button && !submitted) {
button.disabled = false;
}
}
});
}
const adminList = document.getElementById("admin-login-2fa-list");
if (adminList) {
adminList.addEventListener("submit", async function (event) {
const form = event.target.closest("form[data-login2fa-task-id]");
if (!form) {
return;
}
event.preventDefault();
const input = form.querySelector("input[name='code']");
const button = form.querySelector("button[type='submit']");
const message = form.querySelector(".form-message");
const code = normalizeLogin2faCode(input && input.value);
const taskId = form.dataset.login2faTaskId;
if (!taskId || code.length !== 6) {
setFormMessage(message, "请输入 6 位短信验证码。", "warning");
return;
}
if (button) {
button.disabled = true;
}
let submitted = false;
try {
await submitLogin2faCode(taskId, code, "admin");
submitted = true;
setFormMessage(message, "已提交,等待后台继续登录。", "success");
if (input) {
input.value = "";
input.disabled = true;
}
} catch (error) {
setFormMessage(message, error.message || "提交失败,请稍后重试。", "error");
} finally {
if (button && !submitted) {
button.disabled = false;
}
}
});
}
}
bindLogStream();
bindStatusPolling();
bindLogin2faInteractions();
})();