feat: add nav bar button to publish pending config changes
Shows an orange "Publish Config" button to the left of the alert-pie for admin users when there are staged config changes. Uses localStorage to persist staged changes across page navigations so the button appears on any page, not just settings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -125,6 +125,23 @@
|
||||
.nav-links a { margin-right: 0; padding: 6px 0; font-size: 1em; }
|
||||
}
|
||||
|
||||
/* Pending config publish button */
|
||||
.nav-publish-btn {
|
||||
background: #e65100;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 4px 10px;
|
||||
font-size: 0.82em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
margin-left: auto;
|
||||
}
|
||||
.nav-publish-btn:hover { background: #bf360c; }
|
||||
.nav-publish-btn:disabled { opacity: 0.7; cursor: default; }
|
||||
|
||||
/* Swiss railway clock — nav */
|
||||
.nav-pie {
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
{% endif %}
|
||||
<a href="/about"{% if active_page == "about" %} class="active"{% endif %}>About</a>
|
||||
</div>
|
||||
{% if current_user and current_user.admin %}
|
||||
<button id="nav-publish-btn" class="nav-publish-btn" onclick="navPublishConfig()" style="display:none" title="Publish pending config changes to .hb.yaml">⚠ Publish Config</button>
|
||||
{% endif %}
|
||||
<div class="nav-pie" title="Host alert status">
|
||||
<canvas id="alert-pie" width="44" height="44"></canvas>
|
||||
</div>
|
||||
@@ -92,5 +95,40 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateAlertPie();
|
||||
setInterval(updateAlertPie, 30000);
|
||||
navCheckPendingConfig();
|
||||
window.addEventListener('storage', navCheckPendingConfig);
|
||||
});
|
||||
|
||||
function navCheckPendingConfig() {
|
||||
var btn = document.getElementById('nav-publish-btn');
|
||||
if (!btn) return;
|
||||
btn.style.display = localStorage.getItem('hbd_pending_config') ? '' : 'none';
|
||||
}
|
||||
|
||||
async function navPublishConfig() {
|
||||
var btn = document.getElementById('nav-publish-btn');
|
||||
var pending = localStorage.getItem('hbd_pending_config');
|
||||
if (!pending) return;
|
||||
var staged;
|
||||
try { staged = JSON.parse(pending); } catch(e) { return; }
|
||||
if (btn) { btn.disabled = true; btn.textContent = 'Saving…'; }
|
||||
try {
|
||||
var resp = await fetch('/api/0/config', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: pending
|
||||
});
|
||||
if (resp.ok) {
|
||||
localStorage.removeItem('hbd_pending_config');
|
||||
window.location.reload();
|
||||
} else {
|
||||
var err = await resp.json().catch(function() { return {}; });
|
||||
alert('Error: ' + (err.error || resp.statusText));
|
||||
if (btn) { btn.disabled = false; btn.textContent = '⚠ Publish Config'; }
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Network error: ' + e.message);
|
||||
if (btn) { btn.disabled = false; btn.textContent = '⚠ Publish Config'; }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -871,9 +871,13 @@
|
||||
if (count > 0) {
|
||||
document.getElementById('pending-count').textContent = count;
|
||||
banner.style.display = 'flex';
|
||||
localStorage.setItem('hbd_pending_config', JSON.stringify(_staged));
|
||||
} else {
|
||||
banner.style.display = 'none';
|
||||
localStorage.removeItem('hbd_pending_config');
|
||||
}
|
||||
const navBtn = document.getElementById('nav-publish-btn');
|
||||
if (navBtn) navBtn.style.display = count > 0 ? '' : 'none';
|
||||
}
|
||||
|
||||
function stageFormSection(sectionId, apiSection) {
|
||||
@@ -1050,6 +1054,7 @@
|
||||
|
||||
function discardAll() {
|
||||
Object.keys(_staged).forEach(k => delete _staged[k]);
|
||||
localStorage.removeItem('hbd_pending_config');
|
||||
updatePendingBanner();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user