diff --git a/README.md b/README.md index d9193b5..ef66a02 100644 --- a/README.md +++ b/README.md @@ -1,311 +1,3 @@ # Scoreboard-JS -Echtzeit-Anzeigetafel für Sportveranstaltungen — zeigt Timer und Spielstand auf einem separaten Monitor an und wird über ein Admin-Panel gesteuert. - ---- - -## Übersicht - -Das System besteht aus zwei Teilen: - -| Teil | URL | Beschreibung | -|---|---|---| -| **Monitor** | `http://:3000/` | Vollbildanzeige für den Beamer/Monitor | -| **Admin-Panel** | `http://:3000/admin` | Steuerung von Timer, Scoreboard und Anzeige | - -Alle Änderungen vom Admin-Panel werden über **WebSockets (Socket.IO)** in Echtzeit auf dem Monitor angezeigt — ohne Seiten-Reload. - -Der Monitor rendert je nach Serverzustand eine von drei Ansichten: - -| Ansicht | Template | Wann | -|---|---|---| -| Splashscreen | `splashscreen.hbs` | Solange der Splashscreen aktiv ist (Standard beim Start) | -| Timer + Score | `indexScore.hbs` | Splashscreen aus, Scoreboard aktiv (Standard) | -| Nur Timer | `index.hbs` | Splashscreen aus, Scoreboard deaktiviert | - ---- - -## Voraussetzungen - -- Node.js (v18 oder neuer) -- npm - ---- - -## Installation & Start - -```bash -cd scoreboard -npm install -npm start -``` - -Der Server startet mit `nodemon` und ist anschließend unter Port **3000** erreichbar. -Der Socket.IO-Server läuft separat auf Port **3001**. - -### Port ändern - -Der HTTP-Port lässt sich über die Umgebungsvariable `PORT` setzen: - -```bash -PORT=80 npm start -``` - -Auf dem Anzeige-Rechner (Raspberry Pi) wird Port **80** verwendet, damit der -Kiosk-Browser die Seite ohne Portangabe unter `http://localhost` öffnen kann. -Port 80 erfordert erhöhte Rechte. - ---- - -## Projektstruktur - -``` -scoreboard/ -├── app.js # Express-App, Middleware, Routen -├── bin/www # HTTP-Server, startet die App -├── db.json # Persistente Liste bekannter Teamnamen -├── controllers/ -│ ├── socketio.js # Socket.IO-Server (Port 3001) -│ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec) -│ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel) -│ ├── db.js # Lesen/Schreiben der Teamnamen in db.json -│ ├── etc.js # Splashscreen-Status -│ └── cli.js # Start/Stop des Kiosk-Browsers auf dem Anzeigerechner -├── routes/ -│ ├── admin.js # REST-Endpunkte für das Admin-Panel -│ └── index.js # Monitor-Ansicht + WebSocket-Verbindungshandler -├── views/ -│ ├── admin.hbs # Admin-Panel (Handlebars-Template) -│ ├── splashscreen.hbs # Monitor: Vereinslogo -│ ├── index.hbs # Monitor: nur Timer -│ └── indexScore.hbs # Monitor: Timer + Score + Teamnamen -└── public/ - ├── img/ # Logos für den Splashscreen - ├── javascripts/ - │ ├── admin.js # Frontend-Logik Admin-Panel - │ ├── index.js # Frontend-Logik Monitor (ohne Score) - │ ├── indexScore.js # Frontend-Logik Monitor (mit Score) - │ └── splashscreen.js # Frontend-Logik Splashscreen (nur Refresh-Handler) - └── stylesheets/ - ├── admin.css - ├── index.css - ├── indexScore.css - ├── splashscreen.css - └── seven-segment.css # Seven-Segment-Display-Schrift für den Timer -``` - ---- - -## Funktionen - -### Timer - -- Countdown-Timer mit konfigurierbarer Startzeit (Standard: **7:00 Minuten**) -- Start / Pause -- Zurücksetzen auf eine neue Zeit (0:00 bis 10:00, in 15-Sekunden-Schritten) -- Feineinstellung im laufenden Betrieb: ±1s, ±5s, ±10s -- Nach Ablauf wird `timerEnded` an alle Clients gesendet und "ENDE" auf dem Monitor angezeigt - -### Scoreboard - -- Punktestand für **Team A** und **Team B**, direkt im Admin-Panel über +1/-1 steuerbar -- Score direkt auf einen bestimmten Wert setzen -- Score zurücksetzen (beide Teams auf 0) -- **Scoreboard ein-/ausblenden:** Der Monitor wechselt zwischen Timer-only-Ansicht und Timer+Score-Ansicht - -### Teams konfigurieren - -Jedes Team hat folgende Felder: - -| Feld | Beschreibung | -|---|---| -| `name` | Hauptname des Teams | -| `name2` | Zweiter Name (nur bei Spielgemeinschaft) | -| `isSpielgemeinschaft` | Wenn aktiv, werden beide Namen untereinander angezeigt | - -Die Namen können frei eingetippt oder per Dropdown aus der **Team-Datenbank** gewählt werden. - -### Team-Datenbank - -Häufig verwendete Teamnamen werden in `db.json` gespeichert und stehen im -Konfigurationsdialog als Auswahl zur Verfügung. Teams lassen sich im -Debugfenster hinzufügen und löschen. - -### Seitenwechsel (Halbzeit) - -Über "Seitenwechsel" tauschen Team A und Team B auf dem Monitor die Seiten — der Spielstand bleibt unverändert, nur die Anzeigepositionen (links/rechts) wechseln. - -### Spiegelung des Admin-Panels - -Sitzt die Bedienung **hinter** dem Monitor, stimmt die Links/Rechts-Zuordnung im -Admin-Panel nicht mehr mit der Sicht auf die Anzeigetafel überein. Der Schalter -"Teamanzeige im Adminpanel spiegeln" im Debugfenster dreht die Darstellung im -Admin-Panel um — inklusive der +1/-1-Buttons, die dann dem jeweils richtigen Team -zugeordnet werden. Die Monitoranzeige bleibt davon unberührt. - -### Splashscreen - -Standardmäßig zeigt der Monitor beim Start das Vereinslogo. Über den Schalter im -Debugfenster wird zwischen Splashscreen und Spielanzeige umgeschaltet. - -### Debugfenster - -Sammelt die selten benötigten Schalter: Spiegelung, Scoreboard ein/aus, -Splashscreen ein/aus, Monitor neu laden, Kiosk-Browser starten/stoppen sowie die -Verwaltung der Team-Datenbank. - -### Kiosk-Browser steuern - -Auf dem Anzeige-Rechner (Raspberry Pi) kann der Chromium-Browser aus dem -Admin-Panel heraus im Kiosk-Modus gestartet und wieder beendet werden. Die -Funktion ist Linux-spezifisch und setzt einen Benutzer `pi` mit Zugriff auf -Display `:0` voraus. - -### Monitor neu laden - -Der Button "Monitor neu laden" sendet ein `refresh`-Event an alle verbundenen Clients und löst einen Seiten-Reload des Monitors aus. Nötig, wenn zwischen zwei Ansichten gewechselt wurde. - ---- - -## Architektur / Datenfluss - -``` -Admin-Panel (Browser) - │ - │ HTTP REST (fetch) - ▼ - Express-Server :3000 - routes/admin.js - │ - ├── controllers/timer.js ──┐ - ├── controllers/score.js ──┤ - ├── controllers/etc.js ──┤ - ├── controllers/db.js ──┐ │ - └── controllers/cli.js │ │ io.sockets.emit(event, data) - │ ▼ - db.json Socket.IO-Server :3001 - │ - WebSocket-Verbindung - │ - ┌──────────────────────┼──────────────────────┐ - ▼ ▼ ▼ - Monitor (splashscreen) Monitor (index) Monitor (indexScore) - splashscreen.js index.js indexScore.js -``` - -Der gesamte Spielzustand (Timer, Score, Teams, Schalter) liegt **im -Arbeitsspeicher** der Controller. Ein Serverneustart setzt ihn zurück; einzig die -Teamnamen in `db.json` bleiben erhalten. - -### WebSocket-Events - -| Event | Richtung | Beschreibung | -|---|---|---| -| `timerDurationLeft` | Server → Client | Aktuelle Restzeit als String `MM:SS` | -| `timerEnded` | Server → Client | Timer abgelaufen | -| `score` | Server → Client | Aktueller Spielstand als String `X:Y` | -| `scoreSideswitch` | Server → Client | Seitenwechsel ausgelöst — Frontend neu laden | -| `refresh` | Server → Client | Kompletten Seiten-Reload auslösen | - ---- - -## REST-API (Admin-Endpunkte) - -Alle Endpunkte sind unter `/admin` erreichbar. - -### Allgemein - -| Methode | Endpunkt | Beschreibung | -|---|---|---| -| GET | `/admin/refreshMonitor` | Sendet `refresh` an alle Monitors | -| GET | `/admin/openBrowser` | Startet den Kiosk-Browser auf dem Anzeigerechner | -| GET | `/admin/killBrowser` | Beendet den Kiosk-Browser | - -### Anzeige (etc) - -| Methode | Endpunkt | Beschreibung | -|---|---|---| -| GET | `/admin/etcGetValues` | Aktuellen Splashscreen-Status abrufen | -| GET | `/admin/etcToggleSplashscreen` | Splashscreen ein-/ausblenden | - -Antwortformat: -```json -{ "splashscreenEnabled": true } -``` - -### Timer - -| Methode | Endpunkt | Body | Beschreibung | -|---|---|---|---| -| GET | `/admin/timerStart` | — | Timer starten | -| GET | `/admin/timerPause` | — | Timer pausieren | -| POST | `/admin/timerReset` | `{ duration: }` | Timer zurücksetzen | -| POST | `/admin/timerIncDec` | `{ value: }` | Timer erhöhen (positiv) / verringern (negativ) | -| GET | `/admin/timerGetValues` | — | Aktuelle Timer-Werte abrufen | - -`timerStart` und `timerPause` antworten mit **406**, wenn die Aktion nicht möglich -ist (Timer läuft bereits, ist bereits pausiert oder abgelaufen). - -Antwortformat `timerGetValues`: -```json -{ - "isPaused": true, - "duration": "PT7M", - "durationLeft": "PT7M", - "print": "07:00" -} -``` - -### Scoreboard - -| Methode | Endpunkt | Body | Beschreibung | -|---|---|---|---| -| GET | `/admin/scoreToggle` | — | Scoreboard ein-/ausblenden | -| GET | `/admin/scoreGetValues` | — | Aktuelle Score-Werte abrufen | -| GET | `/admin/scoreClearScore` | — | Spielstand beider Teams auf 0 | -| GET | `/admin/scoreToggleSideswitch` | — | Seiten der Teams tauschen | -| GET | `/admin/scoreToggleReferenceMirrored` | — | Admin-Panel-Darstellung spiegeln | -| POST | `/admin/scoreSetScore` | `{ team, score }` | Score direkt setzen | -| POST | `/admin/scoreAlterScore` | `{ team, dir }` | Score um 1 ändern (`dir`: `"inc"` / `"dec"`) | -| POST | `/admin/scoreConfigTeams` | `{ teamA: { name, name2, isSpielgemeinschaft }, teamB: { ... } }` | Teams konfigurieren | - -`team` ist jeweils `"teamA"` oder `"teamB"`. - -Antwortformat `scoreGetValues`: -```json -{ - "enabled": true, - "teamA": { "name": "Team A", "name2": "", "score": 0, "isSpielgemeinschaft": 0 }, - "teamB": { "name": "Team B", "name2": "", "score": 0, "isSpielgemeinschaft": 0 }, - "sideswitch": false, - "referenceMirrored": false, - "print": "0:0", - "teams": ["Arheilgen", "Prechtal"] -} -``` - -### Team-Datenbank - -| Methode | Endpunkt | Body | Beschreibung | -|---|---|---|---| -| GET | `/admin/dbGetValues` | — | Alle gespeicherten Teamnamen abrufen | -| POST | `/admin/dbAddTeam` | `{ teamName }` | Teamnamen hinzufügen (Duplikate und Leerstrings werden ignoriert) | -| POST | `/admin/dbDeleteTeam` | `{ teamName }` | Teamnamen löschen | - -Antwortformat: -```json -{ "teams": ["Arheilgen", "Prechtal"] } -``` - ---- - -## Abhängigkeiten - -| Paket | Verwendung | -|---|---| -| `express` | HTTP-Server und Routing | -| `socket.io` | WebSocket-Kommunikation | -| `moment` | Timer-Berechnung und Zeitformatierung | -| `hbs` | Handlebars als View-Engine | -| `morgan` | HTTP-Request-Logging | -| `nodemon` | Automatischer Server-Neustart bei Dateiänderungen | +Implements a score- and timeboard for various sports \ No newline at end of file diff --git a/scoreboard/app.js b/scoreboard/app.js index 089c7ce..2d97c1d 100644 --- a/scoreboard/app.js +++ b/scoreboard/app.js @@ -1,14 +1,11 @@ -// Einstiegspunkt der Express-Applikation. -// Registriert Middleware, View-Engine und Routen, und exportiert die App für bin/www. - var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); -var indexRouter = require('./routes/index'); // Öffentliche Monitor-Ansicht -var adminRouter = require('./routes/admin'); // Admin-Panel (Timer & Scoreboard steuern) +var indexRouter = require('./routes/index'); +var adminRouter = require('./routes/admin'); var app = express(); diff --git a/scoreboard/bin/www b/scoreboard/bin/www index 95be052..eec0010 100644 --- a/scoreboard/bin/www +++ b/scoreboard/bin/www @@ -12,7 +12,7 @@ var http = require('http'); * Get port from environment and store in Express. */ -var port = normalizePort(process.env.PORT || '3000'); +var port = normalizePort(process.env.PORT || '80'); app.set('port', port); /** diff --git a/scoreboard/controllers/cli.js b/scoreboard/controllers/cli.js index 4943376..f251d3d 100644 --- a/scoreboard/controllers/cli.js +++ b/scoreboard/controllers/cli.js @@ -1,22 +1,17 @@ -// CLI-Controller -// Startet und beendet den Anzeige-Browser auf dem Monitor-Rechner (Raspberry Pi). -// Chromium wird im Kiosk-Modus auf Display :0 geöffnet und zeigt die Monitor-Ansicht an. - const { spawn } = require('node:child_process') -let command; // Referenz auf den laufenden Chromium-Prozess -let browserOpen = false; // Status des Browsers, verhindert Mehrfachstart +let command; +let browserOpen = false; -// Startet Chromium im Kiosk-Modus, sofern noch kein Browser laeuft async function openBrowser() { if(!browserOpen) { - // Als Benutzer "pi" starten, damit der Prozess Zugriff auf das X-Display hat + // const command = spawn("chromium-browser --display=:0 --noerrors --disable-session-crashed-bubble --disable-infobars --start-fullscreen http://localhost:3000 &") command = spawn('sudo', ['-upi', 'chromium-browser', '--display=:0', '--incognito', '--noerrors', '--hide-crash-restore-bubble', '--disable-infobars', '--kiosk', 'http://localhost']); - + command.stdout.on('data', data => { console.log("stdout: ", data.toString()); }); - + command.stderr.on('data', data => { console.log("sterr: ", data.toString()); }); @@ -24,7 +19,6 @@ async function openBrowser() { } } -// Beendet den laufenden Browserprozess, sofern einer gestartet wurde async function killBrowser() { if(browserOpen) { command.kill(); diff --git a/scoreboard/controllers/db.js b/scoreboard/controllers/db.js index 73e29d3..a135404 100644 --- a/scoreboard/controllers/db.js +++ b/scoreboard/controllers/db.js @@ -1,61 +1,46 @@ "use strict"; -// Datenbank-Controller -// Haelt die Liste der bekannten Teamnamen persistent in db.json vor, -// damit sie im Adminpanel als Auswahl-Dropdown angeboten werden koennen. +let db; -const fs = require('node:fs'); -const path = require('node:path'); +// Import ESM into CJS file using immediately invoked function expression because of missing top-level await in CJS +(async () => { + const { LowSync } = await import('lowdb'); + const { JSONFileSync } = await import('lowdb/node'); + db = new LowSync(new JSONFileSync('db.json'), { teams: []}) +})(); -// Pfad relativ zum Modul, nicht zum Arbeitsverzeichnis, damit die Datei -// unabhaengig vom Startverzeichnis immer an derselben Stelle liegt -const dbFile = path.join(__dirname, '..', 'db.json'); - -// Liest db.json ein, faellt bei fehlender oder defekter Datei auf eine leere Liste zurueck -function read() { - try { - const data = JSON.parse(fs.readFileSync(dbFile, 'utf-8')); - if(!Array.isArray(data.teams)) data.teams = []; // Fehlender oder falscher teams-Key - return data; - } catch (err) { - console.error("db.json konnte nicht gelesen werden, verwende leere Teamliste: ", err.message); - return { teams: [] }; - } -} - -// Schreibt den uebergebenen Datensatz zurueck nach db.json -function write(data) { - fs.writeFileSync(dbFile, JSON.stringify(data, null, 2)); -} - -// Returns all teams from db function getTeams() { - return read().teams; + db.read(); + return db.data.teams } // Adds passed teamName to db if not already existing function addTeam(teamName) { - const data = read(); - if(!data.teams.includes(teamName) && teamName != "") { // Teamname not in db - data.teams.push(teamName); - write(data); + console.log(teamName) + db.read(); + if(!db.data.teams.includes(teamName) && teamName != "") { // Teamname not in db + db.data.teams.push(teamName); + db.write(); + } else { // Teamname already in db } } // Deletes passed teamName from db if existing function deleteTeam(teamName) { - const data = read(); - if(data.teams.includes(teamName) && teamName != "") { // Teamname in db - data.teams.splice(data.teams.indexOf(teamName), 1); // Delete corresponding array entry - write(data); + db.read(); + if(db.data.teams.includes(teamName) && teamName != "") { // Teamname in db + db.data.teams.splice(db.data.teams.indexOf(teamName), 1) // Delete corresponding array entry + db.write(); } } // Returns important values from db function getValues() { - return read(); + db.read(); + console.log(db.data) + return db.data } module.exports = { getTeams, addTeam, deleteTeam, getValues -} +} \ No newline at end of file diff --git a/scoreboard/controllers/score.js b/scoreboard/controllers/score.js index 1bdefe0..26b52e3 100644 --- a/scoreboard/controllers/score.js +++ b/scoreboard/controllers/score.js @@ -17,7 +17,7 @@ let teamB = { let sideswitch = false; // Attribute to switch sides after halftime let referenceMirrored = false; // If sitting behind the scoreboard, switch sides for admin board -// Funktionsübersicht (Prototypen ohne Implementierung — nur zur Übersicht) +// Function prototypes function setEnabled(status) {}; function configTeam(team, name, name2, isSpielgemeinschaft) {}; function setScore(team, score) {}; @@ -32,7 +32,6 @@ function setEnabled(status) { enabled = status; } -// Setzt Namen und Spielgemeinschafts-Status eines Teams function configTeam(team, name, name2, isSpielgemeinschaft) { if(team == "teamA") { teamA.name = name; @@ -54,7 +53,7 @@ function setScore(team, score) { } } -// Ändert den Score eines Teams um 1 — Richtung "inc" (erhöhen) oder "dec" (verringern) +// Increment score by one function alterScore(team, dir) { if(team == "teamA") { if(dir == "inc") { diff --git a/scoreboard/controllers/socketio.js b/scoreboard/controllers/socketio.js index ae048c3..5778fc4 100644 --- a/scoreboard/controllers/socketio.js +++ b/scoreboard/controllers/socketio.js @@ -1,12 +1,7 @@ -// Erstellt den zentralen Socket.IO-Server auf Port 3001. -// Wird von timer.js, score.js und den Routen importiert, -// um Events (Timer, Score, Refresh) an alle verbundenen Clients zu senden. - const { Server } = require('socket.io'); -// CORS auf "*" gesetzt, da Admin und Monitor auf unterschiedlichen Ports laufen können const io = new Server(3001, { cors: { origin: "*" } -}); + }); module.exports = io \ No newline at end of file diff --git a/scoreboard/package-lock.json b/scoreboard/package-lock.json index 5e9c263..4eea9ad 100644 --- a/scoreboard/package-lock.json +++ b/scoreboard/package-lock.json @@ -14,6 +14,7 @@ "express-ws": "^5.0.2", "hbs": "~4.0.4", "http-errors": "~1.6.3", + "lowdb": "^7.0.1", "moment": "^2.30.1", "morgan": "~1.9.1", "socket.io": "^4.7.5", @@ -469,6 +470,20 @@ "node": ">= 0.10" } }, + "node_modules/lowdb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", + "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", + "dependencies": { + "steno": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -843,6 +858,17 @@ "node": ">= 0.6" } }, + "node_modules/steno": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", + "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/scoreboard/package.json b/scoreboard/package.json index 3fad970..63f7a92 100644 --- a/scoreboard/package.json +++ b/scoreboard/package.json @@ -12,6 +12,7 @@ "express-ws": "^5.0.2", "hbs": "~4.0.4", "http-errors": "~1.6.3", + "lowdb": "^7.0.1", "moment": "^2.30.1", "morgan": "~1.9.1", "socket.io": "^4.7.5", diff --git a/scoreboard/public/javascripts/indexScore.js b/scoreboard/public/javascripts/indexScore.js index 476cec9..2b54de2 100644 --- a/scoreboard/public/javascripts/indexScore.js +++ b/scoreboard/public/javascripts/indexScore.js @@ -45,37 +45,33 @@ function updateScoreFrontend(values) { document.getElementById("score").innerHTML = values.print; // Set score on admin interface if(!values.sideswitch) { - // Normale Seite: teamA links, teamB rechts if(values.teamA.isSpielgemeinschaft) { document.getElementById("teamA").innerHTML = values.teamA.name + '
' + values.teamA.name2; } else { document.getElementById("teamA").innerHTML = values.teamA.name } - + if(values.teamB.isSpielgemeinschaft) { document.getElementById("teamB").innerHTML = values.teamB.name + '
' + values.teamB.name2; } else { document.getElementById("teamB").innerHTML = values.teamB.name } } else { - // Seitenwechsel nach Halbzeit: teamA und teamB tauschen ihre Anzeigeseite if(values.teamA.isSpielgemeinschaft) { document.getElementById("teamB").innerHTML = values.teamA.name + '
' + values.teamA.name2; } else { document.getElementById("teamB").innerHTML = values.teamA.name } - + if(values.teamB.isSpielgemeinschaft) { document.getElementById("teamA").innerHTML = values.teamB.name + '
' + values.teamB.name2; } else { document.getElementById("teamA").innerHTML = values.teamB.name - } + } } } -// Wird beim Laden der Seite aufgerufen, um Timer und Score sofort mit dem aktuellen -// Serverstand zu synchronisieren (verhindert leere Anzeige nach Seiten-Reload) async function initialUpdate() { timerGetValues(); // Request new values for timer scoreGetValues(); // Request new values for score diff --git a/scoreboard/public/stylesheets/splashscreen.css b/scoreboard/public/stylesheets/splashscreen.css index ed6984a..e69de29 100644 --- a/scoreboard/public/stylesheets/splashscreen.css +++ b/scoreboard/public/stylesheets/splashscreen.css @@ -1,9 +0,0 @@ -html, body { - height: 100%; - overflow: hidden; /* Kein Scrollbalken auf dem Beamer */ -} - -body { - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; - background-color: #47BAEA; /* Gleicher Hintergrund wie die Timer- und Score-Ansicht */ -} diff --git a/scoreboard/routes/admin.js b/scoreboard/routes/admin.js index 02dcfab..52763ce 100644 --- a/scoreboard/routes/admin.js +++ b/scoreboard/routes/admin.js @@ -83,9 +83,8 @@ router.post('/timerReset', function(req, res, next) { res.json(timer.getValues()); // Respond with all important values to update the frontend }); -// Express router endpoint to increase or decrease the timer by a value in seconds router.post('/timerIncDec', function(req, res, next) { - timer.incDec(req.body.value); // Positive Werte = erhöhen, negative Werte = verringern + timer.incDec(req.body.value); res.json(timer.getValues()); }); @@ -152,7 +151,6 @@ router.get('/scoreClearScore', function(req, res, next) { res.json(score.getValues()); // Respond with important values for frontend }); -// Express router endpoint to configure both teams (name, name2, Spielgemeinschaft-flag) router.post('/scoreConfigTeams', function(req, res, next) { score.configTeam("teamA", req.body.teamA.name, req.body.teamA.name2, req.body.teamA.isSpielgemeinschaft); score.configTeam("teamB", req.body.teamB.name, req.body.teamB.name2, req.body.teamB.isSpielgemeinschaft); diff --git a/scoreboard/routes/index.js b/scoreboard/routes/index.js index 70d56ab..a64adca 100644 --- a/scoreboard/routes/index.js +++ b/scoreboard/routes/index.js @@ -18,12 +18,10 @@ router.get('/', function(req, res, next) { } }); -// Websocket-Verbindungshandler: wird ausgelöst, sobald ein Client (Monitor) sich verbindet io.on('connection', (socket) => { console.log("A user connected"); - socket.emit("Hello user from server"); // Begrüßungsnachricht an den neuen Client + socket.emit("Hello user from server"); - // Eingehende Nachrichten vom Client loggen (aktuell nur für Debugging) socket.on('message', (message) => { console.log(message) }) diff --git a/scoreboard/views/admin.hbs b/scoreboard/views/admin.hbs index 735a685..b14274a 100644 --- a/scoreboard/views/admin.hbs +++ b/scoreboard/views/admin.hbs @@ -86,7 +86,24 @@ -
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+