Compare commits
3 commits
a1cea9c37d
...
2a1e0904e8
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a1e0904e8 | |||
| cd4877bdd2 | |||
| 6f4c63aa6e |
11 changed files with 182 additions and 135 deletions
14
README.md
14
README.md
|
|
@ -41,7 +41,7 @@ npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Server startet mit `nodemon` und ist anschließend unter Port **3000** erreichbar.
|
Der Server startet mit `nodemon` und ist anschließend unter Port **3000** erreichbar.
|
||||||
Der Socket.IO-Server läuft separat auf Port **3001**.
|
Die WebSocket-Verbindung läuft über denselben Port — es ist kein zweiter Port nötig.
|
||||||
|
|
||||||
### Port ändern
|
### Port ändern
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ scoreboard/
|
||||||
├── bin/www # HTTP-Server, startet die App
|
├── bin/www # HTTP-Server, startet die App
|
||||||
├── db.json # Persistente Liste bekannter Teamnamen
|
├── db.json # Persistente Liste bekannter Teamnamen
|
||||||
├── controllers/
|
├── controllers/
|
||||||
│ ├── socketio.js # Socket.IO-Server (Port 3001)
|
│ ├── socketio.js # Socket.IO-Server (teilt sich den HTTP-Port)
|
||||||
│ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec)
|
│ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec)
|
||||||
│ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel)
|
│ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel)
|
||||||
│ ├── db.js # Lesen/Schreiben der Teamnamen in db.json
|
│ ├── db.js # Lesen/Schreiben der Teamnamen in db.json
|
||||||
|
|
@ -73,7 +73,7 @@ scoreboard/
|
||||||
│ └── cli.js # Start/Stop des Kiosk-Browsers auf dem Anzeigerechner
|
│ └── cli.js # Start/Stop des Kiosk-Browsers auf dem Anzeigerechner
|
||||||
├── routes/
|
├── routes/
|
||||||
│ ├── admin.js # REST-Endpunkte für das Admin-Panel
|
│ ├── admin.js # REST-Endpunkte für das Admin-Panel
|
||||||
│ └── index.js # Monitor-Ansicht + WebSocket-Verbindungshandler
|
│ └── index.js # Monitor-Ansicht (waehlt das passende Template)
|
||||||
├── views/
|
├── views/
|
||||||
│ ├── admin.hbs # Admin-Panel (Handlebars-Template)
|
│ ├── admin.hbs # Admin-Panel (Handlebars-Template)
|
||||||
│ ├── splashscreen.hbs # Monitor: Vereinslogo
|
│ ├── splashscreen.hbs # Monitor: Vereinslogo
|
||||||
|
|
@ -183,7 +183,7 @@ Admin-Panel (Browser)
|
||||||
├── controllers/db.js ──┐ │
|
├── controllers/db.js ──┐ │
|
||||||
└── controllers/cli.js │ │ io.sockets.emit(event, data)
|
└── controllers/cli.js │ │ io.sockets.emit(event, data)
|
||||||
│ ▼
|
│ ▼
|
||||||
db.json Socket.IO-Server :3001
|
db.json Socket.IO (am selben Port)
|
||||||
│
|
│
|
||||||
WebSocket-Verbindung
|
WebSocket-Verbindung
|
||||||
│
|
│
|
||||||
|
|
@ -197,6 +197,12 @@ Der gesamte Spielzustand (Timer, Score, Teams, Schalter) liegt **im
|
||||||
Arbeitsspeicher** der Controller. Ein Serverneustart setzt ihn zurück; einzig die
|
Arbeitsspeicher** der Controller. Ein Serverneustart setzt ihn zurück; einzig die
|
||||||
Teamnamen in `db.json` bleiben erhalten.
|
Teamnamen in `db.json` bleiben erhalten.
|
||||||
|
|
||||||
|
Bricht die WebSocket-Verbindung ab — auf dem Pi, der das WLAN selbst aufspannt,
|
||||||
|
kommt das vor — verbindet sich der Client automatisch neu und holt sich beim
|
||||||
|
`connect`-Event den aktuellen Stand über die REST-Endpunkte. Ein **Wechsel der
|
||||||
|
Ansicht** während der Trennung (Splashscreen, Scoreboard ein/aus) wird dabei
|
||||||
|
nicht bemerkt; dafür ist "Monitor neu laden" gedacht.
|
||||||
|
|
||||||
### WebSocket-Events
|
### WebSocket-Events
|
||||||
|
|
||||||
| Event | Richtung | Beschreibung |
|
| Event | Richtung | Beschreibung |
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
var app = require('../app');
|
var app = require('../app');
|
||||||
var debug = require('debug')('scoreboard:server');
|
var debug = require('debug')('scoreboard:server');
|
||||||
var http = require('http');
|
var http = require('http');
|
||||||
|
var io = require('../controllers/socketio');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get port from environment and store in Express.
|
* Get port from environment and store in Express.
|
||||||
|
|
@ -21,6 +22,12 @@ app.set('port', port);
|
||||||
|
|
||||||
var server = http.createServer(app);
|
var server = http.createServer(app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach Socket.IO to the same server, so it shares the HTTP port.
|
||||||
|
*/
|
||||||
|
|
||||||
|
io.attach(server);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen on provided port, on all network interfaces.
|
* Listen on provided port, on all network interfaces.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,6 @@ let teamB = {
|
||||||
let sideswitch = false; // Attribute to switch sides after halftime
|
let sideswitch = false; // Attribute to switch sides after halftime
|
||||||
let referenceMirrored = false; // If sitting behind the scoreboard, switch sides for admin board
|
let referenceMirrored = false; // If sitting behind the scoreboard, switch sides for admin board
|
||||||
|
|
||||||
// Funktionsübersicht (Prototypen ohne Implementierung — nur zur Übersicht)
|
|
||||||
function setEnabled(status) {};
|
|
||||||
function configTeam(team, name, name2, isSpielgemeinschaft) {};
|
|
||||||
function setScore(team, score) {};
|
|
||||||
function alterScore(team, dir) {};
|
|
||||||
function clearScore() {};
|
|
||||||
function toggleSideswitch() {};
|
|
||||||
function getEnabled() {};
|
|
||||||
function getValues() {};
|
|
||||||
|
|
||||||
// Enable or disable the scoreboard
|
// Enable or disable the scoreboard
|
||||||
function setEnabled(status) {
|
function setEnabled(status) {
|
||||||
enabled = status;
|
enabled = status;
|
||||||
|
|
@ -52,6 +42,7 @@ function setScore(team, score) {
|
||||||
} else if(team == "teamB") {
|
} else if(team == "teamB") {
|
||||||
teamB.score = score;
|
teamB.score = score;
|
||||||
}
|
}
|
||||||
|
io.sockets.emit('score', print()); // Wie alterScore und clearScore den Monitor mitziehen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ändert den Score eines Teams um 1 — Richtung "inc" (erhöhen) oder "dec" (verringern)
|
// Ändert den Score eines Teams um 1 — Richtung "inc" (erhöhen) oder "dec" (verringern)
|
||||||
|
|
@ -61,7 +52,7 @@ function alterScore(team, dir) {
|
||||||
teamA.score++;
|
teamA.score++;
|
||||||
console.log("teamA inc");
|
console.log("teamA inc");
|
||||||
}
|
}
|
||||||
else if(dir == "dec") {
|
else if(dir == "dec" && teamA.score > 0) { // Kein negativer Spielstand
|
||||||
teamA.score--;
|
teamA.score--;
|
||||||
console.log("teamA dec");
|
console.log("teamA dec");
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +61,7 @@ function alterScore(team, dir) {
|
||||||
teamB.score++;
|
teamB.score++;
|
||||||
console.log("teamB inc");
|
console.log("teamB inc");
|
||||||
}
|
}
|
||||||
else if(dir == "dec") {
|
else if(dir == "dec" && teamB.score > 0) { // Kein negativer Spielstand
|
||||||
teamB.score--;
|
teamB.score--;
|
||||||
console.log("teamB dec");
|
console.log("teamB dec");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
// Erstellt den zentralen Socket.IO-Server auf Port 3001.
|
// Erstellt den zentralen Socket.IO-Server.
|
||||||
// Wird von timer.js, score.js und den Routen importiert,
|
// Wird von timer.js, score.js und den Routen importiert,
|
||||||
// um Events (Timer, Score, Refresh) an alle verbundenen Clients zu senden.
|
// um Events (Timer, Score, Refresh) an alle verbundenen Clients zu senden.
|
||||||
|
|
||||||
const { Server } = require('socket.io');
|
const { Server } = require('socket.io');
|
||||||
|
|
||||||
// CORS auf "*" gesetzt, da Admin und Monitor auf unterschiedlichen Ports laufen können
|
// Der Server wird ohne eigenen Port erzeugt und in bin/www per io.attach() an den
|
||||||
const io = new Server(3001, {
|
// HTTP-Server gehaengt. Dieses Modul wird beim Laden der Routen ausgewertet, also
|
||||||
cors: { origin: "*" }
|
// bevor der HTTP-Server existiert — daher die Trennung von Erzeugen und Anhaengen.
|
||||||
|
// Weil Socket.IO damit unter derselben Herkunft laeuft wie die Seite, entfaellt die
|
||||||
|
// CORS-Ausnahme.
|
||||||
|
const io = new Server();
|
||||||
|
|
||||||
|
// Verbindungs-Handling: der Client meldet sich beim Verbinden selbst mit dem
|
||||||
|
// Socket.IO-eigenen 'connect'-Event und holt sich den aktuellen Stand ueber die
|
||||||
|
// REST-Endpunkte. Serverseitig wird die Verbindung nur protokolliert.
|
||||||
|
io.on('connection', (socket) => {
|
||||||
|
console.log("A user connected");
|
||||||
|
|
||||||
|
socket.on('disconnect', (reason) => {
|
||||||
|
console.log("A user disconnected: ", reason);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = io
|
module.exports = io
|
||||||
|
|
@ -7,15 +7,6 @@ let durationLeft = moment.duration(7, 'minutes'); // durationLeft after the t
|
||||||
|
|
||||||
let isPaused = true; // Status of the timer
|
let isPaused = true; // Status of the timer
|
||||||
|
|
||||||
// Function prototypes
|
|
||||||
function start() {};
|
|
||||||
function pause() {};
|
|
||||||
function reset() {};
|
|
||||||
function end() {};
|
|
||||||
function print() {};
|
|
||||||
function incDec() {};
|
|
||||||
function getValues() {};
|
|
||||||
|
|
||||||
// Start timer not paused and not already finished
|
// Start timer not paused and not already finished
|
||||||
function start() {
|
function start() {
|
||||||
if(isPaused == true && durationLeft.asSeconds() != 0) { // Only allow start if timer ist currently paused and durationLeft is not 0 seconds
|
if(isPaused == true && durationLeft.asSeconds() != 0) { // Only allow start if timer ist currently paused and durationLeft is not 0 seconds
|
||||||
|
|
@ -45,7 +36,9 @@ function pause() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset timer to passed value, and send durationLeft to all clients
|
// Reset timer to passed value, and send durationLeft to all clients.
|
||||||
|
// Ein laufender Timer wird bewusst nicht angehalten, sondern zaehlt von der
|
||||||
|
// neuen Zeit weiter herunter.
|
||||||
function reset(newDuration) {
|
function reset(newDuration) {
|
||||||
console.log("Timer Zurückgesetzt");
|
console.log("Timer Zurückgesetzt");
|
||||||
duration = moment.duration(newDuration, 'seconds').clone(); // Set initial duration to received duration in seconds
|
duration = moment.duration(newDuration, 'seconds').clone(); // Set initial duration to received duration in seconds
|
||||||
|
|
@ -69,7 +62,8 @@ function print() {
|
||||||
|
|
||||||
// Increase or decrease the timer depending on the passed value in seconds
|
// Increase or decrease the timer depending on the passed value in seconds
|
||||||
function incDec(value) {
|
function incDec(value) {
|
||||||
if(Math.abs(value) >= durationLeft.asSeconds()) { // If abs from passed value is greater than seconds left on timer, end timer
|
if(value < 0 && Math.abs(value) >= durationLeft.asSeconds()) { // Nur beim Verringern: faellt die Restzeit auf 0 oder darunter, Timer beenden
|
||||||
|
durationLeft = moment.duration(0, 'seconds'); // Restzeit sauber auf 00:00, sonst bliebe der Timer trotz "ENDE" startbar
|
||||||
end();
|
end();
|
||||||
} else {
|
} else {
|
||||||
durationLeft.add(value, 'second');
|
durationLeft.add(value, 'second');
|
||||||
|
|
@ -87,6 +81,9 @@ function getValues() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// duration, durationLeft und isPaused werden bewusst nicht exportiert: Exporte
|
||||||
|
// werden beim Laden des Moduls einmal ausgewertet und wuerden dauerhaft die
|
||||||
|
// Startwerte liefern. Der aktuelle Stand kommt ueber getValues().
|
||||||
module.exports = {
|
module.exports = {
|
||||||
duration, durationLeft, isPaused, start, pause, reset, end, print, incDec, getValues
|
start, pause, reset, end, print, incDec, getValues
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
|
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument
|
||||||
const socket = io(wss); // Connect to socketio server
|
// verbindet sich dorthin zurueck.
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
// Websockets event handler
|
// Websockets event handler
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
||||||
socket.on('connected', (message) => { // Once client is connected to websocket
|
// 'connect' feuert bei jeder Verbindung, auch nach einem Reconnect. Da der Pi das
|
||||||
console.log("Connected");
|
// WLAN selbst aufspannt, sind kurze Abbrueche normal: danach werden die Werte neu
|
||||||
|
// geholt, damit das Panel nicht auf einem veralteten Stand stehen bleibt.
|
||||||
|
socket.on('connect', () => {
|
||||||
|
console.log("Verbunden");
|
||||||
|
initialUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('timerDurationLeft', (message) => {
|
socket.on('timerDurationLeft', (message) => {
|
||||||
|
|
@ -28,6 +33,22 @@ socket.on('score', (message) => {
|
||||||
// General functions
|
// General functions
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
||||||
|
// Holt JSON von einem Endpunkt. Liefert null statt zu werfen, wenn der Server nicht
|
||||||
|
// erreichbar ist oder mit einem Fehler antwortet — sonst bricht der Aufrufer still ab.
|
||||||
|
async function fetchJson(url, options) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
if(!response.ok) {
|
||||||
|
console.error("Anfrage fehlgeschlagen:", url, response.status);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Server nicht erreichbar:", url, err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initial update gets called whenever page has been loaded
|
// Initial update gets called whenever page has been loaded
|
||||||
async function initialUpdate() {
|
async function initialUpdate() {
|
||||||
etcGetValues(); // Request new values for etc stuff
|
etcGetValues(); // Request new values for etc stuff
|
||||||
|
|
@ -104,6 +125,19 @@ function updateScoreFrontend(values) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Erzeugt einen Dropdown-Eintrag, der den Teamnamen in das angegebene Feld uebernimmt.
|
||||||
|
// Der Name wird als Text gesetzt und per Closure an den Handler gegeben, statt ihn in
|
||||||
|
// einen onclick-String zu schreiben: Namen mit Apostroph zerlegten sonst den Handler.
|
||||||
|
function createTeamDropdownItem(field, team) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.className = "dropdown-item";
|
||||||
|
a.textContent = team;
|
||||||
|
a.addEventListener("click", () => scoreInsertChosenName(field, team));
|
||||||
|
li.appendChild(a);
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
// Update DOM for all db contents with the passed values
|
// Update DOM for all db contents with the passed values
|
||||||
function updateDbFrontend(values) {
|
function updateDbFrontend(values) {
|
||||||
console.log(values);
|
console.log(values);
|
||||||
|
|
@ -122,27 +156,17 @@ function updateDbFrontend(values) {
|
||||||
let selectDelete = document.getElementById("dbTeamToDelete"); // Select for team deletion from db
|
let selectDelete = document.getElementById("dbTeamToDelete"); // Select for team deletion from db
|
||||||
selectDelete.innerHTML = "" // Clears all existing options from select
|
selectDelete.innerHTML = "" // Clears all existing options from select
|
||||||
|
|
||||||
// <li><a class="dropdown-item" onclick="scoreInsertChosenName">Action</a></li>
|
for(const team of values.teams) {
|
||||||
|
|
||||||
for(team of values.teams) {
|
|
||||||
// Create custom Dropdowns with different function arguments for team configuration
|
// Create custom Dropdowns with different function arguments for team configuration
|
||||||
var li = document.createElement("li");
|
teamAnameDropdown.appendChild(createTeamDropdownItem("teamAname", team));
|
||||||
li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamAname','` + team +`')"> `+ team +`</a>`;
|
teamAname2Dropdown.appendChild(createTeamDropdownItem("teamAname2", team));
|
||||||
teamAnameDropdown.appendChild(li);
|
teamBnameDropdown.appendChild(createTeamDropdownItem("teamBname", team));
|
||||||
var li = document.createElement("li");
|
teamBname2Dropdown.appendChild(createTeamDropdownItem("teamBname2", team));
|
||||||
li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamAname2','` + team +`')"> `+ team +`</a>`;
|
|
||||||
teamAname2Dropdown.appendChild(li);
|
|
||||||
var li = document.createElement("li");
|
|
||||||
li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamBname','` + team +`')"> `+ team +`</a>`;
|
|
||||||
teamBnameDropdown.appendChild(li);
|
|
||||||
var li = document.createElement("li");
|
|
||||||
li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamBname2','` + team +`')"> `+ team +`</a>`;
|
|
||||||
teamBname2Dropdown.appendChild(li);
|
|
||||||
|
|
||||||
// Create select options for deletion in debugwindow
|
// Create select options for deletion in debugwindow
|
||||||
let opt = document.createElement("option");
|
let opt = document.createElement("option");
|
||||||
opt.value = team;
|
opt.value = team;
|
||||||
opt.innerHTML = team;
|
opt.textContent = team;
|
||||||
selectDelete.appendChild(opt);
|
selectDelete.appendChild(opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,16 +208,14 @@ async function killBrowser() {
|
||||||
|
|
||||||
// Request important values for etc stuff
|
// Request important values for etc stuff
|
||||||
async function etcGetValues() {
|
async function etcGetValues() {
|
||||||
const response = await fetch("/admin/etcGetValues"); // Call API Endpoint /admin/etcGetValues
|
const data = await fetchJson("/admin/etcGetValues"); // Call API Endpoint /admin/etcGetValues
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete
|
if(data) updateEtcFrontend(data); // Update admin frontend
|
||||||
updateEtcFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle index page to also show scoreboard
|
// Toggle index page to also show scoreboard
|
||||||
async function etcToggleSplashscreen() {
|
async function etcToggleSplashscreen() {
|
||||||
const response = await fetch("/admin/etcToggleSplashscreen");// Call API Endpoint /admin/etcToggleSplashscreen
|
const data = await fetchJson("/admin/etcToggleSplashscreen");// Call API Endpoint /admin/etcToggleSplashscreen
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateEtcFrontend(data); // Update admin frontend
|
||||||
updateEtcFrontend(data); // Update admin frontend
|
|
||||||
refreshMonitor(); // Refresh main monitor to display new frontend
|
refreshMonitor(); // Refresh main monitor to display new frontend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,9 +225,8 @@ async function etcToggleSplashscreen() {
|
||||||
|
|
||||||
// Request important timer values
|
// Request important timer values
|
||||||
async function timerGetValues() {
|
async function timerGetValues() {
|
||||||
const response = await fetch("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
|
const data = await fetchJson("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete
|
if(data) updateTimerFrontend(data); // Update admin frontend
|
||||||
updateTimerFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request to start the timer
|
// Request to start the timer
|
||||||
|
|
@ -235,24 +256,22 @@ async function timerPause() {
|
||||||
// Request to reset the timer with the specified time
|
// Request to reset the timer with the specified time
|
||||||
async function timerReset() {
|
async function timerReset() {
|
||||||
let newDuration = document.getElementById("timerSelectDuration").value;
|
let newDuration = document.getElementById("timerSelectDuration").value;
|
||||||
const response = await fetch("/admin/timerReset", { // Call API Endpoint /admin/timerStart with selected new duration in seconds
|
const data = await fetchJson("/admin/timerReset", { // Call API Endpoint /admin/timerStart with selected new duration in seconds
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({duration: newDuration})
|
body: JSON.stringify({duration: newDuration})
|
||||||
});
|
});
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateTimerFrontend(data); // Update admin frontend
|
||||||
updateTimerFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request to increase or decrease the timer depending on passed value in seconds
|
// Request to increase or decrease the timer depending on passed value in seconds
|
||||||
async function timerIncDec(value) {
|
async function timerIncDec(value) {
|
||||||
const response = await fetch("/admin/timerIncDec", { // Call API Endpoint /admin/timerIncDec with value in seconds to increase or decrease the timer
|
const data = await fetchJson("/admin/timerIncDec", { // Call API Endpoint /admin/timerIncDec with value in seconds to increase or decrease the timer
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({value: value})
|
body: JSON.stringify({value: value})
|
||||||
});
|
});
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateTimerFrontend(data); // Update admin frontend
|
||||||
updateTimerFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
@ -261,49 +280,43 @@ async function timerIncDec(value) {
|
||||||
|
|
||||||
// Request important values for score
|
// Request important values for score
|
||||||
async function scoreGetValues() {
|
async function scoreGetValues() {
|
||||||
const response = await fetch("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues
|
const data = await fetchJson("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete
|
if(data) updateScoreFrontend(data); // Update admin frontend
|
||||||
updateScoreFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle index page to also show scoreboard
|
// Toggle index page to also show scoreboard
|
||||||
async function scoreToggle() {
|
async function scoreToggle() {
|
||||||
const response = await fetch("/admin/scoreToggle"); // Call API Endpoint /admin/scoreToggle
|
const data = await fetchJson("/admin/scoreToggle"); // Call API Endpoint /admin/scoreToggle
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateScoreFrontend(data); // Update admin frontend
|
||||||
updateScoreFrontend(data); // Update admin frontend
|
|
||||||
refreshMonitor(); // Refresh main monitor to display new frontend
|
refreshMonitor(); // Refresh main monitor to display new frontend
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alter the score of passed team in the specified direction
|
// Alter the score of passed team in the specified direction
|
||||||
async function scoreAlterScore(team, dir) {
|
async function scoreAlterScore(team, dir) {
|
||||||
const response = await fetch("/admin/scoreAlterScore", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
const data = await fetchJson("/admin/scoreAlterScore", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({team: team, dir: dir})
|
body: JSON.stringify({team: team, dir: dir})
|
||||||
});
|
});
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateScoreFrontend(data); // Update admin frontend
|
||||||
updateScoreFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch sides
|
// Switch sides
|
||||||
async function scoreToggleSideswitch() {
|
async function scoreToggleSideswitch() {
|
||||||
const response = await fetch("/admin/scoreToggleSideswitch");// Call API Endpoint /admin/scoreSwitch
|
const data = await fetchJson("/admin/scoreToggleSideswitch");// Call API Endpoint /admin/scoreSwitch
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateScoreFrontend(data); // Update admin frontend
|
||||||
updateScoreFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch sides to mirror admin panel when sitting behind main monitor
|
// Switch sides to mirror admin panel when sitting behind main monitor
|
||||||
async function scoreToggleReferenceMirrored() {
|
async function scoreToggleReferenceMirrored() {
|
||||||
const response = await fetch("/admin/scoreToggleReferenceMirrored");// Call API Endpoint /admin/scoreSwitch
|
const data = await fetchJson("/admin/scoreToggleReferenceMirrored");// Call API Endpoint /admin/scoreSwitch
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateScoreFrontend(data); // Update admin frontend
|
||||||
updateScoreFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear score
|
// Clear score
|
||||||
async function scoreClearScore() {
|
async function scoreClearScore() {
|
||||||
const response = await fetch("/admin/scoreClearScore"); // Call API Endpoint /admin/scoreClearScore
|
const data = await fetchJson("/admin/scoreClearScore"); // Call API Endpoint /admin/scoreClearScore
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete
|
if(data) updateScoreFrontend(data); // Update admin frontend
|
||||||
updateScoreFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure Teams
|
// Configure Teams
|
||||||
|
|
@ -320,13 +333,12 @@ async function scoreConfigTeams() {
|
||||||
}
|
}
|
||||||
console.log(teamA, teamB)
|
console.log(teamA, teamB)
|
||||||
|
|
||||||
const response = await fetch("/admin/scoreConfigTeams", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
const data = await fetchJson("/admin/scoreConfigTeams", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({teamA: teamA, teamB: teamB})
|
body: JSON.stringify({teamA: teamA, teamB: teamB})
|
||||||
});
|
});
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateScoreFrontend(data); // Update admin frontend
|
||||||
updateScoreFrontend(data); // Update admin frontend
|
|
||||||
refreshMonitor();
|
refreshMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,31 +348,28 @@ async function scoreConfigTeams() {
|
||||||
|
|
||||||
// Request important values for db contents
|
// Request important values for db contents
|
||||||
async function dbGetValues() {
|
async function dbGetValues() {
|
||||||
const response = await fetch("/admin/dbGetValues"); // Call API Endpoint /admin/dbGetValues
|
const data = await fetchJson("/admin/dbGetValues"); // Call API Endpoint /admin/dbGetValues
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete
|
if(data) updateDbFrontend(data); // Update admin frontend
|
||||||
updateDbFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add team to DB
|
// Add team to DB
|
||||||
async function dbAddTeam() {
|
async function dbAddTeam() {
|
||||||
const response = await fetch("/admin/dbAddTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
const data = await fetchJson("/admin/dbAddTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({teamName: document.getElementById("dbTeamToAdd").value})
|
body: JSON.stringify({teamName: document.getElementById("dbTeamToAdd").value})
|
||||||
});
|
});
|
||||||
document.getElementById("dbTeamToAdd").value = ""; // Empty the text field after sending
|
document.getElementById("dbTeamToAdd").value = ""; // Empty the text field after sending
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateDbFrontend(data);
|
||||||
updateDbFrontend(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete team from DB
|
// Delete team from DB
|
||||||
async function dbDeleteTeam() {
|
async function dbDeleteTeam() {
|
||||||
let teamName = document.getElementById("dbTeamToDelete").value;
|
let teamName = document.getElementById("dbTeamToDelete").value;
|
||||||
const response = await fetch("/admin/dbDeleteTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
const data = await fetchJson("/admin/dbDeleteTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({teamName: teamName})
|
body: JSON.stringify({teamName: teamName})
|
||||||
});
|
});
|
||||||
const data = await response.json(); // Wait for asyncronous transfer to complete
|
if(data) updateDbFrontend(data);
|
||||||
updateDbFrontend(data);
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
|
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument
|
||||||
const socket = io(wss); // Connect to socketio server
|
// verbindet sich dorthin zurueck.
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
// Websockets event handler
|
// Websockets event handler
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
||||||
socket.on('connected', (message) => {
|
// 'connect' feuert bei jeder Verbindung, auch nach einem Reconnect. Da der Pi das WLAN
|
||||||
console.log("Connected");
|
// selbst aufspannt, sind kurze Abbrueche normal: danach werden Timer und Spielstand neu
|
||||||
|
// geholt, damit die Anzeige nicht auf einem veralteten Stand haengen bleibt.
|
||||||
|
socket.on('connect', () => {
|
||||||
|
console.log("Verbunden");
|
||||||
|
initialUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('timerDurationLeft', (message) => {
|
socket.on('timerDurationLeft', (message) => {
|
||||||
|
|
@ -26,6 +31,22 @@ socket.on('refresh', (message) => {
|
||||||
// General functions
|
// General functions
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
||||||
|
// Holt JSON von einem Endpunkt. Liefert null statt zu werfen, wenn der Server nicht
|
||||||
|
// erreichbar ist oder mit einem Fehler antwortet.
|
||||||
|
async function fetchJson(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if(!response.ok) {
|
||||||
|
console.error("Anfrage fehlgeschlagen:", url, response.status);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Server nicht erreichbar:", url, err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update DOM timer elements from passed values
|
// Update DOM timer elements from passed values
|
||||||
function updateTimerFrontend(values) {
|
function updateTimerFrontend(values) {
|
||||||
document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string
|
document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string
|
||||||
|
|
@ -42,7 +63,6 @@ async function initialUpdate() {
|
||||||
|
|
||||||
// Request important timer values
|
// Request important timer values
|
||||||
async function timerGetValues() {
|
async function timerGetValues() {
|
||||||
const response = await fetch("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
|
const data = await fetchJson("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete
|
if(data) updateTimerFrontend(data); // Update admin frontend
|
||||||
updateTimerFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
|
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument
|
||||||
const socket = io(wss); // Connect to socketio server
|
// verbindet sich dorthin zurueck.
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
// Websockets event handler
|
// Websockets event handler
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
||||||
socket.on('connected', (message) => {
|
// 'connect' feuert bei jeder Verbindung, auch nach einem Reconnect. Da der Pi das WLAN
|
||||||
console.log("Connected");
|
// selbst aufspannt, sind kurze Abbrueche normal: danach werden Timer und Spielstand neu
|
||||||
|
// geholt, damit die Anzeige nicht auf einem veralteten Stand haengen bleibt.
|
||||||
|
socket.on('connect', () => {
|
||||||
|
console.log("Verbunden");
|
||||||
|
initialUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('timerDurationLeft', (message) => {
|
socket.on('timerDurationLeft', (message) => {
|
||||||
|
|
@ -35,6 +40,22 @@ socket.on('scoreSideswitch', (message) => {
|
||||||
// General functions
|
// General functions
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
||||||
|
// Holt JSON von einem Endpunkt. Liefert null statt zu werfen, wenn der Server nicht
|
||||||
|
// erreichbar ist oder mit einem Fehler antwortet.
|
||||||
|
async function fetchJson(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if(!response.ok) {
|
||||||
|
console.error("Anfrage fehlgeschlagen:", url, response.status);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Server nicht erreichbar:", url, err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update DOM timer elements from passed values
|
// Update DOM timer elements from passed values
|
||||||
function updateTimerFrontend(values) {
|
function updateTimerFrontend(values) {
|
||||||
document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string
|
document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string
|
||||||
|
|
@ -87,9 +108,8 @@ async function initialUpdate() {
|
||||||
|
|
||||||
// Request important timer values
|
// Request important timer values
|
||||||
async function timerGetValues() {
|
async function timerGetValues() {
|
||||||
const response = await fetch("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
|
const data = await fetchJson("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete
|
if(data) updateTimerFrontend(data); // Update admin frontend
|
||||||
updateTimerFrontend(data); // Update admin frontend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
@ -98,8 +118,7 @@ async function timerGetValues() {
|
||||||
|
|
||||||
// Request important values for score
|
// Request important values for score
|
||||||
async function scoreGetValues() {
|
async function scoreGetValues() {
|
||||||
const response = await fetch("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues
|
const data = await fetchJson("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues
|
||||||
const data = await response.json(); // Wait for asynchronous transfer to complete and parse json (which got received by backend)
|
|
||||||
console.log(data); // Print received data
|
console.log(data); // Print received data
|
||||||
updateScoreFrontend(data); // Update admin frontend with received values for scoreboard
|
if(data) updateScoreFrontend(data); // Update admin frontend with received values for scoreboard
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
|
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument
|
||||||
const socket = io(wss); // Connect to socketio server
|
// verbindet sich dorthin zurueck.
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
// Websockets event handler
|
// Websockets event handler
|
||||||
// ######################################################################################################################################################
|
// ######################################################################################################################################################
|
||||||
|
|
||||||
socket.on('connected', (message) => {
|
|
||||||
console.log("Connected");
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('refresh', (message) => {
|
socket.on('refresh', (message) => {
|
||||||
document.location.reload() // Reload page on received 'refresh' messagen
|
document.location.reload() // Reload page on received 'refresh' messagen
|
||||||
});
|
});
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
let io = require('../controllers/socketio');
|
|
||||||
|
|
||||||
const score = require('../controllers/score');
|
const score = require('../controllers/score');
|
||||||
const etc = require('../controllers/etc');
|
const etc = require('../controllers/etc');
|
||||||
|
|
@ -18,15 +17,4 @@ 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
|
|
||||||
|
|
||||||
// Eingehende Nachrichten vom Client loggen (aktuell nur für Debugging)
|
|
||||||
socket.on('message', (message) => {
|
|
||||||
console.log(message)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,7 @@
|
||||||
<p>Achtung, eine Fehlbedienung kann unerwünschtes Verhalten hervorrufen.</p>
|
<p>Achtung, eine Fehlbedienung kann unerwünschtes Verhalten hervorrufen.</p>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" onclick="scoreToggleReferenceMirrored()" type="checkbox" role="switch" id="scoreSwitchReferenceMirrored">
|
<input class="form-check-input" onclick="scoreToggleReferenceMirrored()" type="checkbox" role="switch" id="scoreSwitchReferenceMirrored">
|
||||||
<label class="form-check-label" for="scoreSwitchEnable">Teamanzeige im Adminpanel spiegeln?</label>
|
<label class="form-check-label" for="scoreSwitchReferenceMirrored">Teamanzeige im Adminpanel spiegeln?</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" onclick="scoreToggle()" type="checkbox" role="switch" id="scoreSwitchEnable">
|
<input class="form-check-input" onclick="scoreToggle()" type="checkbox" role="switch" id="scoreSwitchEnable">
|
||||||
|
|
@ -306,7 +306,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" onclick="etcToggleSplashscreen()" type="checkbox" role="switch" id="etcSwitchEnable">
|
<input class="form-check-input" onclick="etcToggleSplashscreen()" type="checkbox" role="switch" id="etcSwitchEnable">
|
||||||
<label class="form-check-label" for="scoreSwitchEnable">SG-Arheilgen Splashscreen anzeigen?</label>
|
<label class="form-check-label" for="etcSwitchEnable">SG-Arheilgen Splashscreen anzeigen?</label>
|
||||||
</div>
|
</div>
|
||||||
<button class="mb-2 btn btn-lg btn-warning" onclick="refreshMonitor()"><i class="fa-solid fa-rotate"></i> Monitor neu laden</button> <br>
|
<button class="mb-2 btn btn-lg btn-warning" onclick="refreshMonitor()"><i class="fa-solid fa-rotate"></i> Monitor neu laden</button> <br>
|
||||||
<button class="mb-2 btn btn-lg btn-success" onclick="openBrowser()"><i class="fa-solid fa-rocket"></i> Browser öffnen</button> <br>
|
<button class="mb-2 btn btn-lg btn-success" onclick="openBrowser()"><i class="fa-solid fa-rocket"></i> Browser öffnen</button> <br>
|
||||||
|
|
@ -316,7 +316,7 @@
|
||||||
<p class="fw-bold">Team Datenbank:</p>
|
<p class="fw-bold">Team Datenbank:</p>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<span class="input-group-text"><i class="fa-solid fa-user-plus"></i></span>
|
<span class="input-group-text"><i class="fa-solid fa-user-plus"></i></span>
|
||||||
<input type="text" class="form-control" id="dbTeamToAdd" aria-describedby="teamBnameHelp">
|
<input type="text" class="form-control" id="dbTeamToAdd">
|
||||||
<button class="btn btn-outline-success" type="button" onclick="dbAddTeam()"><i class="fa-solid fa-plus"></i></button>
|
<button class="btn btn-outline-success" type="button" onclick="dbAddTeam()"><i class="fa-solid fa-plus"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue