feat(update): add release policy checks and dashboard version verification

This commit is contained in:
Krystie
2026-05-04 18:05:00 -07:00
parent 0c658a26a8
commit f5fe32b999
3 changed files with 413 additions and 23 deletions

View File

@@ -167,11 +167,103 @@ button:focus-visible {
right: 0;
top: 0;
padding-top: 10px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
}
.reload-caddy-main {
display: flex;
align-items: center;
gap: 18px;
}
.dashboard-version {
font-size: 0.78rem;
color: var(--muted);
line-height: 1;
padding: 0 2px;
user-select: text;
background: transparent;
border: none;
cursor: pointer;
transition: color 0.15s ease, opacity 0.15s ease;
}
.dashboard-version:hover {
color: var(--fg);
opacity: 0.9;
}
.version-info-modal-content {
min-width: 420px;
max-width: 620px;
}
.version-info-subtitle {
font-size: 0.85rem;
color: var(--muted);
margin: 0 0 16px;
}
.version-info-status {
font-size: 0.85rem;
color: var(--muted);
margin-bottom: 14px;
}
.version-info-grid {
display: grid;
gap: 10px;
margin-bottom: 18px;
}
.version-info-row {
display: flex;
justify-content: space-between;
gap: 18px;
padding: 10px 12px;
border-radius: 8px;
background: var(--card-bg);
border: 1px solid var(--border);
}
.version-info-label {
font-weight: 600;
color: var(--fg);
}
.version-info-value {
color: var(--muted);
text-align: right;
word-break: break-word;
}
.version-info-history h4 {
margin: 0 0 10px;
}
.version-history-entry {
padding: 10px 12px;
border-radius: 8px;
background: var(--card-bg);
border: 1px solid var(--border);
margin-bottom: 8px;
}
.version-history-status {
color: var(--muted);
font-size: 0.82rem;
margin-left: 6px;
}
.version-history-meta {
font-size: 0.78rem;
color: var(--muted);
margin-top: 4px;
}
.license-status-topbar {
display: flex;
align-items: center;

View File

@@ -83,20 +83,36 @@
<!-- License status + Reload Caddy Button - top right corner -->
<div class="reload-caddy-container">
<div class="theme-toggle-group">
<button id="theme" class="theme-toggle-btn" aria-label="Cycle theme" title="Click to cycle themes">
<span id="theme-icon">🎨</span> <span id="theme-label">Dark</span>
<div class="reload-caddy-main">
<div class="theme-toggle-group">
<button id="theme" class="theme-toggle-btn" aria-label="Cycle theme" title="Click to cycle themes">
<span id="theme-icon">🎨</span> <span id="theme-label">Dark</span>
</button>
<button id="theme-customize-btn" class="theme-customize-link" title="Customize theme colors">Customize Theme</button>
</div>
<div id="license-status-topbar" class="license-status-topbar free" title="Click to manage license">
<span id="license-topbar-icon">&#9734;</span>
<span id="license-topbar-text">FREE TIER</span>
<span id="license-topbar-time"></span>
</div>
<button id="reload-caddy-top" aria-label="Reload Caddy configuration" style="padding: 10px 20px; font-size: 0.95rem; font-weight: 600; background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); border: none; border-radius: 6px; color: white; cursor: pointer; box-shadow: 0 2px 6px rgba(52, 152, 219, 0.3); transition: all 0.2s ease;">
🔄 Reload Caddy
</button>
<button id="theme-customize-btn" class="theme-customize-link" title="Customize theme colors">Customize Theme</button>
</div>
<div id="license-status-topbar" class="license-status-topbar free" title="Click to manage license">
<span id="license-topbar-icon">&#9734;</span>
<span id="license-topbar-text">FREE TIER</span>
<span id="license-topbar-time"></span>
<button id="dashboard-version" class="dashboard-version" type="button" title="View DashCaddy verification info" aria-label="View DashCaddy verification info">Version —</button>
</div>
</div>
<div id="version-info-modal" class="weather-modal">
<div class="weather-modal-content version-info-modal-content">
<h3>DashCaddy Verification Info</h3>
<p class="version-info-subtitle">Current version details and updater verification status.</p>
<div id="version-info-status" class="version-info-status">Loading…</div>
<div id="version-info-grid" class="version-info-grid" style="display:none;"></div>
<div id="version-info-history" class="version-info-history" style="display:none;"></div>
<div class="weather-modal-buttons" style="margin-top: 16px;">
<button id="version-info-close">Close</button>
</div>
<button id="reload-caddy-top" aria-label="Reload Caddy configuration" style="padding: 10px 20px; font-size: 0.95rem; font-weight: 600; background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); border: none; border-radius: 6px; color: white; cursor: pointer; box-shadow: 0 2px 6px rgba(52, 152, 219, 0.3); transition: all 0.2s ease;">
🔄 Reload Caddy
</button>
</div>
</div>
@@ -538,6 +554,117 @@
}
var yr = document.getElementById('footer-year');
if (yr) yr.textContent = new Date().getFullYear();
var versionEl = document.getElementById('dashboard-version');
var versionInfoModal = document.getElementById('version-info-modal');
var versionInfoStatus = document.getElementById('version-info-status');
var versionInfoGrid = document.getElementById('version-info-grid');
var versionInfoHistory = document.getElementById('version-info-history');
var versionInfoClose = document.getElementById('version-info-close');
function formatValue(value) {
if (value == null || value === '') return '—';
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
}
function renderInfoRow(label, value) {
return '<div class="version-info-row"><span class="version-info-label">' + label + '</span><span class="version-info-value">' + formatValue(value) + '</span></div>';
}
function renderHistory(history) {
if (!Array.isArray(history) || !history.length) {
versionInfoHistory.style.display = 'none';
versionInfoHistory.innerHTML = '';
return;
}
versionInfoHistory.style.display = '';
versionInfoHistory.innerHTML = '<h4>Recent Update History</h4>' + history.slice(0, 5).map(function(entry) {
return '<div class="version-history-entry">'
+ '<div><strong>' + formatValue(entry.version) + '</strong> <span class="version-history-status">' + formatValue(entry.status) + '</span></div>'
+ '<div class="version-history-meta">From ' + formatValue(entry.fromVersion) + ' · ' + formatValue(entry.timestamp) + '</div>'
+ '</div>';
}).join('');
}
function openVersionInfo() {
if (!versionInfoModal) return;
versionInfoModal.classList.add('show');
versionInfoStatus.textContent = 'Loading…';
versionInfoGrid.style.display = 'none';
versionInfoHistory.style.display = 'none';
versionInfoGrid.innerHTML = '';
versionInfoHistory.innerHTML = '';
Promise.all([
fetch('/api/v1/system/version', { cache: 'no-store' }).then(function(response) {
if (!response.ok) throw new Error('Version check failed');
return response.json();
}),
fetch('/api/v1/system/update-status', { cache: 'no-store' }).then(function(response) {
if (!response.ok) throw new Error('Update status failed');
return response.json();
}),
fetch('/api/v1/system/update-history', { cache: 'no-store' }).then(function(response) {
if (!response.ok) throw new Error('Update history failed');
return response.json();
})
]).then(function(results) {
var versionData = results[0] || {};
var statusData = results[1] || {};
var historyData = results[2] || {};
var lastResult = statusData.lastResult || {};
var lastPolicy = lastResult.policy || {};
versionInfoStatus.textContent = 'Verification info loaded.';
versionInfoGrid.style.display = 'grid';
versionInfoGrid.innerHTML = [
renderInfoRow('Version', versionData.version),
renderInfoRow('Commit', versionData.commit),
renderInfoRow('Updater Status', statusData.status),
renderInfoRow('Last Check', statusData.lastCheck ? new Date(statusData.lastCheck).toLocaleString() : 'Never'),
renderInfoRow('Update Available', lastResult.available),
renderInfoRow('Eligible', lastPolicy.eligible),
renderInfoRow('Policy Reason', lastPolicy.reason),
renderInfoRow('Channel', lastPolicy.releaseChannel || (lastResult.instance && lastResult.instance.channel)),
renderInfoRow('Instance ID', lastResult.instance && lastResult.instance.instanceId)
].join('');
renderHistory(historyData.history);
}).catch(function(error) {
versionInfoStatus.textContent = 'Could not load verification info: ' + error.message;
});
}
if (versionEl) {
fetch('/api/v1/system/version', { cache: 'no-store' })
.then(function(response) {
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json();
})
.then(function(data) {
if (data && data.success && data.version) {
versionEl.textContent = 'Version ' + data.version;
}
})
.catch(function() {
versionEl.textContent = 'Version unavailable';
});
versionEl.addEventListener('click', openVersionInfo);
}
if (versionInfoClose && versionInfoModal) {
versionInfoClose.addEventListener('click', function() {
versionInfoModal.classList.remove('show');
});
versionInfoModal.addEventListener('click', function(event) {
if (event.target === versionInfoModal) {
versionInfoModal.classList.remove('show');
}
});
}
})();
</script>