- Service Filter Bar: search by name, filter by status (online/offline) - Batch Operations: multi-select containers for start/stop/restart - Container Snapshots: create and manage Docker checkpoints - Added filter bar and batch action bar to index.html - Added snapshot button to Admin tools section - New JS modules: service-filter.js, batch-operations.js, snapshot.js - Updated build.js to include new modules in bundle
139 lines
4.6 KiB
JavaScript
139 lines
4.6 KiB
JavaScript
// ========== BATCH CONTAINER OPERATIONS ==========
|
|
(function() {
|
|
const batchBtn = document.getElementById('batch-operations-btn');
|
|
const batchBar = document.getElementById('batch-action-bar');
|
|
const batchCount = document.getElementById('batch-selected-count');
|
|
const startBtn = document.getElementById('batch-start-btn');
|
|
const stopBtn = document.getElementById('batch-stop-btn');
|
|
const restartBtn = document.getElementById('batch-restart-btn');
|
|
const cancelBtn = document.getElementById('batch-cancel-btn');
|
|
|
|
let batchMode = false;
|
|
let selectedContainers = new Set();
|
|
|
|
function enterBatchMode() {
|
|
batchMode = true;
|
|
selectedContainers.clear();
|
|
batchBar.style.display = '';
|
|
batchBtn.textContent = '✓ Exit Batch Mode';
|
|
updateSelectedCount();
|
|
|
|
// Add checkboxes to all cards with containerId
|
|
const cards = document.querySelectorAll('#cards .card[data-app]');
|
|
cards.forEach(card => {
|
|
const containerId = card.dataset.containerId;
|
|
if (!containerId) return;
|
|
|
|
// Remove existing checkbox if any
|
|
const existing = card.querySelector('.batch-checkbox');
|
|
if (existing) existing.remove();
|
|
|
|
const checkbox = document.createElement('input');
|
|
checkbox.type = 'checkbox';
|
|
checkbox.className = 'batch-checkbox';
|
|
checkbox.dataset.containerId = containerId;
|
|
checkbox.dataset.serviceName = card.querySelector('.name')?.textContent || containerId;
|
|
checkbox.style.cssText = 'position: absolute; top: 8px; left: 8px; z-index: 10; width: 18px; height: 18px; cursor: pointer;';
|
|
checkbox.addEventListener('change', (e) => {
|
|
e.stopPropagation();
|
|
if (checkbox.checked) {
|
|
selectedContainers.add(containerId);
|
|
} else {
|
|
selectedContainers.delete(containerId);
|
|
}
|
|
updateSelectedCount();
|
|
});
|
|
card.style.position = 'relative';
|
|
card.insertBefore(checkbox, card.firstChild);
|
|
});
|
|
}
|
|
|
|
function exitBatchMode() {
|
|
batchMode = false;
|
|
selectedContainers.clear();
|
|
batchBar.style.display = 'none';
|
|
batchBtn.textContent = '☰ Batch Operations';
|
|
|
|
// Remove all checkboxes
|
|
document.querySelectorAll('.batch-checkbox').forEach(cb => cb.remove());
|
|
}
|
|
|
|
function updateSelectedCount() {
|
|
const count = selectedContainers.size;
|
|
batchCount.textContent = `${count} selected`;
|
|
startBtn.disabled = count === 0;
|
|
stopBtn.disabled = count === 0;
|
|
restartBtn.disabled = count === 0;
|
|
}
|
|
|
|
async function batchAction(action) {
|
|
if (selectedContainers.size === 0) return;
|
|
|
|
const containers = Array.from(selectedContainers);
|
|
const actionLabel = { start: 'Starting', stop: 'Stopping', restart: 'Restarting' }[action];
|
|
|
|
if (!confirm(`${actionLabel} ${containers.length} container(s)? This cannot be undone.`)) return;
|
|
|
|
const btns = [startBtn, stopBtn, restartBtn];
|
|
btns.forEach(b => { b.disabled = true; b.textContent = '...'; });
|
|
|
|
let success = 0;
|
|
let failed = 0;
|
|
const errors = [];
|
|
|
|
for (const containerId of containers) {
|
|
try {
|
|
const res = await fetch(`/api/v1/containers/${encodeURIComponent(containerId)}/${action}`, {
|
|
method: 'POST'
|
|
});
|
|
if (res.ok) {
|
|
success++;
|
|
} else {
|
|
failed++;
|
|
const data = await res.json().catch(() => ({}));
|
|
errors.push(`${containerId}: ${data.error || res.statusText}`);
|
|
}
|
|
} catch (e) {
|
|
failed++;
|
|
errors.push(`${containerId}: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
// Restore buttons
|
|
btns[0].textContent = '▶ Start All';
|
|
btns[1].textContent = '⬛ Stop All';
|
|
btns[2].textContent = '🔄 Restart All';
|
|
updateSelectedCount();
|
|
|
|
// Show results
|
|
if (failed === 0) {
|
|
if (typeof showNotification === 'function') {
|
|
showNotification(`${actionLabel} completed: ${success} container(s)`, 'success');
|
|
}
|
|
} else {
|
|
if (typeof showNotification === 'function') {
|
|
showNotification(`${actionLabel}: ${success} succeeded, ${failed} failed`, 'warning');
|
|
}
|
|
console.error('Batch operation errors:', errors);
|
|
}
|
|
|
|
// Refresh dashboard after a short delay
|
|
setTimeout(() => {
|
|
if (typeof refreshAll === 'function') refreshAll();
|
|
}, 1500);
|
|
}
|
|
|
|
batchBtn?.addEventListener('click', () => {
|
|
if (batchMode) {
|
|
exitBatchMode();
|
|
} else {
|
|
enterBatchMode();
|
|
}
|
|
});
|
|
|
|
startBtn?.addEventListener('click', () => batchAction('start'));
|
|
stopBtn?.addEventListener('click', () => batchAction('stop'));
|
|
restartBtn?.addEventListener('click', () => batchAction('restart'));
|
|
cancelBtn?.addEventListener('click', exitBatchMode);
|
|
})();
|