Sonoff4ChPro/index.html
2025-08-29 15:16:22 +02:00

309 lines
7.4 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<title id="title"></title>
<link rel="icon" type="image/svg" href="icon.svg">
<style>
body {
font-family: sans-serif;
font-size: 4.5vw;
margin: 0;
}
* {
font-size: inherit;
}
.relayBox {
margin: 0.25em;
}
.relay {
padding: 0.25em;
background-color: lightgray;
}
.relay div {
padding: 0.25em;
}
input, select {
all: unset;
width: 100%;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
text-align: right;
}
.flex {
display: flex;
}
.name {
flex: 1;
padding: 0;
}
.state {
}
.countdown {
}
.stateOn {
background-color: palegreen;
}
.stateOff {
background-color: indianred;
}
.switchOn {
background-color: palegreen;
}
.switchOff {
background-color: indianred;
}
.switchCycle {
background-color: lightskyblue;
}
.config {
flex: 1;
}
.switch {
text-align: center;
flex: 1;
}
.initial {
text-align: right;
}
@media (min-width: 1000px) {
body {
font-size: 16px;
}
.relayBox {
width: 400px;
}
}
</style>
</head>
<body>
<div id="relayList"></div>
<script>
const title = document.getElementById("title");
const relayList = document.getElementById("relayList");
function getUrl(path) {
return `http://10.42.0.204/${path}`;
}
function setState(index, value) {
set("state", index, value ? 'true' : 'false');
}
function set(key, index, value) {
request(`${key}${index}=${encodeURIComponent(value)}`);
}
let timeout;
function updateValue(tag, clazz, innerTag, value) {
const input = tag.getElementsByClassName(clazz)[0].getElementsByTagName(innerTag)[0];
if (document.activeElement !== input) {
input.value = value;
}
}
function updateState(relayTag, state) {
const tag = relayTag.getElementsByClassName("state")[0];
if (state) {
tag.classList.add("stateOn");
tag.classList.remove("stateOff");
relayTag.classList.add("stateOn");
relayTag.classList.remove("stateOff");
} else {
tag.classList.add("stateOff");
tag.classList.remove("stateOn");
relayTag.classList.add("stateOff");
relayTag.classList.remove("stateOn");
}
}
const SECOND = 1000;
const MINUTE = (60 * SECOND);
const HOUR = (60 * MINUTE);
const DAY = (24 * HOUR);
function countdownString(relayData, millis) {
const rest = Math.ceil((millis - relayData.stateMillis - (Date.now() - dataAge)) / SECOND) * SECOND;
if (millis <= 0 || (relayData.onCount === 0 && !relayData.state)) {
return "";
}
const days = rest / DAY;
const hours = rest / HOUR;
const minutes = rest / MINUTE;
const seconds = rest / SECOND;
if (days >= 1) {
return Math.floor(days) + "d " + Math.floor(hours % 24) + "h " + Math.ceil(minutes % 60) + "m " + Math.ceil(seconds % 60) + "s";
}
if (hours >= 1) {
return Math.floor(hours) + "h " + Math.floor(minutes % 60) + "m " + Math.ceil(seconds % 60) + "s";
}
if (minutes >= 1) {
return Math.floor(minutes) + "m " + Math.ceil(seconds % 60) + "s";
}
if (seconds >= 1) {
return Math.ceil(rest / SECOND) + "s";
}
return "Warte...";
}
function updateCountdown(relayTag, relayData) {
const tag = relayTag.getElementsByClassName("countdown")[0];
if (relayData.state) {
tag.innerText = countdownString(relayData, relayData.onMillis);
} else {
tag.innerText = countdownString(relayData, relayData.offMillis);
}
}
let data;
let dataAge;
function request(query = "") {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => request(), 2000);
const r = new XMLHttpRequest();
r.open("GET", getUrl(`set?${query}`));
r.onreadystatechange = () => {
if (r.readyState === 4 && r.status === 200) {
data = JSON.parse(r.response);
dataAge = Date.now();
title.innerText = data.hostname;
for (let index = 0; index < data.relays.length; index++) {
const relayData = data.relays[index];
const relay = document.getElementById("relay" + index) || create(index);
updateValue(relay, "name", "input", relayData.name);
updateState(relay, relayData.state);
updateCountdown(relay, relayData);
updateValue(relay, "onMillis", "input", relayData.onMillis);
updateValue(relay, "offMillis", "input", relayData.offMillis);
updateValue(relay, "initial", "select", relayData.initial);
}
}
}
r.send();
}
function newDiv(parent, name) {
const div = document.createElement("div")
div.className = name;
parent.append(div);
return div;
}
function newInput(relayIndex, parent, clazz, name, type) {
const div = newDiv(parent, clazz);
const input = document.createElement("input")
input.type = type;
input.onchange = () => set(name, relayIndex, input.value);
div.append(input);
return input;
}
function newButton(relayIndex, parent, clazz, key, value, text) {
const div = newDiv(parent, clazz);
const button = document.createElement("div")
button.innerText = text;
button.onclick = () => set(key, relayIndex, value);
div.append(button);
return button;
}
function newSelect(relayIndex, parent, clazz, name, options) {
const div = newDiv(parent, clazz);
const select = document.createElement("select")
select.onchange = () => set(name, relayIndex, select.value);
div.append(select);
for (const [value, text] of options) {
const option = document.createElement("option");
option.value = value;
option.innerText = text;
select.appendChild(option);
}
return select;
}
function create(relayIndex) {
const relayBox = document.createElement("div");
relayBox.className = "relayBox";
relayList.append(relayBox);
const relay = document.createElement("div");
relay.id = "relay" + relayIndex;
relay.className = "relay";
relayBox.append(relay);
const header = newDiv(relay, "flex");
newInput(relayIndex, header, "name", "name", "text");
newDiv(header, "countdown");
newDiv(header, "state");
const config = newDiv(relay, "flex");
newInput(relayIndex, config, "config onMillis", "onMillis", "number");
newInput(relayIndex, config, "config offMillis", "offMillis", "number");
newSelect(relayIndex, config, "config initial", "initial", [["OFF", "Init: Aus"], ["ON", "Init: Ein"], ["CYCLE", "Init: Zyklus"]]);
const switches = newDiv(relay, "flex");
newButton(relayIndex, switches, "switch switchOn", "state", "true", "Ein");
newButton(relayIndex, switches, "switch switchOff", "state", "false", "Aus");
newButton(relayIndex, switches, "switch switchCycle", "onCount", -1, "Zyklus");
return relay;
}
request();
function update() {
if (!data) {
return;
}
for (let index = 0; index < data.relays.length; index++) {
const relayData = data.relays[index];
const relayTag = document.getElementById("relay" + index) || create(index);
updateCountdown(relayTag, relayData);
}
}
setInterval(() => update(), 500);
</script>
</body>
</html>