Merge claude/2

This commit is contained in:
librelad 2026-07-07 21:13:06 +01:00
commit ac07420ef1
6 changed files with 45 additions and 11 deletions

View File

@ -16,9 +16,17 @@ if (typeof window.ConfigManager === 'undefined') {
window.IPWhitelistManager = this.whitelistManager;
}
async renderConfig(category) {
async renderConfig(category, target) {
const configSection = document.getElementById('config-section');
// `target` (an element or an element id) lets an embedder render into its
// OWN container instead of the page-global #config-section. The shared
// apps-unified-layout keeps a hidden #config-tab > #config-section in the
// DOM at all times, so on the Fleet Overview a plain
// getElementById('config-section') resolves to THAT stale element (two
// nodes share the id) and the form paints where nobody can see it. The
// Backups tab's Configuration sub-tab passes its own container id here.
const configSection = (typeof target === 'string' ? document.getElementById(target) : target)
|| document.getElementById('config-section');
if (!configSection) {
console.error('ConfigManager: config-section element not found');
return;
@ -238,7 +246,7 @@ if (typeof window.ConfigManager === 'undefined') {
} catch (error) {
console.error('ConfigManager: Error rendering ' + category + ' config: ', error);
configSection.innerHTML = '<div class="error-container"><h3>Error Loading Configuration</h3><p>Failed to load configuration: ' + error.message + '</p><button type="button" class="btn btn-primary" onclick="window.configManager.renderConfig(\'' + category + '\')">Retry</button></div>';
configSection.innerHTML = '<div class="error-container"><h3>Error Loading Configuration</h3><p>Failed to load configuration: ' + error.message + '</p><button type="button" class="btn btn-primary" onclick="window.configManager.renderConfig(\'' + category + '\', \'' + configSection.id + '\')">Retry</button></div>';
}
}

View File

@ -29,9 +29,14 @@ Object.assign(BackupPage.prototype, {
<div class="config-divider"></div>
`;
// Unique id (NOT the page-global "config-section"): the shared
// apps-unified-layout keeps a hidden #config-tab > #config-section in the
// DOM, so reusing that id would collide and the config form would render
// into the wrong (invisible) node. renderConfig() is told to target this
// container explicitly.
body.innerHTML = `
${warningHTML}
<div id="config-section" class="backup-embedded-config"></div>
<div id="backup-config-section" class="backup-embedded-config"></div>
`;
this.invokeConfigManager();
@ -39,7 +44,7 @@ Object.assign(BackupPage.prototype, {
async invokeConfigManager(attempt = 0) {
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
try {
await window.configManager.renderConfig('backup');
await window.configManager.renderConfig('backup', 'backup-config-section');
this.enhanceConfigurationWithPresets();
} catch (err) {
console.error('Backup configuration render failed:', err);
@ -47,7 +52,7 @@ Object.assign(BackupPage.prototype, {
return;
}
if (attempt >= 20) {
const sec = document.getElementById('config-section');
const sec = document.getElementById('backup-config-section');
if (sec) sec.innerHTML = `<div class="backup-empty-state">Configuration system not loaded. Try refreshing the page.</div>`;
return;
}

View File

@ -2,7 +2,7 @@
Object.assign(BackupPage.prototype, {
enhanceEngineDetailsButton() {
const selector = '[name="CFG_BACKUP_ENGINE"], [name^="CFG_BACKUP_LOC_"][name$="_ENGINE"]';
document.querySelectorAll(`#config-section ${selector}, .backup-location-details ${selector}`).forEach((engineInput) => {
document.querySelectorAll(`#backup-config-section ${selector}, .backup-location-details ${selector}`).forEach((engineInput) => {
const customSelect = engineInput.closest('.custom-select');
const wrapTarget = customSelect || engineInput;
const group = wrapTarget.closest('.field-group') || wrapTarget.parentElement;

View File

@ -2,7 +2,7 @@
Object.assign(BackupPage.prototype, {
enhanceConfigurationWithPresets() {
this.enhanceEngineDetailsButton();
const lastInput = document.querySelector('#config-section [name="CFG_BACKUP_KEEP_LAST"]');
const lastInput = document.querySelector('#backup-config-section [name="CFG_BACKUP_KEEP_LAST"]');
if (!lastInput) return;
const section = lastInput.closest('.config-category');

View File

@ -86,6 +86,19 @@
background: rgba(var(--text-rgb), 0.1);
}
/* Busy state while a manual refresh is in flight spin the arrow icon and
lock the button so the click gives visible feedback (refreshAll() + re-render
is otherwise silent, which read as "the button does nothing"). `spin` is the
global keyframe from core/theme/css/base.css. */
.backup-refresh-btn.is-refreshing {
pointer-events: none;
opacity: 0.75;
}
.backup-refresh-btn.is-refreshing svg {
animation: spin 0.8s linear infinite;
transform-origin: center;
}
.backup-tabpanel {
display: none;
/* Content inset matching the canonical .tab-panel (5px 24px), so panels

View File

@ -118,8 +118,16 @@ class BackupPage {
return;
}
if (e.target.closest('#backup-refresh-btn')) {
this.refreshAll().then(() => this.render());
const refreshBtn = e.target.closest('#backup-refresh-btn');
if (refreshBtn) {
// Give the click visible feedback: spin + lock the button until
// the refetch + re-render settle. Without this the button looks
// dead because refreshAll()/render() paint nothing new on a tab
// whose data hasn't changed.
if (refreshBtn.classList.contains('is-refreshing')) return;
refreshBtn.classList.add('is-refreshing');
const done = () => refreshBtn.classList.remove('is-refreshing');
this.refreshAll().then(() => this.render()).then(done, done);
return;
}