Water web service
Signed-off-by: Thomas Klaehn <thomas.klaehn@perinet.io>
This commit is contained in:
		
							
								
								
									
										1
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -10,7 +10,6 @@
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "mode": "auto",
 | 
			
		||||
            "program": "${workspaceFolder}/main.go",
 | 
			
		||||
            "preLaunchTask": "npm: build - webui"
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Makefile
									
									
									
									
									
								
							@@ -6,13 +6,12 @@ CONFIG_DIR := /etc/$(PROJECT_NAME)
 | 
			
		||||
SYSTEM_DIR := /usr/lib/systemd/system
 | 
			
		||||
WEB_DIR := /var/lib/$(PROJECT_NAME)
 | 
			
		||||
 | 
			
		||||
CONFIG_FILE := config/config.json
 | 
			
		||||
BIN_FILE := build/bin/$(PROJECT_NAME)
 | 
			
		||||
UNIT_FILE := $(PROJECT_NAME).service
 | 
			
		||||
README_FILE := README.md
 | 
			
		||||
 | 
			
		||||
.PHONY: all
 | 
			
		||||
all: webui service
 | 
			
		||||
all: service
 | 
			
		||||
 | 
			
		||||
.PHONY: service
 | 
			
		||||
service:
 | 
			
		||||
@@ -21,30 +20,14 @@ service:
 | 
			
		||||
	go mod tidy
 | 
			
		||||
	go build -o $(BIN_FILE)
 | 
			
		||||
 | 
			
		||||
.PHONY: webui
 | 
			
		||||
webui:
 | 
			
		||||
	npm install --prefix webui
 | 
			
		||||
	npm run build --prefix webui
 | 
			
		||||
 | 
			
		||||
.PHONY: clean
 | 
			
		||||
clean:
 | 
			
		||||
	go clean
 | 
			
		||||
	rm -rf bin
 | 
			
		||||
	rm -rf webui/build webui/node_modules webui/.svelte-kit
 | 
			
		||||
	-rm $(PROJECT_NAME).tar.gz
 | 
			
		||||
 | 
			
		||||
.PHONY: install
 | 
			
		||||
install: all
 | 
			
		||||
	# Config file
 | 
			
		||||
	@if [ -f $(CONFIG_DIR)/$(notdir $(CONFIG_FILE)) ]; then \
 | 
			
		||||
		echo "$(CONFIG_DIR)/$(notdir $(CONFIG_FILE)) already exists - skipping..."; \
 | 
			
		||||
	else \
 | 
			
		||||
		install -d $(CONFIG_DIR); \
 | 
			
		||||
		install -m 0644 $(CONFIG_FILE) $(CONFIG_DIR); \
 | 
			
		||||
		echo "install -d $(CONFIG_DIR)"; \
 | 
			
		||||
		echo "install -m 0644 $(CONFIG_FILE) $(CONFIG_DIR)"; \
 | 
			
		||||
	fi
 | 
			
		||||
	
 | 
			
		||||
	# Binary
 | 
			
		||||
	install -d $(PREFIX)
 | 
			
		||||
	install -m 0755 $(BIN_FILE) $(PREFIX)
 | 
			
		||||
