Jelajahi Sumber

added a bunch of stuff

marek 1 bulan lalu
induk
melakukan
e73a348f8a

+ 2 - 0
.env.example

@@ -15,6 +15,8 @@ PORT="3000"
 
 # post data
 
+POST_DATA_ENABLED="true"
+
 BACKEND_URL="wss://meteostanica.com"
 BACKEND_KEEPALIVE_STRING="beep"
 BACKEND_WEBSOCKET_KEY=""

+ 38 - 6
apps/postData.js

@@ -1,12 +1,12 @@
 import Meteostanica from "../utils/meteostanica";
+import { watch } from "node:fs";
+import reloadEnv from "../utils/reloadEnv";
 
-const socket = new WebSocket(`${process.env.BACKEND_URL}/ws/sendData/${process.env.BACKEND_WEBSOCKET_KEY}`);
-
-socket.addEventListener("message", (e) => {
-    console.log(e.data)
-})
+let socket
 
 setInterval(() => {
+    if (!socket) return;
+
     const data = Meteostanica.getData()[0]
     socket.send(JSON.stringify(data))
 
@@ -14,5 +14,37 @@ setInterval(() => {
 }, process.env.DATA_POST_INTERVAL * 1000)
 
 setInterval(() => {
+    if (!socket) return;
+
     socket.send(process.env.BACKEND_KEEPALIVE_STRING)
-}, 10000)
+}, 10000)
+
+
+// Function to read and parse the .env file
+async function reloadData() {
+  try {
+    await reloadEnv()
+
+    if (process.env.POST_DATA_ENABLED !== "false") {
+        socket = new WebSocket(`${process.env.BACKEND_URL}/ws/sendData/${process.env.BACKEND_WEBSOCKET_KEY}`)
+
+        socket.addEventListener("message", (e) => {
+            console.log(e.data)
+        })
+    } else {
+        socket = null
+    }
+  } catch (error) {
+    console.error("Failed to reload .env:", error);
+  }
+}
+
+// 1. Initial load
+await reloadData();
+
+// 2. Watch the .env file for changes
+watch(`.env`, async (event, filename) => {
+  if (event === "change") {
+    await reloadData();
+  }
+});

+ 44 - 4
assets/css/style.css

@@ -3,9 +3,10 @@ body {
     margin: 0;
     display: flex;
     flex-direction: column;
-    font-size: 1.2rem;
+    /* font-size: 1.2rem; */
     padding-inline: 10%;
     font-family: sans-serif;
+    line-height: 1.3;
 }
 
 #mainStats {
@@ -18,11 +19,30 @@ body {
     padding-inline: 1rem;
 }
 
-#mainStats > .message {
-    background-color: lightcoral;
-    border: 2px solid red;
+.message {
     padding: 0.5rem 2rem;
     border-radius: 1rem;
+    background-color: rgba(50, 50, 50, 0.3);
+    border: 2px solid rgb(50, 50, 50);
+    color: rgb(50, 50, 50);
+}
+
+.message.error {
+    background-color: rgba(255, 50, 50, 0.3);
+    border: 2px solid rgb(255, 50, 50);
+    color: rgb(255, 50, 50);
+}
+
+.message.info {
+    background-color: rgba(50, 50, 255, 0.3);
+    border: 2px solid rgb(50, 50, 255);
+    color: rgb(50, 50, 255);
+}
+
+.message.success {
+    background-color: rgba(50, 200, 50, 0.3);
+    border: 2px solid rgb(50, 200, 50);
+    color: rgb(50, 200, 50);
 }
 
 #mainStats .stats {
@@ -77,10 +97,30 @@ header .end {
     flex-wrap: wrap;
 }
 
