225 lines
9.3 KiB
JavaScript
225 lines
9.3 KiB
JavaScript
// UPS Configuration page (uses apiFetch for CSRF)
|
|
|
|
function el(tag, attrs, ...children) {
|
|
const e = document.createElement(tag);
|
|
if (attrs) Object.entries(attrs).forEach(([k, v]) => {
|
|
if (k === 'class') e.className = v;
|
|
else if (k === 'dataset') Object.assign(e.dataset, v);
|
|
else if (k.startsWith('on') && typeof v === 'function') e.addEventListener(k.slice(2), v);
|
|
else if (v !== null && v !== undefined) e.setAttribute(k, v);
|
|
});
|
|
children.forEach(c => {
|
|
if (c == null) return;
|
|
e.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
|
|
});
|
|
return e;
|
|
}
|
|
|
|
class ConfigManager {
|
|
constructor() { this.confirmCallback = null; this.init(); }
|
|
|
|
init() { this.bindEvents(); this.loadUPSList(); }
|
|
|
|
bindEvents() {
|
|
document.getElementById('add-ups-btn').addEventListener('click', () => this.showUPSModal());
|
|
document.querySelectorAll('.close').forEach(close => {
|
|
close.addEventListener('click', (e) => this.closeModal(e.target.closest('.modal')));
|
|
});
|
|
document.getElementById('ups-form').addEventListener('submit', (e) => {
|
|
e.preventDefault(); this.saveUPS();
|
|
});
|
|
document.getElementById('test-connection-btn').addEventListener('click', () => this.testConnection());
|
|
document.getElementById('cancel-btn').addEventListener('click', () => {
|
|
this.closeModal(document.getElementById('ups-modal'));
|
|
});
|
|
document.getElementById('confirm-yes').addEventListener('click', () => {
|
|
if (this.confirmCallback) this.confirmCallback();
|
|
this.closeModal(document.getElementById('confirm-modal'));
|
|
});
|
|
document.getElementById('confirm-no').addEventListener('click', () => {
|
|
this.closeModal(document.getElementById('confirm-modal'));
|
|
});
|
|
window.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('modal')) this.closeModal(e.target);
|
|
});
|
|
}
|
|
|
|
async loadUPSList() {
|
|
try {
|
|
const resp = await window.apiFetch('/api/config/ups');
|
|
const list = await resp.json();
|
|
if (!resp.ok) throw new Error(list.detail || 'Failed to load');
|
|
this.renderUPSList(list);
|
|
} catch (err) { this.showToast('Error: ' + err.message, 'error'); }
|
|
}
|
|
|
|
renderUPSList(list) {
|
|
const container = document.getElementById('ups-list');
|
|
container.textContent = '';
|
|
if (list.length === 0) {
|
|
const empty = el('div', { class: 'empty-state' },
|
|
el('h3', null, 'No UPS Configured'),
|
|
el('p', null, 'Click "Add UPS" to configure your first UPS.'));
|
|
container.appendChild(empty);
|
|
return;
|
|
}
|
|
list.forEach(ups => container.appendChild(this.renderUPSItem(ups)));
|
|
}
|
|
|
|
renderUPSItem(ups) {
|
|
const alerts = [];
|
|
if (ups.alert_loadpct_high) alerts.push('Load: ' + ups.alert_loadpct_high + '%');
|
|
if (ups.alert_bcharge_low) alerts.push('Battery: ' + ups.alert_bcharge_low + '%');
|
|
if (ups.alert_on_battery) alerts.push('On Battery');
|
|
if (ups.alert_runtime_low_minutes) alerts.push('Runtime: ' + ups.alert_runtime_low_minutes + 'min');
|
|
if (ups.alert_itemp_high) alerts.push('Temp: ' + ups.alert_itemp_high + '°C');
|
|
const testBtn = el('button', { class: 'btn btn-secondary', onclick: () => this.testUPSConnection(ups.name) }, 'Test');
|
|
const editBtn = el('button', { class: 'btn btn-primary', onclick: () => this.editUPS(ups.name) }, 'Edit');
|
|
const delBtn = el('button', { class: 'btn btn-danger', onclick: () => this.confirmDelete(ups.name) }, 'Delete');
|
|
const item = el('div', { class: 'ups-item' },
|
|
el('div', { class: 'ups-item-header' },
|
|
el('div', { class: 'ups-item-title' }, ups.name),
|
|
el('div', { class: 'ups-item-actions' }, testBtn, editBtn, delBtn)),
|
|
el('div', { class: 'ups-item-details' },
|
|
el('div', { class: 'ups-detail' },
|
|
el('div', { class: 'ups-detail-label' }, 'Host'),
|
|
el('div', { class: 'ups-detail-value' }, ups.host + ':' + ups.port)),
|
|
el('div', { class: 'ups-detail' },
|
|
el('div', { class: 'ups-detail-label' }, 'Polling Interval'),
|
|
el('div', { class: 'ups-detail-value' }, ups.interval_seconds + 's')),
|
|
el('div', { class: 'ups-detail' },
|
|
el('div', { class: 'ups-detail-label' }, 'Alerts'),
|
|
el('div', { class: 'ups-detail-value' }, alerts.length ? alerts.join(', ') : 'None'))));
|
|
return item;
|
|
}
|
|
|
|
showUPSModal(ups = null) {
|
|
const modal = document.getElementById('ups-modal');
|
|
const title = document.getElementById('modal-title');
|
|
const form = document.getElementById('ups-form');
|
|
form.reset();
|
|
if (ups) {
|
|
title.textContent = 'Edit UPS';
|
|
this.populateForm(ups);
|
|
form.dataset.mode = 'edit';
|
|
form.dataset.upsName = ups.name;
|
|
} else {
|
|
title.textContent = 'Add UPS';
|
|
form.dataset.mode = 'add';
|
|
delete form.dataset.upsName;
|
|
}
|
|
modal.style.display = 'block';
|
|
}
|
|
|
|
populateForm(ups) {
|
|
document.getElementById('ups-name').value = ups.name || '';
|
|
document.getElementById('ups-host').value = ups.host || '';
|
|
document.getElementById('ups-port').value = ups.port || 3551;
|
|
document.getElementById('ups-interval').value = ups.interval_seconds || 30;
|
|
document.getElementById('ups-loadpct').value = ups.alert_loadpct_high || '';
|
|
document.getElementById('ups-bcharge').value = ups.alert_bcharge_low || '';
|
|
document.getElementById('ups-onbattery').checked = !!ups.alert_on_battery;
|
|
document.getElementById('ups-runtime').value = ups.alert_runtime_low_minutes || '';
|
|
const itemp = document.getElementById('ups-itemp');
|
|
if (itemp) itemp.value = ups.alert_itemp_high || '';
|
|
}
|
|
|
|
closeModal(modal) { modal.style.display = 'none'; }
|
|
|
|
async saveUPS() {
|
|
const form = document.getElementById('ups-form');
|
|
const fd = new FormData(form);
|
|
const mode = form.dataset.mode;
|
|
const name = form.dataset.upsName;
|
|
const data = {
|
|
name: fd.get('name'),
|
|
host: fd.get('host'),
|
|
port: parseInt(fd.get('port')) || 3551,
|
|
interval_seconds: parseInt(fd.get('interval_seconds')) || 30,
|
|
alert_on_battery: fd.has('alert_on_battery'),
|
|
};
|
|
const num = (k) => { const v = fd.get(k); if (v) data[k] = parseFloat(v); };
|
|
num('alert_loadpct_high'); num('alert_bcharge_low');
|
|
num('alert_runtime_low_minutes'); num('alert_itemp_high');
|
|
try {
|
|
const url = mode === 'edit' ? '/api/config/ups/' + name : '/api/config/ups';
|
|
const method = mode === 'edit' ? 'PUT' : 'POST';
|
|
const resp = await window.apiFetch(url, { method, body: JSON.stringify(data) });
|
|
const result = await resp.json();
|
|
if (!resp.ok) throw new Error(result.detail || 'Save failed');
|
|
this.showToast(result.message, 'success');
|
|
this.closeModal(document.getElementById('ups-modal'));
|
|
this.loadUPSList();
|
|
} catch (err) { this.showToast('Error: ' + err.message, 'error'); }
|
|
}
|
|
|
|
async editUPS(name) {
|
|
try {
|
|
const resp = await window.apiFetch('/api/config/ups/' + name);
|
|
const ups = await resp.json();
|
|
if (!resp.ok) throw new Error(ups.detail || 'Load failed');
|
|
this.showUPSModal(ups);
|
|
} catch (err) { this.showToast('Error: ' + err.message, 'error'); }
|
|
}
|
|
|
|
confirmDelete(name) {
|
|
document.getElementById('confirm-message').textContent =
|
|
'Delete UPS "' + name + '"? This cannot be undone.';
|
|
this.confirmCallback = () => this.deleteUPS(name);
|
|
document.getElementById('confirm-modal').style.display = 'block';
|
|
}
|
|
|
|
async deleteUPS(name) {
|
|
try {
|
|
const resp = await window.apiFetch('/api/config/ups/' + name, { method: 'DELETE' });
|
|
const result = await resp.json();
|
|
if (!resp.ok) throw new Error(result.detail || 'Delete failed');
|
|
this.showToast(result.message, 'success');
|
|
this.loadUPSList();
|
|
} catch (err) { this.showToast('Error: ' + err.message, 'error'); }
|
|
}
|
|
|
|
async testConnection() {
|
|
const form = document.getElementById('ups-form');
|
|
const fd = new FormData(form);
|
|
const data = {
|
|
name: fd.get('name') || 'test',
|
|
host: fd.get('host'),
|
|
port: parseInt(fd.get('port')) || 3551,
|
|
interval_seconds: 30,
|
|
alert_on_battery: false,
|
|
};
|
|
if (!data.host) { this.showToast('Host required', 'error'); return; }
|
|
const btn = document.getElementById('test-connection-btn');
|
|
const orig = btn.textContent;
|
|
btn.textContent = 'Testing...'; btn.disabled = true;
|
|
try {
|
|
const resp = await window.apiFetch('/api/config/ups/test', {
|
|
method: 'POST', body: JSON.stringify(data),
|
|
});
|
|
const r = await resp.json();
|
|
this.showToast(r.message || (r.success ? 'OK' : 'Failed'),
|
|
r.success ? 'success' : 'error');
|
|
} catch (err) { this.showToast('Test failed: ' + err.message, 'error'); }
|
|
finally { btn.textContent = orig; btn.disabled = false; }
|
|
}
|
|
|
|
async testUPSConnection(name) {
|
|
try {
|
|
const resp = await window.apiFetch('/api/config/ups/' + name + '/test', { method: 'POST' });
|
|
const r = await resp.json();
|
|
this.showToast(r.message || (r.success ? 'OK' : 'Failed'),
|
|
r.success ? 'success' : 'error');
|
|
} catch (err) { this.showToast('Test failed: ' + err.message, 'error'); }
|
|
}
|
|
|
|
showToast(message, type = 'info') {
|
|
const container = document.getElementById('toast-container');
|
|
const toast = el('div', { class: 'toast ' + type }, message);
|
|
container.appendChild(toast);
|
|
setTimeout(() => { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 5000);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { new ConfigManager(); });
|