@@ -55,7 +38,7 @@ install: all
 | 
			
		||||
 | 
			
		||||
	# Web ui
 | 
			
		||||
	install -d $(WEB_DIR)
 | 
			
		||||
	cp -r webui/build/* $(WEB_DIR)
 | 
			
		||||
	cp -r webui/* $(WEB_DIR)
 | 
			
		||||
 | 
			
		||||
.PHONY: uninstall
 | 
			
		||||
uninstall:
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ After=multi-user.target
 | 
			
		||||
 | 
			
		||||
[Service]
 | 
			
		||||
Type=idle
 | 
			
		||||
ExecStart=/usr/bin/homeservice -d /var/lib/home/
 | 
			
		||||
ExecStart=/usr/bin/homeservice -w /var/lib/homeservice/
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										125
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								main.go
									
									
									
									
									
								
							@@ -1,37 +1,142 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"io"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	bicycle "git.blackfinn.de/apiservice/bicycle"
 | 
			
		||||
	sauna "git.blackfinn.de/apiservice/sauna"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type switcher struct {
 | 
			
		||||
	Name  string `json:"name"`
 | 
			
		||||
	State bool   `json:"state"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	logger log.Logger = *log.Default()
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	logger.SetPrefix("Homeservice: ")
 | 
			
		||||
	logger.SetFlags(log.Llongfile | log.Ltime)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func patch_request(url string, cmd string) error {
 | 
			
		||||
	req, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader([]byte(cmd)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	res, err := http.DefaultClient.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	logger.Printf("got response!\n")
 | 
			
		||||
	logger.Printf("status code: %d\n", res.StatusCode)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func do_the_switch(sw switcher) {
 | 
			
		||||
	var url string
 | 
			
		||||
	var cmd string
 | 
			
		||||
	switch sw.Name {
 | 
			
		||||
	case "chicken":
 | 
			
		||||
		url = "http://barsch:8080/katara/chicken"
 | 
			
		||||
		if sw.State {
 | 
			
		||||
			cmd = "{\"value\":\"on\"}"
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd = "{\"value\":\"off\"}"
 | 
			
		||||
		}
 | 
			
		||||
	case "bed":
 | 
			
		||||
		url = "http://barsch:8080/katara/bed"
 | 
			
		||||
		if sw.State {
 | 
			
		||||
			cmd = "{\"value\":\"on\"}"
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd = "{\"value\":\"off\"}"
 | 
			
		||||
		}
 | 
			
		||||
	case "sauna":
 | 
			
		||||
		url = "http://barsch:8080/katara/sauna"
 | 
			
		||||
		if sw.State {
 | 
			
		||||
			cmd = "{\"value\":\"on\"}"
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd = "{\"value\":\"off\"}"
 | 
			
		||||
		}
 | 
			
		||||
	case "nut":
 | 
			
		||||
		url = "https://perinode-ms26f.local/sample/gpio2"
 | 
			
		||||
		if sw.State {
 | 
			
		||||
			cmd = "{\"data\": true}"
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd = "{\"data\": false}"
 | 
			
		||||
		}
 | 
			
		||||
	case "wood":
 | 
			
		||||
		url = "https://perinode-gzzx6.local/sample/gpio1"
 | 
			
		||||
		if sw.State {
 | 
			
		||||
			cmd = "{\"data\": true}"
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd = "{\"data\": false}"
 | 
			
		||||
		}
 | 
			
		||||
	case "tomato":
 | 
			
		||||
		url = "https://perinode-jim7u.local/sample/gpio1"
 | 
			
		||||
		if sw.State {
 | 
			
		||||
			cmd = "{\"data\": true}"
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd = "{\"data\": false}"
 | 
			
		||||
		}
 | 
			
		||||
	case "barn":
 | 
			
		||||
		url = "https://perinode-jim7u.local/sample/gpio1"
 | 
			
		||||
		if sw.State {
 | 
			
		||||
			cmd = "{\"data\": true}"
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd = "{\"data\": false}"
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		logger.Printf("Unknown switch: %s", sw.Name)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err := patch_request(url, cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Print(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func http_endpoint_water_switch(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	w.Header().Set("Content-type", "application/json; charset=utf-8;")
 | 
			
		||||
	if r.Method == http.MethodPatch {
 | 
			
		||||
		tmp, err := io.ReadAll(r.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			w.Write(json.RawMessage(`{"error": "cannot unmarshal json to object"}`))
 | 
			
		||||
		} else {
 | 
			
		||||
			var sw switcher
 | 
			
		||||
			err = json.Unmarshal(tmp, &sw)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
				w.Write(json.RawMessage(`{"error": "cannot unmarshal json to object"}`))
 | 
			
		||||
			} else {
 | 
			
		||||
				do_the_switch(sw)
 | 
			
		||||
				w.WriteHeader(http.StatusAccepted)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		w.WriteHeader(http.StatusMethodNotAllowed)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	logger.Println("starting")
 | 
			
		||||
 | 
			
		||||
	var webui_path string
 | 
			
		||||
	flag.StringVar(&webui_path, "d", "./build/webui", "Specify path to serve the web ui. Default is ./static")
 | 
			
		||||
	flag.StringVar(&webui_path, "w", "./webui", "Specify path to serve the web ui. Default is ./webui")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	// Start apis
 | 
			
		||||
	bicycle.Start()
 | 
			
		||||
	sauna.Start()
 | 
			
		||||
 | 
			
		||||
	http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
 | 
			
		||||
	// Serve files from static folder
 | 
			
		||||
	http.Handle("/", http.FileServer(http.Dir(webui_path)))
 | 
			
		||||
 | 
			
		||||
	port := ":5000"
 | 
			
		||||
	http.HandleFunc("/water/switch", http_endpoint_water_switch)
 | 
			
		||||
 | 
			
		||||
	port := ":5005"
 | 
			
		||||
	logger.Println("Server is running on port" + port)
 | 
			
		||||
 | 
			
		||||
	// Start server on port specified above
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/build
 | 
			
		||||
/.svelte-kit
 | 
			
		||||
/package
 | 
			
		||||
.env
 | 
			
		||||
.env.*
 | 
			
		||||
!.env.example
 | 
			
		||||
 | 
			
		||||
# Ignore files for PNPM, NPM and YARN
 | 
			
		||||
pnpm-lock.yaml
 | 
			
		||||
package-lock.json
 | 
			
		||||
yarn.lock
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
	root: true,
 | 
			
		||||
	extends: ['eslint:recommended', 'prettier'],
 | 
			
		||||
	plugins: ['svelte3'],
 | 
			
		||||
	overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
 | 
			
		||||
	parserOptions: {
 | 
			
		||||
		sourceType: 'module',
 | 
			
		||||
		ecmaVersion: 2020
 | 
			
		||||
	},
 | 
			
		||||
	env: {
 | 
			
		||||
		browser: true,
 | 
			
		||||
		es2017: true,
 | 
			
		||||
		node: true
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										12
									
								
								webui/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								webui/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,12 +0,0 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/build
 | 
			
		||||
/.svelte-kit
 | 
			
		||||
/package
 | 
			
		||||
.env
 | 
			
		||||
.env.*
 | 
			
		||||
!.env.example
 | 
			
		||||
.vercel
 | 
			
		||||
.output
 | 
			
		||||
vite.config.js.timestamp-*
 | 
			
		||||
vite.config.ts.timestamp-*
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
engine-strict=true
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/build
 | 
			
		||||
/.svelte-kit
 | 
			
		||||
/package
 | 
			
		||||
.env
 | 
			
		||||
.env.*
 | 
			
		||||
!.env.example
 | 
			
		||||
 | 
			
		||||
# Ignore files for PNPM, NPM and YARN
 | 
			
		||||
pnpm-lock.yaml
 | 
			
		||||
package-lock.json
 | 
			
		||||
yarn.lock
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
	"useTabs": true,
 | 
			
		||||
	"singleQuote": true,
 | 
			
		||||
	"trailingComma": "none",
 | 
			
		||||
	"printWidth": 100,
 | 
			
		||||
	"plugins": ["prettier-plugin-svelte"],
 | 
			
		||||
	"pluginSearchDirs": ["."],
 | 
			
		||||
	"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
FROM node:latest AS build
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY package.json ./
 | 
			
		||||
COPY package-lock.json ./
 | 
			
		||||
RUN npm install
 | 
			
		||||
COPY . ./
 | 
			
		||||
RUN npm run build
 | 
			
		||||
 | 
			
		||||
FROM nginx:1.19-alpine
 | 
			
		||||
COPY --from=build /app/build /usr/share/nginx/html
 | 
			
		||||
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
# create-svelte
 | 
			
		||||
 | 
			
		||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
 | 
			
		||||
 | 
			
		||||
## Creating a project
 | 
			
		||||
 | 
			
		||||
If you're seeing this, you've probably already done this step. Congrats!
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# create a new project in the current directory
 | 
			
		||||
npm create svelte@latest
 | 
			
		||||
 | 
			
		||||
# create a new project in my-app
 | 
			
		||||
npm create svelte@latest my-app
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Developing
 | 
			
		||||
 | 
			
		||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm run dev
 | 
			
		||||
 | 
			
		||||
# or start the server and open the app in a new browser tab
 | 
			
		||||
npm run dev -- --open
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Building
 | 
			
		||||
 | 
			
		||||
To create a production version of your app:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm run build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can preview the production build with `npm run preview`.
 | 
			
		||||
 | 
			
		||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
 | 
			
		||||
							
								
								
									
										210
									
								
								webui/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								webui/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,210 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
    <style>
 | 
			
		||||
        .switch {
 | 
			
		||||
            position: relative;
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            width: 60px;
 | 
			
		||||
            height: 34px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Hide default HTML checkbox */
 | 
			
		||||
        .switch input {
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
            width: 0;
 | 
			
		||||
            height: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* The slider */
 | 
			
		||||
        .slider {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            right: 0;
 | 
			
		||||
            bottom: 0;
 | 
			
		||||
            background-color: #ccc;
 | 
			
		||||
            -webkit-transition: .4s;
 | 
			
		||||
            transition: .4s;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .slider:before {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            content: "";
 | 
			
		||||
            height: 26px;
 | 
			
		||||
            width: 26px;
 | 
			
		||||
            left: 4px;
 | 
			
		||||
            bottom: 4px;
 | 
			
		||||
            background-color: white;
 | 
			
		||||
            -webkit-transition: .4s;
 | 
			
		||||
            transition: .4s;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input:checked + .slider {
 | 
			
		||||
            background-color: #2196F3;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input:focus + .slider {
 | 
			
		||||
            box-shadow: 0 0 1px #2196F3;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input:checked + .slider:before {
 | 
			
		||||
            -webkit-transform: translateX(26px);
 | 
			
		||||
            -ms-transform: translateX(26px);
 | 
			
		||||
            transform: translateX(26px);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Rounded sliders */
 | 
			
		||||
        .slider.round {
 | 
			
		||||
            border-radius: 34px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .slider.round:before {
 | 
			
		||||
            border-radius: 50%;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
        <title id="title">Wasser</title>
 | 
			
		||||
        <script type="text/javaScript">
 | 
			
		||||
 | 
			
		||||
            function init() {
 | 
			
		||||
                const xhr = new XMLHttpRequest();
 | 
			
		||||
                xhr.open("GET", "/water/devices");
 | 
			
		||||
                xhr.send();
 | 
			
		||||
                xhr.responseType = "json";
 | 
			
		||||
                xhr.onload = () => {
 | 
			
		||||
                    if (xhr.readyState == 4 && xhr.status == 200) {
 | 
			
		||||
                        let devices = xhr.response['devices'];
 | 
			
		||||
 | 
			
		||||
                        // remove old lines in switch table
 | 
			
		||||
                        // let table = document.getElementById("switch_tbl");
 | 
			
		||||
                        // let rows = table.getElementsByTagName("tr");
 | 
			
		||||
                        // for(let i = 0; i < rows.length; i++) {
 | 
			
		||||
                        //     table.removeChild(rows[i]);
 | 
			
		||||
                        // }
 | 
			
		||||
 | 
			
		||||
                        // add rows recursively
 | 
			
		||||
                    //     for(let i = 0; i < devices.length; i++) {
 | 
			
		||||
                    //         console.log(devices[i]);
 | 
			
		||||
                    //         let new_row = table.insertRow();
 | 
			
		||||
                    //         let new_cell = new_row.insertCell();
 | 
			
		||||
                    //         new_cell.innerHTML = devices[i].name;
 | 
			
		||||
                    //         let btn = document.createElement("checkbox");
 | 
			
		||||
                    //         btn.setAttribute
 | 
			
		||||
                    //         let button_cell = new_row.insertCell();
 | 
			
		||||
                    //         button_cell.appendChild(btn);
 | 
			
		||||
                    //     }
 | 
			
		||||
                    // } else {
 | 
			
		||||
                    //     console.log(`Error: ${xhr.status}`);
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function check(checkbox, name) {
 | 
			
		||||
                var obj;
 | 
			
		||||
                if(checkbox.checked) {
 | 
			
		||||
                    obj = '{"name":"' + name + '","state":true}'
 | 
			
		||||
                } else {
 | 
			
		||||
                    obj = '{"name":"' + name + '","state":false}'
 | 
			
		||||
                }
 | 
			
		||||
                console.log(obj);
 | 
			
		||||
                const xhr = new XMLHttpRequest();
 | 
			
		||||
                xhr.open("PATCH", "/water/switch");
 | 
			
		||||
                xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
 | 
			
		||||
                xhr.onload = () => {
 | 
			
		||||
                var data = JSON.parse(xhr.responseText);
 | 
			
		||||
                if (xhr.readyState == 4 && xhr.status == "202") {
 | 
			
		||||
                    console.log(data);
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.log(`Error: ${xhr.status}`);
 | 
			
		||||
                }
 | 
			
		||||
                };
 | 
			
		||||
                xhr.send(obj);
 | 
			
		||||
            }
 | 
			
		||||
        </script>
 | 
			
		||||
    </head>
 | 
			
		||||
 | 
			
		||||
    <body onload=init()>
 | 
			
		||||
        <div class="headercontainer">
 | 
			
		||||
            <div class="meta">
 | 
			
		||||
                <div class="left" style="line-height: 24px;font-weight: bold;" id="headline">Wasser</div>
 | 
			
		||||
                <div class="middle"></div>
 | 
			
		||||
                <div class="right"><span></span></div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- <div class="header" id="header_cnt">
 | 
			
		||||
                <div class="headerlogo"><img src="images/logo_perinet.png" width="258" height="94" alt="" /></div>
 | 
			
		||||
            </div> -->
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!--Content-->
 | 
			
		||||
        <div id="content">
 | 
			
		||||
            <table id="switch_tbl">
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Huehner</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="switch">
 | 
			
		||||
                            <input type="checkbox" onchange="check(this, 'chicken')">
 | 
			
		||||
                            <span class="slider round"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Hochbeet</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="switch">
 | 
			
		||||
                            <input type="checkbox" onchange="check(this, 'bed')">
 | 
			
		||||
                            <span class="slider round"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Sauna</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="switch">
 | 
			
		||||
                            <input type="checkbox" onchange="check(this, 'sauna')">
 | 
			
		||||
                            <span class="slider round"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Haselnuss</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="switch">
 | 
			
		||||
                            <input type="checkbox" onchange="check(this, 'nut')">
 | 
			
		||||
                            <span class="slider round"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Holzlager</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="switch">
 | 
			
		||||
                            <input type="checkbox" onchange="check(this, 'wood')">
 | 
			
		||||
                            <span class="slider round"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Tomaten</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="switch">
 | 
			
		||||
                            <input type="checkbox" onchange="check(this, 'tomato')">
 | 
			
		||||
                            <span class="slider round"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Scheune</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="switch">
 | 
			
		||||
                            <input type="checkbox" onchange="check(this, 'barn')">
 | 
			
		||||
                            <span class="slider round"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
	"extends": "./.svelte-kit/tsconfig.json",
 | 
			
		||||
	"compilerOptions": {
 | 
			
		||||
		"allowJs": true,
 | 
			
		||||
		"checkJs": true,
 | 
			
		||||
		"esModuleInterop": true,
 | 
			
		||||
		"forceConsistentCasingInFileNames": true,
 | 
			
		||||
		"resolveJsonModule": true,
 | 
			
		||||
		"skipLibCheck": true,
 | 
			
		||||
		"sourceMap": true,
 | 
			
		||||
		"strict": true
 | 
			
		||||
	}
 | 
			
		||||
	// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
 | 
			
		||||
	//
 | 
			
		||||
	// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
 | 
			
		||||
	// from the referenced tsconfig.json - TypeScript does not merge them in
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5122
									
								
								webui/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5122
									
								
								webui/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,40 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "home",
 | 
			
		||||
	"version": "0.0.1",
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"dev": "vite dev",
 | 
			
		||||
		"build": "vite build",
 | 
			
		||||
		"preview": "vite preview",
 | 
			
		||||
		"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
 | 
			
		||||
		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
 | 
			
		||||
		"test": "playwright test",
 | 
			
		||||
		"test:unit": "vitest",
 | 
			
		||||
		"lint": "prettier --plugin-search-dir . --check . && eslint .",
 | 
			
		||||
		"format": "prettier --plugin-search-dir . --write ."
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@fontsource/fira-mono": "^4.5.10",
 | 
			
		||||
		"@neoconfetti/svelte": "^1.0.0",
 | 
			
		||||
		"@playwright/test": "^1.28.1",
 | 
			
		||||
		"@sveltejs/adapter-auto": "^1.0.0",
 | 
			
		||||
		"@sveltejs/adapter-node": "^1.1.4",
 | 
			
		||||
		"@sveltejs/adapter-static": "^1.0.5",
 | 
			
		||||
		"@sveltejs/kit": "^1.0.0",
 | 
			
		||||
		"@types/cookie": "^0.5.1",
 | 
			
		||||
		"eslint": "^8.28.0",
 | 
			
		||||
		"eslint-config-prettier": "^8.5.0",
 | 
			
		||||
		"eslint-plugin-svelte3": "^4.0.0",
 | 
			
		||||
		"prettier": "^2.8.0",
 | 
			
		||||
		"prettier-plugin-svelte": "^2.8.1",
 | 
			
		||||
		"svelte": "^3.54.0",
 | 
			
		||||
		"svelte-check": "^3.0.1",
 | 
			
		||||
		"typescript": "^4.9.3",
 | 
			
		||||
		"vite": "^4.0.4",
 | 
			
		||||
		"vitest": "^0.25.3"
 | 
			
		||||
	},
 | 
			
		||||
	"type": "module",
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"hamburger-menu-svelte": "^2.2.0",
 | 
			
		||||
		"uplot": "^1.6.30"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
 | 
			
		||||
const config = {
 | 
			
		||||
	webServer: {
 | 
			
		||||
		command: 'npm run build && npm run preview',
 | 
			
		||||
		port: 4173
 | 
			
		||||
	},
 | 
			
		||||
	testDir: 'tests'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta charset="utf-8" />
 | 
			
		||||
		<!-- <link rel="icon" href="%sveltekit.assets%/haus.svg"/> -->
 | 
			
		||||
		<meta name="viewport" content="width=device-width"/>
 | 
			
		||||
		%sveltekit.head%
 | 
			
		||||
	</head>
 | 
			
		||||
	<body>
 | 
			
		||||
		<div>%sveltekit.body%</div>
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -1,411 +0,0 @@
 | 
			
		||||
html,
 | 
			
		||||
body {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    font-size: 18px !important;
 | 
			
		||||
    font-family: 'Muli', arial, verdana, helvetica, sans-serif;
 | 
			
		||||
    font-weight: 300;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    color: #b6b6b6;
 | 
			
		||||
    background: #282929;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modalbg {
 | 
			
		||||
    background: rgba(0, 0, 0, 0.6);
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0px;
 | 
			
		||||
    z-index: 999;
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
    overflow-y: auto;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
    outline: none;
 | 
			
		||||
    color: #b6b6b6;
 | 
			
		||||
    text-decoration-line: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
li {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content {
 | 
			
		||||
    padding: 10rem 0;
 | 
			
		||||
    margin: 2%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
section {
 | 
			
		||||
    margin: auto 0;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
section h1 {
 | 
			
		||||
    margin: 20px auto;
 | 
			
		||||
    font-size: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
section h2 {
 | 
			
		||||
    font-size: 1.4rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table {
 | 
			
		||||
    margin: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td.left {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    font-weight: 700;
 | 
			
		||||
    width: 33%;
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
    vertical-align: text-top;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td.right {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td.input {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    width: 65%;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    vertical-align: text-top;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
form {
 | 
			
		||||
    font-size: 1.2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td select {
 | 
			
		||||
    width: 51%;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    width: 62%;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    margin-left: 2%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td input {
 | 
			
		||||
    width: 60%;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td textarea {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td input span {
 | 
			
		||||
    font-size: 1.0rem;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border: none;
 | 
			
		||||
    background: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
select,
 | 
			
		||||
input {
 | 
			
		||||
    border: 1px #999999 solid;
 | 
			
		||||
    background-color: #f6f6f6;
 | 
			
		||||
    padding: 2px 4px 2px 4px;
 | 
			
		||||
    margin: 0 0 0.5rem 0;
 | 
			
		||||
    color: #b6b6b6 !important;
 | 
			
		||||
    font-size: 1.0rem;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td div input {
 | 
			
		||||
    color: #b6b6b6 !important;
 | 
			
		||||
    border: none;
 | 
			
		||||
    background: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
textarea {
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    width: 98%;
 | 
			
		||||
    height: 10rem;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    resize: none;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    margin-left: 2%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*Navigation*/
 | 
			
		||||
 | 
			
		||||
header {
 | 
			
		||||
    height: 40px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding:10px 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 768px) {
 | 
			
		||||
    header {
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#overlay {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    display: none;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    z-index: 999;
 | 
			
		||||
    background-color: rgba(0, 0, 0, 0.5);
 | 
			
		||||
    /*dim the background*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.update-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#update-plain {
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#update-overlay {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    z-index: 502;
 | 
			
		||||
    transform-origin: 50% 50%;
 | 
			
		||||
    transition: transform 1.00s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
address {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
address table {
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
address td {
 | 
			
		||||
    width: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="submit" i] {
 | 
			
		||||
    margin: 0px 0 6px 0;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    width: auto;
 | 
			
		||||
    height: 45px;
 | 
			
		||||
    z-index: 200;
 | 
			
		||||
    width: 65%;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="submit"] {
 | 
			
		||||
    border-radius: 30px;
 | 
			
		||||
    background: #282929;
 | 
			
		||||
    border: 1px solid #d2d2d2;
 | 
			
		||||
    -webkit-transition: all 1s;
 | 
			
		||||
    transition: all 1s;
 | 
			
		||||
    margin-top: 2%;
 | 
			
		||||
    margin-left: 2%;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    z-index: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="submit" i]:hover {
 | 
			
		||||
    background-color: #f6f6f6;
 | 
			
		||||
    color: #282929;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="file"] {
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    margin: 1%;
 | 
			
		||||
    width: 390px;
 | 
			
		||||
    margin-left: 45px;
 | 
			
		||||
    margin-right: 28px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    background: #282929;
 | 
			
		||||
    margin-left: 2%;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.confi table td.input {
 | 
			
		||||
    padding: 1px 0px 10px 0px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.confi table td.left {
 | 
			
		||||
    padding: 0px 25px 0px 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content #uploadbutton {
 | 
			
		||||
    width: 400px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
footer {
 | 
			
		||||
    background-color: #f6f6f6;
 | 
			
		||||
    padding: 1px 50px 1px 0px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
footer nav :hover {
 | 
			
		||||
    color: #b6b6b6;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
footer td p {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    padding-left: 40%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* upload loader animation*/
 | 
			
		||||
 | 
			
		||||
.loader {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    width: 100px;
 | 
			
		||||
    height: 100px;
 | 
			
		||||
    background: transparent;
 | 
			
		||||
    margin: 30px auto 0 auto;
 | 
			
		||||
    border: solid 10px #b6b6b6;
 | 
			
		||||
    border-top: solid 10px #c2d100;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loader.active {
 | 
			
		||||
    animation: loading 4s ease-in-out;
 | 
			
		||||
    animation-fill-mode: forwards;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes loading {
 | 
			
		||||
    5% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        border-color: #b6b6b6;
 | 
			
		||||
    }
 | 
			
		||||
    15% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        border-color: #c2d100;
 | 
			
		||||
    }
 | 
			
		||||
    30% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        transform: rotate(180deg);
 | 
			
		||||
        border-color: #b6b6b6;
 | 
			
		||||
    }
 | 
			
		||||
    45% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        border-color: #c2d100;
 | 
			
		||||
    }
 | 
			
		||||
    60% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        transform: rotate(180deg);
 | 
			
		||||
        border-color: #b6b6b6;
 | 
			
		||||
    }
 | 
			
		||||
    70% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        border-color: #c2d100;
 | 
			
		||||
    }
 | 
			
		||||
    85% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        transform: rotate(180deg);
 | 
			
		||||
        border-color: #c2d100;
 | 
			
		||||
    }
 | 
			
		||||
    90% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        transform: rotate(1080deg);
 | 
			
		||||
        border-color: #c2d100;
 | 
			
		||||
    }
 | 
			
		||||
    99% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        transform: rotate(1080deg);
 | 
			
		||||
        border-color: #c2d100;
 | 
			
		||||
    }
 | 
			
		||||
    100% {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        transform: rotate(1080deg);
 | 
			
		||||
        border-color: #282929;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*Chrome runable slider*/
 | 
			
		||||
 | 
			
		||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
 | 
			
		||||
    input[type='range'] {
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        /* Standard */
 | 
			
		||||
        width: calc(99.9% - 1.5px);
 | 
			
		||||
        -webkit-appearance: none;
 | 
			
		||||
        background-color: transparent;
 | 
			
		||||
        margin: 0 0 0;
 | 
			
		||||
    }
 | 
			
		||||
    input[type='range']::-webkit-slider-runnable-track {
 | 
			
		||||
        height: fit-content;
 | 
			
		||||
        /* -webkit-appearance: none; */
 | 
			
		||||
        color: #C2D100;
 | 
			
		||||
        /* margin-top: -5px; */
 | 
			
		||||
    }
 | 
			
		||||
    input[type='range']::-webkit-slider-thumb {
 | 
			
		||||
        width: 20px;
 | 
			
		||||
        -webkit-appearance: none;
 | 
			
		||||
        height: 20px;
 | 
			
		||||
        cursor: ew-resize;
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        background: #292659;
 | 
			
		||||
        box-shadow: -1080px 10px 10px 1080px #C1D10A;
 | 
			
		||||
    }
 | 
			
		||||
    input:focus {
 | 
			
		||||
        outline: none;
 | 
			
		||||
        border-color: #C1D10A;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/** FF*/
 | 
			
		||||
 | 
			
		||||
input[type="range"]::-moz-range-progress {
 | 
			
		||||
    background-color: #C1D10A;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="range"]::-moz-range-track {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* IE*/
 | 
			
		||||
 | 
			
		||||
input[type="range"]::-ms-fill-lower {
 | 
			
		||||
    background-color: #C1D10A;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="range"]::-ms-fill-upper {
 | 
			
		||||
    background-color: #C1D10A;
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -1,14 +0,0 @@
 | 
			
		||||
export const handle = async ({ event, resolve }) => {
 | 
			
		||||
	let userid = event.cookies.get('userid');
 | 
			
		||||
 | 
			
		||||
	if (!userid) {
 | 
			
		||||
		// if this is the first time the user has visited this app,
 | 
			
		||||
		// set a cookie so that we recognise them when they return
 | 
			
		||||
		userid = crypto.randomUUID();
 | 
			
		||||
		event.cookies.set('userid', userid, { path: '/' });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	event.locals.userid = userid;
 | 
			
		||||
 | 
			
		||||
	return resolve(event);
 | 
			
		||||
};
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import Hamburger from "hamburger-menu-svelte";
 | 
			
		||||
	const menu_list = [
 | 
			
		||||
		{ name: "Home", url: "/" },
 | 
			
		||||
		{ name: "Bicycle", url: "/bicycle" },
 | 
			
		||||
		{ name: "Sauna", url: "/sauna" }
 | 
			
		||||
	];
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<header class="header">
 | 
			
		||||
    <Hamburger {menu_list}/>
 | 
			
		||||
</header>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
	/* header {
 | 
			
		||||
		height: 40px;
 | 
			
		||||
		display: flex;
 | 
			
		||||
		align-items: center;
 | 
			
		||||
		padding:10px 20px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@media (min-width: 768px) {
 | 
			
		||||
		header {
 | 
			
		||||
			justify-content: space-between;
 | 
			
		||||
		}
 | 
			
		||||
	} */
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,110 +0,0 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    import 'uplot/dist/uPlot.min.css';
 | 
			
		||||
 | 
			
		||||
    export let data_label = "";    // data label in legend
 | 
			
		||||
    export let unit = "";          // unit in legend
 | 
			
		||||
 | 
			
		||||
    export let last_for_seconds = 20;        // Period of time [s] the data are shifted out on the left
 | 
			
		||||
    let plotContainer;
 | 
			
		||||
    let data = [];
 | 
			
		||||
    data[0] = [Date.now()];
 | 
			
		||||
    data[1] = [null];
 | 
			
		||||
 | 
			
		||||
    const PRECISION = 1;
 | 
			
		||||
 | 
			
		||||
    export async function create(legend_label, legend_unit, duration) {
 | 
			
		||||
        data_label = legend_label;
 | 
			
		||||
        unit = legend_unit;
 | 
			
		||||
        last_for_seconds = duration;
 | 
			
		||||
        const uplotModule = await import('uplot');
 | 
			
		||||
        const uPlot = uplotModule.default;
 | 
			
		||||
        redraw(uPlot);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    export async function add_data(new_data) {
 | 
			
		||||
        data[0].push(Date.now());
 | 
			
		||||
        data[1].push(new_data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function redraw(uPlot) {
 | 
			
		||||
        function getSize() {
 | 
			
		||||
            let max_height = window.innerHeight;
 | 
			
		||||
            let max_width = window.innerWidth;
 | 
			
		||||
            let current_posY = plotContainer.getBoundingClientRect().top;
 | 
			
		||||
            let height = Math.max(max_height - current_posY - 200, 250);
 | 
			
		||||
            let width = Math.max(max_width - (2 * 50), 600);
 | 
			
		||||
            return {
 | 
			
		||||
                width: width,
 | 
			
		||||
                height: height,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const opts = {
 | 
			
		||||
            ...getSize(),
 | 
			
		||||
            ms: 1,
 | 
			
		||||
            cursor: {drag: {setScale: false}},
 | 
			
		||||
            points: {show: false},
 | 
			
		||||
            pxSnap: false,
 | 
			
		||||
            pxAlign: 0,
 | 
			
		||||
            series: [
 | 
			
		||||
                {
 | 
			
		||||
                    label: "time"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    label: data_label,
 | 
			
		||||
                    stroke: "#b6b6b6",
 | 
			
		||||
                    paths: uPlot.paths.spline(),
 | 
			
		||||
                    value: (u, v, sidx, didx) => {
 | 
			
		||||
                        if (didx == null) {
 | 
			
		||||
                            let d = u.data[sidx];
 | 
			
		||||
                            v = d[d.length - 1];
 | 
			
		||||
                        }
 | 
			
		||||
                        let value = v;
 | 
			
		||||
                        if ( value != null) {
 | 
			
		||||
                            value = value.toFixed(PRECISION) + unit;
 | 
			
		||||
                        }
 | 
			
		||||
                        return value;
 | 
			
		||||
                    },
 | 
			
		||||
                    pxAlign: 0,
 | 
			
		||||
                    points: { show: false },
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            axes: [{grid: { show: false}}, {grid: { show: false}}],
 | 
			
		||||
            // hooks are used here to filter out the time series from the legend
 | 
			
		||||
            hooks: {
 | 
			
		||||
                init: [
 | 
			
		||||
                    u => {
 | 
			
		||||
                        [...u.root.querySelectorAll('.u-legend .u-series')].forEach((el, i) => {
 | 
			
		||||
                            if (u.series[i].label ==="time") {
 | 
			
		||||
                                el.style.display = 'none';
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let uplot = new uPlot(opts, data, plotContainer);
 | 
			
		||||
 | 
			
		||||
        function update() {
 | 
			
		||||
            const now = Date.now();
 | 
			
		||||
            const scale = { min: now - (last_for_seconds * 1000), max: now };
 | 
			
		||||
 | 
			
		||||
            // shift out data earlier than chart origin
 | 
			
		||||
            let last = Date.now();
 | 
			
		||||
            let first = last - (last_for_seconds * 1000);
 | 
			
		||||
            while(data[0][0] < first) {
 | 
			
		||||
                for(let i = 0; i < data.length; i++) {
 | 
			
		||||
                    data[i].shift();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uplot.setData(data, false);
 | 
			
		||||
            uplot.setScale('x', scale);
 | 
			
		||||
            uplot.setLegend({idx: data[0].length - 1}, false);
 | 
			
		||||
            requestAnimationFrame(update);
 | 
			
		||||
        }
 | 
			
		||||
        requestAnimationFrame(update);
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div bind:this={plotContainer}></div>
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
<?xml version="1.0" standalone="no"?>
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
 | 
			
		||||
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
 | 
			
		||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
 width="1280.000000pt" height="1210.000000pt" viewBox="0 0 1280.000000 1210.000000"
 | 
			
		||||
 preserveAspectRatio="xMidYMid meet">
 | 
			
		||||
<metadata>
 | 
			
		||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
 | 
			
		||||
</metadata>
 | 
			
		||||
<g transform="translate(0.000000,1210.000000) scale(0.100000,-0.100000)"
 | 
			
		||||
fill="#000000" stroke="none">
 | 
			
		||||
<path d="M11345 12075 c-49 -33 -58 -44 -146 -173 -39 -57 -77 -106 -85 -109
 | 
			
		||||
-27 -10 -63 7 -141 69 -121 96 -179 128 -233 128 -98 0 -285 -84 -384 -172
 | 
			
		||||
-79 -70 -152 -176 -166 -241 -23 -110 22 -257 116 -373 l33 -41 -82 -7 c-331
 | 
			
		||||
-28 -656 -105 -851 -203 -314 -157 -535 -430 -726 -893 -73 -179 -139 -389
 | 
			
		||||
-235 -750 -105 -391 -159 -559 -229 -705 -102 -213 -292 -470 -496 -675 -155
 | 
			
		||||
-155 -294 -265 -550 -435 -640 -426 -839 -521 -1245 -596 -211 -40 -362 -31
 | 
			
		||||
-623 37 -333 86 -415 126 -722 350 -415 303 -681 451 -1005 559 -264 88 -490
 | 
			
		||||
121 -770 112 -536 -19 -1152 -196 -1560 -450 -532 -330 -957 -995 -1129 -1762
 | 
			
		||||
-37 -167 -54 -286 -83 -591 -36 -383 -38 -796 -5 -1012 35 -222 114 -439 255
 | 
			
		||||
-702 53 -100 75 -129 167 -221 171 -171 395 -307 579 -349 91 -21 457 -64 495
 | 
			
		||||
-58 16 3 13 10 -21 53 -22 28 -102 115 -178 195 -304 320 -424 526 -519 891
 | 
			
		||||
-33 125 -46 221 -72 519 l-6 75 72 -140 c149 -290 218 -384 305 -421 104 -43
 | 
			
		||||
165 9 219 188 26 85 31 116 60 365 20 165 35 190 36 58 2 -343 21 -830 35
 | 
			
		||||
-915 20 -123 77 -301 118 -370 95 -158 210 -120 281 92 13 40 52 233 85 428
 | 
			
		||||
189 1104 245 1410 261 1439 24 46 33 23 26 -66 -8 -90 -18 -520 -16 -653 1
 | 
			
		||||
-41 9 -97 18 -125 19 -57 39 -65 104 -46 49 15 142 101 161 150 9 21 62 212
 | 
			
		||||
117 423 118 446 184 676 210 733 l18 40 1 -45 c1 -57 -22 -155 -99 -410 -112
 | 
			
		||||
-375 -145 -530 -156 -740 -17 -335 71 -805 187 -993 111 -180 323 -392 481
 | 
			
		||||
-481 114 -63 258 -84 258 -36 0 27 -60 214 -134 420 -106 295 -129 389 -151
 | 
			
		||||
630 -27 283 -17 718 21 945 34 205 186 705 213 705 9 0 -3 -116 -23 -210 -46
 | 
			
		||||
-229 -67 -495 -67 -870 0 -562 54 -984 161 -1257 29 -74 56 -122 63 -112 2 2
 | 
			
		||||
26 96 54 209 49 202 94 329 136 391 25 36 74 76 82 67 4 -3 13 -70 21 -149 19
 | 
			
		||||
-185 39 -290 71 -362 50 -112 234 -253 424 -324 91 -34 242 -63 256 -50 4 4
 | 
			
		||||
-14 94 -40 200 -60 248 -72 354 -51 455 5 23 6 22 34 -17 16 -22 49 -51 72
 | 
			
		||||
-63 38 -20 58 -23 163 -23 l120 0 58 -111 c63 -121 78 -134 151 -134 34 0 60
 | 
			
		||||
11 127 51 125 76 117 76 191 17 99 -80 135 -93 253 -92 105 1 161 15 216 55
 | 
			
		||||
17 12 31 20 33 18 1 -2 15 -33 29 -68 66 -153 185 -323 326 -461 97 -94 199
 | 
			
		||||
-167 223 -157 7 2 49 91 92 196 l79 192 22 -27 c12 -15 53 -83 90 -151 80
 | 
			
		||||
-144 129 -197 288 -310 59 -42 107 -80 107 -83 0 -4 -16 -116 -35 -250 -19
 | 
			
		||||
-134 -35 -244 -35 -245 0 -1 -36 8 -80 21 -103 30 -177 42 -320 53 -87 6 -133
 | 
			
		||||
15 -190 37 -65 24 -89 28 -185 28 -100 1 -115 -1 -165 -26 -30 -15 -74 -47
 | 
			
		||||
-98 -71 l-43 -44 75 -5 c58 -4 90 -12 141 -37 72 -36 105 -72 157 -175 62
 | 
			
		||||
-122 72 -129 226 -146 72 -8 124 -22 222 -60 71 -28 134 -50 140 -50 47 0 -99
 | 
			
		||||
-104 -190 -135 -105 -36 -238 -107 -397 -214 -159 -106 -239 -181 -291 -273
 | 
			
		||||
-82 -144 -96 -325 -35 -449 18 -35 45 -77 62 -93 l30 -28 45 89 c71 142 108
 | 
			
		||||
176 226 203 111 26 228 81 357 167 45 31 87 52 93 48 6 -4 32 -39 57 -79 74
 | 
			
		||||
-117 114 -163 193 -229 136 -114 233 -167 306 -167 14 0 44 -15 67 -34 77 -62
 | 
			
		||||
90 -65 248 -63 126 2 151 -1 202 -20 73 -28 175 -95 260 -174 37 -34 70 -59
 | 
			
		||||
74 -56 3 4 6 73 7 154 1 138 -1 153 -27 228 -19 53 -25 81 -17 83 13 5 195
 | 
			
		||||
125 252 167 34 25 39 26 74 15 l37 -13 60 67 c176 197 266 160 575 -233 62
 | 
			
		||||
-79 130 -156 150 -171 91 -66 140 -152 163 -287 6 -37 13 -69 14 -71 5 -6 148
 | 
			
		||||
29 173 42 42 22 108 98 123 142 21 62 16 143 -15 228 -16 42 -32 79 -37 82
 | 
			
		||||
-21 13 -7 23 19 13 15 -6 83 -24 151 -40 209 -50 213 -55 246 -282 9 -62 18
 | 
			
		||||
-114 20 -116 2 -2 52 23 111 55 71 39 113 69 125 89 46 75 64 293 34 408 -40
 | 
			
		||||
152 -137 262 -311 350 -29 15 -132 54 -229 87 -202 69 -238 86 -361 168 -134
 | 
			
		||||
90 -174 135 -203 223 -13 41 -39 108 -58 149 -26 58 -42 119 -69 265 -40 220
 | 
			
		||||
-85 430 -114 529 l-20 69 98 93 c257 242 366 531 395 1044 l2 44 190 164 c479
 | 
			
		||||
414 733 657 1026 982 418 464 613 726 765 1025 115 228 163 396 211 735 25
 | 
			
		||||
178 24 730 0 950 -46 400 -108 725 -222 1154 -25 92 -45 175 -45 183 0 9 14
 | 
			
		||||
31 30 50 46 51 101 169 171 363 l63 175 2 240 c0 187 4 256 18 315 9 41 19 84
 | 
			
		||||
21 95 4 21 5 21 167 -7 90 -15 190 -27 223 -28 72 0 167 23 205 50 l27 19 102
 | 
			
		||||
-69 101 -68 17 31 c11 21 17 55 17 97 0 58 -5 76 -51 170 -61 125 -106 187
 | 
			
		||||
-215 297 l-81 83 38 82 c38 81 39 87 43 212 5 159 -9 247 -54 342 -36 76 -109
 | 
			
		||||
166 -155 189 -38 20 -125 19 -244 -4 -57 -11 -104 -16 -109 -11 -5 5 7 45 28
 | 
			
		||||
94 59 137 50 223 -26 262 -62 32 -143 4 -293 -100 -44 -31 -85 -56 -92 -56 -9
 | 
			
		||||
0 -12 37 -10 153 2 140 0 155 -20 194 -28 52 -69 82 -125 89 -36 5 -50 1 -83
 | 
			
		||||
-21z m-2875 -9705 c30 -183 56 -273 113 -390 28 -58 52 -107 54 -110 1 -3 -29
 | 
			
		||||
-20 -68 -38 -206 -97 -249 -254 -143 -519 l29 -72 50 56 c54 62 122 99 201
 | 
			
		||||
110 45 6 48 5 35 -10 -7 -9 -64 -55 -126 -102 -129 -97 -174 -143 -211 -216
 | 
			
		||||
l-26 -52 -31 57 c-49 92 -198 221 -336 291 -73 37 -137 55 -193 55 -27 0 -48
 | 
			
		||||
2 -48 5 0 2 12 40 26 82 31 92 29 174 -5 241 l-21 42 21 62 c11 35 30 135 41
 | 
			
		||||
223 11 88 21 160 22 161 0 0 58 19 129 42 141 47 310 120 402 175 33 19 61 34
 | 
			
		||||
62 33 1 -1 11 -58 23 -126z m-454 -1530 c38 -17 106 -38 151 -45 75 -12 143
 | 
			
		||||
-43 143 -63 0 -7 -16 -115 -26 -170 l-5 -34 -31 46 c-16 25 -57 71 -91 101
 | 
			
		||||
-53 48 -66 55 -102 55 -53 0 -180 63 -209 103 -18 24 -19 29 -6 38 24 16 103
 | 
			
		||||
2 176 -31z"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 5.3 KiB  | 
@@ -1,29 +0,0 @@
 | 
			
		||||
<?xml version="1.0" standalone="no"?>
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
 | 
			
		||||
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
 | 
			
		||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
 width="1280.000000pt" height="1210.000000pt" viewBox="0 0 1280.000000 1210.000000"
 | 
			
		||||
 preserveAspectRatio="xMidYMid meet">
 | 
			
		||||
<metadata>
 | 
			
		||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
 | 
			
		||||
</metadata>
 | 
			
		||||
<g transform="translate(0.000000,1210.000000) scale(0.100000,-0.100000)"
 | 
			
		||||
fill="#000000" stroke="none">
 | 
			
		||||
<path d="M5699 11502 c-371 -328 -1786 -1580 -3144 -2782 -1359 -1201 -2489
 | 
			
		||||
-2203 -2512 -2225 l-41 -40 329 -370 c181 -203 333 -371 338 -373 6 -2 949
 | 
			
		||||
828 2098 1844 1148 1017 2434 2154 2857 2529 423 374 773 680 777 680 5 0
 | 
			
		||||
1291 -1135 2859 -2523 1568 -1388 2857 -2525 2864 -2528 7 -3 122 119 267 283
 | 
			
		||||
140 158 289 327 332 375 l78 87 -1038 918 c-571 505 -1882 1665 -2913 2578
 | 
			
		||||
-1031 912 -1998 1768 -2149 1902 -214 190 -277 240 -288 232 -9 -8 -17 -8 -26
 | 
			
		||||
-1 -10 8 -171 -129 -688 -586z"/>
 | 
			
		||||
<path d="M1910 10089 l0 -1201 108 98 c59 53 418 378 798 721 l691 623 6 347
 | 
			
		||||
c4 190 7 406 7 480 l0 133 -805 0 -805 0 0 -1201z"/>
 | 
			
		||||
<path d="M4875 8574 c-836 -740 -1845 -1632 -2242 -1982 l-722 -637 0 -2875
 | 
			
		||||
c-1 -2870 -1 -2875 20 -2920 29 -64 84 -117 146 -140 53 -20 80 -20 1478 -20
 | 
			
		||||
l1425 0 2 1277 c3 1272 3 1278 24 1323 26 58 76 108 134 134 45 21 49 21 1260
 | 
			
		||||
21 1211 0 1215 0 1260 -21 58 -26 108 -76 134 -134 21 -45 21 -51 24 -1322 l2
 | 
			
		||||
-1278 1425 0 c1398 0 1425 0 1478 20 62 23 117 76 146 140 21 45 21 51 20
 | 
			
		||||
2920 l0 2875 -831 735 c-457 404 -1360 1202 -2007 1775 -1658 1467 -1645 1455
 | 
			
		||||
-1651 1455 -3 0 -689 -606 -1525 -1346z"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 1.6 KiB  | 
@@ -1,24 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="iso-8859-1"?>
 | 
			
		||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
			
		||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
 | 
			
		||||
	 viewBox="0 0 485.252 485.252" xml:space="preserve">
 | 
			
		||||
<g id="XMLID_288_">
 | 
			
		||||
	<path id="XMLID_289_" d="M315.478,319.287c-7.563-11.352-20.3-18.164-33.928-18.164h-81.497l-1.975-20.936l65.72,9.052
 | 
			
		||||
		c1.497,0.205,2.993,0.301,4.49,0.301c16.032,0,30.012-11.829,32.255-28.154c2.451-17.84-10.014-34.295-27.859-36.753l-67.87-9.347
 | 
			
		||||
		l-14.95-22.24l-2.626-27.853c-2.118-22.402-22.131-39.03-44.403-36.745c-22.417,2.109-38.863,21.994-36.762,44.402l16.303,172.857
 | 
			
		||||
		c1.975,20.936,19.553,36.93,40.582,36.93h96.766l56.248,84.441c7.865,11.79,20.792,18.174,33.958,18.174
 | 
			
		||||
		c7.771,0,15.62-2.221,22.56-6.846c18.738-12.475,23.803-37.779,11.336-56.51L315.478,319.287z"/>
 | 
			
		||||
	<path id="XMLID_290_" d="M163.643,108.688c30.01,0,54.337-24.333,54.337-54.346C217.98,24.333,193.653,0,163.643,0
 | 
			
		||||
		c-30.026,0-54.354,24.333-54.354,54.343C109.29,84.355,133.617,108.688,163.643,108.688z"/>
 | 
			
		||||
	<path id="XMLID_291_" d="M222.741,406.453H108.063L86.762,236.356c-1.115-8.924-9.17-15.292-18.197-14.153
 | 
			
		||||
		c-8.932,1.115-15.269,9.266-14.154,18.197l23.085,184.377c1.019,8.159,7.961,14.281,16.176,14.281h129.069
 | 
			
		||||
		c9.01,0,16.303-7.301,16.303-16.303C239.044,413.752,231.751,406.453,222.741,406.453z"/>
 | 
			
		||||
	<path id="XMLID_292_" d="M406.618,319.467c0-21.502,24.35-30.94,24.35-61.759c0-20.446-15.583-30.286-26.073-34.594
 | 
			
		||||
		c-2.808-1.152-5.521,1.896-4.014,4.531c3.606,6.301,7.722,16.468,7.722,30.063c0,24.186-22.58,27.358-22.58,61.759
 | 
			
		||||
		c0,22.987,15.241,34.074,25.869,39.012c2.855,1.327,5.712-1.857,4.087-4.555C411.674,346.781,406.618,335.071,406.618,319.467z"/>
 | 
			
		||||
	<path id="XMLID_293_" d="M369.124,249.696c2.855,1.326,5.712-1.857,4.086-4.555c-4.305-7.142-9.361-18.853-9.361-34.457
 | 
			
		||||
		c0-21.502,24.352-30.941,24.352-61.759c0-20.445-15.583-30.286-26.073-34.593c-2.808-1.153-5.522,1.896-4.015,4.53
 | 
			
		||||
		c3.607,6.301,7.723,16.468,7.723,30.063c0,24.187-22.58,27.357-22.58,61.759C343.254,233.672,358.496,244.76,369.124,249.696z"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 2.2 KiB  | 
@@ -1,53 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="iso-8859-1"?>
 | 
			
		||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
			
		||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
			
		||||
	 viewBox="0 0 397.6 397.6" style="enable-background:new 0 0 397.6 397.6;" xml:space="preserve">
 | 
			
		||||
<path d="M313.1,147.875c-6.057,0-11.962,0.654-17.661,1.871l-15.805-53.435l37.842-2.565c4.447-0.301,8.487,2.613,9.602,6.932
 | 
			
		||||
	c0.689,2.671,0.161,5.488-1.45,7.728c-1.611,2.24-4.113,3.638-6.865,3.834l-9.126,0.652c-3.581,0.256-6.276,3.366-6.02,6.947
 | 
			
		||||
	s3.361,6.289,6.947,6.02l9.126-0.652c6.611-0.472,12.623-3.829,16.493-9.21c3.87-5.382,5.14-12.148,3.484-18.566
 | 
			
		||||
	c-2.677-10.375-12.376-17.364-23.069-16.654l-45.936,3.114c-1.969,0.134-3.77,1.154-4.896,2.773
 | 
			
		||||
	c-1.127,1.62-1.457,3.663-0.897,5.556l6.405,21.656H145.477l-6.316-12H155.5c3.59,0,6.5-2.91,6.5-6.5s-2.91-6.5-6.5-6.5h-47
 | 
			
		||||
	c-3.59,0-6.5,2.91-6.5,6.5s2.91,6.5,6.5,6.5h16.086l9.542,18.349l-18.836,33.485c-9.549-3.751-19.929-5.834-30.792-5.834
 | 
			
		||||
	c-46.593,0-84.5,37.906-84.5,84.5s37.907,84.5,84.5,84.5c44.404,0,80.892-34.436,84.225-78c0,0,31.695,0,31.776,0
 | 
			
		||||
	c2.235,0,4.32-1.15,5.511-3.055l68.779-110.047l8.185,27.672c-31.758,12.162-54.376,42.945-54.376,78.93
 | 
			
		||||
	c0,46.594,37.907,84.5,84.5,84.5s84.5-37.906,84.5-84.5S359.693,147.875,313.1,147.875z M84.5,303.875
 | 
			
		||||
	c-39.425,0-71.5-32.075-71.5-71.5s32.075-71.5,71.5-71.5c8.549,0,16.75,1.513,24.355,4.276l-31.482,55.968
 | 
			
		||||
	c-3.726,2.365-6.206,6.516-6.206,11.256c0,7.363,5.969,13.333,13.333,13.333c5.002,0,9.354-2.759,11.636-6.833h59.556
 | 
			
		||||
	C152.395,275.263,121.733,303.875,84.5,303.875z M96.136,225.875c-0.99-1.769-2.37-3.285-4.025-4.439l28.528-50.717
 | 
			
		||||
	c19.37,11.397,32.922,31.647,35.052,55.156H96.136z M168.725,225.875c-2.17-28.365-18.393-52.845-41.715-66.482l14.327-25.471
 | 
			
		||||
	l48.396,91.953H168.725z M200.983,219.337l-48.665-92.462h106.454L200.983,219.337z M313.1,303.875c-39.425,0-71.5-32.075-71.5-71.5
 | 
			
		||||
	c0-30.093,18.697-55.885,45.077-66.418l16.89,57.105c-2.348,2.403-3.8,5.687-3.8,9.313c0,7.363,5.969,13.333,13.333,13.333
 | 
			
		||||
	s13.333-5.97,13.333-13.333c0-6.354-4.449-11.661-10.399-12.999l-16.895-57.123c4.518-0.897,9.184-1.378,13.962-1.378
 | 
			
		||||
	c39.425,0,71.5,32.075,71.5,71.5S352.525,303.875,313.1,303.875z"/>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
<g>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 2.5 KiB  | 
@@ -1,13 +0,0 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import Header from '$lib/Header.svelte';
 | 
			
		||||
	// import Footer from '$lib/Footer.svelte';
 | 
			
		||||
	import '../css/style.css'
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Header />
 | 
			
		||||
 | 
			
		||||
<main>
 | 
			
		||||
	<slot />
 | 
			
		||||
</main>
 | 
			
		||||
 | 
			
		||||
<!-- <Footer/> -->
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
export const prerender = true;
 | 
			
		||||
export const trailingSlash = 'always';
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import icon from "$lib/images/haus.svg"
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Home</title>
 | 
			
		||||
	<meta name="description" content="Home"/>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section id='content_id' class='content'>
 | 
			
		||||
	<h1>Home</h1>
 | 
			
		||||
	<figure>
 | 
			
		||||
		<img src={icon} alt="Home" width=150/>
 | 
			
		||||
	</figure>
 | 
			
		||||
</section>
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
export const prerender = true;
 | 
			
		||||
export const trailingSlash = 'always';
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import { onMount } from "../../../node_modules/svelte/internal";
 | 
			
		||||
	import icon from "$lib/images/wheels.svg"
 | 
			
		||||
 | 
			
		||||
	let backend_url = "https://home.blackfinn.de/api/bicycle";
 | 
			
		||||
	// let backend_url = "http://localhost:5000/api/bicycle";
 | 
			
		||||
 | 
			
		||||
	let year = "2024";
 | 
			
		||||
	let total_distance_value = 0.0;
 | 
			
		||||
	let total_distance_unit = "m"
 | 
			
		||||
	let year_result_value = 0.0;
 | 
			
		||||
	let year_result_unit = "m"
 | 
			
		||||
 | 
			
		||||
	function get_api() {
 | 
			
		||||
		fetch(backend_url)
 | 
			
		||||
		.then(response => response.json())
 | 
			
		||||
		.then(data => {
 | 
			
		||||
			total_distance_value = data.Years[year].total_distance.value
 | 
			
		||||
			total_distance_unit = data.Years[year].total_distance.unit
 | 
			
		||||
			year_result_value = data.Years[year].year_result.value
 | 
			
		||||
			year_result_unit = data.Years[year].year_result.unit
 | 
			
		||||
		}).catch(error => {
 | 
			
		||||
			console.log(error);
 | 
			
		||||
			return [];
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setInterval(() => {
 | 
			
		||||
		get_api();
 | 
			
		||||
	}, 1000)
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		get_api();
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Bicycle</title>
 | 
			
		||||
	<meta name="description" content="Bicycle"/>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section id='content_id' class='content'>
 | 
			
		||||
	<h1>Bicycle</h1>
 | 
			
		||||
	<figure>
 | 
			
		||||
		<img src={icon} alt="Bicycle" width=150/>
 | 
			
		||||
	</figure>
 | 
			
		||||
	<h1>Total distance: {total_distance_value} {total_distance_unit}</h1>
 | 
			
		||||
	<h1>Year result: {year_result_value} {year_result_unit}</h1>
 | 
			
		||||
</section>
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
export const prerender = true;
 | 
			
		||||
export const trailingSlash = 'always';
 | 
			
		||||
@@ -1,152 +0,0 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import { bind, onMount } from "svelte/internal";
 | 
			
		||||
	import icon from "$lib/images/hahn.svg"
 | 
			
		||||
 | 
			
		||||
	// base api url
 | 
			
		||||
	let backend_url = "http://localhost:5000/gates";
 | 
			
		||||
 | 
			
		||||
	let value_state_outer = "";
 | 
			
		||||
	let value_open_times_outer = "";
 | 
			
		||||
	let value_close_times_outer = "";
 | 
			
		||||
 | 
			
		||||
	let options_state = [
 | 
			
		||||
		{id: 1, text: "Schließen" },	// means "is open"
 | 
			
		||||
		{id: 2, text: "Öffnen" },
 | 
			
		||||
		{id: 3, text: "Schließt..." },
 | 
			
		||||
		{id: 4, text: "Öffnet..." },
 | 
			
		||||
	];
 | 
			
		||||
	let options_open = [
 | 
			
		||||
		{id: 1, text: "Sonnenaufgang"},
 | 
			
		||||
		{id: 2, text: "08:00"},
 | 
			
		||||
		{id: 3, text: "09:00"},
 | 
			
		||||
		{id: 4, text: "10:00"},
 | 
			
		||||
		{id: 5, text: "11:00"},
 | 
			
		||||
		{id: 6, text: "12:00"}
 | 
			
		||||
	];
 | 
			
		||||
	let options_close = [
 | 
			
		||||
		{id: 1, text: "Sonnenuntergang"},
 | 
			
		||||
		{id: 2, text: "Sonnenuntergang + 30min"},
 | 
			
		||||
		{id: 3, text: "16:00"},
 | 
			
		||||
		{id: 4, text: "16:30"},
 | 
			
		||||
		{id: 5, text: "17:00"},
 | 
			
		||||
		{id: 6, text: "17:30"},
 | 
			
		||||
		{id: 7, text: "18:00"},
 | 
			
		||||
		{id: 8, text: "18:30"},
 | 
			
		||||
		{id: 9, text: "19:00"},
 | 
			
		||||
		{id: 10, text: "19:30"},
 | 
			
		||||
		{id: 11, text: "20:00"},
 | 
			
		||||
		{id: 12, text: "20:30"},
 | 
			
		||||
		{id: 13, text: "21:00"},
 | 
			
		||||
		{id: 14, text: "21:30"},
 | 
			
		||||
		{id: 15, text: "22:00"},
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
	function get_next_state(state) {
 | 
			
		||||
		if(state == 1) {
 | 
			
		||||
			return 2;
 | 
			
		||||
		}
 | 
			
		||||
		if(state == 2) {
 | 
			
		||||
			return 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handle_button_outer(event) {
 | 
			
		||||
		console.log(value_state_outer)
 | 
			
		||||
		for( var i = 0; i < options_state.length; i++) {
 | 
			
		||||
			if(options_state[i].text == value_state_outer) {
 | 
			
		||||
				fetch(backend_url + "/outer", {
 | 
			
		||||
					method: "PATCH",
 | 
			
		||||
					headers: {
 | 
			
		||||
						'Content-type': 'application/json'
 | 
			
		||||
					},
 | 
			
		||||
					body: JSON.stringify({
 | 
			
		||||
						outer_gate: {state: get_next_state(options_state[i].id)}
 | 
			
		||||
					})
 | 
			
		||||
				})
 | 
			
		||||
				.then(response => response.json())
 | 
			
		||||
				// .then(result => console.log(result))
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handle_open_times_outer() {
 | 
			
		||||
		alert(value_open_times_outer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handle_close_times_outer() {
 | 
			
		||||
		alert(value_close_times_outer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function set_state_outer(index) {
 | 
			
		||||
		for( var i = 0; i < options_state.length; i++) {
 | 
			
		||||
			if(options_state[i].id == index) {
 | 
			
		||||
				value_state_outer = options_state[i].text;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function get_gates() {
 | 
			
		||||
		fetch(backend_url)
 | 
			
		||||
		.then(response => response.json())
 | 
			
		||||
		.then(data => {
 | 
			
		||||
			set_state_outer(data.outer_gate.state)
 | 
			
		||||
		}).catch(error => {
 | 
			
		||||
			console.log(error);
 | 
			
		||||
			return [];
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setInterval(() => {
 | 
			
		||||
		get_gates();
 | 
			
		||||
	}, 1000)
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		get_gates();
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Chicken</title>
 | 
			
		||||
	<meta name="description" content="Chicken"/>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section id='content_id' class='content'>
 | 
			
		||||
	<h1>Chicken</h1>
 | 
			
		||||
	<figure>
 | 
			
		||||
		<img src={icon} alt="Chicken" width=150/>
 | 
			
		||||
	</figure>
 | 
			
		||||
	<hr>
 | 
			
		||||
	<table>
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td class="left">
 | 
			
		||||
				<h2>Außenklappe</h2>
 | 
			
		||||
			</td>
 | 
			
		||||
			<td>
 | 
			
		||||
				<input type="submit" enabled id="button_outer" value={value_state_outer} style="margin-left:55px;width:200px" on:click={(event) => handle_button_outer(event)}/>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td class="left">Öffnen:</td>
 | 
			
		||||
			<td class="input">
 | 
			
		||||
				<select style="margin-left:55px;width:250px;" bind:value={value_open_times_outer} on:change={handle_open_times_outer}>
 | 
			
		||||
					{#each options_open as option}
 | 
			
		||||
						<option value={option}>{option.text}</option>
 | 
			
		||||
					{/each}
 | 
			
		||||
				</select>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td class="left">Schließen:</td>
 | 
			
		||||
			<td class="input">
 | 
			
		||||
				<select style="margin-left:55px;width:250px;"  bind:value={value_close_times_outer} on:change={handle_close_times_outer}>
 | 
			
		||||
					{#each options_close as option}
 | 
			
		||||
						<option value={option}>{option.text}</option>
 | 
			
		||||
					{/each}
 | 
			
		||||
				</select>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
	</table>
 | 
			
		||||
	<hr>
 | 
			
		||||
</section>
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
export const prerender = true;
 | 
			
		||||
export const trailingSlash = 'always';
 | 
			
		||||
@@ -1,60 +0,0 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import { onMount } from "../../../node_modules/svelte/internal";
 | 
			
		||||
    // import Plot from "$lib/Uplot.svelte"
 | 
			
		||||
	import icon from "$lib/images/sauna.svg"
 | 
			
		||||
 | 
			
		||||
	let backend_url = "https://home.blackfinn.de/api/sauna";
 | 
			
		||||
 | 
			
		||||
	let temperature_value = 0.0
 | 
			
		||||
	let temperature_unit = "°C"
 | 
			
		||||
	let time = "00:00:00";
 | 
			
		||||
	let updated = false;
 | 
			
		||||
	let plot;
 | 
			
		||||
 | 
			
		||||
	function get_temperature() {
 | 
			
		||||
		fetch(backend_url)
 | 
			
		||||
		.then(response => response.json())
 | 
			
		||||
		.then(data => {
 | 
			
		||||
			temperature_unit = data.unit
 | 
			
		||||
			temperature_value = data.value
 | 
			
		||||
			time = data.time.slice(11, 19)
 | 
			
		||||
			// if(plot) {
 | 
			
		||||
			// 	plot.add_data(temperature_value);
 | 
			
		||||
			// }
 | 
			
		||||
			updated = JSON.parse(data.valid);
 | 
			
		||||
		}).catch(error => {
 | 
			
		||||
			console.log(error);
 | 
			
		||||
			return [];
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setInterval(() => {
 | 
			
		||||
		get_temperature();
 | 
			
		||||
	}, 1000)
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		// if(plot) {
 | 
			
		||||
		// 	plot.create("Temperature", "°C", 4 * 60 * 60 /* 4h */);
 | 
			
		||||
		// }
 | 
			
		||||
		get_temperature();
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Sauna</title>
 | 
			
		||||
	<meta name="description" content="Sauna"/>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section id='content_id' class='content'>
 | 
			
		||||
	<h1>Sauna</h1>
 | 
			
		||||
	<figure>
 | 
			
		||||
		<img src={icon} alt="Sauna" width=150/>
 | 
			
		||||
	</figure>
 | 
			
		||||
	{#if updated}
 | 
			
		||||
		<div>{time} Uhr:</div>
 | 
			
		||||
		<h1>{temperature_value} {temperature_unit}</h1>
 | 
			
		||||
		<!-- <div><Plot/></div> -->
 | 
			
		||||
	{/if}
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
<!-- <Plot bind:this={plot}/> -->
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 1.5 KiB  | 
@@ -1,3 +0,0 @@
 | 
			
		||||
# https://www.robotstxt.org/robotstxt.html
 | 
			
		||||
User-agent: *
 | 
			
		||||
Disallow:
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
//import adapter from '@sveltejs/adapter-auto';
 | 
			
		||||
// import adapter from '@sveltejs/adapter-node';
 | 
			
		||||
import adapter from '@sveltejs/adapter-static';
 | 
			
		||||
/** @type {import('@sveltejs/kit').Config} */
 | 
			
		||||
const config = {
 | 
			
		||||
	kit: {
 | 
			
		||||
		adapter: adapter({
 | 
			
		||||
			// default options are shown. On some platforms
 | 
			
		||||
			// these options are set automatically — see below
 | 
			
		||||
			pages: '../build/webui',
 | 
			
		||||
			assets: '../build/webui',
 | 
			
		||||
			fallback: null,
 | 
			
		||||
			precompress: false,
 | 
			
		||||
			strict: true
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
import { expect, test } from '@playwright/test';
 | 
			
		||||
 | 
			
		||||
test('about page has expected h1', async ({ page }) => {
 | 
			
		||||
	await page.goto('/about');
 | 
			
		||||
	await expect(page.locator('h1')).toHaveText('About this app');
 | 
			
		||||
});
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
import { sveltekit } from '@sveltejs/kit/vite';
 | 
			
		||||
 | 
			
		||||
/** @type {import('vite').UserConfig} */
 | 
			
		||||
const config = {
 | 
			
		||||
	plugins: [sveltekit()],
 | 
			
		||||
	test: {
 | 
			
		||||
		include: ['src/**/*.{test,spec}.{js,ts}']
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
		Reference in New Issue
	
	Block a user