Files
fnos-agent-checkin/AgentCheckin/app/www/assets/app.js
T

643 lines
28 KiB
JavaScript
Raw Normal View History

/* AgentCheckin frontend — vanilla JS, no dependencies */
(function () {
"use strict";
var state = null;
var pollTimer = null;
var oauthState = null;
var oauthTimer = null;
var logTimer = null;
var $ = function (id) { return document.getElementById(id); };
// ---------------------------------------------------------------- helpers
function toast(msg, kind) {
var t = $("toast");
t.textContent = msg;
t.className = "toast" + (kind ? " " + kind : "");
clearTimeout(t._timer);
t._timer = setTimeout(function () { t.classList.add("hidden"); }, 3200);
}
function api(method, path, body) {
var opts = { method: method, headers: {} };
if (body !== undefined && body !== null) {
opts.headers["Content-Type"] = "application/json";
opts.body = JSON.stringify(body);
}
return fetch("api" + path, opts).then(function (r) {
return r.json().catch(function () { return {}; });
}).then(function (d) {
if (d && d.error) throw new Error(d.error);
return d;
});
}
function esc(s) {
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
var STATUS = {
ok: { label: "签到成功", cls: "ok", icon: "✅" },
ok_unverified: { label: "已领取·待确认", cls: "ok_unverified", icon: "✅" },
already: { label: "今日已签", cls: "already", icon: "☑️" },
inactive: { label: "未开放签到", cls: "inactive", icon: "➖" },
fail: { label: "签到失败", cls: "fail", icon: "❌" },
no_session: { label: "需重新授权", cls: "no_session", icon: "🔒" },
no_auth: { label: "缺少凭据", cls: "no_auth", icon: "🔒" },
retry_later: { label: "活动未下发·待重试", cls: "retry_later", icon: "⏳" }
};
function platformName(p) { return p === "trae" ? "TRAE" : (p === "qoder" ? "Qoder" : "WorkBuddy"); }
// ---------------------------------------------------------------- tabs
document.querySelectorAll(".tab").forEach(function (btn) {
btn.addEventListener("click", function () {
document.querySelectorAll(".tab").forEach(function (b) { b.classList.remove("active"); });
document.querySelectorAll(".tabpage").forEach(function (p) { p.classList.remove("active"); });
btn.classList.add("active");
$("tab-" + btn.dataset.tab).classList.add("active");
if (btn.dataset.tab === "logs") loadLogs();
if (btn.dataset.tab === "accounts") renderAccounts();
if (btn.dataset.tab === "settings") openSettingsTab();
});
});
// ---------------------------------------------------------------- dashboard
function renderTop() {
var alive = state && state.daemon_alive;
var chip = $("daemon-chip");
chip.innerHTML = '<span class="dot"></span>守护进程 ' + (alive ? "运行中" : "已停止");
chip.className = "chip " + (alive ? "chip-green" : "chip-red");
var nr = $("nextrun-chip");
nr.textContent = "下次签到 " + ((state && state.next_run_at) || "—");
nr.className = "chip " + (state && state.next_run_at ? "chip-blue" : "chip-gray");
}
function renderCards() {
var wrap = $("dash-cards");
var accounts = (state && state.accounts) || [];
if (!accounts.length) {
wrap.innerHTML = '<div class="panel" style="grid-column:1/-1;text-align:center;color:var(--muted)">' +
"暂无账号,请先到「账号管理」添加 TRAE / WorkBuddy 账号</div>";
return;
}
wrap.innerHTML = accounts.map(function (a) {
var st = a.today && a.today.status ? STATUS[a.today.status] : null;
var statusHtml = st
? '<span class="status-chip ' + st.cls + '">' + st.icon + " " + st.label + "</span>"
: '<span class="status-chip pending">⏳ 今日未签</span>';
var credits = "";
if (a.last_result && typeof a.last_result.credits === "number") {
credits = "剩余积分 <b>" + a.last_result.credits + "</b>";
} else if (a.last_result && a.last_result.points) {
credits = "本次 +<b>" + a.last_result.points + "</b>";
}
var msg = a.today && a.today.message ? esc(a.today.message) : "";
return (
'<div class="acct-card' + (a.enabled ? "" : " disabled") + '">' +
'<div class="acct-head"><span class="platform-badge ' + esc(a.platform) + '">' + platformName(a.platform) + "</span>" +
'<span class="acct-name">' + esc(a.name) + "</span></div>" +
'<div class="acct-status">' + statusHtml + (msg ? '<span class="muted">' + msg + "</span>" : "") + "</div>" +
'<div class="acct-meta">' + (credits ? "<span>" + credits + "</span>" : "") +
(a.last_result ? "<span>最近运行 " + esc(a.last_result.time || "") + "</span>" : "") +
(!a.enabled ? "<span>已禁用</span>" : "") + "</div>" +
'<div class="acct-actions">' +
'<button class="btn btn-sm" data-act="run" data-id="' + esc(a.id) + '">立即签到</button>' +
'<button class="btn btn-sm" data-act="test" data-id="' + esc(a.id) + '">测连接</button>' +
"</div></div>"
);
}).join("");
wrap.querySelectorAll("button[data-act]").forEach(function (btn) {
btn.addEventListener("click", function () {
if (btn.dataset.act === "run") runCheckin(btn.dataset.id, btn);
else testAccount(btn.dataset.id, btn);
});
});
}
var lastStateJson = "";
function refreshState(force) {
return api("GET", "/state").then(function (d) {
var json = JSON.stringify(d);
if (!force && json === lastStateJson) return; // 数据没变,跳过重渲染
lastStateJson = json;
state = d;
renderTop();
renderCards();
}).catch(function (e) { /* keep old state on transient errors */ });
}
// ---------------------------------------------------------------- run / test
var runningBtn = null;
function runCheckin(accountId, btn) {
if (btn) { btn.disabled = true; btn.textContent = "执行中…"; runningBtn = btn; }
api("POST", "/checkin", accountId ? { account_id: accountId } : {})
.then(function (d) {
if (d.mode === "trigger") {
toast("已触发签到,守护进程正在执行…");
waitForRun(accountId);
} else {
toast("签到完成", "ok");
refreshState();
}
})
.catch(function (e) { toast(e.message, "err"); })
.finally(function () {
if (runningBtn) { runningBtn.disabled = false; runningBtn.textContent = "立即签到"; runningBtn = null; }
});
}
function waitForRun(accountId) {
// poll until today_results contains a fresh entry for the target
var started = Date.now();
var before = JSON.stringify((state && state.today_results) || {});
(function poll() {
if (Date.now() - started > 90000) { refreshState(); return; }
refreshState().then(function () {
var after = JSON.stringify((state && state.today_results) || {});
var done = after !== before && (!accountId || (state.today_results || {})[accountId]);
if (done) {
var r = accountId ? state.today_results[accountId] : null;
if (r) toast(platformName(r.platform) + " " + r.name + ":" + (r.message || r.status), r.status === "fail" || r.status === "no_session" ? "err" : "ok");
else toast("签到完成", "ok");
} else {
setTimeout(poll, 3000);
}
});
})();
}
function testAccount(id, btn) {
btn.disabled = true; btn.textContent = "检测中…";
api("POST", "/accounts/test", { id: id })
.then(function (d) {
var parts = [];
if (d.refresh) parts.push("续期:" + d.refresh);
if (typeof d.checked_in === "boolean") parts.push("今日" + (d.checked_in ? "已签" : "未签"));
if (d.enable === false) parts.push("活动未开放");
if (d.credits && typeof d.credits.remain === "number") parts.push("剩余积分 " + d.credits.remain);
else if (typeof d.credits === "number") parts.push("积分 " + d.credits);
if (d.hint) parts.push(d.hint);
if (d.error) parts.push("错误:" + d.error);
toast(parts.join(";") || "检测完成", d.error ? "err" : "ok");
})
.catch(function (e) { toast(e.message, "err"); })
.finally(function () { btn.disabled = false; btn.textContent = "测连接"; });
}
// ---------------------------------------------------------------- accounts tab
function renderAccounts() {
var wrap = $("account-table-wrap");
var accounts = (state && state.accounts) || [];
if (!accounts.length) {
wrap.innerHTML = '<p class="muted">暂无账号,点击右上角「添加账号」。</p>';
return;
}
var rows = accounts.map(function (a) {
var st = a.today && a.today.status ? STATUS[a.today.status] : null;
return "<tr>" +
"<td><span class='platform-badge " + esc(a.platform) + "'>" + platformName(a.platform) + "</span></td>" +
"<td>" + esc(a.name) + (a.need_reauth ? ' <span class="status-chip no_session">需重新授权</span>' : "") +
(a.platform === "qoder" ? '<div class="muted" style="font-size:11px">' + esc((a.auth && a.auth.domain) || "") + "</div>" : "") + "</td>" +
"<td>" + esc(a.auth.accessToken || "") + "</td>" +
"<td>" + (st ? st.icon + " " + st.label : "⏳ 今日未签") + "</td>" +
"<td>" + (a.last_result && typeof a.last_result.credits === "number" ? a.last_result.credits : "—") + "</td>" +
"<td>" + (a.enabled ? "启用" : "禁用") + "</td>" +
'<td class="actions">' +
'<button class="btn btn-sm" data-a2="run" data-id="' + esc(a.id) + '">签到</button>' +
(a.platform === "qoder" ? '<button class="btn btn-sm" data-a2="fixdomain" data-id="' + esc(a.id) + '" title="自动探测 Token 所属站点并切换">修复站点</button>' +
'<button class="btn btn-sm" data-a2="risk" data-id="' + esc(a.id) + '" title="重新生成设备风控身份(活动列表为空时使用)">刷新风控</button>' +
'<button class="btn btn-sm" data-a2="importdev" data-id="' + esc(a.id) + '" title="把桌面客户端那台机器的设备身份导入进来(活动列表为空时可试)">导入设备身份</button>' : "") +
'<button class="btn btn-sm" data-a2="toggle" data-id="' + esc(a.id) + '">' + (a.enabled ? "禁用" : "启用") + "</button>" +
'<button class="btn btn-sm btn-danger" data-a2="del" data-id="' + esc(a.id) + '">删除</button>' +
"</td></tr>";
}).join("");
wrap.innerHTML = '<table class="tbl"><thead><tr><th>平台</th><th>名称</th><th>Token</th><th>今日</th><th>积分</th><th>状态</th><th>操作</th></tr></thead><tbody>' + rows + "</tbody></table>";
wrap.querySelectorAll("button[data-a2]").forEach(function (btn) {
btn.addEventListener("click", function () {
var id = btn.dataset.id, act = btn.dataset.a2;
if (act === "run") runCheckin(id, btn);
else if (act === "risk") {
btn.disabled = true; btn.textContent = "生成中…";
api("POST", "/accounts/refresh_risk", { id: id })
.then(function (d) {
if (d.ok) { toast("风控身份已更新", "ok"); refreshState(); }
else { toast(d.error || "生成失败", "err"); }
})
.catch(function (e) { toast(e.message, "err"); })
.finally(function () { btn.disabled = false; btn.textContent = "刷新风控"; });
}
else if (act === "fixdomain") {
btn.disabled = true; btn.textContent = "探测中…";
api("POST", "/accounts/fix_domain", { id: id })
.then(function (d) {
if (d.ok) { toast("已切换到 " + d.domain, "ok"); refreshState(); }
else { toast(d.error || "探测失败", "err"); }
})
.catch(function (e) { toast(e.message, "err"); })
.finally(function () { btn.disabled = false; btn.textContent = "修复站点"; });
}
else if (act === "importdev") {
var raw = prompt("把 Qoder 桌面客户端机器上 runtime-info 输出的那一行 JSON 粘进来\n(需含 machineToken / machineCode / machineType):");
if (!raw) return;
var obj = null;
try { obj = JSON.parse(raw.trim()); } catch (e) {
var m = raw.match(/\{[\s\S]*\}/);
if (m) { try { obj = JSON.parse(m[0]); } catch (e2) { obj = null; } }
}
if (!obj || !obj.machineToken || !obj.machineCode || !obj.machineType) {
toast("解析失败:需要包含 machineToken / machineCode / machineType", "err");
return;
}
btn.disabled = true; btn.textContent = "导入中…";
api("POST", "/qoder/import_device", { id: id, machineToken: obj.machineToken,
machineCode: obj.machineCode, machineType: obj.machineType })
.then(function (d) { toast(d.message || "已导入设备身份", "ok"); })
.catch(function (e) { toast(e.message, "err"); })
.finally(function () { btn.disabled = false; btn.textContent = "导入设备身份"; });
}
else if (act === "toggle") {
var acc = state.accounts.find(function (x) { return x.id === id; });
api("POST", "/accounts/update", { id: id, enabled: !acc.enabled })
.then(function () { refreshState(); }).catch(function (e) { toast(e.message, "err"); });
} else if (act === "del") {
if (!confirm("确定删除该账号?")) return;
api("POST", "/accounts/delete", { id: id })
.then(function () { toast("已删除", "ok"); refreshState(); })
.catch(function (e) { toast(e.message, "err"); });
}
});
});
}
// ---------------------------------------------------------------- add modal
function openModal() { $("modal-add").classList.remove("hidden"); }
function closeModal(id) { $(id).classList.add("hidden"); stopOauth(); stopQdLogin(); $("add-msg").textContent = ""; }
document.querySelectorAll("[data-close]").forEach(function (b) {
b.addEventListener("click", function () { closeModal(b.dataset.close); });
});
$("modal-add").addEventListener("click", function (e) {
if (e.target === this) closeModal("modal-add");
});
$("btn-add-account").addEventListener("click", openModal);
document.querySelectorAll(".mtab").forEach(function (btn) {
btn.addEventListener("click", function () {
document.querySelectorAll(".mtab").forEach(function (b) { b.classList.remove("active"); });
document.querySelectorAll(".mtabpage").forEach(function (p) { p.classList.remove("active"); });
btn.classList.add("active");
$("mtab-" + btn.dataset.mtab).classList.add("active");
stopOauth();
});
});
function addMsg(msg, ok) {
var m = $("add-msg");
m.textContent = msg;
m.className = "msg " + (ok ? "ok" : "err");
}
$("btn-wb-manual-add").addEventListener("click", function () {
var token = $("wb-token").value.trim();
if (!token) return addMsg("请填写 Access Token", false);
api("POST", "/accounts/add_wb_manual", {
name: $("wb-name").value.trim(),
access_token: token,
refresh_token: $("wb-refresh").value.trim(),
domain: $("wb-domain").value
}).then(function () {
toast("WorkBuddy 账号已添加", "ok");
closeModal("modal-add"); refreshState();
}).catch(function (e) { addMsg(e.message, false); });
});
$("btn-trae-add").addEventListener("click", function () {
var token = $("trae-token").value.trim();
if (!token) return addMsg("请填写 Access Token", false);
api("POST", "/accounts/add_trae", {
name: $("trae-name").value.trim(),
access_token: token,
refresh_token: $("trae-refresh").value.trim()
}).then(function () {
toast("TRAE 账号已添加", "ok");
closeModal("modal-add"); refreshState();
}).catch(function (e) { addMsg(e.message, false); });
});
// ---- qoder 一键登录(OAuth 设备授权,绑定本机设备)
var qdState = null, qdTimer = null;
function stopQdLogin() {
if (qdTimer) { clearInterval(qdTimer); qdTimer = null; }
qdState = null;
}
$("btn-qoder-login").addEventListener("click", function () {
var realm = $("qoder-domain").value.indexOf("com.cn") >= 0 ? "cn" : "intl";
var btn = this;
stopQdLogin();
btn.disabled = true;
$("qoder-login-link").innerHTML = "";
$("qoder-login-status").textContent = "正在申请授权链接…";
api("POST", "/qoder/login/start", { realm: realm })
.then(function (d) {
qdState = d.state;
var box = $("qoder-login-link");
box.innerHTML = "";
var a = document.createElement("a");
a.href = d.auth_url;
a.target = "_blank";
a.rel = "noopener";
a.textContent = "👉 点这里打开 Qoder 授权页";
box.appendChild(a);
var p = document.createElement("div");
p.className = "muted";
p.textContent = "(或在浏览器打开:" + d.auth_url + ")";
box.appendChild(p);
$("qoder-login-status").textContent =
"等待浏览器里确认授权…(" + Math.round(d.ttl / 60) + " 分钟内有效)";
qdTimer = setInterval(pollQdLogin, 3000);
})
.catch(function (e) { $("qoder-login-status").textContent = e.message; })
.finally(function () { btn.disabled = false; });
});
function pollQdLogin() {
if (!qdState) return;
api("GET", "/qoder/login/poll?state=" + encodeURIComponent(qdState))
.then(function (d) {
if (d.status === "ok") {
stopQdLogin();
$("qoder-login-status").textContent = "登录成功";
toast(d.message || "Qoder 账号已添加", "ok");
closeModal("modal-add");
refreshState();
} else if (d.status === "error") {
stopQdLogin();
$("qoder-login-status").textContent = d.message || "登录失败";
}
})
.catch(function (e) {
stopQdLogin();
$("qoder-login-status").textContent = e.message;
});
}
$("btn-qoder-add").addEventListener("click", function () {
var token = $("qoder-token").value.trim();
if (!token) return addMsg("请填写 Access Token", false);
if (token.length < 10) return addMsg("Access Token 仅 " + token.length + " 字符,明显不完整,请重新复制", false);
var rt0 = $("qoder-refresh").value.trim();
if (rt0 && rt0.length < 10) return addMsg("Refresh Token 仅 " + rt0.length + " 字符,明显不完整,请重新复制", false);
var dev = {};
var map = {
"qoder-machine-id": "machineId",
"qoder-machine-token": "machineToken",
"qoder-machine-code": "machineCode",
"qoder-machine-type": "machineType",
"qoder-version": "version",
};
Object.keys(map).forEach(function (id) {
var v = $(id).value.trim();
if (v) dev[map[id]] = v;
});
api("POST", "/accounts/add_qoder", {
name: $("qoder-name").value.trim(),
access_token: token,
refresh_token: $("qoder-refresh").value.trim(),
domain: $("qoder-domain").value,
device: dev
}).then(function () {
toast("Qoder 账号已添加", "ok");
closeModal("modal-add"); refreshState();
}).catch(function (e) { addMsg(e.message, false); });
});
// ---- workbuddy oauth flow
function stopOauth() {
if (oauthTimer) { clearInterval(oauthTimer); oauthTimer = null; }
oauthState = null;
$("oauth-box").classList.add("hidden");
}
$("btn-oauth-begin").addEventListener("click", function () {
this.disabled = true;
api("POST", "/wb_oauth_begin", {})
.then(function (d) {
oauthState = d.state;
$("oauth-url").value = d.login_url;
$("oauth-box").classList.remove("hidden");
$("oauth-status").textContent = "等待登录…(链接 " + Math.round(d.ttl / 60) + " 分钟内有效)";
oauthTimer = setInterval(pollOauth, 2000);
})
.catch(function (e) { addMsg(e.message, false); })
.finally(function () { this.disabled = false; }.bind(this));
});
$("btn-oauth-copy").addEventListener("click", function () {
var input = $("oauth-url");
input.select();
var done = function () { toast("链接已复制", "ok"); };
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(input.value).then(done, function () { document.execCommand("copy"); done(); });
} else { document.execCommand("copy"); done(); }
});
function pollOauth() {
if (!oauthState) return;
api("GET", "/wb_oauth_poll?state=" + encodeURIComponent(oauthState))
.then(function (d) {
if (d.status === "done") {
stopOauth();
toast("WorkBuddy 账号已绑定", "ok");
closeModal("modal-add");
refreshState();
}
})
.catch(function (e) {
$("oauth-status").textContent = e.message;
stopOauth();
});
}
// ---------------------------------------------------------------- logs
function loadLogs() {
api("GET", "/logs?lines=300").then(function (d) {
$("log-view").textContent = (d.lines || []).join("\n") || "暂无日志";
$("log-view").scrollTop = $("log-view").scrollHeight;
}).catch(function () {
$("log-view").textContent = "日志加载失败";
});
}
$("btn-refresh-logs").addEventListener("click", loadLogs);
setInterval(function () {
if (document.hidden) return;
if ($("log-autorefresh").checked && $("tab-logs").classList.contains("active")) loadLogs();
}, 15000);
// ---------------------------------------------------------------- settings
// Local editing copy: initialised from server state ONLY when the tab is
// opened (and there are no unsaved edits). The 10s state poll must never
// overwrite it, otherwise in-progress edits are silently discarded.
var settings = null;
var settingsDirty = false;
function loadUmidStatus() {
api("GET", "/umid/status").then(function (d) {
var el = $("umid-status");
if (!el) return;
if (!d.supported) {
el.textContent = "当前 CPU 架构 " + d.cpu_arch + " 暂不支持自动生成(需 x86_64 / arm64)";
} else if (d.installed) {
el.textContent = "✅ 已安装 v" + d.version + "(" + d.arch + ",安装于 " + (d.installedAt || "-") + ")";
} else {
el.textContent = "⚠️ 未安装 —— 点击下方按钮下载(约 30MB)";
}
}).catch(function () {});
}
var umidBtn = $("btn-umid-install");
if (umidBtn) {
umidBtn.addEventListener("click", function () {
umidBtn.disabled = true;
umidBtn.textContent = "下载中(约 1-3 分钟)…";
$("umid-msg").textContent = "";
$("umid-msg").className = "msg";
api("POST", "/umid/install", { force: true })
.then(function (d) {
$("umid-msg").textContent = d.message || (d.ok ? "已安装" : "失败");
$("umid-msg").className = "msg " + (d.ok ? "ok" : "err");
toast(d.ok ? "风控组件已就绪" : (d.message || "下载失败"), d.ok ? "ok" : "err");
loadUmidStatus();
})
.catch(function (e) {
$("umid-msg").textContent = e.message;
$("umid-msg").className = "msg err";
})
.finally(function () {
umidBtn.disabled = false;
umidBtn.textContent = "下载 / 重新获取风控组件";
});
});
}
function openSettingsTab() {
if (!settingsDirty) {
settings = JSON.parse(JSON.stringify(
(state && state.settings) || { schedule_times: [], webhook_url: "", notify_on: [] }));
}
renderSettings();
loadUmidStatus();
}
function renderSettings() {
// renders ONLY from the local copy — never re-reads from `state` here
settings = settings || { schedule_times: [], webhook_url: "", notify_on: [] };
var chips = $("time-chips");
chips.innerHTML = "";
(settings.schedule_times || []).forEach(function (t) {
var el = document.createElement("span");
el.className = "time-chip";
el.innerHTML = esc(t) + '<button title="删除">×</button>';
el.querySelector("button").addEventListener("click", function () {
settings.schedule_times = settings.schedule_times.filter(function (x) { return x !== t; });
settingsDirty = true;
renderSettings();
});
chips.appendChild(el);
});
$("webhook-url").value = settings.webhook_url || "";
$("webhook-type").value = settings.webhook_type || "auto";
$("notify-fail").checked = (settings.notify_on || []).indexOf("fail") >= 0;
$("notify-success").checked = (settings.notify_on || []).indexOf("success") >= 0;
}
$("btn-add-time").addEventListener("click", function () {
var v = $("new-time").value;
if (!v) { toast("请先选择要添加的时间", "err"); return; }
settings.schedule_times = settings.schedule_times || [];
if (settings.schedule_times.indexOf(v) >= 0) {
toast("该时间已存在", "err");
return;
}
settings.schedule_times.push(v);
settings.schedule_times.sort();
settingsDirty = true;
renderSettings();
$("new-time").value = "";
});
$("btn-save-settings").addEventListener("click", function () {
var times = (settings.schedule_times || []).slice().sort();
if (!times.length) { $("settings-msg").textContent = "至少保留一个签到时间点"; $("settings-msg").className = "msg err"; return; }
var notify = [];
if ($("notify-fail").checked) notify.push("fail");
if ($("notify-success").checked) notify.push("success");
api("POST", "/settings", {
schedule_times: times,
webhook_url: $("webhook-url").value.trim(),
webhook_type: $("webhook-type").value,
notify_on: notify
}).then(function () {
settingsDirty = false;
$("settings-msg").textContent = "已保存";
$("settings-msg").className = "msg ok";
toast("设置已保存", "ok");
refreshState();
}).catch(function (e) {
$("settings-msg").textContent = e.message;
$("settings-msg").className = "msg err";
});
});
$("btn-test-notify").addEventListener("click", function () {
var url = $("webhook-url").value.trim();
if (!url) { $("settings-msg").textContent = "请先填写 Webhook 地址"; $("settings-msg").className = "msg err"; return; }
// save first so the daemon/notify uses the new url, then fire a real run
var notify = [];
if ($("notify-fail").checked) notify.push("fail");
if ($("notify-success").checked) notify.push("success");
api("POST", "/settings", { webhook_url: url, webhook_type: $("webhook-type").value, notify_on: notify })
.then(function () {
settingsDirty = false;
return api("POST", "/checkin", {});
})
.then(function () {
$("settings-msg").textContent = "已触发一次签到,通知将随结果发送";
$("settings-msg").className = "msg ok";
waitForRun(null);
})
.catch(function (e) {
$("settings-msg").textContent = e.message;
$("settings-msg").className = "msg err";
});
});
// ---------------------------------------------------------------- run all
$("btn-run-all").addEventListener("click", function () { runCheckin(null, null); });
// ---------------------------------------------------------------- boot
var STATE_POLL_MS = 30000;
function boot() {
refreshState(true);
renderAccounts();
pollTimer = setInterval(function () {
if (document.hidden) return; // 页面/iframe 不可见时暂停轮询
refreshState().then(renderAccountsIfVisible);
}, STATE_POLL_MS);
document.addEventListener("visibilitychange", function () {
if (!document.hidden) refreshState(true).then(renderAccountsIfVisible);
});
}
function renderAccountsIfVisible() {
if ($("tab-accounts").classList.contains("active")) renderAccounts();
}
boot();
})();