feat(ui,isotest): Group disabled displays into a collapsible section and refactor display item rendering; start a background screenshot loop during isotest and improve test-run cleanup
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-01-10 - 0.5.0 - feat(ui,isotest)
|
||||
Group disabled displays into a collapsible section and refactor display item rendering; start a background screenshot loop during isotest and improve test-run cleanup
|
||||
|
||||
- Refactored display rendering: introduced renderDisplayItem() and simplified updateDisplaysUI() to separate enabled/disabled displays
|
||||
- Disabled displays are collapsed under a <details> summary showing count ("Disabled Displays (N)")
|
||||
- Added a background screenshot loop in isotest/run-test.sh that runs screenshot.sh every 5 seconds and records SCREENSHOT_LOOP_PID
|
||||
- Improved cleanup in isotest/run-test.sh to kill SCREENSHOT_LOOP_PID and ENABLE_PID if they are running
|
||||
|
||||
## 2026-01-10 - 0.4.15 - fix(isotest)
|
||||
Improve robustness of SPICE display enabler: add logging, wait-for-port and URI parsing, retries and reconnection logic, stabilization delay before configuring, and verification/retry of monitor configuration
|
||||
|
||||
|
||||
@@ -725,31 +725,48 @@ export class UIServer {
|
||||
setInterval(fetchUpdates, 60000); // Check every minute
|
||||
|
||||
// Display management
|
||||
function renderDisplayItem(d) {
|
||||
return '<div class="device-item" style="flex-wrap: wrap; gap: 8px;">' +
|
||||
'<div style="flex: 1; min-width: 150px;">' +
|
||||
'<div class="device-name">' + d.name + '</div>' +
|
||||
'<div style="font-size: 11px; color: var(--text-dim);">' +
|
||||
d.width + 'x' + d.height + ' @ ' + d.refreshRate + 'Hz' +
|
||||
(d.make !== 'Unknown' ? ' • ' + d.make : '') +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div style="display: flex; gap: 4px;">' +
|
||||
(d.isPrimary
|
||||
? '<span class="device-default">Primary</span>'
|
||||
: (d.active ? '<button class="btn btn-primary" style="padding: 2px 8px; margin: 0; font-size: 11px;" onclick="setKioskDisplay(\\'' + d.name + '\\')">Set Primary</button>' : '')) +
|
||||
'<button class="btn ' + (d.active ? 'btn-danger' : 'btn-primary') + '" style="padding: 2px 8px; margin: 0; font-size: 11px;" onclick="toggleDisplay(\\'' + d.name + '\\', ' + !d.active + ')">' +
|
||||
(d.active ? 'Disable' : 'Enable') +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function updateDisplaysUI(data) {
|
||||
const list = document.getElementById('displays-list');
|
||||
if (!data.displays || data.displays.length === 0) {
|
||||
list.innerHTML = '<div style="color: var(--text-dim);">No displays detected</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = data.displays.map(d =>
|
||||
'<div class="device-item" style="flex-wrap: wrap; gap: 8px;">' +
|
||||
'<div style="flex: 1; min-width: 150px;">' +
|
||||
'<div class="device-name">' + d.name + '</div>' +
|
||||
'<div style="font-size: 11px; color: var(--text-dim);">' +
|
||||
d.width + 'x' + d.height + ' @ ' + d.refreshRate + 'Hz' +
|
||||
(d.make !== 'Unknown' ? ' • ' + d.make : '') +
|
||||
'</div>' +
|
||||
|
||||
const enabled = data.displays.filter(d => d.active);
|
||||
const disabled = data.displays.filter(d => !d.active);
|
||||
|
||||
let html = enabled.map(renderDisplayItem).join('');
|
||||
|
||||
if (disabled.length > 0) {
|
||||
html += '<details style="margin-top: 12px;">' +
|
||||
'<summary style="cursor: pointer; color: var(--text-dim); font-size: 12px; padding: 4px 0;">Disabled Displays (' + disabled.length + ')</summary>' +
|
||||
'<div style="margin-top: 8px;">' +
|
||||
disabled.map(renderDisplayItem).join('') +
|
||||
'</div>' +
|
||||
'<div style="display: flex; gap: 4px;">' +
|
||||
(d.isPrimary
|
||||
? '<span class="device-default">Primary</span>'
|
||||
: '<button class="btn btn-primary" style="padding: 2px 8px; margin: 0; font-size: 11px;" onclick="setKioskDisplay(\\'' + d.name + '\\')">Set Primary</button>') +
|
||||
'<button class="btn ' + (d.active ? 'btn-danger' : 'btn-primary') + '" style="padding: 2px 8px; margin: 0; font-size: 11px;" onclick="toggleDisplay(\\'' + d.name + '\\', ' + !d.active + ')">' +
|
||||
(d.active ? 'Disable' : 'Enable') +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
).join('');
|
||||
'</details>';
|
||||
}
|
||||
|
||||
list.innerHTML = html;
|
||||
}
|
||||
|
||||
function fetchDisplays() {
|
||||
|
||||
@@ -63,6 +63,12 @@ fi
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "Shutting down..."
|
||||
if [ -n "$SCREENSHOT_LOOP_PID" ] && kill -0 "$SCREENSHOT_LOOP_PID" 2>/dev/null; then
|
||||
kill "$SCREENSHOT_LOOP_PID" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "$ENABLE_PID" ] && kill -0 "$ENABLE_PID" 2>/dev/null; then
|
||||
kill "$ENABLE_PID" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "$VIEWER_PID" ] && kill -0 "$VIEWER_PID" 2>/dev/null; then
|
||||
kill "$VIEWER_PID" 2>/dev/null || true
|
||||
fi
|
||||
@@ -212,11 +218,15 @@ if [ -f "$SCRIPT_DIR/enable-displays.py" ]; then
|
||||
fi
|
||||
|
||||
echo "Tips:"
|
||||
echo " - pnpm run test:screenshot - Take screenshot"
|
||||
echo " - http://localhost:3006 - Management UI"
|
||||
echo " - socat - UNIX-CONNECT:.nogit/vm/serial.sock - Serial console (login: ecouser/ecouser)"
|
||||
echo ""
|
||||
|
||||
# Start screenshot loop in background (takes screenshots every 5 seconds)
|
||||
echo "Starting screenshot loop..."
|
||||
(while true; do "$SCRIPT_DIR/screenshot.sh" 2>/dev/null; sleep 5; done) &
|
||||
SCREENSHOT_LOOP_PID=$!
|
||||
|
||||
if [ "$AUTO_MODE" = true ]; then
|
||||
echo "=== Auto mode: waiting for display setup ==="
|
||||
|
||||
|
||||
Reference in New Issue
Block a user