5 Commits

Author SHA1 Message Date
1e634ed122 Merge branch 'runner' into 'main'
Runner

See merge request arifhasanic/monitor-im-flur!1
2025-08-27 22:02:55 +00:00
16131521bc remove tags from gitlab yaml 2025-08-27 23:38:47 +02:00
741720ed07 add build image script 2025-08-27 23:34:53 +02:00
4ae7e7968b fix gitlab ci yml 2025-08-27 22:54:09 +02:00
90e6842d71 setup ci, fix files using biome 2025-08-27 22:51:50 +02:00
25 changed files with 88 additions and 302 deletions

View File

@@ -1,57 +0,0 @@
{
"name": "bun",
"image": "oven/bun:debian",
"privileged": true,
"features": {
"ghcr.io/devcontainers/features/common-utils:1": {
"version": "latest",
"configureZshAsDefaultShell": true,
"username": "bun",
"userUid": "1000",
"userGid": "1000"
},
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "stow,tmux,ripgrep,python3-venv,python3-virtualenv"
},
},
"remoteUser": "bun",
"workspaceFolder": "/home/bun/ws",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bun/ws,type=bind",
"containerEnv": {
},
"runArgs": [
"--net=host",
"-e",
"DISPLAY=${env:DISPLAY}",
"-e",
"TERM=${env:TERM}",
"-e",
"SHELL=${env:SHELL}",
"-v",
"${env:SSH_AUTH_SOCK}:/tmp/ssh-agent.socket",
"-e",
"SSH_AUTH_SOCK=/tmp/ssh-agent.socket"
],
"mounts": [
{
"source": "/tmp/.X11-unix",
"target": "/tmp/.X11-unix",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/dri",
"target": "/dev/dri",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/shm",
"target": "/dev/shm",
"type": "bind",
"consistency": "cached"
}
],
"postCreateCommand": {
}
}

19
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,19 @@
default:
image: oven/bun
stages:
- build
- lint
build:
stage: build
script:
- bun install
- bun run build
lint:
stage: lint
script:
- bun install -g biome
- biome check
- biome lint

View File

@@ -1,3 +0,0 @@
# Ignore artifacts:
build
coverage

View File

@@ -1,4 +0,0 @@
{
"tabWidth": 4,
"useTabs": false
}

View File

