Explorar el Código

starting to implement history from interier

marek hace 1 mes
padre
commit
4bc035eb9c
Se han modificado 3 ficheros con 235 adiciones y 7 borrados
  1. 11 4
      assets/css/style.css
  2. 117 3
      templates/en/panel/stations/station.eta
  3. 107 0
      utils/meteostanice.js

+ 11 - 4
assets/css/style.css

@@ -4,11 +4,11 @@ img {
 }
 
 body {
-    min-height: 100dvh;
+    min-height: calc(100dvh - 2em);
     margin: 0;
     display: flex;
     flex-direction: column;
-    padding-inline: 10%;
+    padding: 1em 10%;
     font-family: sans-serif;
     line-height: 1.3;
     background-color: #eee;
@@ -41,6 +41,10 @@ body {
     color: rgb(255, 50, 50);
 }
 
+#mainStats > p {
+    margin: 0;
+}
+
 #mainStats .stats {
     display: flex;
     flex-wrap: wrap;
@@ -69,6 +73,10 @@ body {
 
 #mainStats .stats .values p {
     margin: 0.5em 0;
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 0.5em;
 }
 
 p.messageText, a, button, h1, h2, h3 {
@@ -84,7 +92,6 @@ header {
     align-items: center;
     flex-wrap: wrap;
     gap: 0 3em;
-    padding-block: 1em;
 }
 
 header > * {
@@ -169,7 +176,7 @@ form > div {
 .container-row {
     display: flex;
     flex-wrap: wrap;
-    gap: 0 1em;
+    gap: 1em;
     align-items: center;
     margin-block: 0.5em;
 }

+ 117 - 3
templates/en/panel/stations/station.eta

@@ -8,6 +8,42 @@
     </svg>
 ` %>
 
+<% const tickIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <circle cx="12" cy="12" r="10" />
+            <path d="m9 12l2 2l4-4" />
+        </g>
+    </svg>
+` %>
+
+<% const xIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <circle cx="12" cy="12" r="10" />
+            <path d="m15 9l-6 6m0-6l6 6" />
+        </g>
+    </svg>
+` %>
+
+<% const tempIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0" />
+    </svg>
+` %>
+
+<% const pressureIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 14l4-4M3.34 19a10 10 0 1 1 17.32 0" />
+    </svg>
+` %>
+
+<% const humidityIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5S5 13 5 15a7 7 0 0 0 7 7" />
+    </svg>
+` %>
+
 <% const resetIcon = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
         <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
@@ -17,6 +53,15 @@
     </svg>
 ` %>
 
+<% const historyIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
+            <path d="M3 3v5h5m4-1v5l4 2" />
+        </g>
+    </svg>
+` %>
+
 <% const editIcon = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
         <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
@@ -41,6 +86,76 @@
     <p><strong>description:</strong> <%= it.meteostanica.description %></p>
 <% } %>
 
+<% if (it.data?.[0]) { %>
+    <div id="mainStats">
+        <% /* <div class="message error">
+            <p>High temperature was detcted!</p>
+            <p>High pressure was detcted!</p>
+            <p>High humidity was detcted!</p>
+        </div> */ %>
+
+        <p><em>last updated: <%= it.data?.[0]?.timestamp %></em></p>
+
+        <div class="stats">
+            <div class="wrapper">
+                <h2>indoor</h2>
+                <div class="values indoor">
+                    <p><a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/indoorTemp"><%~ tempIcon %></a> <span id="indoorTemp"><%= it.data?.[0]?.indoorTemp / 100 %></span> °C</p>
+                    <p><a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/indoorPressure"><%~ pressureIcon %></a> <span id="indoorPressure"><%= it.data?.[0]?.indoorPressure / 100 %></span> hPa</p>
+                    <p><a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/indoorHumidity"><%~ humidityIcon %></a> <span id="indoorHumidity"><%= it.data?.[0]?.indoorHumidity / 100 %></span> %</p>
+                </div>
+            </div>
+
+            <div class="wrapper">
+                <h2>outdoor</h2>
+                <div class="values outdoor">
+                    <p><a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorConnected" id="outdoorConnectedIcon"><%~ it.data?.[0]?.outdoorConnected ? tickIcon : xIcon %></a> <span id="outdoorConnectedText"><%= it.data?.[0]?.outdoorConnected ? `connected` : `disconnected` %></span></p>
+                    <p><a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorTemp"><%~ tempIcon %></a> <span id="outdoorTemp"><%= it.data?.[0]?.outdoorTemp / 100 %></span> °C</p>
+                    <p><a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorPressure"><%~ pressureIcon %></a> <span id="outdoorPressure"><%= it.data?.[0]?.outdoorPressure / 100 %></span> hPa</p>
+                    <p><a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorHumidity"><%~ humidityIcon %></a> <span id="outdoorHumidity"><%= it.data?.[0]?.outdoorHumidity / 100 %></span> %</p>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <script defer>
+        setInterval(async () => {
+            const response = await fetch('/en/panel/stations/<%= it.meteostanica.id %>/currentData')
+            const data = await response.json()
+
+            const indoorTemp = document.querySelector(`#indoorTemp`)
+            const indoorPressure = document.querySelector(`#indoorPressure`)
+            const indoorHumidity = document.querySelector(`#indoorHumidity`)
+            const indoorAltitude = document.querySelector(`#indoorAltitude`)
+
+            const outdoorConnectedIcon = document.querySelector(`#outdoorConnectedIcon`)
+            const outdoorConnectedText = document.querySelector(`#outdoorConnectedText`)
+
+            const outdoorTemp = document.querySelector(`#outdoorTemp`)
+            const outdoorPressure = document.querySelector(`#outdoorPressure`)
+            const outdoorHumidity = document.querySelector(`#outdoorHumidity`)
+            const outdoorAltitude = document.querySelector(`#outdoorAltitude`)
+
+            indoorTemp.textContent = data?.indoorTemp / 100
+            indoorPressure.textContent = data?.indoorPressure / 100
+            indoorHumidity.textContent = data?.indoorHumidity / 100
+            indoorAltitude.textContent = data?.indoorAltitude / 100
+
+            const tickIcon = `<%~ tickIcon %>`
+
+            const xIcon = `<%~ xIcon %>`
+
+            outdoorConnectedIcon.innerHTML = data?.outdoorConnected ? tickIcon : xIcon
+            outdoorConnectedText.textContent = data?.outdoorConnected ? `connected` : `disconnected`
+
+            outdoorTemp.textContent = data?.outdoorTemp / 100
+            outdoorPressure.textContent = data?.outdoorPressure / 100
+            outdoorHumidity.textContent = data?.outdoorHumidity / 100
+            outdoorAltitude.textContent = data?.outdoorAltitude / 100
+        }, 10000)
+    </script>
+<% } %>
+
 <p><strong>created:</strong> <%= it.meteostanica.timestamp %></p>
 
 <div class="container-row">
@@ -51,8 +166,7 @@
 <hr>
 
 <div class="container-row">
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history"><%~ historyIcon %> history</a>
     <a role="button" class="primary" href="/en/panel/stations/<%= it.meteostanica.id %>/edit"><%~ editIcon %> edit</a>
     <a role="button" class="danger" href="/en/panel/stations/<%= it.meteostanica.id %>/delete"><%~ deleteIcon %> delete</a>
-</div>
-
-<p>(data with history (graphs) will go here)</p>
+</div>

+ 107 - 0
utils/meteostanice.js

@@ -181,6 +181,113 @@ export default class Meteostanice {
         return result
     }
 
+    static getDataProperty(meteostanica, property) {
+        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        if (!tableNames.find(i => i.name === property)) return null
+
+        const statement = meteostanicaDB.prepare(`
+            SELECT ${property}
+            FROM data
+            WHERE meteostanica = $meteostanica
+            ORDER BY timestamp DESC
+            LIMIT 10;
+        `)
+
+        const result = statement.all({
+            $meteostanica: meteostanica
+        });
+
+        return result
+    }
+
+    static getDataPropertyDaily(meteostanica, property, date) {
+        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        if (!tableNames.find(i => i.name === property)) return null
+
+        const statement = meteostanicaDB.prepare(`
+            SELECT strftime('%Y-%m-%d %H:00:00', timestamp) AS timeMark, 
+                AVG(${property}) AS value
+            FROM data
+            WHERE meteostanica = ? AND date(timestamp) = ?  -- Pass 'YYYY-MM-DD' here
+            GROUP BY timeMark
+            ORDER BY timeMark;
+        `)
+        
+        const result = statement.all(meteostanica, date);
+
+        return result
+    }
+
+    static getDataPropertyMonthly(meteostanica, property, yearMonth) {
+        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        if (!tableNames.find(i => i.name === property)) return null
+
+        const statement = meteostanicaDB.prepare(`
+            SELECT date(timestamp) AS timeMark, 
+                AVG(${property}) AS value
+            FROM data
+            WHERE meteostanica = ? AND strftime('%Y-%m', timestamp) = ? -- Pass 'YYYY-MM' here
+            GROUP BY timeMark
+            ORDER BY timeMark;
+        `)
+        
+        const result = statement.all(meteostanica, yearMonth);
+
+        return result
+    }
+
+    static getDataPropertyYearly(meteostanica, property, year) {
+        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        if (!tableNames.find(i => i.name === property)) return null
+
+        const statement = meteostanicaDB.prepare(`
+            SELECT strftime('%Y-%m', timestamp) AS timeMark, 
+                AVG(${property}) AS value
+            FROM data
+            WHERE meteostanica = ? AND strftime('%Y', timestamp) = ? -- Pass 'YYYY' here
+            GROUP BY timeMark
+            ORDER BY timeMark;
+        `)
+        
+        const result = statement.all(meteostanica, year);
+
+        return result
+    }
+
+    static getDataPropertyAllTime(meteostanica, property) {
+        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        if (!tableNames.find(i => i.name === property)) return null
+
+        const statement = meteostanicaDB.prepare(`
+            SELECT strftime('%Y', timestamp) AS timeMark, 
+                AVG(${property}) AS value
+            FROM data
+            WHERE meteostanica = ?
+            GROUP BY timeMark
+            ORDER BY timeMark;
+        `)
+        
+        const result = statement.all(meteostanica);
+
+        return result
+    }
+
+    static getDateMap(meteostanica) {
+        const statement = meteostanicaDB.query("SELECT DISTINCT date(timestamp) as d FROM data WHERE meteostanica = ? ORDER BY d ASC");
+        const rows = statement.all(meteostanica);
+
+        return rows.reduce((acc, row) => {
+            const [year, month, day] = row.d.split("-");
+
+            if (!acc[year]) acc[year] = {};
+            if (!acc[year][month]) acc[year][month] = [];
+
+            acc[year][month].push(day);
+
+            return acc;
+        }, {});
+    }
+
     static resetWebsocketKey(id) {
         const websocketKey = generateSecureRandomString()