window.onload = () => { const table_elem = document.getElementById("table"); const last_change = document.getElementById("last_change"); const last_ping = document.getElementById("last_ping"); const connected = document.getElementById("connected"); const free_ports = document.getElementById("free_ports"); const timeout_duration = 10 * 1000; const retry_timeout = 5 * 1000; let reconnect_timeout; let ping_timeout; let evtSource; let table = []; let last_update = new Date(); let direction = -1; let oldkey = "last_change"; table_elem.firstChild.firstChild.lastChild.className = "sort sort-down"; let time_ago = (ms) => { let value = ms / 1000; // let prev = 0; let unit = 0; let factors = [ [1, "Sekunde", "n"], [60, "Minute", "n"], [60, "Stunde", "n"], [24, "Tag", "en"], [7, "Woche", "n"], [4.348214, "Monat", "en"], [12, "Jahr", "en"], ]; for (let i in factors) { let factor = factors[i][0]; let new_value = Math.floor(value / factor); if (new_value == 0) break; // prev = Math.floor(value % factor); value = new_value; unit = i; } let factor = factors[unit]; return [value, factors[unit][1] + (value == 1 ? "" : factor[2])]; }; sort = (element, key) => { if (key == oldkey) { direction *= -1; } else { oldkey = key; direction = -1; } for (let child of table_elem.firstChild.firstChild.children) { child.className = ""; } element.className = `sort ${direction > 0 ? "sort-up" : "sort-down"}`; do_sort(oldkey, direction); }; let do_sort = (key, direction) => { let is_number = !!~(["port", "number", "last_change"].indexOf(key)); table = table.sort((a, b) => (direction * ( is_number ? a[key] - b[key] : ("" + a[key]).localeCompare(b[key], "de-DE") ))); populate_table(); }; let fmt = Intl.DateTimeFormat("de-DE", { dateStyle: "medium", timeStyle: "medium", }); let format_date = (date) => fmt.format(date).replace(", ", " "); let format_time = (date) => fmt.format(date).split(", ", 2)[1]; let populate_table = () => { // clear everything except for the header row while (table_elem.rows.length > 1) { table_elem.deleteRow(-1); } for (let row of table) { let tr = table_elem.insertRow(-1); let values = [ row.name === null ? "?" : row.name, row.number, row.port, row.status, time_ago(last_update - row.last_change), ]; let names = [ "name", "number", "port", "status", "last_change", ]; for (let i in values) { let value = values[i]; let name = names[i]; let td = tr.insertCell(-1); td.className = name; if (name == "last_change") { let [number, unit] = value; let span = document.createElement("span"); // span.className = "value"; span.innerText = number; td.appendChild(span); span = document.createElement("span"); span.className = "unit"; span.innerText = unit; td.appendChild(span); } else { td.innerText = value; } } } }; let update_table = (data) => { const allowed_ports = data.allowed_ports.map((x) => x.end - x.start + 1) .reduce((a, b) => a + b, 0); free_ports.innerText = `Freie Ports: ${ allowed_ports - Object.keys(data.allocated_ports).length - data.errored_ports.length }`; // last_change.innerHTML = `Letzte Änderung: ${format_date(new Date(+data.last_update * 1000))}`; table = []; for (let number in data.allocated_ports) { try { let port = data.allocated_ports[number]; number = +number; // allocated port has no state. This means that it is unknown and should not be displayed if (data.port_state[port] == undefined) { continue; } let { status, last_change } = data.port_state[port]; let rejector = data.rejectors[port] || null; if (rejector && rejector instanceof Array) { rejector = rejector.map((x) => "0x" + x.toString(16).padStart(2, 0)) .join(" "); } last_change = new Date(last_change * 1000); let name = data.names[number] || null; switch (status) { case "disconnected": status = rejector ? `getrennt: ${rejector}` : "getrennt"; break; case "idle": status = "bereit"; break; case "in_call": status = "anruf"; break; } table.push({ port, number, status, last_change, rejector, name }); } catch (error) { console.error(error); } } for (let [timestamp, port] of data.errored_ports) { table.push({ port, number: null, status: "Fehler", last_change: new Date(timestamp * 1000), rejector: null, name: "", }); } do_sort(oldkey, direction); populate_table(); }; let format_event = (event, method) => (method || format_date)(new Date(+event.data * 1000)); let display_disconnected; let connect_event_source = () => { clearTimeout(reconnect_timeout); clearTimeout(ping_timeout); ping_timeout = setTimeout(connect_event_source, timeout_duration); evtSource && evtSource.close && evtSource.close(); evtSource = new EventSource("/events"); evtSource.addEventListener("change", (event) => { last_ping.innerText = `Stand: ${format_event(event, format_time)}`; last_update = new Date(+event.data * 1000); fetch("/data") .then((res) => res.json()) .then(update_table); }); evtSource.addEventListener("ping", (event) => { clearTimeout(ping_timeout); ping_timeout = setTimeout(connect_event_source, timeout_duration); last_update = new Date(+event.data * 1000); last_ping.innerText = `Stand: ${format_event(event, format_time)}`; connected.className = "visible"; populate_table(); clearTimeout(display_disconnected); display_disconnected = setTimeout( () => connected.className = "hidden", 5000, ); }); evtSource.onerror = () => { clearTimeout(reconnect_timeout); reconnect_timeout = setTimeout(connect_event_source, retry_timeout); }; }; connect_event_source(); };