@@ -1,5 +1,4 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
"vcs": { "vcs": {
"enabled": false, "enabled": false,
"clientKind": "git", "clientKind": "git",

View File

@@ -10,7 +10,6 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-fast-marquee": "^1.6.5",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"zustand": "^5.0.6", "zustand": "^5.0.6",
@@ -635,8 +634,6 @@
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-fast-marquee": ["react-fast-marquee@1.6.5", "", { "peerDependencies": { "react": ">= 16.8.0 || ^18.0.0", "react-dom": ">= 16.8.0 || ^18.0.0" } }, "sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],

View File

@@ -1,9 +1,9 @@
import js from "@eslint/js"; import js from "@eslint/js";
import globals from "globals"; import { globalIgnores } from "eslint/config";
import reactHooks from "eslint-plugin-react-hooks"; import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh"; import reactRefresh from "eslint-plugin-react-refresh";
import globals from "globals";
import tseslint from "typescript-eslint"; import tseslint from "typescript-eslint";
import { globalIgnores } from "eslint/config";
export default tseslint.config([ export default tseslint.config([
globalIgnores(["dist"]), globalIgnores(["dist"]),

View File

@@ -16,7 +16,6 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-fast-marquee": "^1.6.5",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"zustand": "^5.0.6" "zustand": "^5.0.6"

1
pipeline/build-image.sh Executable file
View File

@@ -0,0 +1 @@
#!/bin/bash

View File

@@ -1,7 +1,42 @@
#root { #root {
height: 100vh;
max-width: 1280px; max-width: 1280px;
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 2rem;
text-align: center; text-align: center;
} }
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -1,11 +1,14 @@
import "@/App.css"; import "@/App.css";
import Flatastic from "@/components/Flatastic/Flatastic";
import Dashboard from "@/components/Dashboard/Dashboard"; import HomeAssistant from "@/components/HomeAssistant/HomeAssistant";
import Timetable from "@/components/Timetable/Timetable";
function App() { function App() {
return ( return (
<> <>
<Dashboard /> <Timetable />
<Flatastic />
<HomeAssistant />
</> </>
); );
} }

View File

@@ -1,12 +1,14 @@
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkMjQ1OTg0YjliYzE0OTNjYTdmZDJmNTA3ODgzN2U1YSIsImlhdCI6MTc1MzQwMjAzNiwiZXhwIjoyMDY4NzYyMDM2fQ.fnLSFKPdk8lkAEB-4ekdGUJ1PSCBxcAyasQF1PyrD3k"; const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkMjQ1OTg0YjliYzE0OTNjYTdmZDJmNTA3ODgzN2U1YSIsImlhdCI6MTc1MzQwMjAzNiwiZXhwIjoyMDY4NzYyMDM2fQ.fnLSFKPdk8lkAEB-4ekdGUJ1PSCBxcAyasQF1PyrD3k";
async function fetchTentHumidity() { async function fetchTentHumidity() {
const url = "/api/states/sensor.third_reality_inc_3rths0224z_luftfeuchtigkeit_2"; const url =
"/api/states/sensor.third_reality_inc_3rths0224z_luftfeuchtigkeit_2";
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Authorization": `Bearer ${token}`, Authorization: `Bearer ${token}`,
} },
}); });
const data = await response.json(); const data = await response.json();
@@ -18,8 +20,8 @@ async function fetchTentTemperature() {
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Authorization": `Bearer ${token}`, Authorization: `Bearer ${token}`,
} },
}); });
const data = await response.json(); const data = await response.json();
@@ -27,4 +29,4 @@ async function fetchTentTemperature() {
return data.state; return data.state;
} }
export { fetchTentHumidity, fetchTentTemperature }; export { fetchTentHumidity, fetchTentTemperature };

View File

@@ -1,28 +0,0 @@
import Flatastic from "@/components/Flatastic/Flatastic";
import HomeAssistant from "@/components/HomeAssistant/HomeAssistant";
import Timetable from "@/components/Timetable/Timetable";
import Footer from "@/components/Footer/Footer";
import style from "./style.module.css";
export default function Dashboard() {
return (
<div className={style.dashboard}>
<div className={style.cardWrapper}>
<div className={style.card}>
<Timetable />
</div>
<div className={style.card}>
<Flatastic />
</div>
<div className={style.card}>
<HomeAssistant />
</div>
</div>
<div className={style.footer}>
<Footer />
</div>
</div>
);
}

View File

@@ -1,25 +0,0 @@
.dashboard {
display: flex;
flex-direction: column;
height: 100%;
}
.cardWrapper {
margin: 30px;
height: 100%;
gap: 30px;
flex-direction: column;
display: flex;
justify-content: flex-start;
}
.card {
border-radius: 10px;
padding: 1px 100px 30px 100px;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
}
.footer {
background-color: rgba(220, 220, 220, 0.5);
}

View File