+input, button {
+    font-size: 1em;
+}
+
 [role="button"], button {
     background-color: rgba(0, 0, 0, 0.1);
     border-radius: 0.5em;
     padding: 0.3em 1em;
     text-decoration: none;
     color: initial;
+    border: none;
+    width: max-content;
+    cursor: pointer;
+}
+
+input {
+    background-color: rgba(0, 0, 0, 0.1);
+    border-radius: 0.5em;
+    padding: 0.3em 1em;
+    border: 2px solid rgba(0, 0, 0);
+}
+
+form {
+    display: flex;
+    flex-direction: column;
+    gap: 1em;
 }

+ 7 - 0
lang/en.js

@@ -0,0 +1,7 @@
+export default {
+    settings: {
+        errors: {
+            noCloudURL: () => `you need to provide a cloud url.`
+        }
+    }
+}

+ 7 - 0
lang/sk.js

@@ -0,0 +1,7 @@
+export default {
+    settings: {
+        errors: {
+            noCloudURL: () => `musíte zadať cloud url.`
+        }
+    }
+}

+ 23 - 1
routes/include/main.js

@@ -4,14 +4,36 @@ import { Eta } from "eta"
 const eta = new Eta({ views: "./templates" })
 
 import Meteostanica from '../../utils/meteostanica'
+import editEnvVariable from '../../utils/editEnvVariable'
 
-export default (langName) => new Elysia()
+export default (langName, lang) => new Elysia()
   .get('/', ({ set }) => {
     const data = Meteostanica.getData()
 
     set.headers['content-type'] = 'text/html; charset=utf8'
     return eta.render(`${langName}/index`, { data })
   })
