This commit is contained in:
tao.chen
2026-09-03 15:11:15 +08:00
parent 58d9a123de
commit 5c494d50cf
3 changed files with 118 additions and 35 deletions
+49 -31
View File
@@ -15,6 +15,7 @@ const state = {
autoscroll: true,
expandedLogs: new Set(), // 已展开的历史日志 record IDre-render 后恢复)
lastLiveKey: null, // 上次渲染的"当前任务身份"(用于判断是否需要重建 DOM)
modalOpen: false, // 实时日志弹窗是否打开
};
// ============ Helpers ============
@@ -67,6 +68,7 @@ async function pollQueue() {
setConn(true, "已连接");
renderQueue();
renderRunning();
if (state.modalOpen) refreshModalLog();
} catch (e) {
setConn(false, "连接失败");
console.warn("queue poll failed:", e);
@@ -114,13 +116,11 @@ function renderQueue() {
function renderRunning() {
const body = $("#running-body");
const stateBadge = $("#running-state");
const sizeEl = $("#log-size");
if (!state.current) {
stateBadge.textContent = "空闲";
stateBadge.className = "badge badge-grey";
body.innerHTML = `<p class="empty">当前没有任务在执行</p>`;
sizeEl.textContent = "";
state.lastLiveKey = null;
return;
}
@@ -130,38 +130,48 @@ function renderRunning() {
stateBadge.className = "badge badge-warn";
const liveKey = `${c.task.script}|${c.task.repo}|${c.task.ref}|${c.startedAt}`;
// 仅渲染尾部以避免长日志卡顿
const fullLog = c.log || "";
if (state.lastLiveKey === liveKey) return;
state.lastLiveKey = liveKey;
body.innerHTML = `
<dl class="running-meta">
<dt>仓库</dt><dd>${escapeHtml(c.task.repo)}</dd>
<dt>分支</dt><dd>${escapeHtml(c.task.ref)}</dd>
<dt>Commit</dt><dd>${escapeHtml(shortCommit(c.task.commit))}</dd>
<dt>脚本</dt><dd>${escapeHtml(c.task.script)}</dd>
<dt>开始</dt><dd>${escapeHtml(fmtFullTime(c.startedAt))}</dd>
</dl>
<div class="running-actions">
<button id="open-log-btn" class="btn-ghost" type="button">查看实时日志</button>
</div>
`;
$("#open-log-btn").addEventListener("click", openLogModal);
}
// ============ Live log modal ============
function openLogModal() {
if (!state.current) return;
$("#log-modal").hidden = false;
state.modalOpen = true;
refreshModalLog();
}
function closeLogModal() {
$("#log-modal").hidden = true;
state.modalOpen = false;
}
function refreshModalLog() {
const logBox = $("#modal-log");
const sizeEl = $("#log-size");
if (!logBox) return;
const fullLog = state.current ? (state.current.log || "") : "";
const tail = fullLog.length > LOG_TAIL_CHARS
? fullLog.slice(fullLog.length - LOG_TAIL_CHARS)
: fullLog;
if (state.lastLiveKey !== liveKey) {
// 任务身份变化:重建结构并贴底
body.innerHTML = `
<dl class="running-meta">
<dt>仓库</dt><dd>${escapeHtml(c.task.repo)}</dd>
<dt>分支</dt><dd>${escapeHtml(c.task.ref)}</dd>
<dt>Commit</dt><dd>${escapeHtml(shortCommit(c.task.commit))}</dd>
<dt>脚本</dt><dd>${escapeHtml(c.task.script)}</dd>
<dt>开始</dt><dd>${escapeHtml(fmtFullTime(c.startedAt))}</dd>
</dl>
<pre class="log-box" id="live-log"></pre>
`;
state.lastLiveKey = liveKey;
const logBox = $("#live-log");
logBox.textContent = tail || "(等待脚本输出…)";
sizeEl.textContent = fullLog.length
? `${(fullLog.length / 1024).toFixed(1)} KB`
: "";
requestAnimationFrame(() => {
logBox.scrollTop = logBox.scrollHeight;
});
return;
}
// 同一任务:仅更新日志内容,保持 DOM 节点稳定
const logBox = $("#live-log");
// 仅当贴底或已开启自动滚动时才追尾,避免用户向上滚动时被打断
const distanceFromBottom = logBox.scrollHeight - logBox.scrollTop - logBox.clientHeight;
const atBottom = distanceFromBottom <= 24;
logBox.textContent = tail || "(等待脚本输出…)";
@@ -284,7 +294,7 @@ $("#log-autoscroll").addEventListener("click", (e) => {
state.autoscroll = !state.autoscroll;
e.currentTarget.classList.toggle("active", state.autoscroll);
if (state.autoscroll) {
const logBox = $("#live-log");
const logBox = $("#modal-log");
if (logBox) {
requestAnimationFrame(() => {
logBox.scrollTop = logBox.scrollHeight;
@@ -293,6 +303,14 @@ $("#log-autoscroll").addEventListener("click", (e) => {
}
});
// 弹窗关闭:点击 backdrop / 关闭按钮 / Esc
document.querySelectorAll("#log-modal [data-close]").forEach((el) => {
el.addEventListener("click", closeLogModal);
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && state.modalOpen) closeLogModal();
});
pollQueue();
pollHistory();
setInterval(pollQueue, POLL_QUEUE_MS);