marek 1 месяц назад
Родитель
Сommit
4c056241e5

+ 4 - 0
assets/css/style.css

@@ -181,6 +181,10 @@ form > div {
     margin-block: 0.5em;
 }
 
+.container-row.menu {
+    justify-content: center;
+}
+
 hr {
     width: 100%;
     margin-block: 1em;

Разница между файлами не показана из-за своего большого размера
+ 6 - 0
assets/js/chart.umd.js


+ 60 - 1
lang/en.js

@@ -20,6 +20,23 @@ const general = {
     },
   },
 
+  dateFormats: {
+    months: {
+      [`01`]: () => `January`,
+      [`02`]: () => `February`,
+      [`03`]: () => `March`,
+      [`04`]: () => `April`,
+      [`05`]: () => `May`,
+      [`06`]: () => `June`,
+      [`07`]: () => `July`,
+      [`08`]: () => `August`,
+      [`09`]: () => `September`,
+      [`10`]: () => `October`,
+      [`11`]: () => `November`,
+      [`12`]: () => `December`,
+    }
+  },
+
   functionWords: {
     and: () => `and`,
   },
@@ -34,7 +51,36 @@ const general = {
   },
 }
 
+const icons = {
+  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>
+    `,
+
+  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>
+    `,
+
+  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>
+    `,
+
+  bluetoothConnectionIcon: () => `
+        <svg 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="m7 7l10 10l-5 5V2l5 5L7 17m11-5h3M3 12h3" />
+        </svg>
+    `
+}
+
 export default {
+  general,
+  icons,
+  
   emails: {
     auth: {
       subject: () => `login link`,
@@ -58,7 +104,7 @@ export default {
       verificationTokenUsedOrExpired: () => `verification token already used or expired. please try again.`,
       invalidVerificationCode: () => `invalid verification code. please try again.`,
       loginNeeded: () => `please log in first.`,
-      
+
       ratelimits: {
         email: (details) => {
           if (!Number.parseInt(details?.duration)) return `too many requests for this email. try again later.`
@@ -92,6 +138,19 @@ export default {
 
       turnstile: general.errors.turnstile,
     },
+
+    history: {
+      properties: {
+        indoorTemp: () => `${icons.tempIcon()} indoor temperature`,
+        indoorPressure: () => `${icons.pressureIcon()} indoor pressure`,
+        indoorHumidity: () => `${icons.humidityIcon()} indoor humidity`,
+
+        outdoorConnected: () => `${icons.bluetoothConnectionIcon()} external unit connection`,
+        outdoorTemp: () => `${icons.tempIcon()} outdoor temperature`,
+        outdoorPressure: () => `${icons.pressureIcon()} outdoor pressure`,
+        outdoorHumidity: () => `${icons.humidityIcon()} outdoor humidity`,
+      },
+    },
   },
 
   websocket: {

+ 60 - 1
lang/sk.js

@@ -24,6 +24,23 @@ const general = {
     },
   },
 
+  dateFormats: {
+    months: {
+      [`01`]: () => `január`,
+      [`02`]: () => `február`,
+      [`03`]: () => `marec`,
+      [`04`]: () => `apríl`,
+      [`05`]: () => `máj`,
+      [`06`]: () => `jún`,
+      [`07`]: () => `júl`,
+      [`08`]: () => `august`,
+      [`09`]: () => `september`,
+      [`10`]: () => `október`,
+      [`11`]: () => `november`,
+      [`12`]: () => `december`,
+    }
+  },
+
   functionWords: {
     and: () => `a`,
   },
@@ -38,7 +55,36 @@ const general = {
   },
 }
 
+const icons = {
+  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>
+    `,
+
+  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>
+    `,
+
+  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>
+    `,
+
+  bluetoothConnectionIcon: () => `
+        <svg 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="m7 7l10 10l-5 5V2l5 5L7 17m11-5h3M3 12h3" />
+        </svg>
+    `
+}
+
 export default {
+  general,
+  icons,
+
   emails: {
     auth: {
       subject: () => `prihlasovací link`,
@@ -62,7 +108,7 @@ export default {
       verificationTokenUsedOrExpired: () => `verifikačný token už bol použitý alebo vypršal. prosím skúste to znova.`,
       invalidVerificationCode: () => `neplatný verifikačný kód. prosím skúste to znova.`,
       loginNeeded: () => `najprv sa prihláste prosím.`,
-      
+
       ratelimits: {
         email: (details) => {
           if (!Number.parseInt(details?.duration)) return `príliš veľa žiadostí pre tento email. skúste to znova neskôr.`
@@ -96,6 +142,19 @@ export default {
 
       turnstile: general.errors.turnstile,
     },
+
+    history: {
+      properties: {
+        indoorTemp: () => `${icons.tempIcon()} vnutorná teplota`,
+        indoorPressure: () => `${icons.pressureIcon()} vnutorný tlak`,
+        indoorHumidity: () => `${icons.humidityIcon()} vnutorná vlhkosť`,
+
+        outdoorConnected: () => `${icons.bluetoothConnectionIcon()} pripojenie externej jednotky`,
+        outdoorTemp: () => `${icons.tempIcon()} vonkajšia teplota`,
+        outdoorPressure: () => `${icons.pressureIcon()} vonkajší tlak`,
+        outdoorHumidity: () => `${icons.humidityIcon()} vonkajšia vlhkosť`,
+      },
+    },
   },
 
   websocket: {

+ 28 - 0
routes/include/panel/stations.js

@@ -8,7 +8,10 @@ import Meteostanice from '../../../utils/meteostanice';
 import validateTurnstile from '../../../utils/validateTurnstile';
 import normalizeEmail from '../../../utils/normalizeEmail';
 
+import stationsHistory from './stationsHistory';
+
 export default (langName, lang) => new Elysia({ prefix: "/stations" })
+  .use(stationsHistory(langName, lang))
   .get("/", async ({ cookie, redirect, set }) => {
     const token = cookie.session.value
     const session = await Auth.getSession(token)
@@ -280,4 +283,29 @@ export default (langName, lang) => new Elysia({ prefix: "/stations" })
     Meteostanice.delete(meteostanica.id)
 
     return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel/stations`)
+  })
+  .get('/:station/currentData', async ({ cookie, redirect, set, params: { station } }) => {
+    const token = cookie.session.value
+    const session = await Auth.getSession(token)
+
+    if (!session) {
+      return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
+    }
+
+    const user = Auth.getUser(session.email)
+    
+    if (!station) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user })
+    }
+    
+    const meteostanica = Meteostanice.get(session.email, station)
+    
+    if (!meteostanica) {
+      set.headers['content-type'] = 'text/html; charset=utf8'
+      return eta.render(`${langName}/panel/stations/notFound`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user })
+    }
+
+    const data = Meteostanice.getData(station)
+    return data?.[0]
   })

+ 242 - 0
routes/include/panel/stationsHistory.js

@@ -0,0 +1,242 @@
+import { Elysia } from 'elysia'
+
+import { Eta } from "eta"
+const eta = new Eta({ views: "./templates" })
+
+import Auth from '../../../utils/auth';
+import Meteostanice from '../../../utils/meteostanice'
+
+export default (langName, lang) => new Elysia({ prefix: "/:station/history" })
+  .get('/', async ({ cookie, redirect, set, params: { station } }) => {
+    const token = cookie.session.value
+    const session = await Auth.getSession(token)
+
+    if (!session) {
+        return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
+    }
+
+    const user = Auth.getUser(session.email)
+    
+    if (!station) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const meteostanica = Meteostanice.get(session.email, station)
+    
+    if (!meteostanica) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+
+    set.headers['content-type'] = 'text/html; charset=utf8'
+    return eta.render(`${langName}/panel/stations/history/index`, { lang, meteostanica, user })
+  })
+  .get(`/:property`, async ({ cookie, redirect, set, params: { station, property }, query: { day, month, year } }) => {
+    const token = cookie.session.value
+    const session = await Auth.getSession(token)
+
+    if (!session) {
+        return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
+    }
+
+    const user = Auth.getUser(session.email)
+    
+    if (!station) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const meteostanica = Meteostanice.get(session.email, station)
+    
+    if (!meteostanica) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const dateMap = Meteostanice.getDateMap(station)
+    
+    const years = Object.keys(dateMap)
+    const months = Object.keys(dateMap[years[years.length - 1]])
+    const days = dateMap[years[years.length - 1]][months[months.length - 1]]
+
+    const lastYear = years[years.length - 1]
+    const lastMonth = months[months.length - 1]
+    const lastDay = days[days.length - 1]
+
+    const data = Meteostanice.getDataPropertyDaily(station, property, `${year ?? lastYear}-${month ?? lastMonth}-${day ?? lastDay}`)
+
+    if (!data) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/history/notFound`, { lang, user, property })
+    }
+
+    set.headers['content-type'] = 'text/html; charset=utf8'
+    return eta.render(`${langName}/panel/stations/history/property`, { lang, user, dateMap: { years, months, days }, type: `daily`, property, meteostanica, data })
+  })
+ .get(`/:property/daily`, async ({ cookie, redirect, set, params: { station, property }, query: { day, month, year } }) => {
+    const token = cookie.session.value
+    const session = await Auth.getSession(token)
+
+    if (!session) {
+        return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
+    }
+
+    const user = Auth.getUser(session.email)
+    
+    if (!station) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const meteostanica = Meteostanice.get(session.email, station)
+    
+    if (!meteostanica) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const dateMap = Meteostanice.getDateMap(station)
+    
+    const years = Object.keys(dateMap)
+    const months = Object.keys(dateMap[years[years.length - 1]])
+    const days = dateMap[years[years.length - 1]][months[months.length - 1]]
+
+    const lastYear = years[years.length - 1]
+    const lastMonth = months[months.length - 1]
+    const lastDay = days[days.length - 1]
+
+    const data = Meteostanice.getDataPropertyDaily(station, property, `${year ?? lastYear}-${month ?? lastMonth}-${day ?? lastDay}`)
+
+    if (!data) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/history/notFound`, { lang, user, property })
+    }
+
+    set.headers['content-type'] = 'text/html; charset=utf8'
+    return eta.render(`${langName}/panel/stations/history/property`, { lang, user, dateMap: { years, months, days }, type: `daily`, property, meteostanica, data })
+  })
+  .get(`/:property/monthly`, async ({ cookie, redirect, set, params: { station, property }, query: { month, year } }) => {
+    const token = cookie.session.value
+    const session = await Auth.getSession(token)
+
+    if (!session) {
+        return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
+    }
+
+    const user = Auth.getUser(session.email)
+    
+    if (!station) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const meteostanica = Meteostanice.get(session.email, station)
+    
+    if (!meteostanica) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const dateMap = Meteostanice.getDateMap(station)
+    
+    const years = Object.keys(dateMap)
+    const months = Object.keys(dateMap[years[years.length - 1]])
+
+    const lastYear = years[years.length - 1]
+    const lastMonth = months[months.length - 1]
+
+    const data = Meteostanice.getDataPropertyMonthly(station, property, `${year ?? lastYear}-${month ?? lastMonth}`)
+
+    if (!data) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/history/notFound`, { lang, user, property })
+    }
+
+    set.headers['content-type'] = 'text/html; charset=utf8'
+    return eta.render(`${langName}/panel/stations/history/property`, { lang, user, dateMap: { years, months }, type: `monthly`, property, meteostanica, data })
+  })
+  .get(`/:property/yearly`, async ({ cookie, redirect, set, params: { station, property }, query: { month, year } }) => {
+    const token = cookie.session.value
+    const session = await Auth.getSession(token)
+
+    if (!session) {
+        return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
+    }
+
+    const user = Auth.getUser(session.email)
+    
+    if (!station) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const meteostanica = Meteostanice.get(session.email, station)
+    
+    if (!meteostanica) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const dateMap = Meteostanice.getDateMap(station)
+    
+    const years = Object.keys(dateMap)
+
+    const lastYear = years[years.length - 1]
+
+    const data = Meteostanice.getDataPropertyYearly(station, property, year ?? lastYear)
+
+    if (!data) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/history/notFound`, { lang, user, property })
+    }
+
+    set.headers['content-type'] = 'text/html; charset=utf8'
+    return eta.render(`${langName}/panel/stations/history/property`, { lang, user, dateMap: { years }, type: `yearly`, property, meteostanica, data })
+  })
+  .get(`/:property/allTime`, async ({ cookie, redirect, set, params: { station, property }, query: { month, year } }) => {
+    const token = cookie.session.value
+    const session = await Auth.getSession(token)
+
+    if (!session) {
+        return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
+    }
+
+    const user = Auth.getUser(session.email)
+    
+    if (!station) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const meteostanica = Meteostanice.get(session.email, station)
+    
+    if (!meteostanica) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
+    }
+    
+    const dateMap = Meteostanice.getDateMap(station)
+
+    const data = Meteostanice.getDataPropertyAllTime(station, property)
+
+    if (!data) {
+        set.headers['content-type'] = 'text/html; charset=utf8'
+        return eta.render(`${langName}/panel/stations/history/notFound`, { lang, user, property })
+    }
+
+    set.headers['content-type'] = 'text/html; charset=utf8'
+    return eta.render(`${langName}/panel/stations/history/property`, { lang, user, dateMap, type: `allTime`, property, meteostanica, data })
+  })
+//   .get(`/:property/allTime`, ({ params: { property }, set }) => {
+//     const data = Meteostanica.getDataPropertyAllTime(property)
+//     const dateMap = Meteostanica.getDateMap()
+
+//     if (!data) {
+//         set.headers['content-type'] = 'text/html; charset=utf8'
+//         return eta.render(`${langName}/history/notFound`, { property })
+//     }
+
+//     set.headers['content-type'] = 'text/html; charset=utf8'
+//     return eta.render(`${langName}/history/property`, { lang, dateMap, type: `allTime`, property, data })
+//   })

+ 33 - 0
templates/en/panel/stations/history/index.eta

@@ -0,0 +1,33 @@
+<% layout("/en/panel/stations/history/layout", { title: it.meteostanica.name }) %>
+
+<%~ include("/en/panel/partials/navbar") %>
+
+<% 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>
+` %>
+
+<%~ include("/en/panel/stations/partials/details", { meteostanica: it.meteostanica }) %>
+
+<h2><%~ historyIcon %> history</h2>
+
+<h3>indoor</h3>
+
+<div class="container-row">
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/indoorTemp"><%~ it?.lang?.stations?.history.properties?.indoorTemp() %></a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/indoorPressure"><%~ it?.lang?.stations?.history.properties?.indoorPressure() %></a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/indoorHumidity"><%~ it?.lang?.stations?.history.properties?.indoorHumidity() %></a>
+</div>
+
+<h3>outdoor</h3>
+
+<div class="container-row">
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorConnected"><%~ it?.lang?.stations?.history.properties?.outdoorConnected() %></a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorTemp"><%~ it?.lang?.stations?.history.properties?.outdoorTemp() %></a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorPressure"><%~ it?.lang?.stations?.history.properties?.outdoorPressure() %></a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/outdoorHumidity"><%~ it?.lang?.stations?.history.properties?.outdoorHumidity() %></a>
+</div>

+ 14 - 0
templates/en/panel/stations/history/layout.eta

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><%= it.title ? `${it.title} — meteostanica` : "meteostanica" %></title>
+    <link rel="stylesheet" href="/assets/css/style.css">
+</head>
+<body>
+    <script src="/assets/js/chart.umd.js"></script>
+    
+    <%~ it.body %>
+</body>
+</html>

+ 101 - 0
templates/en/panel/stations/history/property.eta

@@ -0,0 +1,101 @@
+<% layout("/en/panel/stations/history/layout", { title: it.meteostanica.name }) %>
+
+<%~ include("/en/panel/partials/navbar") %>
+
+<% const backIcon = `
+    <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 19l-7-7l7-7m7 7H5" />
+    </svg>
+` %>
+
+<%~ include("/en/panel/stations/partials/details", { meteostanica: it.meteostanica }) %>
+
+<div class="container-row">
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history"><%~ backIcon %></a>
+    <h2><%~ it?.lang?.stations?.history.properties?.[it?.property]() %></h2>
+</div>
+
+<div class="historyLinks">
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/daily">daily</a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/monthly">monthly</a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/yearly">yearly</a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/allTime">all time</a>
+</div>
+
+<% if (it?.dateMap?.years) { %>
+    <form id="historyForm">
+        <% if (it?.dateMap?.days) { %>
+            <div>
+            <label for="day">day</label>
+            <select id="day" name="day">
+                <% for (const item of it?.dateMap?.days) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.days[it?.dateMap?.days.length - 1] ? `selected` : `` %> ><%= item %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <% if (it?.dateMap?.months) { %>
+            <div>
+            <label for="month">month</label>
+            <select id="month" name="month">
+                <% for (const item of it?.dateMap?.months) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.months[it?.dateMap?.months.length - 1] ? `selected` : `` %> > <%= it?.lang?.general.dateFormats.months?.[item]() %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <div>
+            <label for="year">year</label>
+            <select id="year" name="year">
+                <% for (const item of it?.dateMap?.years) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.years[it?.dateMap?.years.length - 1] ? `selected` : `` %> > <%= item %></option>
+                <% } %>
+            </select>
+        </div>
+
+        <button type="submit">load</button>
+    </form>
+<% } %>
+
+<% const time = [] %>
+<% const data = [] %>
+
+<% for (const item of it.data) { %>
+    <% time.push(item.timeMark) %>
+    
+    <% if (it.property === "outdoorConnected") {%>
+        <% data.push(item.value) %>
+        <% continue %>
+    <% } %>
+
+    <% data.push(item.value / 100) %>
+<% } %>
+
+<div>
+    <canvas id="historyChart"></canvas>
+</div>
+
+<script>
+  const ctx = document.getElementById('historyChart');
+
+  new Chart(ctx, {
+    type: 'bar',
+    data: {
+      labels: <%~ JSON.stringify(time, null, 2) %>,
+      datasets: [{
+        label: `<%= it.property %>`,
+        data: <%~ JSON.stringify(data, null, 2) %>,
+        borderWidth: 1
+      }]
+    },
+    options: {
+      scales: {
+        y: {
+          beginAtZero: true
+        }
+      }
+    }
+  });
+</script>

+ 76 - 0
templates/en/panel/stations/partials/details.eta

@@ -0,0 +1,76 @@
+<% const backIcon = `
+    <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 19l-7-7l7-7m7 7H5" />
+    </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">
+            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
+            <path d="M3 3v5h5" />
+        </g>
+    </svg>
+` %>
+
+<% const currentIcon = `
+    <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 10l2 2l4-4" />
+            <rect width="20" height="14" x="2" y="3" rx="2" />
+            <path d="M12 17v4m-4 0h8" />
+        </g>
+    </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">
+            <path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
+            <path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />
+        </g>
+    </svg>
+` %>
+
+<% const deleteIcon = `
+  <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="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
+  </svg>
+` %>
+
+<div class="container-row">
+    <a role="button" href="/en/panel/stations"><%~ backIcon %></a>
+    <h2><%= it.meteostanica.name %></h2>
+</div>
+
+<% if (it.meteostanica.description) { %>
+    <p><strong>description:</strong> <%= it.meteostanica.description %></p>
+<% } %>
+
+<p><strong>created:</strong> <%= it.meteostanica.timestamp %></p>
+
+<div class="container-row">
+    <p><strong>websocket key:</strong> <%= it.meteostanica.websocketKey %></p>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/resetWebsocketKey"><%~ resetIcon %> reset</a>
+</div>
+
+<div class="container-row">
+    <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>
+
+<hr>
+
+<div class="container-row menu">
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>"><%~ currentIcon %> current</a>
+    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/history"><%~ historyIcon %> history</a>
+</div>

+ 2 - 67
templates/en/panel/stations/station.eta

@@ -2,12 +2,6 @@
 
 <%~ include("/en/panel/partials/navbar") %>
 
-<% const backIcon = `
-    <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 19l-7-7l7-7m7 7H5" />
-    </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">
@@ -44,62 +38,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">
-            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
-            <path d="M3 3v5h5" />
-        </g>
-    </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">
-            <path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
-            <path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />
-        </g>
-    </svg>
-` %>
-
-<% const deleteIcon = `
-  <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="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
-  </svg>
-` %>
-
-<div class="container-row">
-    <a role="button" href="/en/panel/stations"><%~ backIcon %></a>
-    <h2><%= it.meteostanica.name %></h2>
-</div>
-
-<% if (it.meteostanica.description) { %>
-    <p><strong>description:</strong> <%= it.meteostanica.description %></p>
-<% } %>
-
-<p><strong>created:</strong> <%= it.meteostanica.timestamp %></p>
-
-<div class="container-row">
-    <p><strong>websocket key:</strong> <%= it.meteostanica.websocketKey %></p>
-    <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/resetWebsocketKey"><%~ resetIcon %> reset</a>
-</div>
-
-<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>
+<%~ include("/en/panel/stations/partials/details", { meteostanica: it.meteostanica }) %>
 
 <% if (it.data?.[0]) { %>
     <div id="mainStats">
@@ -135,13 +74,12 @@
 
     <script defer>
         setInterval(async () => {
-            const response = await fetch('/en/panel/stations/<%= it.meteostanica.id %>/currentData')
+            const response = await fetch('/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`)
@@ -149,12 +87,10 @@
             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 %>`
 
@@ -166,7 +102,6 @@
             outdoorTemp.textContent = data?.outdoorTemp / 100
             outdoorPressure.textContent = data?.outdoorPressure / 100
             outdoorHumidity.textContent = data?.outdoorHumidity / 100
-            outdoorAltitude.textContent = data?.outdoorAltitude / 100
         }, 10000)
     </script>
 <% } %>

+ 33 - 0
templates/sk/panel/stations/history/index.eta

@@ -0,0 +1,33 @@
+<% layout("/sk/panel/stations/history/layout", { title: it.meteostanica.name }) %>
+
+<%~ include("/sk/panel/partials/navbar") %>
+
+<% 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>
+` %>
+
+<%~ include("/sk/panel/stations/partials/details", { meteostanica: it.meteostanica }) %>
+
+<h2><%~ historyIcon %> história</h2>
+
+<h3>vnutorné</h3>
+
+<div class="container-row">
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/indoorTemp"><%~ it?.lang?.stations?.history.properties?.indoorTemp() %></a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/indoorPressure"><%~ it?.lang?.stations?.history.properties?.indoorPressure() %></a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/indoorHumidity"><%~ it?.lang?.stations?.history.properties?.indoorHumidity() %></a>
+</div>
+
+<h3>vonkajšie</h3>
+
+<div class="container-row">
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/outdoorConnected"><%~ it?.lang?.stations?.history.properties?.outdoorConnected() %></a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/outdoorTemp"><%~ it?.lang?.stations?.history.properties?.outdoorTemp() %></a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/outdoorPressure"><%~ it?.lang?.stations?.history.properties?.outdoorPressure() %></a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/outdoorHumidity"><%~ it?.lang?.stations?.history.properties?.outdoorHumidity() %></a>
+</div>

+ 14 - 0
templates/sk/panel/stations/history/layout.eta

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="sk">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><%= it.title ? `${it.title} — meteostanica` : "meteostanica" %></title>
+    <link rel="stylesheet" href="/assets/css/style.css">
+</head>
+<body>
+    <script src="/assets/js/chart.umd.js"></script>
+    
+    <%~ it.body %>
+</body>
+</html>

+ 101 - 0
templates/sk/panel/stations/history/property.eta

@@ -0,0 +1,101 @@
+<% layout("/sk/panel/stations/history/layout", { title: it.meteostanica.name }) %>
+
+<%~ include("/sk/panel/partials/navbar") %>
+
+<% const backIcon = `
+    <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 19l-7-7l7-7m7 7H5" />
+    </svg>
+` %>
+
+<%~ include("/sk/panel/stations/partials/details", { meteostanica: it.meteostanica }) %>
+
+<div class="container-row">
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history"><%~ backIcon %></a>
+    <h2><%~ it?.lang?.stations?.history.properties?.[it?.property]() %></h2>
+</div>
+
+<div class="historyLinks">
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/daily">deň</a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/monthly">mesiac</a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/yearly">rok</a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history/<%= it.property %>/allTime">celý čas</a>
+</div>
+
+<% if (it?.dateMap?.years) { %>
+    <form id="historyForm">
+        <% if (it?.dateMap?.days) { %>
+            <div>
+            <label for="day">deň</label>
+            <select id="day" name="day">
+                <% for (const item of it?.dateMap?.days) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.days[it?.dateMap?.days.length - 1] ? `selected` : `` %> ><%= item %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <% if (it?.dateMap?.months) { %>
+            <div>
+            <label for="month">mesiac</label>
+            <select id="month" name="month">
+                <% for (const item of it?.dateMap?.months) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.months[it?.dateMap?.months.length - 1] ? `selected` : `` %> > <%= it?.lang?.general.dateFormats.months?.[item]() %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <div>
+            <label for="year">rok</label>
+            <select id="year" name="year">
+                <% for (const item of it?.dateMap?.years) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.years[it?.dateMap?.years.length - 1] ? `selected` : `` %> > <%= item %></option>
+                <% } %>
+            </select>
+        </div>
+
+        <button type="submit">načítať</button>
+    </form>
+<% } %>
+
+<% const time = [] %>
+<% const data = [] %>
+
+<% for (const item of it.data) { %>
+    <% time.push(item.timeMark) %>
+    
+    <% if (it.property === "outdoorConnected") {%>
+        <% data.push(item.value) %>
+        <% continue %>
+    <% } %>
+
+    <% data.push(item.value / 100) %>
+<% } %>
+
+<div>
+    <canvas id="historyChart"></canvas>
+</div>
+
+<script>
+  const ctx = document.getElementById('historyChart');
+
+  new Chart(ctx, {
+    type: 'bar',
+    data: {
+      labels: <%~ JSON.stringify(time, null, 2) %>,
+      datasets: [{
+        label: `<%= it.property %>`,
+        data: <%~ JSON.stringify(data, null, 2) %>,
+        borderWidth: 1
+      }]
+    },
+    options: {
+      scales: {
+        y: {
+          beginAtZero: true
+        }
+      }
+    }
+  });
+</script>

+ 76 - 0
templates/sk/panel/stations/partials/details.eta

@@ -0,0 +1,76 @@
+<% const backIcon = `
+    <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 19l-7-7l7-7m7 7H5" />
+    </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">
+            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
+            <path d="M3 3v5h5" />
+        </g>
+    </svg>
+` %>
+
+<% const currentIcon = `
+    <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 10l2 2l4-4" />
+            <rect width="20" height="14" x="2" y="3" rx="2" />
+            <path d="M12 17v4m-4 0h8" />
+        </g>
+    </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">
+            <path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
+            <path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />
+        </g>
+    </svg>
+` %>
+
+<% const deleteIcon = `
+  <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="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
+  </svg>
+` %>
+
+<div class="container-row">
+    <a role="button" href="/panel/stations"><%~ backIcon %></a>
+    <h2><%= it.meteostanica.name %></h2>
+</div>
+
+<% if (it.meteostanica.description) { %>
+    <p><strong>popis:</strong> <%= it.meteostanica.description %></p>
+<% } %>
+
+<p><strong>vytvorená:</strong> <%= it.meteostanica.timestamp %></p>
+
+<div class="container-row">
+    <p><strong>websocket kľúč:</strong> <%= it.meteostanica.websocketKey %></p>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/resetWebsocketKey"><%~ resetIcon %> reset</a>
+</div>
+
+<div class="container-row">
+    <a role="button" class="primary" href="/panel/stations/<%= it.meteostanica.id %>/edit"><%~ editIcon %> upraviť</a>
+    <a role="button" class="danger" href="/panel/stations/<%= it.meteostanica.id %>/delete"><%~ deleteIcon %> odstrániť</a>
+</div>
+
+<hr>
+
+<div class="container-row menu">
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>"><%~ currentIcon %> aktuálne</a>
+    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history"><%~ historyIcon %> história</a>
+</div>

+ 2 - 67
templates/sk/panel/stations/station.eta

@@ -2,12 +2,6 @@
 
 <%~ include("/sk/panel/partials/navbar") %>
 
-<% const backIcon = `
-    <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 19l-7-7l7-7m7 7H5" />
-    </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">
@@ -44,62 +38,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">
-            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
-            <path d="M3 3v5h5" />
-        </g>
-    </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">
-            <path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
-            <path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />
-        </g>
-    </svg>
-` %>
-
-<% const deleteIcon = `
-  <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="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
-  </svg>
-` %>
-
-<div class="container-row">
-    <a role="button" href="/panel/stations"><%~ backIcon %></a>
-    <h2><%= it.meteostanica.name %></h2>
-</div>
-
-<% if (it.meteostanica.description) { %>
-    <p><strong>popis:</strong> <%= it.meteostanica.description %></p>
-<% } %>
-
-<p><strong>vytvorená:</strong> <%= it.meteostanica.timestamp %></p>
-
-<div class="container-row">
-    <p><strong>websocket kľúč:</strong> <%= it.meteostanica.websocketKey %></p>
-    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/resetWebsocketKey"><%~ resetIcon %> reset</a>
-</div>
-
-<hr>
-
-<div class="container-row">
-    <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/history"><%~ historyIcon %> história</a>
-    <a role="button" class="primary" href="/panel/stations/<%= it.meteostanica.id %>/edit"><%~ editIcon %> upraviť</a>
-    <a role="button" class="danger" href="/panel/stations/<%= it.meteostanica.id %>/delete"><%~ deleteIcon %> odstrániť</a>
-</div>
+<%~ include("/sk/panel/stations/partials/details", { meteostanica: it.meteostanica }) %>
 
 <% if (it.data?.[0]) { %>
     <div id="mainStats">
@@ -141,7 +80,6 @@
             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`)
@@ -149,24 +87,21 @@
             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`
+            outdoorConnectedText.textContent = data?.outdoorConnected ? `pripojené` : `odpojené`
 
             outdoorTemp.textContent = data?.outdoorTemp / 100
             outdoorPressure.textContent = data?.outdoorPressure / 100
             outdoorHumidity.textContent = data?.outdoorHumidity / 100
-            outdoorAltitude.textContent = data?.outdoorAltitude / 100
         }, 10000)
     </script>
 <% } %>

+ 11 - 11
utils/meteostanice.js

@@ -182,10 +182,10 @@ export default class Meteostanice {
     }
 
     static getDataProperty(meteostanica, property) {
-        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
         if (!tableNames.find(i => i.name === property)) return null
 
-        const statement = meteostanicaDB.prepare(`
+        const statement = meteostaniceDB.prepare(`
             SELECT ${property}
             FROM data
             WHERE meteostanica = $meteostanica
@@ -201,10 +201,10 @@ export default class Meteostanice {
     }
 
     static getDataPropertyDaily(meteostanica, property, date) {
-        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
         if (!tableNames.find(i => i.name === property)) return null
 
-        const statement = meteostanicaDB.prepare(`
+        const statement = meteostaniceDB.prepare(`
             SELECT strftime('%Y-%m-%d %H:00:00', timestamp) AS timeMark, 
                 AVG(${property}) AS value
             FROM data
@@ -219,10 +219,10 @@ export default class Meteostanice {
     }
 
     static getDataPropertyMonthly(meteostanica, property, yearMonth) {
-        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
         if (!tableNames.find(i => i.name === property)) return null
 
-        const statement = meteostanicaDB.prepare(`
+        const statement = meteostaniceDB.prepare(`
             SELECT date(timestamp) AS timeMark, 
                 AVG(${property}) AS value
             FROM data
@@ -237,10 +237,10 @@ export default class Meteostanice {
     }
 
     static getDataPropertyYearly(meteostanica, property, year) {
-        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
         if (!tableNames.find(i => i.name === property)) return null
 
-        const statement = meteostanicaDB.prepare(`
+        const statement = meteostaniceDB.prepare(`
             SELECT strftime('%Y-%m', timestamp) AS timeMark, 
                 AVG(${property}) AS value
             FROM data
@@ -255,10 +255,10 @@ export default class Meteostanice {
     }
 
     static getDataPropertyAllTime(meteostanica, property) {
-        const tableNames = meteostanicaDB.prepare(`PRAGMA table_info('data');`).all()
+        const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
         if (!tableNames.find(i => i.name === property)) return null
 
-        const statement = meteostanicaDB.prepare(`
+        const statement = meteostaniceDB.prepare(`
             SELECT strftime('%Y', timestamp) AS timeMark, 
                 AVG(${property}) AS value
             FROM data
@@ -273,7 +273,7 @@ export default class Meteostanice {
     }
 
     static getDateMap(meteostanica) {
-        const statement = meteostanicaDB.query("SELECT DISTINCT date(timestamp) as d FROM data WHERE meteostanica = ? ORDER BY d ASC");
+        const statement = meteostaniceDB.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) => {

Некоторые файлы не были показаны из-за большого количества измененных файлов