video/vnd.dlna.mpeg-tts
•
3.35 KB
•
122 lines
interface CiStepResultResponse {
name: string;
status: string;
exit_code: number | null;
}
interface CiJobResultResponse {
name: string;
status: string;
exit_code: number | null;
steps: CiStepResultResponse[];
log: string;
}
interface CiRunStatusResponse {
id: string;
status: string;
jobs: CiJobResultResponse[];
}
export function initCiLogPolling() {
const container = document.getElementById("ci-jobs-container");
if (!container) return;
const statusUrl = container.dataset.statusUrl;
const initialStatus = container.dataset.initialStatus;
if (!statusUrl) return;
// Don't poll if already finished - but still do one fetch to load logs
const isFinished = initialStatus === "success" || initialStatus === "failed";
async function fetchAndUpdate(): Promise<boolean> {
try {
const res = await fetch(statusUrl);
if (!res.ok) return true; // Stop polling on error
const data: CiRunStatusResponse = await res.json();
// Update overall status badge
const statusBadge = document.getElementById("run-status");
if (statusBadge) {
statusBadge.textContent = data.status;
statusBadge.className = `issue-status status-${data.status}`;
}
// Update each job's logs and status
for (const job of data.jobs) {
const jobEl = container?.querySelector(`[data-job-name="${job.name}"]`);
if (!jobEl) continue;
// Update job status
const statusEl = jobEl.querySelector(".ci-job-status");
if (statusEl) {
statusEl.textContent = job.status;
statusEl.className = `ci-job-status status-${job.status}`;
}
// Update step statuses
for (const step of job.steps) {
const stepEl = jobEl.querySelector(`[data-step-name="${step.name}"]`);
if (!stepEl) continue;
const stepStatusEl = stepEl.querySelector(".ci-step-status");
if (stepStatusEl) {
stepStatusEl.className = `ci-step-status status-${step.status}`;
}
// Update or add exit code
let exitEl = stepEl.querySelector(".ci-step-exit");
if (step.exit_code !== null) {
if (!exitEl) {
exitEl = document.createElement("span");
exitEl.className = "ci-step-exit";
stepEl.appendChild(exitEl);
}
exitEl.textContent = `(${step.exit_code})`;
exitEl.className = `ci-step-exit ${step.exit_code === 0 ? "ci-exit-success" : "ci-exit-failure"}`;
}
}
// Update log with auto-scroll
const logEl = jobEl.querySelector<HTMLElement>(".ci-log");
if (logEl) {
// Check if user is scrolled to bottom before updating
const isAtBottom =
logEl.scrollTop + logEl.clientHeight >= logEl.scrollHeight - 5;
logEl.textContent = job.log || "(no output)";
// If they were at bottom, keep them there
if (isAtBottom) {
logEl.scrollTop = logEl.scrollHeight;
}
}
}
// Return true if we should stop polling (finished)
return data.status === "success" || data.status === "failed";
} catch {
// Stop polling on exception - user can refresh if needed
return true;
}
}
async function poll() {
const shouldStop = await fetchAndUpdate();
if (!shouldStop) {
// Schedule next poll after 1 second
// Using setTimeout instead of setInterval ensures we don't pile up
// requests on slow connections
setTimeout(poll, 1000);
}
}
if (isFinished) {
// Just load logs once, don't poll
fetchAndUpdate();
} else {
// Start polling
poll();
}
}