Merge pull request #6 from Robert-112/upgrade_v1.2

Merge 1.2 to Master
This commit is contained in:
Robert Richter 2020-07-31 13:39:17 +02:00 committed by GitHub
commit 3415819c25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 19099 additions and 15385 deletions

13
.foreverignore Normal file
View File

@ -0,0 +1,13 @@
node_modules/*
public/*
views/*
misc/*
*.html
*.jpeg
*.png
*.mp3
*.wav
*.md
*.sqlite3
*-journal
sessions

10
.gitignore vendored
View File

@ -80,9 +80,8 @@ _old/
# db
*.sqlite3
# other
*.db
sessions
# ssl
.crt
@ -95,4 +94,11 @@ public/media/*
!public/media/marker-shadow.png
!public/media/bell_long.mp3
!public/media/bell_short.mp3
!public/media/bell_message.mp3
*.bak
*.save
# backup-files
misc/*
!misc/server.cert
!misc/server.key

View File

@ -1,7 +1,7 @@
# Wachalarm-IP-Web
![enter image description here](https://user-images.githubusercontent.com/19272095/54090568-cbbe6d00-4375-11e9-937e-ae2a6cd9ea7a.jpg)
# DEMO
[📺🔥 https://am7.info.tm/](https://am7.info.tm/)
[📺🔥 https://wachalarm.info.tm/](https://wachalarm.info.tm/)
Login-Daten:
- Benutzer: me
@ -9,17 +9,11 @@ Login-Daten:
Die Demo-Version zeigt frei erfundene Einsätze die jede Stunde neu alarmiert werden. Ohne Login wird der Wachalarm mit reduziertem Inhalt dargestellt (zur Wahrung des Datenschutzes).
## TO-DO
Nachfolgende Funktionen befinden sich noch in der Entwicklung:
- Dauer der Anzeige des Wachalarms sollte durch Benutzer festgelegt werden können (aktuell immer 10 Minuten)
- für jeden neuen Einsatz sollte eine UUID erstellt werden, die bei nachfolgen Alarmierungen verglichen wird (zur Vermeidung von doppelten Alarmierungen)
- Rückmeldefunktion für Einsatzkräfte (auf der Oberfläche des Wachalarms, ohne externe Schnittstelle)
- Mehr Informationen für angemeldete Benutzer ("Angemeldet als ...", Berechtigungen, etc.)
# Beschreibung
Ziel dieser Version des Wachalarms soll es sein, auf jedem Endgerät - egal ob Windows, Linux, Mac, PC oder Smartphone - Alarme anzuzeigen ohne zusätzliche Software zu installieren. Da es sich beim Wachalarm-IP-Web um eine reine Web-Server-Anwendung handelt, sollte dieser am besten durch eine Leitstelle betrieben werden, die Einsatzalarme direkt an das System übergibt. Der Zugriff erfolgt dann innerhalb eines geschützten Netzwerkes (VPN, LAN etc.) oder direkt über das Internet (sofern freigegeben und gewollt).
Der Web-Server empfängt Einsatzdaten über eine definierte [Schnittstelle
](#schnittstelle) aus dem Einsatzleitsystem (oder anderen Systemen) und übersendet diese dann an die jeweiligen Clients.
## Funktionsumfang
- Anzeige verschiedener Wachalarme für einzelne Wachen, Träger oder ganze Kreise
- Ausgabe synthetischer Sprachdurchsagen (Gong, Einsatzart, Stichwort, Ort, Ortsteile, beteiligte Einsatzmittel, Sondersignal)
@ -32,6 +26,11 @@ Der Web-Server empfängt Einsatzdaten über eine definierte [Schnittstelle
- Volle kompatibilität mit den gängigen Browsern (getestet in Chrome, Firefox, Safari, Microsoft Edge)
- Basierend auf modernsten Web-Technologien ([Node.js](https://nodejs.org/), [Express](https://expressjs.com/de/), [Socket.io](https://socket.io/), [Passport](http://www.passportjs.org/), [SQLite](https://www.sqlite.org/), [Bootstrap](https://getbootstrap.com/), [Leaflet](https://leafletjs.com/))
## offene Punkte
Neue Funktionen oder bekannte Probleme werden schrittweise hinterlegt.
Eine Aufzählung findet sich [hier](https://github.com/Robert-112/Wachalarm-IP-Web/TODO.md).
# Installation & Konfiguration
## Vorbereitung & Installation
1. Installation von [Node.js](https://nodejs.org/) (Version 10 LTS oder höher)

54
TODO.md Normal file
View File

@ -0,0 +1,54 @@
# Fehler und geplante Neuerungen
## 1. Priorität (Fehler)
- für jeden neuen Einsatz sollte eine UUID erstellt werden, die bei nachfolgen Alarmierungen verglichen wird (zur Vermeidung von doppelten Alarmierungen, falls über die Schnittstelle der Alarm nochmals gleichlautend übermittelt wird)
- Wachalarm-Besonderheiten-Text bei senden an bestehende Verbindung fehlerhaft (KW)
- Darstellung in Safari-Mobil fehlerhaft (generell Mobil, ggf. extra Darstellung)
- Buttons für Sounds werden fehlerhaft dargestellt
- Uhrzeit in der Datenbank (und im Log) ist auf UTC, sollte aber lokale Zeit sein
- Absturz bei unbekannter/falscher Wachennummer in Alarmmonitor-URL
- openstreetmap credit
- name, leitstelle, version immer mitgeben
- Eingaben auf validität prüfen
## 2. Priorität (notwendige Anpassungen)
- Mehr Informationen für angemeldete Benutzer ("Angemeldet als ...", Berechtigungen, etc.)
- Login verbessern:
- Login-Seite benötigt Fehlerrückmeldung (wie Nutzerverwaltung): falsches Kennwort, Nutzer nicht vorhanden etc.
- Login/Logout protokollieren
- fehlerhafte/doppelte Logins protokollieren
- prüfen ob es sinnvoll ist, bereits eingeloggte User nicht mehr zulassen (Session prüfen)
- bei fehlendem Login zur Login-Seite weiterleiten und nach dem Login die zuvor besuchte Seite anzeigen
- Information in Wachalarm-Bild ob alle Rechte, oder ob reduzierte Version
- ❗Benutzerrechte in Implizite (darf reduziert sehen) und Explizite (darf alles sehen) unterscheiden
- ❗/waip/0 umändern, so dass nur die Wachalarme angezeigt werden, für die man entsprechende rechte hat
- ❗/waip/einsatz-Id schaffen
- Seite mit aktiven Clients anpassen:
- nicht zwingend als Tabelle, sondern eher als .col mit Buttons um Aktionen an Clients zu senden
- einzelnen Client über Verwaltungsoberfläche neu laden lassen
- besserer Log-Status: Browserversion ermitteln, User ermitteln
- Uhrzeit am Anfang irgendwo platzieren (nicht immer oben links)
- Maus auf Alarmmonitor nach Zeit x ausblenden
- Datenbank nach bestimmter Zeit aufräumen
- Client-IP bei Reverse-Proxy richtig ermitteln
- eingehende JSON-Objekte auf plausibilität prüfen
## 3. Priorität (Neuerungen)
- Rückmeldefunktion für Einsatzkräfte
- Schnittstelle zu weiterem Wachalarm-Web-Server um Einsätze und Rückmeldungen untereinander auszutauschen
- Pakete aktualisieren
- textfit aktualisieren
## 4. Priorität (zu späterer Zeit)
- anpassen der Durchsage je Benutzer, durch eigene Ersetzung und Reihenfolge
- Ausnahmen festlegen können, wann keine Musik abgespielt wird
- Indivduelle Texte für Web-Anwendung hinterlegen können:
- "© Leitstelle Lausitz"
- Impressium
- Datenschutzerklärung
- Titel der Anwendung
- Versionsnummer

10547
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,28 +2,39 @@
"name": "waip-web",
"version": "0.1.2",
"description": "Web-Version des Wachalarm-IP",
"repository": {
"type": "git",
"url": "https://github.com/Robert-112/Wachalarm-IP-Web.git"
},
"license": "Creative Commons Attribution Share Alike 4.0 International",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@turf/turf": "^5.1.6",
"async": "^3.1.0",
"bcrypt": "^3.0.6",
"bcrypt": "^3.0.7",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"connect-sqlite3": "^0.9.11",
"cookie-parser": "^1.4.4",
"express": "^4.17.1",
"express-session": "^1.16.2",
"npm": "^6.10.2",
"passport": "^0.4.0",
"express-session": "^1.17.0",
"json2csv": "^5.0.1",
"nodemailer": "^6.4.10",
"npm": "^6.13.4",
"passport": "^0.4.1",
"passport-ip": "^0.1.2",
"passport-local": "^1.0.0",
"passport.socketio": "^3.7.0",
"pug": "^2.0.4",
"req-flash": "0.0.3",
"serve-favicon": "^2.5.0",
"socket.io": "^2.2.0",
"sqlite3": "^4.0.9"
"socket.io": "^2.3.0",
"socket.io-client": "^2.3.0",
"sqlite3": "^4.1.1",
"twit": "^2.2.11",
"uuid": "^8.1.0"
},
"devDependencies": {},
"engines": {

1067
public/css/bootstrap.css vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2
public/css/vis-timeline.css Executable file

File diff suppressed because one or more lines are too long

View File

@ -24,7 +24,7 @@ body {
}
.fullheight {
height: calc(100vh - 60px - 5rem);
height: calc(100vh - 60px - 5rem - 2rem);
}
/*** Anpassungen an Bootstrap ******************/
@ -61,10 +61,6 @@ audio {
height: 40% !important;
}
.h-70 {
height: 70% !important;
}
.h-45 {
height: 45% !important;
}
@ -73,10 +69,18 @@ audio {
height: 55% !important;
}
.h-60 {
height: 60% !important;
}
.h-65 {
height: 65% !important;
}
.h-70 {
height: 70% !important;
}
.h-80 {
height: 80% !important;
}
@ -161,7 +165,23 @@ audio {
}
}
/*** Texte des Bildschirmschoners dynamisch bewegen lassen **********/
/*** Texte des Bildschirmschoners dynamisch bewegen lassen ***/
.clock_y {
position:fixed;
}
.client_wache {
animation: x2 1200s linear infinite alternate;
}
@keyframes x2 {
100% {
transform: translatex(300%);
}
}
/*** Texte des Bildschirmschoners dynamisch bewegen lassen
.clock_x {
animation: x 1200s linear infinite alternate;
@ -183,12 +203,12 @@ audio {
}
}
.client_wache {
animation: x2 1200s linear infinite alternate;
******/
#em_alarmiert, #rmld_container {
font-size: 1.6vw;
}
@keyframes x2 {
100% {
transform: translatex(300%);
}
#headline {
height: 2rem;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

Binary file not shown.

546
public/js/client_dbrd.js Executable file
View File

@ -0,0 +1,546 @@
$(document).ready(function() {
// Sound nicht beim laden der Seite abspielen
var audio = document.getElementById('audio');
audio.src = ('/media/bell_message.mp3');
audio.volume = 0.0;
setTimeout(function () {
audio.pause();
audio.currentTime = 0;
audio.volume = 1.0;
}, 1000);
});
/* ########################### */
/* ######### LEAFLET ######### */
/* ########################### */
// Karte definieren
var map = L.map('map', {
zoomControl: false
}).setView([51.733005, 14.338048], 13);
// Layer der Karte
mapLink = L.tileLayer(
map_tile, {
maxZoom: 18,
attribution: map_attribution
}).addTo(map);
// Icon der Karte zuordnen
var redIcon = new L.Icon({
iconUrl: '/media/marker-icon-2x-red.png',
shadowUrl: '/media/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
// Icon setzen
var marker = L.marker(new L.LatLng(0, 0), {
icon: redIcon
}).addTo(map);
// GeoJSON vordefinieren
var geojson = L.geoJSON().addTo(map);
/* ########################### */
/* ####### Rückmeldung ####### */
/* ########################### */
var counter_rmld = [];
var counter_ID = 0;
function start_counter(zeitstempel, ablaufzeit) {
// Split timestamp into [ Y, M, D, h, m, s ]
var t1 = zeitstempel.split(/[- :]/),
t2 = ablaufzeit.split(/[- :]/);
var start = new Date(t1[0], t1[1] - 1, t1[2], t1[3], t1[4], t1[5]),
end = new Date(t2[0], t2[1] - 1, t2[2], t2[3], t2[4], t2[5]);
clearInterval(counter_ID);
counter_ID = setInterval(function () {
do_progressbar(start, end);
}, 1000);
};
function reset_rmld(p_uuid) {
var bar_uuid = 'bar-' + p_uuid;
$('#pg-ek').children().each(function (i) {
if (!$(this).hasClass(bar_uuid)) {
$(this).remove();
};
});
$('#pg-ma').children().each(function (i) {
if (!$(this).hasClass(bar_uuid)) {
$(this).remove();
};
});
$('#pg-fk').children().each(function (i) {
if (!$(this).hasClass(bar_uuid)) {
$(this).remove();
};
});
};
function add_resp_progressbar(p_uuid, p_id, p_type, p_agt, p_start, p_end) {
// Hintergrund der Progressbar festlegen
var bar_background = '';
var bar_border = '';
if (p_agt) {
bar_border = 'border border-warning';
};
switch (p_type) {
case 'ek':
bar_background = 'bg-success';
break;
case 'ma':
bar_background = 'bg-info';
break;
case 'fk':
bar_background = 'bg-light';
break;
default:
bar_background = '';
break;
};
var bar_uuid = 'bar-' + p_uuid;
// pruefen ob div mit id 'pg-'+p_id schon vorhanden ist
var pgbar = document.getElementById('pg-' + p_id);
if (!pgbar) {
$('#pg-' + p_type).append('<div class="progress mt-1 position-relative ' + bar_border + ' ' + bar_uuid + '" id="pg-' + p_id + '" style="height: 15px; font-size: 14px;"></div>');
$('#pg-' + p_id).append('<div id="pg-bar-' + p_id + '" class="progress-bar progress-bar-striped ' + bar_background + '" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>');
$('#pg-bar-' + p_id).append('<small id="pg-text-' + p_id + '" class="justify-content-center d-flex position-absolute w-100"></small>');
} else {
// TODO PG-Bar ändern falls neue/angepasste Rückmeldung
};
// Zeitschiene Anpassen
clearInterval(counter_rmld[p_id]);
counter_rmld[p_id] = 0;
counter_rmld[p_id] = setInterval(function () {
do_rmld_bar(p_id, p_start, p_end);
}, 1000);
};
function do_rmld_bar(p_id, start, end) {
//console.log(p_id);
today = new Date();
// restliche Zeit ermitteln
var current_progress = Math.round(100 / (start.getTime() - end.getTime()) * (start.getTime() - today.getTime()));
var diff = Math.abs(end - today);
var minutesDifference = Math.floor(diff / 1000 / 60);
diff -= minutesDifference * 1000 * 60;
var secondsDifference = Math.floor(diff / 1000);
if (secondsDifference <= 9) {
secondsDifference = '0' + secondsDifference;
};
var minutes = minutesDifference + ':' + secondsDifference;
// Progressbar anpassen
if (current_progress >= 100) {
$('#pg-bar-' + p_id)
.css('width', '100%')
.attr('aria-valuenow', 100)
.addClass('ion-md-checkmark-circle');
$('#pg-text-' + p_id).text('');
// FIXME Counter_Id not defined
clearInterval(counter_ID[p_id]);
} else {
$('#pg-bar-' + p_id)
.css('width', current_progress + '%')
.attr('aria-valuenow', current_progress);
$('#pg-text-' + p_id).text(minutes);
};
};
function recount_rmld(p_uuid) {
var bar_uuid = 'bar-' + p_uuid;
var agt_count = 0;
// Zähler auf 0 Setzen
$('#ek-counter').text(0);
$('#ma-counter').text(0);
$('#fk-counter').text(0);
$('#agt-counter').text(0);
// EK zählen
$('#pg-ek').children().each(function (i) {
if ($(this).hasClass(bar_uuid)) {
var tmp_count = parseInt($('#ek-counter').text());
$('#ek-counter').text(tmp_count + 1);
if ($(this).hasClass('border-warning')) {
agt_count++;
};
};
});
// MA zählen
$('#pg-ma').children().each(function (i) {
if ($(this).hasClass(bar_uuid)) {
var tmp_count = parseInt($('#ma-counter').text());
$('#ma-counter').text(tmp_count + 1);
if ($(this).hasClass('border-warning')) {
agt_count++;
};
};
});
// FK zählen
$('#pg-fk').children().each(function (i) {
if ($(this).hasClass(bar_uuid)) {
var tmp_count = parseInt($('#fk-counter').text());
$('#fk-counter').text(tmp_count + 1);
if ($(this).hasClass('border-warning')) {
agt_count++;
};
};
});
// AGT setzen
$('#agt-counter').text(agt_count);
// Rückmeldecontainer anzeigen/ausblenden
if ($('#ek-counter').text() == '0' && $('#ma-counter').text() == '0' && $('#fk-counter').text() == '0' && $('#agt-counter').text() == '0') {
$('#rmld_container').addClass('d-none');
} else {
$('#rmld_container').removeClass('d-none');
};
};
/* ########################### */
/* ####### Timeline ######## */
/* ########################### */
// DOM element where the Timeline will be attached
var container = document.getElementById('visualization');
var items = new vis.DataSet();
var groups = new vis.DataSet();
// Configuration for the Timeline
var customDate = new Date();
var alert_start = new Date(customDate.setMinutes(customDate.getMinutes() - 2));
var timeline_end = new Date(customDate.setMinutes(customDate.getMinutes() + 13));
var options = {
rollingMode: {
follow: true,
offset: 0.25
},
start: alert_start,
end: timeline_end
};
// Create a Timeline
var timeline = new vis.Timeline(container, items, options);
timeline.setGroups(groups);
/* ########################### */
/* ######## SOCKET.IO ######## */
/* ########################### */
// Websocket
var socket = io('/dbrd');
// Wachen-ID bei Connect an Server senden
socket.on('connect', function () {
socket.emit('dbrd', dbrd_uuid);
$('#waipModal').modal('hide');
// TODO: bei Reconnect des Clients durch Verbindungsabbruch, erneut Daten anfordern
});
socket.on('connect_error', function (err) {
$('#waipModalTitle').html('FEHLER');
$('#waipModalBody').html('Verbindung zum Server getrennt!');
$('#waipModal').modal('show');
});
// ID von Server und Client vergleichen, falls ungleich -> Seite neu laden
socket.on('io.version', function (server_id) {
if (client_id != server_id) {
$('#waipModal').modal('hide');
setTimeout(function () {
$('#waipModalTitle').html('ACHTUNG');
$('#waipModalBody').html('Neue Server-Version. Seite wird in 10 Sekunden neu geladen!');
$('#waipModal').modal('show');
setTimeout(function () {
location.reload();
}, 10000);
}, 1000);
};
});
// ggf. Fehler ausgeben
socket.on('io.error', function (data) {
console.log('Error:', data);
});
// Daten löschen, Uhr anzeigen
socket.on('io.deleted', function (data) {
console.log('del')
// Einsatz nicht mehr vorhanden
$('#waipModal').modal('hide');
setTimeout(function () {
$('#waipModalTitle').html('ACHTUNG');
$('#waipModalBody').html(`Der aufgerufene Einsatz wurde gel&ouml;scht und ist in diesem System nicht mehr verfügbar.<br>
Sie werden in einer Minute auf die Startseite zurückgeleitet.`);
$('#waipModal').modal('show');
setTimeout(function () {
window.location.href = window.location.origin;
}, 60000);
}, 1000);
});
// Einsatzdaten laden, Wachalarm anzeigen
socket.on('io.Einsatz', function (data) {
// DEBUG
console.log(data);
// Einsatz-ID speichern
waip_id = data.id;
// DBRD-ID und Zeit setzten
$('#dbrd_id').html(data.uuid);
$('#einsatz_datum').html(data.zeitstempel);
// Hintergrund der Einsatzart zunächst entfernen
$('#einsatz_art').removeClass(function (index, className) {
return (className.match(/(^|\s)bg-\S+/g) || []).join(' ');
});
// Icon der Einsatzart enfernen
$('#einsatz_stichwort').removeClass();
// Art und Stichwort festlegen hinterlegen
switch (data.einsatzart) {
case 'Brandeinsatz':
$('#einsatz_art').addClass('bg-danger');
$('#einsatz_stichwort').addClass('ion-md-flame');
$('#rueckmeldung').removeClass('d-none');
break;
case 'Hilfeleistungseinsatz':
$('#einsatz_art').addClass('bg-info');
$('#einsatz_stichwort').addClass('ion-md-construct');
$('#rueckmeldung').removeClass('d-none');
break;
case 'Rettungseinsatz':
$('#einsatz_art').addClass('bg-warning');
$('#einsatz_stichwort').addClass('ion-md-medkit');
break;
case 'Krankentransport':
$('#einsatz_art').addClass('bg-success');
$('#einsatz_stichwort').addClass('ion-md-medical');
break;
default:
$('#einsatz_art').addClass('bg-secondary');
$('#einsatz_stichwort').addClass('ion-md-information-circle');
};
$('#einsatz_stichwort').html(' ' + data.stichwort);
// Sondersignal setzen
$('#sondersignal').removeClass();
switch (data.sondersignal) {
case 1:
$('#sondersignal').addClass('ion-md-notifications');
break;
default:
$('#sondersignal').addClass('ion-md-notifications-off');
};
// Ortsdaten zusammenstellen und setzen
$('#einsatzort_list').empty();
if (data.objekt) {
$('#einsatzort_list').append('<li class="list-group-item">' + data.objekt+ '</li>');
};
if (data.ort) {
$('#einsatzort_list').append('<li class="list-group-item">' + data.ort+ '</li>');
};
if (data.ortsteil) {
$('#einsatzort_list').append('<li class="list-group-item">' + data.ortsteil+ '</li>');
};
if (data.strasse) {
$('#einsatzort_list').append('<li class="list-group-item">' + data.strasse+ '</li>');
};
if (data.besonderheiten) {
$('#einsatzort_list').append('<li class="list-group-item text-warning">' + data.besonderheiten+ '</li>');
};
// Alte Einsatzmittel loeschen
var table_em = document.getElementById('table_einsatzmittel');
table_em.getElementsByTagName('tbody')[0].innerHTML = '';
// Einsatzmittel-Tabelle
for (var i in data.einsatzmittel) {
var wache_vorhanden = false;
var wache_zeile = 0;
var wachen_idstr =data.einsatzmittel[i].wachenname.replace(/[^A-Z0-9]+/ig, '_');
for (var j = 0, row; row = table_em.rows[j]; j++) {
//console.log(row.cells[0].innerHTML);
if (row.cells[0].innerHTML == data.einsatzmittel[i].wachenname) {
wache_vorhanden = true;
wache_zeile = j;
};
};
if (!wache_vorhanden){
// Zeile fuer Wache anlegen, falls diese noch nicht hinterlegt
var tableRef = document.getElementById('table_einsatzmittel').getElementsByTagName('tbody')[0];
var newRow = tableRef.insertRow();
//var newCell = newRow.insertCell(0);
// Wachennamen hinterlegen
var new_th = document.createElement('th');
new_th.innerHTML = data.einsatzmittel[i].wachenname;
//var newText = document.createTextNode(data.einsatzmittel[i].wachenname);
//newCell.outerHTML = "<th></th>";
//newCell.appendChild(newText);
newRow.appendChild(new_th);
//Flex-Element fuer Einsatzmittel der Wache erzeugen
var flex_div_wa = document.createElement('div');
flex_div_wa.className = 'd-flex flex-wrap justify-content-between align-items-center';
flex_div_wa.id = wachen_idstr;
//Flexelement zur Tabelle hinzuefuegen
var new_td = document.createElement('td');
new_td.appendChild(flex_div_wa);
newRow.appendChild(new_td);
//table_em.rows[wache_zeile].cells[1].appendChild(flex_div_wa);
};
//Flex-Element fuer Einsatzmittel erzeugen
var flex_div_em = document.createElement('div');
flex_div_em.className = 'flex-fill rounded bg-secondary p-2 m-1';
//Justify-Rahmen feuer Einsatzmittel erzeugen
var justify_div = document.createElement('div');
justify_div.className = 'd-flex justify-content-between';
//Einsatzmittel-Div erzeugen
var em_div = document.createElement('div');
em_div.className = 'pr-2';
em_div.innerHTML = data.einsatzmittel[i].einsatzmittel;
//Status-Div erzeugen
var status_div = document.createElement('div');
switch (data.einsatzmittel[i].status) {
case '1':
status_div.className = 'p-2 badge badge-info';
break;
case '2':
status_div.className = 'p-2 badge badge-success';
break;
case '3':
status_div.className = 'p-2 badge badge-warning';
break;
case '4':
status_div.className = 'p-2 badge badge-danger';
break;
default:
status_div.className = 'p-2 badge badge-dark';
break;
}
status_div.innerHTML = data.einsatzmittel[i].status;
//Erzeugte Div zusammensetzen
flex_div_em.appendChild(justify_div);
justify_div.appendChild(em_div);
justify_div.appendChild(status_div);
// Einsatzmittel hinzuefuegen
document.getElementById(wachen_idstr).appendChild(flex_div_em);
};
// Karte leeren
map.removeLayer(marker);
map.removeLayer(geojson);
// Karte setzen
if (data.wgs84_x && data.wgs84_y) {
marker = L.marker(new L.LatLng(data.wgs84_x, data.wgs84_y), {
icon: redIcon
}).addTo(map);
map.setView(new L.LatLng(data.wgs84_x, data.wgs84_y), 15);
} else {
geojson = L.geoJSON(JSON.parse(data.wgs84_area));
geojson.addTo(map);
map.fitBounds(geojson.getBounds());
map.setZoom(13);
};
// Marker in Timeline setzen
var markerText = 'Alarmierung';
var alarm_zeit = 'alarm_zeit';
timeline.addCustomTime(
data.zeitstempel,
alarm_zeit
);
timeline.customTimes[timeline.customTimes.length - 1].hammer.off("panstart panmove panend");
timeline.setCustomTimeMarker(markerText, alarm_zeit, false);
// TODO Ablaufzeit setzen
});
socket.on('io.new_rmld', function (data) {
// DEBUG
console.log(data);
// FIXME Änderung des Funktions-Typ berücksichtigen
// Neue Rueckmeldung hinterlegen
data.forEach(function (arrayItem) {
// HTML festlegen
var item_type = '';
var item_content = '';
var item_classname = '';
// wenn Einsatzkraft dann:
if (arrayItem.einsatzkraft) {
item_content = 'Einsatzkraft';
item_classname = 'ek';
item_type = 'ek';
};
// wenn Maschinist dann:
if (arrayItem.maschinist) {
item_content = 'Maschinist';
item_classname = 'ma';
item_type = 'ma';
};
// wenn Fuehrungskraft dann:
if (arrayItem.fuehrungskraft) {
item_content = 'Führungskraft';
item_classname = 'fk'
item_type = 'fk';
};
// wenn AGT
var item_agt = arrayItem.agt;
if (arrayItem.agt){
item_content = item_content + (' (AGT)');
item_classname = item_classname + ('-agt');
};
// Variablen für Anzeige vorbereiten
var pg_waip_uuid = arrayItem.waip_uuid;
var pg_rmld_uuid = arrayItem.rmld_uuid;
var pg_start = new Date(arrayItem.set_time);
var pg_end = new Date(arrayItem.arrival_time);
var timeline_item = {
id: arrayItem.rmld_uuid,
group: arrayItem.wache_id,
className: item_classname,
start: new Date(arrayItem.set_time),
end: new Date(arrayItem.arrival_time),
content: item_content
};
// Progressbar hinterlegen
add_resp_progressbar(pg_waip_uuid, pg_rmld_uuid, item_type, item_agt, pg_start, pg_end);
// in Timeline hinterlegen
items.update(timeline_item);
groups.update({ id: arrayItem.wache_id, content: arrayItem.wache_name });
// Anzahl der Rückmeldung zählen
recount_rmld(pg_waip_uuid);
});
var audio = document.getElementById('audio');
audio.src = ('/media/bell_message.mp3');
// Audio-Blockade des Browsers erkennen
var playPromise = document.querySelector('audio').play();
if (playPromise !== undefined) {
playPromise.then(function () {
audio.play();
}).catch(function (error) {
console.log('Notification playback failed');
});
};
});

108
public/js/client_rmld.js Executable file
View File

@ -0,0 +1,108 @@
/* ########################### */
/* ######### LEAFLET ######### */
/* ########################### */
// Karte definieren
var map = L.map('map', {
zoomControl: false
}).setView([51.733005, 14.338048], 13);
// Layer der Karte
// TODO: internen Kartendienst setzten
mapLink = L.tileLayer(
map_tile, {
maxZoom: 18,
attribution: map_attribution
}).addTo(map);
// Icon der Karte zuordnen
var redIcon = new L.Icon({
iconUrl: '/media/marker-icon-2x-red.png',
shadowUrl: '/media/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
// Icon setzen
var marker = L.marker(new L.LatLng(0, 0), {
icon: redIcon
}).addTo(map);
// Karte setzen
map.removeLayer(marker);
if (einsatzdaten_obj.wgs84_x && einsatzdaten_obj.wgs84_y) {
marker = L.marker(new L.LatLng(einsatzdaten_obj.wgs84_x, einsatzdaten_obj.wgs84_y), {
icon: redIcon
}).addTo(map);
map.setView(new L.LatLng(einsatzdaten_obj.wgs84_x, einsatzdaten_obj.wgs84_y), 13);
} else {
var geojson = L.geoJSON(JSON.parse(einsatzdaten_obj.wgs84_area)).addTo(map);
map.fitBounds(geojson.getBounds());
map.setZoom(13);
};
/* ########################### */
/* ####### Funktionen ######## */
/* ########################### */
// Split timestamp into [ Y, M, D, h, m, s ]
var t1 = einsatzdaten_obj.zeitstempel.split(/[- :]/);
var d = new Date(t1[0], t1[1] - 1, t1[2], t1[3], t1[4], t1[5]);
// Zeitwerte
var curr_day = d.getDay();
var curr_date = d.getDate();
var curr_month_id = d.getMonth();
curr_month_id = curr_month_id + 1;
var curr_year = d.getFullYear();
var curr_hour = d.getHours();
var curr_min = d.getMinutes();
var curr_sek = d.getSeconds();
// Tag und Monat Anpassen
if ((String(curr_date)).length == 1)
curr_date = '0' + curr_date;
if ((String(curr_month_id)).length == 1)
curr_month_id = '0' + curr_month_id;
// Uhrzeit anpassen
if (curr_min <= 9) {
curr_min = '0' + curr_min;
};
if (curr_hour <= 9) {
curr_hour = '0' + curr_hour;
};
if (curr_sek <= 9) {
curr_sek = '0' + curr_sek;
};
var curr_month = d.getMonth();
var curr_year = d.getFullYear();
// Datum und Uhrzeit setzen
$("#einsatz_datum").text(curr_date + '.' + curr_month_id + '.' + curr_year);
$("#einsatz_uhrzeit").text(curr_hour + ':' + curr_min + ':' + curr_sek);
/* ########################### */
/* ####### Rückmeldung ####### */
/* ########################### */
$('#rueckmeldung').each(function(index) {
$(this).on("click", function(){
$('#responseModal').modal('show');
});
});
/* ########################### */
/* ######## SOCKET.IO ######## */
/* ########################### */
// Websocket
//var socket = io.connect();
// Wachen-ID bei Connect an Server senden
/*socket.on('connect', function() {
socket.emit('dbrd_uuid', wachen_id);
$('#waipModal').modal('hide');
});*/

803
public/js/client_waip.js Normal file
View File

@ -0,0 +1,803 @@
// TODO: Remote-Reload per Socket
// TODO: Modal bei Server-Verbindung, und Modal bei Reload
$(document).ready(function () {
set_clock();
});
$(window).on('resize', function () {
resize_text();
// Position neu setzen
var newq = makeNewPosition();
$('.clock_y').css('top', newq[0]);
$('.clock_y').css('left', newq[1]);
// langsam verschieben
animateDiv();
});
/* ############################ */
/* ######### BUTTONS ########## */
/* ############################ */
var waipAudio = document.getElementById('audio');
waipAudio.addEventListener('ended', function () {
console.log('ended');
var tmp_element;
// Pause-Symbol in Play-Symbol
tmp_element = document.querySelector('.ion-md-pause');
if (tmp_element.classList.contains('ion-md-pause')) {
tmp_element.classList.remove('ion-md-pause');
tmp_element.classList.add('ion-md-play-circle');
};
// Lautsprecher-Symbol in Leise-Symbol
tmp_element = document.querySelector('.ion-md-volume-high');
if (tmp_element.classList.contains('ion-md-volume-high')) {
tmp_element.classList.remove('ion-md-volume-high');
tmp_element.classList.add('ion-md-volume-off');
};
// Button Hintergrund entfernen, falls vorhanden
tmp_element = document.querySelector('#volume');
if (tmp_element.classList.contains('btn-danger')) {
tmp_element.classList.remove('btn-danger');
};
});
waipAudio.addEventListener('play', function () {
var tmp_element;
// Pause-Symbol in Play-Symbol
tmp_element = document.querySelector('.ion-md-play-circle');
if (tmp_element.classList.contains('ion-md-play-circle')) {
tmp_element.classList.remove('ion-md-play-circle');
tmp_element.classList.add('ion-md-pause');
};
// Lautsprecher-Symbol in Leise-Symbol
tmp_element = document.querySelector('.ion-md-volume-off');
if (tmp_element.classList.contains('ion-md-volume-off')) {
tmp_element.classList.remove('ion-md-volume-off');
tmp_element.classList.add('ion-md-volume-high');
};
// Button Hintergrund entfernen, falls vorhanden
tmp_element = document.querySelector('#volume');
if (tmp_element.classList.contains('btn-danger')) {
tmp_element.classList.remove('btn-danger');
};
});
$('#replay').on('click', function (event) {
document.getElementById('audio').play();
});
/* ############################ */
/* ####### TEXT-RESIZE ######## */
/* ############################ */
// Größen dynamisch anpassen, Hintergrundfarbe ggf. anpassen
function resize_text() {
// Uhr-Text nur Anpassen wenn sichtbar
if ($('#waipclock').is(':visible')) {
textFit(document.getElementsByClassName('tf_clock'), {
minFontSize: 3,
maxFontSize: 500
});
$('body').css('background-color', '#000');
};
// Tableau nur Anpassen wenn sichtbar
if ($('#waiptableau').is(':visible')) {
textFit(document.getElementsByClassName('tf_singleline'), {
minFontSize: 1,
maxFontSize: 500
});
textFit(document.getElementsByClassName('tf_multiline'), {
detectMultiLine: false
});
// Karte neu setzen
map.invalidateSize();
$('body').css('background-color', '#222');
};
};
// Text nach bestimmter Laenge, in Abhaengigkeit von Zeichen, umbrechen
function break_text_15(text) {
var new_text;
new_text = text.replace(/.{15}(\s+|\-+)+/g, '$&@')
new_text = new_text.split(/@/);
new_text = new_text.join('<br>');
//console.log(new_text);
return new_text;
};
function break_text_35(text) {
var new_text;
new_text = text.replace(/.{50}\S*\s+/g, '$&@').split(/\s+@/);
new_text = new_text.join('<br>');
//console.log(new_text);
return new_text;
};
/* ############################ */
/* ####### INAKTIVITAET ####### */
/* ############################ */
var timeoutID;
// Inactivitaet auswerten
function setup_inactivcheck() {
this.addEventListener('mousemove', resetActivTimer, false);
this.addEventListener('mousedown', resetActivTimer, false);
this.addEventListener('keypress', resetActivTimer, false);
this.addEventListener('DOMMouseScroll', resetActivTimer, false);
this.addEventListener('mousewheel', resetActivTimer, {
passive: true
}, false);
this.addEventListener('touchmove', resetActivTimer, false);
this.addEventListener('MSPointerMove', resetActivTimer, false);
start_inactivtimer();
};
setup_inactivcheck();
// warte xxxx Millisekunden um dann do_on_Inactive zu starten
function start_inactivtimer() {
clearTimeout(timeoutID);
timeoutID = window.setTimeout(do_on_Inactive, 3000);
};
// bei Inaktivitaet Header/Footer ausblenden
function do_on_Inactive() {
// do something
$('.navbar').fadeOut('slow');
$('.footer').fadeOut('slow');
$('.fullheight').css({
height: 'calc(100vh - 4rem)',
cursor: 'none'
});
$('body').css({
paddingTop: '1rem',
margin: 0
});
resize_text();
};
// bei Activitaet Header/Footer einblenden
function do_on_Active() {
start_inactivtimer();
// do something
$('.navbar').fadeIn('slow');
$('.footer').fadeIn('slow');
$('body').css({
marginBottom: '60px',
paddingTop: '5rem',
paddingBottom: '0'
});
$('.fullheight').css({
height: 'calc(100vh - 60px - 7rem)',
cursor: 'auto'
});
resize_text();
};
// bei Event (Aktiviaet) alles zuruecksetzen
function resetActivTimer(e) {
do_on_Active();
};
/* ############################ */
/* ####### Progressbar ####### */
/* ############################ */
var counter_ID = 0;
function start_counter(zeitstempel, ablaufzeit) {
// Split timestamp into [ Y, M, D, h, m, s ]
var t1 = zeitstempel.split(/[- :]/),
t2 = ablaufzeit.split(/[- :]/);
var start = new Date(t1[0], t1[1] - 1, t1[2], t1[3], t1[4], t1[5]),
end = new Date(t2[0], t2[1] - 1, t2[2], t2[3], t2[4], t2[5]);
clearInterval(counter_ID);
counter_ID = setInterval(function () {
do_progressbar(start, end);
}, 1000);
};
function do_progressbar(start, end) {
today = new Date();
// restliche Zeit ermitteln
var current_progress = Math.round(100 / (end.getTime() - start.getTime()) * (end.getTime() - today.getTime()));
var diff = Math.abs(end - today);
var minutesDifference = Math.floor(diff / 1000 / 60);
diff -= minutesDifference * 1000 * 60;
var secondsDifference = Math.floor(diff / 1000);
if (secondsDifference <= 9) {
secondsDifference = '0' + secondsDifference;
};
var minutes = minutesDifference + ':' + secondsDifference;
// Progressbar anpassen
$('#hilfsfrist')
.css('width', current_progress + '%')
.attr('aria-valuenow', current_progress)
.text(minutes + ' min');
};
/* ########################### */
/* ######### LEAFLET ######### */
/* ########################### */
// Karte definieren
var map = L.map('map', {
zoomControl: false
}).setView([51.733005, 14.338048], 13);
// Layer der Karte
mapLink = L.tileLayer(
map_tile, {
maxZoom: 18,
attribution: map_attribution
}).addTo(map);
// Icon der Karte zuordnen
var redIcon = new L.Icon({
iconUrl: '/media/marker-icon-2x-red.png',
shadowUrl: '/media/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
// Icon setzen
var marker = L.marker(new L.LatLng(0, 0), {
icon: redIcon
}).addTo(map);
// GeoJSON vordefinieren
var geojson = L.geoJSON().addTo(map);
/* ########################### */
/* ######## SOCKET.IO ######## */
/* ########################### */
// Websocket
var socket = io('/waip');
// Wachen-ID bei Connect an Server senden
socket.on('connect', function () {
socket.emit('WAIP', wachen_id);
$('#waipModal').modal('hide');
// TODO: bei Reconnect des Clients durch Verbindungsabbruch, erneut Daten anfordern
});
socket.on('connect_error', function (err) {
$('#waipModalTitle').html('FEHLER');
$('#waipModalBody').html('Verbindung zum Server getrennt!');
$('#waipModal').modal('show');
});
// ID von Server und Client vergleichen, falls ungleich -> Seite neu laden
socket.on('io.version', function (server_id) {
if (client_id != server_id) {
$('#waipModal').modal('hide');
setTimeout(function () {
$('#waipModalTitle').html('ACHTUNG');
$('#waipModalBody').html('Neue Server-Version. Seite wird in 10 Sekunden neu geladen!');
$('#waipModal').modal('show');
setTimeout(function () {
location.reload();
}, 10000);
}, 1000);
};
});
// ggf. Fehler ausgeben
socket.on('io.error', function (data) {
console.log('Error:', data);
});
// Sounds stoppen
socket.on('io.stopaudio', function (data) {
tmp_audio = document.getElementById('audio');
tmp_audio.pause();
tmp_audio.currentTime = 0;
});
// Sounds abspielen
socket.on('io.playtts', function (data) {
var audio = document.getElementById('audio');
audio.src = (data);
console.log($('#audio'));
// Audio-Blockade des Browsers erkennen
var playPromise = document.querySelector('audio').play();
// In browsers that dont yet support this functionality,
// playPromise wont be defined.
if (playPromise !== undefined) {
playPromise.then(function () {
// Automatic playback started!
audio.play();
//$('.ion-md-volume-high').toggleClass('ion-md-pause');
}).catch(function (error) {
console.log('Automatic playback failed');
// Automatic playback failed.
// Show a UI element to let the user manually start playback.
var tmp_element;
tmp_element = document.querySelector('#volume');
if (!tmp_element.classList.contains('btn-danger')) {
tmp_element.classList.add('btn-danger');
};
tmp_element = document.querySelector('.ion-md-volume-high');
if (tmp_element.classList.contains('ion-md-volume-high')) {
tmp_element.classList.remove('ion-md-volume-high');
tmp_element.classList.add('ion-md-volume-off');
};
});
};
});
// Daten löschen, Uhr anzeigen
socket.on('io.standby', function (data) {
// Einsatz-ID auf 0 setzen
waip_id = null;
// TODO: Wenn vorhanden, hier #hilfsfrist zurücksetzen
$('#einsatz_art').removeClass(function (index, className) {
return (className.match(/(^|\s)bg-\S+/g) || []).join(' ');
});
$('#einsatz_stichwort').removeClass();
$('#einsatz_stichwort').html('');
$('#sondersignal').removeClass();
$('#ortsdaten').html('');
$('#besonderheiten').html('');
$('#em_alarmiert').empty();
$('#em_weitere').html('');
reset_rmld();
recount_rmld();
map.setView(new L.LatLng(0, 0), 14);
// Tareset_responsebleau ausblenden
$('#waiptableau').addClass('d-none');
$('#waipclock').removeClass('d-none');
// Text anpassen
resize_text();
// Position neu setzen
setTimeout(function () {
// Position neu setzen
var newq = makeNewPosition();
$('.clock_y').css('top', newq[0]);
$('.clock_y').css('left', newq[1]);
// langsam verschieben
animateDiv();
}, 1000);
});
// Einsatzdaten laden, Wachalarm anzeigen
socket.on('io.new_waip', function (data) {
// DEBUG
console.log(data);
// Einsatz-ID speichern
waip_id = data.id;
// Alarmzeitsetzen:
$('#date-time').html(data.zeitstempel);
// Hintergrund der Einsatzart zunächst entfernen
$('#einsatz_art').removeClass(function (index, className) {
return (className.match(/(^|\s)bg-\S+/g) || []).join(' ');
});
// Icon der Einsatzart enfernen
$('#einsatz_stichwort').removeClass();
// Art und Stichwort festlegen hinterlegen
switch (data.einsatzart) {
case 'Brandeinsatz':
$('#einsatz_art').addClass('bg-danger');
$('#einsatz_stichwort').addClass('ion-md-flame');
$('#rueckmeldung').removeClass('d-none');
break;
case 'Hilfeleistungseinsatz':
$('#einsatz_art').addClass('bg-info');
$('#einsatz_stichwort').addClass('ion-md-construct');
$('#rueckmeldung').removeClass('d-none');
break;
case 'Rettungseinsatz':
$('#einsatz_art').addClass('bg-warning');
$('#einsatz_stichwort').addClass('ion-md-medkit');
break;
case 'Krankentransport':
$('#einsatz_art').addClass('bg-success');
$('#einsatz_stichwort').addClass('ion-md-medical');
break;
default:
$('#einsatz_art').addClass('bg-secondary');
$('#einsatz_stichwort').addClass('ion-md-information-circle');
};
$('#einsatz_stichwort').html(' ' + data.stichwort);
// Sondersignal setzen
$('#sondersignal').removeClass();
switch (data.sondersignal) {
case 1:
$('#sondersignal').addClass('ion-md-notifications');
break;
default:
$('#sondersignal').addClass('ion-md-notifications-off');
};
// Ortsdaten zusammenstellen und setzen
var small_ortsdaten;
small_ortsdaten = '';
if (data.objekt) {
small_ortsdaten = small_ortsdaten + break_text_15(data.objekt) + '<br>';
};
if (data.ort) {
small_ortsdaten = small_ortsdaten + break_text_15(data.ort) + '<br>';
};
if (data.ortsteil) {
small_ortsdaten = small_ortsdaten + break_text_15(data.ortsteil) + '<br>';
};
if (data.strasse) {
small_ortsdaten = small_ortsdaten + break_text_15(data.strasse) + '<br>';
};
if (small_ortsdaten.substr(small_ortsdaten.length - 4) == '<br>') {
small_ortsdaten = small_ortsdaten.slice(0, -4);
};
$('#ortsdaten').html(small_ortsdaten);
// Besonderheiten setzen
$('#besonderheiten').html(break_text_35(data.besonderheiten));
// alarmierte Einsatzmittel setzen
$('#em_alarmiert').empty();
var data_em_alarmiert = JSON.parse(data.em_alarmiert);
for (var i in data_em_alarmiert) {
var tmp = data_em_alarmiert[i].name.replace(/[^a-z0-9\s]/gi, '').replace(/[_\s]/g, '-');
$('#em_alarmiert').append('<div id="cn_' + tmp + '" class="rounded bg-secondary d-flex justify-content-between p-2 m-1"></div>');
$('#cn_' + tmp).append('<div class="pr-2">' + data_em_alarmiert[i].name + '</div>');
};
// weitere alarmierte Einsatzmittel setzen
$('#em_weitere').html('');
try {
var data_em_weitere = JSON.parse(data.em_weitere);
if (data_em_weitere.length > 0) {
var tmp_weitere;
for (var i in data_em_weitere) {
if (tmp_weitere) {
tmp_weitere = tmp_weitere + ', ' + data_em_weitere[i].name;
} else {
tmp_weitere = data_em_weitere[i].name;
};
};
$('#em_weitere').html(tmp_weitere);
};
} catch (e) {
//console.log(e); // error in the above string (in this case, yes)!
};
// Karte leeren
map.removeLayer(marker);
map.removeLayer(geojson);
// Karte setzen
if (data.wgs84_x && data.wgs84_y) {
marker = L.marker(new L.LatLng(data.wgs84_x, data.wgs84_y), {
icon: redIcon
}).addTo(map);
map.setView(new L.LatLng(data.wgs84_x, data.wgs84_y), 15);
} else {
geojson = L.geoJSON(JSON.parse(data.wgs84_area));
geojson.addTo(map);
map.fitBounds(geojson.getBounds());
map.setZoom(13);
};
// Ablaufzeit setzen
start_counter(data.zeitstempel, data.ablaufzeit);
// alte Rückmeldung entfernen
reset_rmld(data.uuid);
recount_rmld(data.uuid);
// TODO: Einzeige vergrößern wenn Felder nicht angezeigt werden
// Uhr ausblenden
$('#waipclock').addClass('d-none');
$('#waiptableau').removeClass('d-none');
// Text anpassen
resize_text();
});
socket.on('io.new_rmld', function (data) {
// DEBUG
console.log(data);
// FIXME Änderung des Funktions-Typ berücksichtigen
// Neue Rueckmeldung hinterlegen
data.forEach(function (arrayItem) {
// HTML festlegen
var item_type = '';
// wenn Einsatzkraft dann:
if (arrayItem.einsatzkraft) {
item_type = 'ek';
};
// wenn Maschinist dann:
if (arrayItem.maschinist) {
item_type = 'ma';
};
// wenn Fuehrungskraft dann:
if (arrayItem.fuehrungskraft) {
item_type = 'fk';
};
// wenn AGT
var item_agt = arrayItem.agt;
// Variablen für Anzeige vorbereiten
var pg_waip_uuid = arrayItem.waip_uuid;
console.log(arrayItem.waip_uuid);
console.log(pg_waip_uuid);
var pg_rmld_uuid = arrayItem.rmld_uuid;
var pg_start = new Date(arrayItem.set_time);
var pg_end = new Date(arrayItem.arrival_time);
// Progressbar hinterlegen
add_resp_progressbar(pg_waip_uuid, pg_rmld_uuid, item_type, item_agt, pg_start, pg_end);
// Anzahl der Rückmeldung zählen
recount_rmld(pg_waip_uuid);
});
// Text anpassen
resize_text();
// Bing abspielen
var audio = document.getElementById('audio');
audio.src = ('/media/bell_message.mp3');
// Audio-Blockade des Browsers erkennen
var playPromise = document.querySelector('audio').play();
if (playPromise !== undefined) {
playPromise.then(function () {
audio.play();
}).catch(function (error) {
console.log('Notification playback failed');
});
};
});
/* ########################### */
/* ####### Rückmeldung ####### */
/* ########################### */
var counter_rmld = [];
function reset_rmld(p_uuid) {
var bar_uuid = 'bar-' + p_uuid;
$('#pg-ek').children().each(function (i) {
if (!$(this).hasClass(bar_uuid)) {
$(this).remove();
};
});
$('#pg-ma').children().each(function (i) {
if (!$(this).hasClass(bar_uuid)) {
$(this).remove();
};
});
$('#pg-fk').children().each(function (i) {
if (!$(this).hasClass(bar_uuid)) {
$(this).remove();
};
});
/*$('#pg-ek').empty();
$('#pg-ma').empty();
$('#pg-fk').empty();
$('#ek-counter').text(0);
$('#ma-counter').text(0);
$('#fk-counter').text(0);
$('#agt-counter').text(0);*/
};
function add_resp_progressbar(p_uuid, p_id, p_type, p_agt, p_start, p_end) {
// Hintergrund der Progressbar festlegen
var bar_background = '';
var bar_border = '';
if (p_agt) {
bar_border = 'border border-warning';
};
switch (p_type) {
case 'ek':
bar_background = 'bg-success';
break;
case 'ma':
bar_background = 'bg-info';
break;
case 'fk':
bar_background = 'bg-light';
break;
default:
bar_background = '';
break;
};
var bar_uuid = 'bar-' + p_uuid;
// pruefen ob div mit id 'pg-'+p_id schon vorhanden ist
var pgbar = document.getElementById('pg-' + p_id);
if (!pgbar) {
$('#pg-' + p_type).append('<div class="progress mt-1 position-relative ' + bar_border + ' ' + bar_uuid + '" id="pg-' + p_id + '" style="height: 15px; font-size: 14px;"></div>');
$('#pg-' + p_id).append('<div id="pg-bar-' + p_id + '" class="progress-bar progress-bar-striped ' + bar_background + '" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>');
$('#pg-bar-' + p_id).append('<small id="pg-text-' + p_id + '" class="justify-content-center d-flex position-absolute w-100"></small>');
} else {
// TODO PG-Bar ändern falls neue/angepasste Rückmeldung
};
// Zeitschiene Anpassen
clearInterval(counter_rmld[p_id]);
counter_rmld[p_id] = 0;
counter_rmld[p_id] = setInterval(function () {
do_rmld_bar(p_id, p_start, p_end);
}, 1000);
};
function do_rmld_bar(p_id, start, end) {
//console.log(p_id);
today = new Date();
// restliche Zeit ermitteln
var current_progress = Math.round(100 / (start.getTime() - end.getTime()) * (start.getTime() - today.getTime()));
var diff = Math.abs(end - today);
var minutesDifference = Math.floor(diff / 1000 / 60);
diff -= minutesDifference * 1000 * 60;
var secondsDifference = Math.floor(diff / 1000);
if (secondsDifference <= 9) {
secondsDifference = '0' + secondsDifference;
};
var minutes = minutesDifference + ':' + secondsDifference;
// Progressbar anpassen
if (current_progress >= 100) {
$('#pg-bar-' + p_id)
.css('width', '100%')
.attr('aria-valuenow', 100)
.addClass('ion-md-checkmark-circle');
$('#pg-text-' + p_id).text('');
clearInterval(counter_ID[p_id]);
} else {
$('#pg-bar-' + p_id)
.css('width', current_progress + '%')
.attr('aria-valuenow', current_progress);
$('#pg-text-' + p_id).text(minutes);
};
};
function recount_rmld(p_uuid) {
var bar_uuid = 'bar-' + p_uuid;
var agt_count = 0;
// Zähler auf 0 Setzen
$('#ek-counter').text(0);
$('#ma-counter').text(0);
$('#fk-counter').text(0);
$('#agt-counter').text(0);
// EK zählen
$('#pg-ek').children().each(function (i) {
if ($(this).hasClass(bar_uuid)) {
var tmp_count = parseInt($('#ek-counter').text());
$('#ek-counter').text(tmp_count + 1);
if ($(this).hasClass('border-warning')) {
agt_count++;
};
};
});
// MA zählen
$('#pg-ma').children().each(function (i) {
if ($(this).hasClass(bar_uuid)) {
var tmp_count = parseInt($('#ma-counter').text());
$('#ma-counter').text(tmp_count + 1);
if ($(this).hasClass('border-warning')) {
agt_count++;
};
};
});
// FK zählen
$('#pg-fk').children().each(function (i) {
if ($(this).hasClass(bar_uuid)) {
var tmp_count = parseInt($('#fk-counter').text());
$('#fk-counter').text(tmp_count + 1);
if ($(this).hasClass('border-warning')) {
agt_count++;
};
};
});
// AGT setzen
$('#agt-counter').text(agt_count);
// Rückmeldecontainer anzeigen/ausblenden
if ($('#ek-counter').text() == '0' && $('#ma-counter').text() == '0' && $('#fk-counter').text() == '0' && $('#agt-counter').text() == '0') {
$('#rmld_container').addClass('d-none');
} else {
$('#rmld_container').removeClass('d-none');
};
};
/* ########################### */
/* ####### SCREENSAVER ####### */
/* ########################### */
// Uhrzeit und Datum für Bildschirmschoner
function set_clock() {
// TODO Sekunden anzeigen
// Wochentage
var d_names = new Array('Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag');
// Monate
var m_names = new Array('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember');
// Aktuelle Zeit
var d = new Date();
var curr_day = d.getDay();
var curr_date = d.getDate();
var curr_month_id = d.getMonth();
curr_month_id = curr_month_id + 1;
var curr_year = d.getFullYear();
var curr_hour = d.getHours();
var curr_min = d.getMinutes();
var curr_sek = d.getSeconds();
// Tag und Monat Anpassen
if ((String(curr_date)).length == 1)
curr_date = '0' + curr_date;
if ((String(curr_month_id)).length == 1)
curr_month_id = '0' + curr_month_id;
// Uhrzeit und Minute anpassen
if (curr_min <= 9) {
curr_min = '0' + curr_min;
};
if (curr_hour <= 9) {
curr_hour = '0' + curr_hour;
};
if (curr_sek <= 9) {
curr_sek = '0' + curr_sek;
};
var curr_month = d.getMonth();
var curr_year = d.getFullYear();
var element_time = curr_hour + ':' + curr_min;
var element_day = d_names[curr_day] + ', ' + curr_date + '. ' + m_names[curr_month];
var element_date_time = curr_date + '.' + curr_month_id + '.' + curr_year + ' - ' + element_time + ':' + curr_sek;
// Easter-Egg :-)
if (element_time.substr(0, 5) == '13:37') {
element_time = '1337';
};
// nur erneuern wenn sich Zeit geändert hat
if ($('#time').text() !== element_time) {
// Uhrzeit anzeigen
$('#time').html(element_time);
// Datum (Text) anzeigen
$('#day').html(element_day);
// Textgröße neu setzen
resize_text();
};
};
// Uhrzeit jede Sekunden anpassen
setInterval(set_clock, 1000);
// Uhrzeit verschieben
$(document).ready(function () {
setTimeout(function () {
// Position neu setzen
var newq = makeNewPosition();
$('.clock_y').css('top', newq[0]);
$('.clock_y').css('left', newq[1]);
// langsam verschieben
animateDiv();
}, 1000);
});
// neue Random-Position fuer Uhrzeit ermitteln
function makeNewPosition() {
// Get viewport dimensions
var h = $('.fullheight').height() - $('.clock_y').height();
var w = $('.fullheight').width() - $('.clock_y').width();
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh, nw];
};
// Verschieben animieren
function animateDiv() {
var newq = makeNewPosition();
var oldq = $('.clock_y').offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
$('.clock_y').animate({
top: newq[0],
left: newq[1]
}, speed, function () {
animateDiv();
});
};
// Verschiebe-Geschwindigkeit berechnen
function calcSpeed(prev, next) {
var x = Math.abs(prev[1] - next[1]);
var y = Math.abs(prev[0] - next[0]);
var greatest = x > y ? x : y;
var speedModifier = 0.001;
var speed = Math.ceil(greatest / speedModifier);
return speed;
};

60
public/js/vis-timeline.min.js vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -1,507 +0,0 @@
// TODO: Remote-Reload per Socket
// TODO: Client-Server-Version abgleichen
// TODO: Modal bei Chrome, dass Audio erst bei interaktion aktiv
$(document).ready(function() {
set_clock();
});
$(window).on("resize", function() {
resize_text();
});
$('#replay').on('click', function(event) {
audio.play();
});
/* ############################ */
/* ######### BUTTONS ########## */
/* ############################ */
var waipAudio = document.getElementById('audio');
waipAudio.addEventListener('ended', function(){
$('.ion-md-pause').toggleClass("ion-md-play-circle");
});
waipAudio.addEventListener("play", function () {
$('.ion-md-play-circle').toggleClass("ion-md-pause");
});
/* ############################ */
/* ####### TEXT-RESIZE ######## */
/* ############################ */
// Textgröße dynamisch anpassen, Hintergrundfarbe ggf. anpassen
function resize_text() {
// Uhr-Text nur Anpassen wenn sichtbar
if ($('#waipclock').is(':visible')) {
textFit(document.getElementsByClassName('tf_clock'), {
minFontSize: 3,
maxFontSize: 500
});
$("body").css("background-color", "#000");
};
// Tableau-Text nur Anpassen wenn sichtbar
if ($('#waiptableau').is(':visible')) {
textFit(document.getElementsByClassName('tf_singleline'), {
minFontSize: 1,
maxFontSize: 500
});
textFit(document.getElementsByClassName('tf_multiline'), {
detectMultiLine: false
});
textFit(document.getElementsByClassName('tf_test'), {
widthOnly: true,
alignHoriz: false,
alignVert: false,
alignVertWithFlexbox: false
});
map.invalidateSize();
$("body").css("background-color", "#222");
};
};
// Text nach bestimmter Laenge, in Abhaengigkeit von Zeichen, umbrechen
function break_text_15(text){
var new_text;
new_text = text.replace(/.{15}(\s+|\-+)+/g, "$&@")
new_text = new_text.split(/@/);
new_text= new_text.join("<br>");
console.log(new_text);
return new_text;
};
function break_text_35(text){
var new_text;
new_text = text.replace(/.{50}\S*\s+/g, "$&@").split(/\s+@/);
new_text= new_text.join("<br>");
console.log(new_text);
return new_text;
};
/* ############################ */
/* ####### INAKTIVITAET ####### */
/* ############################ */
var timeoutID;
// Inactivitaet auswerten
function setup_inactivcheck() {
this.addEventListener("mousemove", resetActivTimer, false);
this.addEventListener("mousedown", resetActivTimer, false);
this.addEventListener("keypress", resetActivTimer, false);
this.addEventListener("DOMMouseScroll", resetActivTimer, false);
this.addEventListener("mousewheel", resetActivTimer, {
passive: true
}, false);
this.addEventListener("touchmove", resetActivTimer, false);
this.addEventListener("MSPointerMove", resetActivTimer, false);
start_inactivtimer();
};
setup_inactivcheck();
// warte xxxx Millisekunden um dann do_on_Inactive zu starten
function start_inactivtimer() {
clearTimeout(timeoutID);
timeoutID = window.setTimeout(do_on_Inactive, 3000);
};
// bei Inaktivitaet Header/Footer ausblenden
function do_on_Inactive() {
// do something
$(".navbar").fadeOut("slow");
$(".footer").fadeOut("slow");
$(".fullheight").css({
height: 'calc(100vh - 2rem)'
});
$("body").css({
paddingTop: "1rem",
margin: 0
});
resize_text();
};
// bei Activitaet Header/Footer einblenden
function do_on_Active() {
start_inactivtimer();
// do something
$(".navbar").fadeIn('slow');
$(".footer").fadeIn('slow');
$("body").css({
marginBottom: '60px',
paddingTop: '5rem',
paddingBottom: '0'
});
$(".fullheight").css({
height: 'calc(100vh - 60px - 5rem)'
});
resize_text();
};
// bei Event (Aktiviaet) alles zuruecksetzen
function resetActivTimer(e) {
do_on_Active();
};
/* ############################ */
/* ####### Progressbar ####### */
/* ############################ */
var counter_ID = 0;
function start_counter(zeitstempel, ablaufzeit) {
// Split timestamp into [ Y, M, D, h, m, s ]
var t1 = zeitstempel.split(/[- :]/),
t2 = ablaufzeit.split(/[- :]/);
var start = new Date(t1[0], t1[1] - 1, t1[2], t1[3], t1[4], t1[5]),
end = new Date(t2[0], t2[1] - 1, t2[2], t2[3], t2[4], t2[5]);
clearInterval(counter_ID);
counter_ID = setInterval(function() {
do_progressbar(start, end);
}, 1000);
};
function do_progressbar(start, end) {
today = new Date();
// restliche Zeit ermitteln
var current_progress = Math.round(100 / (end.getTime() - start.getTime()) * (end.getTime() - today.getTime()));
var diff = Math.abs(end - today);
var minutesDifference = Math.floor(diff / 1000 / 60);
diff -= minutesDifference * 1000 * 60;
var secondsDifference = Math.floor(diff / 1000);
if (secondsDifference <= 9) {
secondsDifference = '0' + secondsDifference;
};
var minutes = minutesDifference + ':' + secondsDifference;
// Progressbar anpassen
$("#hilfsfrist")
.css("width", current_progress + "%")
.attr("aria-valuenow", current_progress)
.text(minutes + " min");
};
/* ########################### */
/* ######### LEAFLET ######### */
/* ########################### */
// Karte definieren
var map = L.map('map', {
zoomControl: false
}).setView([51.733005, 14.338048], 13);
// Layer der Karte
mapLink = L.tileLayer(
map_tile, {
maxZoom: 18
}).addTo(map);
// Icon der Karte zuordnen
var redIcon = new L.Icon({
iconUrl: '/media/marker-icon-2x-red.png',
shadowUrl: '/media/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
// Icon setzen
var marker = L.marker(new L.LatLng(0, 0), {
icon: redIcon
}).addTo(map);
/* ########################### */
/* ######## SOCKET.IO ######## */
/* ########################### */
// Websocket
var socket = io.connect();
// Wachen-ID bei Connect an Server senden
socket.on('connect', function() {
socket.emit('wachen_id', wachen_id);
$('#waipModal').modal('hide');
});
socket.on('connect_error', function(err) {
$('#waipModalTitle').html('FEHLER');
$('#waipModalBody').html('Verbindung zum Server getrennt!');
$('#waipModal').modal('show');
});
// ID von Server und Client vergleichen, falls ungleich -> Seite neu laden
socket.on('io.version', function(server_id) {
// TODO: socket.emit(lade client xxx neu)
if (client_id != server_id) {
$('#waipModalTitle').html('ACHTUNG');
$('#waipModalBody').html('Neue Server-Version. Seite wird in 10 Sekunden neu geladen!');
if ($('#waipModal').hasClass('in')) {
$('#waipModal').modal('hide');
};
$('#waipModal').modal('show');
setTimeout(function() {
location.reload();
}, 10000);
};
});
// ggf. Fehler ausgeben
socket.on('io.error', function(data) {
console.log('Error:', data);
});
// Sounds stoppen
socket.on('io.stopaudio', function(data) {
var audio = document.getElementById('audio');
audio.pause;
});
// Sounds abspielen
socket.on('io.playtts', function(data) {
var audio = document.getElementById('audio');
audio.src = (data);
// Audio-Blockade des Browsers erkennen
var promise = document.querySelector('audio').play();
if (promise !== undefined) {
promise.then(function(_) {
audio.play();
}).catch(function(error) {
//$('#waipModalTitle').html('Audio-Fehler');
//$('#waipModalBody').html('Die automatische Audio-Wiedergabe wird durch Ihren Browser blockiert! Fehlermeldung: ' + error.message);
//$('#waipModal').modal('show');
$('#volume').addClass("btn-danger");
$('.ion-md-volume-high').toggleClass("ion-md-volume-off");
});
};
});
// Daten löschen, Uhr anzeigen
socket.on('io.standby', function(data) {
// Einsatz-ID auf 0 setzen
waip_id = null;
// TODO: Wenn vorhanden, hier #hilfsfrist zurücksetzen
$('#einsatz_art').removeClass(function(index, className) {
return (className.match(/(^|\s)bg-\S+/g) || []).join(' ');
});
$('#einsatz_stichwort').removeClass();
$('#einsatz_stichwort').html('');
$('#sondersignal').removeClass();
$('#ortsdaten').html('');
$('#besonderheiten').html('');
$('#em_alarmiert').empty();
$('#em_weitere').html('');
map.setView(new L.LatLng(0, 0), 14);
// Tableau ausblenden
$("#waiptableau").addClass("d-none");
$("#waipclock").removeClass("d-none");
// Text anpassen
resize_text();
});
// Einsatzdaten laden, Wachalarm anzeigen
socket.on('io.neuerEinsatz', function(data) {
// DEBUG
//console.log(data);
// Einsatz-ID speichern
waip_id = data.id;
// Hintergrund der Einsatzart zunächst entfernen
$('#einsatz_art').removeClass(function(index, className) {
return (className.match(/(^|\s)bg-\S+/g) || []).join(' ');
});
// Icon der Einsatzart enfernen
$('#einsatz_stichwort').removeClass();
// Rückmeldung ausblenden
$('#rueckmeldung').addClass("d-none");
// Art und Stichwort festlegen hinterlegen
switch (data.einsatzart) {
case 'Brandeinsatz':
$('#einsatz_art').addClass("bg-danger");
$('#einsatz_stichwort').addClass("ion-md-flame");
$('#rueckmeldung').removeClass("d-none");
break;
case 'Hilfeleistungseinsatz':
$('#einsatz_art').addClass("bg-info");
$('#einsatz_stichwort').addClass("ion-md-construct");
$('#rueckmeldung').removeClass("d-none");
break;
case 'Rettungseinsatz':
$('#einsatz_art').addClass("bg-warning");
$('#einsatz_stichwort').addClass("ion-md-medkit");
break;
case 'Krankentransport':
$('#einsatz_art').addClass("bg-success");
$('#einsatz_stichwort').addClass("ion-md-medical");
break;
default:
$('#einsatz_art').addClass("bg-secondary");
$('#einsatz_stichwort').addClass("ion-md-information-circle");
};
$('#einsatz_stichwort').html(' ' + data.stichwort);
// Sondersignal setzen
$('#sondersignal').removeClass();
switch (data.sondersignal) {
case 1:
$('#sondersignal').addClass('ion-md-notifications');
break;
default:
$('#sondersignal').addClass('ion-md-notifications-off');
};
// Ortsdaten zusammenstellen und setzen
var small_ortsdaten;
small_ortsdaten = '';
if (data.objekt) {
small_ortsdaten = small_ortsdaten + break_text_15(data.objekt) + '<br>';
};
if (data.ort) {
small_ortsdaten = small_ortsdaten + break_text_15(data.ort) + '<br>';
};
if (data.ortsteil) {
small_ortsdaten = small_ortsdaten + break_text_15(data.ortsteil) + '<br>';
};
if (data.strasse) {
small_ortsdaten = small_ortsdaten + break_text_15(data.strasse) + '<br>';
};
if (small_ortsdaten.substr(small_ortsdaten.length - 4) == '<br>') {
small_ortsdaten = small_ortsdaten.slice(0, -4);
};
$('#ortsdaten').html(small_ortsdaten);
// Besonderheiten setzen
$('#besonderheiten').html(break_text_35(data.besonderheiten));
// alarmierte Einsatzmittel setzen
$('#em_alarmiert').empty();
var data_em_alarmiert = JSON.parse(data.em_alarmiert);
for (var i in data_em_alarmiert) {
$('#em_alarmiert').append('<li class="list-group-item d-flex justify-content-between align-items-center">' + data_em_alarmiert[i].name + '</li>');
};
// weitere alarmierte Einsatzmittel setzen
$('#em_weitere').html('');
var data_em_weitere = JSON.parse(data.em_weitere);
if (data_em_weitere == null) {
// TODO: Einzeige vergrößern (-.h-5) wenn keine weiteren Einsatzmittel beteiligt
} else {
var tmp;
for (var i in data_em_weitere) {
if (tmp) {
tmp = tmp + ', ' + data_em_weitere[i].name;
} else {
tmp = data_em_weitere[i].name;
};
};
$('#em_weitere').html(tmp);
};
// Karte setzen
map.removeLayer(marker);
marker = L.marker(new L.LatLng(data.wgs84_x, data.wgs84_y), {
icon: redIcon
}).addTo(map);
map.setView(new L.LatLng(data.wgs84_x, data.wgs84_y), 15);
// Hilfsfrist setzen
start_counter(data.zeitstempel, data.ablaufzeit);
// Uhr ausblenden
$("#waipclock").addClass("d-none");
$("#waiptableau").removeClass("d-none");
// Text anpassen
resize_text();
});
/* ########################### */
/* ####### SCREENSAVER ####### */
/* ########################### */
// Uhrzeit und Datum für Bildschirmschoner
function set_clock() {
// Wochentage
var d_names = new Array('Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag');
// Monate
var m_names = new Array('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember');
// Aktuelle Zeit
var d = new Date();
var curr_day = d.getDay();
var curr_date = d.getDate();
var curr_month_id = d.getMonth();
curr_month_id = curr_month_id + 1;
var curr_year = d.getFullYear();
var curr_hour = d.getHours();
var curr_min = d.getMinutes();
// Tag und Monat Anpassen
if ((String(curr_date)).length == 1)
curr_date = '0' + curr_date;
if ((String(curr_month_id)).length == 1)
curr_month_id = '0' + curr_month_id;
// Uhrzeit und Minute anpassen
if (curr_min <= 9) {
curr_min = '0' + curr_min;
};
if (curr_hour <= 9) {
curr_hour = '0' + curr_hour;
};
var curr_month = d.getMonth();
var curr_year = d.getFullYear();
var element_time = curr_hour + ':' + curr_min;
var element_day = d_names[curr_day] + ', ' + curr_date + '. ' + m_names[curr_month];
var element_date_time = curr_date + '.' + curr_month_id + '.' + curr_year + ' - ' + element_time;
// nur erneuern wenn sich Zeit geändert hat
if (document.getElementById('time').textContent !== element_time) {
// Uhrzeit anzeigen
document.getElementById('time').innerHTML = element_time;
// Datum (Text) anzeigen
document.getElementById('day').innerHTML = element_day;
// Datum anzeigen, sofern sichtbar
document.getElementById('date-time').innerHTML = element_date_time;
// Textgröße neu setzen
resize_text();
};
};
// Uhrzeit jede Sekunden anpassen
setInterval(set_clock, 1000);
/* ########################### */
/* ####### Rückmeldung ####### */
/* ########################### */
$('#rueckmeldung').each(function(index) {
$(this).on("click", function(){
$('#responseModal').modal('show');
});
});
$('#send_response').on('click', function() {
// Rückmeldung senden
socket.emit(
'response',
waip_id,
$('#radios_res_ek').prop('checked'),
$('#radios_res_ma').prop('checked'),
$('#radios_res_fk').prop('checked'),
$('#cb_res_agt').prop('checked')
);
});
socket.on('io.response', function(data) {
// Rückmeldungen hinterlegen
$('#rueckmeldung').empty();
//{einsatzkraft: "0", maschinist: "0", fuehrungskraft: "0", atemschutz: "0"}
for (var i in data) {
var item_class = 'list-group-item flex-fill list-group-item-action tf_singleline';
switch (data[i]) {
case '0':
item_class = item_class + ' text-danger';
break;
case '1':
item_class = item_class + ' text-warning';
break;
default:
item_class = item_class + ' text-success';
};
$('#rueckmeldung').append('<a class="' + item_class + '">' + data[i] + ' ' + i + '</a>');
};
resize_text();
});

BIN
public/media/bell_message.mp3 Executable file

Binary file not shown.

View File

@ -1,24 +1,42 @@
// Module laden
var fs = require('fs');
var express = require('express');
var app = express();
var http = require('http') //.Server(app);
var https = require('https'); //.Server(app);
var webserver = https.createServer({
key: fs.readFileSync('./server/server.key', 'utf8'),
cert: fs.readFileSync('./server/server.cert', 'utf8')
const fs = require('fs');
const express = require('express');
const app = express();
const http = require('http');
const https = require('https');
const webserver = https.createServer({
key: fs.readFileSync('./misc/server.key', 'utf8'),
cert: fs.readFileSync('./misc/server.cert', 'utf8')
}, app);
var io = require('socket.io').listen(webserver);
var async = require('async');
var path = require('path');
var favicon = require('serve-favicon');
var bodyParser = require('body-parser');
var bcrypt = require('bcrypt');
var passport = require('passport');
const io = require('socket.io').listen(webserver);
const io_api = require('socket.io-client');
const async = require('async');
const path = require('path');
const favicon = require('serve-favicon');
const bodyParser = require('body-parser');
const bcrypt = require('bcrypt');
const passport = require('passport');
const { v4: uuidv4 } = require('uuid');
// Basis-Konfiguration laden und generische App-UUID erzeugen
var app_cfg = require('./server/app_cfg.js');
app_cfg.global.app_id = uuidv4();
app_cfg.public.version = 'Version 1.2.1';
// Remote-Api aktivieren
var remote_api;
if (app_cfg.endpoint.enabled) {
remote_api = io_api.connect(app_cfg.endpoint.host, {
reconnect: true
});
};
// Express-Einstellungen
app.set('views', path.join(__dirname, 'views'));
app.locals.basedir = app.get('views');
app.set('view engine', 'pug');
if (!app_cfg.global.development) {
app.set('view cache', true);
};
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
@ -27,21 +45,24 @@ app.use(bodyParser.urlencoded({
}));
// Scripte einbinden
var app_cfg = require('./server/app_cfg.js');
var sql_cfg = require('./server/sql_cfg')(fs, bcrypt, app_cfg);
var sql = require('./server/sql_qry')(sql_cfg, async, app_cfg)
var waip_io = require('./server/waip_io')(io, sql, async, app_cfg);
var udp = require('./server/udp')(app_cfg, waip_io, sql);
var sql = require('./server/sql_qry')(sql_cfg, app_cfg);
var brk = require('./server/broker')(app_cfg, sql, uuidv4);
var waip = require('./server/waip')(io, sql, fs, brk, async, app_cfg);
var saver = require('./server/saver')(app_cfg, sql, waip, uuidv4, io, remote_api);
var api = require('./server/api')(io, sql, app_cfg, remote_api, saver);
var socket = require('./server/socket')(io, sql, app_cfg, waip);
var udp = require('./server/udp')(app_cfg, sql, saver);
var auth = require('./server/auth')(app, app_cfg, sql_cfg, async, bcrypt, passport, io);
var routes = require('./server/routing')(app, sql, app_cfg, passport, auth, udp);
var routes = require('./server/routing')(app, sql, uuidv4, app_cfg, passport, auth, udp, saver);
// Server starten
webserver.listen(app_cfg.global.https_port, function() {
webserver.listen(app_cfg.global.https_port, function () {
sql.db_log('Anwendung', 'Wachalarm-IP-Webserver auf Port ' + app_cfg.global.https_port + ' gestartet');
});
// Redirect all HTTP traffic to HTTPS
http.createServer(function(req, res) {
http.createServer(function (req, res) {
var host = req.headers.host;
// prüfen ob host gesetzt, sonst 404
if (typeof host !== 'undefined' && host) {
@ -60,5 +81,3 @@ http.createServer(function(req, res) {
res.end();
};
}).listen(app_cfg.global.http_port);
// TODO: auf HTTPS mit TLS1.2 umstellen, inkl. WSS

118
server/api.js Executable file
View File

@ -0,0 +1,118 @@
module.exports = function (io, sql, app_cfg, remote_api, saver) {
// ###
// Server Socket.IO Empfangs-API (anderer Server stellt Verbindung her und sendet Daten)
// ###
if (app_cfg.api.enabled) {
// Namespace API festlegen
var nsp_api = io.of('/api');
nsp_api.on('connection', function (socket) {
// versuche Remote-IP zu ermitteln
var remote_ip = socket.handshake.headers["x-real-ip"] || socket.handshake.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
// Remote-Verbindung nur zulassen, wenn IP in Access-List, und Access-List ueberhaupt befuellt
if (!app_cfg.api.access_list.includes(remote_ip) && app_cfg.api.access_list.length > 0) {
socket.disconnect(true);
sql.db_log('API', 'Verbindung von ' + remote_ip + ' geschlossen, da nicht in Zugangsliste.');
};
//TODO API: Eingehende Verbindung nur mit passendem Geheimnis zulassen, das Ergebnis loggen
// in Liste der Clients mit aufnehmen
sql.db_client_update_status(socket, 'api');
// Neuen Einsatz speichern
socket.on('from_client_to_server_new_waip', function (raw_data) {
var data = raw_data.data;
var app_id = raw_data.app_id;
// nur speichern wenn app_id nicht eigenen globalen app_id entspricht
if (app_id != app_cfg.global.app_id) {
saver.save_new_waip(data, remote_ip, app_id);
if (app_cfg.global.development) {
sql.db_log('API', 'Neuer Wachalarm von ' + remote_ip + ': ' + JSON.stringify(data));
} else {
sql.db_log('API', 'Neuer Wachalarm von ' + remote_ip + '. Wird verarbeitet.');
};
};
});
// neue externe Rueckmeldung speichern
socket.on('from_client_to_server_new_rmld', function (raw_data) {
var data = raw_data.data;
var app_id = raw_data.app_id;
// nur speichern wenn app_id nicht eigenen globalen app_id entspricht
if (app_id != app_cfg.global.app_id) {
saver.save_new_rmld(data, remote_ip, app_id, function (result) {
if (!result) {
sql.db_log('API', 'Fehler beim speichern der Rückmeldung von ' + remote_ip + ': ' + JSON.stringify(data));
};
});
};
});
// Disconnect
socket.on('disconnect', function () {
sql.db_log('API', 'Schnittstelle von ' + remote_ip + ' (' + socket.id + ') geschlossen.');
sql.db_client_delete(socket);
});
});
};
// ###
// Client Socket.IO Sende-API (Daten an Server senden, zu denen eine Verbindung hergestellt wurde)
// ###
if (app_cfg.endpoint.enabled) {
// Verbindung zu anderem Server aufbauen
// TODO API: Verbindungsaufbau mit passendem Geheimnis absichern, IP-Adresse senden
// Verbindungsaufbau protokollieren
remote_api.on('connect', function () {
sql.db_log('API', 'Verbindung mit ' + app_cfg.endpoint.host + ' ergestellt');
});
// Fehler protokollieren
remote_api.on('connect_error', function (err) {
sql.db_log('API', 'Verbindung zu ' + app_cfg.endpoint.host + ' verloren, Fehler: ' + err);
});
// Verbindungsabbau protokollieren
remote_api.on('disconnect', function (reason) {
sql.db_log('API', 'Verbindung zu ' + app_cfg.endpoint.host + ' verloren, Fehler: ' + reason);
});
// neuer Einsatz vom Endpoint-Server
remote_api.on('from_server_to_client_new_waip', function (raw_data) {
var data = raw_data.data;
var app_id = raw_data.app_id;
// nur speichern wenn app_id nicht eigenen globalen app_id entspricht
if (app_id != app_cfg.global.app_id) {
// nicht erwuenschte Daten ggf. enfernen (Datenschutzoption)
saver.save_new_waip(data, app_cfg.endpoint.host, app_id);
if (app_cfg.global.development) {
sql.db_log('API', 'Neuer Wachalarm von ' + app_cfg.endpoint.host + ': ' + JSON.stringify(data));
} else {
sql.db_log('API', 'Neuer Wachalarm von ' + app_cfg.endpoint.host + '. Wird verarbeitet.');
};
};
});
// neue Rückmeldung vom Endpoint-Server
remote_api.on('from_server_to_client_new_rmld', function (raw_data) {
var data = raw_data.data;
var app_id = raw_data.app_id;
// nur speichern wenn app_id nicht eigenen globalen app_id entspricht
if (app_id != app_cfg.global.app_id) {
saver.save_new_rmld(data, app_cfg.endpoint.host, app_id, function (result) {
if (!result) {
sql.db_log('API', 'Fehler beim speichern der Rückmeldung von ' + app_cfg.endpoint.host + ': ' + JSON.stringify(data));
};
});
};
});
};
};

View File

@ -1,22 +1,71 @@
var app_cfg = {};
// Server-Einstellungen
app_cfg.global = {
development: true,
http_port: 3000,
https_port: 3443,
udpport: 60233,
database: './database.sqlite3',
soundpath: '/public/media/',
mediapath: '/media/',
time_to_delete_waip: 60,
time_to_delete_waip: 4,
default_time_for_standby: 10,
circumcircle: 5,
defaultuser: 'me',
defaultpass: '123',
defaultuserip: '127.0.0.1',
ip_auth_range: ['::ffff:172.16.5.0/24', '::ffff:192.168.2.0/24'],
saltRounds: 10,
sessionsecret: '0987654321abcdef#xyz',
app_id: process.pid,
map_tile: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
sessionsecret: '0987654321abcdef#xyz'
};
// Einstellungen zur Erscheinung der Seite
app_cfg.public = {
url: 'https://wachalarm.mooo.com',
app_name: 'Wachalarm IP-Web',
company: 'Leitstelle Lausitz',
map_tile: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
map_attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
ext_imprint: false,
url_imprint: 'https://www.nix.nix/impressium',
ext_privacy: false,
url_privacy: 'https://www.nix.nix/datenschutz'
};
// Einstellungen fuer Backups von Rueckmeldungen
app_cfg.rmld = {
backup_to_file: true,
backup_path: '/misc/bkp/',
backup_to_mail: false,
mailserver_host: 'smtp.xxx.xxx',
mailserver_port: 587,
secure_mail: false,
unauthorized_mail: false,
mail_user: 'testuser',
mail_pass: 'testuserpass',//'testpass',
mail_from: 'xyz@xxx.xxx'//'keineantwort@wachalarm.info.tm'
};
// Schnittstelle um Daten von anderen Clients zu empfangen
app_cfg.api = {
enabled: true,
secret: 'asdfwert1234567890#',
access_list: ['192.168.2.20', '192.168.2.30', '80.147.87.170']
};
// Schnittstelle um Daten an andere Server zu senden
app_cfg.endpoint = {
enabled: true,
host: 'https://wachalarm.leitstelle-lausitz.de/api',
secret: 'asdfwert1234567890#'
};
// Schnittstellendaten von bestimmten Clients entfernen (Datenschutzoption)
app_cfg.filter = {
enabled: true,
on_message_from: ['192.168.2.20', 'https://192.168.1.25:8090/api'],
remove_data: ['besonderheiten', 'strasse', 'objekt', 'objektnr', 'wachfolge', 'wgs84_x', 'wgs84_y']
};
module.exports = app_cfg;

View File

@ -78,7 +78,7 @@ module.exports = function(app, app_cfg, db, async, bcrypt, passport, io) {
passport.deserializeUser(function(id, done) {
db.get(`SELECT id, user, permissions,
(select reset_counter from waip_configs where user_id = ?) reset_counter
(select reset_counter from waip_user_config where user_id = ?) reset_counter
FROM waip_users WHERE id = ?`, [id, id], function(err, row) {
if (!row) {
return done(null, false);
@ -123,17 +123,17 @@ module.exports = function(app, app_cfg, db, async, bcrypt, passport, io) {
// if(err)
if (row) {
req.flash('errorMessage', "Es existiert bereits ein Benutzer mit diesem Namen!");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
} else {
bcrypt.hash(req.body.password, app_cfg.global.saltRounds, function(err, hash) {
db.run('INSERT INTO waip_users ( user, password, permissions, ip_address ) VALUES( ?, ?, ?, ? )', req.body.username, hash, req.body.permissions, req.body.ip, function(err) {
// if(err)
if (this.lastID) {
req.flash('successMessage', "Neuer Benutzer wurde angelegt.");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
} else {
req.flash('errorMessage', "Da ist etwas schief gegangen...");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
}
});
});
@ -144,14 +144,14 @@ module.exports = function(app, app_cfg, db, async, bcrypt, passport, io) {
function deleteUser(req, res) {
if (req.user.id == req.body.id) {
req.flash('errorMessage', "Sie können sich nicht selbst löschen!");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
} else {
db.run('DELETE FROM waip_users WHERE id = ?', req.body.id, function(err) {
if (err) {
//...
} else {
req.flash('successMessage', "Benutzer \'" + req.body.username + "\' wurde gelöscht!");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
}
});
};
@ -195,15 +195,15 @@ module.exports = function(app, app_cfg, db, async, bcrypt, passport, io) {
//...
console.log(err);
req.flash('errorMessage', "Da ist etwas schief gegangen...");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
} else {
req.flash('successMessage', "Benutzer aktualisiert.");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
}
});
} else {
req.flash('errorMessage', "Da ist etwas schief gegangen...");
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
}
});
};

124
server/broker.js Executable file
View File

@ -0,0 +1,124 @@
module.exports = function (app_cfg, sql, uuidv4) {
// Module laden
const twit = require('twit');
function alert_vmtl_list(list_data, callback) {
// waip_wachen_id, vmlt_typ, vmlt_account_name, vmtl_account_group, waip_id
if (app_cfg.global.development) {
console.log('Liste Vermittlung: ' + JSON.stringify(list_data));
};
if (list_data.vmtl_typ == 'twitter') {
// wenn es sich um eine Twitter-Liste/Gruppe handelt, Account-Zugangsdaten ermitteln
sql.db_vmtl_get_tw_account(list_data, function (vmtl_data) {
// vmtl_data: tw_screen_name, tw_consumer_key, tw_consumer_secret, tw_access_token_key, tw_access_token_secret, uuid, einsatzart, name_wache
if (app_cfg.global.development) {
console.log('Twitter-Account-Daten: ' + JSON.stringify(vmtl_data));
};
if (vmtl_data) {
// Prüfen ob zuletzt bereits eine Nachricht gesendet wurde (Doppelalarmierung vermeiden)
sql.db_vmtl_check_history(vmtl_data, list_data, function (exists) {
if (!exists) {
var T = new twit({
consumer_key: vmtl_data.tw_consumer_key,
consumer_secret: vmtl_data.tw_consumer_secret,
access_token: vmtl_data.tw_access_token_key,
access_token_secret: vmtl_data.tw_access_token_secret
})
var params = {
screen_name: vmtl_data.tw_screen_name
};
// Twitter-Liste beschicken
T.get('lists/list', params, function (error, lists, response) {
if (!error) {
var list_obj = lists.filter(function (o) {
return o.name == vmtl_data.list;
});
var member_params = {
list_id: list_obj[0].id_str,
count: 50
};
// mit List_id die Mitglieder der Liste auslesen
T.get('lists/members', member_params, function (error, members, response) {
if (!error) {
if (app_cfg.global.development) {
console.log('Mitglieder der Twitter-Liste: ' + JSON.stringify(members));
console.log('Response der Twitter-Liste: ' + JSON.stringify(response));
};
// an jedes Mitglied der Liste eine Meldung senden
var arrayLength = members.users.length;
for (var i = 0; i < arrayLength; i++) {
// Mitteilungstext festelgen
console.log(members);
var tw_text = String.fromCodePoint(0x1F4DF) + ' ' + String.fromCodePoint(0x1F6A8) + String.fromCodePoint(0x0A) +
'Einsatz für ' + vmtl_data.name_wache + ' ' + String.fromCodePoint(0x27A1) + ' ' + vmtl_data.einsatzart + ', ' + vmtl_data.stichwort + String.fromCodePoint(0x0A) +
'jetzt Rückmeldung senden: ' + app_cfg.public.url + '/rmld/' + vmtl_data.uuid + '/' + uuidv4();
// Parameter der Mitteilung
var msg_params = {
event: {
type: "message_create",
message_create: {
target: {
recipient_id: members.users[i].id_str
},
message_data: {
text: tw_text
}
}
}
};
// Mitteilung senden
T.post('direct_messages/events/new', msg_params, function (error, members, response) {
if (!error) {
sql.db_log('VMTL', 'Einsatz-Link gesendet: ' + JSON.stringify(members));
callback && callback(vmtl_data.list);
} else {
console.log('e1');
sql.db_log('VMTL', 'Fehler beim senden eines Einsatz-Links: ' + error);
};
});
};
} else {
console.log('e2');
sql.db_log('VMTL', 'Fehler beim lesen der Mitglieder der Twitter-Liste: ' + error);
callback && callback(null);
};
});
} else {
console.log('e3');
sql.db_log('VMTL', 'Fehler beim lesen der Twitter-Liste: ' + error);
callback && callback(null);
};
});
} else {
console.log('e4');
sql.db_log('VMTL', 'Rückmeldungs-Link für Twitter-Account ' + list_data.vmtl_account_name + ' bereits zuvor gesendet. Wird verworfen.');
callback && callback(null);
};
});
} else {
console.log('e5');
sql.db_log('VMTL', 'Zugangsdaten für Twitter-Account ' + list_data.vmtl_account_name + ' konnten nicht ermittelt werden.');
callback && callback(null);
};
});
} else {
// andere Listen/Gruppen/Schnittstellen koennten hier noch abgefragt werden
console.log('e6');
callback && callback(null);
};
};
return {
alert_vmtl_list: alert_vmtl_list
};
};

424
server/routing.js Normal file → Executable file
View File

@ -1,56 +1,115 @@
module.exports = function(app, sql, app_cfg, passport, auth, udp) {
module.exports = function (app, sql, uuidv4, app_cfg, passport, auth, udp, saver) {
// get index
app.get('/', function(req, res) {
sql.db_list_wachen(function(data) {
var data_wachen = data
sql.db_list_traeger(function(data) {
var data_traeger = data
sql.db_list_kreis(function(data) {
var data_kreis = data
/* ########################### */
/* ##### Statische Seiten #### */
/* ########################### */
// Startseite
app.get('/', function (req, res) {
sql.db_einsatz_count_all(function (data) {
res.render('home', {
public: app_cfg.public,
title: 'Startseite',
list_wache: data_wachen,
list_traeger: data_traeger,
list_kreis: data_kreis,
anzahl_einsaetze: data,
user: req.user
});
});
});
});
});
// get /waip
app.get('/waip', function(req, res) {
res.redirect('/waip/0');
});
// get /waip/<wachennummer>
// TODO: Abstruz bei unbekannter/falscher Wachennummer
app.get('/waip/:wachen_id', function(req, res, next) {
var parmeter_id = req.params.wachen_id;
sql.db_wache_vorhanden(parmeter_id, function(result) {
if (result) {
res.render('waip', {
title: 'Alarmmonitor',
wachen_id: parmeter_id,
data_wache: ' ' + result.name,
app_id: app_cfg.global.app_id,
map_tile: app_cfg.global.map_tile,
// Ueber die Anwendung
app.get('/about', function (req, res) {
res.render('about', {
public: app_cfg.public,
title: 'Über',
user: req.user
});
});
// Impressum
app.get('/impressum', function (req, res) {
if (app_cfg.public.ext_imprint) {
res.redirect(app_cfg.public.url_imprint);
} else {
var err = new Error('Wache ' + parmeter_id + ' nicht vorhanden!');
err.status = 404;
res.render('imprint', {
public: app_cfg.public,
title: 'Impressum',
user: req.user
});
};
});
// Datenschutzerklaerung
app.get('/datenschutz', function (req, res) {
if (app_cfg.public.ext_privacy) {
res.redirect(app_cfg.public.url_privacy);
} else {
res.render('privacy', {
public: app_cfg.public,
title: 'Datenschutzerklärung',
user: req.user
});
};
});
// API
app.get('/api', function (req, res, next) {
var err = new Error('Sie sind nicht berechtigt diese Seite aufzurufen!');
err.status = 403;
next(err);
}
});
/* ##################### */
/* ####### Login ####### */
/* ##################### */
// Loginseite
app.get('/login', function (req, res) {
res.render('login', {
public: app_cfg.public,
title: 'Login',
user: req.user,
error: req.flash('error')
});
});
// get /config
app.get('/config', auth.ensureAuthenticated, function(req, res) {
sql.db_get_userconfig(req.user.id, function(data) {
res.render('config', {
// Login-Formular verarbeiten
app.post('/login', passport.authenticate('local', {
failureRedirect: '/login',
failureFlash: 'Login fehlgeschlagen! Bitte prüfen Sie Benutzername und Passwort.'
}), function (req, res) {
if (req.body.rememberme) {
// der Benutzer muss sich fuer 5 Jahre nicht anmelden
req.session.cookie.maxAge = 5 * 365 * 24 * 60 * 60 * 1000;
};
res.redirect('/');
});
// Login mit IP verarbeiten
app.post('/login_ip', passport.authenticate('ip', {
failureRedirect: '/login',
failureFlash: 'Login mittels IP-Adresse fehlgeschlagen!'
}), function (req, res) {
// der Benutzer muss sich fuer 5 Jahre nicht anmelden
req.session.cookie.maxAge = 5 * 365 * 24 * 60 * 60 * 1000;
res.redirect('/');
});
// Logout verarbeiten
app.post('/logout', function (req, res) {
req.session.destroy(function (err) {
res.redirect('/');
})
});
/* ######################### */
/* ##### Einstellungen ##### */
/* ######################### */
// Einstellungen anzeigen
app.get('/config', auth.ensureAuthenticated, function (req, res) {
sql.db_user_get_config(req.user.id, function (data) {
res.render('user/user_config', {
public: app_cfg.public,
title: 'Einstellungen',
user: req.user,
reset_counter: data
@ -58,40 +117,182 @@ module.exports = function(app, sql, app_cfg, passport, auth, udp) {
});
});
app.post('/config', auth.ensureAuthenticated, function(req, res) {
sql.db_set_userconfig(req.user.id, req.body.set_reset_counter, function(data) {
// Einstellungen speichern
app.post('/config', auth.ensureAuthenticated, function (req, res) {
sql.db_user_set_config(req.user.id, req.body.set_reset_counter, function (data) {
res.redirect('/config');
});
});
// get /help
app.get('/help', function(req, res) {
res.render('help', {
title: 'Hilfe',
/* ##################### */
/* ##### Wachalarm ##### */
/* ##################### */
// /waip nach /waip/0 umleiten
app.get('/waip', function (req, res) {
sql.db_wache_get_all(function (data) {
res.render('overviews/overview_waip', {
public: app_cfg.public,
title: 'Alarmmonitor',
list_wachen: data,
user: req.user
});
});
});
// get /uhr
app.get('/test_clock', function(req, res) {
res.render('test_clock', {
title: 'Test Uhr',
// Alarmmonitor aufloesen /waip/<wachennummer>
app.get('/waip/:wachen_id', function (req, res, next) {
var parmeter_id = req.params.wachen_id;
sql.db_wache_vorhanden(parmeter_id, function (wache) {
if (wache) {
res.render('waip', {
public: app_cfg.public,
title: 'Alarmmonitor',
wachen_id: parmeter_id,
data_wache: wache.name,
map_tile: app_cfg.public.map_tile,
map_attribution: app_cfg.public.map_attribution,
app_id: app_cfg.global.app_id,
user: req.user
});
} else {
var err = new Error('Wache ' + parmeter_id + ' nicht vorhanden!');
err.status = 404;
next(err);
};
});
});
// get /test_tableau
app.get('/test_tableau', function(req, res) {
res.render('test_wachalarm', {
title: 'Test Wachalarm',
/* ######################## */
/* ###### Dashboard ####### */
/* ######################## */
// Dasboard-Uebersicht
app.get('/dbrd', function (req, res, next) {
// pruefen ob ein Paramater fuer die Einsatznummer angegeben wurde, dann Dashboard direkt oeffnen
if (req.query.enr_str) {
sql.db_einsatz_get_uuid_by_enr(req.query.enr_str, function (data) {
if (data) {
res.redirect('/dbrd/' + data);
} else {
var err = new Error('Dashboard oder Einsatz nicht (mehr) vorhanden!');
err.status = 404;
next(err);
};
});
} else {
sql.db_einsatz_get_active(function (data) {
res.render('overviews/overview_dbrd', {
public: app_cfg.public,
title: 'Dashboard',
map_tile: app_cfg.public.map_tile,
map_attribution: app_cfg.public.map_attribution,
user: req.user,
dataSet: data
});
});
};
});
// Dasboard fuer einen Einsatz
app.get('/dbrd/:dbrd_uuid', function (req, res, next) {
var dbrd_uuid = req.params.dbrd_uuid;
sql.db_einsatz_check_uuid(dbrd_uuid, function (wache) {
if (wache) {
res.render('dbrd', {
public: app_cfg.public,
title: 'Dashboard',
dbrd_uuid: dbrd_uuid,
map_tile: app_cfg.public.map_tile,
map_attribution: app_cfg.public.map_attribution,
app_id: app_cfg.global.app_id,
user: req.user
});
} else {
var err = new Error('Dashboard oder Einsatz nicht (mehr) vorhanden!');
err.status = 404;
next(err);
};
});
});
// get /show_active_user
app.get('/show_active_user', auth.ensureAdmin, function(req, res) {
sql.db_get_active_clients(function(data) {
res.render('show_active_user', {
/* ######################## */
/* ##### Rueckmeldung ##### */
/* ######################## */
// Rueckmeldungs-Aufruf ohne waip_uuid eblehnen
app.get('/rmld', function (req, res, next) {
var err = new Error('Rückmeldungen sind nur mit gültiger Einsatz-ID erlaubt!');
err.status = 404;
next(err);
});
// Rueckmeldungs-Aufruf mit waip_uuid aber ohne rmld_uuid an zufällige rmld_uuid weiterleiten
app.get('/rmld/:waip_uuid', function (req, res, next) {
res.redirect('/rmld/' + req.params.waip_uuid + '/' + uuidv4());
});
// Rueckmeldung anzeigen /rueckmeldung/waip_uuid/rmld_uuid
app.get('/rmld/:waip_uuid/:rmld_uuid', function (req, res, next) {
var waip_uuid = req.params.waip_uuid;
var rmld_uuid = req.params.rmld_uuid;
sql.db_einsatz_get_by_uuid(waip_uuid, function (einsatzdaten) {
if (einsatzdaten) {
sql.db_user_check_permission(req.user, einsatzdaten.id, function (valid) {
if (!valid) {
delete einsatzdaten.objekt;
delete einsatzdaten.besonderheiten;
delete einsatzdaten.strasse;
delete einsatzdaten.wgs84_x;
delete einsatzdaten.wgs84_y;
};
einsatzdaten.rmld_uuid = rmld_uuid;
res.render('rmld', {
public: app_cfg.public,
title: 'Einsatz-Rückmeldung',
user: req.user,
einsatzdaten: einsatzdaten,
map_tile: app_cfg.public.map_tile,
map_attribution: app_cfg.public.map_attribution,
error: req.flash("errorMessage"),
success: req.flash("successMessage")
});
});
} else {
var err = new Error('Der angefragte Einsatz ist nicht - oder nicht mehr - vorhanden!');
err.status = 404;
next(err);
};
});
});
// Rueckmeldung entgegennehmen
app.post('/rmld/:waip_uuid/:rmld_uuid', function (req, res) {
// Remote-IP erkennen, fuer Fehler-Auswertung
var remote_ip = req.headers["x-real-ip"] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
// auf Saver verweisen
saver.save_new_rmld(req.body, remote_ip, 'web', function (saved) {
var waip_uuid = req.body.waip_uuid;
var rmld_uuid = req.body.rmld_uuid;
if (saved) {
req.flash('successMessage', 'Rückmeldung erfolgreich gesendet, auf zum Einsatz!');
res.redirect('/rmld/' + waip_uuid + '/' + rmld_uuid);
} else {
req.flash('errorMessage', 'Fehler beim Senden der Rückmeldung!');
res.redirect('/rmld/' + waip_uuid + '/' + rmld_uuid);
};
});
});
/* ########################## */
/* ##### Administration ##### */
/* ########################## */
// verbundene Clients anzeigen
app.get('/adm_show_clients', auth.ensureAdmin, function (req, res) {
sql.db_client_get_connected(function (data) {
res.render('admin/adm_show_clients', {
public: app_cfg.public,
title: 'Verbundene PCs/Benutzer',
user: req.user,
dataSet: data
@ -99,10 +300,11 @@ module.exports = function(app, sql, app_cfg, passport, auth, udp) {
});
});
// get /show_active_waip
app.get('/show_active_waip', auth.ensureAdmin, function(req, res) {
sql.db_get_active_waips(function(data) {
res.render('show_active_waip', {
// laufende Einsaetze anzeigen
app.get('/adm_show_missions', auth.ensureAdmin, function (req, res) {
sql.db_einsatz_get_active(function (data) {
res.render('admin/adm_show_missions', {
public: app_cfg.public,
title: 'Akutelle Einsätze',
user: req.user,
dataSet: data
@ -110,10 +312,11 @@ module.exports = function(app, sql, app_cfg, passport, auth, udp) {
});
});
// get /show_log
app.get('/show_log', auth.ensureAdmin, function(req, res) {
sql.db_get_log(function(data) {
res.render('show_log', {
// Logdatei
app.get('/adm_show_log', auth.ensureAdmin, function (req, res) {
sql.db_log_get_5000(function (data) {
res.render('admin/adm_show_log', {
public: app_cfg.public,
title: 'Log-Datei',
user: req.user,
dataSet: data
@ -121,33 +324,35 @@ module.exports = function(app, sql, app_cfg, passport, auth, udp) {
});
});
// get /test_alert
app.get('/test_alert', auth.ensureAdmin, function(req, res) {
res.render('test_alert', {
// direkten Alarm ausloesen
app.get('/adm_run_alert', auth.ensureAdmin, function (req, res) {
res.render('admin/adm_run_alert', {
public: app_cfg.public,
title: 'Test-Alarm',
user: req.user,
});
});
app.post('/test_alert', auth.ensureAdmin, function(req, res) {
app.post('/adm_run_alert', auth.ensureAdmin, function (req, res) {
udp.send_message(req.body.test_alert);
res.redirect('/test_alert');
res.redirect('/adm_run_alert');
});
// get /edit_users
app.get('/edit_users', auth.ensureAdmin, function(req, res) {
sql.db_get_users(function(data) {
res.render('edit_users', {
// Benutzer editieren
app.get('/adm_edit_users', auth.ensureAdmin, function (req, res) {
sql.db_user_get_all(function (data) {
res.render('admin/adm_edit_users', {
public: app_cfg.public,
title: 'Benutzer und Rechte verwalten',
user: req.user,
users: data,
error: req.flash("errorMessage"),
success: req.flash("successMessage")
error: req.flash('errorMessage'),
success: req.flash('successMessage')
});
});
});
app.post('/edit_users', auth.ensureAdmin, function(req, res) {
app.post('/adm_edit_users', auth.ensureAdmin, function (req, res) {
if (req.user && req.user.permissions == "admin") {
switch (req.body["modal_method"]) {
case "DELETE":
@ -161,57 +366,70 @@ module.exports = function(app, sql, app_cfg, passport, auth, udp) {
break;
}
} else {
res.redirect('/edit_users');
res.redirect('/adm_edit_users');
}
});
// get /login
app.get('/login', function(req, res) {
res.render('login', {
title: 'Login',
/* ###################### */
/* ##### Testseiten ##### */
/* ###################### */
// Wachalarm-Uhr testen
app.get('/test_clock', function (req, res) {
res.render('tests/test_clock', {
public: app_cfg.public,
title: 'Test Uhr',
user: req.user
});
});
app.post('/login', passport.authenticate('local', {
failureRedirect: '/login'
}), function(req, res) {
if(req.body.rememberme){
// der Benutzer muss sich fuer 5 Jahre nicht anmelden
req.session.cookie.maxAge = 5 * 365 * 24 * 60 * 60 * 1000;
};
res.redirect('/');
// Alarmmonitor testen
app.get('/test_wachalarm', function (req, res) {
res.render('tests/test_wachalarm', {
public: app_cfg.public,
title: 'Test Wachalarm',
user: req.user
});
});
app.post('/login_ip', passport.authenticate('ip', {
failureRedirect: '/login'
}), function(req, res) {
// der Benutzer muss sich fuer 5 Jahre nicht anmelden
req.session.cookie.maxAge = 5 * 365 * 24 * 60 * 60 * 1000;
res.redirect('/');
// Rueckmeldung testen
app.get('/test_rueckmeldung', function (req, res) {
res.render('tests/test_rueckmeldung', {
public: app_cfg.public,
title: 'Test Einsatz-Rückmeldung',
user: req.user
});
});
app.post('/logout', function(req, res) {
req.session.destroy(function(err) {
res.redirect('/');
})
// Dashboard testen
app.get('/test_dashboard', function (req, res) {
res.render('tests/test_dashboard', {
public: app_cfg.public,
title: 'Test Dashboard',
user: req.user
});
});
// catch 404 and forward to error handler
app.use(function(req, res, next) {
/* ######################## */
/* ##### Fehlerseiten ##### */
/* ######################## */
// 404 abfangen und an error handler weiterleiten
app.use(function (req, res, next) {
var err = new Error('Seite nicht gefunden!');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.locals.error = app_cfg.global.development ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error', {
public: app_cfg.public,
user: req.user
});
});

237
server/saver.js Executable file
View File

@ -0,0 +1,237 @@
module.exports = function (app_cfg, sql, waip, uuidv4, io, remote_api) {
// Module laden
const turf = require('@turf/turf');
// Variablen festlegen
var uuid_pattern = new RegExp('^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', 'i');
// Speichern eines neuen Einsatzes
function save_new_waip(waip_data, remote_addr, app_id) {
// ist JSON valide
validate_waip(waip_data, function (waip_json) {
if (waip_json) {
// Polygon erzeugen und zuweisen falls nicht vorhanden
if (!waip_json.ortsdaten.wgs84_area) {
var wgs_x = parseFloat(waip_json.ortsdaten.wgs84_x);
var wgs_y = parseFloat(waip_json.ortsdaten.wgs84_y);
var point = turf.point([wgs_y, wgs_x]);
var buffered = turf.buffer(point, 1, {
steps: app_cfg.global.circumcircle,
units: 'kilometers'
});
var bbox = turf.bbox(buffered);
var new_point = turf.randomPoint(1, {
bbox: bbox
});
var new_buffer = turf.buffer(new_point, 1, {
steps: app_cfg.global.circumcircle,
units: 'kilometers'
})
waip_json.ortsdaten.wgs84_area = new_buffer;
};
// pruefen, ob vielleicht schon ein Einsatz mit einer UUID gespeichert ist
sql.db_einsatz_get_uuid_by_enr(waip_json.einsatzdaten.nummer, function (waip_uuid) {
if (waip_uuid) {
// wenn ein Einsatz mit UUID schon vorhanden ist, dann diese setzten / ueberschreiben
waip_json.einsatzdaten.uuid = waip_uuid;
} else {
// uuid erzeugen und zuweisen falls nicht bereits in JSON vorhanden, oder falls keine korrekte uuid
if (!waip_json.einsatzdaten.uuid || !uuid_pattern.test(waip_json.einsatzdaten.uuid)) {
waip_json.einsatzdaten.uuid = uuidv4();
};
};
// nicht erwuenschte Daten ggf. enfernen (Datenschutzoption)
filter_api_data(waip_json, remote_addr, function (data_filtered) {
// Einsatz in DB Speichern
waip.waip_speichern(data_filtered);
sql.db_log('WAIP', 'Neuer Einsatz von ' + remote_addr + ' wird jetzt verarbeitet: ' + JSON.stringify(data_filtered));
});
// Einsatzdaten per API weiterleiten (entweder zum Server oder zum verbunden Client)
api_server_to_client_new_waip(waip_json, app_id);
api_client_to_server_new_waip(waip_json, app_id);
});
} else {
sql.db_log('WAIP', 'Fehler: Einsatz von ' + remote_addr + ' nicht valide: ' + JSON.stringify(waip_data));
};
});
};
function save_new_rmld(data, remote_addr, app_id, callback) {
validate_rmld(data, function (valid) {
if (valid) {
// Rueckmeldung speichern und verteilen
sql.db_rmld_save(data, function (result) {
if (result) {
sql.db_log('RMLD', 'Rückmeldung von ' + remote_addr + ' erhalten und gespeichert: ' + JSON.stringify(data));
waip.rmld_verteilen_by_uuid(data.waip_uuid, data.rmld_uuid);
callback && callback(true);
} else {
sql.db_log('RMLD', 'Fehler beim speichern der Rückmeldung von ' + remote_addr + ': ' + JSON.stringify(data));
callback && callback(false);
};
});
// RMLD-Daten per API weiterleiten (entweder zum Server oder zum verbunden Client)
api_server_to_client_new_rmld(data, app_id);
api_client_to_server_new_rmld(data, app_id);
} else {
sql.db_log('RMLD', 'Fehler: Rückmeldung von ' + remote_addr + ' nicht valide: ' + JSON.stringify(waip_data));
callback && callback(false);
};
});
};
function validate_waip(data, callback) {
// TODO Validierung: Einsatzdaten auf Validität prüfen
// Typ ist NULL
if (data === null) {
callback && callback(false);
};
// Typ ist undefined
if (data === undefined) {
callback && callback(false);
};
// Typ ist String
if (data.constructor == String) {
// String versuchen in JSON umzuwandeln
try {
var tmp = JSON.parse(data);
callback && callback(tmp);
} catch (error) {
callback && callback(false);
};
};
// Typ ist Object
if (data.constructor === Object) {
// teste ob der String des Objects JSON-Konform ist
var text = JSON.stringify(data);
if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
//falls ja, dann versuche String in JSON zu parsen
try {
var tmp = JSON.parse(text);
callback && callback(tmp);
} catch (error) {
callback && callback(false);
};
} else {
callback && callback(false);
};
};
// Log
if (app_cfg.global.development) {
console.log('Validierung WAIP: ' + JSON.stringify(data));
};
};
function validate_rmld(data, callback) {
// TODO Validierung: Rueckmeldung auf plausibilität
// Log
if (app_cfg.global.development) {
console.log('Validierung RMLD: ' + JSON.stringify(data));
};
callback && callback(true);
// SQL-Log
};
function api_server_to_client_new_waip(data, app_id) {
// Rückmeldung an verbundenen Client senden, falls funktion aktiviert
if (app_cfg.api.enabled) {
// testen ob app_id auch eine uuid ist, falls nicht, eigene app_uuid setzen
if (!uuid_pattern.test(app_id)) {
app_id = app_cfg.global.app_id;
};
io.of('/api').emit('from_server_to_client_new_waip', {
data: data,
app_id: app_id
});
sql.db_log('API', 'Einsatz an Clients gesendet: ' + JSON.stringify(data));
};
};
function api_server_to_client_new_rmld(data, app_id) {
// Rückmeldung an verbundenen Client senden, falls funktion aktiviert
if (app_cfg.api.enabled) {
// testen ob app_id auch eine uuid ist, falls nicht, eigene app_uuid setzen
if (!uuid_pattern.test(app_id)) {
app_id = app_cfg.global.app_id;
};
io.of('/api').emit('from_server_to_client_new_rmld', {
data: data,
app_id: app_id
});
sql.db_log('API', 'Rückmeldung an Clients gesendet: ' + JSON.stringify(data));
};
};
function api_client_to_server_new_waip(data, app_id) {
// Alarm an Remote-Server senden, falls funktion aktiviert
if (app_cfg.endpoint.enabled) {
// testen ob app_id auch eine uuid ist, falls nicht, eigene app_uuid setzen
if (!uuid_pattern.test(app_id)) {
app_id = app_cfg.global.app_id;
};
remote_api.emit('from_client_to_server_new_waip', {
data: data,
app_id: app_id
});
sql.db_log('API', 'Neuen Wachalarm an ' + app_cfg.endpoint.host + ' gesendet: ' + JSON.stringify(data));
};
};
function api_client_to_server_new_rmld(data, app_id) {
// Rückmeldung an Remote-Server senden, falls funktion aktiviert
if (app_cfg.endpoint.enabled) {
// testen ob app_id auch eine uuid ist, falls nicht, eigene app_uuid setzen
if (!uuid_pattern.test(app_id)) {
app_id = app_cfg.global.app_id;
};
remote_api.emit('from_client_to_server_new_rmld', {
data: data,
app_id: app_id
});
sql.db_log('API', 'Rückmeldung an ' + app_cfg.endpoint.host + ' gesendet: ' + JSON.stringify(data));
};
};
function filter_api_data(data, remote_ip, callback) {
// unnoetige Zeichen aus socket_id entfernen, um diese als Dateinamen zu verwenden
if (app_cfg.filter.enabled) {
// Filter nur anwenden wenn Einsatzdaten von bestimmten IP-Adressen kommen
if (app_cfg.filter.on_message_from.includes(remote_ip)) {
var data_filtered = data;
// Schleife definieren
function loop_done(data_filtered) {
callback && callback(data_filtered);
};
var itemsProcessed = 0;
// nicht gewollte Daten entfernen
app_cfg.filter.remove_data.forEach(function (item, index, array) {
data_filtered.einsatzdaten[item] = '';
data_filtered.ortsdaten[item] = '';
// Schleife erhoehen
itemsProcessed++;
if (itemsProcessed === array.length) {
// Schleife beenden
loop_done(data_filtered);
};
});
} else {
callback && callback(data);
};
} else {
callback && callback(data);
};
};
return {
save_new_waip: save_new_waip,
save_new_rmld: save_new_rmld
};
};

90
server/socket.js Executable file
View File

@ -0,0 +1,90 @@
module.exports = function (io, sql, app_cfg, waip) {
// Socket.IO Alarmmonitor
var nsp_waip = io.of('/waip');
nsp_waip.on('connection', function (socket) {
// versuche Client-IP zu ermitteln
var client_ip = socket.handshake.headers["x-real-ip"] || socket.handshake.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
//zuerst Server-Version senden, damit der Client diese prueft und die Seite ggf. neu laedt
socket.emit('io.version', app_cfg.global.app_id);
// Aufruf des Alarmmonitors einer bestimmten Wache verarbeiten
socket.on('WAIP', function (wachen_id) {
sql.db_log('DEBUG', 'Alarmmonitor Nr. ' + wachen_id + ' von ' + client_ip + ' (' + socket.id + ') aufgerufen.');
// prüfen ob Wachenummer in der Datenbank hinterlegt ist
sql.db_wache_vorhanden(wachen_id, function (result) {
// wenn die Wachennummer vorhanden/plausibel dann weiter
if (result) {
// Socket-Room beitreiten
socket.join(wachen_id, function () {
// prüfen ob für diese Wache Einsätze vorhanden sind
sql.db_einsatz_ermitteln(wachen_id, socket, function (result_einsatz) {
if (result_einsatz) {
// nur den ersten Einsatz senden, falls mehrere vorhanden sind
var waip_id = result_einsatz[0].waip_einsaetze_ID;
sql.db_log('WAIP', 'Einsatz ' + waip_id + ' für Wache ' + wachen_id + ' vorhanden, wird jetzt an Client ' + socket.id + ' gesendet.');
//letzten Einsatz verteilen
waip.waip_verteilen(waip_id, socket, wachen_id);
//vorhandene Rückmeldungen verteilen
waip.rmld_verteilen_for_one_client(waip_id, socket, wachen_id);
} else {
sql.db_log('WAIP', 'Kein Einsatz für Wache ' + wachen_id + ' vorhanden, gehe in Standby');
// falls kein Einsatz vorhanden ist, dann Standby senden
socket.emit('io.standby', null);
};
});
// in Statusüberischt speichern
sql.db_client_update_status(socket, null);
});
} else {
sql.db_log('ERROR', 'Fehler: Wachnnummer ' + wachen_id + 'nicht vorhanden!');
socket.emit('io.error', 'Fehler: Wachnnummer \'' + wachen_id + '\' nicht vorhanden!');
};
});
});
// Disconnect
socket.on('disconnect', function () {
sql.db_log('DEBUG', 'Alarmmonitor von ' + client_ip + ' (' + socket.id + ') geschlossen.');
sql.db_client_delete(socket);
});
});
// Socket.IO Dashboard
var nsp_dbrd = io.of('/dbrd');
nsp_dbrd.on('connection', function (socket) {
// versuche Client-IP zu ermitteln
var client_ip = socket.handshake.headers["x-real-ip"] || socket.handshake.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
//zuerst Server-Version senden, damit der Client diese prueft und die Seite ggf. neu laedt
socket.emit('io.version', app_cfg.global.app_id);
// Aufruf des Dashboards eines bestimmten Einsatzes verarbeiten
socket.on('dbrd', function (uuid) {
sql.db_log('DEBUG', 'Dashboard ' + uuid + ' von ' + client_ip + ' (' + socket.id + ') aufgerufen.');
// prüfen ob Dashboard/Einsatz vorhanden
sql.db_einsatz_check_uuid(uuid, function (dbrd_uuid) {
// wenn die Wachennummer vorhanden dann weiter
if (dbrd_uuid) {
// Socket-Room beitreiten
socket.join(dbrd_uuid.uuid, function () {
sql.db_log('DBRD', 'Einsatz ' + dbrd_uuid.uuid + ' für Dashboard ' + dbrd_uuid.uuid + ' vorhanden, wird jetzt an Client ' + socket.id + ' gesendet.');
//letzten Einsatz verteilen
waip.dbrd_verteilen(dbrd_uuid.uuid, socket);
// in Statusüberischt speichern
sql.db_client_update_status(socket, dbrd_uuid.uuid);
});
} else {
sql.db_log('ERROR', 'Fehler: Dashboard ' + uuid + 'nicht (mehr) vorhanden!');
socket.emit('io.error', 'Fehler: Dashboard \'' + uuid + '\' nicht (mehr) vorhanden!');
};
});
});
// Disconnect
socket.on('disconnect', function (uuid) {
sql.db_log('DEBUG', 'Dashboard ' + uuid + ' von ' + client_ip + ' (' + socket.id + ') geschlossen.');
sql.db_client_delete(socket);
});
});
};

View File

@ -1,13 +1,11 @@
module.exports = function (fs, bcrypt, app_cfg) {
// TODO: gegen better-sqlite3 ersetzen
// Datenbank einrichten
const sqlite3 = require('sqlite3').verbose();
var dbFile = app_cfg.global.database;
var dbExists = fs.existsSync(dbFile);
// Datenbank erstellen
// Datenbank erstellen, falls nicht vorhanden
var db = new sqlite3.Database(dbFile, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
if (err) {
console.error(err.message);
@ -28,8 +26,9 @@ module.exports = function (fs, bcrypt, app_cfg) {
db.serialize(function () {
// Einsatz-Tabelle erstellen
db.run(`CREATE TABLE waip_einsaetze (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
zeitstempel DATETIME DEFAULT CURRENT_TIMESTAMP,
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
uuid TEXT,
zeitstempel DATETIME DEFAULT (DATETIME(CURRENT_TIMESTAMP, 'LOCALTIME')),
einsatznummer TEXT,
alarmzeit TEXT,
einsatzart TEXT,
@ -43,10 +42,12 @@ module.exports = function (fs, bcrypt, app_cfg) {
objektnr TEXT,
objektart TEXT,
wachenfolge INTEGER,
sonstiger_ort TEXT,
wgs84_x TEXT,
wgs84_y TEXT)`);
wgs84_y TEXT,
wgs84_area TEXT,
UNIQUE (id, uuid))`);
// Einsatzmittel-Tabelle erstellen
// TODO: Erweitern um Status, Staerke, AGT
db.run(`CREATE TABLE waip_einsatzmittel (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
waip_einsaetze_ID INTEGER NOT NULL,
@ -70,10 +71,18 @@ module.exports = function (fs, bcrypt, app_cfg) {
name_kreis TEXT,
wgs84_x TEXT,
wgs84_y TEXT)`);
// History-Tabelle erstellen
db.run(`CREATE TABLE waip_history (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
waip_uuid TEXT,
socket_id TEXT,
uuid_einsatz_grunddaten TEXT,
uuid_em_alarmiert TEXT,
uuid_em_weitere TEXT)`);
// Client-Tabelle erstellen
db.run(`CREATE TABLE waip_clients (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
connect_time DATETIME DEFAULT CURRENT_TIMESTAMP,
connect_time DATETIME DEFAULT (DATETIME(CURRENT_TIMESTAMP, 'LOCALTIME')),
socket_id TEXT,
client_ip TEXT,
room_name TEXT,
@ -85,11 +94,18 @@ module.exports = function (fs, bcrypt, app_cfg) {
// Rueckmelde-Tabelle erstellen
db.run(`CREATE TABLE waip_response (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
waip_einsaetze_id INTEGER NOT NULL,
einsatzkraft TEXT,
maschinist TEXT,
fuehrungskraft TEXT,
atemschutz TEXT)`);
waip_uuid TEXT,
rmld_uuid TEXT,
alias TEXT,
einsatzkraft INTEGER,
maschinist INTEGER,
fuehrungskraft INTEGER,
agt INTEGER,
set_time DATETIME,
arrival_time DATETIME,
wache_id INTEGER,
wache_nr INTEGER,
wache_name TEXT)`);
// Benutzer-Tabelle erstellen
db.run(`CREATE TABLE waip_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -98,18 +114,46 @@ module.exports = function (fs, bcrypt, app_cfg) {
permissions TEXT,
ip_address TEXT)`);
// Einstellungs-Tabelle für Benutzer erstellen
db.run(`CREATE TABLE waip_configs (
db.run(`CREATE TABLE waip_user_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
reset_counter INTEGER)`);
reset_counter INTEGER,
display_options TEXT,
sound_options TEXT,
FOREIGN KEY(user_id) REFERENCES waip_users(id))`);
// Ersetzungs-Tabelle fuer Einsatzmittelnamen erstellen
db.run(`CREATE TABLE waip_ttsreplace (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
einsatzmittel_typ TEXT,
einsatzmittel_rufname TEXT)`);
// Vermittlungs-Tabelle erstellen
db.run(`CREATE TABLE waip_vmtl (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
waip_wachenname TEXT,
vmtl_typ TEXT,
vmtl_account_name TEXT,
vmtl_account_group TEXT,
vmtl_history TEXT)`);
// Twitter-Account-Tabelle erstellen
db.run(`CREATE TABLE waip_tw_accounts (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
tw_screen_name TEXT,
tw_consumer_key TEXT,
tw_consumer_secret TEXT,
tw_access_token_key TEXT,
tw_access_token_secret TEXT)`);
// Export-Tabelle erstellen
db.run(`CREATE TABLE waip_export (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
export_typ TEXT,
export_name TEXT,
export_text TEXT,
export_filter TEXT,
export_recipient TEXT)`);
// Log erstellen
db.run(`CREATE TABLE waip_log (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
log_time DATETIME DEFAULT CURRENT_TIMESTAMP,
log_time DATETIME DEFAULT (DATETIME(CURRENT_TIMESTAMP, 'LOCALTIME')),
log_typ TEXT,
log_text TEXT)`);
// Default-Wachen speichern
@ -117,24 +161,26 @@ module.exports = function (fs, bcrypt, app_cfg) {
nr_wache, nr_traeger, nr_kreis, name_wache, name_traeger, name_kreis, wgs84_x, wgs84_y)
VALUES
(0,\'0\',0,\'Global - Alle Einsätze\',\'Global\',\'Global\',\'0\',\'0\'),
(520101,\'01\',52,\'CB FW Cottbus 1\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7329037496\',\'14.3377829699\'),
(520201,\'02\',52,\'CB FW Cottbus 2\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7654389234\',\'14.3352138763\'),
(521101,\'11\',52,\'CB FW Branitz\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7363607074\',\'14.3673504518\'),
(521102,\'11\',52,\'CB FW Dissenchen\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7628368787\',\'14.3925021325\'),
(521103,\'11\',52,\'CB FW Kahren\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7226384039\',\'14.409874149\'),
(521104,\'11\',52,\'CB FW Kiekebusch\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7241108468\',\'14.3624851518\'),
(521201,\'12\',52,\'CB FW Merzdorf\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7791567132\',\'14.3860451556\'),
(521202,\'12\',52,\'CB FW Sandow\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7656727968\',\'14.3352466545\'),
(521203,\'12\',52,\'CB FW Saspow\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7887535908\',\'14.356240843\'),
(521204,\'12\',52,\'CB FW Willmersdorf\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.8057039156\',\'14.3803709656\'),
(521301,\'13\',52,\'CB FW Döbbrick\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.8173690763\',\'14.3421287282\'),
(521302,\'13\',52,\'CB FW Schmellwitz\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.784034245\',\'14.3351144771\'),
(521303,\'13\',52,\'CB FW Sielow\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7994083121\',\'14.3041128045\'),
(521304,\'13\',52,\'CB FW Ströbitz\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.754401066\',\'14.3010033167\'),
(521401,\'14\',52,\'CB FW Gallinchen\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.710035337\',\'14.3533658895\'),
(521402,\'14\',52,\'CB FW Groß Gaglow\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7125349362\',\'14.3200961957\'),
(521403,\'14\',52,\'CB FW Madlow\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7204777791\',\'14.3457788456\'),
(521404,\'14\',52,\'CB FW Sachsendorf\',\'Stadt Cottbus\',\'Stadt Cottbus\',\'51.7328922537\',\'14.3192552006\'),
(520101,\'01\',52,\'CB FW Cottbus 1\',\'Stadt Cottbus - Berufsfeuerwehr\',\'Stadt Cottbus\',\'51.7329037496\',\'14.3377829699\'),
(520201,\'02\',52,\'CB FW Cottbus 2\',\'Stadt Cottbus - Berufsfeuerwehr\',\'Stadt Cottbus\',\'51.7654389234\',\'14.3352138763\'),
(520301,\'03\',52,\'CB FW Cottbus 3\',\'Stadt Cottbus - Berufsfeuerwehr\',\'Stadt Cottbus\',\'51.743946\',\'14.320619\'),
(521101,\'11\',52,\'CB FW Branitz\',\'Stadt Cottbus - Löschbezirk 1\',\'Stadt Cottbus\',\'51.7363607074\',\'14.3673504518\'),
(521102,\'11\',52,\'CB FW Dissenchen\',\'Stadt Cottbus - Löschbezirk 1\',\'Stadt Cottbus\',\'51.7628368787\',\'14.3925021325\'),
(521103,\'11\',52,\'CB FW Kahren\',\'Stadt Cottbus - Löschbezirk 1\',\'Stadt Cottbus\',\'51.7226384039\',\'14.409874149\'),
(521104,\'11\',52,\'CB FW Kiekebusch\',\'Stadt Cottbus - Löschbezirk 1\',\'Stadt Cottbus\',\'51.7241108468\',\'14.3624851518\'),
(521201,\'12\',52,\'CB FW Merzdorf\',\'Stadt Cottbus - Löschbezirk 2\',\'Stadt Cottbus\',\'51.7791567132\',\'14.3860451556\'),
(521202,\'12\',52,\'CB FW Sandow\',\'Stadt Cottbus - Löschbezirk 2\',\'Stadt Cottbus\',\'51.7656727968\',\'14.3352466545\'),
(521203,\'12\',52,\'CB FW Saspow\',\'Stadt Cottbus - Löschbezirk 2\',\'Stadt Cottbus\',\'51.7887535908\',\'14.356240843\'),
(521204,\'12\',52,\'CB FW Willmersdorf\',\'Stadt Cottbus - Löschbezirk 2\',\'Stadt Cottbus\',\'51.8057039156\',\'14.3803709656\'),
(521301,\'13\',52,\'CB FW Döbbrick\',\'Stadt Cottbus - Löschbezirk 3\',\'Stadt Cottbus\',\'51.8173690763\',\'14.3421287282\'),
(521302,\'13\',52,\'CB FW Schmellwitz\',\'Stadt Cottbus - Löschbezirk 3\',\'Stadt Cottbus\',\'51.784034245\',\'14.3351144771\'),
(521303,\'13\',52,\'CB FW Sielow\',\'Stadt Cottbus - Löschbezirk 3\',\'Stadt Cottbus\',\'51.7994083121\',\'14.3041128045\'),
(521304,\'13\',52,\'CB FW Ströbitz\',\'Stadt Cottbus - Löschbezirk 3\',\'Stadt Cottbus\',\'51.754401066\',\'14.3010033167\'),
(521401,\'14\',52,\'CB FW Gallinchen\',\'Stadt Cottbus - Löschbezirk 4\',\'Stadt Cottbus\',\'51.710035337\',\'14.3533658895\'),
(521402,\'14\',52,\'CB FW Groß Gaglow\',\'Stadt Cottbus - Löschbezirk 4\',\'Stadt Cottbus\',\'51.7125349362\',\'14.3200961957\'),
(521403,\'14\',52,\'CB FW Madlow\',\'Stadt Cottbus - Löschbezirk 4\',\'Stadt Cottbus\',\'51.7204777791\',\'14.3457788456\'),
(521404,\'14\',52,\'CB FW Sachsendorf\',\'Stadt Cottbus - Löschbezirk 4\',\'Stadt Cottbus\',\'51.7328922537\',\'14.3192552006\'),
(521501,\'15\',52,\'CB FW Gerätehaus Süd\',\'Stadt Cottbus - Gerätehäuser\',\'Stadt Cottbus\',\'51.718385\',\'14.337278\'),
(610101,\'01\',61,\'LDS FW Lübben\',\'Stadt Lübben\',\'Landkreis Dahme-Spreewald\',\'51.9430718379\',\'13.8955064944\'),
(610102,\'01\',61,\'LDS FW Lubolz\',\'Stadt Lübben\',\'Landkreis Dahme-Spreewald\',\'51.9631954482\',\'13.8277078818\'),
(610104,\'01\',61,\'LDS FW Neuendorf (Lübben)\',\'Stadt Lübben\',\'Landkreis Dahme-Spreewald\',\'51.9080633268\',\'13.8557762577\'),
@ -311,6 +357,7 @@ module.exports = function (fs, bcrypt, app_cfg) {
(619002,\'90\',61,\'LDS RW Bestensee\',\'Rettungswachen Dahme-Spreewald\',\'Landkreis Dahme-Spreewald\',\'52.2393402333\',\'13.6651219943\'),
(619004,\'90\',61,\'LDS RW Königs Wusterhausen\',\'Rettungswachen Dahme-Spreewald\',\'Landkreis Dahme-Spreewald\',\'52.3038264488\',\'13.6298907439\'),
(619005,\'90\',61,\'LDS RW Schulzendorf\',\'Rettungswachen Dahme-Spreewald\',\'Landkreis Dahme-Spreewald\',\'52.3595783918\',\'13.6008186158\'),
(619008,\'90\',61,\'LDS RW Bindow\',\'Rettungswachen Dahme-Spreewald\',\'Landkreis Dahme-Spreewald\',\'52.283327\',\'13.743823\'),
(619009,\'90\',61,\'LDS RW Golßen\',\'Rettungswachen Dahme-Spreewald\',\'Landkreis Dahme-Spreewald\',\'51.9799257518\',\'13.5771941984\'),
(619012,\'90\',61,\'LDS RW Luckau\',\'Rettungswachen Dahme-Spreewald\',\'Landkreis Dahme-Spreewald\',\'51.8504295155\',\'13.7130790573\'),
(619015,\'90\',61,\'LDS RW Goyatz\',\'Rettungswachen Dahme-Spreewald\',\'Landkreis Dahme-Spreewald\',\'52.014304731\',\'14.1786723155\'),
@ -778,10 +825,11 @@ module.exports = function (fs, bcrypt, app_cfg) {
db.run(`INSERT OR REPLACE INTO waip_ttsreplace (
einsatzmittel_typ, einsatzmittel_rufname)
VALUES
(\'10\',\'ELW\'),
(\'10\',\'KDOW\'),
(\'11\',\'ELW\'),
(\'12\',\'ELW 2\'),
(\'14\',\'KDOW\'),
(\'19\',\'MTW\'),
(\'19\',\'MTF\'),
(\'20\',\'Tanklöschfahrzeug\'),
(\'21\',\'Tanklöschfahrzeug\'),
(\'22\',\'Vorauslöschfahrzeug\'),
@ -789,13 +837,14 @@ module.exports = function (fs, bcrypt, app_cfg) {
(\'24\',\'Tanklöschfahrzeug\'),
(\'25\',\'Großtanklöschfahrzeug\'),
(\'26\',\'Tanklöschfahrzeug\'),
(\'27\',\'Tanklöschfahrzeug\'),
(\'29\',\'Großtanklöschfahrzeug\'),
(\'30\',\'Drehleiter\'),
(\'31\',\'Drehleiter\'),
(\'32\',\'Drehleiter\'),
(\'33\',\'Drehleiter\'),
(\'34\',\'Hubarbeitsbühne\'),
(\'35\',\'Drehleiter\'),
(\'35\',\'Gelenkmast\'),
(\'36\',\'Teleskopmast\'),
(\'37\',\'Teleskopmast \'),
(\'38\',\'Hubretter\'),
@ -808,20 +857,32 @@ module.exports = function (fs, bcrypt, app_cfg) {
(\'46\',\'Löschfahrzeug\'),
(\'47\',\'TSF\'),
(\'48\',\'TSF\'),
(\'49\',\'Speziallöschfahrzeug\'),
(\'51\',\'Rüstwagen\'),
(\'52\',\'Rüstwagen\'),
(\'59\',\'Rüstwagen\'),
(\'53\',\'Rüstwagen\'),
(\'59\',\'Gerätewagen\'),
(\'61\',\'Schlauchwagen\'),
(\'62\',\'Schlauchwagen\'),
(\'63\',\'Schlauchwagen\'),
(\'64\',\'Schlauchtransportwagen\'),
(\'65\',\'Wechsellader\'),
(\'66\',\'Wechsellader\'),
(\'67\',\'Wechsellader\'),
(\'69\',\'TSA\'),
(\'76\',\'Krad\'),
(\'78\',\'Löschboot\'),
(\'79\',\'Mehrzweckboot\'),
(\'82\',\'NEF\'),
(\'83\',\'RTW\'),
(\'85\',\'KTW\'),
(\'88\',\'Rettungsboot\')`);
(\'88\',\'Rettungsboot\'),
(\'91\',\'Gerätewagen Dekontamination Personal\')`);
// Benutzer-Tabelle mit Standard-Admin befuellen
bcrypt.hash(app_cfg.global.defaultpass, app_cfg.global.saltRounds, function (err, hash) {
db.run(`INSERT INTO waip_users ( user, password, permissions, ip_address ) VALUES( ?, ?, 'admin', ? )`,
app_cfg.global.defaultuser, hash, app_cfg.global.defaultuserip, function (err) {
app_cfg.global.defaultuser, hash, app_cfg.global.defaultuserip,
function (err) {
if (err) {
console.error(err.message);
};

1282
server/sql_qry.js Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,26 @@
module.exports = function(app_cfg, waip_io, sql) {
module.exports = function (app_cfg, sql, saver) {
// Module laden
var dgram = require('dgram');
var udp_server = dgram.createSocket('udp4');
// Funktion um zu pruefen, ob Nachricht im JSON-Format ist
function isValidJSON(text) {
try {
JSON.parse(text);
return true;
} catch (error) {
return false;
}
};
// UDP-Server für Schnittstelle starten
udp_server.bind(app_cfg.global.udpport);
udp_server.on('listening', function() {
udp_server.on('listening', function () {
var address = udp_server.address();
sql.db_log('Anwendung', 'UDP Server auf ' + address.address + ':' + address.port + ' gestartet.');
});
// Warten auf Einsatzdaten
udp_server.on('message', function(message, remote) {
if (isValidJSON(message)) {
sql.db_log('WAIP', 'Neuer Einsatz von ' + remote.address + ':' + remote.port + ': ' + message);
waip_io.einsatz_speichern(message);
} else {
sql.db_log('Fehler-WAIP', 'Fehler: Einsatz von ' + remote.address + ':' + remote.port + ' Fehlerhaft: ' + message);
}
udp_server.on('message', function (message, remote) {
saver.save_new_waip(message.toString('utf8'), remote.address + ':' + remote.port, 'udp')
});
// UDP-Daten senden
function send_message(message) {
udp_server.send(message, 0, message.length, app_cfg.global.udpport, 'localhost', function(err, bytes) {
udp_server.send(message, 0, message.length, app_cfg.global.udpport, 'localhost', function (err, bytes) {
if (err) throw err;
sql.db_log('UDP-Testalarm an localhost Port ' + app_cfg.global.udpport + ' gesendet.');
//client.close();
sql.db_log('WAIP', 'UDP-Testalarm an localhost:' + app_cfg.global.udpport + ' gesendet.');
});
};

485
server/waip.js Executable file
View File

@ -0,0 +1,485 @@
module.exports = function (io, sql, fs, brk, async, app_cfg) {
// Module laden
const {
parse
} = require('json2csv');
const nodemailer = require('nodemailer');
function waip_speichern(einsatz_daten) {
// Einsatzmeldung in Datenbank speichern und verteilen
sql.db_einsatz_speichern(einsatz_daten, function (waip_id) {
sql.db_log('DEBUG', 'Neuen Einsatz mit der ID ' + waip_id + ' gespeichert.');
// nach dem Speichern anhand der waip_id die beteiligten Wachennummern zum Einsatz ermitteln
sql.db_einsatz_get_rooms(waip_id, function (socket_rooms) {
// socket_rooms muss groesser als 1 sein, da sonst nur der Standard-Raum '0' vorhanden ist
if (socket_rooms.length > 1) {
socket_rooms.forEach(function (rooms) {
// fuer jede Wache(rooms.room) die verbundenen Sockets(Clients) ermitteln und den Einsatz verteilen
var room_sockets = io.nsps['/waip'].adapter.rooms[rooms.room];
if (typeof room_sockets !== 'undefined') {
Object.keys(room_sockets.sockets).forEach(function (socket_id) {
var socket = io.of('/waip').connected[socket_id];
waip_verteilen(waip_id, socket, rooms.room);
sql.db_log('WAIP', 'Einsatz ' + waip_id + ' wird an ' + socket.id + ' (' + rooms.room + ') gesendet');
});
};
});
} else {
// wenn kein Raum (keine Wache) ausser '0' zurueckgeliefert wird, dann Einsatz direkt wieder loeschen weil keine Wachen dazu hinterlegt
sql.db_log('Fehler-WAIP', 'Fehler: Keine Wache für den Einsatz mit der ID ' + waip_id + ' vorhanden! Einsatz wird gelöscht!');
sql.db_einsatz_loeschen(waip_id);
};
});
// pruefen ob für die beteiligten Wachen eine Verteiler-Liste hinterlegt ist, falls ja: Rueckmeldungs-Link senden
sql.db_vmtl_get_list(waip_id, function (list) {
if (list) {
brk.alert_vmtl_list(list, function (result) {
if (!result) {
sql.db_log('VMTL', 'Link zur Einsatz-Rückmeldung erfolgreich an Vermittler-Liste gesendet. ' + result);
} else {
sql.db_log('VMTL', 'Fehler beim senden des Links zur Einsatz-Rueckmeldung an die Vermittler-Liste: ' + result);
};
});
} else {
sql.db_log('VMTL', 'Keine Vermittler-Liste für Wachen im Einsatz ' + waip_id + ' hinterlegt. Rückmeldung wird nicht verteilt.');
};
});
});
};
function waip_verteilen(waip_id, socket, wachen_nr) {
// Einsatzdaten für eine Wache aus Datenbank laden und an Client verteilen
var user_obj = socket.request.user;
sql.db_einsatz_get_by_waipid(waip_id, wachen_nr, user_obj.id, function (einsatzdaten) {
if (einsatzdaten) {
// Berechtigung des Users ueberpruefen
sql.db_user_check_permission(user_obj, waip_id, function (valid) {
// Wenn nutzer nicht angemeldet, Daten entfernen
if (!valid) {
einsatzdaten.objekt = '';
einsatzdaten.besonderheiten = '';
einsatzdaten.strasse = '';
einsatzdaten.wgs84_x = '';
einsatzdaten.wgs84_y = '';
};
// pruefen ob Einsatz bereits genau so beim Client angezeigt wurde (Doppelalarmierung)
sql.db_einsatz_check_history(waip_id, einsatzdaten, socket.id, function (result) {
if (!result) {
// Einsatz an Client senden
socket.emit('io.new_waip', einsatzdaten);
sql.db_log('WAIP', 'Einsatz ' + waip_id + ' fuer Wache ' + wachen_nr + ' an Socket ' + socket.id + ' gesendet.');
sql.db_client_update_status(socket, waip_id);
// Sound erstellen
tts_erstellen(app_cfg, socket.id, einsatzdaten, function (tts) {
if (tts) {
// Sound-Link senden
socket.emit('io.playtts', tts);
sql.db_log('WAIP', 'ttsfile: ' + tts);
};
});
} else {
// Log das Einsatz explizit nicht an Client gesendet wurde
sql.db_log('WAIP', 'Einsatz ' + waip_id + ' fuer Wache ' + wachen_nr + ' nicht an Socket ' + socket.id + ' gesendet, da bereits angezeigt (Doppelalarmierung).');
};
});
});
} else {
// wenn keine Einsatzdaten, dann Standby senden
socket.emit('io.standby', null);
sql.db_log('WAIP', 'Kein Einsatz für Wache ' + wachen_nr + ' vorhanden, Standby an Socket ' + socket.id + ' gesendet.');
sql.db_client_update_status(socket, null);
};
});
};
function rmld_verteilen_by_uuid(waip_uuid, rmld_uuid) {
// Einsatz-ID mittels Einsatz-UUID ermitteln, und Rueckmelung an alle relevanten Clients verteilen
sql.db_einsatz_get_waipid_by_uuid(waip_uuid, function (waip_id) {
// am Einsatz beteilite Socket-Räume ermitteln
sql.db_einsatz_get_rooms(waip_id, function (socket_rooms) {
if (socket_rooms) {
socket_rooms.forEach(function (row) {
// fuer jede Wache(row.room) die verbundenen Sockets(Clients) ermitteln und Standby senden
var room_sockets = io.nsps['/waip'].adapter.rooms[row.room];
if (typeof room_sockets !== 'undefined') {
Object.keys(room_sockets.sockets).forEach(function (socket_id) {
// wenn Raum zum Einsatz aufgerufen ist, dann Rueckmeldung aus DB laden und an diesen versenden
sql.db_rmld_get_by_rmlduuid(rmld_uuid, function (rmld_obj) {
if (rmld_obj) {
// Rückmeldung an Clients/Räume senden, wenn richtiger Einsatz angezeigt wird
sql.db_client_check_waip_id(socket_id, waip_id, function (same_id) {
if (same_id) {
var socket = io.of('/waip').connected[socket_id];
socket.emit('io.new_rmld', rmld_obj);
sql.db_log('RMLD', 'Rückmeldung ' + rmld_uuid + ' für den Einsatz mit der ID ' + waip_id + ' an Wache ' + row.room + ' gesendet.');
sql.db_log('DEBUG', 'Rückmeldung JSON: ' + JSON.stringify(rmld_obj));
};
});
};
});
});
};
});
};
});
// Dashboards ermitteln, welche den Einsatz geladen haben
sql.db_socket_get_dbrd(waip_id, function (dbrd_sockets) {
if (dbrd_sockets) {
// Rueckmeldung auslesen
sql.db_rmld_get_by_rmlduuid(rmld_uuid, function (rmld_obj) {
if (rmld_obj) {
// Rückmeldung an Dashboards senden
dbrd_sockets.forEach(function (row) {
var socket = io.of('/dbrd').connected[row.socket_id];
socket.emit('io.new_rmld', rmld_obj);
sql.db_log('RMLD', 'Rückmeldung ' + rmld_uuid + ' für den Einsatz mit der ID ' + waip_id + ' an Dashboard ' + waip_uuid + ' gesendet.');
sql.db_log('DEBUG', 'Rückmeldung JSON: ' + JSON.stringify(rmld_obj));
});
};
});
};
});
});
};
function rmld_verteilen_for_one_client(waip_id, socket, wachen_id) {
// Rueckmeldung an einen bestimmten Client senden
if (typeof socket.id !== 'undefined') {
sql.db_rmld_get_fuer_wache(waip_id, wachen_id, function (rmld_obj) {
console.log(rmld_obj);
if (rmld_obj) {
// Rueckmeldung nur an den einen Socket senden
socket.emit('io.new_rmld', rmld_obj);
sql.db_log('RMLD', 'Vorhandene Rückmeldungen an Socket ' + socket.id + ' gesendet.');
sql.db_log('DEBUG', 'Rückmeldung JSON: ' + JSON.stringify(rmld_obj));
} else {
sql.db_log('RMLD', 'Keine Rückmeldungen für Einsatz-ID' + waip_id + ' und Wachen-ID ' + wachen_id + ' vorhanden.');
};
});
};
};
function dbrd_verteilen(dbrd_uuid, socket) {
// Einsatzdaten an Dashboard senden
sql.db_einsatz_get_by_uuid(dbrd_uuid, function (einsatzdaten) {
if (einsatzdaten) {
sql.db_user_check_permission(socket.request.user, einsatzdaten.id, function (valid) {
if (!valid) {
delete einsatzdaten.objekt;
delete einsatzdaten.besonderheiten;
delete einsatzdaten.strasse;
delete einsatzdaten.wgs84_x;
delete einsatzdaten.wgs84_y;
};
socket.emit('io.Einsatz', einsatzdaten);
sql.db_log('DBRD', 'Einsatzdaten für Dashboard ' + dbrd_uuid + ' an Socket ' + socket.id + ' gesendet');
sql.db_client_update_status(socket, einsatzdaten.id);
});
// Rueckmeldungen auslesen
rmld_verteilen_for_one_client(einsatzdaten.id, socket, 0);
} else {
// Standby senden
// BUG hier kein standby senden, sonder nicht vorhanden
socket.emit('io.standby', null);
sql.db_log('DBRD', 'Der angefragte Einsatz ' + dbrd_uuid + ' ist nicht - oder nicht mehr - vorhanden!, Standby an Socket ' + socket.id + ' gesendet.');
sql.db_client_update_status(socket, null);
};
});
};
// TODO WAIP: Funktion um Clients remote "neuzustarten" (Seite neu laden), niedrige Prioritaet
function tts_erstellen(app_cfg, socket_id, einsatzdaten, callback) {
// unnoetige Zeichen aus socket_id entfernen, um diese als Dateinamen zu verwenden
var id = socket_id.replace(/\W/g, '');
// Pfade der Sound-Dateien defeinieren
var wav_tts = process.cwd() + app_cfg.global.soundpath + id + '.wav';
var mp3_tmp = process.cwd() + app_cfg.global.soundpath + id + '_tmp.mp3';
var mp3_tts = process.cwd() + app_cfg.global.soundpath + id + '.mp3';
var mp3_url = app_cfg.global.mediapath + id + '.mp3';
// unterscheiden des Alarmgongs nach Einsatzart
if (einsatzdaten.einsatzart == "Brandeinsatz" || einsatzdaten.einsatzart == "Hilfeleistungseinsatz") {
var mp3_bell = process.cwd() + app_cfg.global.soundpath + 'bell_long.mp3';
} else {
var mp3_bell = process.cwd() + app_cfg.global.soundpath + 'bell_short.mp3';
};
// Zusammensetzen der Sprachansage
async.map(JSON.parse(einsatzdaten.em_alarmiert), sql.db_tts_einsatzmittel, function (err, einsatzmittel) {
// Grunddaten
var tts_text = einsatzdaten.einsatzart + ', ' + einsatzdaten.stichwort;
if (einsatzdaten.objekt) {
var tts_text = tts_text + '. ' + einsatzdaten.objekt + ', ' + einsatzdaten.ort + ', ' + einsatzdaten.ortsteil;
} else {
var tts_text = tts_text + '. ' + einsatzdaten.ort + ', ' + einsatzdaten.ortsteil;
};
// Einsatzmittel
tts_text = tts_text + '. Für ' + einsatzmittel.join(", ");
// Unterscheidung nach Sondersignal
if (einsatzdaten.sondersignal == 1) {
tts_text = tts_text + ', mit Sondersignal';
} else {
tts_text = tts_text + ', ohne Sonderrechte';
};
// Abschluss
tts_text = tts_text + '. Ende der Durchsage!';
// ungewollte zeichen aus Sprachansage entfernen
tts_text = tts_text.replace(/:/g, " ");
tts_text = tts_text.replace(/\//g, " ");
tts_text = tts_text.replace(/-/g, " ");
// Sprachansage als mp3 erstellen
switch (process.platform) {
// Windows
case 'win32':
// Powershell
var proc = require('child_process');
var commands = [
// TTS-Schnittstelle von Windows ansprechen
'Add-Type -AssemblyName System.speech;' +
'$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;' +
// Ausgabedatei und Sprachtext
'$speak.SetOutputToWaveFile(\"' + wav_tts + '\");' +
'$speak.Speak(\"' + tts_text + '\");' +
'$speak.Dispose();' +
// speak.wav in mp3 umwandeln
'ffmpeg -nostats -hide_banner -loglevel 0 -y -i ' + wav_tts + ' -vn -ar 44100 -ac 2 -ab 128k -f mp3 ' + mp3_tmp + ';' +
// Gong und Ansage zu einer mp3 zusammensetzen
'ffmpeg -nostats -hide_banner -loglevel 0 -y -i \"concat:' + mp3_bell + '|' + mp3_tmp + '\" -acodec copy ' + mp3_tts + ';' +
'rm ' + wav_tts + ';' +
'rm ' + mp3_tmp + ';'
];
var options = {
shell: true
};
var childD = proc.spawn('powershell', commands);
childD.stdin.setEncoding('ascii');
childD.stderr.setEncoding('ascii');
childD.stderr.on('data', function (data) {
sql.db_log('Fehler-TTS', data);
callback && callback(null);
});
childD.on('exit', function () {
callback && callback(mp3_url);
});
childD.stdin.end();
break;
// LINUX
case 'linux':
// bash
var proc = require('child_process');
var commands = [
// TTS-Schnittstelle SVOX PicoTTS
'-c', `
pico2wave --lang=de-DE --wave=` + wav_tts + ` \"` + tts_text + `\"
ffmpeg -nostats -hide_banner -loglevel 0 -y -i ` + wav_tts + ` -vn -ar 44100 -ac 2 -ab 128k -f mp3 ` + mp3_tmp + `
ffmpeg -nostats -hide_banner -loglevel 0 -y -i \"concat:` + mp3_bell + `|` + mp3_tmp + `\" -acodec copy ` + mp3_tts + `
rm ` + wav_tts + `
rm ` + mp3_tmp
];
var options = {
shell: true
};
if (app_cfg.global.development) {
console.log(commands);
};
var childD = proc.spawn('/bin/sh', commands);
childD.stdin.setEncoding('ascii');
childD.stderr.setEncoding('ascii');
childD.on('exit', function (code, signal) {
if (code > 0) {
sql.db_log('Fehler-TTS', 'Exit-Code ' + code + '; Fehler beim erstellen der TTS-Datei');
callback && callback(null);
} else {
callback && callback(mp3_url);
};
});
childD.stdin.end();
break;
// anderes OS
default:
sql.db_log('Fehler-TTS', 'TTS für dieses Server-Betriebssystem nicht verfügbar!');
callback && callback(null);
};
});
};
setInterval(function () {
// (alle 10 Sekunden)
sql.db_socket_get_all_to_standby(function (socket_ids) {
// alle User-Einstellungen prüfen und ggf. Standby senden
if (socket_ids) {
socket_ids.forEach(function (row) {
var socket = io.of('/waip').connected[row.socket_id];
if (typeof socket !== 'undefined') {
socket.emit('io.standby', null);
socket.emit('io.stopaudio', null);
sql.db_log('WAIP', 'Standby an Socket ' + socket.id + ' gesendet');
sql.db_client_update_status(socket, null);
};
});
};
});
sql.db_einsatz_get_old(app_cfg.global.time_to_delete_waip, function (waip) {
// nach alten Einsaetzen suchen und diese ggf. loeschen
if (waip) {
sql.db_log('WAIP', 'Einsatz mit der ID ' + waip.id + ' ist veraltet und kann gelöscht werden.')
// Dashboards trennen, deren Einsatz geloescht wurde
sql.db_socket_get_dbrd(waip.id, function (socket_ids) {
// TODO TEST: Dashboard-Trennen-Funktion testen
if (socket_ids) {
socket_ids.forEach(function (row) {
var socket = io.of('/dbrd').connected[row.socket_id];
if (typeof socket !== 'undefined') {
socket.emit('io.deleted', null);
sql.db_log('DBRD', 'Dashboard mit dem Socket ' + socket.id + ' getrennt, da Einsatz gelöscht.');
sql.db_client_update_status(socket, null);
};
});
};
});
// beteiligte Wachen zum Einsatz ermitteln
sql.db_einsatz_get_rooms(waip.id, function (data) {
if (data) {
data.forEach(function (row) {
// fuer jede Wache (row.room) die verbundenen Sockets(Clients) ermitteln und Standby senden
var room_sockets = io.nsps['/waip'].adapter.rooms[row.room];
if (typeof room_sockets !== 'undefined') {
Object.keys(room_sockets.sockets).forEach(function (socket_id) {
// Standby senden
var socket = io.of('/waip').connected[socket_id];
sql.db_client_check_waip_id(socket.id, waip.id, function (same_id) {
if (same_id) {
socket.emit('io.standby', null);
socket.emit('io.stopaudio', null);
sql.db_log('WAIP', 'Standby an Socket ' + socket.id + ' gesendet');
sql.db_client_update_status(socket, null);
};
});
});
};
});
};
sql.db_rmld_get_for_export(waip.einsatznummer, waip.uuid, function (full_rmld) {
// beteiligte Wachen aus den Einsatz-Rueckmeldungen filtern
var arry_wachen = full_rmld.map(a => a.wache_nr);
sql.db_export_get_for_rmld(arry_wachen, function (export_data) {
// SQL gibt ist eine Schliefe (db.each), fuer jedes Ergebnis wird eine CSV/Mail erstellt
if (export_data) {
// je Export eine CSV erstellen, die nur die gewuenschten Rueckmeldungen enthaelt
var part_rmld = full_rmld.filter(obj => String(obj.wache_nr).startsWith(String(export_data.export_filter)));
// CSV-Spalten definieren
var csv_col = ['id', 'einsatznummer', 'waip_uuid', 'rmld_uuid', 'alias', 'einsatzkraft', 'maschinist', 'fuehrungskraft', 'agt', 'set_time', 'arrival_time', 'wache_id', 'wache_nr', 'wache_name'];
var opts = {
csv_col
};
try {
var csv = parse(part_rmld, opts);
// CSV Dateiname und Pfad festlegen
var csv_filename = export_data.export_name.replace(/[|&;$%@"<>()+,]/g, '');
csv_filename = csv_filename.replace(/ /g, "_");
csv_filename = 'einsatz_' + part_rmld[0].einsatznummer + '_export_' + csv_filename + '.csv';
csv_path = process.cwd() + app_cfg.rmld.backup_path;
// CSV in Backup-Ordner speichern, falls aktiviert
if (app_cfg.rmld.backup_to_file) {
// Ordner erstellen
fs.mkdir(csv_path, {
recursive: true
}, function (err) {
if (err) {
sql.db_log('EXPORT', 'Fehler beim Erstellen des Backup-Ordners: ' + err);
};
// CSV speichern
fs.writeFile(csv_path + csv_filename, csv, function (err) {
if (err) {
sql.db_log('EXPORT', 'Fehler beim speichern der Export-CSV: ' + err);
};
});
});
};
// CSV per Mail versenden, falls aktiviert
if (app_cfg.rmld.backup_to_mail) {
// pruefen ob Mail plausibel ist
var validmail = /\S+@\S+\.\S+/;
if (validmail.test(export_data.export_recipient)) {
// Mail-Server
var transport = nodemailer.createTransport({
host: app_cfg.rmld.mailserver_host,
port: app_cfg.rmld.mailserver_port,
secure: app_cfg.rmld.secure_mail,
auth: {
user: app_cfg.rmld.mail_user,
pass: app_cfg.rmld.mail_pass
},
tls: {
rejectUnauthorized: app_cfg.rmld.unauthorized_mail
}
});
var mail_message = {
from: 'Wachalarm-IP-Web <' + app_cfg.rmld.mail_from + '>',
to: export_data.export_recipient,
subject: 'Automatischer Export Wachalarm-IP-Web - ' + export_data.export_name + ' - Einsatz ' + part_rmld[0].einsatznummer,
html: 'Hallo,<br><br>anbei der automatische Export aller Einsatz-R&uuml;ckmeldungen f&uuml;r den Einsatz ' + part_rmld[0].einsatznummer + '<br><br>Mit freundlichen Gr&uuml;&szlig;en<br><br>' + app_cfg.public.company + '<br>',
attachments: [{
filename: csv_filename,
content: csv
}]
};
transport.sendMail(mail_message, function (err, info) {
if (err) {
sql.db_log('EXPORT', 'Fehler beim senden der Export-Mail an ' + export_data.export_recipient + ': ' + err);
} else {
sql.db_log('EXPORT', 'Mail an ' + export_data.export_recipient + ' gesendet: ' + JSON.stringify(info));
}
});
} else {
sql.db_log('EXPORT', 'Fehler beim versenden der Export-Mail an ' + export_data.export_recipient + ' - keine richtige Mail-Adresse!');
};
};
} catch (err) {
sql.db_log('EXPORT', 'Fehler beim erstellen der Export-CSV: ' + err);
};
};
});
// alte Rueckmeldungen loeschen
sql.db_rmld_loeschen(waip.uuid);
});
// alten Einsatz loeschen
sql.db_einsatz_loeschen(waip.id);
sql.db_log('WAIP', 'Einsatz-Daten zu Einsatz ' + waip.id + ' gelöscht.');
});
};
});
// loeschen alter Sounddaten nach alter (15min) und socket-id (nicht mehr verbunden)
fs.readdirSync(process.cwd() + app_cfg.global.soundpath).forEach(file => {
// nur die mp3s von alten clients loeschen
if (file.substring(0, 4) != 'bell' && file.substring(file.length - 3) == 'mp3' && file.substring(file.length - 8) != '_tmp.mp3') {
// Socket-ID aus Datei-Namen extrahieren
socket_name = file.substring(0, file.length - 4);
// Socket-ID anpassen, damit die SQL-Abfrage ein Ergebnis liefert
socket_name = socket_name.replace('waip', '/waip#');
sql.db_socket_get_by_id(socket_name, function (data) {
if (!data) {
fs.unlink(process.cwd() + app_cfg.global.soundpath + file, function (err) {
if (err) return sql.db_log('Fehler-WAIP', err);
sql.db_log('WAIP', 'Veraltete Sound-Datei ' + file + ' wurde gelöscht.');
});
};
});
};
});
}, 10000);
return {
waip_speichern: waip_speichern,
waip_verteilen: waip_verteilen,
rmld_verteilen_for_one_client: rmld_verteilen_for_one_client,
rmld_verteilen_by_uuid: rmld_verteilen_by_uuid,
dbrd_verteilen: dbrd_verteilen
};
};

View File

@ -1,322 +0,0 @@
module.exports = function(io, sql, async, app_cfg) {
// Socket.IO
io.on('connection', function(socket) {
sql.db_log('WAIP', 'Anwendung von ' + socket.request.connection.remoteAddress + ' (' + socket.id + ') geoeffnet');
io.sockets.to(socket.id).emit('io.version', app_cfg.global.app_id);
// disconnect
socket.on('disconnect', function() {
sql.db_log('WAIP', 'Alarmmonitor von ' + socket.request.connection.remoteAddress + ' (' + socket.id + ') geschlossen');
sql.db_client_delete(socket.id);
});
// Aufruf des Alarmmonitors einer bestimmten Wache verarbeiten
socket.on('wachen_id', function(wachen_id) {
sql.db_log('WAIP', 'Alarmmonitor Nr. ' + wachen_id + ' von ' + socket.request.connection.remoteAddress + ' (' + socket.id + ') aufgerufen');
// prüfen ob Wachenummer in der Datenbank hinterlegt ist
sql.db_wache_vorhanden(wachen_id,function(result) {
// wenn die Wachennummer vorhanden/plausibel dann weiter
if (result) {
// Socket-Room beitreiten
socket.join(wachen_id, function() {
// Socket-ID und Client-IP in der Datenbank speichern
sql.db_client_save(socket.id, socket.request.connection.remoteAddress, wachen_id);
// prüfen ob für diese Wache ein Einsatz vorhanden ist
sql.db_einsatz_vorhanden(wachen_id, socket.request.user.id, function(result_einsatz) {
if (result_einsatz) {
console.log(result_einsatz[0].waip_einsaetze_ID);
sql.db_log('WAIP', 'Einsatz ' + result_einsatz[0].waip_einsaetze_ID + ' fuer Wache ' + wachen_id + ' vorhanden');
//letzten Einsatz verteilen
einsatz_verteilen(result_einsatz[0].waip_einsaetze_ID, socket.id, wachen_id);
sql.db_update_client_status(socket, result_einsatz[0].waip_einsaetze_ID);
//vorhanden Rückmeldungen verteilen
sql.db_get_response(result_einsatz[0].waip_einsaetze_ID, function(result){
if (result) {
reuckmeldung_verteilen(result_einsatz[0].waip_einsaetze_ID, result);
};
});
} else {
sql.db_log('WAIP', 'Kein Einsatz fuer Wache ' + wachen_id + ' vorhanden, Standby');
//oder falls kein Einsatz vorhanden ist, dann
io.sockets.to(socket.id).emit('io.standby', null);
sql.db_update_client_status(socket, null);
};
});
});
} else {
sql.db_log('Fehler-WAIP', 'Fehler: Wachnnummer ' + wachen_id + 'nicht vorhanden');
io.sockets.to(socket.id).emit('io.error', 'Fehler: Wachnnummer \'' + wachen_id + '\' nicht vorhanden!');
};
});
});
socket.on('response', function(waip_id, ek, ma, fk, agt) {
var i_ek = ek ? 1 : 0;
var i_ma = ma ? 1 : 0;
var i_fk = fk ? 1 : 0;
var i_agt = agt ? 1 : 0;
sql.db_update_response(waip_id, i_ek, i_ma, i_fk, i_agt, function(result){
reuckmeldung_verteilen(waip_id, result);
});
});
// TODO: socket.on(Version) um Server-Version abzugleichen
});
// Einsatzmeldung in Datenbank speichern
function einsatz_speichern(message) {
// Einsatzmeldung (JSON) speichern
sql.db_einsatz_speichern(JSON.parse(message), function(waip_id) {
// nach dem Speichern anhand der waip_id die beteiligten Wachennummern zum Einsatz ermitteln
sql.db_log('WAIP', 'DEBUG: ' + waip_id);
sql.db_get_einsatzwachen(waip_id, function(data) {
if (data) {
data.forEach(function(row) {
// fuer jede Wache(row.room) die verbundenen Sockets(Clients) ermitteln und Einsatz verteilen
var room_stockets = io.sockets.adapter.rooms[row.room];
if (typeof room_stockets !== 'undefined') {
Object.keys(room_stockets.sockets).forEach(function(socketId) {
einsatz_verteilen(waip_id, socketId, row.room);
sql.db_log('WAIP', 'Einsatz ' + waip_id + ' wird an ' + socketId + ' (' + row.room + ') gesendet');
});
};
});
} else {
sql.db_log('Fehler-WAIP', 'Fehler: Wache für waip_id ' + waip_id + ' nicht vorhanden!');
};
});
});
};
// Einsatz an Client verteilen
function einsatz_verteilen(waip_id, socket_id, wachen_nr) {
// Einsatzdaten für eine Wache aus Datenbank laden
sql.db_get_einsatzdaten(waip_id, wachen_nr, io.sockets.sockets[socket_id].request.user.id, function(einsatzdaten) {
if (einsatzdaten) {
// Berechtigung ueberpruefen
var permissions = io.sockets.sockets[socket_id].request.user.permissions;
sql.db_check_permission(permissions, waip_id, function(valid) {
//console.log(permissions + ' ' + wachen_nr);
//if (permissions == wachen_nr || permissions == 'admin') {} else {
if (!valid) {
einsatzdaten.objekt = '';
einsatzdaten.besonderheiten = '';
einsatzdaten.strasse = '';
einsatzdaten.wgs84_x = einsatzdaten.wgs84_x.substring(0, einsatzdaten.wgs84_x.indexOf('.') + 3);
einsatzdaten.wgs84_y = einsatzdaten.wgs84_y.substring(0, einsatzdaten.wgs84_y.indexOf('.') + 3);
};
// Einsatz senden
io.sockets.to(socket_id).emit('io.neuerEinsatz', einsatzdaten)
sql.db_log('WAIP', 'Einsatz ' + waip_id + ' fuer Wache ' + wachen_nr + ' an Socket ' + socket_id + ' gesendet');
sql.db_update_client_status(io.sockets.sockets[socket_id], waip_id);
// Sound erstellen
tts_erstellen(app_cfg, socket_id, einsatzdaten, function(tts) {
// Sound senden
sql.db_log('WAIP', 'ttsfile: ' + tts);
io.sockets.to(socket_id).emit('io.playtts', tts);
});
});
} else {
// Standby senden
io.sockets.to(socket_id).emit('io.standby', null);
sql.db_log('WAIP', 'Kein Einsatz fuer Wache ' + wachen_nr + ' vorhanden, Standby an Socket ' + socket_id + ' gesendet..');
sql.db_update_client_status(io.sockets.sockets[socket_id], null);
};
});
};
function reuckmeldung_verteilen(waip_id, result) {
sql.db_get_einsatzwachen(waip_id, function(data) {
if (data) {
data.forEach(function(row) {
// fuer jede Wache(row.room) die verbundenen Sockets(Clients) ermitteln und Einsatz verteilen
var room_stockets = io.sockets.adapter.rooms[row.room];
if (typeof room_stockets !== 'undefined') {
Object.keys(room_stockets.sockets).forEach(function(socket_id) {
io.sockets.to(socket_id).emit('io.response', result)
sql.db_log('WAIP', 'Rückmeldung ' + result + ' an Socket ' + socket_id + ' gesendet');
});
};
});
} else {
sql.db_log('Fehler-WAIP', 'Fehler: Wache für waip_id ' + waip_id + ' nicht vorhanden, Rückmeldung konnte nicht verteilt werden!');
};
});
};
function tts_erstellen(app_cfg, socket_id, einsatzdaten, callback) {
// unnoetige Zeichen aus socket_id entfernen
var id = socket_id.replace(/\W/g, '');
// Pfade der Sound-Dateien defeinieren
var wav_tts = process.cwd() + app_cfg.global.soundpath + id + '.wav';
var mp3_tmp = process.cwd() + app_cfg.global.soundpath + id + '_tmp.mp3';
var mp3_tts = process.cwd() + app_cfg.global.soundpath + id + '.mp3';
var mp3_url = app_cfg.global.mediapath + id + '.mp3';
// Unterscheiden des Alarmgongs nach Einsatzart
if (einsatzdaten.einsatzart == "Brandeinsatz" || einsatzdaten.einsatzart == "Hilfeleistungseinsatz") {
var mp3_bell = process.cwd() + app_cfg.global.soundpath + 'bell_long.mp3';
} else {
var mp3_bell = process.cwd() + app_cfg.global.soundpath + 'bell_short.mp3';
};
// Zusammensetzen der Sprachansage
async.map(JSON.parse(einsatzdaten.em_alarmiert), sql.db_tts_einsatzmittel, function(err, einsatzmittel) {
// Grunddaten
var tts_text = einsatzdaten.einsatzart + ', ' + einsatzdaten.stichwort;
if (einsatzdaten.objekt) {
var tts_text = tts_text + '. ' + einsatzdaten.objekt + ', ' + einsatzdaten.ort + ', ' + einsatzdaten.ortsteil;
} else {
var tts_text = tts_text + '. ' + einsatzdaten.ort + ', ' + einsatzdaten.ortsteil;
};
// Einsatzmittel
tts_text = tts_text + '. Für ' + einsatzmittel.join(", ");
// Unterscheidung nach Sondersignal
if (einsatzdaten.sondersignal == 1) {
tts_text = tts_text + ', mit Sondersignal';
} else {
tts_text = tts_text + ', ohne Sonderrechte';
};
// Abschluss
tts_text = tts_text + '. Ende der Durchsage!';
// ungewollte zeichen aus Sprachansage entfernen
tts_text = tts_text.replace(/:/g, " ");
tts_text = tts_text.replace(/\//g, " ");
tts_text = tts_text.replace(/-/g, " ");
// Sprachansage als mp3 erstellen
switch (process.platform) {
//if (process.platform === "win32") {
case 'win32':
// Powershell
var proc = require('child_process');
var commands = [
// TTS-Schnittstelle von Windows
'Add-Type -AssemblyName System.speech;' +
'$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;' +
// Ausgabedatei und Sprachtext
'$speak.SetOutputToWaveFile(\"' + wav_tts + '\");' +
'$speak.Speak(\"' + tts_text + '\");' +
'$speak.Dispose();' +
// speak.wav in mp3 umwandeln
'ffmpeg -nostats -hide_banner -loglevel 0 -y -i ' + wav_tts + ' -vn -ar 44100 -ac 2 -ab 128k -f mp3 ' + mp3_tmp + ';' +
// Gong und Ansage zu einer mp3 zusammensetzen
'ffmpeg -nostats -hide_banner -loglevel 0 -y -i \"concat:' + mp3_bell + '|' + mp3_tmp + '\" -acodec copy ' + mp3_tts + ';' +
'rm ' + wav_tts + ';' +
'rm ' + mp3_tmp + ';'
];
var options = {
shell: true
};
var childD = proc.spawn('powershell', commands);
childD.stdin.setEncoding('ascii');
childD.stderr.setEncoding('ascii');
childD.stderr.on('data', function(data) {
sql.db_log('Fehler-TTS', data);
callback && callback(null);
});
childD.on('exit', function() {
callback && callback(mp3_url);
});
childD.stdin.end();
break;
case 'linux':
// bash
var proc = require('child_process');
var commands = [
// TTS-Schnittstelle SVOX PicoTTS
'-c', `
pico2wave --lang=de-DE --wave=` + wav_tts + ` \"` + tts_text + `\"
ffmpeg -nostats -hide_banner -loglevel 0 -y -i ` + wav_tts + ` -vn -ar 44100 -ac 2 -ab 128k -f mp3 ` + mp3_tmp + `
ffmpeg -nostats -hide_banner -loglevel 0 -y -i \"concat:` + mp3_bell + `|` + mp3_tmp + `\" -acodec copy ` + mp3_tts + `
rm ` + wav_tts + `
rm ` + mp3_tmp
];
var options = {
shell: true
};
console.log(commands);
var childD = proc.spawn('/bin/sh', commands);
childD.stdin.setEncoding('ascii');
childD.stderr.setEncoding('ascii');
childD.stderr.on('data', function(data) {
sql.db_log('Fehler-TTS', data);
callback && callback(null);
});
childD.on('exit', function() {
callback && callback(mp3_url);
});
childD.stdin.end();
break;
// } else {
default:
sql.db_log('Fehler-TTS', 'TTS für dieses Server-Betriebssystem nicht verfügbar');
callback && callback(null);
};
});
};
// Aufräumen (alle 10 Sekunden)
setInterval(function() {
// alle User-Einstellungen prüfen und ggf. Standby senden
sql.db_get_sockets_to_standby(function(socket_ids){
if (socket_ids) {
socket_ids.forEach(function(row) {
io.sockets.to(row.socket_id).emit('io.standby', null);
io.sockets.to(row.socket_id).emit('io.stopaudio', null);
sql.db_log('WAIP', 'Standby an Socket ' + row.socket_id + ' gesendet');
sql.db_update_client_status(io.sockets.sockets[row.socket_id], null);
});
};
});
// Nach alten Einsaetzen suchen und diese ggf. loeschen
sql.db_get_alte_einsaetze(app_cfg.global.time_to_delete_waip, function(waip_id) {
if (waip_id) {
sql.db_log('WAIP', 'Einsatz mit der ID ' + waip_id + ' ist veraltet und kann gelöscht werden.')
//beteiligte Wachen ermitteln
sql.db_get_einsatzwachen(waip_id, function(data) {
if (data) {
data.forEach(function(row) {
// fuer jede Wache(row.room) die verbundenen Sockets(Clients) ermitteln und Standby senden
var room_stockets = io.sockets.adapter.rooms[row.room];
if (typeof room_stockets !== 'undefined') {
Object.keys(room_stockets.sockets).forEach(function(socketId) {
// Standby senden
// TODO: Standby nur senden, wenn kein anderer (als der zu löschende) Einsatz angezeigt wird
sql.db_check_client_waipid(socketId, waip_id, function(same_id) {
if (same_id) {
io.sockets.to(socketId).emit('io.standby', null);
io.sockets.to(socketId).emit('io.stopaudio', null);
sql.db_log('WAIP', 'Standby an Socket ' + socketId + ' gesendet');
sql.db_update_client_status(io.sockets.sockets[socketId], null);
};
});
});
};
});
};
// Einsatz löschen
sql.db_log('WAIP', 'Einsatz ' + waip_id + ' wird gelöscht');
sql.db_einsatz_loeschen(waip_id);
});
};
});
// TODO: löschen alter Sounddaten nach alter (15min) und socket-id (nicht mehr verbunden)
// TODO: Löschen alter Einstze nach 24 h
// alte mp3s loeschen
const fs = require('fs');
fs.readdirSync(process.cwd() + app_cfg.global.soundpath).forEach(file => {
// nur die mp3s von alten clients loeschen
if (file.substring(0, 4) != 'bell' && file.substring(file.length - 3) == 'mp3' && file.substring(file.length - 8) != '_tmp.mp3') {
sql.db_get_socket_by_id(file.substring(0, file.length - 4), function(data) {
if (!data) {
fs.unlink(process.cwd() + app_cfg.global.soundpath + file, function(err) {
if (err) return sql.db_log('Fehler-WAIP', err);
sql.db_log('WAIP', file + ' wurde erfolgreich geloescht');
});
};
});
};
})
}, 10000);
// TODO: Funktion um Clients "neuzustarten" (Seite remote neu laden)
return {
einsatz_speichern: einsatz_speichern
};
};

19
views/about.pug Normal file
View File

@ -0,0 +1,19 @@
extends layout
block content
main(role='main')
.container
.row
.col
.jumbotron.text-center
h1.display-1.text-danger.font-weight-bold.d-none.d-lg-block= public.app_name
h1.text-danger.font-weight-bold.d-lg-none= public.app_name
hr.my-4
.lead
h4.text-info Entwickelt von Robert Richter
p Alle Rechte vorbehalten
br
p.text-muted
| mehr Informationen unter
a(href="https://github.com/Robert-112/Wachalarm-IP-Web") https://github.com/Robert-112/Wachalarm-IP-Web
.display-4.text-muted.ion-logo-github

View File

@ -1,7 +1,4 @@
extends layout
append head
link(rel='stylesheet', href='/css/ionicons.min.css')
extends ../layout
block content
main(role='main')
@ -42,7 +39,7 @@ block content
p='IP-Adresse: ' + val.ip_address
.card-footer.text-right
button.btn.btn-primary.mx-2.ion-md-create(onclick='EditUser(this);', data-id=val.id, data-user=val.user, data-permissions=val.permissions, data-ip_address=val.ip_address) Bearbeiten
form(action='/edit_users', method='POST', style='display:inline-block;')
form(action='/adm_edit_users', method='POST', style='display:inline-block;')
input(type='hidden', name='modal_method', value='DELETE')
input(type='hidden', name='id', value=val.id)
input(type='hidden', name='username', value=val.user)
@ -56,7 +53,7 @@ block content
button.close(type='button', data-dismiss='modal', aria-label='Close')
span(aria-hidden='true') ×
.modal-body
form#formModal(action="/edit_users", method="POST", oninput='password2.setCustomValidity(password2.value != password.value ? "Passwörter stimmen nicht überein!" : "")')
form#formModal(action="/adm_edit_users", method="POST", oninput='password2.setCustomValidity(password2.value != password.value ? "Passwörter stimmen nicht überein!" : "")')
.form-group
input#modal_method(type='hidden', name='modal_method', value='')
input#modal_id(type='hidden', name='modal_id', value='')

View File

@ -1,7 +1,4 @@
extends layout
append head
link(rel='stylesheet', href='/css/ionicons.min.css')
extends ../layout
block content
main(role='main')
@ -19,7 +16,7 @@ block content
.card.border-dark.mb-3.w-100
.card-header Test-Alarm
.card-body.text-dark
form#test_alert1(action="/test_alert", method="POST")
form#test_alert1(action="/adm_run_alert", method="POST")
.form-group
label(for='text_test_alert1') Alarmtext
textarea#text_test_alert1.form-control(rows='5' type='text', name='test_alert') {"einsatzdaten":{"nummer":"0815","alarmzeit":"01.01.19&01:00","art":"Sonstiges","stichwort":"S:Testeinsatz","sondersignal":1,"besonderheiten":"DEMO Wachalarm-IP-Web - Testeinsatz","patient":""},"ortsdaten":{"ort":"Elsterwerda","ortsteil":"Biehla","strasse":"Haidaer Str. 47A","objekt":"","objektnr":"-1","objektart":"","wachfolge":"611302","wgs84_x":"52.471244","wgs84_y":"13.502943"},"alarmdaten":[{"typ":"ALARM","netzadresse":"","wachenname":"EE FW Elsterwerda","einsatzmittel":"FL EE 02/14-01","zeit_a":"18:41","zeit_b":"","zeit_c":""}]}

View File

@ -1,10 +1,14 @@
extends layout
extends ../layout
append head
link(rel='stylesheet', href='/css/datatables.min.css')
script(src='/js/datatables.min.js')
script(src='/js/dataTables.bootstrap4.min.js')
// TODO: Seite mit aktiven Clients anpassen:
//  - nicht zwingend als Tabelle, sondern eher als .col mit Buttons um Aktionen an Clients zu senden
//  - einzelnen Client über Verwaltungsoberfläche neu laden lassen
block content
main(role='main')
.container

View File

@ -1,4 +1,4 @@
extends layout
extends ../layout
append head
link(rel='stylesheet', href='/css/datatables.min.css')

View File

@ -1,4 +1,4 @@
extends layout
extends ../layout
block content
main(role='main')
@ -10,6 +10,7 @@ block content
ol
each val in dataSet
li= val.einsatzart + ', ' + val.stichwort + ', ' + val.ort + ' ' + val.ortsteil
a(href="/rmld/" + val.uuid)= val.uuid
ul
if val.a
each val_a in (val.a).split(",")

17
views/dbrd.pug Executable file
View File

@ -0,0 +1,17 @@
extends layout
append head
link(rel='stylesheet', href='/css/leaflet.css')
block content
include includes/modal_info
.container-fluid
include includes/master_dashboard
script(src='/socket.io/socket.io.js')
script(src='/js/leaflet.js')
script.
dbrd_uuid='#{dbrd_uuid}'
map_tile='#{map_tile}'
map_attribution='!{map_attribution}'
client_id="#{app_id}"
script(src='/js/client_dbrd.js')

View File

@ -4,20 +4,11 @@ block content
main(role='main')
.container
.row
.col-12
.card.border-warning
.card-header
h1= error.status
.card-body
.card-title
h5.ion-md-bug= " Fehler - " +message
pre.text-monospace
|
| __/\\\\\\\\\\\\\\\____/\\\\\\\\\________/\\\\\\\\\___________/\\\\\_________/\\\\\\\\\_____
| _\/\\\///////////___/\\\///////\\\____/\\\///////\\\_______/\\\///\\\_____/\\\///////\\\___
| _\/\\\_____________\/\\\_____\/\\\___\/\\\_____\/\\\_____/\\\/__\///\\\__\/\\\_____\/\\\___
| _\/\\\\\\\\\\\_____\/\\\\\\\\\\\/____\/\\\\\\\\\\\/_____/\\\______\//\\\_\/\\\\\\\\\\\/____
| _\/\\\///////______\/\\\//////\\\____\/\\\//////\\\____\/\\\_______\/\\\_\/\\\//////\\\____
| _\/\\\_____________\/\\\____\//\\\___\/\\\____\//\\\___\//\\\______/\\\__\/\\\____\//\\\___
| _\/\\\_____________\/\\\_____\//\\\__\/\\\_____\//\\\___\///\\\__/\\\____\/\\\_____\//\\\__
| _\/\\\\\\\\\\\\\\\_\/\\\______\//\\\_\/\\\______\//\\\____\///\\\\\/_____\/\\\______\//\\\_
.col
.jumbotron.text-center
h1.display-1.font-weight-bold= error.status
p.text-muted Es ist ein Fehler aufgetreten
hr.my-4
.lead
h4.text-danger= message
.display-4.text-muted.ion-ios-bug

View File

@ -1,16 +0,0 @@
extends layout
block content
main(role='main')
.container.h-100
.row.justify-content-center.h-100.d-flex
.col-md-8
.card.text-center
h1.card-header Wachalarm-IP-Web
.card-body
p Entwickelt von Robert Richter
p Alle Rechte vorbehalten
br
p
| mehr Informationen unter
a(href="https://github.com/Robert-112/Wachalarm-IP-Web") https://github.com/Robert-112/Wachalarm-IP-Web

View File

@ -4,66 +4,62 @@ block content
main(role='main')
.jumbotron
.container
h1.text-danger.display-3 Wachalarm-IP-Web
p Dieser Webdienst zeigt Wachalarme im Vollbild an (inkl. synthetischer Sprachdurchsage und Karte).
p Nachfolgend können Sie aus einer der aufgef&uuml;hrten Wachen ausw&auml;hlen. Entsprechend Ihrer Berechtigungen werden Ihnen auf dem Alarmmonitor alle oder nur ausgew&auml;hlte Einsatzdaten angezeigt.
h1.text-danger.display-4= public.app_name
h5.text-muted &copy; #{public.company}
hr
a Dieser Webdienst zeigt Wachalarme im Vollbild an (inkl. synthetischer Sprachdurchsage und Karte).
br
a Zusätzlich besteht für Einsatzkräfte die Möglichkeit zur einfachen Rückmeldung.
.container
.row
.col-md-12
.card
.col-12
.border-top.mx-3.mb-3
.col-12.col-md-4.d-flex.align-self-stretch.p-3
.card.w-100.h-100
.card-header.text-center
.display-2.ion-md-desktop
h4.text-danger Alarmmonitor
.card-body
p Zeigt Wachalarme inkl. eingehender Rückmeldungen im Vollbild an. Es können verschiedene Monitore ausgewählt werden.
.card-footer.text-center
a.btn.btn-outline-info.ion-md-arrow-round-forward(href='/waip/', role='button') Alarmmonitor aufrufen
.col-12.col-md-4.d-flex.align-self-stretch.p-3
.card.w-100.h-100.bg-dark
.card-header.text-center
.display-2.ion-md-clipboard
h4.text-danger Dashboard
.card-body
p Zeigt eine Gesamtübersicht pro Einsatz an und bietet detailierte Informationen über die alarmierten Kräfte inkl. deren Rückmeldungen.
.card-footer.text-center
a.btn.btn-outline-info.ion-md-arrow-round-forward(href='/dbrd/', role='button') Dashboard aufrufen
.col-12.col-md-4.d-flex.align-self-stretch.p-3
.card.w-100.h-100
.card-header.text-center
.display-2.ion-md-paper-plane
h4.text-warning Rückmeldung
.card-body
p Mithilfe der Rückmeldefunktion können Einsatzkräfte mitteilen ob und in welcher Funktion Sie am Einsatz teilnehmen.
p.text-muted Details zur Funktion erhalten Sie auf Anfrage bei der #{public.company}.
.col-12
.border-top.m-3
.col-12.p-3
if !user
.card.border-warning
.card-header
h4.card-title.text-warning Sie sind nicht eingeloggt!
h4.card-title.text-warning Sie sind nicht angemeldet
.card-body
p.card-text.text-muted Bitte melden Sie sich #[a(href="/login") hier] an.
p Auch ohne Anmeldung haben Sie Zugriff auf alle Funktionen, jedoch werden datenschutzrelevante Inhalte ausgeblendet.
p.text-muted Um mehr Funktionen nutzen zu können, melden Sie sich bitte #[a(href="/login") hier] an, sofern Sie über Zugangsdaten verfügen.
else
.card.bg-primary
.card-header
h4.card-title.text-info='Sie sind als Nutzer \''+user.user+'\' angemeldet.'
h4.card-title.text-info='Sie sind als Nutzer \''+user.user+'\' angemeldet'
.card-body
p.card-text Entsprechend Ihrer Berechtigungen haben Zugriff auf folgende Wachalarme:
p Entsprechend Ihrer Berechtigungen haben Sie vollen Zugriff auf folgende Wachalarme:
ul
each val in user.permissions.split(',')
li
if user.permissions == 'admin'
a.text-muted Sie sind Administrator und haben somit vollständigen Zugriff auf #[a(href="/waip/0") alle] Wachalarme
a Sie sind Administrator und haben somit vollst&auml;ndigen Zugriff auf #[a(href="/waip/0") alle] Wachalarme
else
a(href="/waip/" + val)= val
.col-md-4.p-3
.card.h-100
.card-header
h1.text-info Wache
.card-body
p.text-muted Zeigt den Wachalarm einer der einzelnen Wache (z.B. Feuerwach, Rettungswache etc.) an.
.card-footer.text-right
.dropdown
button.btn.btn-info.dropdown-toggle(type='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| bitte ausw&auml;hlen
.dropdown-menu
each item in list_wache
a.dropdown-item(href='/waip/'+ item.nr)= item.name
.col-md-4.p-3
.card.h-100
.card-header
h1.text-info Tr&auml;ger
.card-body
p.text-muted Zeigt alle Wachalarme der Wachen eines Tr&auml;gers (Amt, amtsfreie Gemeinde, Stadt) an.
.card-footer.text-right
.dropdown
button.btn.btn-info.dropdown-toggle(type='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| bitte ausw&auml;hlen
.dropdown-menu
each item in list_traeger
a.dropdown-item(href='/waip/'+ item.nr)= item.name
.col-md-4.p-3
.card.h-100
.card-header
h1.text-info Kreis
.card-body
p.text-muted Zeigt alle Wachalarme des gesamten Kreises (egal ob f&uuml; Feuerwehr oder Rettungsdienst) an.
.card-footer.text-right
.dropdown
button.btn.btn-info.dropdown-toggle(type='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| bitte ausw&auml;hlen
.dropdown-menu
each item in list_kreis
a.dropdown-item(href='/waip/'+ item.nr)= item.name

40
views/imprint.pug Executable file
View File

@ -0,0 +1,40 @@
extends layout
block content
main(role='main')
.container
.row
.col
h1.display-1 Impressum
h2 Angaben gemäß § 5 TMG
p
| Robert Richter
br
| Am Mühlenteich 7
br
| 03099 Kolkwitz
br
h2 Kontakt
p
| Telefon: null eins fünf eins eins zwei vier fünf null acht vier sieben
br
| E-Mail: robertrichter87 [at] web.de
hr
h3 Haftung für Inhalte
p
| Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.
p
| Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.
h3 Haftung für Links
p
| Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar.
p
| Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.
h3 Urheberrecht
p
| Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.
p
| Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
p.text-muted
| Quelle:
a(href='https://www.e-recht24.de/impressum-generator.html') https://www.e-recht24.de/impressum-generator.html

View File

@ -1,3 +1,5 @@
footer.footer
.container-fluid
span.text-muted &copy; Leitstelle Lausitz - #{new Date().getFullYear()}
.d-flex.justify-content-between
.span.text-muted.text-left &copy; #{public.company} - #{new Date().getFullYear()}
.span.text-muted.text-right= public.version

View File

@ -1,36 +1,44 @@
header
// Fixed navbar
nav.navbar.navbar-expand-md.navbar-dark.bg-primary.fixed-top
a.navbar-brand(href='/') Wachalarm
nav.navbar.navbar-expand-lg.navbar-dark.bg-primary.fixed-top
a.navbar-brand(href='/')= public.app_name
button.navbar-toggler(type='button', data-toggle='collapse', data-target='#navbarCollapse', aria-controls='navbarColor01', aria-expanded='false', aria-label='Toggle navigation')
span.navbar-toggler-icon
#navbarCollapse.collapse.navbar-collapse
ul.navbar-nav.mr-auto
li.nav-item(class=(title == 'Startseite') ? 'active' : null)
a.nav-link.ion-md-home(href='/') Start
a.text-nowrap.nav-link.ion-md-home(href='/') &nbsp;Startseite
li.nav-item(class=(title == 'Alarmmonitor') ? 'active' : null)
a.nav-link.ion-md-desktop(href='/waip') Alarmmonitor
li.nav-item(class=(title == 'Hilfe') ? 'active' : null)
a.nav-link.ion-md-help-buoy(href='/help') Hilfe
a.text-nowrap.nav-link.ion-md-desktop(href='/waip') &nbsp;Alarmmonitor
li.nav-item(class=(title == 'Dashboard') ? 'active' : null)
a.text-nowrap.nav-link.ion-md-clipboard(href='/dbrd') &nbsp;Dashboard
ul.navbar-nav.ml-auto
li.text-nowrap.nav-item.dropdown
a#navbarWeiteresDropdown.text-nowrap.nav-link.dropdown-toggle.ion-md-information-circle-outline(href='#', role='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| &nbsp;Info
.dropdown-menu(aria-labelledby='navbarWeiteresDropdown')
a.text-nowrap.dropdown-item(href='/impressum') &nbsp;Impressum
a.text-nowrap.dropdown-item(href='/datenschutz') &nbsp;Datenschutzerkl&auml;ung
.dropdown-divider
a.text-nowrap.dropdown-item(href='/about') &nbsp;&Uuml;ber diese Anwendung
if user
if user.permissions == 'admin'
li.nav-item.dropdown
a#navbarDropdown.nav-link.dropdown-toggle(href='#', role='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| Administration
.dropdown-menu(aria-labelledby='navbarDropdown')
a.dropdown-item.ion-md-contacts(href='/edit_users') Benutzer und Rechte verwalten
li.text-nowrap.nav-item.dropdown
a#navbarAdminDropdown.text-nowrap.nav-link.dropdown-toggle.ion-md-build(href='#', role='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| &nbsp;Administration
.dropdown-menu(aria-labelledby='navbarAdminDropdown')
a.text-nowrap.dropdown-item.ion-md-contacts(href='/adm_edit_users') &nbsp;Benutzer und Rechte verwalten
.dropdown-divider
a.dropdown-item(href='/show_active_user') Verbundene PCs/Benutzer
a.dropdown-item(href='/show_active_waip') laufende Einsätze
a.text-nowrap.dropdown-item.ion-md-globe(href='/adm_show_clients') &nbsp;Verbundene PCs und Benutzer anzeigen
a.text-nowrap.dropdown-item.ion-md-list(href='/adm_show_missions') &nbsp;laufende Eins&auml;tze anzeigen
.dropdown-divider
a.dropdown-item.text-danger.ion-md-warning(href='/test_alert') Test-Alarm
a.dropdown-item.ion-md-document(href='/show_log') Log-Datei
a.text-nowrap.dropdown-item.text-danger.ion-md-warning(href='/adm_run_alert') &nbsp;Test-Alarm versenden
a.text-nowrap.dropdown-item.ion-md-book(href='/adm_show_log') &nbsp;Log-Datei einsehen
li.nav-item.pr-3(class=(title == 'Einstellungen') ? 'active' : null)
a.ion-md-settings.nav-link(href='/config') Einstellungen
a.text-nowrap.ion-md-settings.nav-link(href='/config') &nbsp;Einstellungen
li.nav-item
form(action='/logout', method='POST')
button.btn.btn-outline-warning.ion-md-log-out(type='submit')=' \''+user.user +'\' abmelden'
button.btn.btn-dark.text-nowrap.ion-md-log-out(type='submit')=' \''+user.user +'\' abmelden'
else
li.nav-item(class=(title == 'Login') ? 'active' : null)
a.nav-link.ion-md-log-in(href='/login') Anmelden
a.text-nowrap.nav-link.ion-md-log-in(href='/login') Anmelden

View File

@ -0,0 +1,325 @@
script(type='text/javascript', src='/js/vis-timeline.min.js')
link(rel='stylesheet', href='/css/vis-timeline.css')
style(type='text/css').
#visualization {
width: 100%;
height: 100%;
/*border: 1px solid lightgray;*/
}
.vis-item.ek {
background-color: #00bc8c;
border-color: #444;
}
.vis-item.ma {
background-color: #3498DB;
border-color: #444;
}
.vis-item.fk {
background-color: #adb5bd;
border-color: #444;
}
.vis-item.ek-agt {
background-color: #00bc8c;
border-color: #F39C12;
border-width: 2px;
}
.vis-item.ma-agt {
background-color: #3498DB;
border-color: #F39C12;
border-width: 2px;
}
.vis-item.fk-agt {
background-color: #adb5bd;
border-color: #F39C12;
border-width: 2px;
}
.vis-item, .vis-inner, .vis-time-axis .vis-text {
color: #fff;
}
.vis-timeline {
/*border: 1px solid #444;*/
background: #303030;
}
.vis-time-axis .vis-grid.vis-minor {
border-color: #444;
}
.vis-current-time {
background: #E74C3C;
}
.vis-custom-time {
background: #3498DB;
}
/*.vis-custom-time > .vis-custom-time-marker*/
.vis-custom-time-marker {
right: 2px;
}
.vis-rolling-mode-btn:before {
content: "\2B8C";
}
.row
.col-12.d-flex.justify-content-between.flex-wrap
.d-flex.justify-content-start
.text-muted Alarmierung:&nbsp;
#einsatz_datum.text-muted.text-right &nbsp; 2345
.d-flex.justify-content-end
#dbrd_id.text-secondary 2341234345453
.row
//.col-12.text-center.p-3
h2.rounded.bg-danger Einsatzübersicht
.col-lg-4.col-12
.row.no-gutters
.col-10.mt-2
#einsatz_art.align-items-center.font-weight-bold.rounded.bg-secondary.p-3.mr-2
#einsatz_stichwort.ion-md-apps -Stichwort-
.col-2.mt-2
.align-items-center.justify-content-center.rounded.bg-secondary.text-info.p-3.text-center
#sondersignal.ion-md-apps
div.border-top.m-3
.row
.col-lg-12.col-md-6.col-12
.card.mt-2
.card-body.p-0
#map.rounded(style={height: '30em'})
div.border-top.m-3.d-md-none.d-lg-block
.col-lg-12.col-md-6.col-12
.card.mt-2
.card-header.bg-dark.px-3
.text-info.font-weight-bold Einsatzort
.card-body.p-0
ul#einsatzort_list.list-group.list-group-flush
li.list-group-item -Objekt-
li.list-group-item -Ort-
li.list-group-item -Orststeil-
li.list-group-item -Straße Hsnr-
li.list-group-item.text-warning -Besonderheiten-
div.border-top.mx-3.mt-3.mb-2.d-block.d-lg-none
//.col-12
div.border-top.my-3
.card.bg-secondary.mt-2
.card-header.bg-light.p-2
//h5.text-info.font-weight-bold CB FW Cottbus 1
div.border-top.my-3.border-warning
.card-body.p-1
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 00/00-00
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/00-00
div.p-2.badge.badge-warning 3
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB T-Dienst
div.p-2.badge.badge-danger 4
.card.bg-secondary.mt-2
.card-header.bg-light.p-2
h5.text-info.font-weight-bold CB FW Madlow
.card-body.p-1
p Alarmierte Einsatzmittel
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 12/00-00
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL SPN 00/00-00
div.p-2.badge.badge-info 1
.card.bg-secondary.mt-2
.card-header.bg-light.p-2
h5.text-info.font-weight-bold CB FW Cottbus 3
.card-body.p-1
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 KAT CB 00/00-00
div.p-2.badge.badge-success 2
//table.table.table-striped
tbody
tr
th
div.d-flex.justify-content-between.align-items-center
div FL CB 00/00-00
div.badge.badge-success 2
th
div.d-flex.justify-content-between.align-items-center
div FL CB 12/00-00
div.badge.badge-warning 3
tr
th
div.d-flex.justify-content-between.align-items-center
div FL CB T-Dienst
div.badge.badge-danger 4
th
div.d-flex.justify-content-between.align-items-center
div KAT CB 00/00-00
div.badge.badge-success 2
tr
th
div.d-flex.justify-content-between.align-items-center
div FL SPN 00/00-00
div.badge.badge-info 1
.col-lg-8.col-12
.row.no-gutters
.col-12
//.card.bg-dark.mt-2
.card-header.bg-secondary.p-2
h5.text-danger.text-center Alarmierte Einsatzmittel
.card-body.p-1
.card.mt-2
table#table_einsatzmittel.table.table-hover
thead.table-borderless
tr
th(scope='col').text-info.pl-3 Wache
th(scope='col').text-info.pl-3 Einsatzmittel
tbody
tr
th(scope='row') CB FW Cottbus 1
td
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/82-01
div.p-2.badge.badge-warning 3
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/71-01
div.p-2.badge.badge-danger 4
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/85-01
div.p-2.badge.badge-info 1
tr
th(scope='row') CB FW Madlow
td
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 11/23-01
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 12/46-01
div.p-2.badge.badge-warning 3
//.card.bg-dark.mt-2
.card-header.bg-secondary.p-2
h5.text-danger.text-center Alarmierte Einsatzmittel
.card-body.p-1
.row
.col-3
div.border-right CB FW Cottbus 1
.col-9
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/82-01
div.p-2.badge.badge-warning 3
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/71-01
div.p-2.badge.badge-danger 4
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/85-01
div.p-2.badge.badge-info 2
.card-body.p-1
.row
.col-3
CB FW Madlow
.col-9
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 11/23-01
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-secondary.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 12/46-01
div.p-2.badge.badge-warning 3
div.border-top.m-3
.card
.card-header.bg-dark.px-3
.text-info.font-weight-bold Einsatzrückmeldungen
.card-body.p-2#rmld_container
.d-flex.fustify-content-between.font-weight-bold
.list-group.list-group-horizontal.text-center.w-100
.list-group-item.flex-fill.text-success
a#ek-counter 0
a &nbsp;EK
.list-group-item.flex-fill.text-info
a#ma-counter 0
a &nbsp;MA
.list-group-item.flex-fill.text-light
a#fk-counter 0
a &nbsp;FK
.list-group.text-center
.list-group-item.border.border-warning.flex-fill.text-warning
a#agt-counter 0
a &nbsp;AGT
//a.badge.badge-warning 2&nbsp;AGT
.row
.col-4#pg-ek.pr-1
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-success(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100')
a(style='') 2min
//.progress.mt-1.border.border-warning(style='height: 20px;')
.progress-bar.progress-bar-striped.bg-success(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100')
h 2min
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-success.ion-md-checkmark-circle(role='progressbar', style='width: 100%', aria-valuenow='100', aria-valuemin='0', aria-valuemax='100')
.col-4#pg-ma.px-1
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-info(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100') 3min
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-info(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100') 1min
.col-4#pg-fk.pl-1
//.progress.mt-1.border.border-warning
.progress-bar.progress-bar-striped.bg-light(role='progressbar', style='width: 50%', aria-valuenow='50', aria-valuemin='0', aria-valuemax='100') 10min
.card-body.p-2
#visualization.border.rounded(style='background-color:white')
//div.border-top.m-3
//.card.bg-dark.mt-2
.card-header.bg-secondary.p-2
h5.text-danger.text-center CB FW Madlow
//.card-body.p-2
#visualization2.border.rounded(style='background-color:white')
//.card-body.p-2
.row
.col-10
#rueckmeldung.list-group.list-group-horizontal.text-center
a.list-group-item.list-group-item-warning.flex-fill.text-info 1 EK
a.list-group-item.list-group-item-light.flex-fill.text-danger 1 MA
a.list-group-item.list-group-item-light.flex-fill 0 FK
.col-2
a.bg-warning.rounded.text-dark 0 AGT
//.col-12
div.border-top.m-3
.card.bg-dark.mt-2
.card-header.bg-secondary.p-2
h5.text-center.text-warning Zusammenfassung
.card-body.p-1
#rueckmeldung.list-group.list-group-horizontal.text-center.font-weight-bold
h3.list-group-item.flex-fill.text-info 5 EK
h3.list-group-item.flex-fill.text-danger 2 MA
h3.list-group-item.flex-fill 0 FK
h3.list-group-item.list-group-item-light.flex-fill.text-dark 0 AGT
//button#rueckmeldung.btn.btn-danger.btn-lg.btn-block.ion-md-paper-plane(type='button') Rückmeldung senden
// TODO: zeitstrahl der den Ablauf anzeigt
.d-none
audio#audio(controls='')
source(src='', type='audio/mpeg')

View File

@ -0,0 +1,148 @@
.row.no-gutters
.col-12.d-flex.justify-content-between
p#einsatz_datum.text-muted 01.01.2020
p#einsatz_uhrzeit.text-muted.text-right 11:22:33
.col-10
case einsatzdaten.einsatzart
when 'Brandeinsatz'
.align-items-center.font-weight-bold.rounded.bg-danger.p-3.mr-2
.ion-md-flame #{' ' + einsatzdaten.stichwort}
when 'Hilfeleistungseinsatz'
.align-items-center.font-weight-bold.rounded.bg-info.p-3.mr-2
.ion-md-construct #{' ' + einsatzdaten.stichwort}
when 'Rettungseinsatz'
.align-items-center.font-weight-bold.rounded.bg-warning.p-3.mr-2
.ion-md-medkit #{' ' + einsatzdaten.stichwort}
when 'Krankentransport'
.align-items-center.font-weight-bold.rounded.bg-success.p-3.mr-2
.ion-md-medical #{' ' + einsatzdaten.stichwort}
when 'Sonstiges'
.align-items-center.font-weight-bold.rounded.bg-secondary.p-3.mr-2
.ion-md-information-circle #{' ' + einsatzdaten.stichwort}
default
.align-items-center.font-weight-bold.rounded.bg-light.p-3.mr-2
.ion-md-apps -Stichwort-
.col-2
.align-items-center.justify-content-center.rounded.bg-light.text-info.p-3
case einsatzdaten.sondersignal
when 1
.ion-md-notifications.text-center
when 0
.ion-md-notifications-off.text-center
default
.ion-md-apps.text-center
.col-12
div.border-top.m-3
.card.mt-2
.card-body.p-0
#map.rounded(style={height: '20em'})
.card-body.p-0
ul.list-group.list-group-flush
if einsatzdaten.objekt
li.list-group-item #{einsatzdaten.objekt}
if einsatzdaten.ort
li.list-group-item #{einsatzdaten.ort}
if einsatzdaten.ortsteil
li.list-group-item #{einsatzdaten.ortsteil}
if einsatzdaten.strasse
li.list-group-item #{einsatzdaten.strasse}
if einsatzdaten.besonderheiten
li.list-group-item.text-warning #{einsatzdaten.besonderheiten}
unless einsatzdaten
li.list-group-item -Objekt-
li.list-group-item -Ort-
li.list-group-item -Orststeil-
li.list-group-item -Straße Hsnr-
li.list-group-item.text-warning -Besonderheiten-
.col-12
div.border-top.m-3
.card.bg-secondary.mt-2
.card-header.bg-light.p-2
h5.text-danger.text-center Alarmierte Einsatzmittel
.card-body.p-1
div.d-flex.flex-wrap.justify-content-between.align-items-center
each val in einsatzdaten.einsatzmittel
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 #{val.einsatzmittel}
unless val.status == null
case val.status
when '1'
div.p-2.badge.badge-info #{val.status}
when '2'
div.p-2.badge.badge-success #{val.status}
when '3'
div.p-2.badge.badge-warning #{val.status}
when '4'
div.p-2.badge.badge-danger #{val.status}
default
div.p-2.badge.badge-dark #{val.status}
//.col-12
div.border-top.my-3
.card.bg-secondary.mt-2
.card-header.bg-light.p-2
h5.text-info.font-weight-bold CB FW Cottbus 1
.card-body.p-1
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 00/00-00
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 01/00-00
div.p-2.badge.badge-warning 3
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB T-Dienst
div.p-2.badge.badge-danger 4
.card.bg-secondary.mt-2
.card-header.bg-light.p-2
h5.text-info.font-weight-bold CB FW Madlow
.card-body.p-1
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL CB 12/00-00
div.p-2.badge.badge-success 2
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 FL SPN 00/00-00
div.p-2.badge.badge-info 1
.card.bg-secondary.mt-2
.card-header.bg-light.p-2
h5.text-info.font-weight-bold CB FW Cottbus 3
.card-body.p-1
div.d-flex.flex-wrap.justify-content-between.align-items-center
div.flex-fill.rounded.bg-light.p-2.m-1
div.d-flex.justify-content-between
div.pr-2 KAT CB 00/00-00
div.p-2.badge.badge-success 2
//table.table.table-striped
tbody
tr
th
div.d-flex.justify-content-between.align-items-center
div FL CB 00/00-00
div.badge.badge-success 2
th
div.d-flex.justify-content-between.align-items-center
div FL CB 12/00-00
div.badge.badge-warning 3
tr
th
div.d-flex.justify-content-between.align-items-center
div FL CB T-Dienst
div.badge.badge-danger 4
th
div.d-flex.justify-content-between.align-items-center
div KAT CB 00/00-00
div.badge.badge-success 2
tr
th
div.d-flex.justify-content-between.align-items-center
div FL SPN 00/00-00
div.badge.badge-info 1
.col-12
div.border-top.m-3
button#rueckmeldung.btn.btn-danger.btn-lg.btn-block.ion-md-paper-plane(type='button') Rückmeldung senden

View File

@ -0,0 +1,125 @@
// BUG: Darstellung in Safari-Mobil fehlerhaft (generell Mobil, ggf. extra Darstellung)
#waiptableau
.row.no-gutters
#headline.col-12.d-flex.justify-content-between.py-1.text-muted
.btn-group.h-100.mr-1
label#replay.btn.btn-outline-light.m-0.py-0
.ion-md-play-circle
label#volume.btn.btn-outline-light.m-0.py-0
.ion-md-volume-high
#date-time || -Datum- - -Uhrzeit-
#wachenname.ion-md-business= data_wache || ' -Wachenname-'
//.col-6.h-5.d-flex.flex-row.align-items-center.justify-content-end.py-1.text-muted.tf_singleline
// TODO Information in Wachalarm-Bild ob alle Rechte, oder ob reduzierte Version
// BUG: Buttons für Sounds werden fehlerhaft dargestellt
//.col-6.h-5.d-flex.align-items-center.py-1.text-muted.tf_singleline
button#replay.btn.btn-outline-light.h-100.py-1
.ion-md-play-circle
button#volume.btn.btn-outline-light.h-100.mx-1
.ion-md-volume-high
#date-time
div -Datum- - -Uhrzeit-
//.col-6.h-5.d-flex.flex-row.align-items-center.justify-content-end.py-1.text-muted.tf_singleline
// TODO Information in Wachalarm-Bild ob alle Rechte, oder ob reduzierte Version
#wachenname.ion-md-business=data_wache || ' -Wachenname-'
.row.no-gutters.fullheight
.col-10.h-15.h-20_ls.pr-3
#einsatz_art.h-100.w-100.d-flex.align-items-center.font-weight-bold.p-3.rounded.bg-dark.tf_singleline
#einsatz_stichwort.ion-md-apps -Stichwort-
.col-2.h-15.h-20_ls.d-flex.align-items-center.justify-content-center.p-3.rounded.bg-dark.text-info.tf_singleline
#sondersignal.ion-md-apps
.col-12.col-5_ls.h-35.h-70_ls.pt-3_pt.ptr-3_ls
#map.h-100.rounded
.col-12.col-7_ls.h-45.h-70_ls
.row.no-gutters.h-100.pt-3
.col-12.h-20.h-100.w-100#rmld_container
// TODO: Rueckmedlung ohne Rueckmeldung ausbleden
.d-flex.fustify-content-between.font-weight-bold.h-30
.list-group.list-group-horizontal.text-center.w-100.h-100
.list-group-item.flex-fill.text-success.py-0.align-self-center
a#ek-counter 0
a &nbsp;EK
.list-group-item.flex-fill.text-info.py-0.align-self-center
a#ma-counter 0
a &nbsp;MA
.list-group-item.flex-fill.text-light.py-0.align-self-center
a#fk-counter 0
a &nbsp;FK
.list-group.list-group-horizontal.text-center.h-100
.list-group-item.flex-fill.rounded.border.border-warning.text-warning.py-0.align-self-center
a#agt-counter 0
a &nbsp;AGT
//a.badge.badge-warning 2&nbsp;AGT
.row.h-70.pt-1
.col-4#pg-ek.pr-1
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-success(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100')
a(style='') 2min
//.progress.mt-1.border.border-warning(style='height: 20px;')
.progress-bar.progress-bar-striped.bg-success(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100')
h 2min
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-success.ion-md-checkmark-circle(role='progressbar', style='width: 100%', aria-valuenow='100', aria-valuemin='0', aria-valuemax='100')
.col-4#pg-ma.px-1
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-info(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100') 3min
//.progress.mt-1
.progress-bar.progress-bar-striped.bg-info(role='progressbar', style='width: 25%', aria-valuenow='25', aria-valuemin='0', aria-valuemax='100') 1min
.col-4#pg-fk.pl-1
//.progress.mt-1.border.border-warning
.progress-bar.progress-bar-striped.bg-light(role='progressbar', style='width: 50%', aria-valuenow='50', aria-valuemin='0', aria-valuemax='100') 10min
.col-6.h-60.d-flex.align-items-end.justify-content-start.tf_multiline
#ortsdaten.flex-fill -Objekt-
br
| -Ort-
br
| -Ortsteil-
br
| -Straße Hsnr-
//.col-6.h-65.d-flex.align-items-around.justify-content-end
#em_alarmiert.col-6.h-60.d-flex.flex-wrap.align-content-end
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB 01/42-01
div.p-2.badge.badge-success 2
div.rounded.bg-secondary.d-flex.justify-content-between.flex-fill.p-2.m-1
div.pr-2 FL CB Wachenname
//ul#em_alarmiert.list-group
li.list-group-item.d-flex.justify-content-between.align-items-center -Einsatzmittel 1-
li.list-group-item.d-flex.justify-content-between.align-items-center -Einsatzmittel 2-
li.list-group-item.d-flex.justify-content-between.align-items-center -Einsatzmittel n-
// TODO: Status mit anzeigen (als .badge.badge-pill)
// TODO: auflistung vieler Fahrzeuge verbessern. flexfill
.col-12.h-5.d-flex.align-items-end.justify-content-center.text-muted.tf_singleline
#em_weitere -weiteres Einsatzmittel 1-, -weiteres Einsatzmittel 2-, -weiteres Einsatzmittel n-
.col-12.h-15.d-flex.align-items-center.rounded.bg-dark.font-weight-bold.text-info.tf_singleline
// TODO: Besonderheiten bei neuer Alarmierung neu in der größe angpassen
#besonderheiten -Besonderheiten-
.col-12.h-5.d-flex.align-items-end.justify-content-center.pt-3
.progress(style='height: 100%;').flex-fill
#hilfsfrist.progress-bar.progress-bar-striped.progress-bar-animated(role='progressbar', aria-valuenow='0', aria-valuemin='0', aria-valuemax='100', style='width: 0%')
audio#audio(controls='')
source(src='', type='audio/mpeg')

View File

@ -1,35 +0,0 @@
// Modal
#responseModal.modal.fade(tabindex='-1', role='dialog', aria-hidden='true')
.modal-dialog.modal-dialog-centered(role='document')
.modal-content
.modal-header
h3#responseModalTitle.modal-title.text-danger Einsatzrückmeldung
button.close(type='button', data-dismiss='modal', aria-label='Close')
span(aria-hidden='true') ×
#responseModalBody.modal-body
h4.text-info
| Aufgabenwahrnehmung
p Ich komme als:
.form-check
input#radios_res_ek.form-check-input(type='radio', name='radios_res', value='1')
label.form-check-label(for='radios_res_ek')
| Einsatzkraft
.form-check
input#radios_res_ma.form-check-input(type='radio', name='radios_res', value='1')
label.form-check-label(for='radios_res_ma')
| Maschinist
.form-check
input#radios_res_fk.form-check-input(type='radio', name='radios_res', value='1')
label.form-check-label(for='radios_res_fk')
| Führungskraft
br
p zum Einsatz.
hr
h4.text-info
| AGT
.form-check
input#cb_res_agt.form-check-input(type='checkbox', value='1')
label.form-check-label(for='cb_res_agt')
| Außerdem bin ich Atemschutzgeräteträger!
.modal-footer
button#send_response.btn.btn-warning(type='button', data-dismiss='modal') Senden

View File

@ -0,0 +1,62 @@
// Modal
// if (!einsatzdaten) {
// var einsatzdaten = {};
// einsatzdaten.uuid = '0';
// einsatzdaten.wachen = {};
// einsatzdaten.wachen.waip_wachen_ID = 0;
// einsatzdaten.wachen.wachenname = '';
// }
#responseModal.modal.fade(tabindex='-1', role='dialog', aria-hidden='true')
.modal-dialog.modal-dialog-centered(role='document')
.modal-content
.modal-header
h3#responseModalTitle.modal-title.text-info Einsatzrückmeldung
button.close(type='button', data-dismiss='modal', aria-label='Close')
span(aria-hidden='true') ×
#responseModalBody.modal-body
form#send_response.was-validated(action=einsatzdaten.uuid, method="POST")
input(type="hidden" name="waip_uuid" value=einsatzdaten.uuid)
input(type="hidden" name="rmld_uuid" value=einsatzdaten.rmld_uuid)
.form-group
p.text-muted Ich komme als ...
.custom-control.custom-radio.form-control-lg
input#radios_res_ek.custom-control-input(type='radio', name='radio_efunction', value='ek', required='')
label.custom-control-label(for='radios_res_ek')
| Einsatzkraft
.custom-control.custom-radio.form-control-lg
input#radios_res_ma.custom-control-input(type='radio', name='radio_efunction', value='ma', required='')
label.custom-control-label(for='radios_res_ma')
| Maschinist
.custom-control.custom-radio.form-control-lg
input#radios_res_fk.custom-control-input(type='radio', name='radio_efunction', value='fk', required='')
label.custom-control-label(for='radios_res_fk')
| Führungskraft
.invalid-feedback wählen Sie eine Einsatzfunktion!
div.border-top.my-3
.form-group
.custom-control.custom-switch.form-control-lg
input#cb_res_agt.custom-control-input(type='checkbox', name='cb_agt', value='1')
label.custom-control-label.text-warning(for='cb_res_agt')
| und bin Atemschutzgeräteträger!
div.border-top.my-3
.form-group
label(for='eintreffzeit').text-muted in ungefähr ...
select#eintreffzeit.form-control.form-control-lg(name='eintreffzeit', required='')
option(value='') bitte Eintreffzeit wählen
option(value='5') 5 Minuten
option(value='10') 10 Minuten
option(value='15') 15 Minuten
option(value='20') 20 Minuten
.form-group
label(for='wachenauswahl').text-muted zur Wache ...
select#wachenauswahl.form-control.form-control-lg(name='wachenauswahl', required='')
option(value='') bitte Wache wählen
each val in einsatzdaten.wachen
option(value=val.waip_wachen_ID) #{val.wachenname}
//option(value='2') CB FW Cottbus 3
//option(value='3') CB FW Madlow
//option(value='4') CB FW Kieckebusch
.modal-footer
button.btn.btn-lg.btn-block.btn-warning.btn-outline-primary.p-3.ion-md-paper-plane(type='submit', form="send_response", value='submit') Rückmeldung absenden!

View File

@ -1,46 +0,0 @@
#waiptableau.fullheight.row.no-gutters
.col-6.h-5.d-flex.align-items-center.py-1.text-dark
button#replay.btn.btn-outline-dark.h-100.py-1.tf_singleline
.ion-md-play-circle
button#volume.btn.btn-outline-dark.h-100.mx-1.tf_singleline
.ion-md-volume-high
#date-time.
.tf_singleline -Datum- - -Uhrzeit-
.col-6.h-5.d-flex.flex-row.align-items-center.justify-content-end.py-1.text-dark.tf_singleline
#wachenname.ion-md-business=data_wache || ' -Wachenname-'
.col-10.h-15.h-20_ls.pr-3
#einsatz_art.h-100.w-100.d-flex.align-items-center.font-weight-bold.p-3.rounded.bg-light.tf_singleline
#einsatz_stichwort.ion-md-apps -Stichwort-
.col-2.h-15.h-20_ls.d-flex.align-items-center.justify-content-center.p-3.rounded.bg-light.text-info.tf_singleline
#sondersignal.ion-md-apps
.col-12.col-5_ls.h-35.h-70_ls.pt-3_pt.ptr-3_ls
#map.h-100.rounded
.col-12.col-7_ls.h-40.h-70_ls
.row.no-gutters.h-100.pt-3
.col-12.h-10.h-100.w-100
#rueckmeldung.list-group.list-group-horizontal.text-center.h-100.w-100
a.list-group-item.flex-fill.list-group-item-action.text-secondary.tf_singleline
.ion-md-paper-plane
.col-6.h-65.d-flex.align-items-end.justify-content-start.tf_multiline
#ortsdaten.flex-fill -Objekt-
br
| -Ort-
br
| -Ortsteil-
br
| -Straße Hsnr-
.col-6.h-65.d-flex.align-items-end.justify-content-end.tf_multiline
ul#em_alarmiert.list-group
li.list-group-item.d-flex.justify-content-between.align-items-center -Einsatzmittel 1-
li.list-group-item.d-flex.justify-content-between.align-items-center -Einsatzmittel 2-
li.list-group-item.d-flex.justify-content-between.align-items-center -Einsatzmittel n-
// TODO: Status mit anzeigen (als .badge.badge-pill)
.col-12.h-5.d-flex.align-items-end.justify-content-center.text-dark.tf_singleline
#em_weitere -weiteres Einsatzmittel 1-, -weiteres Einsatzmittel 2-, -weiteres Einsatzmittel n-
.col-12.h-20.d-flex.align-items-center.rounded.bg-light.font-weight-bold.text-info.tf_singleline
#besonderheiten -Besonderheiten-
.col-12.h-5.d-flex.align-items-end.justify-content-center.pt-3
.progress(style='height: 100%;').flex-fill
#hilfsfrist.progress-bar.progress-bar-striped.progress-bar-animated(role='progressbar', aria-valuenow='0', aria-valuemin='0', aria-valuemax='100', style='width: 0%')
audio#audio(controls='')
source(src='', type='audio/mpeg')

View File

@ -2,6 +2,12 @@ extends layout
block content
main(role='main')
// TODO: - Login verbessern:
// Login-Seite benötigt Fehlerrückmeldung (wie Nutzerverwaltung): falsches Kennwort, Nutzer nicht vorhanden etc.
// - Login/Logout protokollieren
// - fehlerhafte/doppelte Logins protokollieren 
// - prüfen ob es sinnvoll ist, bereits eingeloggte User nicht mehr zulassen (Session prüfen)
// - bei fehlendem Login zur Login-Seite weiterleiten und nach dem Login die zuvor besuchte Seite anzeigen
.container
.row
.col-sm-9.col-md-7.col-lg-5.mx-auto
@ -17,9 +23,9 @@ block content
label(for='inputPassword') Passwort
input#login-password.form-control(type='password' name='password' placeholder='Passwort' required='')
.form-group
.form-check
input#rembemerme.form-check-input(type='checkbox' name='rememberme')
label.form-check-label.text-warning(for='rembemerme') Anmeldung dauerhaft speichern
.custom-control.custom-switch
input#rembemerme.custom-control-input(type='checkbox' name='rememberme' checked='')
label.custom-control-label.text-warning(for='rembemerme') Anmeldung dauerhaft speichern
button.btn.btn-lg.btn-primary.btn-block.text-uppercase(type='submit') Anmelden
.row
.col-sm-9.col-md-7.col-lg-5.mx-auto

View File

@ -0,0 +1,67 @@
extends ../layout
append head
link(rel='stylesheet', href='/css/leaflet.css')
block content
main(role='main')
.container
.row
.col-12.p-3
.card.bg-dark
.card-body.text-muted.text-center
h3 Dashboard-Übersicht
.col-12
div.border-top.m-3
.row
each val, index in dataSet
.col-12.col-xl-6.d-flex.align-self-stretch.p-3
.card.w-100
.card-header
case val.einsatzart
when 'Brandeinsatz'
h5.font-weight-bold.text-danger.ion-md-flame= ' ' + val.einsatzart + ' - ' + val.stichwort
when 'Hilfeleistungseinsatz'
h5.font-weight-bold.text-info.ion-md-construct= ' ' + val.einsatzart + ' - ' + val.stichwort
when 'Rettungseinsatz'
h5.font-weight-bold.text-warning.ion-md-medkit= ' ' + val.einsatzart + ' - ' + val.stichwort
when 'Krankentransport'
h5.font-weight-bold.text-success.ion-md-medical= ' ' + val.einsatzart + ' - ' + val.stichwort
default
h5.font-weight-bold.ion-md-information-circle= ' ' + val.einsatzart + ' - ' + val.stichwort
.card-body
if val.ortsteil
p= val.ort + ', ' + val.ortsteil
else
p= val.ort
.w-100.rounded( id='map' + val.uuid style='height:150px')
.card-footer.text-right
a.btn.btn-primary.mx-2.ion-md-arrow-round-forward(href='/dbrd/' + val.uuid, role='button') Dashboard aufrufen
else
.col-12.p-3
.card.bg-danger.w-100
.card-header
h5 Achtung
.card-body
div Aktuell sind keine Einsätze verfügbar. Bitte versuchen Sie es später erneut.
script(src='/js/leaflet.js')
script.
var data = !{JSON.stringify(dataSet).replace(/<\//g, '<\\/')}
for (var i in data) {
// Karte definieren
var map = L.map('map'+data[i].uuid, {
zoomControl: false
});
// Layer der Karte
mapLink = L.tileLayer(
'#{map_tile}', {
maxZoom: 12,
attribution: '!{map_attribution}'
}).addTo(map);
// Karte setzen
var geojson = L.geoJSON(JSON.parse(data[i].wgs84_area)).addTo(map);
map.fitBounds(geojson.getBounds());
map.setZoom(13);
};

View File

@ -0,0 +1,64 @@
extends ../layout
block content
main(role='main')
.container
.row
.col-12.p-3
.card.bg-dark
.card-body.text-muted.text-center
h3 Alarmmonitor-Übersicht
.col-12
div.border-top.m-3
.row
.col-12.d-flex.align-self-stretch.p-3
.card.border-success.w-100.h-100
.card-header
h2.text-success Alle Wachalarme
.card-body
p Zeigt Ihnen alle Wachalarme der im System hinterlegten Wachen an. Entsprechend Ihrer Berechtigungen werden alle Informationen oder nur Teilinformationen angezeigt.
p Wollen Sie nur Alarme für einen bestimmten Bereich erhalten, wählen Sie bitte einen Alarmmonitor für die unten genannten Bereiche.
.card-footer.text-right
a.btn.btn-success.ion-md-arrow-round-forward(href='/waip/0', role='button') alle Wachalarme anzeigen
.col-12.col-md-4.d-flex.align-self-stretch.p-3
.card.w-100.h-100
.card-header
h3.text-info Alarmmonitor Wache
.card-body
p.text-muted Zeigt den Wachalarm einer einzelnen Wache (z.B. Feuerwache, Rettungswache etc.) an.
.card-footer.text-right
.dropdown
button.btn.btn-info.dropdown-toggle(type='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| bitte ausw&auml;hlen
.dropdown-menu
each item in list_wachen
if item.typ == 'wache'
a.dropdown-item(href='/waip/'+ item.nr)= item.name
.col-12.col-md-4.d-flex.align-self-stretch.p-3
.card.w-100.h-100
.card-header
h3.text-info Alarmmonitor Tr&auml;ger
.card-body
p.text-muted Zeigt alle Wachalarme der Wachen eines Tr&auml;gers (Amt, amtsfreie Gemeinde, Stadt) an.
.card-footer.text-right
.dropdown
button.btn.btn-info.dropdown-toggle(type='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| bitte ausw&auml;hlen
.dropdown-menu
each item in list_wachen
if item.typ == 'traeger'
a.dropdown-item(href='/waip/'+ item.nr)= item.name
.col-12.col-md-4.d-flex.align-self-stretch.p-3
.card.w-100.h-100
.card-header
h3.text-info Alarmmonitor Kreis
.card-body
p.text-muted Zeigt alle Wachalarme des gesamten Kreises (egal ob f&uuml; Feuerwehr oder Rettungsdienst) an.
.card-footer.text-right
.dropdown
button.btn.btn-info.dropdown-toggle(type='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false')
| bitte ausw&auml;hlen
.dropdown-menu
each item in list_wachen
if item.typ == 'kreis'
a.dropdown-item(href='/waip/'+ item.nr)= item.name

144
views/privacy.pug Executable file
View File

@ -0,0 +1,144 @@
extends layout
block content
main(role='main')
.container
.row
.col
h1.display-1 Datenschutzerklärung
h2 1. Datenschutz auf einen Blick
h3 Allgemeine Hinweise
p
| Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgeführten Datenschutzerklärung.
h3 Datenerfassung auf dieser Website
p
strong Wer ist verantwortlich für die Datenerfassung auf dieser Website?
p
| Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Impressum dieser Website entnehmen.
p
strong Wie erfassen wir Ihre Daten?
p
| Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z. B. um Daten handeln, die Sie in ein Kontaktformular eingeben.
p
| Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z. B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie diese Website betreten.
p
strong Wofür nutzen wir Ihre Daten?
p
| Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden.
p
strong Welche Rechte haben Sie bezüglich Ihrer Daten?
p
| Sie haben jederzeit das Recht unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung oder Löschung dieser Daten zu verlangen. Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben, können Sie diese Einwilligung jederzeit für die Zukunft widerrufen. Außerdem haben Sie das Recht, unter bestimmten Umständen die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu.
p
| Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden.
h2 2. Allgemeine Hinweise und Pflichtinformationen
h3 Datenschutz
p
| Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.
p
| Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Die vorliegende Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck das geschieht.
p
| Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.
h3 Hinweis zur verantwortlichen Stelle
p Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:
p
| Robert Richter
br
| Am Mühlenteich 7
br
| 03099 Kolkwitz
p
| Telefon: null eins fünf eins eins zwei vier fünf null acht vier sieben
br
| E-Mail: robertrichter87 [at] web.de
p
| Verantwortliche Stelle ist die natürliche oder juristische Person, die allein oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z. B. Namen, E-Mail-Adressen o. Ä.) entscheidet.
h3 Widerruf Ihrer Einwilligung zur Datenverarbeitung
p
| Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.
h3
| Widerspruchsrecht gegen die Datenerhebung in besonderen Fällen sowie gegen Direktwerbung (Art. 21 DSGVO)
p
| WENN DIE DATENVERARBEITUNG AUF GRUNDLAGE VON ART. 6 ABS. 1 LIT. E ODER F DSGVO ERFOLGT, HABEN SIE JEDERZEIT DAS RECHT, AUS GRÜNDEN, DIE SICH AUS IHRER BESONDEREN SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN WIDERSPRUCH EINZULEGEN; DIES GILT AUCH FÜR EIN AUF DIESE BESTIMMUNGEN GESTÜTZTES PROFILING. DIE JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT, ENTNEHMEN SIE DIESER DATENSCHUTZERKLÄRUNG. WENN SIE WIDERSPRUCH EINLEGEN, WERDEN WIR IHRE BETROFFENEN PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES SEI DENN, WIR KÖNNEN ZWINGENDE SCHUTZWÜRDIGE GRÜNDE FÜR DIE VERARBEITUNG NACHWEISEN, DIE IHRE INTERESSEN, RECHTE UND FREIHEITEN ÜBERWIEGEN ODER DIE VERARBEITUNG DIENT DER GELTENDMACHUNG, AUSÜBUNG ODER VERTEIDIGUNG VON RECHTSANSPRÜCHEN (WIDERSPRUCH NACH ART. 21 ABS. 1 DSGVO).
p
| WERDEN IHRE PERSONENBEZOGENEN DATEN VERARBEITET, UM DIREKTWERBUNG ZU BETREIBEN, SO HABEN SIE DAS RECHT, JEDERZEIT WIDERSPRUCH GEGEN DIE VERARBEITUNG SIE BETREFFENDER PERSONENBEZOGENER DATEN ZUM ZWECKE DERARTIGER WERBUNG EINZULEGEN; DIES GILT AUCH FÜR DAS PROFILING, SOWEIT ES MIT SOLCHER DIREKTWERBUNG IN VERBINDUNG STEHT. WENN SIE WIDERSPRECHEN, WERDEN IHRE PERSONENBEZOGENEN DATEN ANSCHLIESSEND NICHT MEHR ZUM ZWECKE DER DIREKTWERBUNG VERWENDET (WIDERSPRUCH NACH ART. 21 ABS. 2 DSGVO).
h3 Beschwerderecht bei der zuständigen Aufsichtsbehörde
p
| Im Falle von Verstößen gegen die DSGVO steht den Betroffenen ein Beschwerderecht bei einer Aufsichtsbehörde, insbesondere in dem Mitgliedstaat ihres gewöhnlichen Aufenthalts, ihres Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes zu. Das Beschwerderecht besteht unbeschadet anderweitiger verwaltungsrechtlicher oder gerichtlicher Rechtsbehelfe.
h3 Recht auf Datenübertragbarkeit
p
| Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.
h3 SSL- bzw. TLS-Verschlüsselung
p
| Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von „http://“ auf „https://“ wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.
p
| Wenn die SSL- bzw. TLS-Verschlüsselung aktiviert ist, können die Daten, die Sie an uns übermitteln, nicht von Dritten mitgelesen werden.
h3 Auskunft, Löschung und Berichtigung
p
| Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden.
h3 Recht auf Einschränkung der Verarbeitung
p
| Sie haben das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Hierzu können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden. Das Recht auf Einschränkung der Verarbeitung besteht in folgenden Fällen:
ul
li
| Wenn Sie die Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten bestreiten, benötigen wir in der Regel Zeit, um dies zu überprüfen. Für die Dauer der Prüfung haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.
li
| Wenn die Verarbeitung Ihrer personenbezogenen Daten unrechtmäßig geschah/geschieht, können Sie statt der Löschung die Einschränkung der Datenverarbeitung verlangen.
li
| Wenn wir Ihre personenbezogenen Daten nicht mehr benötigen, Sie sie jedoch zur Ausübung, Verteidigung oder Geltendmachung von Rechtsansprüchen benötigen, haben Sie das Recht, statt der Löschung die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.
li
| Wenn Sie einen Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss eine Abwägung zwischen Ihren und unseren Interessen vorgenommen werden. Solange noch nicht feststeht, wessen Interessen überwiegen, haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.
p
| Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschränkt haben, dürfen diese Daten von ihrer Speicherung abgesehen nur mit Ihrer Einwilligung oder zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der Rechte einer anderen natürlichen oder juristischen Person oder aus Gründen eines wichtigen öffentlichen Interesses der Europäischen Union oder eines Mitgliedstaats verarbeitet werden.
h2 3. Datenerfassung auf dieser Website
h3 Cookies
p
| Unsere Internetseiten verwenden so genannte „Cookies“. Cookies sind kleine Textdateien und richten auf Ihrem Endgerät keinen Schaden an. Sie werden entweder vorübergehend für die Dauer einer Sitzung (Session-Cookies) oder dauerhaft (permanente Cookies) auf Ihrem Endgerät gespeichert. Session-Cookies werden nach Ende Ihres Besuchs automatisch gelöscht. Permanente Cookies bleiben auf Ihrem Endgerät gespeichert bis Sie diese selbst löschen oder eine automatische Lösung durch Ihren Webbrowser erfolgt.
p
| Teilweise können auch Cookies von Drittunternehmen auf Ihrem Endgerät gespeichert werden, wenn Sie unsere Seite betreten (Third-Party-Cookies). Diese ermöglichen uns oder Ihnen die Nutzung bestimmter Dienstleistungen des Drittunternehmens (z.B. Cookies zur Abwicklung von Zahlungsdienstleistungen).
p
| Cookies haben verschiedene Funktionen. Zahlreiche Cookies sind technisch notwendig, da bestimmte Webseitenfunktionen ohne diese nicht funktionieren würden (z.B. die Warenkorbfunktion oder die Anzeige von Videos). Andere Cookies dienen dazu das Nutzerverhalten auszuwerten oder Werbung anzuzeigen.
p
| Cookies, die zur Durchführung des elektronischen Kommunikationsvorgangs (notwendige Cookies) oder zur Bereitstellung bestimmter, von Ihnen erwünschter Funktionen (funktionale Cookies, z. B. für die Warenkorbfunktion) oder zur Optimierung der Webseite (z.B. Cookies zur Messung des Webpublikums) erforderlich sind, werden auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO gespeichert, sofern keine andere Rechtsgrundlage angegeben wird. Der Websitebetreiber hat ein berechtigtes Interesse an der Speicherung von Cookies zur technisch fehlerfreien und optimierten Bereitstellung seiner Dienste. Sofern eine Einwilligung zur Speicherung von Cookies abgefragt wurde, erfolgt die Speicherung der betreffenden Cookies ausschließlich auf Grundlage dieser Einwilligung (Art. 6 Abs. 1 lit. a DSGVO); die Einwilligung ist jederzeit widerrufbar.
p
| Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies informiert werden und Cookies nur im Einzelfall erlauben, die Annahme von Cookies für bestimmte Fälle oder generell ausschließen sowie das automatische Löschen der Cookies beim Schließen des Browsers aktivieren. Bei der Deaktivierung von Cookies kann die Funktionalität dieser Website eingeschränkt sein.
p
| Soweit Cookies von Drittunternehmen oder zu Analysezwecken eingesetzt werden, werden wir Sie hierüber im Rahmen dieser Datenschutzerklärung gesondert informieren und ggf. eine Einwilligung abfragen.
h3 Server-Log-Dateien
p
| Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind:
ul
li Browsertyp und Browserversion
li verwendetes Betriebssystem
li Referrer URL
li Hostname des zugreifenden Rechners
li Uhrzeit der Serveranfrage
li IP-Adresse
p
| Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen.
p
| Die Erfassung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Der Websitebetreiber hat ein berechtigtes Interesse an der technisch fehlerfreien Darstellung und der Optimierung seiner Website hierzu müssen die Server-Log-Files erfasst werden.
h2 4. Plugins und Tools
h3 Google Web Fonts
p
| Diese Seite nutzt zur einheitlichen Darstellung von Schriftarten so genannte Web Fonts, die von Google bereitgestellt werden. Die Google Fonts sind lokal installiert. Eine Verbindung zu Servern von Google findet dabei nicht statt.
p
| Weitere Informationen zu Google Web Fonts finden Sie unter
a(href='https://developers.google.com/fonts/faq', target='_blank', rel='noopener noreferrer') https://developers.google.com/fonts/faq
| und in der Datenschutzerklärung von Google:
a(href='https://policies.google.com/privacy?hl=de', target='_blank', rel='noopener noreferrer') https://policies.google.com/privacy?hl=de
| .
h3 OpenStreetMap
p
| Wir nutzen den Kartendienst von OpenStreetMap (OSM). Anbieterin ist die Open-Street-Map Foundation (OSMF), 132 Maney Hill Road, Sutton Coldfield, West Midlands, B72 1JU, United Kingdom.
p
| Wenn Sie eine Website besuchen, auf der OpenStreetMap eingebunden ist, werden u. a. Ihre IP-Adresse und weitere Informationen über Ihr Verhalten auf dieser Website an die OSMF weitergeleitet. OpenStreetMap speichert hierzu unter Umständen Cookies in Ihrem Browser. Das sind Textdateien, die auf Ihrem Computer gespeichert werden und die eine Analyse der Benutzung der Website durch Sie ermöglichen. Sie können die Speicherung der Cookies durch eine entsprechende Einstellung Ihrer Browser-Software verhindern; wir weisen Sie jedoch darauf hin, dass Sie in diesem Fall gegebenenfalls nicht sämtliche Funktionen dieser Website vollumfänglich werden nutzen können.
p
| Ferner kann Ihr Standort erfasst werden, wenn Sie dies in Ihren Geräteeinstellungen z. B. auf Ihrem Handy zugelassen haben. Der Anbieter dieser Seite hat keinen Einfluss auf diese Datenübertragung. Details entnehmen Sie der Datenschutzerklärung von OpenStreetMap unter folgendem Link:
a(href='https://wiki.osmfoundation.org/wiki/Privacy_Policy', target='_blank', rel='noopener noreferrer') https://wiki.osmfoundation.org/wiki/Privacy_Policy
| .
p
| Die Nutzung von OpenStreetMap erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote und einer leichten Auffindbarkeit der von uns auf der Website angegebenen Orte. Dies stellt ein berechtigtes Interesse im Sinne von Art. 6 Abs. 1 lit. f DSGVO dar. Sofern eine entsprechende Einwilligung abgefragt wurde (z. B. eine Einwilligung zur Speicherung von Cookies), erfolgt die Verarbeitung ausschließlich auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO; die Einwilligung ist jederzeit widerrufbar.
p.text-muted
| Quelle:
a(href='https://www.e-recht24.de') https://www.e-recht24.de

16
views/rmld.pug Executable file
View File

@ -0,0 +1,16 @@
extends layout
append head
link(rel='stylesheet', href='/css/leaflet.css')
block content
include includes/modal_info
include includes/modal_rmld
.container-fluid
include includes/master_rueckmeldung
script.
map_tile='#{map_tile}'
map_attribution='!{map_attribution}'
var einsatzdaten_obj = !{JSON.stringify(einsatzdaten).replace(/<\//g, '<\\/')}
script(src='/js/leaflet.js')
script(src='/js/client_rmld.js')

View File

@ -8,5 +8,5 @@ block content
include includes/clock
script(src='/js/textFit.min.js')
script(src='/js/waip.js')
script(src='/js/waip_client.js')

12
views/tests/test_dashboard.pug Executable file
View File

@ -0,0 +1,12 @@
extends /layout
append head
link(rel='stylesheet', href='/css/leaflet.css')
block content
//include includes/modal_response
.container-fluid
include /includes/master_dashboard
script(src='/js/leaflet.js')
script(src='/js/client_dbrd.js')

View File

@ -0,0 +1,12 @@
extends layout
append head
link(rel='stylesheet', href='/css/leaflet.css')
block content
include includes/modal_rmld
.container-fluid
include includes/master_rueckmeldung
script(src='/js/leaflet.js')
script(src='/js/rueckmeldung_client.js')

View File

@ -1,4 +1,4 @@
extends layout
extends ../layout
append head
link(rel='stylesheet', href='/css/ionicons.min.css')
@ -6,10 +6,9 @@ append head
link(rel='stylesheet', href='/css/waip.css')
block content
include includes/modal_response
.container-fluid
include includes/wachalarm
include ../includes/master_wachalarm
script(src='/js/leaflet.js')
script(src='/js/textFit.min.js')
script(src='/js/waip.js')
script(src='/js/client_waip.js')

View File

@ -1,7 +1,4 @@
extends layout
append head
link(rel='stylesheet', href='/css/ionicons.min.css')
extends ../layout
block content
main(role='main')
@ -26,3 +23,5 @@ block content
option(selected)= i+1
else
option= i+1
// TODO: anpassen der Durchsage je Benutzer, durch eigene Ersetzung und Reihenfolge
// TODO: Ausnahmen festlegen können, wann keine Musik abgespielt wird

View File

@ -4,21 +4,22 @@ append head
link(rel='stylesheet', href='/css/leaflet.css')
block content
include includes/modal
include includes/modal_response
include includes/modal_info
//include includes/modal_rmld
.container-fluid
#waipclock.d-none
include includes/clock
include includes/master_clock
#waiptableau.d-none
include includes/wachalarm
include includes/master_wachalarm
script.
var map_tile = !{JSON.stringify(map_tile).replace(/<\//g, '<\\/')}
var client_id = !{JSON.stringify(app_id).replace(/<\//g, '<\\/')}
map_tile='#{map_tile}'
map_attribution='!{map_attribution}'
client_id="#{app_id}"
script(src='/js/leaflet.js')
script(src='/js/textFit.min.js')
script(src='/socket.io/socket.io.js')
script.
wachen_id="#{wachen_id}"
waip_id=null
script(src='/js/waip.js')
script(src='/js/client_waip.js')