const recentActivityDiv = document.getElementById("recent-activity-body"); const recentActivityDivLimit = 1000; function addRecentActivityEntry(event, content, timestamp) { const eventRow = document.createElement('tr'); eventRow.className = "event"; eventRow.innerHTML = ` ${event} ${content} ${timestamp}`; recentActivityDiv.prepend(eventRow); animateElementAddition(eventRow); const childCount = recentActivityDiv.childElementCount; if (childCount > recentActivityDivLimit) { for (let index = recentActivityDivLimit; index < childCount; index++) { recentActivityDiv.removeChild( recentActivityDiv.children[index] ) } } } function animateElementAddition(e, color = "lightblue") { e.style.backgroundColor = color; e.style.lineHeight = "0px"; e.style.overflow = "hidden"; e.style.opacity = 0; setTimeout(function() { e.style.transition = "background-color 1.2s ease-out, line-height 6s, opacity 0.3s ease-out"; e.style.backgroundColor = ""; e.style.lineHeight = ""; e.style.opacity = ""; }, 50); } function animateElementChange(e, color = "lightblue") { e.style.transition = "background-color 0.6s ease-out"; setTimeout(function() { e.style.backgroundColor = color; setTimeout(function() { e.style.backgroundColor = ""; }, 600); }, 50); } function animateElementAndRemove(e, color) { // TODO: This needs more work, as it needs to fit in with keeping the // related bits that reference the number of builds up to date e.remove(); // e.style.borderWidth = "20px;"; // e.style.borderStyle = "solid"; // e.style.borderColor = color; // e.style.transition = "border 10s"; // e.addEventListener("transitionend", function() { // e.remove(); // }, {once: true}); // setTimeout(function() { // e.style.borderColor = ""; // }, 1000); } function buildDetailsFromDom(id) { return document.getElementById("build-" + id).dataset; } function agentDetailsFromDom(id) { return document.getElementById("agent-" + id).dataset; } function updateAgentBuildRelatedElements(agentId) { const agentBuildsElement = document.getElementById( "agent-" + agentId + "-builds" ); const childCount = agentBuildsElement.childElementCount; const noAllocatedBuildsElement = document.getElementById( "agent-" + agentId + "-no-allocated-builds" ); const plusXBuildsElement = document.getElementById( "agent-" + agentId + "-plus-x-builds" ); if (childCount == 0) { noAllocatedBuildsElement.style.display = "block"; plusXBuildsElement.style.display = "none"; animateElementChange(noAllocatedBuildsElement); } else { for (let index = 0; index < Math.min(childCount, 4); index++) { agentBuildsElement.children[index].style.display = "block"; } if (childCount > 4) { noAllocatedBuildsElement.style.display = "none"; plusXBuildsElement.style.display = "block"; const otherBuildCount = childCount - 4; if (otherBuildCount == 1) { plusXBuildsElement.textContent = "Plus 1 other build"; } else { plusXBuildsElement.textContent = `Plus ${otherBuildCount} other builds`; } animateElementChange(plusXBuildsElement); } else { noAllocatedBuildsElement.style.display = "none"; plusXBuildsElement.style.display = "none"; } } } function buildSubmittedHandler(e) { const data = JSON.parse(e.data); console.log("build-submitted", data); addRecentActivityEntry( "Build submitted", `${data.derivation.slice(44, -4)}`, data.timestamp ); } function buildCanceledHandler(e) { console.log("build-canceled", e); addRecentActivityEntry( "Build canceled", `${data.derivation.slice(44, -4)}, tags: ${data.tags}`, data.timestamp ); } function buildSuccessHandler(e) { const data = JSON.parse(e.data); console.log("build-success", data); const buildElement = document.getElementById( "build-" + data.build_id ); const buildDetails = buildDetailsFromDom(data.build_id); addRecentActivityEntry( "Build success", `${buildDetails.derivation.slice(44, -4)}`, data.timestamp ); animateElementAndRemove(buildElement, "green"); updateAgentBuildRelatedElements(data.agent_id); } function buildFailureHandler(e) { const data = JSON.parse(e.data); console.log("build-failure", data); const buildElement = document.getElementById( "build-" + data.build_id ); animateElementAndRemove(buildElement, "red"); updateAgentBuildRelatedElements(data.agent_id); } function buildStartedHandler(e) { const data = JSON.parse(e.data); console.log("build-started", data); const buildDetails = buildDetailsFromDom(data.build_id); const agentDetails = agentDetailsFromDom(data.agent_id); addRecentActivityEntry( "Build started", `${buildDetails.derivation.slice(44, -4)}, agent: ${agentDetails.name}`, data.timestamp ); } function buildSetupFailureHandler(e) { const data = JSON.parse(e.data); console.log("build-setup-failure", data); const buildDetails = buildDetailsFromDom(data.build_id); const agentDetails = agentDetailsFromDom(data.agent_id); addRecentActivityEntry( "Build setup failure", `${buildDetails.derivation.slice(44, -4)}, agent: ${agentDetails.name}`, data.timestamp ); const buildElement = document.getElementById( "build-" + data.build_id ); animateElementAndRemove(buildElement, "red"); updateAgentBuildRelatedElements(data.agent_id); } function allocationPlanUpdateHandler(e) { const data = JSON.parse(e.data); console.log("allocation-plan-update", data); for (const agentId in data.allocation_plan_counts) { const planSizeElement = document.getElementById( "agent-" + agentId + "-plan-size" ); const updatedValue = data.allocation_plan_counts[agentId]; if (updatedValue != planSizeElement.dataset.value) { planSizeElement.textContent = `Plan size: ${data.allocation_plan_counts[agentId]}` planSizeElement.dataset.value = updatedValue; animateElementChange(planSizeElement); } } } function agentBuildsAllocatedHandler(e) { const data = JSON.parse(e.data); console.log("agent-builds-allocated", data); const agentBuildsElement = document.getElementById( "agent-" + data.agent_id + "-builds" ); var newElements = []; data.builds.forEach(function(build) { const buildElement = document.getElementById("build-" + build.uuid); if (!buildElement) { const buildElement = document.createElement('div'); buildElement.id = "build-" + build.uuid; buildElement.className = "build"; buildElement.dataset.derivation = build.derivation_name; const drv = build.derivation_name.slice(44, -4); buildElement.innerHTML = ` ${drv} `; build.tags.sort(function(a, b) { return a.key > b.key; }).forEach(function(tag) { var val; if (tag.value.length == 40) { val = tag.value.substring(0, 8); } else { val = tag.value; } buildElement.innerHTML += ` ${tag.key}: ${val} `; }); if (agentBuildsElement.childElementCount > 4) { buildElement.style.display = "none"; } agentBuildsElement.append(buildElement); newElements.push(buildElement); } }); if (agentBuildsElement.childElementCount > 4) { document.getElementById( "agent-" + data.agent_id + "-no-allocated-builds" ).style.display = "none"; const plusBuilds = document.getElementById( "agent-" + data.agent_id + "-plus-x-builds" ) plusBuilds.style.display = "block"; const otherBuildCount = agentBuildsElement.childElementCount - 4; if (otherBuildCount == 1) { plusBuilds.textContent = "Plus 1 other build"; } else { plusBuilds.textContent = `Plus ${otherBuildCount} other builds`; } animateElementChange(plusBuilds); } for (const element of newElements) { setTimeout( function() { animateElementChange(element); }, 50 ); } } function agentStatusUpdateHandler(e) { const data = JSON.parse(e.data); console.log("agent-status-update", data); const loadElement = document.getElementById( "agent-" + data.agent_id + "-load" ); const loadPercentage = Math.round((100 * data.load_average["1"]) / data.processor_count); loadElement.textContent = `Load: ${loadPercentage}%`; var loadClass; if (loadPercentage < 150) { loadClass = "agent-load-normal"; } else if (loadPercentage < 250) { loadClass = "agent-load-medium"; } else { loadClass = "agent-load-high"; } loadElement.className = `agent-load ${loadClass}`; animateElementChange(loadElement); } const lastEventId = document.getElementById("main").dataset.stateid; const evtSource = new EventSource(`/events?last_event_id=${lastEventId}`); evtSource.addEventListener("build-submitted", buildSubmittedHandler); evtSource.addEventListener("build-success", buildSuccessHandler); evtSource.addEventListener("build-failure", buildFailureHandler); evtSource.addEventListener("build-started", buildStartedHandler); evtSource.addEventListener("build-setup-failure", buildSetupFailureHandler); evtSource.addEventListener("allocation-plan-update", allocationPlanUpdateHandler); evtSource.addEventListener("agent-builds-allocated", agentBuildsAllocatedHandler); evtSource.addEventListener("agent-status-update", agentStatusUpdateHandler); console.log("starting listening");