librelad eaafd1bb38 refactor(webui): relocate admin area into features/admin/ + shared extractions
- features/admin/: the 10 admin-owned config controllers, the 5 admin pages
  (overview/system/charts/metric/storage), ssh-page.js, peers-page.js, plus
  admin.css/ip-whitelist.css/ssh.css (eager). config-manager.js kept last in
  the load order (it news the sub-managers).
- shared/js/: config-shared.js + config-options.js (ConfigShared/ConfigOptions
  globals consumed cross-feature by backup/apps/tasks).
- shared/css/: forms.css + config.css (generic form + config-form primitives
  borrowed by apps/backup/admin).
- Updated all path strings in system-loader.js (config component) and
  config-manager.js (lazyLoad of admin/ssh/peers controllers); index.html CSS
  hrefs. No /js/components/{config,admin,ssh,peers}/ refs remain.

js/components/ now holds only shared UI (topbar, notifications, eo-modal,
update-notifier, mobile-menu, confirmation-dialog).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 02:10:09 +01:00

125 lines
3.7 KiB
JavaScript
Executable File

class ConfigForm {
constructor() {
this.form = null;
this._submitHandlerAttached = false;
}
resetForm() {
this.form = document.getElementById('config-form');
if (this.form) {
this.form.reset();
}
}
snapshotOriginal() {
const original = {};
const data = window.configData && window.configData.config;
if (!data) return original;
Object.entries(data).forEach(([key, entry]) => {
original[key] = entry && entry.value !== undefined ? String(entry.value) : '';
});
return original;
}
collectChanges(original) {
const changes = [];
if (!this.form) return changes;
const current = {};
const inputs = this.form.querySelectorAll('input, select, textarea');
inputs.forEach((input) => {
const name = input.name;
if (!name || !name.startsWith('CFG_')) return;
if (name.endsWith('_PORT_MANAGER')) return; // UI-only aggregate
let value;
if (input.type === 'checkbox') {
value = input.checked ? 'true' : 'false';
} else {
value = (input.value || '').trim();
}
current[name] = value;
});
Object.keys(current).forEach((name) => {
const oldValue = original[name] !== undefined ? original[name] : '';
const newValue = current[name];
if (oldValue === newValue) return;
const encoded = newValue.replace(/\|/g, '%7C');
changes.push(`${name}=${encoded}`);
});
return changes;
}
async saveConfig() {
this.form = document.getElementById('config-form');
if (!this.form) {
console.error('ConfigForm: Form not found');
return;
}
const original = this.snapshotOriginal();
const changes = this.collectChanges(original);
if (changes.length === 0) {
this.showNotification('No configuration changes to save.', 'info');
return;
}
const encoded = changes.join('|');
try {
if (!window.tasksManager || !window.tasksManager.router) {
throw new Error('Task system not available');
}
const task = await window.tasksManager.router.routeAction('config_update', {
changes: `'${encoded.replace(/'/g, "'\\''")}'`
});
this.showNotification(
`Saving ${changes.length} configuration change${changes.length === 1 ? '' : 's'}...`,
'success'
);
if (task && window.librePortalSPA && typeof window.librePortalSPA.navigate === 'function') {
setTimeout(() => window.librePortalSPA.navigate(`/tasks/all?task=${task.id}`), 400);
} else if (task && window.navigateToRoute) {
setTimeout(() => window.navigateToRoute(`tasks/all?task=${task.id}`), 400);
}
} catch (error) {
console.error('ConfigForm: Error saving configuration:', error);
this.showNotification('Failed to save configuration: ' + error.message, 'error');
}
}
// preventDefault stops the form from falling back to GET (which dumps every
// CFG into the URL).
attachSubmitHandler() {
const form = document.getElementById('config-form');
if (!form) return;
if (form.dataset.submitWired === '1') return;
form.dataset.submitWired = '1';
form.addEventListener('submit', (event) => {
event.preventDefault();
this.saveConfig();
});
}
showNotification(message, type) {
type = type || 'info';
if (window.notificationSystem) {
window.notificationSystem.show(message, type);
return;
}
const notification = document.createElement('div');
notification.className = 'notification notification-' + type;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) notification.parentNode.removeChild(notification);
}, 5000);
}
}
window.ConfigForm = ConfigForm;