diff --git a/static/app.css b/static/app.css index 6e120a8..8e22ea6 100644 --- a/static/app.css +++ b/static/app.css @@ -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; +} diff --git a/static/app.js b/static/app.js index a6e2791..c879999 100644 --- a/static/app.js +++ b/static/app.js @@ -15,6 +15,7 @@ const state = { autoscroll: true, expandedLogs: new Set(), // 已展开的历史日志 record ID(re-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 = `

当前没有任务在执行

`; - 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 = ` +
+
仓库
${escapeHtml(c.task.repo)}
+
分支
${escapeHtml(c.task.ref)}
+
Commit
${escapeHtml(shortCommit(c.task.commit))}
+
脚本
${escapeHtml(c.task.script)}
+
开始
${escapeHtml(fmtFullTime(c.startedAt))}
+
+
+ +
+ `; + $("#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 = ` -
-
仓库
${escapeHtml(c.task.repo)}
-
分支
${escapeHtml(c.task.ref)}
-
Commit
${escapeHtml(shortCommit(c.task.commit))}
-
脚本
${escapeHtml(c.task.script)}
-
开始
${escapeHtml(fmtFullTime(c.startedAt))}
-
-

-    `;
-    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);
diff --git a/static/index.html b/static/index.html
index 0ab2286..72b4d0f 100644
--- a/static/index.html
+++ b/static/index.html
@@ -41,10 +41,6 @@
       

当前没有任务在执行

-
- - -
@@ -56,6 +52,21 @@
+ +