+  .get('/settings', ({ set }) => {
+    set.headers['content-type'] = 'text/html; charset=utf8'
+    return eta.render(`${langName}/settings`, { postDataEnabled: process.env.POST_DATA_ENABLED, cloudURL: process.env.BACKEND_URL })
+  })
+  .post('/settings', async ({ body, set, redirect }) => {
+    const postDataEnabled = body?.postDataEnabled
+    const cloudURL = body?.cloudURL
+
+    postDataEnabled?.replaceAll(`"`, ``)
+    cloudURL?.replaceAll(`"`, ``)
+
+    if (!cloudURL) {
+      set.headers['content-type'] = 'text/html; charset=utf8'
+      return eta.render(`${langName}/settings`, { lang, error: `noCloudURL` })
+    }
+    
+    await editEnvVariable(`POST_DATA_ENABLED`, postDataEnabled ?? "false")
+    await editEnvVariable(`BACKEND_URL`, cloudURL)
+
+    return redirect(`${langName === `sk` ? `/` : `/en`}`)
+  })
   .get('/currentData', () => {
     const data = Meteostanica.getData()
     return data?.[0]

+ 3 - 1
routes/lang/en.js

@@ -2,5 +2,7 @@ import { Elysia } from 'elysia'
 
 import mainRoutes from '../include/main'
 
+import lang from "../../lang/en"
+
 export default new Elysia({ prefix: "/en" })
-  .use(mainRoutes("en"))
+  .use(mainRoutes("en", lang))

+ 3 - 1
routes/lang/sk.js

@@ -2,5 +2,7 @@ import { Elysia } from 'elysia'
 
 import mainRoutes from '../include/main'
 
+import lang from "../../lang/sk"
+
 export default new Elysia()
-  .use(mainRoutes("sk"))
+  .use(mainRoutes("sk", lang))

+ 22 - 16
templates/en/index.eta

@@ -48,29 +48,31 @@
 ` %>
 
 <div id="mainStats">
-    <% /* <div class="message">
-        <p>oops</p>
+    <% /* <div class="message error">
+        <p>High temperature was detcted!</p>
+        <p>High pressure was detcted!</p>
+        <p>High humidity was detcted!</p>
     </div> */ %>
 
     <div class="stats">
         <div class="wrapper">
-            <h3>indoor</h3>
+            <h2>indoor</h2>
             <div class="values indoor">
-                <p><%~ tempIcon %> <span id="indoorTemp"><%= it.data?.[0]?.indoorTemp / 100 %></span> °C</p>
-                <p><%~ pressureIcon %> <span id="indoorPressure"><%= it.data?.[0]?.indoorPressure / 100 %></span> hPa</p>
-                <p><%~ humidityIcon %> <span id="indoorHumidity"><%= it.data?.[0]?.indoorHumidity / 100 %></span> %</p>
-                <p><%~ altitudeIcon %> <span id="indoorAltitude"><%= it.data?.[0]?.indoorAltitude / 100 %></span> m</p>
+                <p><a role="button" href="/history/indoorTemp"><%~ tempIcon %></a> <span id="indoorTemp"><%= it.data?.[0]?.indoorTemp / 100 %></span> °C</p>
+                <p><a role="button" href="/history/indoorPressure"><%~ pressureIcon %></a> <span id="indoorPressure"><%= it.data?.[0]?.indoorPressure / 100 %></span> hPa</p>
+                <p><a role="button" href="/history/indoorHumidity"><%~ humidityIcon %></a> <span id="indoorHumidity"><%= it.data?.[0]?.indoorHumidity / 100 %></span> %</p>
+                <p><a role="button" href="/history/indoorAltitude"><%~ altitudeIcon %></a> <span id="indoorAltitude"><%= it.data?.[0]?.indoorAltitude / 100 %></span> m</p>
             </div>
         </div>
 
         <div class="wrapper">
-            <h3>outdoor</h3>
+            <h2>outdoor</h2>
             <div class="values outdoor">
-                <p id="outdoorConnected"><%~ it.data?.[0]?.outdoorConnected ? `${tickIcon} connected` : `${xIcon} disconnected` %></p>
-                <p><%~ tempIcon %> <span id="outdoorTemp"><%= it.data?.[0]?.outdoorTemp / 100 %></span> °C</p>
-                <p><%~ pressureIcon %> <span id="outdoorPressure"><%= it.data?.[0]?.outdoorPressure / 100 %></span> hPa</p>
-                <p><%~ humidityIcon %> <span id="outdoorHumidity"><%= it.data?.[0]?.outdoorHumidity / 100 %></span> %</p>
-                <p><%~ altitudeIcon %> <span id="outdoorAltitude"><%= it.data?.[0]?.outdoorAltitude / 100 %></span> m</p>
+                <p><a role="button" href="/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="/history/outdoorTemp"><%~ tempIcon %></a> <span id="outdoorTemp"><%= it.data?.[0]?.outdoorTemp / 100 %></span> °C</p>
+                <p><a role="button" href="/history/outdoorPressure"><%~ pressureIcon %></a> <span id="outdoorPressure"><%= it.data?.[0]?.outdoorPressure / 100 %></span> hPa</p>
+                <p><a role="button" href="/history/outdoorHumidity"><%~ humidityIcon %></a> <span id="outdoorHumidity"><%= it.data?.[0]?.outdoorHumidity / 100 %></span> %</p>
+                <p><a role="button" href="/history/outdoorAltitude"><%~ altitudeIcon %></a> <span id="outdoorAltitude"><%= it.data?.[0]?.outdoorAltitude / 100 %></span> m</p>
             </div>
         </div>
     </div>
@@ -86,7 +88,9 @@
         const indoorHumidity = document.querySelector(`#indoorHumidity`)
         const indoorAltitude = document.querySelector(`#indoorAltitude`)
 
-        const outdoorConnected = document.querySelector(`#outdoorConnected`)
+        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`)
@@ -101,10 +105,12 @@
 
         const xIcon = `<%~ xIcon %>`
 
-        outdoorConnected.innerHTML = data?.outdoorConnected ? `${tickIcon} connected` : `${xIcon} disconnected`
+        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>
+</script>

+ 19 - 9
templates/en/partials/topbar.eta

@@ -17,6 +17,24 @@
     </svg>
 ` %>
 
+<% const homeIcon = `
+    <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="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
+            <path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
+        </g>
+    </svg>
+` %>
+
+<% const settingsIcon = `
+    <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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
+            <circle cx="12" cy="12" r="3" />
+        </g>
+    </svg>
+` %>
+
 <% const skFlag = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
         <mask id="SVGuywqVbel">
@@ -34,15 +52,6 @@
     </svg>
 ` %>
 
-<% const settingsIcon = `
-    <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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
-            <circle cx="12" cy="12" r="3" />
-        </g>
-    </svg>
-` %>
-
 <% const date = new Date() %>
 
 <header>
@@ -52,6 +61,7 @@
     
     <div class="end">
         <a role="button" href="/"><%~ skFlag %></a>
+        <a role="button" href="/en"><%~ homeIcon %></a>
         <a role="button" href="/en/settings"><%~ settingsIcon %></a>
     </div>
 

+ 27 - 0
templates/en/settings.eta

@@ -0,0 +1,27 @@
+<% layout("/en/layout") %>
+
+<%~ include("/en/partials/topbar") %>
+
+<h2>settings</h2>
+
+<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors)?.(it?.errorDetails) %>
+
+<% if (typeof errorValue === "string") { %>
+    <div class="message error">
+        <p><%= errorValue %></p>
+    </div>
+<% } %>
+
+<form action="/en/settings" method="post">
+    <div>
+      <input type="checkbox" id="postDataEnabled" name="postDataEnabled" <%= it.postDataEnabled !== "false" ? `checked` : `` %> value="true">
+      <label for="postDataEnabled">post data</label>
+    </div>
+
+    <div>
+      <label for="cloudURL">cloud url</label>
+      <input type="text" id="cloudURL" name="cloudURL" placeholder="wss://meteostanica.com" value="<%= it.cloudURL ?? `wss://meteostanica.com` %>">
+    </div>
+
+    <button type="submit">save</button>
+</form>

+ 21 - 15
templates/sk/index.eta

@@ -48,29 +48,31 @@
 ` %>
 
 <div id="mainStats">
-    <% /* <div class="message">
-        <p>oops</p>
+    <% /* <div class="message error">
+        <p>Bola zistená vysoká teplota!</p>
+        <p>Bol zistený vysoký tlak!</p>
+        <p>Bola zistená vysoká vlhkosť!</p>
     </div> */ %>
 
     <div class="stats">
         <div class="wrapper">
-            <h3>vnútorné</h3>
+            <h2>vnútorné</h2>
             <div class="values indoor">
-                <p><%~ tempIcon %> <span id="indoorTemp"><%= it.data?.[0]?.indoorTemp / 100 %></span> °C</p>
-                <p><%~ pressureIcon %> <span id="indoorPressure"><%= it.data?.[0]?.indoorPressure / 100 %></span> hPa</p>
-                <p><%~ humidityIcon %> <span id="indoorHumidity"><%= it.data?.[0]?.indoorHumidity / 100 %></span> %</p>
-                <p><%~ altitudeIcon %> <span id="indoorAltitude"><%= it.data?.[0]?.indoorAltitude / 100 %></span> m</p>
+                <p><a role="button" href="/history/indoorTemp"><%~ tempIcon %></a> <span id="indoorTemp"><%= it.data?.[0]?.indoorTemp / 100 %></span> °C</p>
+                <p><a role="button" href="/history/indoorPressure"><%~ pressureIcon %></a> <span id="indoorPressure"><%= it.data?.[0]?.indoorPressure / 100 %></span> hPa</p>
+                <p><a role="button" href="/history/indoorHumidity"><%~ humidityIcon %></a> <span id="indoorHumidity"><%= it.data?.[0]?.indoorHumidity / 100 %></span> %</p>
+                <p><a role="button" href="/history/indoorAltitude"><%~ altitudeIcon %></a> <span id="indoorAltitude"><%= it.data?.[0]?.indoorAltitude / 100 %></span> m</p>
             </div>
         </div>
 
         <div class="wrapper">
-            <h3>vonkajšie</h3>
+            <h2>vonkajšie</h2>
             <div class="values outdoor">
-                <p id="outdoorConnected"><%~ it.data?.[0]?.outdoorConnected ? `${tickIcon} pripojené` : `${xIcon} odpojené` %></p>
-                <p><%~ tempIcon %> <span id="outdoorTemp"><%= it.data?.[0]?.outdoorTemp / 100 %></span> °C</p>
-                <p><%~ pressureIcon %> <span id="outdoorPressure"><%= it.data?.[0]?.outdoorPressure / 100 %></span> hPa</p>
-                <p><%~ humidityIcon %> <span id="outdoorHumidity"><%= it.data?.[0]?.outdoorHumidity / 100 %></span> %</p>
-                <p><%~ altitudeIcon %> <span id="outdoorAltitude"><%= it.data?.[0]?.outdoorAltitude / 100 %></span> m</p>
+                <p><a role="button" href="/history/outdoorConnected" id="outdoorConnectedIcon"><%~ it.data?.[0]?.outdoorConnected ? tickIcon : xIcon %></a> <span id="outdoorConnectedText"><%= it.data?.[0]?.outdoorConnected ? `pripojené` : `odpojené` %></span></p>
+                <p><a role="button" href="/history/outdoorTemp"><%~ tempIcon %></a> <span id="outdoorTemp"><%= it.data?.[0]?.outdoorTemp / 100 %></span> °C</p>
+                <p><a role="button" href="/history/outdoorPressure"><%~ pressureIcon %></a> <span id="outdoorPressure"><%= it.data?.[0]?.outdoorPressure / 100 %></span> hPa</p>
+                <p><a role="button" href="/history/outdoorHumidity"><%~ humidityIcon %></a> <span id="outdoorHumidity"><%= it.data?.[0]?.outdoorHumidity / 100 %></span> %</p>
+                <p><a role="button" href="/history/outdoorAltitude"><%~ altitudeIcon %></a> <span id="outdoorAltitude"><%= it.data?.[0]?.outdoorAltitude / 100 %></span> m</p>
             </div>
         </div>
     </div>
@@ -86,7 +88,9 @@
         const indoorHumidity = document.querySelector(`#indoorHumidity`)
         const indoorAltitude = document.querySelector(`#indoorAltitude`)
 
-        const outdoorConnected = document.querySelector(`#outdoorConnected`)
+        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`)
@@ -101,7 +105,9 @@
 
         const xIcon = `<%~ xIcon %>`
 
-        outdoorConnected.innerHTML = data?.outdoorConnected ? `${tickIcon} pripojené` : `${xIcon} odpojené`
+        outdoorConnectedIcon.innerHTML = data?.outdoorConnected ? tickIcon : xIcon
+        outdoorConnectedText.textContent = data?.outdoorConnected ? `pripojené` : `odpojené`
+
         outdoorTemp.textContent = data?.outdoorTemp / 100
         outdoorPressure.textContent = data?.outdoorPressure / 100
         outdoorHumidity.textContent = data?.outdoorHumidity / 100

+ 19 - 9
templates/sk/partials/topbar.eta

@@ -17,6 +17,24 @@
     </svg>
 ` %>
 
+<% const homeIcon = `
+    <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="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
+            <path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
+        </g>
+    </svg>
+` %>
+
+<% const settingsIcon = `
+    <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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
+            <circle cx="12" cy="12" r="3" />
+        </g>
+    </svg>
+` %>
+
 <% const usFlag = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
         <mask id="SVGuywqVbel">
@@ -31,15 +49,6 @@
     </svg>
 ` %>
 
-<% const settingsIcon = `
-    <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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
-            <circle cx="12" cy="12" r="3" />
-        </g>
-    </svg>
-` %>
-
 <% const date = new Date() %>
 
 <header>
@@ -49,6 +58,7 @@
     
     <div class="end">
         <a role="button" href="/en"><%~ usFlag %></a>
+        <a role="button" href="/"><%~ homeIcon %></a>
         <a role="button" href="/settings"><%~ settingsIcon %></a>
     </div>
     

+ 27 - 0
templates/sk/settings.eta

@@ -0,0 +1,27 @@
+<% layout("/sk/layout") %>
+
+<%~ include("/sk/partials/topbar") %>
+
+<h2>nastavenia</h2>
+
+<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors)?.(it?.errorDetails) %>
+
+<% if (typeof errorValue === "string") { %>
+    <div class="message error">
+        <p><%= errorValue %></p>
+    </div>
+<% } %>
+
+<form action="/settings" method="post">
+    <div>
+      <input type="checkbox" id="postDataEnabled" name="postDataEnabled" <%= it.postDataEnabled !== "false" ? `checked` : `` %> value="true">
+      <label for="postDataEnabled">odosielať dáta</label>
+    </div>
+
+    <div>
+      <label for="cloudURL">cloud url</label>
+      <input type="text" id="cloudURL" name="cloudURL" placeholder="wss://meteostanica.com" value="<%= it.cloudURL ?? `wss://meteostanica.com` %>">
+    </div>
+
+    <button type="submit">uložiť</button>
+</form>

+ 46 - 0
utils/editEnvVariable.js

@@ -0,0 +1,46 @@
+export default async (key, newValue) => {
+  const envFile = Bun.file(`.env`);
+  let content = "";
+
+  // 1. Read existing file if it exists
+  if (await envFile.exists()) {
+    content = await envFile.text();
+  }
+
+  const lines = content.split("\n");
+  let keyFound = false;
+
+  // Format the value: wrap in quotes if it contains spaces
+  const safeValue = newValue.includes(" ") ? `"${newValue}"` : newValue;
+
+  // 2. Scan lines to find and replace the key
+  for (let i = 0; i < lines.length; i++) {
+    const line = lines[i];
+    
+    // Regex matches the key at the start of the line (ignoring leading spaces)
+    const keyRegex = new RegExp(`^\\s*${key}\\s*=`);
+    
+    if (keyRegex.test(line)) {
+      lines[i] = `${key}=${safeValue}`;
+      keyFound = true;
+      break;
+    }
+  }
+
+  // 3. If the key wasn't found, append it to the end
+  if (!keyFound) {
+    // Prevent appending to the same line if the file didn't end with a newline
+    if (lines.length > 0 && lines[lines.length - 1] !== "") {
+        lines.push(""); 
+    }
+    lines.push(`${key}=${safeValue}`);
+  }
+
+  // 4. Write the updated content back to the file
+  await Bun.write(`.env`, lines.join("\n"));
+
+  // 5. Update the live process environment so your app can use it immediately
+  process.env[key] = newValue;
+
+//   console.log(`✅ Set ${key} to newly updated value in ${ENV_PATH}`);
+}

+ 28 - 0
utils/reloadEnv.js

@@ -0,0 +1,28 @@
+export default async () => {
+    const file = Bun.file(`.env`);
+
+    if ((await file.exists())) {
+        const text = await file.text();
+        const lines = text.split("\n");
+
+        for (const line of lines) {
+            // Ignore comments and empty lines
+            if (!line || line.trim().startsWith("#")) continue;
+
+            // Match key=value pairs
+            const match = line.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/);
+            if (match) {
+            const key = match[1];
+            let value = match[2] || "";
+            
+            // Strip surrounding quotes if they exist
+            value = value.replace(/^(['"])(.*)\1$/, "$2").trim();
+            
+            // Update the environment variable
+            process.env[key] = value;
+            }
+        }
+    };
+
+    console.log(`.env reloaded`);
+}