@@ -58,9 +58,7 @@ export default function DepartureList(props: {
return ( return (
<div className={style.container}> <div className={style.container}>
<div className={style.heading}> <h2>{name} Departures</h2>
<h2>{name}</h2>
</div>
<div className={style.departureLists}> <div className={style.departureLists}>
{departureTables(left)} {departureTables(left)}
{departureTables(right)} {departureTables(right)}

View File

@@ -1,12 +1,7 @@
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: center;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
border-radius: 10px;
padding: 0 10px 20px 10px;
margin-bottom: 20px;
} }
.departureLists { .departureLists {
@@ -14,9 +9,4 @@
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
gap: 120px;
}
.heading {
text-align: left;
} }

View File

@@ -1,10 +1,8 @@
import { useFlatasticStore } from "@/store/flatastic";
import { useEffect } from "react"; import { useEffect } from "react";
import { useFlatasticStore } from "@/store/flatastic";
import type { FlatasticChore } from "@/types/flatasticChore"; import type { FlatasticChore } from "@/types/flatasticChore";
import style from "./style.module.css";
const idToNameMap: Record<number, string> = { const idToNameMap: Record<number, string> = {
1836104: "Gruber", 1836104: "Gruber",
1836101: "Darius", 1836101: "Darius",
@@ -27,13 +25,11 @@ export default function Flatastic() {
return ( return (
<div> <div>
<h1>Flatastic Chores</h1> <h1>Flatastic Chores</h1>
<ul className={style.choreList}> <ul>
{chores.map((chore: FlatasticChore) => ( {chores.map((chore: FlatasticChore) => (
<li key={chore.id} className={style.chore}> <li key={chore.id} style={{ textAlign: "left" }}>
<span className={style.userName}> {idToNameMap[chore.currentUser]}: {chore.title} -
{idToNameMap[chore.currentUser]} Points: {chore.points}
</span>
: {chore.title} - {"🪙".repeat(chore.points)}
</li> </li>
))} ))}
</ul> </ul>

View File

@@ -1,20 +0,0 @@
.choreList {
list-style-type: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
border-radius: 10px;
gap: 10px;
padding: 10px 0;
}
.chore {
padding: 5px 10px;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
text-align: left;
}
.userName {
font-weight: bold;
}

View File

@@ -1,17 +0,0 @@
import Marquee from "react-fast-marquee";
import style from "./style.module.css";
export default function Footer() {
let pasta =
'Ok, so I was playing the hit game Among Us the other day, and when the game started, a red bean-shaped character that appeared to be wearing a spacesuit told me "shh," while having his index finger in front of where his mouth should be. I believe this act made this red bean character extremely suspicious. To understand why this red bean character is suspicious, we first must understand how the game “Among Us” works. The game consists of 10 bean-shaped characters, called crewmates, that are given tasks for them to complete. As these characters do their tasks, they may witness abnormal things that are not supposed to happen, such as the lights turning off on their own, sudden reactor meltdown and other crewmates dying. These acts show that there is an imposter among the crewmates that is sabotaging and is trying to kill everyone. Now why is this important to determine why the red bean is suspicious? Well now we know how the game works, now we must analyze the red beans actions. At the beginning of the game, the red bean tells us “shh” while having his index finger in front of where his mouth should be. This action suggests that the red bean wants us to be quiet, or keep our mouths shut. Now why would the red bean want us to do this? This could be because the red bean wants to limit our communication in order to prevent us from spreading information. What information does the red bean want to prevent from spreading? We can assume that the reason why the red bean wants to prevent us from spreading information, is because he is actually the imposter, and he is planning on committing the crimes mentioned earlier. He does not want others to find out about actions he will cause, therefore he does not want us to communicate with each other. This concludes the reason for why I believe the red bean from the hit game Among Us is suspicious. So if you happen to see a red bean-shaped character wearing a spacesuit, please be careful.';
return (
<div className={style.container}>
<div className={style.info}>BREAKING</div>
<div className={style.marquee}>
<Marquee>{pasta}</Marquee>
</div>
</div>
);
}

View File

@@ -1,17 +0,0 @@
.container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.info {
background-color: red;
color: white;
font-weight: bold;
padding: 5px;
}
.marquee {
}

View File

@@ -1,8 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useHomeAssistantStore } from "@/store/homeAssistant"; import { useHomeAssistantStore } from "@/store/homeAssistant";
import style from "./style.module.css";
export default function Timetable() { export default function Timetable() {
const fetchHomeAssistantData = useHomeAssistantStore( const fetchHomeAssistantData = useHomeAssistantStore(
(state) => state.fetch, (state) => state.fetch,
@@ -23,17 +21,8 @@ export default function Timetable() {
return ( return (
<div> <div>
<h1>Tent</h1> <h1>Tent</h1>
<div className={style.cardContainer}> <p>Temperature: {tentTemperature}°C</p>
<div className={style.card}> <p>Humidity: {tentHumidity}%</p>
<h4>Temperature</h4>
<p>{tentTemperature}°C</p>
</div>
<div className={style.card}>
<h4>Humidity</h4>
<p>{tentHumidity}%</p>
</div>
</div>
</div> </div>
); );
} }

View File

@@ -1,18 +0,0 @@
.cardContainer {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 20px;
}
.card {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
align-items: stretch;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
border-radius: 10px;
}

View File

@@ -2,8 +2,6 @@ import { useEffect } from "react";
import DepartureList from "@/components/DepartureList/DepartureList"; import DepartureList from "@/components/DepartureList/DepartureList";
import { useKVVStore } from "@/store/kvv"; import { useKVVStore } from "@/store/kvv";
import style from "./style.module.css";
export default function Timetable() { export default function Timetable() {
const fetchTimetable = useKVVStore((state) => state.fetch); const fetchTimetable = useKVVStore((state) => state.fetch);
const pStreet = useKVVStore((state) => state.pStreet); const pStreet = useKVVStore((state) => state.pStreet);
@@ -18,8 +16,8 @@ export default function Timetable() {
}, [fetchTimetable]); }, [fetchTimetable]);
return ( return (
<div className={style.wrapper}> <div>
<h1>Timetable 🚉</h1> <h1>Timetable</h1>
<DepartureList departures={pStreet.departureList} name="P-Street" /> <DepartureList departures={pStreet.departureList} name="P-Street" />
<DepartureList departures={hStreet.departureList} name="H-Street" /> <DepartureList departures={hStreet.departureList} name="H-Street" />
</div> </div>

View File

@@ -10,35 +10,12 @@ export default function TimetableRow({
const hour = String(departure.dateTime.hour).padStart(2, "0"); const hour = String(departure.dateTime.hour).padStart(2, "0");
const minute = String(departure.dateTime.minute).padStart(2, "0"); const minute = String(departure.dateTime.minute).padStart(2, "0");
const dateTimeString = `${hour}:${minute}`; const dateTimeString = `${hour}:${minute}`;
const lineNumber = departure.servingLine.number;
return ( return (
<tr className={styles.timetableRow}> <tr className={styles.timetableRow}>
<td className={styles.departureTime}>{dateTimeString}</td> <td>{dateTimeString}</td>
<td <td>{departure.servingLine.number}</td>
className={`${lineNumberToStyle(lineNumber)} ${styles.lineNumber}`} <td>({departure.servingLine.direction})</td>
>
{lineNumber}
</td>
<td>{departure.servingLine.direction}</td>
</tr> </tr>
); );
} }
const lineNumberToStyle = (number) => {
switch (number) {
case "S2":
return styles.S2;
break;
case "S5":
case "S51":
return styles.S5;
break;
case "S1":
case "S11":
return styles.S1;
break;
default:
return styles.lineNumberDefault;
}
};

View File

@@ -1,31 +1,3 @@
td {
padding: 4px 7px;
}
.timetableRow { .timetableRow {
text-align: left; text-align: left;
} }
.departureTime {
font-weight: 600;
}
.lineNumber {
text-align: center;
}
.S2 {
background-color: rgba(200, 100, 200, 0.2);
}
.S5 {
background-color: rgba(230, 20, 20, 0.2);
}
.S1 {
background-color: rgba(20, 180, 90, 0.2);
}
.lineNumberDefault {
background-color: rgba(100, 100, 100, 0.2);
}