Adds application
This commit is contained in:
commit
b2dc69142a
12 changed files with 1683 additions and 0 deletions
5
.env.dist
Normal file
5
.env.dist
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
DOMAIN="https://my.home.assistant.tld"
|
||||||
|
TOKEN="TOKEN"
|
||||||
|
|
||||||
|
TEMPERATURE_SENSOR=sensor.temperature_sensor
|
||||||
|
MOISTURE_SENSOR=sensor.moisture_sensor
|
||||||
39
.forgejo/workflows/build-release-container.yaml
Normal file
39
.forgejo/workflows/build-release-container.yaml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
name: build-container
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- release-*
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILD_TARGET: DOCKER
|
||||||
|
BUILD_LABEL: release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: node-20
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Run build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Install Docker
|
||||||
|
run: curl https://get.docker.com | sh
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
- name: Build and Push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: neintonine/watchmemelt:${{ env.BUILD_LABEL }}
|
||||||
|
context: .
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.idea/
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
17
build/build-cli.mjs
Normal file
17
build/build-cli.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
|
||||||
|
if (!process.env.BUILD_TARGET) {
|
||||||
|
process.env.BUILD_TARGET = "LOCAL";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.BUILD_LABEL) {
|
||||||
|
process.env.BUILD_LABEL = "development";
|
||||||
|
}
|
||||||
|
|
||||||
|
import context from './context.mjs';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await context.rebuild();
|
||||||
|
await context.dispose();
|
||||||
|
})()
|
||||||
|
|
||||||
20
build/context.mjs
Normal file
20
build/context.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as esbuild from "esbuild";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const isDev = process.env.BUILD_TARGET !== 'DOCKER';
|
||||||
|
|
||||||
|
const context = await esbuild.context({
|
||||||
|
entryPoints: [
|
||||||
|
path.join('source', 'main.ts')
|
||||||
|
],
|
||||||
|
bundle: true,
|
||||||
|
outdir: './dist/',
|
||||||
|
platform: 'node',
|
||||||
|
target: 'node10.4',
|
||||||
|
sourcemap: isDev ? 'external' : false,
|
||||||
|
loader: {
|
||||||
|
'.node': 'copy',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default context
|
||||||
3
build/watch.mjs
Normal file
3
build/watch.mjs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import context from './context.mjs'
|
||||||
|
|
||||||
|
await context.watch();
|
||||||
1418
package-lock.json
generated
Normal file
1418
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
17
package.json
Normal file
17
package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "node ./build/build-cli.mjs",
|
||||||
|
"watch": "node ./build/watch.mjs",
|
||||||
|
"start": "node ./dist/main.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^24.2.1",
|
||||||
|
"dotenv": "^17.2.1",
|
||||||
|
"esbuild": "^0.25.9",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"undici": "^7.13.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/JetBrainsMono-Medium.woff2
Normal file
BIN
public/JetBrainsMono-Medium.woff2
Normal file
Binary file not shown.
102
public/index.html
Normal file
102
public/index.html
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Watch me melt...</title>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
src: local("JetBrainsMono"), url("JetBrainsMono-Medium.woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background-color: #f1e1d1;
|
||||||
|
--text-color: black;
|
||||||
|
--muted-color: #777777;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background-color: #282c34;
|
||||||
|
--text-color: #f1e1d1;
|
||||||
|
--muted-color: #bca6a2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 6rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#temp_display :after {
|
||||||
|
content: "C";
|
||||||
|
}
|
||||||
|
|
||||||
|
#moisture :after {
|
||||||
|
content: "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: var(--muted-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="centered">
|
||||||
|
<h2 style="margin-bottom: 0.5rem">
|
||||||
|
Watch me melt!
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<span class="muted">in my office...</span>
|
||||||
|
<h3 id="timestamp"></h3>
|
||||||
|
<h1 id="temp_display">0</h1>
|
||||||
|
<h2 id="moisture">0</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const TEMP_DISPLAY = document.querySelector("#temp_display");
|
||||||
|
const MOISTURE = document.querySelector("#moisture");
|
||||||
|
const TIMESTAMP_DISPLAY = document.querySelector("#timestamp");
|
||||||
|
|
||||||
|
async function requestValues() {
|
||||||
|
const result = await fetch('/get');
|
||||||
|
const data = await result.json();
|
||||||
|
|
||||||
|
TIMESTAMP_DISPLAY.textContent = new Date().toLocaleString();
|
||||||
|
TEMP_DISPLAY.textContent = `${data.temperature.toLocaleString(undefined, { minimumFractionDigits: 2 })}°C`;
|
||||||
|
MOISTURE.textContent = `${data.moisture}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(requestValues, 1000);
|
||||||
|
requestValues();
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
44
source/main.ts
Normal file
44
source/main.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import express from "express";
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import {Agent} from "undici";
|
||||||
|
|
||||||
|
if (fs.existsSync(".env.dist")) {
|
||||||
|
dotenv.config({ path: '.env.dist'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const URI = `${process.env.DOMAIN}/api/states`;
|
||||||
|
|
||||||
|
async function getSensorValue(sensor: string): Promise<number> {
|
||||||
|
const result = await fetch(`${URI}/${sensor}`, {
|
||||||
|
dispatcher: new Agent({
|
||||||
|
connect: {
|
||||||
|
rejectUnauthorized: false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: [
|
||||||
|
['Authorization', `Bearer ${process.env.TOKEN}`]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
if (!result.ok) {
|
||||||
|
console.error(result)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const data: unknown = await result.json();
|
||||||
|
return parseInt(data.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = express()
|
||||||
|
|
||||||
|
server.use("/", express.static("public"));
|
||||||
|
|
||||||
|
server.get('/get', async (req, res) => {
|
||||||
|
const result = {
|
||||||
|
temperature: await getSensorValue(process.env.TEMPERATURE_SENSOR ?? ''),
|
||||||
|
moisture: await getSensorValue(process.env.MOISTURE_SENSOR ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen(8080);
|
||||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"_version": "20.1.0",
|
||||||
|
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2023"],
|
||||||
|
"module": "node16",
|
||||||
|
"target": "es2022",
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "node16"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue