From 3bbd04820449df44ee2999e1535a8e4db3259fb0 Mon Sep 17 00:00:00 2001 From: GitCaddy Date: Sun, 11 Jan 2026 18:01:53 +0000 Subject: [PATCH] feat(runners): add AJAX polling for real-time status updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add element IDs to status/disk/bandwidth tiles for targeted updates - Add JavaScript polling every 10 seconds to update runner status - Preserve SVG icons during AJAX updates by separating icon and text spans - Add form ID to runner-form for Update Instructions button - Show Connected when online, Last seen when offline 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- templates/shared/actions/runner_edit.tmpl | 133 +++++++++++----------- 1 file changed, 65 insertions(+), 68 deletions(-) diff --git a/templates/shared/actions/runner_edit.tmpl b/templates/shared/actions/runner_edit.tmpl index 6b1a8da561..48dc3ab993 100644 --- a/templates/shared/actions/runner_edit.tmpl +++ b/templates/shared/actions/runner_edit.tmpl @@ -8,11 +8,11 @@
Status
- - {{if .Runner.IsOnline}}{{svg "octicon-check-circle" 16}}{{else}}{{svg "octicon-x-circle" 16}}{{end}} - {{.Runner.StatusLocaleName ctx.Locale}} + + {{if .Runner.IsOnline}}{{svg "octicon-check-circle" 16}}{{else}}{{svg "octicon-x-circle" 16}}{{end}} + {{.Runner.StatusLocaleName ctx.Locale}} -
+
{{if .Runner.IsOnline}}Connected{{else if .Runner.LastOnline}}Last seen {{DateUtils.TimeSince .Runner.LastOnline}}{{else}}Never connected{{end}}
@@ -24,16 +24,16 @@ {{$diskUsed := .RunnerCapabilities.Disk.UsedPercent}} {{$diskFreeGB := DivideFloat64 (Int64ToFloat64 .RunnerCapabilities.Disk.Free) 1073741824.0}} {{$diskTotalGB := DivideFloat64 (Int64ToFloat64 .RunnerCapabilities.Disk.Total) 1073741824.0}} - - {{if ge $diskUsed 95.0}}{{svg "octicon-alert" 16}}{{else if ge $diskUsed 85.0}}{{svg "octicon-alert" 16}}{{else}}{{svg "octicon-database" 16}}{{end}} - {{printf "%.0f" $diskUsed}}% used + + {{if ge $diskUsed 95.0}}{{svg "octicon-alert" 16}}{{else if ge $diskUsed 85.0}}{{svg "octicon-alert" 16}}{{else}}{{svg "octicon-database" 16}}{{end}} + {{printf "%.0f" $diskUsed}}% used -
+
{{printf "%.1f" $diskFreeGB}} GB free of {{printf "%.0f" $diskTotalGB}} GB
{{else}} - {{svg "octicon-database" 16}} No data -
Waiting for report
+ {{svg "octicon-database" 16}} No data +
Waiting for report
{{end}}
@@ -41,16 +41,17 @@
Network
{{if and .RunnerCapabilities .RunnerCapabilities.Bandwidth}} - - {{svg "octicon-arrow-down" 16}} {{printf "%.0f" .RunnerCapabilities.Bandwidth.DownloadMbps}} Mbps + + {{svg "octicon-arrow-down" 16}} + {{printf "%.0f" .RunnerCapabilities.Bandwidth.DownloadMbps}} Mbps -
+
{{if gt .RunnerCapabilities.Bandwidth.Latency 0.0}}{{printf "%.0f" .RunnerCapabilities.Bandwidth.Latency}} ms latency{{end}} {{if .RunnerCapabilities.Bandwidth.TestedAt}}- tested {{DateUtils.TimeSince .RunnerCapabilities.Bandwidth.TestedAt}}{{end}}
{{else}} - {{svg "octicon-globe" 16}} No data -
Waiting for test
+ {{svg "octicon-globe" 16}} No data +
Waiting for test
{{end}}
@@ -131,7 +132,7 @@
{{end}} -
+ {{template "base/disable_form_autofill"}}
AI Instructions
@@ -305,70 +306,66 @@ }) .then(response => response.json()) .then(data => { - // Update status tile - const statusTile = document.querySelector('.runner-container .column:first-child .segment'); - if (statusTile) { - const statusLabel = statusTile.querySelector('.label'); - const statusText = statusTile.querySelector('.tw-text-xs'); + // Update status tile - only change class and text, preserve icons + const statusLabel = document.getElementById('status-label'); + const statusText = document.getElementById('status-text'); + const statusSubtext = document.getElementById('status-subtext'); - if (statusLabel) { - statusLabel.className = 'ui ' + (data.is_online ? 'green' : 'red') + ' large label'; - statusLabel.innerHTML = (data.is_online ? - '' : - '') + - ' ' + data.status; - } - - if (statusText) { - statusText.textContent = data.is_online ? 'Connected' : - (data.last_online ? 'Last seen ' + new Date(data.last_online).toLocaleString() : 'Never connected'); + if (statusLabel) { + statusLabel.className = 'ui ' + (data.is_online ? 'green' : 'red') + ' large label'; + } + if (statusText) { + statusText.textContent = data.status; + } + if (statusSubtext) { + if (data.is_online) { + statusSubtext.textContent = 'Connected'; + } else if (data.last_online) { + statusSubtext.textContent = 'Last seen ' + new Date(data.last_online).toLocaleString(); + } else { + statusSubtext.textContent = 'Never connected'; } } - // Update disk tile + // Update disk tile - only change class and text if (data.disk) { - const diskTile = document.querySelector('.runner-container .column:nth-child(2) .segment'); - if (diskTile) { - const diskLabel = diskTile.querySelector('.label'); - const diskText = diskTile.querySelector('.tw-text-xs'); - const usedPct = data.disk.used_percent; + const diskLabel = document.getElementById('disk-label'); + const diskText = document.getElementById('disk-text'); + const diskSubtext = document.getElementById('disk-subtext'); + const usedPct = data.disk.used_percent; - if (diskLabel) { - const color = usedPct >= 95 ? 'red' : (usedPct >= 85 ? 'yellow' : 'green'); - const icon = usedPct >= 85 ? 'octicon-alert' : 'octicon-database'; - diskLabel.className = 'ui ' + color + ' large label'; - diskLabel.innerHTML = ' ' + - Math.round(usedPct) + '% used'; - } - - if (diskText) { - diskText.textContent = formatBytes(data.disk.free_bytes) + ' free of ' + formatBytes(data.disk.total_bytes); - } + if (diskLabel) { + const color = usedPct >= 95 ? 'red' : (usedPct >= 85 ? 'yellow' : 'green'); + diskLabel.className = 'ui ' + color + ' large label'; + } + if (diskText) { + diskText.textContent = Math.round(usedPct) + '% used'; + } + if (diskSubtext) { + diskSubtext.textContent = formatBytes(data.disk.free_bytes) + ' free of ' + formatBytes(data.disk.total_bytes); } } - // Update bandwidth tile + // Update bandwidth tile - only change class and text if (data.bandwidth) { - const bwTile = document.querySelector('.runner-container .column:nth-child(3) .segment'); - if (bwTile) { - const bwLabel = bwTile.querySelector('.label'); - const bwText = bwTile.querySelector('.tw-text-xs'); - const mbps = data.bandwidth.download_mbps; + const bwLabel = document.getElementById('bw-label'); + const bwText = document.getElementById('bw-text'); + const bwSubtext = document.getElementById('bw-subtext'); + const mbps = data.bandwidth.download_mbps; - if (bwLabel) { - const color = mbps >= 100 ? 'green' : (mbps >= 10 ? 'blue' : 'yellow'); - bwLabel.className = 'ui ' + color + ' large label'; - bwLabel.innerHTML = ' ' + - Math.round(mbps) + ' Mbps'; - } - - if (bwText && data.bandwidth.latency_ms) { - let text = Math.round(data.bandwidth.latency_ms) + ' ms latency'; - if (data.bandwidth.tested_at) { - text += ' - tested ' + new Date(data.bandwidth.tested_at).toLocaleString(); - } - bwText.textContent = text; + if (bwLabel) { + const color = mbps >= 100 ? 'green' : (mbps >= 10 ? 'blue' : 'yellow'); + bwLabel.className = 'ui ' + color + ' large label'; + } + if (bwText) { + bwText.textContent = Math.round(mbps) + ' Mbps'; + } + if (bwSubtext && data.bandwidth.latency_ms) { + let text = Math.round(data.bandwidth.latency_ms) + ' ms latency'; + if (data.bandwidth.tested_at) { + text += ' - tested ' + new Date(data.bandwidth.tested_at).toLocaleString(); } + bwSubtext.textContent = text; } } })