update UX: badge→modal flow, orange update button, Update All, toast notifications, workflow triggers
This commit is contained in:
@@ -17,8 +17,10 @@
|
||||
|
||||
<!-- Tab: Available Updates -->
|
||||
<div id="updates-available" class="panel-section active">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 8px; align-items: center;">
|
||||
<button id="updates-check-btn" class="btn-accent-solid">🔍 Check for Updates</button>
|
||||
<button id="updates-update-all-btn" style="display: none; padding: 6px 14px; font-size: 0.82rem; background: #f97316; color: #fff; border: 1px solid #f97316; border-radius: 6px; cursor: pointer;">⬆️ Update All</button>
|
||||
<span id="updates-count-badge" style="display: none; padding: 4px 10px; border-radius: 12px; font-size: 0.78rem; font-weight: 600; background: var(--accent); color: var(--bg);"></span>
|
||||
</div>
|
||||
<div id="updates-available-container" style="max-height: 450px; overflow-y: auto;">
|
||||
<div class="panel-empty"><span class="empty-icon">📦</span> Click "Check for Updates" to scan containers.</div>
|
||||
@@ -94,13 +96,24 @@
|
||||
if (updates.length === 0) {
|
||||
availableContainer.innerHTML = '<div class="panel-empty"><span class="empty-icon">✅</span>All containers are up to date.</div>';
|
||||
lastCheckSpan.textContent = '';
|
||||
document.getElementById('updates-update-all-btn').style.display = 'none';
|
||||
document.getElementById('updates-count-badge').style.display = 'none';
|
||||
window._pendingUpdates = [];
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 0.85rem;">';
|
||||
html += '<tr style="border-bottom: 1px solid var(--border); color: var(--muted);"><th style="padding: 8px; text-align: left;">Container</th><th style="padding: 8px; text-align: left;">Image</th><th style="padding: 8px; text-align: left;">Current</th><th style="padding: 8px; text-align: left;">Latest</th><th style="padding: 8px; text-align: right;">Actions</th></tr>';
|
||||
for (const u of updates) {
|
||||
html += `<tr style="border-bottom: 1px solid var(--border);">`;
|
||||
// Match app by containerId first, then name
|
||||
const appId = (() => {
|
||||
const apps = window.APPS || [];
|
||||
for (const a of apps) {
|
||||
if (a.containerId === u.containerId || a.name === u.containerName || a.id === u.containerName) return a.id;
|
||||
}
|
||||
return u.containerName;
|
||||
})();
|
||||
html += `<tr data-app-id="${escapeHtml(appId)}" style="border-bottom: 1px solid var(--border);">`;
|
||||
html += `<td style="padding: 8px; font-weight: 500;">${escapeHtml(u.containerName)}</td>`;
|
||||
html += `<td style="padding: 8px; color: var(--muted);">${escapeHtml(u.imageName)}</td>`;
|
||||
html += `<td style="padding: 8px;"><code style="font-size: 0.78rem; background: var(--bg); padding: 2px 6px; border-radius: 4px;">${escapeHtml(u.currentDigest)}</code></td>`;
|
||||
@@ -114,6 +127,20 @@
|
||||
availableContainer.innerHTML = html;
|
||||
lastCheckSpan.textContent = updates.length + ' update(s) available';
|
||||
|
||||
// Show count badge and Update All button
|
||||
const countBadge = document.getElementById('updates-count-badge');
|
||||
const updateAllBtn = document.getElementById('updates-update-all-btn');
|
||||
if (countBadge) {
|
||||
countBadge.textContent = updates.length + ' pending';
|
||||
countBadge.style.display = '';
|
||||
}
|
||||
if (updateAllBtn && updates.length > 0) {
|
||||
updateAllBtn.style.display = '';
|
||||
}
|
||||
|
||||
// Store updates for Update All button
|
||||
window._pendingUpdates = updates;
|
||||
|
||||
// Wire update buttons
|
||||
availableContainer.querySelectorAll('.update-now-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
@@ -174,6 +201,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Update All — sequentially, skip failures
|
||||
async function updateAllContainers() {
|
||||
const updates = window._pendingUpdates || [];
|
||||
if (!updates.length) return;
|
||||
const btn = document.getElementById('updates-update-all-btn');
|
||||
if (!confirm(`Update all ${updates.length} containers? Each will restart.`)) return;
|
||||
btn.textContent = '⏳ Updating...';
|
||||
btn.disabled = true;
|
||||
let success = 0, failed = 0;
|
||||
for (const u of updates) {
|
||||
try {
|
||||
const r = await secureFetch(`/api/v1/updates/update/${encodeURIComponent(u.containerId)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ autoRollback: true })
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.success) success++;
|
||||
else failed++;
|
||||
} catch (_) { failed++; }
|
||||
}
|
||||
btn.textContent = `✅ Done`;
|
||||
showNotification(`Update all: ${success} succeeded, ${failed} failed.`, success > 0 && failed === 0 ? 'success' : 'error');
|
||||
setTimeout(() => {
|
||||
btn.textContent = '⬆️ Update All';
|
||||
btn.disabled = false;
|
||||
loadAvailable();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
document.getElementById('updates-update-all-btn')?.addEventListener('click', updateAllContainers);
|
||||
|
||||
async function checkForUpdates() {
|
||||
checkBtn.textContent = '🔍 Checking...';
|
||||
checkBtn.disabled = true;
|
||||
@@ -499,6 +558,21 @@
|
||||
});
|
||||
wireModal(modal, cancelBtn);
|
||||
|
||||
// Open Update Management modal, optionally scrolled to a specific app
|
||||
window.openUpdateModal = function(appId) {
|
||||
modal?.classList.add('show');
|
||||
loadAvailable().then(() => {
|
||||
if (!appId) return;
|
||||
// Scroll to and highlight the matching row
|
||||
const row = availableContainer.querySelector(`[data-app-id="${appId}"]`);
|
||||
if (row) {
|
||||
row.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
row.style.background = 'rgba(249,115,22,0.15)';
|
||||
setTimeout(() => { row.style.background = ''; }, 3000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Lazy-load tabs
|
||||
document.querySelector('[data-panel="updates-history"]')?.addEventListener('click', loadHistory);
|
||||
document.querySelector('[data-panel="updates-auto"]')?.addEventListener('click', loadAutoConfig);
|
||||
|
||||
Reference in New Issue
Block a user