feat: make noVNC and xterm.js console links open in minimal popup windows

This commit is contained in:
2026-01-25 11:54:33 -05:00
parent 59057b98e3
commit e322c0cda7
5 changed files with 163 additions and 97 deletions

View File

@@ -1408,6 +1408,63 @@
let charts = {};
let peakRates = {};
// Handle chart toggle clicks via event delegation (works for dynamically created elements)
const handleChartToggle = (interfaceId) => {
const chartContainer = document.querySelector(`[data-chart-container="${interfaceId}"]`);
if (chartContainer) {
chartContainer.classList.toggle('open');
// Create chart on first open
if (chartContainer.classList.contains('open') && !charts[interfaceId]) {
const attemptCreateChart = (retries = 5, delay = 200) => {
const interfaceData = networkData?.interfaces?.find(i =>
i.id.replace(/[^a-z0-9]/gi, '-') === interfaceId
);
const canvas = document.getElementById(`chart-${interfaceId}`);
if (canvas && interfaceData && interfaceData.history) {
const historyLength = Math.max(
interfaceData.history.download?.length || 0,
interfaceData.history.upload?.length || 0
);
if (historyLength > 0) {
console.log('Creating chart for interface:', interfaceId);
requestAnimationFrame(() => {
setTimeout(() => {
if (!charts[interfaceId]) {
createChart(interfaceId, interfaceData);
}
}, 50);
});
return;
}
}
if (retries > 0) {
console.log(`Chart data not ready for ${interfaceId}, retrying... (${retries} attempts left)`);
setTimeout(() => attemptCreateChart(retries - 1, delay), delay);
}
};
attemptCreateChart();
}
}
};
// Event delegation for chart toggle buttons - attach once to document
document.addEventListener('click', (e) => {
const toggleButton = e.target.closest('.chart-toggle');
if (toggleButton) {
const interfaceId = toggleButton.dataset.interface;
if (interfaceId) {
handleChartToggle(interfaceId);
}
}
});
const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return '0 B';
const k = 1024;
@@ -1669,96 +1726,7 @@
.map((iface) => createInterfaceCard(iface))
.join('');
// Set up chart toggles - use event delegation or ensure fresh listeners
document.querySelectorAll('.chart-toggle').forEach(button => {
button.addEventListener('click', (e) => {
const interfaceId = e.currentTarget.dataset.interface;
const container = document.querySelector(`[data-chart-container="${interfaceId}"]`);
if (container) {
container.classList.toggle('open');
// Create chart on first open
if (container.classList.contains('open') && !charts[interfaceId]) {
const attemptCreateChart = (retries = 5, delay = 200) => {
const interfaceData = networkData?.interfaces?.find(i =>
i.id.replace(/[^a-z0-9]/gi, '-') === interfaceId
);
const canvas = document.getElementById(`chart-${interfaceId}`);
if (canvas && interfaceData && interfaceData.history) {
const historyLength = Math.max(
interfaceData.history.download?.length || 0,
interfaceData.history.upload?.length || 0
);
if (historyLength > 0) {
console.log('Creating chart for interface:', interfaceId, 'with data:', {
id: interfaceData.id,
name: interfaceData.name,
historyLength: historyLength
});
// Use requestAnimationFrame to ensure DOM is ready
requestAnimationFrame(() => {
setTimeout(() => {
if (!charts[interfaceId]) {
createChart(interfaceId, interfaceData);
}
}, 50);
});
return;
}
}
// Retry if data not ready yet
if (retries > 0) {
console.log(`Chart data not ready for ${interfaceId}, retrying... (${retries} attempts left)`);
setTimeout(() => attemptCreateChart(retries - 1, delay), delay);
} else {
console.warn('Failed to create chart for', interfaceId, 'after multiple attempts. Available interfaces:', networkData?.interfaces?.map(i => i.id));
// Try one more time with latest data from next poll
const checkOnNextUpdate = () => {
const latestData = networkData?.interfaces?.find(i =>
i.id.replace(/[^a-z0-9]/gi, '-') === interfaceId
);
if (latestData && latestData.history && container.classList.contains('open')) {
const historyLength = Math.max(
latestData.history.download?.length || 0,
latestData.history.upload?.length || 0
);
if (historyLength > 0 && !charts[interfaceId]) {
console.log('Creating chart for', interfaceId, 'with data from next update');
createChart(interfaceId, latestData);
}
}
};
// Check on next data update
const originalUpdate = updateInterfaceData;
const checkOnce = (interfaceData) => {
if (interfaceData.id.replace(/[^a-z0-9]/gi, '-') === interfaceId && container.classList.contains('open')) {
checkOnNextUpdate();
}
};
// Set up a one-time check
setTimeout(() => {
if (container.classList.contains('open') && !charts[interfaceId]) {
checkOnNextUpdate();
}
}, 500);
}
};
// Start attempting to create chart
attemptCreateChart();
}
}
});
});
// Chart toggles are handled by event delegation at document level
};
const createChart = (interfaceId, interfaceData) => {