102 lines
5.3 KiB
JavaScript
102 lines
5.3 KiB
JavaScript
/* Shared configuration form. Persistence is supplied by the desktop/web adapter. */
|
|
const ConfigUI = (() => {
|
|
let original, baseline = '', saving = false;
|
|
const form = () => document.getElementById('config-form');
|
|
const values = () => Object.fromEntries(Object.keys(ConfiguracionModelo.fields).map(id => [id, document.getElementById(id)?.value ?? '']));
|
|
const snapshot = () => JSON.stringify(values());
|
|
const dirty = () => !!form() && baseline !== snapshot();
|
|
function message(text, error = false) {
|
|
const status = document.getElementById('config-status');
|
|
if (status) { status.textContent = text; status.className = error ? 'text-danger' : 'text-white'; }
|
|
}
|
|
function canLeave() {
|
|
if (saving) { message('Esperá a que termine el guardado.'); return false; }
|
|
if (!dirty()) return true;
|
|
if (!window.confirm('Tenés cambios sin guardar. ¿Querés descartarlos y salir?')) return false;
|
|
baseline = snapshot();
|
|
return true;
|
|
}
|
|
function visibility() {
|
|
const v = values();
|
|
for (const [id, visible] of Object.entries({bloque_secundario: v.Modo !== 'Recolector_USB', LlaveR: v.Modo === 'Visor', bloque_por_IP_WIFI: v.Modo === 'Recolector_IP', vocesAudioDiv: v.audio === 'Activado', Server: v.Tipo_SMTP === 'Manual', puerto: v.Tipo_SMTP === 'Manual'})) {
|
|
const element = document.getElementById(id);
|
|
if (element) element.hidden = !visible;
|
|
}
|
|
for (const id of ['Usuario_SMTP', 'Contrasenia_SMTP']) document.getElementById(id).disabled = v.Tipo_SMTP === 'Deshabilitado' || saving;
|
|
for (const id of ['Server_SMTP', 'Puerto_SMTP']) document.getElementById(id).disabled = v.Tipo_SMTP !== 'Manual' || saving;
|
|
}
|
|
function errors(fields) {
|
|
for (const id of Object.keys(ConfiguracionModelo.fields)) {
|
|
const element = document.getElementById(id);
|
|
if (!element) continue;
|
|
element.classList.toggle('is-invalid', !!fields[id]);
|
|
element.setAttribute('aria-invalid', String(!!fields[id]));
|
|
document.getElementById(id + '-error').textContent = fields[id] || '';
|
|
}
|
|
const first = Object.keys(fields)[0];
|
|
if (first) document.getElementById(first)?.focus();
|
|
}
|
|
function mount(config, interfaces = {}, voices = []) {
|
|
original = config;
|
|
const f = form();
|
|
if (!f) return;
|
|
const selected = ConfiguracionModelo.read(config);
|
|
const ip = document.getElementById('r_ip');
|
|
ip.replaceChildren();
|
|
const addresses = new Set(Object.values(interfaces).flat().filter(i => i.family === 'IPv4' && !i.internal).map(i => i.address));
|
|
if (selected.r_ip && selected.r_ip !== 'false') addresses.add(selected.r_ip);
|
|
for (const value of addresses) ip.add(new Option(value, value));
|
|
if (!ip.options.length) ip.add(new Option('Sin interfaces disponibles', ''));
|
|
const voice = document.getElementById('vocesAudio');
|
|
voice.replaceChildren();
|
|
const names = new Set(voices.map(v => v.name));
|
|
if (selected.vocesAudio) names.add(selected.vocesAudio);
|
|
for (const value of names) voice.add(new Option(value, value));
|
|
if (!voice.options.length) voice.add(new Option('Sin voces disponibles', ''));
|
|
for (const [id, value] of Object.entries(selected)) {
|
|
const element = document.getElementById(id);
|
|
if (element.tagName === 'SELECT' && value && ![...element.options].some(o => o.value === value)) element.add(new Option(value, value));
|
|
element.value = value;
|
|
element.setAttribute('aria-describedby', id + '-error');
|
|
if (!document.getElementById(id + '-error')) {
|
|
const feedback = document.createElement('div');
|
|
feedback.id = id + '-error'; feedback.className = 'invalid-feedback'; element.after(feedback);
|
|
}
|
|
}
|
|
visibility(); baseline = snapshot(); errors({}); message('Sin cambios pendientes.');
|
|
f.oninput = f.onchange = () => { visibility(); message(dirty() ? 'Cambios sin guardar.' : 'Sin cambios pendientes.'); };
|
|
f.onsubmit = event => { event.preventDefault(); GuardarConf('conf'); };
|
|
}
|
|
async function save(persist) {
|
|
if (saving || !form()) return false;
|
|
const data = values();
|
|
const invalid = ConfiguracionModelo.validate(data);
|
|
errors(invalid);
|
|
if (Object.keys(invalid).length) { message('Revisá los campos marcados.', true); return false; }
|
|
const draft = ConfiguracionModelo.build(original, data);
|
|
saving = true;
|
|
const controls = [...form().querySelectorAll('input, select, button')];
|
|
const disabled = controls.map(e => e.disabled);
|
|
controls.forEach(e => { e.disabled = true; });
|
|
message('Guardando…');
|
|
try {
|
|
await persist(draft);
|
|
original = draft; baseline = snapshot(); message('Configuración guardada.');
|
|
return draft;
|
|
} catch (error) {
|
|
if (error.fields) errors(error.fields);
|
|
message(error.message || 'No se pudo guardar. Tus cambios siguen en el formulario.', true);
|
|
return false;
|
|
} finally {
|
|
saving = false;
|
|
controls.forEach((e, i) => { e.disabled = disabled[i]; });
|
|
visibility();
|
|
}
|
|
}
|
|
window.addEventListener('click', event => {
|
|
if (event.target.closest('[data-bs-toggle="tab"], #Wclo') && !canLeave()) { event.preventDefault(); event.stopImmediatePropagation(); }
|
|
}, true);
|
|
window.addEventListener('beforeunload', event => { if (dirty() || saving) { event.preventDefault(); event.returnValue = ''; } });
|
|
return {mount, save, dirty, canLeave, visibility};
|
|
})();
|