- 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>
125 lines
3.7 KiB
JavaScript
Executable File
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;
|