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
+54
View File
@@ -277,3 +277,57 @@ a { color: var(--accent); text-decoration: none; }
.status-timeout { color: var(--warn); background: rgba(224, 164, 88, 0.12); }
.status-skipped { color: var(--skipped); background: rgba(154, 163, 178, 0.12); }
.status-running { color: var(--accent); background: rgba(91, 141, 239, 0.12); }
/* ============ Running actions + Modal ============ */
.running-actions { margin-top: 12px; display: flex; gap: 8px; }
.modal {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
}
.modal[hidden] { display: none; }
.modal-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.6);
}
.modal-dialog {
position: relative;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--shadow);
width: min(900px, 92vw);
height: min(72vh, 720px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.modal-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
background: var(--surface-2);
}
.modal-head h3 { margin: 0; font-size: 14px; font-weight: 600; color: var(--text); }
.modal-log {
flex: 1;
height: auto;
border: none;
border-radius: 0;
}
.modal-foot {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
border-top: 1px solid var(--border);
background: var(--surface-2);
font-size: 12px;
}
+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);
+15 -4
View File
@@ -41,10 +41,6 @@
<div id="running-body" class="card-body running-body">
<p class="empty">当前没有任务在执行</p>
</div>
<div class="card-foot">
<button id="log-autoscroll" class="btn-ghost active" type="button">自动滚动</button>
<span id="log-size" class="muted"></span>
</div>
</section>
<section class="card" id="history-card">
@@ -56,6 +52,21 @@
</section>
</main>
<div id="log-modal" class="modal" hidden role="dialog" aria-labelledby="modal-title" aria-modal="true">
<div class="modal-backdrop" data-close></div>
<div class="modal-dialog">
<header class="modal-head">
<h3 id="modal-title">实时日志</h3>
<button class="btn-ghost" data-close type="button">关闭</button>
</header>
<pre id="modal-log" class="log-box modal-log">(等待脚本输出…)</pre>
<footer class="modal-foot">
<button id="log-autoscroll" class="btn-ghost active" type="button">自动滚动</button>
<span id="log-size" class="muted"></span>
</footer>
</div>
</div>
<script src="/static/app.js"></script>
</body>
</html>