Adds application

This commit is contained in:
Michel Fedde 2025-08-16 16:22:11 +02:00
commit b2dc69142a
12 changed files with 1683 additions and 0 deletions

5
.env.dist Normal file
View file

@ -0,0 +1,5 @@
DOMAIN="https://my.home.assistant.tld"
TOKEN="TOKEN"
TEMPERATURE_SENSOR=sensor.temperature_sensor
MOISTURE_SENSOR=sensor.moisture_sensor

View 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
View file

@ -0,0 +1,3 @@
.idea/
node_modules/
dist/

17
build/build-cli.mjs Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
import context from './context.mjs'
await context.watch();

1418
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

17
package.json Normal file
View 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"
}
}

Binary file not shown.

102
public/index.html Normal file
View 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
View 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
View 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"
}
}