ctucx.git: nixfiles

ctucx' nixfiles

commit f32d5b3946addd830d059cbd22b427f1bc4c3d5b
parent 1c0ea98ef0b305c1be628daec41636cb1ae09aa6
Author: Leah (ctucx) <git@ctu.cx>
Date: Tue, 13 Dec 2022 16:41:48 +0100

machines/lollo/smarthome/mqtt-webui: add departures
9 files changed, 509 insertions(+), 296 deletions(-)
M
flake.lock
|
31
++++++++++++++++++++++++++++---
M
flake.nix
|
9
+++++++++
M
machines/lollo/smarthome/default.nix
|
3
++-
A
machines/lollo/smarthome/departures2mqtt.nix
|
19
+++++++++++++++++++
D
machines/lollo/smarthome/mqtt-webui-config.nix
|
254
-------------------------------------------------------------------------------
D
machines/lollo/smarthome/mqtt-webui.nix
|
38
--------------------------------------
A
machines/lollo/smarthome/mqtt-webui/config.nix
|
320
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/smarthome/mqtt-webui/default.nix
|
39
+++++++++++++++++++++++++++++++++++++++
A
machines/lollo/smarthome/mqtt-webui/extra-css/extra.css
|
92
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/flake.lock b/flake.lock
@@ -131,6 +131,30 @@
         "type": "github"
       }
     },
+    "departures2mqtt": {
+      "inputs": {
+        "flake-utils": [
+          "flake-utils"
+        ],
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1670953611,
+        "narHash": "sha256-Nux7niW2fFY/kAfUrlHre/PumoMq2JryTKvIVnJlhhE=",
+        "ref": "master",
+        "rev": "41a0e352cc99ccfb918af7a5316842fb37b1fd4f",
+        "revCount": 1,
+        "type": "git",
+        "url": "https://git.ctu.cx/departures2mqtt"
+      },
+      "original": {
+        "ref": "master",
+        "type": "git",
+        "url": "https://git.ctu.cx/departures2mqtt"
+      }
+    },
     "dns-nix": {
       "inputs": {
         "flake-utils": "flake-utils_2",

@@ -379,10 +403,10 @@
         ]
       },
       "locked": {
-        "lastModified": 1670944808,
-        "narHash": "sha256-NRj7wq9khChJUCf8MXcH1BHaKvdpv9MFUXbG2kY/c1c=",
+        "lastModified": 1670953149,
+        "narHash": "sha256-VADBy42h3jxcrlMtwaNIf3Xo1B/gnOt3pj0hRde65Lo=",
         "ref": "master",
-        "rev": "0f0b70aed5014799e20bdcbe328d413c99cd922e",
+        "rev": "06253735ad68b6133a4fbcac5a2b847aae1a7b5a",
         "revCount": 18,
         "type": "git",
         "url": "https://git.ctu.cx/mqtt-webui"

@@ -553,6 +577,7 @@
         "ctucx-gallery": "ctucx-gallery",
         "ctucx-things": "ctucx-things",
         "darwin": "darwin",
+        "departures2mqtt": "departures2mqtt",
         "dns-nix": "dns-nix",
         "dnsmasq-lease-overview": "dnsmasq-lease-overview",
         "flake-utils": "flake-utils_3",
diff --git a/flake.nix b/flake.nix
@@ -21,6 +21,7 @@
 
       inputs.sdm2mqtt.overlay
       inputs.lacrosse2mqtt.overlay
+      inputs.departures2mqtt.overlay
       inputs.mqtt-webui.overlay
 
       inputs.ctucx-things.overlay

@@ -169,6 +170,14 @@
       inputs.flake-utils.follows = "flake-utils";
     };
 
+    departures2mqtt = {
+      type  = "git";
+      url   = "https://git.ctu.cx/departures2mqtt";
+      ref   = "master";
+      inputs.nixpkgs.follows     = "nixpkgs";
+      inputs.flake-utils.follows = "flake-utils";
+    };
+
     mqtt-webui = {
       type  = "git";
       url   = "https://git.ctu.cx/mqtt-webui";
diff --git a/machines/lollo/smarthome/default.nix b/machines/lollo/smarthome/default.nix
@@ -9,11 +9,12 @@
     ./zigbee2mqtt.nix
     ./sdm2mqtt.nix
     ./lacrosse2mqtt.nix
+    ./departures2mqtt.nix
 
     ./influxdb2.nix
     ./telegraf.nix
 
-    ./mqtt-webui.nix
+    ./mqtt-webui
   ];
 
 }
diff --git a/machines/lollo/smarthome/departures2mqtt.nix b/machines/lollo/smarthome/departures2mqtt.nix
@@ -0,0 +1,19 @@
+{ inputs, config, pkgs, ... }:
+
+{
+
+  systemd.services.departures2mqtt = {
+    onFailure = [ "email-notify@%i.service" ];
+    startAt   = [
+      "*-*-* 00..09:00/5"
+      "*-*-* 10..19:00/2"
+      "*-*-* 20..23:00/5"
+    ];
+
+    serviceConfig = {
+      Type      = "oneshot";
+      ExecStart = "${pkgs.departures2mqtt}/bin/departures2mqtt --mqtt-host=10.0.0.1 --mqtt-topic=departures2mqtt --stations=1505,2946,2187";
+    };
+  };
+
+}
diff --git a/machines/lollo/smarthome/mqtt-webui-config.nix b/machines/lollo/smarthome/mqtt-webui-config.nix
@@ -1,253 +0,0 @@
-let
-
-  Switch = name: topic: {
-    title         = name;
-    type          = "switch";
-    icon          = "icons/power_button.png";
-    topic.get     = topic;
-    topic.set     = "${topic}/set";
-    transform.get = "return (message.state == 'ON') ? true : false";
-    transform.set = "return JSON.stringify({state: (input) ? 'ON' : 'OFF'})";
-  };
-
-  BrighnessSlider = name: topic: {
-    title           = name;
-    type            = "slider";
-    icon            = "icons/bulb.png";
-    sliderMinValue  = 0;
-    sliderMaxValue  = 254;
-    sliderStepValue = 1;
-    topic.get       = topic;
-    topic.set       = "${topic}/set";
-    transform.get   = "return message.brightness";
-    transform.set   = "return JSON.stringify({brightness: Number(input)})";
-  };
-
-  ColorTemperatureSlider = name: topic: {
-    title           = name;
-    type            = "slider";
-    icon            = "icons/bulb.png";
-    sliderMinValue  = 250;
-    sliderMaxValue  = 454;
-    sliderStepValue = 1;
-    topic.get       = topic;
-    topic.set       = "${topic}/set";
-    transform.get   = "return message.color_temp";
-    transform.set   = "return JSON.stringify({color_temp: Number(input)})";
-  };
-
-  DimmableLamp = name: topic: {
-    title = name;
-    items = [
-      (Switch          "Power"     topic)
-      (BrighnessSlider "Brighness" topic)
-    ];
-  };
-
-  WhiteSpectrumLamp = name: topic: {
-    title = name;
-    items = [
-      (Switch                 "Power"             topic)
-      (BrighnessSlider        "Brighness"         topic)
-      (ColorTemperatureSlider "Color Temperature" topic)
-    ];
-  };
-
-  ColorSpectrumLamp = name: topic: {
-    title = name;
-    items = [
-      (Switch                 "Power"             topic)
-      (BrighnessSlider        "Brighness"         topic)
-      (ColorTemperatureSlider "Color Temperature" topic)
-
-      {
-        title           = "Color";
-        type            = "select";
-        icon            = "icons/bulb.png";
-        topic.get       = topic;
-        topic.set       = "${topic}/set";
-        transform.get   = "return message.color.x + ','+message.color.y";
-        transform.set   = "return JSON.stringify({color: {x: input.split(',')[0], y: input.split(',')[1]}})";
-        selectOptions   = [
-          {
-            label = "Red";
-            value = "0.71,0.26";
-          }
-          {
-            label = "Green";
-            value = "0.19,0.78";
-          }
-          {
-            label = "Blue";
-            value = "0.09,0.13";
-          }
-        ];
-      }
-    ];
-  };
-
-
-in {
-
-  pages =  [
-    {
-      id        = "mainpage";
-      icon     = "favicon-512x512.png";
-      title    = "Smart-Home";
-      sections = [
-        {
-          title = "Rooms";
-          items = [
-            {
-              title = "Leah's room";
-              type = "text";
-              icon = "icons/electric_range.png";
-              link = "#leah";
-            }
-            {
-              title = "Isa's room";
-              type = "text";
-              icon = "icons/electric_range.png";
-              link = "#isa";
-            }
-          ];
-        }
-
-        (WhiteSpectrumLamp "Hallway: Ceiling Light" "zigbee2mqtt/ikea_lamp_hallway")
-
-        (WhiteSpectrumLamp "Kitchen: Ceiling Light" "zigbee2mqtt/ikea_lamp_kitchen")
-
-        (WhiteSpectrumLamp "Bathroom: Ceiling Light" "zigbee2mqtt/ikea_lamp_bathroom")
-
-       {
-          title = "Temperature-Sensors";
-          items = [
-            {
-              title     = "Fridge";
-              type      = "text";
-              topic     = "lacrosse2mqtt/33";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'";
-            }
-            {
-              title     = "Bathroom";
-              type      = "text";
-              topic     = "lacrosse2mqtt/5";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
-            }
-          ];
-        }
-      ];
-    }
-
-    {
-      id       = "leah";
-      icon     = "favicon-512x512.png";
-      title    = "Leah's room";
-      sections = [
-        (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_l")
-
-        (DimmableLamp "Desk" "zigbee2mqtt/led_stripe_desk")
-
-        (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_l_rgb")
-
-        {
-          title = "Power-Meter";
-          items = [
-            {
-              title     = "Voltage";
-              type      = "text";
-              topic     = "sdm2mqtt/leah";
-              icon      = "icons/power.png";
-              transform = "return Math.round((message.voltage + Number.EPSILON) * 100) / 100 + ' V'";
-            }
-            {
-              title     = "Power";
-              type      = "text";
-              topic     = "sdm2mqtt/leah";
-              icon      = "icons/power.png";
-              transform = "return Math.round((message.power + Number.EPSILON) * 100) / 100 + ' W'";
-            }
-            {
-              title     = "Frequency";
-              type      = "text";
-              topic     = "sdm2mqtt/leah";
-              icon      = "icons/power.png";
-              transform = "return message.frequency + ' Hz'";
-            }
-            {
-              title     = "cos φ";
-              type      = "text";
-              topic     = "sdm2mqtt/leah";
-              icon      = "icons/power.png";
-              transform = "return Math.round((message.cosphi + Number.EPSILON) * 100) / 100";
-            }
-            {
-              title     = "Total Import";
-              type      = "text";
-              topic     = "sdm2mqtt/leah";
-              icon      = "icons/power.png";
-              transform = "return Math.round((message.import + Number.EPSILON) * 100) / 100 + ' kWh'";
-            }
-          ];
-        }
-
-        {
-          title = "Temperature-Sensors";
-          items = [
-            {
-              title     = "Temperature";
-              type      = "text";
-              topic     = "lacrosse2mqtt/3a";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'";
-            }
-          ];
-        }
-      ];
-    }
-
-    {
-      id       = "isa";
-      icon     = "favicon-512x512.png";
-      title    = "Isa's room";
-      sections = [
-        (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_i")
-
-        (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_i_rgb")
-
-        {
-          title = "Switches";
-          items = [
-            (Switch "Desk (L)" "zigbee2mqtt/ikea_control_outlet_i_desk_l")
-            (Switch "Desk (R)" "zigbee2mqtt/ikea_control_outlet_i_desk_r")
-          ];
-        }
-
-        {
-          title = "Temperature-Sensors";
-          items = [
-            {
-              title     = "Temperature";
-              type      = "text";
-              topic     = "lacrosse2mqtt/3c";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'";
-            }
-            {
-              title     = "Humidity";
-              type      = "text";
-              topic     = "lacrosse2mqtt/3c";
-              icon      = "icons/thermostat.png";
-              transform = "return message.humidity + ' %'";
-            }
-          ];
-        }
-
-      ];
-    }
-
-  ];
-
-}-
\ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui.nix b/machines/lollo/smarthome/mqtt-webui.nix
@@ -1,37 +0,0 @@
-{ inputs, lib, pkgs, ... }:
-
-let
-
-  configFile = pkgs.writeTextDir "config.json" (builtins.toJSON (import ./mqtt-webui-config.nix));
-
-in {
-
-  services = {
-    nginx    = {
-      enable = true;
-      virtualHosts."smart.home.ctu.cx" = {
-        enableACME = true;
-        forceSSL   = true;
-        kTLS       = true;
-
-        locations  = {
-          "/" = {
-            root  = "${pkgs.buildEnv {
-              name  = "mqtt-webui-env";
-              paths = [
-                pkgs.mqtt-webui
-                configFile
-              ];
-            }}/";
-          };
-          "/mqtt" = {
-            proxyPass       = "http://127.0.0.1:9005";
-            proxyWebsockets = true;
-          };
-        };
-      };
-    };
-
-  };
-
-}-
\ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui/config.nix b/machines/lollo/smarthome/mqtt-webui/config.nix
@@ -0,0 +1,319 @@
+let
+
+  Switch = name: topic: {
+    title         = name;
+    type          = "switch";
+    icon          = "icons/power_button.png";
+    topic.get     = topic;
+    topic.set     = "${topic}/set";
+    transform.get = "return (message.state == 'ON') ? true : false";
+    transform.set = "return JSON.stringify({state: (input) ? 'ON' : 'OFF'})";
+  };
+
+  BrighnessSlider = name: topic: {
+    title           = name;
+    type            = "slider";
+    icon            = "icons/bulb.png";
+    sliderMinValue  = 0;
+    sliderMaxValue  = 254;
+    sliderStepValue = 1;
+    topic.get       = topic;
+    topic.set       = "${topic}/set";
+    transform.get   = "return message.brightness";
+    transform.set   = "return JSON.stringify({brightness: Number(input)})";
+  };
+
+  ColorTemperatureSlider = name: topic: {
+    title           = name;
+    type            = "slider";
+    icon            = "icons/bulb.png";
+    sliderMinValue  = 250;
+    sliderMaxValue  = 454;
+    sliderStepValue = 1;
+    topic.get       = topic;
+    topic.set       = "${topic}/set";
+    transform.get   = "return message.color_temp";
+    transform.set   = "return JSON.stringify({color_temp: Number(input)})";
+  };
+
+  DimmableLamp = name: topic: {
+    title = name;
+    items = [
+      (Switch          "Power"     topic)
+      (BrighnessSlider "Brighness" topic)
+    ];
+  };
+
+  WhiteSpectrumLamp = name: topic: {
+    title = name;
+    items = [
+      (Switch                 "Power"             topic)
+      (BrighnessSlider        "Brighness"         topic)
+      (ColorTemperatureSlider "Color Temperature" topic)
+    ];
+  };
+
+  ColorSpectrumLamp = name: topic: {
+    title = name;
+    items = [
+      (Switch                 "Power"             topic)
+      (BrighnessSlider        "Brighness"         topic)
+      (ColorTemperatureSlider "Color Temperature" topic)
+
+      {
+        title           = "Color";
+        type            = "select";
+        icon            = "icons/bulb.png";
+        topic.get       = topic;
+        topic.set       = "${topic}/set";
+        transform.get   = "return message.color.x + ','+message.color.y";
+        transform.set   = "return JSON.stringify({color: {x: input.split(',')[0], y: input.split(',')[1]}})";
+        selectOptions   = [
+          {
+            label = "Red";
+            value = "0.71,0.26";
+          }
+          {
+            label = "Green";
+            value = "0.19,0.78";
+          }
+          {
+            label = "Blue";
+            value = "0.09,0.13";
+          }
+        ];
+      }
+    ];
+  };
+
+
+in {
+
+  appName  = "Smart-Home";
+  extraCSS = "/extra.css";
+  pages    =  [
+    {
+      id        = "mainpage";
+      icon     = "favicon-512x512.png";
+      title    = "Smart-Home";
+      sections = [
+        {
+          title = "Rooms";
+          items = [
+            {
+              title = "Leah's room";
+              type = "text";
+              icon = "icons/electric_range.png";
+              link = "#leah";
+            }
+            {
+              title = "Isa's room";
+              type = "text";
+              icon = "icons/electric_range.png";
+              link = "#isa";
+            }
+          ];
+        }
+
+        (WhiteSpectrumLamp "Hallway: Ceiling Light" "zigbee2mqtt/ikea_lamp_hallway")
+
+        (WhiteSpectrumLamp "Kitchen: Ceiling Light" "zigbee2mqtt/ikea_lamp_kitchen")
+
+        (WhiteSpectrumLamp "Bathroom: Ceiling Light" "zigbee2mqtt/ikea_lamp_bathroom")
+
+       {
+          title = "Temperature-Sensors";
+          items = [
+            {
+              title     = "Fridge";
+              type      = "text";
+              topic     = "lacrosse2mqtt/33";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'";
+            }
+            {
+              title     = "Bathroom";
+              type      = "text";
+              topic     = "lacrosse2mqtt/5";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
+            }
+          ];
+        }
+
+        {
+          items = [
+            {
+              title = "Departures";
+              type = "text";
+              icon = "icons/electric_range.png";
+              link = "#departures";
+            }
+            {
+              title = "Grafana-Dashboard";
+              type = "text";
+              icon = "icons/sun.png";
+              link = "https://grafana.ctu.cx/d/FRDYqjEGz/smarthome-influx?orgId=1&refresh=5s";
+            }
+          ];
+        }
+
+      ];
+    }
+
+    {
+      id       = "leah";
+      title    = "Leah's room";
+      sections = [
+        (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_l")
+
+        (DimmableLamp "Desk" "zigbee2mqtt/led_stripe_desk")
+
+        (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_l_rgb")
+
+        {
+          title = "Power-Meter";
+          items = [
+            {
+              title     = "Voltage";
+              type      = "text";
+              topic     = "sdm2mqtt/leah";
+              icon      = "icons/power.png";
+              transform = "return Math.round((message.voltage + Number.EPSILON) * 100) / 100 + ' V'";
+            }
+            {
+              title     = "Power";
+              type      = "text";
+              topic     = "sdm2mqtt/leah";
+              icon      = "icons/power.png";
+              transform = "return Math.round((message.power + Number.EPSILON) * 100) / 100 + ' W'";
+            }
+            {
+              title     = "Frequency";
+              type      = "text";
+              topic     = "sdm2mqtt/leah";
+              icon      = "icons/power.png";
+              transform = "return message.frequency + ' Hz'";
+            }
+            {
+              title     = "cos φ";
+              type      = "text";
+              topic     = "sdm2mqtt/leah";
+              icon      = "icons/power.png";
+              transform = "return Math.round((message.cosphi + Number.EPSILON) * 100) / 100";
+            }
+            {
+              title     = "Total Import";
+              type      = "text";
+              topic     = "sdm2mqtt/leah";
+              icon      = "icons/power.png";
+              transform = "return Math.round((message.import + Number.EPSILON) * 100) / 100 + ' kWh'";
+            }
+          ];
+        }
+
+        {
+          title = "Temperature-Sensors";
+          items = [
+            {
+              title     = "Temperature";
+              type      = "text";
+              topic     = "lacrosse2mqtt/3a";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'";
+            }
+          ];
+        }
+
+      ];
+    }
+
+    {
+      id       = "isa";
+      title    = "Isa's room";
+      sections = [
+        (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_i")
+
+        (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_i_rgb")
+
+        {
+          title = "Switches";
+          items = [
+            (Switch "Desk (L)" "zigbee2mqtt/ikea_control_outlet_i_desk_l")
+            (Switch "Desk (R)" "zigbee2mqtt/ikea_control_outlet_i_desk_r")
+          ];
+        }
+
+        {
+          title = "Temperature-Sensors";
+          items = [
+            {
+              title     = "Temperature";
+              type      = "text";
+              topic     = "lacrosse2mqtt/3c";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'";
+            }
+            {
+              title     = "Humidity";
+              type      = "text";
+              topic     = "lacrosse2mqtt/3c";
+              icon      = "icons/thermostat.png";
+              transform = "return message.humidity + ' %'";
+            }
+          ];
+        }
+
+      ];
+    }
+
+    {
+      id       = "departures";
+      title    = "Departures";
+      sections = [
+        {
+          items = [
+            {
+              type      = "html";
+              topic     = "departures2mqtt";
+              html      = "<div class=\"loader\"></div>";
+              transform = ''
+                  clearInterval(globalThis.departuresUpdater)
+                  globalThis.departuresUpdater = setInterval(() => {
+                    let element = document.querySelector('[data-last-updated]');
+                    element.textContent = 'Last Update: ' + (Math.floor(Date.now() / 1000) - element.dataset.lastUpdated) + ' seconds ago';
+                  }, 10000)
+
+                  let output = "";
+
+                  Object.entries(message.departures).forEach((data) => {
+                     output += '<div class="departures"><div class="table-column table-title">' + data[0] + '</div><div class="table">';
+
+                     output += '<div class="table-row line-column"><div class="table-column table-heading">Linie</div>';
+                     data[1].forEach((departure) => output += '<div class="table-column">' + departure.line + '</div>');
+                     output += '</div>';
+
+                     output += '<div class="table-row direction-column"><div class="table-column table-heading">Richtung</div>';
+                     data[1].forEach((departure) => output += '<div class="table-column">' + departure.direction + '</div>');
+                     output += '</div>';
+
+                     output += '<div class="table-row dep-column"><div class="table-column table-heading">Abfahrt</div>';
+                     data[1].forEach((departure) => output += '<div class="table-column">' + (Number(departure.departure_in) ? departure.departure_in + " min" : "sofort") + '</div>');
+                     output += '</div>';
+
+                     output += '</div></div>';
+                  });
+
+                  output += '<div class="lastUpdated" data-last-updated="' + message.lastUpdated + '">Last Update: ' + (Math.floor(Date.now() / 1000) - message.lastUpdated) + ' seconds ago</div>';
+
+                  return output;
+              '';
+            }
+          ];
+        }
+      ];
+    }
+
+  ];
+
+}+
\ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui/default.nix b/machines/lollo/smarthome/mqtt-webui/default.nix
@@ -0,0 +1,38 @@
+{ inputs, lib, pkgs, ... }:
+
+let
+
+  configFile = pkgs.writeTextDir "config.json" (builtins.toJSON (import ./config.nix));
+
+in {
+
+  services = {
+    nginx    = {
+      enable = true;
+      virtualHosts."smart.home.ctu.cx" = {
+        enableACME = true;
+        forceSSL   = true;
+        kTLS       = true;
+
+        locations  = {
+          "/" = {
+            root  = "${pkgs.buildEnv {
+              name  = "mqtt-webui-env";
+              paths = [
+                pkgs.mqtt-webui
+                configFile
+                ./extra-css
+              ];
+            }}/";
+          };
+          "/mqtt" = {
+            proxyPass       = "http://127.0.0.1:9005";
+            proxyWebsockets = true;
+          };
+        };
+      };
+    };
+
+  };
+
+}+
\ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui/extra-css/extra.css b/machines/lollo/smarthome/mqtt-webui/extra-css/extra.css
@@ -0,0 +1,91 @@
+section > div[data-mqtt-topic="departures2mqtt"] {
+	background: #444;
+	padding: 0;
+}
+
+section > div[data-mqtt-topic="departures2mqtt"] * {
+	box-sizing: unset;
+}
+
+section > div[data-mqtt-topic="departures2mqtt"] > .loader {
+	margin: 0 auto;
+}
+
+.lastUpdated {
+	text-align: center;
+	font-style: italic;
+}
+
+.table {
+	display: flex;
+	flex-direction: row;
+}
+
+.table-row {
+	display: flex;
+	flex-direction: column;
+	width: 100%;
+}
+
+.table-column {
+	padding: .5em;
+	height: 1em;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+}
+
+.table-column {
+	background-color: #ddd;
+}
+
+.line-column {
+	width: 20%;
+}
+
+.direction-column {
+	width: 60%;
+}
+
+.dep-column {
+	width: 20%;
+}
+
+.table {
+	flex-wrap: wrap;
+	padding: 20px;
+	padding-top: 10px;
+	background: #444;
+}
+
+.table-column {
+	background: #444;
+	color: white;
+}
+
+.table-column:not(.table-heading):not(.table-title) {
+	font-family: monospace;
+	font-size: 15px;
+	background: #222;
+	border-bottom: 1px solid grey;
+	color: #ffaa00;
+}
+
+.table-row:last-child {
+	border-bottom: 0;
+}
+
+.table-title {
+	padding: .85em;
+	padding-bottom: 0;
+	font-size: 1.4em;
+}
+
+.table-heading {
+	border-left: 2px solid white;
+}
+
+.table-empty {
+	flex-basis: 100%;
+	text-align: center;
+}+
\ No newline at end of file