Update panel: always show version status (up-to-date or update available)
Replace dismissable toast with persistent status row showing a green dot + "Up to date" with commit hash, or amber dot + "Update available" with an inline Install button. One or the other is always visible. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
eb1aa9d434
commit
436a009059
2 changed files with 65 additions and 60 deletions
|
|
@ -213,74 +213,72 @@
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update toast */
|
/* Update status */
|
||||||
.panel-updates {
|
.panel-updates {
|
||||||
transition: all var(--transition-base);
|
transition: all var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-toast {
|
.update-status {
|
||||||
position: relative;
|
|
||||||
padding: 12px;
|
|
||||||
background: var(--accent-dim);
|
|
||||||
border-left: 3px solid var(--accent);
|
|
||||||
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-dismiss {
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
right: 4px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-muted);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 10px;
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-dismiss:hover {
|
.update-status-dot {
|
||||||
background: var(--accent-hover);
|
width: 8px;
|
||||||
color: var(--text-primary);
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-toast-title {
|
.update-status-dot.current {
|
||||||
|
background: var(--status-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-status-dot.available {
|
||||||
|
background: var(--status-amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-status-title {
|
||||||
font-family: var(--font-ui);
|
font-family: var(--font-ui);
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--accent);
|
color: var(--text-primary);
|
||||||
margin-bottom: 4px;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-toast-info {
|
.update-status-hash {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-muted);
|
||||||
margin-bottom: 10px;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-toast-btn {
|
.update-status-text {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
height: 30px;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-install-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 26px;
|
||||||
|
padding: 0 12px;
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: var(--font-ui);
|
font-family: var(--font-ui);
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-toast-btn:hover {
|
.update-install-btn:hover {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-toast-btn:disabled {
|
.update-install-btn:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -391,42 +391,50 @@
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Panel — Update checker
|
// Panel — Update checker
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
let updateDismissed = false;
|
|
||||||
|
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
if (updateDismissed) return;
|
const panel = $('#panelUpdates');
|
||||||
|
if (!panel) return;
|
||||||
try {
|
try {
|
||||||
const res = await Atlus.apiFetch('/api/updates/check');
|
const res = await Atlus.apiFetch('/api/updates/check');
|
||||||
if (!res || !res.ok) return;
|
if (!res || !res.ok) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const panel = $('#panelUpdates');
|
|
||||||
if (!panel) return;
|
panel.classList.remove('hidden');
|
||||||
|
|
||||||
if (data.available && data.behind_count > 0) {
|
if (data.available && data.behind_count > 0) {
|
||||||
showUpdateToast(panel, data);
|
showUpdateAvailable(panel, data);
|
||||||
} else {
|
} else {
|
||||||
panel.classList.add('hidden');
|
showUpToDate(panel, data);
|
||||||
}
|
}
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
function showUpdateToast(panel, data) {
|
function showUpToDate(panel, data) {
|
||||||
panel.classList.remove('hidden');
|
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="update-toast">
|
<div class="update-status update-current">
|
||||||
<button class="update-dismiss" title="Dismiss">×</button>
|
<span class="update-status-dot current"></span>
|
||||||
<div class="update-toast-title">Update available</div>
|
<div class="update-status-text">
|
||||||
<div class="update-toast-info">${data.behind_count} commit${data.behind_count !== 1 ? 's' : ''} behind (${data.remote_hash})</div>
|
<div class="update-status-title">Up to date</div>
|
||||||
<button class="update-toast-btn">Install Update</button>
|
<div class="update-status-hash">${data.local_hash}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUpdateAvailable(panel, data) {
|
||||||
|
panel.innerHTML = `
|
||||||
|
<div class="update-status update-available">
|
||||||
|
<span class="update-status-dot available"></span>
|
||||||
|
<div class="update-status-text">
|
||||||
|
<div class="update-status-title">Update available</div>
|
||||||
|
<div class="update-status-hash">${data.behind_count} commit${data.behind_count !== 1 ? 's' : ''} behind (${data.remote_hash})</div>
|
||||||
|
</div>
|
||||||
|
<button class="update-install-btn" title="Install update">Update</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
panel.querySelector('.update-dismiss').addEventListener('click', () => {
|
panel.querySelector('.update-install-btn').addEventListener('click', async (e) => {
|
||||||
panel.classList.add('hidden');
|
|
||||||
updateDismissed = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
panel.querySelector('.update-toast-btn').addEventListener('click', async (e) => {
|
|
||||||
const btn = e.target;
|
const btn = e.target;
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = 'Updating…';
|
btn.textContent = 'Updating…';
|
||||||
|
|
@ -435,10 +443,9 @@
|
||||||
const res = await Atlus.apiFetch('/api/updates/apply', { method: 'POST' });
|
const res = await Atlus.apiFetch('/api/updates/apply', { method: 'POST' });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
btn.textContent = 'Restarting…';
|
btn.textContent = 'Restarting…';
|
||||||
// Server will restart — try to reload after a delay
|
|
||||||
setTimeout(() => attemptReload(0), 4000);
|
setTimeout(() => attemptReload(0), 4000);
|
||||||
} else {
|
} else {
|
||||||
btn.textContent = 'Update failed';
|
btn.textContent = 'Failed';
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue