refactor: fix css, rename src_data dir

This commit is contained in:
Yurii
2024-01-15 15:39:31 +03:00
parent a9c9457918
commit 5a70403444
5 changed files with 14 additions and 10 deletions

81
src_data/static/app.css Normal file
View File

@@ -0,0 +1,81 @@
@media (min-width: 1280px) {
.container {
max-width: 1000px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1000px;
}
}
.hidden {
display: none !important;
}
header,
main,
footer {
padding-top: 1.5rem !important;
padding-bottom: 1.5rem !important;
}
article {
margin-top: 1rem;
margin-bottom: 1rem;
}
footer {
text-align: center;
}
button.success {
background-color: var(--pico-form-element-valid-border-color);
border-color: var(--pico-form-element-valid-border-color);
}
button.failed {
background-color: var(--pico-form-element-invalid-border-color);
border-color: var(--pico-form-element-invalid-border-color);
}
tr.network:hover {
--pico-background-color: var(--pico-primary-focus);
cursor: pointer;
}
.greatSignal {
background-color: var(--pico-form-element-valid-border-color);
}
.normalSignal {
background-color: #e48500;
}
.badSignal {
background-color: var(--pico-form-element-invalid-border-color);
}
.primary {
border: 0.25rem solid var(--pico-form-element-invalid-border-color);
padding: 1rem;
margin-bottom: 1rem;
}
.logo {
display: inline-block;
padding: calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal);
vertical-align: baseline;
line-height: var(--pico-line-height);
background-color: var(--pico-code-kbd-background-color);
border-radius: var(--pico-border-radius);
color: var(--pico-code-kbd-color);
font-weight: bolder;
font-size: 1.3rem;
font-family: var(--pico-font-family-monospace);
}
nav li a:has(> div.logo) {
margin-bottom: 0;
}

665
src_data/static/app.js Normal file
View File

@@ -0,0 +1,665 @@
function setupForm(formSelector) {
const form = document.querySelector(formSelector);
if (!form) {
return;
}
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
if (button) {
defaultText = button.textContent;
}
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (button) {
button.textContent = 'Please wait...';
button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true);
}
const onSuccess = (response) => {
if (button) {
button.textContent = 'Saved';
button.classList.add('success');
button.removeAttribute('aria-busy');
setTimeout(() => {
button.removeAttribute('disabled');
button.classList.remove('success', 'failed');
button.textContent = defaultText;
}, 5000);
}
};
const onFailed = (response) => {
if (button) {
button.textContent = 'Error';
button.classList.add('failed');
button.removeAttribute('aria-busy');
setTimeout(() => {
button.removeAttribute('disabled');
button.classList.remove('success', 'failed');
button.textContent = defaultText;
}, 5000);
}
};
try {
let fd = new FormData(form);
let checkboxes = form.querySelectorAll('input[type="checkbox"]');
for (let checkbox of checkboxes) {
fd.append(checkbox.getAttribute('name'), checkbox.checked);
}
let response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: form2json(fd)
});
if (response.ok) {
onSuccess(response);
} else {
onFailed(response);
}
} catch (err) {
onFailed(false);
}
});
}
function setupNetworkScanForm(formSelector, tableSelector) {
const form = document.querySelector(formSelector);
if (!form) {
console.error("form not found");
return;
}
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
if (button) {
defaultText = button.innerHTML;
}
const onSubmitFn = async (event) => {
if (event) {
event.preventDefault();
}
if (button) {
button.innerHTML = 'Please wait...';
button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true);
}
let table = document.querySelector(tableSelector);
if (!table) {
console.error("table not found");
return;
}
const onSuccess = async (response) => {
let result = await response.json();
console.log('networks: ', result);
let tbody = table.querySelector('tbody');
if (!tbody) {
tbody = table.createTBody();
}
while (tbody.rows.length > 0) {
tbody.rows[0].remove();
}
for (let i = 0; i < result.length; i++) {
let row = tbody.insertRow(-1);
row.classList.add("network");
row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid);
row.onclick = function () {
const input = document.querySelector('input.sta-ssid');
const ssid = this.getAttribute('data-ssid');
if (!input || !ssid) {
return;
}
input.value = ssid;
input.focus();
};
row.insertCell().textContent = "#" + (i + 1);
row.insertCell().innerHTML = result[i].hidden ? '<i>Hidden</i>' : result[i].ssid;
const signalCell = row.insertCell();
const signalElement = document.createElement("kbd");
signalElement.textContent = result[i].signalQuality + "%";
if (result[i].signalQuality > 60) {
signalElement.classList.add('greatSignal');
} else if (result[i].signalQuality > 40) {
signalElement.classList.add('normalSignal');
} else {
signalElement.classList.add('badSignal');
}
signalCell.appendChild(signalElement);
}
if (button) {
button.innerHTML = defaultText;
button.removeAttribute('disabled');
button.removeAttribute('aria-busy');
}
};
const onFailed = async (response) => {
table.classList.remove('hidden');
if (button) {
button.innerHTML = defaultText;
button.removeAttribute('disabled');
button.removeAttribute('aria-busy');
}
};
let attempts = 5;
let timer = setInterval(async () => {
attempts--;
try {
let response = await fetch(url, { cache: 'no-cache' });
if (response.status == 200) {
clearInterval(timer);
await onSuccess(response);
} else if (attempts <= 0) {
await onFailed(response);
}
} catch (err) {
clearInterval(timer);
onFailed(err);
}
}, 2000);
};
form.addEventListener('submit', onSubmitFn);
onSubmitFn();
}
function setupRestoreBackupForm(formSelector) {
const form = document.querySelector(formSelector);
if (!form) {
return;
}
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
if (button) {
defaultText = button.textContent;
}
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (button) {
button.textContent = 'Please wait...';
button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true);
}
const onSuccess = (response) => {
if (button) {
button.textContent = 'Restored';
button.classList.add('success');
button.removeAttribute('aria-busy');
setTimeout(() => {
button.removeAttribute('disabled');
button.classList.remove('success', 'failed');
button.textContent = defaultText;
}, 5000);
}
};
const onFailed = (response) => {
if (button) {
button.textContent = 'Error';
button.classList.add('failed');
button.removeAttribute('aria-busy');
setTimeout(() => {
button.removeAttribute('disabled');
button.classList.remove('success', 'failed');
button.textContent = defaultText;
}, 5000);
}
};
const files = form.querySelector('#restore-file').files;
if (files.length <= 0) {
onFailed(false);
return;
}
let reader = new FileReader();
reader.readAsText(files[0]);
reader.onload = async function () {
try {
let response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: reader.result
});
if (response.ok) {
onSuccess(response);
} else {
onFailed(response);
}
} catch (err) {
onFailed(false);
}
};
reader.onerror = function () {
console.log(reader.error);
};
});
}
function setupUpgradeForm(formSelector) {
const form = document.querySelector(formSelector);
if (!form) {
return;
}
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
if (button) {
defaultText = button.textContent;
}
const statusToText = (status) => {
switch (status) {
case 0:
return "None";
case 1:
return "No file";
case 2:
return "Success";
case 3:
return "Prohibited";
case 4:
return "Aborted";
case 5:
return "Error on start";
case 6:
return "Error on write";
case 7:
return "Error on finish";
default:
return "Unknown";
}
};
const onResult = async (response) => {
if (!response) {
return;
}
const result = await response.json();
let resItem = form.querySelector('.upgrade-firmware-result');
if (resItem && result.firmware.status > 1) {
resItem.textContent = statusToText(result.firmware.status);
resItem.classList.remove('hidden');
if (result.firmware.status == 2) {
resItem.classList.remove('failed');
resItem.classList.add('success');
} else {
resItem.classList.remove('success');
resItem.classList.add('failed');
if (result.firmware.error != "") {
resItem.textContent += ": " + result.firmware.error;
}
}
}
resItem = form.querySelector('.upgrade-filesystem-result');
if (resItem && result.filesystem.status > 1) {
resItem.textContent = statusToText(result.filesystem.status);
resItem.classList.remove('hidden');
if (result.filesystem.status == 2) {
resItem.classList.remove('failed');
resItem.classList.add('success');
} else {
resItem.classList.remove('success');
resItem.classList.add('failed');
if (result.filesystem.error != "") {
resItem.textContent += ": " + result.filesystem.error;
}
}
}
};
const onSuccess = (response) => {
onResult(response);
if (button) {
button.textContent = defaultText;
button.removeAttribute('aria-busy');
setTimeout(() => {
button.removeAttribute('disabled');
button.classList.remove('success', 'failed');
button.textContent = defaultText;
}, 5000);
}
};
const onFailed = (response) => {
if (button) {
button.textContent = 'Error';
button.classList.add('failed');
button.removeAttribute('aria-busy');
setTimeout(() => {
button.removeAttribute('disabled');
button.classList.remove('success', 'failed');
button.textContent = defaultText;
}, 5000);
}
};
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (button) {
button.textContent = 'Uploading...';
button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true);
}
try {
let fd = new FormData(form);
let response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
body: fd
});
if (response.status >= 200 && response.status < 500) {
onSuccess(response);
} else {
onFailed(response);
}
} catch (err) {
onFailed(false);
}
});
}
async function loadNetworkStatus() {
let response = await fetch('/api/network/status', { cache: 'no-cache' });
let result = await response.json();
setValue('.network-hostname', result.hostname);
setValue('.network-mac', result.mac);
setState('.network-connected', result.isConnected);
setValue('.network-ssid', result.ssid);
setValue('.network-signal', result.signalQuality);
setValue('.network-ip', result.ip);
setValue('.network-subnet', result.subnet);
setValue('.network-gateway', result.gateway);
setValue('.network-dns', result.dns);
setBusy('.main-busy', '.main-table', false);
}
async function loadNetworkSettings() {
let response = await fetch('/api/network/settings', { cache: 'no-cache' });
let result = await response.json();
setInputValue('.network-hostname', result.hostname);
setCheckboxValue('.network-use-dhcp', result.useDhcp);
setInputValue('.network-static-ip', result.staticConfig.ip);
setInputValue('.network-static-gateway', result.staticConfig.gateway);
setInputValue('.network-static-subnet', result.staticConfig.subnet);
setInputValue('.network-static-dns', result.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
setInputValue('.sta-ssid', result.sta.ssid);
setInputValue('.sta-password', result.sta.password);
setInputValue('.sta-channel', result.sta.channel);
setBusy('#sta-settings-busy', '#sta-settings', false);
setInputValue('.ap-ssid', result.ap.ssid);
setInputValue('.ap-password', result.ap.password);
setInputValue('.ap-channel', result.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
}
async function loadSettings() {
let response = await fetch('/api/settings', { cache: 'no-cache' });
let result = await response.json();
setCheckboxValue('.system-debug', result.system.debug);
setCheckboxValue('.system-use-serial', result.system.useSerial);
setCheckboxValue('.system-use-telnet', result.system.useTelnet);
setBusy('#system-settings-busy', '#system-settings', false);
setCheckboxValue('.portal-use-auth', result.portal.useAuth);
setInputValue('.portal-login', result.portal.login);
setInputValue('.portal-password', result.portal.password);
setBusy('#portal-settings-busy', '#portal-settings', false);
setInputValue('.opentherm-in-pin', result.opentherm.inPin);
setInputValue('.opentherm-out-pin', result.opentherm.outPin);
setInputValue('.opentherm-member-id-code', result.opentherm.memberIdCode);
setCheckboxValue('.opentherm-dhw-present', result.opentherm.dhwPresent);
setCheckboxValue('.opentherm-sw-mode', result.opentherm.summerWinterMode);
setCheckboxValue('.opentherm-heating-ch2-enabled', result.opentherm.heatingCh2Enabled);
setCheckboxValue('.opentherm-heating-ch1-to-ch2', result.opentherm.heatingCh1ToCh2);
setCheckboxValue('.opentherm-dhw-to-ch2', result.opentherm.dhwToCh2);
setCheckboxValue('.opentherm-dhw-blocking', result.opentherm.dhwBlocking);
setCheckboxValue('.opentherm-sync-modulation-with-heating', result.opentherm.modulationSyncWithHeating);
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
setInputValue('.mqtt-server', result.mqtt.server);
setInputValue('.mqtt-port', result.mqtt.port);
setInputValue('.mqtt-user', result.mqtt.user);
setInputValue('.mqtt-password', result.mqtt.password);
setInputValue('.mqtt-prefix', result.mqtt.prefix);
setInputValue('.mqtt-interval', result.mqtt.interval);
setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
setRadioValue('.outdoor-sensor-type', result.sensors.outdoor.type);
setInputValue('.outdoor-sensor-pin', result.sensors.outdoor.pin);
setInputValue('.outdoor-sensor-offset', result.sensors.outdoor.offset);
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
setRadioValue('.indoor-sensor-type', result.sensors.indoor.type);
setInputValue('.indoor-sensor-pin', result.sensors.indoor.pin);
setInputValue('.indoor-sensor-offset', result.sensors.indoor.offset);
setInputValue('.indoor-sensor-ble-addresss', result.sensors.indoor.bleAddresss);
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
setCheckboxValue('.extpump-use', result.externalPump.use);
setInputValue('.extpump-pin', result.externalPump.pin);
setInputValue('.extpump-pc-time', result.externalPump.postCirculationTime);
setInputValue('.extpump-as-interval', result.externalPump.antiStuckInterval);
setInputValue('.extpump-as-time', result.externalPump.antiStuckTime);
setBusy('#extpump-settings-busy', '#extpump-settings', false);
}
async function loadVars() {
let response = await fetch('/api/vars');
let result = await response.json();
setState('.ot-connected', result.states.otStatus);
setState('.ot-emergency', result.states.emergency);
setState('.ot-heating', result.states.heating);
setState('.ot-dhw', result.states.dhw);
setState('.ot-flame', result.states.flame);
setState('.ot-fault', result.states.fault);
setState('.ot-diagnostic', result.states.diagnostic);
setState('.ot-external-pump', result.states.externalPump);
setValue('.ot-modulation', result.sensors.modulation);
setValue('.ot-pressure', result.sensors.pressure);
setValue('.ot-dhw-flow-rate', result.sensors.dhwFlowRate);
setValue('.ot-fault-code', result.sensors.faultCode ? ("E" + result.sensors.faultCode) : "-");
setValue('.indoor-temp', result.temperatures.indoor);
setValue('.outdoor-temp', result.temperatures.outdoor);
setValue('.heating-temp', result.temperatures.heating);
setValue('.heating-setpoint-temp', result.parameters.heatingSetpoint);
setValue('.dhw-temp', result.temperatures.dhw);
setBusy('.ot-busy', '.ot-table', false);
setValue('.version', result.system.version);
setValue('.build-date', result.system.buildDate);
setValue('.uptime', result.system.uptime);
setValue('.uptime-days', Math.floor(result.system.uptime / 86400));
setValue('.uptime-hours', Math.floor(result.system.uptime % 86400 / 3600));
setValue('.uptime-min', Math.floor(result.system.uptime % 3600 / 60));
setValue('.uptime-sec', Math.floor(result.system.uptime % 60));
setValue('.free-heap', result.system.freeHeap);
setValue('.total-heap', result.system.totalHeap);
setValue('.max-free-block-heap', result.system.maxFreeBlockHeap);
setValue('.reset-reason', result.system.resetReason);
setState('.mqtt-connected', result.system.mqttConnected);
setBusy('.system-busy', '.system-table', false);
}
function setBusy(busySelector, contentSelector, value) {
let busy = document.querySelector(busySelector);
let content = document.querySelector(contentSelector);
if (!busy || !content) {
return;
}
if (!value) {
busy.classList.add('hidden');
content.classList.remove('hidden');
} else {
busy.classList.remove('hidden');
content.classList.add('hidden');
}
}
function setState(selector, value) {
let item = document.querySelector(selector);
if (!item) {
return;
}
item.setAttribute('aria-invalid', !value);
}
function setValue(selector, value) {
let item = document.querySelector(selector);
if (!item) {
return;
}
item.innerHTML = value;
}
function setCheckboxValue(selector, value) {
let item = document.querySelector(selector);
if (!item) {
return;
}
item.checked = value;
}
function setRadioValue(selector, value) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
for (let item of items) {
item.checked = item.value == value;
}
}
function setInputValue(selector, value) {
let item = document.querySelector(selector);
if (!item) {
return;
}
item.value = value;
}
function form2json(data) {
let method = function (object, pair) {
let keys = pair[0].replace(/\]/g, '').split('[');
let key = keys[0];
let value = pair[1];
if (value === 'true' || value === 'false') {
value = value === 'true';
} else if (typeof (value) === 'string' && value.trim() !== '' && !isNaN(value)) {
value = parseFloat(value);
}
if (keys.length > 1) {
let i, x, segment;
let last = value;
let type = isNaN(keys[1]) ? {} : [];
value = segment = object[key] || type;
for (i = 1; i < keys.length; i++) {
x = keys[i];
if (i == keys.length - 1) {
if (Array.isArray(segment)) {
segment.push(last);
} else {
segment[x] = last;
}
} else if (segment[x] == undefined) {
segment[x] = isNaN(keys[i + 1]) ? {} : [];
}
segment = segment[x];
}
}
object[key] = value;
return object;
}
let object = Array.from(data).reduce(method, {});
return JSON.stringify(object);
}

BIN
src_data/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

4
src_data/static/pico.min.css vendored Normal file

File diff suppressed because one or more lines are too long