const { html, render } = lighterhtml; const $container = document.getElementById("sensors-container"); const $filters = document.createElement("div"); $filters.className = "filters"; $container.appendChild($filters); const $sensorsContainer = document.createElement("div"); $sensorsContainer.className = "sensors"; $container.appendChild($sensorsContainer); const $config = document.createElement("div"); $config.className = "config"; $container.appendChild($config); let sensorComponents = []; function load() { fetch("/api/sensors") .then((r) => r.json()) .then((sensors) => { $sensorsContainer.innerHTML = '' sensorComponents = sensors.map((sensor) => createSensor(sensor)); sensorComponents.forEach((component) => $sensorsContainer.appendChild(component.container) ); }); } function preventPropagation(e) { e.stopPropagation(); } function hideConfig() { renderConfig({ sensor: { config: {} }, shown: false }); } function showConfigOf(sensor, onSave) { renderConfig({ sensor, onSave, shown: true }); } function renderConfig({ sensor, onSave, shown }) { const handleSave = async (e) => { e.preventDefault(); e.stopPropagation(); const data = Object.fromEntries(new FormData(e.target)); Promise.all( Object.entries(data).map(([name, value]) => fetch(`/api/sensors/${sensor.sensor}/config/${name}`, { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ value }), }) ) ).then(() => onSave?.()); hideConfig(); }; const config = sensor.config; render( $config, html`
` ); } const CURRENT_FILTERS = { interval: "week", refresh: "1s", customFrom: new Date().toISOString(), customTo: new Date().toISOString(), }; function refreshAll() { sensorComponents.forEach(c => c.refreshValues()) } let isCustomSelected = false let lastIntervalSelected = 'week' function splitDateTime(v) { const d = new Date(v) const date = d.getFullYear() + '-' + (d.getMonth() + 1).toString().padStart(2, '0') + '-' + d.getDate().toString().padStart(2, '0') const time = d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0') return [date, time] } function renderFilters() { const handleApply = async (e) => { e.preventDefault(); e.stopPropagation(); const data = Object.fromEntries(new FormData(e.target)); CURRENT_FILTERS.interval = data.interval; if (data.interval !== "custom") { const [from, to] = getDataInterval(CURRENT_FILTERS.interval); CURRENT_FILTERS.customFrom = from.toISOString(); CURRENT_FILTERS.customTo = to.toISOString(); } else { CURRENT_FILTERS.customFrom = new Date(`${data.fromDate} ${data.fromTime}`) CURRENT_FILTERS.customTo = new Date(`${data.toDate} ${data.toTime}`) } refreshAll() renderFilters() }; const handleIntervalChange = (e) => { lastIntervalSelected = e.target.value renderFilters() } const customFrom = splitDateTime(CURRENT_FILTERS.customFrom) const customTo = splitDateTime(CURRENT_FILTERS.customTo) const isCustomSelected = lastIntervalSelected === 'custom' render( $filters, html`
${isCustomSelected ? html`
` : undefined}
` ); } function getDataInterval() { switch (CURRENT_FILTERS.interval) { case "hour": { return [new Date(Date.now() - 3600 * 1000), new Date()]; } case "day": { return [new Date(Date.now() - 24 * 3600 * 1000), new Date()]; } case "week": { return [new Date(Date.now() - 5 * 24 * 3600 * 1000), new Date()]; } case "month": { return [new Date(Date.now() - 30 * 24 * 3600 * 1000), new Date()]; } case "year": { return [new Date(Date.now() - 356 * 24 * 3600 * 1000), new Date()]; } case "custom": { return [ new Date(CURRENT_FILTERS.customFrom), new Date(CURRENT_FILTERS.customTo), ]; } } } function createSensor(sensor) { const container = document.createElement("div"); container.className = "sensor"; const header = document.createElement("div"); header.className = "header"; const body = document.createElement("div"); body.className = "body"; const showSettings = () => { showConfigOf(sensor, () => refreshSensorConfig()); }; const renderHeader = () => { render( header, html`
${sensor.config?.name ?? sensor.sensor}
` ); }; const renderBody = (range, values) => { const { from, to } = range; Plotly.newPlot( body, [ { type: "lines", x: values.map((v) => new Date(v.timestamp * 1000)), y: values.map((v) => v.value), }, ], { xaxis: { range: [from, to], type: "date" }, margin: { l: 50, r: 20, b: 60, t: 20, pad: 5, }, }, { responsive: true, } ); }; const refreshValues = () => { const [from, to] = getDataInterval(CURRENT_FILTERS.interval); fetch( `/api/sensors/${sensor.sensor}/values?from=${Math.round( from.getTime() / 1000 )}&to=${Math.round(to.getTime() / 1000)}` ) .then((r) => r.json()) .then((values) => renderBody({ from, to }, values ?? [])); }; const refreshSensorConfig = () => { fetch(`/api/sensors/${sensor.sensor}/config`) .then((r) => r.json()) .then((c) => (sensor.config = c)) .then(() => { renderHeader(); refreshValues(); }); }; renderHeader(); refreshValues(); container.appendChild(header); container.appendChild(body); return { container, refreshValues }; } load(); renderFilters();