Alte Version abgeändert auf neue Datebank /-Struktur).

This commit is contained in:
rxf
2026-03-27 17:26:46 +01:00
commit b7736413d4
87 changed files with 54060 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
node-modules
PYTHON
.gitignore
.dockerignore
build_and_copy.sh
buildit.sh
copycontainder.sh
docker-compose.yml
Dockerfile*
nginx-proxy

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules/
Feinstaub_ToDos.pdf
this_is_MacBig
.idea

17
.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>feinstaub_js</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

32
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,32 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Run npm",
"runtimeExecutable": "npm",
"cwd": "${workspaceFolder}",
"args": []
},
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"env": {
"MONGOHOST": "217.72.203.152",
"MONGOPORT": "27037",
"MONGOAUTH": "true",
"MONGOUSRP": "admin:mongo4noise",
"MONGOBASE": "sensor_data"
},
"program": "${workspaceFolder}/geiger-app.js"
}
]
}

25
Dockerfile_geiger-web Normal file
View File

@@ -0,0 +1,25 @@
FROM node:alpine
ADD package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/
WORKDIR /opt/app
ADD . /opt/app
RUN apk update
RUN apk upgrade
RUN apk add --no-cache tzdata
ENV TZ Europe/Berlin
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apk add curl
RUN touch cmds.sh \
&& echo 'npm start' >>cmds.sh
EXPOSE 3005
CMD sh ./cmds.sh

4
README.md Normal file
View File

@@ -0,0 +1,4 @@
# Geiger_WEB
Verschiedene Grafiken zur Auswertung des Multigeiger.
Beschreibung folgt ... irgendwann.

74
akws/build_akws.js Normal file
View File

@@ -0,0 +1,74 @@
// After doing a wikidata query use the file query.json to build or rebuild the databae th1_akws
// 2020-10-07
const { MongoClient } = require('mongodb');
const fs = require('fs').promises;
// Consts
const PORT = process.env.SERVERPORT || 3005; // Port for server
const debug = (process.env.DEBUG == "true");
const MONGOHOST = process.env.MONGOHOST || 'localhost';
const MONGOPORT = process.env.MONGOPORT || 27017;
const MONGOAUTH = (process.env.MONGOAUTH == "true");
const MONGOUSRP = process.env.MONGOUSRP || "";
const MONGOBASE = process.env.MONGOBASE || 'allsensors';
const MONGO_URL = MONGOAUTH ? 'mongodb://'+MONGOUSRP+'@' + MONGOHOST + ':' + MONGOPORT + '/?authSource=admin' : 'mongodb://'+MONGOHOST+':'+MONGOPORT; // URL to mongo database
// Read whole file 'query.json' int memory
async function readQuery(name) {
try {
const query = await fs.readFile(name);
return JSON.parse(query);
} catch (e) {
console.error(`File ${name} not found. ${e}`);
}
}
// Find and return one entry from the database
// Params
// name: name of entry
//
// Return
// null if not found, else complete entry
async function findOneEntry(client, name) {
const erg = await client.db("allsensors").collection("th1_akws")
.findOne({name:name});
// console.log(erg);
return erg;
}
async function getAllEntries(client) {
const cursor = client.db("allsensors").collection("th1_akws")
.find({});
const results = await cursor.toArray();
console.log(`Anzahl der Einträge: ${results.length}`);
}
async function main(){
const query = await readQuery('akws/query.json');
const client = new MongoClient(MONGO_URL, {useNewUrlParser: true , useUnifiedTopology: true});
try {
await client.connect();
for(let entry of query) {
if (await findOneEntry(client, entry.name) == null) {
const result = await client.db("allsensors").collection("th1_akws")
.insertOne(entry);
console.log(`new entry ${entry.name} with result: ${result.insertedID}`);
} else {
process.stdout.write('.');
}
}
} catch(e) {
console.error(e);
} finally {
await client.close();
}
}
main().catch(console.error());

1
akws/query.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,147 @@
*Without aggregation. with dates
# Wikidata SPARQL-Query for nuclear power plants, research reactors, waste facilities
# regardless of their status
# one result line per location - check columns count and types
# 20200612 with service entry/retirement dates; no aggregation
# 20200518
# 20200509 initial version
# Paste this code into the code input box of https://query.wikidata.org/
# Then click the blue arrow to start the query and
# to get in a few seconds about 480 results in the #defaultView:Map
#defaultView:Table # select this as the result default view
# defaultView:Map
# Display item details by clicking onto clickable items in the mid column.
# Once you have results, you can change the result view from "Table" to "Map" to visualize the locations.
# You will be directed to the wikidata entry.
# In the upper right corner of that wikidata entry you find links to wikipedia entries for that itme in the available languages.
SELECT DISTINCT ?country ?name ?item ?geo ?itemType ?types ?itemInception ?itemStarttime ?itemServiceentry ?itemServiceretirement ?itemEndtime
WITH
{
SELECT ?item
WHERE
{
{
?item wdt:P31/wdt:P279* wd:Q1739545.
}
UNION
{
?item wdt:P31/wdt:P279* wd:Q1438105.
}
}
} AS %allitems
WHERE
{
INCLUDE %allitems
?item wdt:P31/wdt:P279* wd:Q1739545.
?item wdt:P625 ?geo.
?item wdt:P31 ?itemType.
OPTIONAL { ?item wdt:P17 ?itemCountry. }
OPTIONAL { ?item wdt:P729 ?itemServiceentry. }
OPTIONAL { ?item wdt:P730 ?itemServiceretirement. }
OPTIONAL { ?item wdt:P582 ?itemEndtime }
OPTIONAL { ?item wdt:P571 ?itemInception }
OPTIONAL { ?item wdt:P580 ?itemStarttime }
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q134447}, "Nuclear power plant, ", "") AS ?itemType1)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q1438105}, "Nuclear research reactor, ", "") AS ?itemType2)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q21493801}, "Nuclear waste facility, ", "") AS ?itemType3)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q14510027}, "Fusion reactor, ", "") AS ?itemType4)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q1298668}, "Nuclear research project, ", "") AS ?itemType5)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q1229765}, "Vessel, ", "") AS ?itemType6)
BIND( CONCAT(?itemType1, ?itemType2, ?itemType3, ?itemType4, ?itemType5, ?itemType6) AS ?itemType123456)
BIND( IF(STRLEN(?itemType123456)=0, "Nuclear facility (unspecified), ", ?itemType123456) AS ?types1)
BIND( SUBSTR( ?types1, 1, STRLEN($types1)-2 ) AS $types )
SERVICE wikibase:label {
bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en".
?itemCountry rdfs:label ?country.
?item rdfs:label ?name.
}
}
ORDER BY ?country ?name
*WIth aggregation
# Wikidata SPARQL-Query for nuclear power plants, research reactors, waste facilities
# regardless of their status
# one result line per location - check columns count and types
# 20200518
# 20200509 initial version
# Paste this code into the code input box of https://query.wikidata.org/
# Then click the blue arrow to start the query and
# to get in a few seconds about 480 results in the #defaultView:Map
#defaultView:Table # select this as the result default view
# defaultView:Map
# Display item details by clicking onto clickable items in the mid column.
# Once you have results, you can change the result view from "Table" to "Map" to visualize the locations.
# You will be directed to the wikidata entry.
# In the upper right corner of that wikidata entry you find links to wikipedia entries for that itme in the available languages.
# The Query aggregates different item types at the same location into a single result entry
SELECT DISTINCT ?country ?name ?item ?geo (COUNT(?itemType) AS ?count) ?types
WITH
{
SELECT ?item
WHERE
{
{
?item wdt:P31/wdt:P279* wd:Q1739545.
}
UNION
{
?item wdt:P31/wdt:P279* wd:Q1438105.
}
}
} AS %allitems
WHERE
{
INCLUDE %allitems
?item wdt:P31/wdt:P279* wd:Q1739545.
?item wdt:P625 ?geo.
?item wdt:P31 ?itemType.
OPTIONAL { ?item wdt:P17 ?itemCountry. }
OPTIONAL { ?item wdt:P729 ?itemServiceentry. }
OPTIONAL { ?item wdt:P730 ?itemServiceretirement. }
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q134447}, "Nuclear power plant, ", "") AS ?itemType1)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q1438105}, "Nuclear research reactor, ", "") AS ?itemType2)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q21493801}, "Nuclear waste facility, ", "") AS ?itemType3)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q14510027}, "Fusion reactor, ", "") AS ?itemType4)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q1298668}, "Nuclear research project, ", "") AS ?itemType5)
BIND( IF(EXISTS {?item wdt:P31/wdt:P279* wd:Q1229765}, "Vessel, ", "") AS ?itemType6)
BIND( CONCAT(?itemType1, ?itemType2, ?itemType3, ?itemType4, ?itemType5, ?itemType6) AS ?itemType123456)
BIND( IF(STRLEN(?itemType123456)=0, "Nuclear facility (unspecified), ", ?itemType123456) AS ?types1)
BIND( SUBSTR( ?types1, 1, STRLEN($types1)-2 ) AS $types )
SERVICE wikibase:label {
bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en".
?itemCountry rdfs:label ?country.
?item rdfs:label ?name.
}
} GROUP BY ?item ?itemType ?types ?name ?geo ?country
ORDER BY ?country ?name

63
build_and_copy.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/bash
# Build Docker-Container
#
# Call: buildit.sh name [target]
#
# The Dockerfile must be named like Dockerfile_name
#
# 2018-09-20 rxf
# - before sending docker image to remote, tag actual remote image
#
# 2018-09-14 rxf
# - first Version
#
set -x
port=""
orgName=geiger-web
name=geiger-web
usage()
{
echo "Usage build_and_copy.sh [-p port] [-n name] target"
echo " Build docker container $name and copy to target"
echo "Params:"
echo " target: Where to copy the container to "
echo " -p port: ssh port (default 22)"
echo " -n name: new name for container (default: $orgName)"
}
while getopts n:p:h? o
do
case "$o" in
n) name="$OPTARG";;
p) port="-p $OPTARG";;
h) usage; exit 0;;
*) usage; exit 1;;
esac
done
shift $((OPTIND-1))
while [ $# -gt 0 ]; do
if [[ -z "$target" ]]; then
target=$1
shift
else
echo "bad option $1"
# exit 1
shift
fi
done
docker build -f Dockerfile_$orgName -t $name .
dat=`date +%Y%m%d%H%M`
if [ "$target" == "localhost" ]
then
docker tag $name $name:V_$dat
exit
fi
ssh $port $target "docker tag $name $name:V_$dat"
docker save $name | bzip2 | pv | ssh $port $target 'bunzip2 | docker load'

43
docker-compose.yml Normal file
View File

@@ -0,0 +1,43 @@
version: '3'
services:
fst-mongo:
image: mongo
volumes:
- /private/var/lib/mongodb:/data/db
- /private/var/log/feinstaub:/var/log
ports:
- '27017:27017'
container_name: fst-mongo
# command: '--auth'
restart: always
fst-data:
image: fst-data
volumes:
- /private/var/log/feinstaub:/var/log
environment:
- TTS_KEY=YU4D8C6L39L53B8Q
- MONGOHOST=fst-mongo
# - MONGOAUTH=true
# - "MONGOUSRP=rxf:5C5dB|m"
container_name: fst-data
restart: always
# fst-web:
# image: fst-web
# volumes:
# - /private/var/log/feinstaub:/var/log
## - "/etc/timezone:/etc/timezone:ro"
## - "/etc/localtime:/etc/localtime:ro"
# environment:
## - VIRTUAL_HOST=feinstaub.rexfue.de
# - MONGOHOST=fst-mongo
## - MONGOAUTH=true
## - "MONGOUSRP=rxf:5C5dB|m"
# - TZ=Europe/Berlin
# ports:
# - '3005:3005'
# container_name: fst-web
# restart: always

129
docs/MIGRATION_LOG.md Normal file
View File

@@ -0,0 +1,129 @@
# Migration zu neuer Datenbank-Struktur
**Datum:** 27. März 2026
## Ziel
Migration von Multi-Sensor-DB (PM + THP + Radiation) zu fokussierter DB (nur Radiation + THP)
## Änderungen
### Entfernt:
- ❌ PM (Feinstaub) Sensoren komplett
-`problemsensors` Collection
-`mapdata` Collection (ersetzt durch `properties.values`)
- ❌ Separate `data_<sid>` Collections
### Neu:
- ✅ Zentrale `radioactivity_sensors` Collection
- ✅ Zentrale `thp_sensors` Collection
- ✅ Aktuelle Werte in `properties.values`
- ✅ Name als Array mit Historie
- ✅ Vereinfachte Location-Struktur
### Beibehalten (auskommentiert):
- 💤 AKW-Funktionalität (für spätere Verwendung)
## Bearbeitete Dateien
### ✅ Abgeschlossen
-**routes/fsdata.js**
- Radiation: `data_<sid>``radioactivity_sensors`
- THP: `data_<sid>``thp_sensors`
- properties.name als Array handhaben
- PM-Funktionen auskommentiert (movAvgSDSWeek, calcMinMaxAvgSDS, isPM, getStatistics)
-**routes/utilities.js**
- PM-Teile aus calcMovingAverage entfernt
- Unterstützung für neue `values` Struktur hinzugefügt
- `datetime.$date` Format-Handling
-**routes/apidata.js**
- getAPIdata(): Type-basierte Collection-Auswahl (radioactivity_sensors/thp_sensors)
- getAPIprops(): name Array, location.country statt address
- getApiCities(): PM-Filter entfernt, type-basiert
- PM-Funktionen auskommentiert (getAPIalldata, isPM)
-**routes/mapdata.js**
- getaktdata(): properties Collection statt mapdata
- properties.values für aktuelle Werte
- location.loc statt location
- indoor als Number (0/1)
- getRegionSensors(): PM-Filter entfernt
- AKW-Funktionen auskommentiert (getakwdata)
-**docs/MongoDB_Struktur.md**
- THP-Struktur dokumentiert
- Neue Collections beschrieben
- Migrations-Anleitungen hinzugefügt
## Wichtige Änderungen im Detail
### 1. Collections
-`data_<sid>` (tausende separate Collections)
-`radioactivity_sensors` (eine zentrale Collection)
-`thp_sensors` (eine zentrale Collection)
-`mapdata` Collection
- ✅ Aktuelle Werte jetzt in `properties.values`
### 2. Properties-Struktur
- `name`: String → Array von {name, since}
- `location.address`: Objekt → nur `location.country`: String
- `indoor`: Boolean → Number (0/1)
- Neu: `location.id`, `location.exact_loc`, `location.since`
- Neu: `type: "radioactivity"` zur Typ-Identifikation
- Neu: `values` Objekt mit aktuellen Messwerten
### 3. Zeitstempel
- Alt: `datetime: Date`
- Neu: `datetime: { $date: Date }`
- Beide Formate werden jetzt unterstützt
### 4. Query-Änderungen
- Sensor-ID-Filter: `sensorid: sid` statt Collection-Name
- Zeitstempel-Filter: `'datetime.$date'` statt `datetime`
- Messwerte-Zugriff: `values.counts_per_minute` statt `counts_per_minute`
- Location-Zugriff: `location.loc` statt `location` (für GeoJSON)
### 5. Indizes (müssen erstellt werden!)
```javascript
db.radioactivity_sensors.createIndex({ sensorid: 1, "datetime.$date": 1 });
db.radioactivity_sensors.createIndex({ "datetime.$date": -1 });
db.thp_sensors.createIndex({ sensorid: 1, "datetime.$date": 1 });
db.thp_sensors.createIndex({ "datetime.$date": -1 });
db.properties.createIndex({ type: 1 });
db.properties.createIndex({ "location.loc": "2dsphere" });
```
## Nächste Schritte
1. **Datenbank-Verbindung**: Environment-Variablen für neue DB setzen
2. **Indizes erstellen**: Obige Indizes in MongoDB anlegen
3. **Testen**: Alle Funktionen durchTesten
4. **AKW-Features**: Bei Bedarf auskommentierte AKW-Funktionen reaktivieren
## Bekannte Einschränkungen
- `othersensors` Array in properties wird möglicherweise nicht mehr gefüllt
- Stadt-Namen (Stadt-basierte Queries) funktionieren möglicherweise nicht mehr
- PM-bezogene API-Endpunkte geben Fehler zurück
- AKW-Funktionalität ist auskommentiert
## Bugfixes nach Migration
### 27.03.2026 - Frontend Name-Handling
**Problem:** `Uncaught TypeError: data.name.startsWith is not a function`
- Frontend erwartete name als String, erhielt aber Array
**Lösung:**
- routes/fsdata.js: getSensorProperties konvertiert name-Array zu String für Frontend
- public/js/global.js: holAddress auf neue location.country Struktur angepasst
### 27.03.2026 - MongoDB datetime.$date Query-Fehler
**Problem:** `MongoServerError: FieldPath field names may not start with '$'`
- MongoDB interpretiert `$date` in `'datetime.$date'` als Operator, nicht als Feldname
**Lösung:**
- Alle Queries auf direktes `datetime` Feld umgestellt (BSON Date)
- Betroffene Funktionen:
- routes/fsdata.js: readRadiationMovingAverage, readRadiationAverages, readClimateAverages
- routes/apidata.js: getAPIdataSensor
- utilities.js bleibt unverändert (behandelt beide Formate bei Datenverarbeitung)
**Hinweis:** Datenbank muss datetime als BSON Date speichern, nicht als verschachteltes Objekt

856
docs/MongoDB_Struktur.md Normal file
View File

@@ -0,0 +1,856 @@
# MongoDB Datenbankstruktur - Geiger_WEB_26
## Übersicht
**Datenbankname:** `allsensors` (aus MONGOBASE Environment-Variable)
Die Datenbank enthält Messdaten von Strahlungs-, Feinstaub- und Klimasensoren.
---
## Collections
### 1. **properties**
Speichert die Eigenschaften und Metadaten aller Sensoren.
#### Struktur:
```javascript
{
_id: Number, // Sensor-ID (z.B. 140, 1234)
name: String, // Sensor-Typ/Name
// Beispiele:
// - Feinstaub: "SDS011", "PMS7003", "PMS3003", "PMS5003", "HPM", "SDS021", "PPD42NS"
// - Strahlung: "Radiation <Type>" (z.B. "Radiation SBM-20", "Radiation Si22G")
// - Klima: andere Typen für Temp/Hum/Press
date_since: Date, // Startdatum des Sensors
last_seen: Date, // Letzter Kontakt mit dem Sensor
location: [ // Array mit Standort-Historie (neuester = letzter Eintrag)
{
loc: { // GeoJSON Point
type: "Point",
coordinates: [longitude, latitude] // [lon, lat] Format!
},
altitude: Number, // Höhe über NN in Metern
address: { // Adressinformationen
country: String, // Ländercode (z.B. "DE", "AT")
plz: String, // Postleitzahl
city: String, // Stadt
street: String // Straße (optional)
}
}
],
indoor: Boolean, // true = Innenraum-Sensor
othersensors: [ // Zugehörige Sensoren (für Multi-Sensor-Geräte)
{
id: Number, // oder direkt: Number (ältere Einträge)
name: String
}
]
}
```
#### Verwendung:
- Abruf per Sensor-ID: `db.collection('properties').findOne({_id: sensorId})`
- Geo-Abfragen für Karten-Bounds
- Filtern nach Sensor-Typ (PM, Radiation, etc.)
---
### 2. **data_<sid>** (Dynamische Collections)
Für jeden Sensor existiert eine eigene Collection mit seinen Messdaten.
Format: `data_140`, `data_1234`, etc.
#### Struktur (abhängig vom Sensor-Typ):
**Feinstaub-Sensoren (PM):**
```javascript
{
datetime: Date, // Zeitstempel der Messung
P1: Number, // PM10 Wert (Feinstaub < 10μm)
P2: Number, // PM2.5 Wert (Feinstaub < 2.5μm)
}
```
**Klima-Sensoren (THP):**
```javascript
{
datetime: Date, // Zeitstempel der Messung
temperature: Number, // Temperatur in °C
humidity: Number, // Luftfeuchtigkeit in %
pressure: Number, // Luftdruck in Pa
pressure_at_sealevel: Number // Normierter Luftdruck auf Meereshöhe
}
```
**Strahlungs-Sensoren (Radiation):**
```javascript
{
datetime: Date, // Zeitstempel der Messung
counts_per_minute: Number // CPM (Counts per Minute)
}
```
#### Verwendung:
- Zeitbereich-Abfragen mit `datetime` Filter
- Aggregation für Durchschnittswerte über Zeiträume
- Sortiert nach `datetime`
---
### 3. **values**
Vorberechnete/aggregierte Tageswerte für schnelleren API-Zugriff.
#### Struktur:
```javascript
{
_id: String, // Format: "<sid>_YYYYMMDD" (z.B. "140_20180602")
values: [ // Array mit Messwerten des Tages
{
datetime: Date,
P1: Number, // oder andere Sensor-spezifische Felder
P2: Number,
// ... weitere Felder je nach Sensor-Typ
}
]
}
```
#### Verwendung:
- Schneller Zugriff auf Tagesdaten
- Mehrere Tage werden durch mehrere findOne-Calls abgerufen
---
### 4. **mapdata**
Aktuelle Sensordaten für die Karten-Visualisierung.
#### Struktur:
```javascript
{
_id: Number, // Sensor-ID
name: String, // Sensor-Name (z.B. "Radiation SBM-20")
location: { // GeoJSON Point
type: "Point",
coordinates: [longitude, latitude]
},
indoor: Boolean, // Innen-/Außensensor
values: { // Aktuellster Messwert
datetime: Date,
counts_per_minute: Number, // oder P1, P2, temperature, etc.
// ... je nach Sensor-Typ
}
}
```
#### Verwendung:
- Geo-Abfragen mit `$geoWithin` für Karten-Bounds
- Filter nach Sensor-Namen (z.B. `/Radiation/`)
- Anzeige aktueller Werte auf der Karte
---
### 5. **problemsensors**
Sensoren mit erkannten Problemen/Anomalien.
#### Struktur:
```javascript
{
_id: Number, // Sensor-ID (0 = Metadaten-Eintrag mit Textbeschreibungen)
problemNr: Number, // Problem-Typ-Nummer
// ... weitere problem-spezifische Felder
}
```
#### Besonderheit:
- `_id: 0` enthält Textbeschreibungen der Problem-Typen
- Bulk-Updates mit `bulkWrite()` und `upsert: true`
---
### 6. **akws**
Atomkraftwerke-Daten.
#### Struktur:
```javascript
{
_id: Mixed,
Name: String, // Name des AKW
lat: Number, // Breitengrad
lon: Number, // Längengrad
Status: String, // "aktiv" oder Stillgelegt-Status
Baujahr: String, // Baujahr
Stillgeleg: String, // Stilllegungsjahr (falls zutreffend)
Wiki_Link: String // Wikipedia-Link
}
```
---
### 7. **th1_akws**
Erweiterte AKW-Daten (vermutlich aus Wikidata).
#### Struktur:
```javascript
{
_id: Mixed,
name: String, // Name
geo: String, // Format: "POINT(lon lat)"
types: String, // "Nuclear power plant" oder andere
item: String, // Wikidata/Wikipedia-Link
itemServiceentry: String, // Inbetriebnahme-Datum
itemServiceretirement: String // Stilllegungs-Datum
}
```
---
## Indizes und Performance
### GeoJSON-Indizes:
- `properties.location.0.loc` - 2dsphere Index für Geo-Abfragen
- `mapdata.location` - 2dsphere Index für Kartenansicht
### Zeitstempel-Indizes:
- `data_<sid>.datetime` - Index für schnelle Zeitbereich-Abfragen
- Sortierung erfolgt meistens nach `datetime`
---
## Wichtige Queries
### 1. Sensor-Eigenschaften abrufen:
```javascript
db.collection('properties').findOne({_id: sensorId})
```
### 2. Messdaten für Zeitraum:
```javascript
db.collection('data_' + sensorId).find({
datetime: {
$gte: new Date(start),
$lt: new Date(end)
}
}).sort({datetime: 1}).toArray()
```
### 3. Aggregierte Durchschnittswerte:
```javascript
db.collection('data_' + sensorId).aggregate([
{$sort: {datetime: 1}},
{$match: {datetime: {$gte: startDate, $lt: endDate}}},
{
$group: {
_id: {
$toDate: {
$subtract: [
{$toLong: '$datetime'},
{$mod: [{$toLong: '$datetime'}, zeitinterval]}
]
}
},
cpmAvg: {$avg: '$counts_per_minute'},
count: {$sum: 1}
}
},
{$sort: {_id: 1}}
])
```
### 4. Geo-Abfrage für Karten-Bereich:
```javascript
db.collection('mapdata').find({
location: {
$geoWithin: {
$box: [[west, south], [east, north]]
}
},
name: /Radiation/
})
```
### 5. Sensoren innerhalb Polygon:
```javascript
db.collection('properties').find({
'location.0.loc': {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [polygonCoords]
}
}
}
})
```
---
## Sensor-Typen
### Feinstaub (PM):
- SDS011, PMS7003, PMS3003, PMS5003, HPM, SDS021, PPD42NS
- Felder: `P1` (PM10), `P2` (PM2.5)
### Strahlung (Radiation):
- Typen: SBM-20, SBM-19, Si22G
- Feld: `counts_per_minute` (CPM)
- Umrechnungsfaktoren in µSv/h:
- SBM-20: 1/2.47/60
- SBM-19: 1/9.81888/60
- Si22G: 0.081438/60
### Klima (THP):
- Felder: `temperature`, `humidity`, `pressure`
- Druckberechnung auf Meereshöhe mit Formel aus BMP180 Datenblatt
---
## Verbindung
```javascript
const MONGO_URL = 'mongodb://rexfueAdmin:D6grTasE56@207.180.224.98:20019/?authSource=admin'
const MONGOBASE = 'allsensors'
MongoClient.connect(MONGO_URL, {useNewUrlParser: true, useUnifiedTopology: true})
.then(client => {
const db = client.db(MONGOBASE);
// ... Verwendung der Datenbank
})
```
---
## Notizen
1. **Dynamische Collections**: Für jeden neuen Sensor wird automatisch eine `data_<sid>` Collection angelegt
2. **Location-Array**: Das neueste Location-Objekt ist immer am Ende des Arrays (`location[location.length-1]`)
3. **Zeitstempel**: Alle Zeitstempel sind als JavaScript Date-Objekte gespeichert
4. **GeoJSON**: Koordinaten sind im Format `[longitude, latitude]` (nicht lat/lon!)
5. **Offline-Sensoren**:
- > 2 Stunden offline: `cpm = -1`
- > 7 Tage offline: `cpm = -2`
- Aktiv: tatsächlicher CPM-Wert
---
---
# NEUE DATENBANKSTRUKTUR (Geplant für zukünftige Verwendung)
## Übersicht
Die neue MongoDB-Struktur fokussiert sich ausschließlich auf **Geiger-Sensoren (Radioaktivität)** und vereinfacht einige Aspekte der Datenorganisation.
---
## Hauptunterschiede zur aktuellen Struktur
| Aspekt | Alte Struktur | Neue Struktur |
|--------|---------------|---------------|
| Sensor-Typen | Multi-Sensor (PM, THP, Radiation) | Nur Radioaktivität |
| Messdaten-Collections | Separate `data_<sid>` pro Sensor | Eine zentrale `radioactivity_sensors` |
| Sensor-Name | `name: String` | `name: Array` mit Historie |
| Adresse | Detailliert im `location.address` | Nur `country` im `location` |
| Aktuelle Werte | Separate `mapdata` Collection | Direkt in `properties.values` |
| Indoor-Flag | `indoor: Boolean` | `indoor: Number (0/1)` |
| Location-ID | Nicht vorhanden | `location.id` |
| Zeitstempel-Feld | `datetime` in `data_<sid>` | `datetime.$date` in `radioactivity_sensors` |
| Sensor-Identifikation | Collection-Name (`data_<sid>`) | Feld `sensorid` |
| Zusätzliche Felder | - | `hv_pulses`, `counts`, `sample_time_ms` |
---
## Collections (Neue Struktur)
### 1. **properties** (Neue Struktur)
Enthält sowohl Metadaten als auch aktuelle Messwerte der Sensoren.
#### Struktur:
```javascript
{
_id: Number, // Sensor-ID
type: String, // Sensor-Typ: "radioactivity"
name: [ // Name-Historie als Array
{
name: String, // z.B. "Radiation Si22G", "Radiation SBM-20"
since: { // Gültig seit
$date: Date
}
}
],
location: [ // Location-Historie als Array
{
loc: { // GeoJSON Point
type: "Point",
coordinates: [longitude, latitude]
},
id: Number, // Location-ID (neu)
altitude: Number, // Höhe über NN in Metern
since: { // Gültig seit
$date: Date
},
exact_loc: Number, // 0 = ungefähr, 1 = exakt
indoor: Number, // 0 = outdoor, 1 = indoor
country: String // ISO Ländercode (z.B. "AU", "DE")
}
],
values: { // Aktuellste Messwerte (neu in properties!)
counts_per_minute: Number, // CPM (Counts per Minute)
hv_pulses: Number, // Hochspannungs-Pulse
counts: Number, // Gesamt-Counts in der Messperiode
sample_time_ms: Number, // Messzeit in Millisekunden
timestamp: { // Zeitstempel der Messung
$date: Date
}
}
}
```
#### Beispiel:
```javascript
{
_id: 12345,
type: "radioactivity",
name: [
{
name: "Radiation Si22G",
since: { $date: "2024-06-20T10:13:12.970Z" }
}
],
location: [
{
loc: {
type: "Point",
coordinates: [151.032, -33.792]
},
id: 70638,
altitude: 62,
since: { $date: "2024-06-20T10:13:12.970Z" },
exact_loc: 0,
indoor: 0,
country: "AU"
}
],
values: {
counts_per_minute: 61,
hv_pulses: 44,
counts: 154,
sample_time_ms: 151052,
timestamp: { $date: "2026-03-27T13:18:15.000Z" }
}
}
```
---
### 2. **radioactivity_sensors** (Zentrale Messdaten-Collection)
**WICHTIG**: Anders als in der alten Struktur gibt es **keine** separaten `data_<sid>` Collections mehr!
Alle Sensordaten werden in einer einzigen Collection `radioactivity_sensors` gespeichert.
#### Struktur:
```javascript
{
datetime: { // Zeitstempel der Messung
$date: Date
},
sensorid: Number, // Sensor-ID (Referenz zu properties._id)
values: { // Messwerte
counts_per_minute: Number, // CPM (Counts per Minute)
hv_pulses: Number, // Hochspannungs-Pulse
counts: Number, // Gesamt-Counts in der Messperiode
sample_time_ms: Number // Messzeit in Millisekunden
}
}
```
#### Beispiel:
```javascript
{
datetime: { $date: "2024-06-20T10:56:55.000Z" },
sensorid: 74797,
values: {
counts_per_minute: 46,
hv_pulses: 823,
counts: 114,
sample_time_ms: 146826
}
}
```
#### Verwendung:
```javascript
// Alle Daten für einen Sensor abrufen
db.collection('radioactivity_sensors').find({
sensorid: 74797,
datetime: { $gte: new Date(start), $lt: new Date(end) }
}).sort({ datetime: 1 }).toArray()
// Aggregation für Durchschnittswerte
db.collection('radioactivity_sensors').aggregate([
{ $match: { sensorid: 74797, datetime: { $gte: startDate, $lt: endDate } } },
{ $sort: { datetime: 1 } },
{
$group: {
_id: {
$toDate: {
$subtract: [
{ $toLong: '$datetime' },
{ $mod: [{ $toLong: '$datetime' }, zeitinterval] }
]
}
},
cpmAvg: { $avg: '$values.counts_per_minute' },
countsAvg: { $avg: '$values.counts' },
count: { $sum: 1 }
}
},
{ $sort: { _id: 1 } }
])
```
#### Vorteile gegenüber alten `data_<sid>` Collections:
✅ Zentralisiert: Nur eine Collection statt tausende
✅ Einfachere Wartung und Backups
✅ Sensor-übergreifende Queries möglich
✅ Index auf `sensorid` für schnelle Filterung
#### Nachteile:
⚠️ Größere Collection → Indizes wichtiger
⚠️ `sensorid` muss bei jeder Query gefiltert werden
---
### 3. **thp_sensors** (Zentrale Klima-Messdaten-Collection)
Analog zu `radioactivity_sensors` werden alle Klima-Sensordaten (Temperatur, Luftfeuchtigkeit, Luftdruck) in einer einzigen Collection gespeichert.
#### Struktur:
```javascript
{
datetime: { // Zeitstempel der Messung
$date: Date
},
sensorid: Number, // Sensor-ID (Referenz zu properties._id)
values: { // Messwerte
temperature: Number, // Temperatur in °C
pressure: Number, // Luftdruck in Pa
humidity: Number, // Luftfeuchtigkeit in %
pressure_at_sealevel: Number // Normierter Luftdruck auf Meereshöhe in Pa
}
}
```
#### Beispiel:
```javascript
{
datetime: { $date: "2024-06-20T11:02:08.000Z" },
sensorid: 550,
values: {
temperature: 28.42,
pressure: 98326.75,
humidity: 47.15,
pressure_at_sealevel: 101559.71
}
}
```
#### Verwendung:
```javascript
// Alle Daten für einen THP-Sensor abrufen
db.collection('thp_sensors').find({
sensorid: 550,
datetime: { $gte: new Date(start), $lt: new Date(end) }
}).sort({ datetime: 1 }).toArray()
// Aggregation für Durchschnittswerte
db.collection('thp_sensors').aggregate([
{ $match: { sensorid: 550, datetime: { $gte: startDate, $lt: endDate } } },
{ $sort: { datetime: 1 } },
{
$group: {
_id: {
$toDate: {
$subtract: [
{ $toLong: '$datetime' },
{ $mod: [{ $toLong: '$datetime' }, zeitinterval] }
]
}
},
tempAvg: { $avg: '$values.temperature' },
humiAvg: { $avg: '$values.humidity' },
pressAvg: { $avg: '$values.pressure_at_sealevel' },
count: { $sum: 1 }
}
},
{ $sort: { _id: 1 } }
])
```
---
## Wichtige Änderungen für die Migration
### 1. Name-Feld
**Alt:**
```javascript
name: "Radiation Si22G"
```
**Neu:**
```javascript
name: [
{
name: "Radiation Si22G",
since: { $date: "2024-06-20T10:13:12.970Z" }
}
]
```
**Zugriff auf aktuellen Namen:**
```javascript
const currentName = sensor.name[sensor.name.length - 1].name;
```
---
### 2. Location-Feld
**Alt:**
```javascript
location: [
{
loc: { type: "Point", coordinates: [lon, lat] },
altitude: 62,
address: {
country: "AU",
plz: "2000",
city: "Sydney"
}
}
]
```
**Neu:**
```javascript
location: [
{
loc: { type: "Point", coordinates: [lon, lat] },
id: 70638,
altitude: 62,
since: { $date: "2024-06-20T10:13:12.970Z" },
exact_loc: 0,
indoor: 0,
country: "AU"
}
]
```
**Zugriff auf aktuelle Location:**
```javascript
const currentLoc = sensor.location[sensor.location.length - 1];
const country = currentLoc.country; // Nicht mehr currentLoc.address.country!
const isIndoor = currentLoc.indoor === 1; // Nicht mehr Boolean!
```
---
### 3. Aktuelle Werte
**Alt:** Separate `mapdata` Collection
```javascript
db.collection('mapdata').findOne({_id: sensorId})
```
**Neu:** Direkt in `properties.values`
```javascript
const sensor = db.collection('properties').findOne({_id: sensorId});
const currentValue = sensor.values.counts_per_minute;
const lastUpdate = sensor.values.timestamp.$date;
```
---
### 4. Indoor-Flag
**Alt:**
```javascript
if (sensor.indoor) { ... } // Boolean
```
**Neu:**
```javascript
if (sensor.location[sensor.location.length - 1].indoor === 1) { ... } // Number!
```
---
## Neue Messwerte-Felder
### hv_pulses (Hochspannungs-Pulse)
Anzahl der Hochspannungspulse, die zur Ansteuerung des Geiger-Müller-Zählrohrs verwendet wurden.
### counts (Gesamt-Counts)
Gesamtzahl der registrierten Impulse während der Messperiode.
### sample_time_ms (Messzeit)
Dauer der Messperiode in Millisekunden. Wichtig für genaue CPM-Berechnungen:
```javascript
const cpm = (counts / sample_time_ms) * 60000;
```
---
## Migrations-Checkliste
### Code-Anpassungen erforderlich:
#### Datenbank-Zugriffe:
- [ ] **Messdaten-Collection**: Von `db.collection('data_' + sid)` zu `db.collection('radioactivity_sensors')`
- [ ] **Sensor-Filter**: Query um `sensorid: sid` erweitern (statt Collection-Name)
- [ ] **Indizes**: `sensorid` und `datetime` Index auf `radioactivity_sensors` erstellen
- [ ] **Zeitstempel-Feld**: `datetime.$date` statt nur `datetime` beim Lesen beachten
#### Metadaten-Zugriffe:
- [ ] **Name-Zugriff**: Von `sensor.name` zu `sensor.name[sensor.name.length-1].name`
- [ ] **Country-Zugriff**: Von `sensor.location[i].address.country` zu `sensor.location[i].country`
- [ ] **Indoor-Check**: Von Boolean zu Number-Vergleich (`=== 1`)
- [ ] **Aktuelle Werte**: Von `mapdata` Collection zu `properties.values`
- [ ] **Address-Felder**: Entfernen von `plz`, `city`, `street` (nur noch `country`)
#### Neue Felder:
- [ ] **Messwerte**: `values.counts_per_minute` statt direktem `counts_per_minute`
- [ ] **Neue Metriken**: `hv_pulses`, `counts`, `sample_time_ms` verarbeiten
- [ ] **Location-ID**: Neues Feld `location.id` berücksichtigen
- [ ] **exact_loc**: Neues Feld für Positionsgenauigkeit
- [ ] **Zeitstempel-Format**: `$date`-Wrapper bei MongoDB-Exporten beachten
#### Performance-Optimierungen:
- [ ] **Compound Index**: `{sensorid: 1, datetime: 1}` auf `radioactivity_sensors`
- [ ] **Projection**: Nur benötigte Felder aus `values` laden
- [ ] **Query-Limits**: Pagination bei großen Zeiträumen über mehrere Sensoren
### Migrations-Beispiele:
#### Messdaten abrufen:
**Alt:**
```javascript
const collection = db.collection('data_' + sid);
const docs = await collection.find({
datetime: { $gte: new Date(start), $lt: new Date(end) }
}).sort({ datetime: 1 }).toArray();
```
**Neu:**
```javascript
const collection = db.collection('radioactivity_sensors');
const docs = await collection.find({
sensorid: sid,
'datetime.$date': { $gte: new Date(start), $lt: new Date(end) }
}).sort({ 'datetime.$date': 1 }).toArray();
// Werte-Zugriff:
const cpm = docs[0].values.counts_per_minute; // Nicht docs[0].counts_per_minute!
```
#### Aggregation:
**Alt:**
```javascript
const collection = db.collection('data_' + sid);
const docs = await collection.aggregate([
{ $match: { datetime: { $gte: start, $lt: end } } },
{ $group: {
_id: ...,
cpmAvg: { $avg: '$counts_per_minute' }
}}
]).toArray();
```
**Neu:**
```javascript
const collection = db.collection('radioactivity_sensors');
const docs = await collection.aggregate([
{ $match: {
sensorid: sid,
'datetime.$date': { $gte: start, $lt: end }
}},
{ $group: {
_id: ...,
cpmAvg: { $avg: '$values.counts_per_minute' } // Path in values!
}}
]).toArray();
```
### Vorteile der neuen Struktur:
**Einfacher**: Keine separate `mapdata` Collection nötig
**Historisch**: Name- und Location-Änderungen werden nachvollziehbar
**Fokussiert**: Nur Radioaktivitätssensoren, keine Multi-Sensor-Logik
**Zusätzliche Metriken**: Mehr technische Details (HV-Pulse, Sample-Time)
**Zentralisiert**: Alle Messdaten in einer Collection statt tausenden
**Sensor-übergreifend**: Queries über mehrere Sensoren gleichzeitig möglich
**Wartungsfreundlich**: Keine dynamischen Collection-Namen mehr
### Nachteile / Herausforderungen:
⚠️ **Weniger Details**: Keine vollständigen Adressen (Stadt, PLZ, Straße)
⚠️ **Breaking Changes**: Viele Code-Stellen müssen angepasst werden
⚠️ **Boolean → Number**: Weniger typsicher für `indoor` und `exact_loc`
⚠️ **Indizes kritisch**: Ohne richtigen Index auf `sensorid` + `datetime` langsam
⚠️ **Größere Collection**: Mehr Daten in einer Collection → Monitoring wichtig
⚠️ **Nested Values**: Alle Messwerte in `values` → längere Accesspfade
### Empfohlene Indizes für neue Struktur:
```javascript
// Essenziell für radioactivity_sensors
db.radioactivity_sensors.createIndex({ sensorid: 1, "datetime.$date": 1 });
db.radioactivity_sensors.createIndex({ "datetime.$date": -1 }); // Für neueste Werte
// Für properties
db.properties.createIndex({ type: 1 });
db.properties.createIndex({ "location.country": 1 });
db.properties.createIndex({ "location.loc": "2dsphere" }); // Geo-Queries
db.properties.createIndex({ "values.timestamp.$date": -1 }); // Aktualitäts-Check
```
---
## Zusammenfassung: Kritischste Änderungen
### 🔴 Kritisch - Muss angepasst werden:
1. **Collection-Zugriff für Messdaten**
- ❌ Alt: `db.collection('data_' + sid)`
- ✅ Neu: `db.collection('radioactivity_sensors')` mit `{sensorid: sid}`
2. **Messwerte-Zugriff**
- ❌ Alt: `doc.counts_per_minute`
- ✅ Neu: `doc.values.counts_per_minute`
3. **Name-Zugriff**
- ❌ Alt: `sensor.name`
- ✅ Neu: `sensor.name[sensor.name.length-1].name`
4. **Adress-Zugriff**
- ❌ Alt: `sensor.location[i].address.country`
- ✅ Neu: `sensor.location[i].country`
### 🟡 Wichtig - Sollte beachtet werden:
5. **Indoor-Flag**: `indoor === 1` statt `indoor === true`
6. **Zeitstempel**: `datetime.$date` statt `datetime` bei Exporten
7. **Aktuelle Werte**: `properties.values` statt `mapdata` Collection
8. **Index**: Compound Index auf `{sensorid: 1, "datetime.$date": 1}` essentiell!
### 🟢 Optional - Neue Features:
9. **Neue Metriken**: `hv_pulses`, `counts`, `sample_time_ms` verfügbar
10. **Location-Historie**: `location[].since` zeigt Gültigkeit
11. **Positionsgenauigkeit**: `exact_loc` (0=ungefähr, 1=exakt)
12. **Location-ID**: `location[].id` für eindeutige Identifikation
---
**Stand der Dokumentation:** 27. März 2026

45
docs/info.md Normal file
View File

@@ -0,0 +1,45 @@
#Infos zur Karte
###Allgemein
Auf der Karte wird jede Zählstation mit dem Radioaktivitäts-Symbol angezeigt. Die Farbe des Symbols ändert sich mit der Zählrate, der Zusammenhang ist rechts oben in der Legende dargestellt.
Hat der Sensor seit mind. 1 Stunde keine Daten mehr gesendet, so wird er dunkelgrau eingefärbt.
Die Karte ist standardmäßig auf Stuttgart ausgerichtet. Wenn der Aufruf der Webseite mit einer Sensornummer erfolgt, so wird die Karte auf diesen Sensor zentriert (z.B. für den Sensor Nr. 34188 ist der Aufruf dann: <https://multigeiger.citysensor.de/34188>).
###Bedienelemente
* Die Karte kann mit dem Mausrad oder den beiden Knöpfen **+/-** in der linken obere Ecke ein- und ausgezoomed werden.
* Mit gedrückter Maustaste läßt sich die Karte verschieben.
Über der Karte befindet sich die Navigationsleiste mit folgenden Knöpfen/Eingabefeldern:
* **Zählrohre**
Damit wird umgeschaltet, ob **alle** Sensoren oder nur die Sensoren, die ein **Si22G**-Zählrohr haben, angezeigt werden.
* **Ort oder Sensornummer suchen**
In dieses Eingabefeld kann ein Ort oder eine Sensornummer eingegeben werden. Die Karte wird dann darauf zentriert.
* **Info**
Es erscheint diese Info-Seite.
Ein Klick auf das Atom-Symbol bringt eine Info-Tafel zur Anzeige. Auf dieser Tafel stehen folgende Informationen:
* Sensor-Nummer
* Typ des Zählrohres
* Adresse des Sensors (falls in der Datenbank vorhanden)
* Aktueller Messwert in cpm (Impulse pro Minute) und µSv/h (Micro-Sievert pro Stunde)
* Link **Grafik anzeigen**
###Grafik
Durch Klick auf **Grafik anzeigen** öffnet sich ein Fenster, das den Verlauf der letzten 24 Stunden anzeigt.
Die angezeigten Werte sind jeweils der Mittelwert über 10min.
Über die Knöpfe **-24h -12h +12h +24h** kann der Zeitstrahl um die jeweilige Anzahl an Stunden verschoben werden.
Der Knopf **live** schaltet wieder auf den Live-Mode um, d.h. es werden wieder die Daten der letzten 24 Stunden angezeigt.
Über den Knopf **7d** kann der Verlauf einer ganzen Woche betrachtet werden.
Auch hier kann der Zeitstrahl verschoben werden, und zwar um 3 bzw. 7 Tage.
Außerdem kann über den **Einstellung**-Knopf festgelegt werden, über welchen Zeitraum die Werte gemittelt werden
und ob eine gleitende oder statische Mittelwertbildung durchgeführt weden soll.
Über den Knopf **30d** wird die Darstellung der Tagesmittelwerte jeden Tages der letzten 30 Tage als Balken-Diagramm angezeigt.
Durch Klick auf den Knopf **Ende** wird die Grafik verlassen.

170
geiger-app.js Executable file
View File

@@ -0,0 +1,170 @@
const express = require('express');
const geigerApp = express();
//var assert = require('assert');
const bodyParser = require("body-parser");
const MongoClient = require('mongodb').MongoClient;
const os = require('os');
const moment = require('moment');
// Consts
const PORT = process.env.SERVERPORT || 3005; // Port for server
const debug = (process.env.DEBUG == "true");
const MONGOHOST = process.env.MONGOHOST || 'localhost';
const MONGOPORT = process.env.MONGOPORT || 27017;
const MONGOAUTH = (process.env.MONGOAUTH == "true");
const MONGOUSRP = process.env.MONGOUSRP || "";
const MONGOBASE = process.env.MONGOBASE || 'Feinstaubi_A';
const MONGO_URL = MONGOAUTH ? 'mongodb://'+MONGOUSRP+'@' + MONGOHOST + ':' + MONGOPORT + '/?authSource=admin' : 'mongodb://'+MONGOHOST+':'+MONGOPORT; // URL to mongo database
console.log(os.hostname());
if (debug) {
console.log(`MongoURL = "${MONGO_URL}" and Database = ${MONGOBASE}`);
}
geigerApp.set('views','./views');
geigerApp.set('view engine','pug');
geigerApp.use(express.static("public"));
geigerApp.use(express.static("node_modules/bootstrap/dist"));
geigerApp.use(express.static("node_modules/jquery/dist"));
geigerApp.use(express.static("node_modules/moment/min"));
geigerApp.use(express.static("node_modules/leaflet/dist"));
geigerApp.use(express.static("node_modules/d3/dist"));
let requested;
async function checkHost(req, res, next) {
if (
(req.headers.host == 'test1.rexfue.de') ||
(req.headers.host == 'multigeiger.rexfue.de') ||
(req.headers.host == 'multigeiger.citysensor.de') ||
(req.headers.host == 'test2.citysensor.de') ||
(req.headers.host == 'localhost:'+PORT) ||
(req.headers.host == 'nuccy:3005') ||
(req.headers.host == 'h2953026.stratoserver.net:8082') ||
(req.headers.host == '213.136.85.253:'+PORT) ||
(req.headers.host == '192.168.178.78:'+PORT) ||
(req.headers.host == 'macbig:'+PORT) //Port is important if the url has it
) {
req.url = '/fs' + req.url;
}
// console.log("Path:",req.path);
if(req.path.startsWith('/TEST')) {
req.url = '/TEST' + req.url;
}
let uri = req.url.substr(3);
let city = "unknown";
let dbs = geigerApp.get('dbase');
// if (!isNaN(uri.substring(1) - parseInt(uri.substring(1))))
if (isNaN(uri.substring(1))) {
city = await apidatas.api.getCity(dbs, parseInt(uri.substring(1)));
}
if(
(!isNaN(uri.substring(1) - parseInt(uri.substring(1)))) ||
(uri.substring(1,4)=='api') ||
((uri.substring(1,4) == 'map') && (uri.substring(4,5) != 'd'))
) {
console.log(moment().format()," ", uri, " ", city);
}
if (req.url.substring(4,5) == 'i') {
req.url = '/fs/' + req.url.substring(5);
}
next();
}
geigerApp.get('*', checkHost);
geigerApp.post('*', checkHost);
geigerApp.use(bodyParser.urlencoded({ extended: true }));
geigerApp.use(bodyParser.json());
//app.post('/sensors', function(res,req,next){
// var body = res.body;
// var espid = res.headers['x-sensor'];
//
// console.log(espid,body);
//
//})
geigerApp.get('/fs/fsdata/help', function(req, res, next) {
res.sendFile(__dirname+'/public/info.html');
});
geigerApp.get('/fs/fsdata/splash', function(req, res, next) {
res.sendFile(__dirname+'/public/splash.html');
});
geigerApp.get('/fs/fsdata/settingW', function(req, res, next) {
res.sendFile(__dirname+'/public/settingsW.html');
});
geigerApp.get('/fs/fsdata/settingD', function(req, res, next) {
res.sendFile(__dirname+'/public/settingsD.html');
});
geigerApp.get('/fs/fsdata/statistik', function(req, res, next) {
res.sendFile(__dirname+'/public/statistik.html');
});
geigerApp.get('/fs/fsdata/centermap', function(req, res, next) {
res.sendFile(__dirname+'/public/centermap.html');
});
geigerApp.get('/fs/fsdata/helpmap', function(req, res, next) {
res.sendFile(__dirname+'/public/helpmap.html');
});
geigerApp.get('/fs/fsdata/fehlersensoren', function(req, res, next) {
res.sendFile(__dirname+'/public/fehlersensoren.html');
});
geigerApp.get('/fs/fsdata/fehlerliste', function(req, res, next) {
res.sendFile(__dirname+'/public/fehlerliste.html');
});
geigerApp.get('/fs/fsdata/selsensor', function(req, res, next) {
res.sendFile(__dirname+'/public/selsensor.html');
});
geigerApp.get('/fs/fsdata/ymax', function(req, res, next) {
res.sendFile(__dirname+'/public/ymax.html');
});
geigerApp.get('/fs/fsdata/selnewday', function(req, res, next) {
res.sendFile(__dirname+'/public/selnewday.html');
});
geigerApp.get('/fs/fsdata/erralert', function(req, res, next) {
res.sendFile(__dirname+'/public/erralert.html');
});
var indexs = require('./routes/index');
geigerApp.use('/fs/',indexs);
var fsdatas1 = require('./routes/fsdata');
geigerApp.use('/fs/fsdata',fsdatas1);
var fsdatas2 = require('./routes/mapdata');
geigerApp.use('/fs/mapdata',fsdatas2);
var apidatas = require('./routes/apidata');
geigerApp.use('/fs/api',apidatas);
const connect = MongoClient.connect(MONGO_URL, {useNewUrlParser: true ,useUnifiedTopology: true});
connect
.then(client => {
geigerApp.set('dbase', client.db(MONGOBASE)); // Übergabe von db
geigerApp.listen(PORT, function () {
console.log(moment().format("YYYY-MM-DD HH:mm"), "App listens on port " + PORT +', Mongo at ' + MONGOHOST);
})
})
.catch(err => {
console.log(err);
process.exit(-1);
});

6809
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Executable file
View File

@@ -0,0 +1,30 @@
{
"name": "multigeiger",
"version": "2.9.6",
"date": "2022-03-16",
"description": "Graphics for multigeiger sensors",
"main": "geiger-app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node geiger-app.js >>/var/log/geiger-web.log 2>&1"
},
"author": "rxf@gmail.com",
"license": "ISC",
"dependencies": {
"axios": "^0.26.1",
"body-parser": "^1.19.2",
"bootstrap": "^5.1.3",
"d3": "^7.3.0",
"express": "^4.17.3",
"gpx-parse": "^0.10.4",
"jquery": "^3.6.0",
"jquery-ui": "^1.13.1",
"leaflet": "^1.7.1",
"math": "0.0.3",
"mathjs": "^10.4.0",
"moment": "^2.29.1",
"mongodb": "^4.4.1",
"npm": "^8.5.4",
"pug": "^3.0.2"
}
}

BIN
public/.DS_Store vendored Normal file

Binary file not shown.

2330
public/Stuttgart.gpx Normal file

File diff suppressed because it is too large Load Diff

12
public/centermap.html Normal file
View File

@@ -0,0 +1,12 @@
<br />
<form onkeypress="return event.keyCode != 13;">
<fieldset>
<label for="newmapcenter">Karte neu zentrieren auf (Ort oder Sensor-Nr):&nbsp;&nbsp;</label>
<input class='text ui-widget-content ui-corner-all' name = 'newmapcenter' id='newmapcenter' type='text' />
<!-- <br />
<label for="sensornr">Default Sensor-Nummer (wird verwendet, wenn beim Aufruf kein Sensor angegeben ist):</label>
<input class='text ui-widget-content ui-corner-all' name = 'sensornr' id='sensornr' type='text' placeholder='Sensor-Nummer' />
-->
<input type='submit' tabindex='-1' style="position:absolute; top:-1000px">
</fieldset>
</form>

BIN
public/css/.DS_Store vendored Normal file

Binary file not shown.

1
public/css/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/ui-lightness/

569
public/css/jBox.css Executable file
View File

@@ -0,0 +1,569 @@
/* Global */
.jBox-wrapper {
text-align: left;
}
.jBox-wrapper,
.jBox-wrapper * {
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.jBox-title,
.jBox-content,
.jBox-container {
position: relative;
word-break: break-word;
}
.jBox-container {
background: #fff;
}
.jBox-content {
padding: 8px 10px;
overflow: auto;
-webkit-transition: opacity .15s;
transition: opacity .15s;
}
/* jBox Tooltip */
.jBox-Tooltip .jBox-container,
.jBox-Mouse .jBox-container {
border-radius: 3px;
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
}
.jBox-Tooltip .jBox-title,
.jBox-Mouse .jBox-title {
padding: 8px 10px 0;
font-weight: bold;
}
.jBox-hasTitle.jBox-Tooltip .jBox-content,
.jBox-hasTitle.jBox-Mouse .jBox-content {
padding-top: 5px;
}
/* Pointer */
.jBox-pointer {
position: absolute;
overflow: hidden;
}
.jBox-pointer-top { top: 0; }
.jBox-pointer-bottom { bottom: 0; }
.jBox-pointer-left { left: 0; }
.jBox-pointer-right { right: 0; }
.jBox-pointer-top,
.jBox-pointer-bottom {
width: 30px;
height: 12px;
}
.jBox-pointer-left,
.jBox-pointer-right {
width: 12px;
height: 30px;
}
.jBox-pointer:after {
content: '';
width: 20px;
height: 20px;
position: absolute;
background: #fff;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.jBox-pointer-top:after {
left: 5px;
top: 6px;
box-shadow: -1px -1px 4px rgba(0, 0, 0, .2);
}
.jBox-pointer-right:after {
top: 5px;
right: 6px;
box-shadow: 1px -1px 4px rgba(0, 0, 0, .2);
}
.jBox-pointer-bottom:after {
left: 5px;
bottom: 6px;
box-shadow: 1px 1px 4px rgba(0, 0, 0, .2);
}
.jBox-pointer-left:after {
top: 5px;
left: 6px;
box-shadow: -1px 1px 4px rgba(0, 0, 0, .2);
}
/* jBox Modal & jBox Confirm */
.jBox-Modal .jBox-container,
.jBox-Confirm .jBox-container {
border-radius: 3px;
box-shadow: 0 3px 15px rgba(0, 0, 0, .4), 0 0 5px rgba(0, 0, 0, .4);
}
.jBox-Modal .jBox-title,
.jBox-Confirm .jBox-title {
border-radius: 3px 3px 0 0;
padding: 10px 15px;
background: #f4f5f6;
border-bottom: 1px solid #ddd;
text-shadow: 0 1px 0 #fff;
}
.jBox-Modal.jBox-closeButton-title .jBox-title,
.jBox-Confirm.jBox-closeButton-title .jBox-title {
padding-right: 55px;
}
.jBox-Modal.jBox-closeButton-box:before,
.jBox-Confirm.jBox-closeButton-box:before {
box-shadow: 0 3px 15px rgba(0, 0, 0, .4), 0 0 5px rgba(0, 0, 0, .4);
}
/* jBox Modal */
.jBox-Modal .jBox-content {
padding: 12px 15px;
}
/* jBox Confirm */
.jBox-Confirm .jBox-content {
text-align: center;
padding: 45px 35px;
}
.jBox-Confirm-footer {
border-top: 1px solid #e2e2e2;
background: #fafafa;
border-radius: 0 0 3px 3px;
text-align: center;
padding: 10px 0;
}
.jBox-Confirm-button {
display: inline-block;
cursor: pointer;
font-size: 15px;
line-height: 30px;
height: 30px;
border-radius: 3px;
padding: 0 20px;
-webkit-transition: color .2s, background-color .2s;
transition: color .2s, background-color .2s;
}
.jBox-Confirm-button-cancel {
text-shadow: 0 1px 1px rgba(255, 255, 255, .6);
background: #ddd;
color: #999;
margin-right: 25px;
}
.jBox-Confirm-button-cancel:hover {
background: #ccc;
color: #666;
}
.jBox-Confirm-button-submit {
text-shadow: 0 -1px 1px rgba(0, 0, 0, .2);
background: #5fc04c;
color: #fff;
}
.jBox-Confirm-button-submit:hover {
background: #53a642;
}
.jBox-Confirm-button-cancel:active,
.jBox-Confirm-button-submit:active {
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .26);
}
/* jBox Notice */
.jBox-Notice {
-webkit-transition: margin .2s;
transition: margin .2s;
}
.jBox-Notice .jBox-container {
border-radius: 3px;
box-shadow: 0 0 3px rgba(0, 0, 0, .2);
color: #fff;
text-shadow: 0 -1px 0 #000;
background: #333;
background-image: linear-gradient(to bottom, #444, #222);
}
.jBox-Notice .jBox-content {
border-radius: 3px;
padding: 12px 20px;
}
.jBox-Notice .jBox-title {
padding: 8px 20px 0;
font-weight: bold;
}
.jBox-hasTitle.jBox-Notice .jBox-content {
padding-top: 5px;
}
.jBox-Notice-color .jBox-container {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .3);
}
.jBox-Notice-gray .jBox-container {
color: #666;
text-shadow: 0 1px 0 #fff;
background: #f4f4f4;
background-image: linear-gradient(to bottom, #fafafa, #f0f0f0);
}
.jBox-Notice-red .jBox-container {
background: #b02222;
background-image: linear-gradient(to bottom, #ee2222, #b02222);
}
.jBox-Notice-green .jBox-container {
background: #70a800;
background-image: linear-gradient(to bottom, #95cc2a, #70a800);
}
.jBox-Notice-blue .jBox-container {
background: #2b91d9;
background-image: linear-gradient(to bottom, #5abaff, #2b91d9);
}
.jBox-Notice-yellow .jBox-container {
color: #744700;
text-shadow: 0 1px 0 rgba(255, 255, 255, .6);
background: #ffb11f;
background-image: linear-gradient(to bottom, #ffd665, #ffb11f);
}
/* jBox Image */
.jBox-Image {
background: #fff;
padding: 8px 8px 45px;
border-radius: 5px;
}
.jBox-Image .jBox-content {
padding: 0;
width: 100%;
height: 100%;
}
.jBox-image-container {
border-radius: 5px;
background: #fff center center no-repeat;
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
}
.jBox-image-label {
box-sizing: border-box;
position: absolute;
background: #fff;
top: 100%;
left: 0;
width: 100%;
color: #333;
margin-top: -35px;
padding: 0 90px 5px 10px;
border-radius: 0 0 5px 5px;
-webkit-transition: opacity .3s;
transition: opacity .3s;
opacity: 0;
}
.jBox-image-label.active {
opacity: 1;
}
.jBox-image-pointer-next,
.jBox-image-pointer-prev {
position: absolute;
bottom: 0px;
width: 22px;
height: 45px;
background: no-repeat center center url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ijc0LjcgMjI0IDE4LjcgMzIiPg0KPHBhdGggZmlsbD0iIzAwMDAwMCIgZD0iTTkzLDIyNy40TDgwLjQsMjQwTDkzLDI1Mi42YzAuNCwwLjQsMC40LDEuMSwwLDEuNWwtMS42LDEuNmMtMC40LDAuNC0xLDAuNS0xLjUsMEw3NSwyNDAuN2MtMC40LTAuNC0wLjUtMSwwLTEuNWwxNC45LTE0LjljMC40LTAuNCwxLTAuNCwxLjUsMGwxLjYsMS42QzkzLjUsMjI2LjQsOTMuNCwyMjcsOTMsMjI3LjR6Ii8+DQo8L3N2Zz4=);
background-size: 11px auto;
cursor: pointer;
opacity: .6;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transition: opacity .2s;
transition: opacity .2s;
}
.jBox-image-pointer-next:hover,
.jBox-image-pointer-prev:hover {
opacity: 1;
}
.jBox-image-pointer-next {
right: 8px;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
}
.jBox-image-pointer-prev {
right: 30px;
}
.jBox-image-open #jBox-overlay {
background-color: rgba(0, 0, 0, .86);
}
.jBox-Image.jBox-loading .jBox-container:before {
left: auto;
top: auto;
bottom: -33px;
right: 55px;
margin-top: -9px;
margin-left: -9px;
}
/* Close button */
.jBox-closeButton {
cursor: pointer;
position: absolute;
}
.jBox-closeButton svg {
position: absolute;
top: 50%;
right: 50%;
}
.jBox-closeButton path {
-webkit-transition: fill .2s;
transition: fill .2s;
}
.jBox-closeButton path {
fill: #aaa;
}
.jBox-closeButton:hover path {
fill: #888;
}
.jBox-closeButton:active path {
fill: #666;
}
/* Close button in overlay */
#jBox-overlay .jBox-closeButton {
top: 0;
right: 0;
width: 40px;
height: 40px;
}
#jBox-overlay .jBox-closeButton svg {
width: 20px;
height: 20px;
margin-top: -10px;
margin-right: -10px;
}
#jBox-overlay .jBox-closeButton path {
fill: #d2d4d6;
}
#jBox-overlay .jBox-closeButton:hover path {
fill: #fff;
}
#jBox-overlay .jBox-closeButton:active path {
fill: #b2b4b6;
}
/* Close button in title */
.jBox-closeButton-title .jBox-closeButton {
top: 0;
right: 0;
bottom: 0;
width: 40px;
}
.jBox-closeButton-title .jBox-closeButton svg {
width: 12px;
height: 12px;
margin-top: -6px;
margin-right: -6px;
}
/* Close button in box */
.jBox-closeButton-box .jBox-closeButton {
top: -8px;
right: -10px;
width: 24px;
height: 24px;
background: #fff;
border-radius: 50%;
}
.jBox-closeButton-box .jBox-closeButton svg {
width: 10px;
height: 10px;
margin-top: -5px;
margin-right: -5px;
}
.jBox-hasTitle.jBox-Modal.jBox-closeButton-box .jBox-closeButton {
background: #f4f5f6;
}
.jBox-closeButton-box:before {
content: '';
position: absolute;
top: -8px;
right: -10px;
width: 24px;
height: 24px;
border-radius: 50%;
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
}
.jBox-pointerPosition-top.jBox-closeButton-box:before {
top: 4px;
}
.jBox-pointerPosition-right.jBox-closeButton-box:before {
right: 2px;
}
/* Overlay */
#jBox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
background-color: rgba(0, 0, 0, .6);
}
/* Block scrolling */
body[class^="jBox-blockScroll-"],
body[class*=" jBox-blockScroll-"] {
overflow: hidden;
}
/* Draggable */
.jBox-draggable {
cursor: move;
}
/* Spinner */
@keyframes jBoxLoading {
to {transform: rotate(360deg);}
}
@-webkit-keyframes jBoxLoading {
to {-webkit-transform: rotate(360deg);}
}
.jBox-loading .jBox-content {
min-height: 32px;
min-width: 38px;
opacity: 0;
}
.jBox-spinner {
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
}
.jBox-spinner:before {
content: 'Loading…';
display: block;
width: 20px;
height: 20px;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.jBox-spinner:not(:required):before {
content: '';
border-radius: 50%;
border: 2px solid rgba(0, 0, 0, .3);
border-top-color: rgba(0, 0, 0, .6);
animation: jBoxLoading .6s linear infinite;
-webkit-animation: jBoxLoading .6s linear infinite;
}
/* IE8 fixes */
.jBox-IE8.jBox-Tooltip .jBox-container,
.jBox-IE8.jBox-Mouse .jBox-container {
border: 1px solid #aaa;
}
.jBox-IE8 .jBox-pointer:after {
display: none;
}
.jBox-IE8 .jBox-pointer {
border: 0;
background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAYAAACN1PRVAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNq01l0OwyAIAGAlvY+n8ZJ6Gk/EqqkNtf7ApCQ+LM34iuCmRUQzihjj6FH+kjWL8N4/Ph9GHpiTnC9SwDbhLGyvspSScc71KkOa/HpuuRhIK+psE2pjONouCQg7kBSEXUgC2tHo52mTTBpnaEATWlaYK6MrhIAaceWpOcsCrYp6FV4H/90zTWjUQ/gSevVQq0ecHqoOxWpYoO7p5O9ku2fnVtp7QAik2rsK3fnpWfjynJWpbw+1BkghurrYDjiCptg/4AxaYhJwBbEwDsiB2NgM5EIirAdKIDFGQSmU1+NaIPjJYt2I25vxT4ABAMhWvtle2YvmAAAAAElFTkSuQmCC);
}
.jBox-IE8 .jBox-pointer-top { background-position: center top; }
.jBox-IE8 .jBox-pointer-bottom { background-position: center bottom; }
.jBox-IE8 .jBox-pointer-left { background-position: left center; }
.jBox-IE8 .jBox-pointer-right { background-position: right center; }
.jBox-IE8.jBox-Modal .jBox-container {
border: 3px solid #aaa;
}
/* No SVG support fixes */
.jBox-nosvg .jBox-closeButton:before {
font-family: Verdana, sans-serif;
content: 'x';
text-align: center;
font-size: 18px;
color: #888;
}

View File

@@ -0,0 +1,333 @@
Authors ordered by first contribution
A list of current team members is available at http://jqueryui.com/about
Paul Bakaus <paul.bakaus@gmail.com>
Richard Worth <rdworth@gmail.com>
Yehuda Katz <wycats@gmail.com>
Sean Catchpole <sean@sunsean.com>
John Resig <jeresig@gmail.com>
Tane Piper <piper.tane@gmail.com>
Dmitri Gaskin <dmitrig01@gmail.com>
Klaus Hartl <klaus.hartl@gmail.com>
Stefan Petre <stefan.petre@gmail.com>
Gilles van den Hoven <gilles@webunity.nl>
Micheil Bryan Smith <micheil@brandedcode.com>
Jörn Zaefferer <joern.zaefferer@gmail.com>
Marc Grabanski <m@marcgrabanski.com>
Keith Wood <kbwood@iinet.com.au>
Brandon Aaron <brandon.aaron@gmail.com>
Scott González <scott.gonzalez@gmail.com>
Eduardo Lundgren <eduardolundgren@gmail.com>
Aaron Eisenberger <aaronchi@gmail.com>
Joan Piedra <theneojp@gmail.com>
Bruno Basto <b.basto@gmail.com>
Remy Sharp <remy@leftlogic.com>
Bohdan Ganicky <bohdan.ganicky@gmail.com>
David Bolter <david.bolter@gmail.com>
Chi Cheng <cloudream@gmail.com>
Ca-Phun Ung <pazu2k@gmail.com>
Ariel Flesler <aflesler@gmail.com>
Maggie Wachs <maggie@filamentgroup.com>
Scott Jehl <scottjehl@gmail.com>
Todd Parker <todd@filamentgroup.com>
Andrew Powell <andrew@shellscape.org>
Brant Burnett <btburnett3@gmail.com>
Douglas Neiner <doug@dougneiner.com>
Paul Irish <paul.irish@gmail.com>
Ralph Whitbeck <ralph.whitbeck@gmail.com>
Thibault Duplessis <thibault.duplessis@gmail.com>
Dominique Vincent <dominique.vincent@toitl.com>
Jack Hsu <jack.hsu@gmail.com>
Adam Sontag <ajpiano@ajpiano.com>
Carl Fürstenberg <carl@excito.com>
Kevin Dalman <development@allpro.net>
Alberto Fernández Capel <afcapel@gmail.com>
Jacek Jędrzejewski (http://jacek.jedrzejewski.name)
Ting Kuei <ting@kuei.com>
Samuel Cormier-Iijima <sam@chide.it>
Jon Palmer <jonspalmer@gmail.com>
Ben Hollis <bhollis@amazon.com>
Justin MacCarthy <Justin@Rubystars.biz>
Eyal Kobrigo <kobrigo@hotmail.com>
Tiago Freire <tiago.freire@gmail.com>
Diego Tres <diegotres@gmail.com>
Holger Rüprich <holger@rueprich.de>
Ziling Zhao <zilingzhao@gmail.com>
Mike Alsup <malsup@gmail.com>
Robson Braga Araujo <robsonbraga@gmail.com>
Pierre-Henri Ausseil <ph.ausseil@gmail.com>
Christopher McCulloh <cmcculloh@gmail.com>
Andrew Newcomb <ext.github@preceptsoftware.co.uk>
Lim Chee Aun <cheeaun@gmail.com>
Jorge Barreiro <yortx.barry@gmail.com>
Daniel Steigerwald <daniel@steigerwald.cz>
John Firebaugh <john_firebaugh@bigfix.com>
John Enters <github@darkdark.net>
Andrey Kapitcyn <ru.m157y@gmail.com>
Dmitry Petrov <dpetroff@gmail.com>
Eric Hynds <eric@hynds.net>
Chairat Sunthornwiphat <pipo@sixhead.com>
Josh Varner <josh.varner@gmail.com>
Stéphane Raimbault <stephane.raimbault@gmail.com>
Jay Merrifield <fracmak@gmail.com>
J. Ryan Stinnett <jryans@gmail.com>
Peter Heiberg <peter@heiberg.se>
Alex Dovenmuehle <adovenmuehle@gmail.com>
Jamie Gegerson <git@jamiegegerson.com>
Raymond Schwartz <skeetergraphics@gmail.com>
Phillip Barnes <philbar@gmail.com>
Kyle Wilkinson <kai@wikyd.org>
Khaled AlHourani <me@khaledalhourani.com>
Marian Rudzynski <mr@impaled.org>
Jean-Francois Remy <jeff@melix.org>
Doug Blood <dougblood@gmail.com>
Filippo Cavallarin <filippo.cavallarin@codseq.it>
Heiko Henning <heiko@thehennings.ch>
Aliaksandr Rahalevich <saksmlz@gmail.com>
Mario Visic <mario@mariovisic.com>
Xavi Ramirez <xavi.rmz@gmail.com>
Max Schnur <max.schnur@gmail.com>
Saji Nediyanchath <saji89@gmail.com>
Corey Frang <gnarf37@gmail.com>
Aaron Peterson <aaronp123@yahoo.com>
Ivan Peters <ivan@ivanpeters.com>
Mohamed Cherif Bouchelaghem <cherifbouchelaghem@yahoo.fr>
Marcos Sousa <falecomigo@marcossousa.com>
Michael DellaNoce <mdellanoce@mailtrust.com>
George Marshall <echosx@gmail.com>
Tobias Brunner <tobias@strongswan.org>
Martin Solli <msolli@gmail.com>
David Petersen <public@petersendidit.com>
Dan Heberden <danheberden@gmail.com>
William Kevin Manire <williamkmanire@gmail.com>
Gilmore Davidson <gilmoreorless@gmail.com>
Michael Wu <michaelmwu@gmail.com>
Adam Parod <mystic414@gmail.com>
Guillaume Gautreau <guillaume+github@ghusse.com>
Marcel Toele <EleotleCram@gmail.com>
Dan Streetman <ddstreet@ieee.org>
Matt Hoskins <matt@nipltd.com>
Giovanni Giacobbi <giovanni@giacobbi.net>
Kyle Florence <kyle.florence@gmail.com>
Pavol Hluchý <lopo@losys.sk>
Hans Hillen <hans.hillen@gmail.com>
Mark Johnson <virgofx@live.com>
Trey Hunner <treyhunner@gmail.com>
Shane Whittet <whittet@gmail.com>
Edward A Faulkner <ef@alum.mit.edu>
Adam Baratz <adam@adambaratz.com>
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
Eike Send <eike.send@gmail.com>
Kris Borchers <kris.borchers@gmail.com>
Eddie Monge <eddie@eddiemonge.com>
Israel Tsadok <itsadok@gmail.com>
Carson McDonald <carson@ioncannon.net>
Jason Davies <jason@jasondavies.com>
Garrison Locke <gplocke@gmail.com>
David Murdoch <david@davidmurdoch.com>
Benjamin Scott Boyle <benjamins.boyle@gmail.com>
Jesse Baird <jebaird@gmail.com>
Jonathan Vingiano <jvingiano@gmail.com>
Dylan Just <dev@ephox.com>
Hiroshi Tomita <tomykaira@gmail.com>
Glenn Goodrich <glenn.goodrich@gmail.com>
Tarafder Ashek-E-Elahi <mail.ashek@gmail.com>
Ryan Neufeld <ryan@neufeldmail.com>
Marc Neuwirth <marc.neuwirth@gmail.com>
Philip Graham <philip.robert.graham@gmail.com>
Benjamin Sterling <benjamin.sterling@kenzomedia.com>
Wesley Walser <waw325@gmail.com>
Kouhei Sutou <kou@clear-code.com>
Karl Kirch <karlkrch@gmail.com>
Chris Kelly <ckdake@ckdake.com>
Jason Oster <jay@kodewerx.org>
Felix Nagel <info@felixnagel.com>
Alexander Polomoshnov <alex.polomoshnov@gmail.com>
David Leal <dgleal@gmail.com>
Igor Milla <igor.fsp.milla@gmail.com>
Dave Methvin <dave.methvin@gmail.com>
Florian Gutmann <f.gutmann@chronimo.com>
Marwan Al Jubeh <marwan.aljubeh@gmail.com>
Milan Broum <midlis@googlemail.com>
Sebastian Sauer <info@dynpages.de>
Gaëtan Muller <m.gaetan89@gmail.com>
Michel Weimerskirch <michel@weimerskirch.net>
William Griffiths <william@ycymro.com>
Stojce Slavkovski <stojce@gmail.com>
David Soms <david.soms@gmail.com>
David De Sloovere <david.desloovere@outlook.com>
Michael P. Jung <michael.jung@terreon.de>
Shannon Pekary <spekary@gmail.com>
Dan Wellman <danwellman@hotmail.com>
Matthew Edward Hutton <meh@corefiling.co.uk>
James Khoury <james@jameskhoury.com>
Rob Loach <robloach@gmail.com>
Alberto Monteiro <betimbrasil@gmail.com>
Alex Rhea <alex.rhea@gmail.com>
Krzysztof Rosiński <rozwell69@gmail.com>
Ryan Olton <oltonr@gmail.com>
Genie <386@mail.com>
Rick Waldron <waldron.rick@gmail.com>
Ian Simpson <spoonlikesham@gmail.com>
Lev Kitsis <spam4lev@gmail.com>
TJ VanToll <tj.vantoll@gmail.com>
Justin Domnitz <jdomnitz@gmail.com>
Douglas Cerna <douglascerna@yahoo.com>
Bert ter Heide <bertjh@hotmail.com>
Jasvir Nagra <jasvir@gmail.com>
Yuriy Khabarov <13real008@gmail.com>
Harri Kilpiö <harri.kilpio@gmail.com>
Lado Lomidze <lado.lomidze@gmail.com>
Amir E. Aharoni <amir.aharoni@mail.huji.ac.il>
Simon Sattes <simon.sattes@gmail.com>
Jo Liss <joliss42@gmail.com>
Guntupalli Karunakar <karunakarg@yahoo.com>
Shahyar Ghobadpour <shahyar@gmail.com>
Lukasz Lipinski <uzza17@gmail.com>
Timo Tijhof <krinklemail@gmail.com>
Jason Moon <jmoon@socialcast.com>
Martin Frost <martinf55@hotmail.com>
Eneko Illarramendi <eneko@illarra.com>
EungJun Yi <semtlenori@gmail.com>
Courtland Allen <courtlandallen@gmail.com>
Viktar Varvanovich <non4eg@gmail.com>
Danny Trunk <dtrunk90@gmail.com>
Pavel Stetina <pavel.stetina@nangu.tv>
Michael Stay <metaweta@gmail.com>
Steven Roussey <sroussey@gmail.com>
Michael Hollis <hollis21@gmail.com>
Lee Rowlands <lee.rowlands@previousnext.com.au>
Timmy Willison <timmywillisn@gmail.com>
Karl Swedberg <kswedberg@gmail.com>
Baoju Yuan <the_guy_1987@hotmail.com>
Maciej Mroziński <maciej.k.mrozinski@gmail.com>
Luis Dalmolin <luis.nh@gmail.com>
Mark Aaron Shirley <maspwr@gmail.com>
Martin Hoch <martin@fidion.de>
Jiayi Yang <tr870829@gmail.com>
Philipp Benjamin Köppchen <xgxtpbk@gws.ms>
Sindre Sorhus <sindresorhus@gmail.com>
Bernhard Sirlinger <bernhard.sirlinger@tele2.de>
Jared A. Scheel <jared@jaredscheel.com>
Rafael Xavier de Souza <rxaviers@gmail.com>
John Chen <zhang.z.chen@intel.com>
Robert Beuligmann <robertbeuligmann@gmail.com>
Dale Kocian <dale.kocian@gmail.com>
Mike Sherov <mike.sherov@gmail.com>
Andrew Couch <andy@couchand.com>
Marc-Andre Lafortune <github@marc-andre.ca>
Nate Eagle <nate.eagle@teamaol.com>
David Souther <davidsouther@gmail.com>
Mathias Stenbom <mathias@stenbom.com>
Sergey Kartashov <ebishkek@yandex.ru>
Avinash R <nashpapa@gmail.com>
Ethan Romba <ethanromba@gmail.com>
Cory Gackenheimer <cory.gack@gmail.com>
Juan Pablo Kaniefsky <jpkaniefsky@gmail.com>
Roman Salnikov <bardt.dz@gmail.com>
Anika Henke <anika@selfthinker.org>
Samuel Bovée <samycookie2000@yahoo.fr>
Fabrício Matté <ult_combo@hotmail.com>
Viktor Kojouharov <vkojouharov@gmail.com>
Pawel Maruszczyk (http://hrabstwo.net)
Pavel Selitskas <p.selitskas@gmail.com>
Bjørn Johansen <post@bjornjohansen.no>
Matthieu Penant <thieum22@hotmail.com>
Dominic Barnes <dominic@dbarnes.info>
David Sullivan <david.sullivan@gmail.com>
Thomas Jaggi <thomas@responsive.ch>
Vahid Sohrabloo <vahid4134@gmail.com>
Travis Carden <travis.carden@gmail.com>
Bruno M. Custódio <bruno@brunomcustodio.com>
Nathanael Silverman <nathanael.silverman@gmail.com>
Christian Wenz <christian@wenz.org>
Steve Urmston <steve@urm.st>
Zaven Muradyan <megalivoithos@gmail.com>
Woody Gilk <shadowhand@deviantart.com>
Zbigniew Motyka <zbigniew.motyka@gmail.com>
Suhail Alkowaileet <xsoh.k7@gmail.com>
Toshi MARUYAMA <marutosijp2@yahoo.co.jp>
David Hansen <hansede@gmail.com>
Brian Grinstead <briangrinstead@gmail.com>
Christian Klammer <christian314159@gmail.com>
Steven Luscher <jquerycla@steveluscher.com>
Gan Eng Chin <engchin.gan@gmail.com>
Gabriel Schulhof <gabriel.schulhof@intel.com>
Alexander Schmitz <arschmitz@gmail.com>
Vilhjálmur Skúlason <vis@dmm.is>
Siebrand Mazeland <siebrand@kitano.nl>
Mohsen Ekhtiari <mohsenekhtiari@yahoo.com>
Pere Orga <gotrunks@gmail.com>
Jasper de Groot <mail@ugomobi.com>
Stephane Deschamps <stephane.deschamps@gmail.com>
Jyoti Deka <dekajp@gmail.com>
Andrei Picus <office.nightcrawler@gmail.com>
Ondrej Novy <novy@ondrej.org>
Jacob McCutcheon <jacob.mccutcheon@gmail.com>
Monika Piotrowicz <monika.piotrowicz@gmail.com>
Imants Horsts <imants.horsts@inbox.lv>
Eric Dahl <eric.c.dahl@gmail.com>
Dave Stein <dave@behance.com>
Dylan Barrell <dylan@barrell.com>
Daniel DeGroff <djdegroff@gmail.com>
Michael Wiencek <mwtuea@gmail.com>
Thomas Meyer <meyertee@gmail.com>
Ruslan Yakhyaev <ruslan@ruslan.io>
Brian J. Dowling <bjd-dev@simplicity.net>
Ben Higgins <ben@extrahop.com>
Yermo Lamers <yml@yml.com>
Patrick Stapleton <github@gdi2290.com>
Trisha Crowley <trisha.crowley@gmail.com>
Usman Akeju <akeju00+github@gmail.com>
Rodrigo Menezes <rod333@gmail.com>
Jacques Perrault <jacques_perrault@us.ibm.com>
Frederik Elvhage <frederik.elvhage@googlemail.com>
Will Holley <willholley@gmail.com>
Uri Gilad <antishok@gmail.com>
Richard Gibson <richard.gibson@gmail.com>
Simen Bekkhus <sbekkhus91@gmail.com>
Chen Eshchar <eshcharc@gmail.com>
Bruno Pérel <brunoperel@gmail.com>
Mohammed Alshehri <m@dralshehri.com>
Lisa Seacat DeLuca <ldeluca@us.ibm.com>
Anne-Gaelle Colom <coloma@westminster.ac.uk>
Adam Foster <slimfoster@gmail.com>
Luke Page <luke.a.page@gmail.com>
Daniel Owens <daniel@matchstickmixup.com>
Michael Orchard <morchard@scottlogic.co.uk>
Marcus Warren <marcus@envoke.com>
Nils Heuermann <nils@world-of-scripts.de>
Marco Ziech <marco@ziech.net>
Patricia Juarez <patrixd@gmail.com>
Ben Mosher <me@benmosher.com>
Ablay Keldibek <atomio.ak@gmail.com>
Thomas Applencourt <thomas.applencourt@irsamc.ups-tlse.fr>
Jiabao Wu <jiabao.foss@gmail.com>
Eric Lee Carraway <github@ericcarraway.com>
Victor Homyakov <vkhomyackov@gmail.com>
Myeongjin Lee <aranet100@gmail.com>
Liran Sharir <lsharir@gmail.com>
Weston Ruter <weston@xwp.co>
Mani Mishra <manimishra902@gmail.com>
Hannah Methvin <hannahmethvin@gmail.com>
Leonardo Balter <leonardo.balter@gmail.com>
Benjamin Albert <benjamin_a5@yahoo.com>
Michał Gołębiowski <m.goleb@gmail.com>
Alyosha Pushak <alyosha.pushak@gmail.com>
Fahad Ahmad <fahadahmad41@hotmail.com>
Matt Brundage <github@mattbrundage.com>
Francesc Baeta <francesc.baeta@gmail.com>
Piotr Baran <piotros@wp.pl>
Mukul Hase <mukulhase@gmail.com>
Konstantin Dinev <kdinev@mail.bw.edu>
Rand Scullard <rand@randscullard.com>
Dan Strohl <dan@wjcg.net>
Maksim Ryzhikov <rv.maksim@gmail.com>
Amine HADDAD <haddad@allegorie.tv>
Amanpreet Singh <apsdehal@gmail.com>
Alexey Balchunas <bleshik@gmail.com>
Peter Kehl <peter.kehl@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Johannes Schäfer <johnschaefer@gmx.de>
Ville Skyttä <ville.skytta@iki.fi>
Ryan Oriecuia <ryan.oriecuia@visioncritical.com>

View File

@@ -0,0 +1,43 @@
Copyright jQuery Foundation and other contributors, https://jquery.org/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/jquery-ui
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code contained within the demos directory.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,559 @@
<!doctype html>
<html lang="us">
<head>
<meta charset="utf-8">
<title>jQuery UI Example Page</title>
<link href="jquery-ui.css" rel="stylesheet">
<style>
body{
font-family: "Trebuchet MS", sans-serif;
margin: 50px;
}
.demoHeaders {
margin-top: 2em;
}
#dialog-link {
padding: .4em 1em .4em 20px;
text-decoration: none;
position: relative;
}
#dialog-link span.ui-icon {
margin: 0 5px 0 0;
position: absolute;
left: .2em;
top: 50%;
margin-top: -8px;
}
#icons {
margin: 0;
padding: 0;
}
#icons li {
margin: 2px;
position: relative;
padding: 4px 0;
cursor: pointer;
float: left;
list-style: none;
}
#icons span.ui-icon {
float: left;
margin: 0 4px;
}
.fakewindowcontain .ui-widget-overlay {
position: absolute;
}
select {
width: 200px;
}
</style>
</head>
<body>
<h1>Welcome to jQuery UI!</h1>
<div class="ui-widget">
<p>This page demonstrates the widgets and theme you selected in Download Builder. Please make sure you are using them with a compatible jQuery version.</p>
</div>
<h1>YOUR COMPONENTS:</h1>
<!-- Accordion -->
<h2 class="demoHeaders">Accordion</h2>
<div id="accordion">
<h3>First</h3>
<div>Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.</div>
<h3>Second</h3>
<div>Phasellus mattis tincidunt nibh.</div>
<h3>Third</h3>
<div>Nam dui erat, auctor a, dignissim quis.</div>
</div>
<!-- Autocomplete -->
<h2 class="demoHeaders">Autocomplete</h2>
<div>
<input id="autocomplete" title="type &quot;a&quot;">
</div>
<!-- Button -->
<h2 class="demoHeaders">Button</h2>
<button id="button">A button element</button>
<button id="button-icon">An icon-only button</button>
<!-- Checkboxradio -->
<h2 class="demoHeaders">Checkboxradio</h2>
<form style="margin-top: 1em;">
<div id="radioset">
<input type="radio" id="radio1" name="radio"><label for="radio1">Choice 1</label>
<input type="radio" id="radio2" name="radio" checked="checked"><label for="radio2">Choice 2</label>
<input type="radio" id="radio3" name="radio"><label for="radio3">Choice 3</label>
</div>
</form>
<!-- Controlgroup -->
<h2 class="demoHeaders">Controlgroup</h2>
<fieldset>
<legend>Rental Car</legend>
<div id="controlgroup">
<select id="car-type">
<option>Compact car</option>
<option>Midsize car</option>
<option>Full size car</option>
<option>SUV</option>
<option>Luxury</option>
<option>Truck</option>
<option>Van</option>
</select>
<label for="transmission-standard">Standard</label>
<input type="radio" name="transmission" id="transmission-standard">
<label for="transmission-automatic">Automatic</label>
<input type="radio" name="transmission" id="transmission-automatic">
<label for="insurance">Insurance</label>
<input type="checkbox" name="insurance" id="insurance">
<label for="horizontal-spinner" class="ui-controlgroup-label"># of cars</label>
<input id="horizontal-spinner" class="ui-spinner-input">
<button>Book Now!</button>
</div>
</fieldset>
<!-- Tabs -->
<h2 class="demoHeaders">Tabs</h2>
<div id="tabs">
<ul>
<li><a href="#tabs-1">First</a></li>
<li><a href="#tabs-2">Second</a></li>
<li><a href="#tabs-3">Third</a></li>
</ul>
<div id="tabs-1">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div>
<div id="tabs-2">Phasellus mattis tincidunt nibh. Cras orci urna, blandit id, pretium vel, aliquet ornare, felis. Maecenas scelerisque sem non nisl. Fusce sed lorem in enim dictum bibendum.</div>
<div id="tabs-3">Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.</div>
</div>
<h2 class="demoHeaders">Dialog</h2>
<p>
<button id="dialog-link" class="ui-button ui-corner-all ui-widget">
<span class="ui-icon ui-icon-newwin"></span>Open Dialog
</button>
</p>
<h2 class="demoHeaders">Overlay and Shadow Classes</h2>
<div style="position: relative; width: 96%; height: 200px; padding:1% 2%; overflow:hidden;" class="fakewindowcontain">
<p>Lorem ipsum dolor sit amet, Nulla nec tortor. Donec id elit quis purus consectetur consequat. </p><p>Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. </p><p>Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. </p><p>Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. </p><p>Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. </p><p>Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. </p>
<!-- ui-dialog -->
<div class="ui-widget-overlay ui-front"></div>
<div style="position: absolute; width: 320px; left: 50px; top: 30px; padding: 1.2em" class="ui-widget ui-front ui-widget-content ui-corner-all ui-widget-shadow">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</div>
<!-- ui-dialog -->
<div id="dialog" title="Dialog Title">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
<h2 class="demoHeaders">Framework Icons (content color preview)</h2>
<ul id="icons" class="ui-widget ui-helper-clearfix">
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-n"><span class="ui-icon ui-icon-caret-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-ne"><span class="ui-icon ui-icon-caret-1-ne"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-e"><span class="ui-icon ui-icon-caret-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-se"><span class="ui-icon ui-icon-caret-1-se"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-s"><span class="ui-icon ui-icon-caret-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-sw"><span class="ui-icon ui-icon-caret-1-sw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-w"><span class="ui-icon ui-icon-caret-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-nw"><span class="ui-icon ui-icon-caret-1-nw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-2-n-s"><span class="ui-icon ui-icon-caret-2-n-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-2-e-w"><span class="ui-icon ui-icon-caret-2-e-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-n"><span class="ui-icon ui-icon-triangle-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-ne"><span class="ui-icon ui-icon-triangle-1-ne"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-e"><span class="ui-icon ui-icon-triangle-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-se"><span class="ui-icon ui-icon-triangle-1-se"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-s"><span class="ui-icon ui-icon-triangle-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-sw"><span class="ui-icon ui-icon-triangle-1-sw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-w"><span class="ui-icon ui-icon-triangle-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-nw"><span class="ui-icon ui-icon-triangle-1-nw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-2-n-s"><span class="ui-icon ui-icon-triangle-2-n-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-2-e-w"><span class="ui-icon ui-icon-triangle-2-e-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-n"><span class="ui-icon ui-icon-arrow-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-ne"><span class="ui-icon ui-icon-arrow-1-ne"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-e"><span class="ui-icon ui-icon-arrow-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-se"><span class="ui-icon ui-icon-arrow-1-se"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-s"><span class="ui-icon ui-icon-arrow-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-sw"><span class="ui-icon ui-icon-arrow-1-sw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-w"><span class="ui-icon ui-icon-arrow-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-nw"><span class="ui-icon ui-icon-arrow-1-nw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-n-s"><span class="ui-icon ui-icon-arrow-2-n-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-ne-sw"><span class="ui-icon ui-icon-arrow-2-ne-sw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-e-w"><span class="ui-icon ui-icon-arrow-2-e-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-se-nw"><span class="ui-icon ui-icon-arrow-2-se-nw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-n"><span class="ui-icon ui-icon-arrowstop-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-e"><span class="ui-icon ui-icon-arrowstop-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-s"><span class="ui-icon ui-icon-arrowstop-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-w"><span class="ui-icon ui-icon-arrowstop-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-n"><span class="ui-icon ui-icon-arrowthick-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-ne"><span class="ui-icon ui-icon-arrowthick-1-ne"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-e"><span class="ui-icon ui-icon-arrowthick-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-se"><span class="ui-icon ui-icon-arrowthick-1-se"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-s"><span class="ui-icon ui-icon-arrowthick-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-sw"><span class="ui-icon ui-icon-arrowthick-1-sw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-w"><span class="ui-icon ui-icon-arrowthick-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-nw"><span class="ui-icon ui-icon-arrowthick-1-nw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-n-s"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-ne-sw"><span class="ui-icon ui-icon-arrowthick-2-ne-sw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-e-w"><span class="ui-icon ui-icon-arrowthick-2-e-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-se-nw"><span class="ui-icon ui-icon-arrowthick-2-se-nw"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-n"><span class="ui-icon ui-icon-arrowthickstop-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-e"><span class="ui-icon ui-icon-arrowthickstop-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-s"><span class="ui-icon ui-icon-arrowthickstop-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-w"><span class="ui-icon ui-icon-arrowthickstop-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-w"><span class="ui-icon ui-icon-arrowreturnthick-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-n"><span class="ui-icon ui-icon-arrowreturnthick-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-e"><span class="ui-icon ui-icon-arrowreturnthick-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-s"><span class="ui-icon ui-icon-arrowreturnthick-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-w"><span class="ui-icon ui-icon-arrowreturn-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-n"><span class="ui-icon ui-icon-arrowreturn-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-e"><span class="ui-icon ui-icon-arrowreturn-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-s"><span class="ui-icon ui-icon-arrowreturn-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-w"><span class="ui-icon ui-icon-arrowrefresh-1-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-n"><span class="ui-icon ui-icon-arrowrefresh-1-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-e"><span class="ui-icon ui-icon-arrowrefresh-1-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-s"><span class="ui-icon ui-icon-arrowrefresh-1-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-4"><span class="ui-icon ui-icon-arrow-4"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-4-diag"><span class="ui-icon ui-icon-arrow-4-diag"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-extlink"><span class="ui-icon ui-icon-extlink"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-newwin"><span class="ui-icon ui-icon-newwin"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-refresh"><span class="ui-icon ui-icon-refresh"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-shuffle"><span class="ui-icon ui-icon-shuffle"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-transfer-e-w"><span class="ui-icon ui-icon-transfer-e-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-transferthick-e-w"><span class="ui-icon ui-icon-transferthick-e-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-folder-collapsed"><span class="ui-icon ui-icon-folder-collapsed"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-folder-open"><span class="ui-icon ui-icon-folder-open"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-document"><span class="ui-icon ui-icon-document"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-document-b"><span class="ui-icon ui-icon-document-b"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-note"><span class="ui-icon ui-icon-note"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-mail-closed"><span class="ui-icon ui-icon-mail-closed"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-mail-open"><span class="ui-icon ui-icon-mail-open"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-suitcase"><span class="ui-icon ui-icon-suitcase"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-comment"><span class="ui-icon ui-icon-comment"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-person"><span class="ui-icon ui-icon-person"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-print"><span class="ui-icon ui-icon-print"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-trash"><span class="ui-icon ui-icon-trash"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-locked"><span class="ui-icon ui-icon-locked"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-unlocked"><span class="ui-icon ui-icon-unlocked"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-bookmark"><span class="ui-icon ui-icon-bookmark"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-tag"><span class="ui-icon ui-icon-tag"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-home"><span class="ui-icon ui-icon-home"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-flag"><span class="ui-icon ui-icon-flag"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-calculator"><span class="ui-icon ui-icon-calculator"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-cart"><span class="ui-icon ui-icon-cart"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-pencil"><span class="ui-icon ui-icon-pencil"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-clock"><span class="ui-icon ui-icon-clock"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-disk"><span class="ui-icon ui-icon-disk"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-calendar"><span class="ui-icon ui-icon-calendar"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-zoomin"><span class="ui-icon ui-icon-zoomin"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-zoomout"><span class="ui-icon ui-icon-zoomout"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-search"><span class="ui-icon ui-icon-search"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-wrench"><span class="ui-icon ui-icon-wrench"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-gear"><span class="ui-icon ui-icon-gear"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-heart"><span class="ui-icon ui-icon-heart"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-star"><span class="ui-icon ui-icon-star"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-link"><span class="ui-icon ui-icon-link"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-cancel"><span class="ui-icon ui-icon-cancel"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-plus"><span class="ui-icon ui-icon-plus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-plusthick"><span class="ui-icon ui-icon-plusthick"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-minus"><span class="ui-icon ui-icon-minus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-minusthick"><span class="ui-icon ui-icon-minusthick"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-close"><span class="ui-icon ui-icon-close"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-closethick"><span class="ui-icon ui-icon-closethick"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-key"><span class="ui-icon ui-icon-key"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-lightbulb"><span class="ui-icon ui-icon-lightbulb"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-scissors"><span class="ui-icon ui-icon-scissors"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-clipboard"><span class="ui-icon ui-icon-clipboard"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-copy"><span class="ui-icon ui-icon-copy"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-contact"><span class="ui-icon ui-icon-contact"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-image"><span class="ui-icon ui-icon-image"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-video"><span class="ui-icon ui-icon-video"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-script"><span class="ui-icon ui-icon-script"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-alert"><span class="ui-icon ui-icon-alert"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-info"><span class="ui-icon ui-icon-info"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-notice"><span class="ui-icon ui-icon-notice"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-help"><span class="ui-icon ui-icon-help"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-check"><span class="ui-icon ui-icon-check"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-bullet"><span class="ui-icon ui-icon-bullet"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-radio-off"><span class="ui-icon ui-icon-radio-off"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-radio-on"><span class="ui-icon ui-icon-radio-on"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-pin-w"><span class="ui-icon ui-icon-pin-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-pin-s"><span class="ui-icon ui-icon-pin-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-play"><span class="ui-icon ui-icon-play"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-pause"><span class="ui-icon ui-icon-pause"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-next"><span class="ui-icon ui-icon-seek-next"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-prev"><span class="ui-icon ui-icon-seek-prev"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-end"><span class="ui-icon ui-icon-seek-end"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-first"><span class="ui-icon ui-icon-seek-first"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-stop"><span class="ui-icon ui-icon-stop"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-eject"><span class="ui-icon ui-icon-eject"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-volume-off"><span class="ui-icon ui-icon-volume-off"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-volume-on"><span class="ui-icon ui-icon-volume-on"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-power"><span class="ui-icon ui-icon-power"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-signal-diag"><span class="ui-icon ui-icon-signal-diag"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-signal"><span class="ui-icon ui-icon-signal"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-0"><span class="ui-icon ui-icon-battery-0"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-1"><span class="ui-icon ui-icon-battery-1"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-2"><span class="ui-icon ui-icon-battery-2"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-3"><span class="ui-icon ui-icon-battery-3"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-plus"><span class="ui-icon ui-icon-circle-plus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-minus"><span class="ui-icon ui-icon-circle-minus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-close"><span class="ui-icon ui-icon-circle-close"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-e"><span class="ui-icon ui-icon-circle-triangle-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-s"><span class="ui-icon ui-icon-circle-triangle-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-w"><span class="ui-icon ui-icon-circle-triangle-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-n"><span class="ui-icon ui-icon-circle-triangle-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-e"><span class="ui-icon ui-icon-circle-arrow-e"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-s"><span class="ui-icon ui-icon-circle-arrow-s"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-w"><span class="ui-icon ui-icon-circle-arrow-w"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-n"><span class="ui-icon ui-icon-circle-arrow-n"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-zoomin"><span class="ui-icon ui-icon-circle-zoomin"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-zoomout"><span class="ui-icon ui-icon-circle-zoomout"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-check"><span class="ui-icon ui-icon-circle-check"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circlesmall-plus"><span class="ui-icon ui-icon-circlesmall-plus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circlesmall-minus"><span class="ui-icon ui-icon-circlesmall-minus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-circlesmall-close"><span class="ui-icon ui-icon-circlesmall-close"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-squaresmall-plus"><span class="ui-icon ui-icon-squaresmall-plus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-squaresmall-minus"><span class="ui-icon ui-icon-squaresmall-minus"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-squaresmall-close"><span class="ui-icon ui-icon-squaresmall-close"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-dotted-vertical"><span class="ui-icon ui-icon-grip-dotted-vertical"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-dotted-horizontal"><span class="ui-icon ui-icon-grip-dotted-horizontal"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-solid-vertical"><span class="ui-icon ui-icon-grip-solid-vertical"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-solid-horizontal"><span class="ui-icon ui-icon-grip-solid-horizontal"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-gripsmall-diagonal-se"><span class="ui-icon ui-icon-gripsmall-diagonal-se"></span></li>
<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-diagonal-se"><span class="ui-icon ui-icon-grip-diagonal-se"></span></li>
</ul>
<!-- Slider -->
<h2 class="demoHeaders">Slider</h2>
<div id="slider"></div>
<!-- Datepicker -->
<h2 class="demoHeaders">Datepicker</h2>
<div id="datepicker"></div>
<!-- Progressbar -->
<h2 class="demoHeaders">Progressbar</h2>
<div id="progressbar"></div>
<!-- Progressbar -->
<h2 class="demoHeaders">Selectmenu</h2>
<select id="selectmenu">
<option>Slower</option>
<option>Slow</option>
<option selected="selected">Medium</option>
<option>Fast</option>
<option>Faster</option>
</select>
<!-- Spinner -->
<h2 class="demoHeaders">Spinner</h2>
<input id="spinner">
<!-- Menu -->
<h2 class="demoHeaders">Menu</h2>
<ul style="width:100px;" id="menu">
<li><div>Item 1</div></li>
<li><div>Item 2</div></li>
<li><div>Item 3</div>
<ul>
<li><div>Item 3-1</div></li>
<li><div>Item 3-2</div></li>
<li><div>Item 3-3</div></li>
<li><div>Item 3-4</div></li>
<li><div>Item 3-5</div></li>
</ul>
</li>
<li><div>Item 4</div></li>
<li><div>Item 5</div></li>
</ul>
<!-- Tooltip -->
<h2 class="demoHeaders">Tooltip</h2>
<p id="tooltip">
<a href="#" title="That&apos;s what this widget is">Tooltips</a> can be attached to any element. When you hover
the element with your mouse, the title attribute is displayed in a little box next to the element, just like a native tooltip.
</p>
<!-- Highlight / Error -->
<h2 class="demoHeaders">Highlight / Error</h2>
<div class="ui-widget">
<div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0 .7em;">
<p><span class="ui-icon ui-icon-info" style="float: left; margin-right: .3em;"></span>
<strong>Hey!</strong> Sample ui-state-highlight style.</p>
</div>
</div>
<br>
<div class="ui-widget">
<div class="ui-state-error ui-corner-all" style="padding: 0 .7em;">
<p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;"></span>
<strong>Alert:</strong> Sample ui-state-error style.</p>
</div>
</div>
<script src="external/jquery/jquery.js"></script>
<script src="jquery-ui.js"></script>
<script>
$( "#accordion" ).accordion();
var availableTags = [
"ActionScript",
"AppleScript",
"Asp",
"BASIC",
"C",
"C++",
"Clojure",
"COBOL",
"ColdFusion",
"Erlang",
"Fortran",
"Groovy",
"Haskell",
"Java",
"JavaScript",
"Lisp",
"Perl",
"PHP",
"Python",
"Ruby",
"Scala",
"Scheme"
];
$( "#autocomplete" ).autocomplete({
source: availableTags
});
$( "#button" ).button();
$( "#button-icon" ).button({
icon: "ui-icon-gear",
showLabel: false
});
$( "#radioset" ).buttonset();
$( "#controlgroup" ).controlgroup();
$( "#tabs" ).tabs();
$( "#dialog" ).dialog({
autoOpen: false,
width: 400,
buttons: [
{
text: "Ok",
click: function() {
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
]
});
// Link to open the dialog
$( "#dialog-link" ).click(function( event ) {
$( "#dialog" ).dialog( "open" );
event.preventDefault();
});
$( "#datepicker" ).datepicker({
inline: true
});
$( "#slider" ).slider({
range: true,
values: [ 17, 67 ]
});
$( "#progressbar" ).progressbar({
value: 20
});
$( "#spinner" ).spinner();
$( "#menu" ).menu();
$( "#tooltip" ).tooltip();
$( "#selectmenu" ).selectmenu();
// Hover states on the static widgets
$( "#dialog-link, #icons li" ).hover(
function() {
$( this ).addClass( "ui-state-hover" );
},
function() {
$( this ).removeClass( "ui-state-hover" );
}
);
</script>
</body>
</html>

1312
public/css/jquery-ui-1.12.1/jquery-ui.css vendored Normal file

File diff suppressed because it is too large Load Diff

18706
public/css/jquery-ui-1.12.1/jquery-ui.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,886 @@
/*!
* jQuery UI CSS Framework 1.12.1
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/theming/
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0); /* support: IE8 */
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
pointer-events: none;
}
/* Icons
----------------------------------*/
.ui-icon {
display: inline-block;
vertical-align: middle;
margin-top: -.25em;
position: relative;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
.ui-widget-icon-block {
left: 50%;
margin-left: -8px;
display: block;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-accordion .ui-accordion-header {
display: block;
cursor: pointer;
position: relative;
margin: 2px 0 0 0;
padding: .5em .5em .5em .7em;
font-size: 100%;
}
.ui-accordion .ui-accordion-content {
padding: 1em 2.2em;
border-top: 0;
overflow: auto;
}
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
}
.ui-menu {
list-style: none;
padding: 0;
margin: 0;
display: block;
outline: 0;
}
.ui-menu .ui-menu {
position: absolute;
}
.ui-menu .ui-menu-item {
margin: 0;
cursor: pointer;
/* support: IE10, see #8844 */
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
}
.ui-menu .ui-menu-item-wrapper {
position: relative;
padding: 3px 1em 3px .4em;
}
.ui-menu .ui-menu-divider {
margin: 5px 0;
height: 0;
font-size: 0;
line-height: 0;
border-width: 1px 0 0 0;
}
.ui-menu .ui-state-focus,
.ui-menu .ui-state-active {
margin: -1px;
}
/* icon support */
.ui-menu-icons {
position: relative;
}
.ui-menu-icons .ui-menu-item-wrapper {
padding-left: 2em;
}
/* left-aligned */
.ui-menu .ui-icon {
position: absolute;
top: 0;
bottom: 0;
left: .2em;
margin: auto 0;
}
/* right-aligned */
.ui-menu .ui-menu-icon {
left: auto;
right: 0;
}
.ui-button {
padding: .4em 1em;
display: inline-block;
position: relative;
line-height: normal;
margin-right: .1em;
cursor: pointer;
vertical-align: middle;
text-align: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/* Support: IE <= 11 */
overflow: visible;
}
.ui-button,
.ui-button:link,
.ui-button:visited,
.ui-button:hover,
.ui-button:active {
text-decoration: none;
}
/* to make room for the icon, a width needs to be set here */
.ui-button-icon-only {
width: 2em;
box-sizing: border-box;
text-indent: -9999px;
white-space: nowrap;
}
/* no icon support for input elements */
input.ui-button.ui-button-icon-only {
text-indent: 0;
}
/* button icon element(s) */
.ui-button-icon-only .ui-icon {
position: absolute;
top: 50%;
left: 50%;
margin-top: -8px;
margin-left: -8px;
}
.ui-button.ui-icon-notext .ui-icon {
padding: 0;
width: 2.1em;
height: 2.1em;
text-indent: -9999px;
white-space: nowrap;
}
input.ui-button.ui-icon-notext .ui-icon {
width: auto;
height: auto;
text-indent: 0;
white-space: normal;
padding: .4em 1em;
}
/* workarounds */
/* Support: Firefox 5 - 40 */
input.ui-button::-moz-focus-inner,
button.ui-button::-moz-focus-inner {
border: 0;
padding: 0;
}
.ui-controlgroup {
vertical-align: middle;
display: inline-block;
}
.ui-controlgroup > .ui-controlgroup-item {
float: left;
margin-left: 0;
margin-right: 0;
}
.ui-controlgroup > .ui-controlgroup-item:focus,
.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus {
z-index: 9999;
}
.ui-controlgroup-vertical > .ui-controlgroup-item {
display: block;
float: none;
width: 100%;
margin-top: 0;
margin-bottom: 0;
text-align: left;
}
.ui-controlgroup-vertical .ui-controlgroup-item {
box-sizing: border-box;
}
.ui-controlgroup .ui-controlgroup-label {
padding: .4em 1em;
}
.ui-controlgroup .ui-controlgroup-label span {
font-size: 80%;
}
.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item {
border-left: none;
}
.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item {
border-top: none;
}
.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content {
border-right: none;
}
.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content {
border-bottom: none;
}
/* Spinner specific style fixes */
.ui-controlgroup-vertical .ui-spinner-input {
/* Support: IE8 only, Android < 4.4 only */
width: 75%;
width: calc( 100% - 2.4em );
}
.ui-controlgroup-vertical .ui-spinner .ui-spinner-up {
border-top-style: solid;
}
.ui-checkboxradio-label .ui-icon-background {
box-shadow: inset 1px 1px 1px #ccc;
border-radius: .12em;
border: none;
}
.ui-checkboxradio-radio-label .ui-icon-background {
width: 16px;
height: 16px;
border-radius: 1em;
overflow: visible;
border: none;
}
.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,
.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon {
background-image: none;
width: 8px;
height: 8px;
border-width: 4px;
border-style: solid;
}
.ui-checkboxradio-disabled {
pointer-events: none;
}
.ui-datepicker {
width: 17em;
padding: .2em .2em 0;
display: none;
}
.ui-datepicker .ui-datepicker-header {
position: relative;
padding: .2em 0;
}
.ui-datepicker .ui-datepicker-prev,
.ui-datepicker .ui-datepicker-next {
position: absolute;
top: 2px;
width: 1.8em;
height: 1.8em;
}
.ui-datepicker .ui-datepicker-prev-hover,
.ui-datepicker .ui-datepicker-next-hover {
top: 1px;
}
.ui-datepicker .ui-datepicker-prev {
left: 2px;
}
.ui-datepicker .ui-datepicker-next {
right: 2px;
}
.ui-datepicker .ui-datepicker-prev-hover {
left: 1px;
}
.ui-datepicker .ui-datepicker-next-hover {
right: 1px;
}
.ui-datepicker .ui-datepicker-prev span,
.ui-datepicker .ui-datepicker-next span {
display: block;
position: absolute;
left: 50%;
margin-left: -8px;
top: 50%;
margin-top: -8px;
}
.ui-datepicker .ui-datepicker-title {
margin: 0 2.3em;
line-height: 1.8em;
text-align: center;
}
.ui-datepicker .ui-datepicker-title select {
font-size: 1em;
margin: 1px 0;
}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year {
width: 45%;
}
.ui-datepicker table {
width: 100%;
font-size: .9em;
border-collapse: collapse;
margin: 0 0 .4em;
}
.ui-datepicker th {
padding: .7em .3em;
text-align: center;
font-weight: bold;
border: 0;
}
.ui-datepicker td {
border: 0;
padding: 1px;
}
.ui-datepicker td span,
.ui-datepicker td a {
display: block;
padding: .2em;
text-align: right;
text-decoration: none;
}
.ui-datepicker .ui-datepicker-buttonpane {
background-image: none;
margin: .7em 0 0 0;
padding: 0 .2em;
border-left: 0;
border-right: 0;
border-bottom: 0;
}
.ui-datepicker .ui-datepicker-buttonpane button {
float: right;
margin: .5em .2em .4em;
cursor: pointer;
padding: .2em .6em .3em .6em;
width: auto;
overflow: visible;
}
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
float: left;
}
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi {
width: auto;
}
.ui-datepicker-multi .ui-datepicker-group {
float: left;
}
.ui-datepicker-multi .ui-datepicker-group table {
width: 95%;
margin: 0 auto .4em;
}
.ui-datepicker-multi-2 .ui-datepicker-group {
width: 50%;
}
.ui-datepicker-multi-3 .ui-datepicker-group {
width: 33.3%;
}
.ui-datepicker-multi-4 .ui-datepicker-group {
width: 25%;
}
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
border-left-width: 0;
}
.ui-datepicker-multi .ui-datepicker-buttonpane {
clear: left;
}
.ui-datepicker-row-break {
clear: both;
width: 100%;
font-size: 0;
}
/* RTL support */
.ui-datepicker-rtl {
direction: rtl;
}
.ui-datepicker-rtl .ui-datepicker-prev {
right: 2px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next {
left: 2px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-prev:hover {
right: 1px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next:hover {
left: 1px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane {
clear: right;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button {
float: left;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
.ui-datepicker-rtl .ui-datepicker-group {
float: right;
}
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
border-right-width: 0;
border-left-width: 1px;
}
/* Icons */
.ui-datepicker .ui-icon {
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
left: .5em;
top: .3em;
}
.ui-dialog {
position: absolute;
top: 0;
left: 0;
padding: .2em;
outline: 0;
}
.ui-dialog .ui-dialog-titlebar {
padding: .4em 1em;
position: relative;
}
.ui-dialog .ui-dialog-title {
float: left;
margin: .1em 0;
white-space: nowrap;
width: 90%;
overflow: hidden;
text-overflow: ellipsis;
}
.ui-dialog .ui-dialog-titlebar-close {
position: absolute;
right: .3em;
top: 50%;
width: 20px;
margin: -10px 0 0 0;
padding: 1px;
height: 20px;
}
.ui-dialog .ui-dialog-content {
position: relative;
border: 0;
padding: .5em 1em;
background: none;
overflow: auto;
}
.ui-dialog .ui-dialog-buttonpane {
text-align: left;
border-width: 1px 0 0 0;
background-image: none;
margin-top: .5em;
padding: .3em 1em .5em .4em;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: right;
}
.ui-dialog .ui-dialog-buttonpane button {
margin: .5em .4em .5em 0;
cursor: pointer;
}
.ui-dialog .ui-resizable-n {
height: 2px;
top: 0;
}
.ui-dialog .ui-resizable-e {
width: 2px;
right: 0;
}
.ui-dialog .ui-resizable-s {
height: 2px;
bottom: 0;
}
.ui-dialog .ui-resizable-w {
width: 2px;
left: 0;
}
.ui-dialog .ui-resizable-se,
.ui-dialog .ui-resizable-sw,
.ui-dialog .ui-resizable-ne,
.ui-dialog .ui-resizable-nw {
width: 7px;
height: 7px;
}
.ui-dialog .ui-resizable-se {
right: 0;
bottom: 0;
}
.ui-dialog .ui-resizable-sw {
left: 0;
bottom: 0;
}
.ui-dialog .ui-resizable-ne {
right: 0;
top: 0;
}
.ui-dialog .ui-resizable-nw {
left: 0;
top: 0;
}
.ui-draggable .ui-dialog-titlebar {
cursor: move;
}
.ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
.ui-resizable {
position: relative;
}
.ui-resizable-handle {
position: absolute;
font-size: 0.1px;
display: block;
-ms-touch-action: none;
touch-action: none;
}
.ui-resizable-disabled .ui-resizable-handle,
.ui-resizable-autohide .ui-resizable-handle {
display: none;
}
.ui-resizable-n {
cursor: n-resize;
height: 7px;
width: 100%;
top: -5px;
left: 0;
}
.ui-resizable-s {
cursor: s-resize;
height: 7px;
width: 100%;
bottom: -5px;
left: 0;
}
.ui-resizable-e {
cursor: e-resize;
width: 7px;
right: -5px;
top: 0;
height: 100%;
}
.ui-resizable-w {
cursor: w-resize;
width: 7px;
left: -5px;
top: 0;
height: 100%;
}
.ui-resizable-se {
cursor: se-resize;
width: 12px;
height: 12px;
right: 1px;
bottom: 1px;
}
.ui-resizable-sw {
cursor: sw-resize;
width: 9px;
height: 9px;
left: -5px;
bottom: -5px;
}
.ui-resizable-nw {
cursor: nw-resize;
width: 9px;
height: 9px;
left: -5px;
top: -5px;
}
.ui-resizable-ne {
cursor: ne-resize;
width: 9px;
height: 9px;
right: -5px;
top: -5px;
}
.ui-progressbar {
height: 2em;
text-align: left;
overflow: hidden;
}
.ui-progressbar .ui-progressbar-value {
margin: -1px;
height: 100%;
}
.ui-progressbar .ui-progressbar-overlay {
background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
height: 100%;
filter: alpha(opacity=25); /* support: IE8 */
opacity: 0.25;
}
.ui-progressbar-indeterminate .ui-progressbar-value {
background-image: none;
}
.ui-selectable {
-ms-touch-action: none;
touch-action: none;
}
.ui-selectable-helper {
position: absolute;
z-index: 100;
border: 1px dotted black;
}
.ui-selectmenu-menu {
padding: 0;
margin: 0;
position: absolute;
top: 0;
left: 0;
display: none;
}
.ui-selectmenu-menu .ui-menu {
overflow: auto;
overflow-x: hidden;
padding-bottom: 1px;
}
.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
font-size: 1em;
font-weight: bold;
line-height: 1.5;
padding: 2px 0.4em;
margin: 0.5em 0 0 0;
height: auto;
border: 0;
}
.ui-selectmenu-open {
display: block;
}
.ui-selectmenu-text {
display: block;
margin-right: 20px;
overflow: hidden;
text-overflow: ellipsis;
}
.ui-selectmenu-button.ui-button {
text-align: left;
white-space: nowrap;
width: 14em;
}
.ui-selectmenu-icon.ui-icon {
float: right;
margin-top: 0;
}
.ui-slider {
position: relative;
text-align: left;
}
.ui-slider .ui-slider-handle {
position: absolute;
z-index: 2;
width: 1.2em;
height: 1.2em;
cursor: default;
-ms-touch-action: none;
touch-action: none;
}
.ui-slider .ui-slider-range {
position: absolute;
z-index: 1;
font-size: .7em;
display: block;
border: 0;
background-position: 0 0;
}
/* support: IE8 - See #6727 */
.ui-slider.ui-state-disabled .ui-slider-handle,
.ui-slider.ui-state-disabled .ui-slider-range {
filter: inherit;
}
.ui-slider-horizontal {
height: .8em;
}
.ui-slider-horizontal .ui-slider-handle {
top: -.3em;
margin-left: -.6em;
}
.ui-slider-horizontal .ui-slider-range {
top: 0;
height: 100%;
}
.ui-slider-horizontal .ui-slider-range-min {
left: 0;
}
.ui-slider-horizontal .ui-slider-range-max {
right: 0;
}
.ui-slider-vertical {
width: .8em;
height: 100px;
}
.ui-slider-vertical .ui-slider-handle {
left: -.3em;
margin-left: 0;
margin-bottom: -.6em;
}
.ui-slider-vertical .ui-slider-range {
left: 0;
width: 100%;
}
.ui-slider-vertical .ui-slider-range-min {
bottom: 0;
}
.ui-slider-vertical .ui-slider-range-max {
top: 0;
}
.ui-sortable-handle {
-ms-touch-action: none;
touch-action: none;
}
.ui-spinner {
position: relative;
display: inline-block;
overflow: hidden;
padding: 0;
vertical-align: middle;
}
.ui-spinner-input {
border: none;
background: none;
color: inherit;
padding: .222em 0;
margin: .2em 0;
vertical-align: middle;
margin-left: .4em;
margin-right: 2em;
}
.ui-spinner-button {
width: 1.6em;
height: 50%;
font-size: .5em;
padding: 0;
margin: 0;
text-align: center;
position: absolute;
cursor: default;
display: block;
overflow: hidden;
right: 0;
}
/* more specificity required here to override default borders */
.ui-spinner a.ui-spinner-button {
border-top-style: none;
border-bottom-style: none;
border-right-style: none;
}
.ui-spinner-up {
top: 0;
}
.ui-spinner-down {
bottom: 0;
}
.ui-tabs {
position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
padding: .2em;
}
.ui-tabs .ui-tabs-nav {
margin: 0;
padding: .2em .2em 0;
}
.ui-tabs .ui-tabs-nav li {
list-style: none;
float: left;
position: relative;
top: 0;
margin: 1px .2em 0 0;
border-bottom-width: 0;
padding: 0;
white-space: nowrap;
}
.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
float: left;
padding: .5em 1em;
text-decoration: none;
}
.ui-tabs .ui-tabs-nav li.ui-tabs-active {
margin-bottom: -1px;
padding-bottom: 1px;
}
.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
cursor: text;
}
.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
cursor: pointer;
}
.ui-tabs .ui-tabs-panel {
display: block;
border-width: 0;
padding: 1em 1.4em;
background: none;
}
.ui-tooltip {
padding: 8px;
position: absolute;
z-index: 9999;
max-width: 300px;
}
body .ui-tooltip {
border-width: 2px;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,443 @@
/*!
* jQuery UI CSS Framework 1.12.1
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/theming/
*
* To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6
*/
/* Component containers
----------------------------------*/
.ui-widget {
font-family: Arial,Helvetica,sans-serif;
font-size: 1em;
}
.ui-widget .ui-widget {
font-size: 1em;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: Arial,Helvetica,sans-serif;
font-size: 1em;
}
.ui-widget.ui-widget-content {
border: 1px solid #c5c5c5;
}
.ui-widget-content {
border: 1px solid #dddddd;
background: #ffffff;
color: #333333;
}
.ui-widget-content a {
color: #333333;
}
.ui-widget-header {
border: 1px solid #dddddd;
background: #e9e9e9;
color: #333333;
font-weight: bold;
}
.ui-widget-header a {
color: #333333;
}
/* Interaction states
----------------------------------*/
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default,
.ui-button,
/* We use html here because we need a greater specificity to make sure disabled
works properly when clicked or hovered */
html .ui-button.ui-state-disabled:hover,
html .ui-button.ui-state-disabled:active {
border: 1px solid #c5c5c5;
background: #f6f6f6;
font-weight: normal;
color: #454545;
}
.ui-state-default a,
.ui-state-default a:link,
.ui-state-default a:visited,
a.ui-button,
a:link.ui-button,
a:visited.ui-button,
.ui-button {
color: #454545;
text-decoration: none;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus,
.ui-button:hover,
.ui-button:focus {
border: 1px solid #cccccc;
background: #ededed;
font-weight: normal;
color: #2b2b2b;
}
.ui-state-hover a,
.ui-state-hover a:hover,
.ui-state-hover a:link,
.ui-state-hover a:visited,
.ui-state-focus a,
.ui-state-focus a:hover,
.ui-state-focus a:link,
.ui-state-focus a:visited,
a.ui-button:hover,
a.ui-button:focus {
color: #2b2b2b;
text-decoration: none;
}
.ui-visual-focus {
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active,
a.ui-button:active,
.ui-button:active,
.ui-button.ui-state-active:hover {
border: 1px solid #003eff;
background: #007fff;
font-weight: normal;
color: #ffffff;
}
.ui-icon-background,
.ui-state-active .ui-icon-background {
border: #003eff;
background-color: #ffffff;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #ffffff;
text-decoration: none;
}
/* Interaction Cues
----------------------------------*/
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
border: 1px solid #dad55e;
background: #fffa90;
color: #777620;
}
.ui-state-checked {
border: 1px solid #dad55e;
background: #fffa90;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
.ui-widget-header .ui-state-highlight a {
color: #777620;
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error {
border: 1px solid #f1a899;
background: #fddfdf;
color: #5f3f3f;
}
.ui-state-error a,
.ui-widget-content .ui-state-error a,
.ui-widget-header .ui-state-error a {
color: #5f3f3f;
}
.ui-state-error-text,
.ui-widget-content .ui-state-error-text,
.ui-widget-header .ui-state-error-text {
color: #5f3f3f;
}
.ui-priority-primary,
.ui-widget-content .ui-priority-primary,
.ui-widget-header .ui-priority-primary {
font-weight: bold;
}
.ui-priority-secondary,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70); /* support: IE8 */
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35); /* support: IE8 */
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
width: 16px;
height: 16px;
}
.ui-icon,
.ui-widget-content .ui-icon {
background-image: url("images/ui-icons_444444_256x240.png");
}
.ui-widget-header .ui-icon {
background-image: url("images/ui-icons_444444_256x240.png");
}
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon,
.ui-button:hover .ui-icon,
.ui-button:focus .ui-icon {
background-image: url("images/ui-icons_555555_256x240.png");
}
.ui-state-active .ui-icon,
.ui-button:active .ui-icon {
background-image: url("images/ui-icons_ffffff_256x240.png");
}
.ui-state-highlight .ui-icon,
.ui-button .ui-state-highlight.ui-icon {
background-image: url("images/ui-icons_777620_256x240.png");
}
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon {
background-image: url("images/ui-icons_cc0000_256x240.png");
}
.ui-button .ui-icon {
background-image: url("images/ui-icons_777777_256x240.png");
}
/* positioning */
.ui-icon-blank { background-position: 16px 16px; }
.ui-icon-caret-1-n { background-position: 0 0; }
.ui-icon-caret-1-ne { background-position: -16px 0; }
.ui-icon-caret-1-e { background-position: -32px 0; }
.ui-icon-caret-1-se { background-position: -48px 0; }
.ui-icon-caret-1-s { background-position: -65px 0; }
.ui-icon-caret-1-sw { background-position: -80px 0; }
.ui-icon-caret-1-w { background-position: -96px 0; }
.ui-icon-caret-1-nw { background-position: -112px 0; }
.ui-icon-caret-2-n-s { background-position: -128px 0; }
.ui-icon-caret-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -65px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -65px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 1px -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-on { background-position: -96px -144px; }
.ui-icon-radio-off { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 3px;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 3px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 3px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 3px;
}
/* Overlays */
.ui-widget-overlay {
background: #aaaaaa;
opacity: .003;
filter: Alpha(Opacity=.3); /* support: IE8 */
}
.ui-widget-shadow {
-webkit-box-shadow: 0px 0px 5px #666666;
box-shadow: 0px 0px 5px #666666;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,74 @@
{
"name": "jquery-ui",
"title": "jQuery UI",
"description": "A curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library.",
"version": "1.12.1",
"homepage": "http://jqueryui.com",
"author": {
"name": "jQuery Foundation and other contributors",
"url": "https://github.com/jquery/jquery-ui/blob/1.12.1/AUTHORS.txt"
},
"main": "ui/widget.js",
"maintainers": [
{
"name": "Scott González",
"email": "scott.gonzalez@gmail.com",
"url": "http://scottgonzalez.com"
},
{
"name": "Jörn Zaefferer",
"email": "joern.zaefferer@gmail.com",
"url": "http://bassistance.de"
},
{
"name": "Mike Sherov",
"email": "mike.sherov@gmail.com",
"url": "http://mike.sherov.com"
},
{
"name": "TJ VanToll",
"email": "tj.vantoll@gmail.com",
"url": "http://tjvantoll.com"
},
{
"name": "Felix Nagel",
"email": "info@felixnagel.com",
"url": "http://www.felixnagel.com"
},
{
"name": "Alex Schmitz",
"email": "arschmitz@gmail.com",
"url": "https://github.com/arschmitz"
}
],
"repository": {
"type": "git",
"url": "git://github.com/jquery/jquery-ui.git"
},
"bugs": "https://bugs.jqueryui.com/",
"license": "MIT",
"scripts": {
"test": "grunt"
},
"dependencies": {},
"devDependencies": {
"commitplease": "2.3.0",
"grunt": "0.4.5",
"grunt-bowercopy": "1.2.4",
"grunt-cli": "0.1.13",
"grunt-compare-size": "0.4.0",
"grunt-contrib-concat": "0.5.1",
"grunt-contrib-csslint": "0.5.0",
"grunt-contrib-jshint": "0.12.0",
"grunt-contrib-qunit": "1.0.1",
"grunt-contrib-requirejs": "0.4.4",
"grunt-contrib-uglify": "0.11.1",
"grunt-git-authors": "3.1.0",
"grunt-html": "6.0.0",
"grunt-jscs": "2.1.0",
"load-grunt-tasks": "3.4.0",
"rimraf": "2.5.1",
"testswarm": "1.1.0"
},
"keywords": []
}

View File

@@ -0,0 +1,30 @@
.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
.ui-timepicker-div dl { text-align: left; }
.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; }
.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; }
.ui-timepicker-div td { font-size: 90%; }
.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
.ui-timepicker-div .ui_tpicker_unit_hide{ display: none; }
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input { background: none; color: inherit; border: none; outline: none; border-bottom: solid 1px #555; width: 95%; }
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input:focus { border-bottom-color: #aaa; }
.ui-timepicker-rtl{ direction: rtl; }
.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; }
.ui-timepicker-rtl dl dt{ float: right; clear: right; }
.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; }
/* Shortened version style */
.ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time,
.ui-timepicker-div.ui-timepicker-oneLine dt { display: none; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; }
.ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd,
.ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before,
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before,
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide,
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; }

1
public/css/leaflet-velocity.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.leaflet-control-velocity{background-color:hsla(0,0%,100%,.7);padding:0 5px;margin:0!important;color:#333;font:11px/1.5 Helvetica Neue,Arial,Helvetica,sans-serif}.velocity-overlay{position:absolute;z-index:1}

673
public/css/style.css Executable file
View File

@@ -0,0 +1,673 @@
htnml {
height: 90%;
}
body {
margin: auto;
width: 100%;
height: 100%;
font: 14px Montserrat Helvetica, Arial, sans-serif;
border-top: 1px solid black;
border-bottom: 1px solid black;
}
h3 {
font-size: 18px;
font-weight: bold;
}
#meldung {
margin : auto;
/* width: 100%; */
text-align: center;
background-color: #DDDDDD;
padding-top: 30px;
height: 100%;
position: relative;
border: 3px solid red;
}
#buttons {
clear: both;
/* margin: auto; */
margin-top: 10px;
margin-right: 20px;
/* text-align: right; */
width: 100%;
}
#buttonsLeft {
width: 48%;
float: left;
/* margin-bottom: 5px; */
margin-left: 15px;
/* border: 1px solid blue; */
}
#buttonsRight {
text-align: right;
width: 45%;
float: right;
/* margin-bottom: 5px; */
margin-right: 15px;
/* border: 1px solid blue; */
}
#buttright {
justify-content:flex-end;
}
nav { display: flex;}
#btninfo {
/* float: right; */
margin-top: 3px;
}
#btnda, #btnmo, #btnwe {
width: 80px;
margin-right: 10px;
}
.mybuttons{
height: 30px;
font-size: 95%;
margin-bottom: 0px;
}
.myinputs{
height: 30px;
}
#btnlegende {
margin-right: 20px;
margin-top: 2px;
/* display: none; */
}
#btnTST {
margin-right: 20px;
margin-top: 2px;
/* display: none; */
}
#btnset {
margin-right: 10px;
/* display: none; */
}
#btnshwgrafic {
magin: auto;
}
/* Grafik-Fenster */
#placeholderFS_1 {
width: 100%;
margin: auto;
margin-bottom:8px;
}
#placeholderBME {
width: 100%;
margin-top: -10px;
}
.infoTafel th,td {
margin: auto;
text-align: left;
padding: 0 10px;
border: 1px solid black;
background-color: rgba(238,238,238,0.6) ! important;
}
.infoTafel th {
text-align: center;
}
.infoTafel tr {
height: 20px;
}
.errTafel {
text-align: center;
}
.errTafelsmall {
font-size:75%;
color: black;
}
#author {
font-size:80%;
width: 100%;
margin: auto;
padding-bottom: 20px;
padding-top: 5px;
background: #DDD;
}
#mailadr {
float: left;
margin-left: 2%;
}
#versn {
float: right;
margin-right: 2%;
}
#help {
width: 700px;
}
#sensors {
margin: 30px auto;
align:center;
}
#sensors td, #sensors th{
text-align: center;
width: 200px;
}
#fstb {
padding-top: 30px;
}
#alert {
text-align: center;
}
#map {
clear: both;
height: 90vh;
z-index: 0;
border-top: solid 1px black;
border-bottom: solid 1px black;
}
#mapdate {
font-size: 70%;
}
#nosensor {
text-align: center;
font-size: 120%;
font-weight: bold;
display: none;
}
.hrefsimu {
color: blue;
cursor:pointer;
}
#fehlersensoren {
}
/*
#infoTable table, #infoTable tr, #infoTable td {
margin: auto;
text-align: center;
border: none !important;
}
*/
#popuptext {
width : 150px;
/* height: 150px; */
}
#infoTable table, #infoTable tr, #infoTable td {
background-color: palegreen;
border:none !important;
margin: auto;
font-weight: bold;
width: 100px;
text-align: center;
/* font-size:110%;*/
}
#inforohr {
text-align: center;
font-weight: bold;
}
/*
#infoBtn {
margin: 10px 0 0 25px;
font-size: 120%;
}
*/
#fehlerexplain {
margin-left: 30px;
padding: 5px;
border: solid 2px red;
}
/*label, input { display: block; }
*/
/*
radio {
display: inline-block;
margin-bottom: 10px;
mrgin-right: 10px;
disaplay: inline;
}
*/
#page-mask {
background: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
visibility: hidden;
}
.highcharts-tooltip span {
background-color: white;
opacity: 1;
z-index: 9999 !important;
}
#errorDialog {
color: red;
}
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: lightgray;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: lightgray;
}
::-ms-input-placeholder { /* Microsoft Edge */
color: lightgray;
}
#underymax {
font-size: 75%;
}
#alarm {
display: none;
}
#datumtxt {
font-size: 120%;
font-weight: bold;
position: absolute;
top: 170px;
right: 25px;
z-index: 99;
}
.ui-widget-content a {
color: blue;
/* Überschreibt das default in jquery (das ist nicht blau) */
}
/*
#stat_table {
margin: auto;
}
#stat_table tr:hover {
background-color: #ffff99;
border: 1px solid black;
}
#stat_table caption {
text-align: center;
}
#stat_table table, #stat_table th {
border: 1px solid black;
}
#stat_table th {
padding: 5px;
text-align: center;
background-color: green;
color: white;
}
*/
.w25, .w10 {
text-align:right;
width: 80px;
}
#body2 {
border-top: 3px green solid;
}
.bord1 {
border-top: 2px black solid;
}
.setting_head {
background-color: blue;
color: white;
padding-left: 20px;
padding-top: 5px;
padding-bottom: 5px;
}
#avgselect {
margin-left:30px;
}
#lueber {
margin-left: 15px;
margin-right: 5px;
}
.ui-dialog {
z-index:1000 !important;
}
.cb {
/* background-color: #86bc24;
opacity: 0.8; */
font-size: 130%;
text-align: center;
display: inline-block;
width: 130px;
height: 40px;
border: solid 2px #B5B5B5;
}
.centerbutt {
border-radius: 5px;
}
.info {
padding: 6px 8px;
font: 12px Arial, Helvetica, sans-serif;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: #777;
}
img.leaflet-tile {
filter: grayscale(85%) saturate(150%) hue-rotate(180deg) contrast(90%) brightness(110%);
-webkit-filter: grayscale(85%) saturate(150%) hue-rotate(180deg) contrast(90%) brightness(110%);
}
.mapbox {
position: relative;
}
#overlay {
margin: auto;
position: absolute;
top: 60px;
left: 0px;
border: 2px #86bc24 solid;
width: 710px;
/* height: 100%; */
background-color: white;
display: none;
}
button.btnlink {
background:none;border:none;
color: blue;
margin-left: 15px;
text-decoration: underline;
}
#loading {
position : absolute;
top: 200px;
left: 200px;
display: none;
width: 200px;
color: blue;
border: 1px solid blue;
text-align: center;
z-index:20;
background-color:lightcyan;
vertical-align: center;
}
.ib {
background-color: #86bc24;
opacity: 0.8;
border: none;
padding: 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 200%;
margin: 4px 2px;
width: 100px;
height: 100px;
}
.infobutt {
border-radius: 50%;
}
#marker_text {
font-family: Verdana, 'Lucida Sans Unicode', sans-serif;
}
.my-custom-control {
padding:5px 10px;
background: rgba(0,0,0,0.5);
color: #fff;
font-size: 10px;
line-height: 15px;
border-radius: 5px;
}
.my-custom-control:empty {
display: none;
}
select {
/* background-color: #86bc24;
opacity: 0.8; */
font-size: 130%;
text-align: center;
display: inline-block;
width: 130px;
height: 30px;
}
.dropdown-.menue {
max-width: max-content;
}
.navbg {
background: #DDD;
}
/*
.ui-dialog {
z-index:1000000000;
top: 0; left: 0;
margin: auto;
position: fixed;
max-width: 100%;
max-height: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
}
.ui-dialog .ui-dialog-content {
flex: 1;
}
.ui-dialog .ui-dialog-buttonpane {
background:white;
}
*/
.has-search .form-control {
padding-left: 2.375rem;
}
.has-search .form-control-feedback {
position: absolute;
z-index: 2;
display: block;
width: 2.375rem;
height: 2.375rem;
line-height: 2.375rem;
text-align: center;
pointer-events: none;
color: #aaa;
}
/*
@media (max-width: 576px) {
.legend {
display:none;
}
#btnlegende {
display:inline;
}
}
*/
.dropdown-menu {
white-space: nowrap;
padding-right: 10px;
}
#popuptext, #infoaddr, #akwpopuptext{
text-align: center;
}
#infoBtn {
margin: 10px auto;
}
#newmapcenter {
width: 250px;
height: 33px;
padding-left: 25px;
}
.spspan {
float: right;
margin-right: 6px;
margin-top: -25px;
}
.input-icon{
position: absolute;
left: 5px;
top: calc(50% - 0.5em); /* Keep icon in center of input, regardless of the input height */
}
.input-wrapper{
position: relative;
}
#indoor {
font-weight: bold;
text-align:center;
}
.ndmarker {
position: absolute;
font-size: 24px;
}
#splashChecklabel {
margin-top: 9px;
font-size: 110%;
}
.legend {
font-family: arial, sans-serif;
color:black;
font-size: 12px;
position: absolute;
left: 0px;
bottom: 0px;
height: 35%;
width: 130px;
z-index: 999;
background-color: rgba(238,238,238,0.80);
border: solid 1px black;
display: none;
}
#legend-inner-cpm {
position: absolute;
left: 20px;
bottom: 15px;
z-index: 1001;
pointer-events: none;
height: 90%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding-top: 10px;
}
#legend-inner-cpm .gradient {
opacity: 0.8;
width: 20px;
/* background: -webkit-linear-gradient(bottom, #00796b 0%, #00796b 16%, #f9a825 32%, #e65100 48%, #dd2c00 72%, #dd2c00 80%, #8c0084 100%); */
background: -webkit-linear-gradient(bottom, #9ECDEA 0%, #9ECDEA 8%, #7F7F7F 8%, #7F7F7F 16%, #00796b 16%, #f9a825 44%, #e65100 58%, #dd2c00 72%, #dd2c00 86%, #8c0084 100%);
/* background: linear-gradient(to top, #00796b 0%, #00796b 16%, #f9a825 32%, #e65100 48%, #dd2c00 72%, #dd2c00 80%, #8c0084 100%); */
background: linear-gradient(to top, #9ECDEA 0%, #9ECDEA 8%, #7F7F7F 8%, #7F7F7F 16%,
#267A45 16%,
#66FA5F 30%,
#F8Fc00 44%,
#FF0000 58%,
#9000FF 100%);
position: relative;
}
#legend-inner-cpm .labels {
width: 150px;
position: relative;
}
#legend-inner-cpm .labels .label {
position: absolute;
-webkit-transform: translateY(50%);
transform: translateY(50%);
}
#legend-inner-cpm .labels .label1 {
position: absolute;
-webkit-transform: translateY(50%);
transform: translateY(50%);
margin-left: 10px;
}
#legend-inner-cpm .labels .label:before {
content: '\2013 ';
}
#bandgapregion {
margin-left: 50px;
color: gray;
}
#kraftw, #togwind {
margin-left: 30px;
}

21
public/erralert.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id='alert' style="font-family: 'arial';">
<h1>Fehler</h1>
<p>
Name oder Sensor-Nummer im Aufruf falsch.
</p>
<p>
Bitte gültigen Name oder gültige Sensornummer verwenden
oder hier auf <strong>Übersicht</strong> klicken.
</p>
<a href="/" data-confirm="Übersicht"></a>
</div>
</body>
</html>

18
public/fehlerliste.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id='help' style="font-family: 'arial';">
<h1>Fehlerhaften Sensoren</h1>
<ul id = "fehlersensorenliste"></ul>
<p>
Falls Dein Sensor dabei ist, kannst Du mir gerne eine <a class="hrefsimu" href="mailto:rexfue@gmail.com">email</a>
schreiben. Wir können dann gemeinsam entscheiden, welche Maßnahmen wir ergreifen können.
</p>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id='help' style="font-family: 'arial';">
<h2>Warum wird mein Sensor <span class="psensornr"> Nr. </span> nicht angezeigt?</h2>
<p>
Es gibt viele Gründe, warum ein bestimmter Sensor nicht auf der Karte erscheint:
<ul>
<li>Der Sensor sendet schon über eine Woche keine Daten (mehr) zum Server</li>
<li>Der Sensor ist noch nicht an der Datenbank angemeldet (siehe
<a href="https://luftdaten.info/feinstaubsensor-bauen">Letzte Schritte</a>
auf luftdaten.info (ganz unten).</li>
<li>Der Sensor zeigt unsinnige oder unplausible Werte.</li>
</ul>
</p>
<p>
<h3>Unsinnige und unplausible Werte</h3>
Bevor die Daten zu Anzeige kommen, werden sie überprüft. Es wird der Mittelwert über alle Werte
von 'gestern' gebildet und daraus dann die folgenden Bedingungen abgeleitet. Sensoren, die mindestens eine
dieser Bedingungen erfüllen, werden <b>nicht</b> angezeigt.
<ul>
<li>Sensoren, die dauernd Werte von 1999 (bei P10) oder 999 (bei P2.5) ausgeben. Diese
Sensoren sind eindeutig defekt (oder ein Tierchen ist in die Laser-Kammer gekrabbelt).</li>
<li>Sensoren, die dauernd Werte kleiner 1 für P10 bzw. P2.5 anzeigen. Auch hier ist ein
Defekt (oder eine Verstopfung der Ansaug-Leitung) anzunehmen.</li>
<li>Sensoren, die keine oder nur sehr kleine Änderungen im Laufe des Tages gemessen haben.</li>
<li>Sensoren, deren P2.5-Wert im Tagesmittel über 50 &mu;g/m<sup>3</sup> liegt</li>
<li>Sensoren, bei denen über den ganzen Tag P10 und P2.5 sehr eng beieinander liegen</li>
<li>Sensoren, bei denen über den ganzen Tag P10 und P2.5 sehr weit auseinander liegen</li>
</ul>
</p>
<p>
<div id="fehlerexplain"></div> <br />
</p>
Du kannst mir gerne eine email ( <a href="mailto:rexfue@gmail.com">rexfue@gmail.com</a> )
schreiben. Vielleicht können wir das Problem gemeinsam lösen.
</body>
</html>

5
public/fontawesome/css/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

10
public/help.html Executable file
View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id='help' style="font-family: 'arial';">
<h2>Diese Seite existiert leider noch nicht!</h2>
</body>
</html>

402
public/help_map.html Normal file
View File

@@ -0,0 +1,402 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>help_map</title>
<style type="text/css">
body {
font-family: Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: white;
padding: 30px; }
body > *:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }
a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
text-decoration: none; }
h1 tt, h1 code {
font-size: inherit; }
h2 tt, h2 code {
font-size: inherit; }
h3 tt, h3 code {
font-size: inherit; }
h4 tt, h4 code {
font-size: inherit; }
h5 tt, h5 code {
font-size: inherit; }
h6 tt, h6 code {
font-size: inherit; }
h1 {
font-size: 28px;
color: black; }
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }
h3 {
font-size: 18px; }
h4 {
font-size: 16px; }
h5 {
font-size: 14px; }
h6 {
color: #777777;
font-size: 14px; }
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }
hr {
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0;
}
body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }
li p.first {
display: inline-block; }
li {
margin: 0; }
ul, ol {
padding-left: 30px; }
ul :first-child, ol :first-child {
margin-top: 0; }
dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
table {
padding: 0;border-collapse: collapse; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
img {
max-width: 100%; }
span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }
.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #555;
vertical-align: middle;
background-color: #fcfcfc;
border: solid 1px #ccc;
border-bottom-color: #bbb;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #bbb
}
* {
-webkit-print-color-adjust: exact;
}
@media screen and (min-width: 914px) {
body {
width: 854px;
margin:0 auto;
}
}
@media print {
table, pre {
page-break-inside: avoid;
}
pre {
word-wrap: break-word;
}
}
</style>
</head>
<body>
<h2 id="toc_0">Info zur Karte</h2>
<p>Auf dieser Karte hat jeder Sensor ein rundes Icon (Radioaktivitäs-Symbol). Die Farbe des Symbols ist abhängig von der aktuellen Zählrate. Es gelten folgende Beziehungen: </p>
<table>
<thead>
<tr>
<th>Wert</th>
<th>Farbe</th>
<th>Bemerkung</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>grau</td>
<td>der Sensor hat seit mind. einer Stunde keine Daten gesendet</td>
</tr>
<tr>
<td>&lt; 30</td>
<td>grün</td>
<td>die sogenannte Null-Rate</td>
</tr>
<tr>
<td>&lt; 100</td>
<td>gelbgrün</td>
<td></td>
</tr>
<tr>
<td>&lt; 250</td>
<td>gelb</td>
<td></td>
</tr>
<tr>
<td>&lt; 500</td>
<td>orange</td>
<td></td>
</tr>
<tr>
<td>&gt;= 500</td>
<td>rot</td>
<td></td>
</tr>
</tbody>
</table>
<p>Wenn sich ein Sensor länger als einen Monat nicht gemeldet hat, wird er nicht mehr angezeigt.</p>
<p>Bein Aufruf der Karte ohne Sensornummer (also <a href="https://test1.rexfue.de">https://test1.rexfue.de</a> oder <a href="https://test1.rexfue.de/map">https://test1.rexfue.de/map</a> ) wird die Karte auf Stuttgart-Mitte zentriert. Wird eine Sensornummer mit übergeben (als z.B. <a href="https://test1.rexfue.de/map?sid=31122">https://test1.rexfue.de/map?sid=31122</a> so wird die Karte auf diesen Sensor zentriert.<br>
Ebenso wird auf den Sensor zentriert, wenn von der Grafik-Darstellung auf die Kartendarstellung umgeschaltet wird.</p>
<p>Ein Klick auf ein Sensor-Icon lässt eine kleine Infotafel aufpoppen. Auf dieser Tafel steht die Sensornummer, der aktuelle Wert (in cpm = counts per minute) sowie ein Link. Durch Klick auf den Link erreicht man die Grafik-Ausgabe, d.h. es wird der zeitliche Verlauf der Sensorwerte dargestellt.</p>
<p>Die Karte kann durch Klick auf das Plus- bzw. Minus-Zeichen links oben gezoomed werden. Durch Klicken in der Karte und halten kann mit der Maus der Kartenausschnitt verschoben werden.</p>
<p>Mit dem <strong>Zentrieren</strong>-Knopf kann ein neuer Kartenmittelpunkt festgelegt werden. Es muss ein Ort eingegeben werden. Die Suche nach den Koordintaen des Ortes erfolgt online, d.h. es kann sein, dass der falsche Ort gefunden wird, dann kann die Suche verfeinert werden. Z.B wenn Stuttgart in den USA gesucht werden soll, dann kann Stuttgart,USA eingegeben werden.</p>
<p>Mit dem <strong>zurück</strong>-Knopf gelangt man zurück zur zuletzt angezeigten Grafik-Darstellung.</p>
<p>Da die Sensoren alle 10min Daten senden, wird auch die Information auf der Karte alle 10min erneuert.</p>
<p>Die Grafik eines Sensor kann auch direkt über die URL <a href="https://test1.rexfue.de/xxxx">https://test1.rexfue.de/xxxx</a> aufgerufen werden. xxxx ist die gewünschte Sensor-Nummer, also z.B.: <a href="https://test1.rexfue.de/31122">https://test1.rexfue.de/31122</a>.</p>
<p>Hier gehts zum <a href="&#x27;https://rexfue.de/impressum.html&#x27;">Impressum</a></p>
</body>
</html>

30
public/help_map.md Normal file
View File

@@ -0,0 +1,30 @@
##Info zur Karte
Auf dieser Karte hat jeder Sensor ein rundes Icon (Radioaktivitäs-Symbol). Die Farbe des Symbols ist abhängig von der aktuellen Zählrate. Es gelten folgende Beziehungen:
| Wert | Farbe | Bemerkung
-------|-------|----------
0 | grau |der Sensor hat seit mind. einer Stunde keine Daten gesendet
< 30 | grün | die sogenannte Null-Rate
< 100 | gelbgrün |
< 250 | gelb |
< 500 | orange |
\>= 500 | rot
Wenn sich ein Sensor länger als einen Monat nicht gemeldet hat, wird er nicht mehr angezeigt.
Bein Aufruf der Karte ohne Sensornummer (also <https://test1.rexfue.de> oder <https://test1.rexfue.de/map> ) wird die Karte auf Stuttgart-Mitte zentriert. Wird eine Sensornummer mit übergeben (als z.B. <https://test1.rexfue.de/map?sid=31122> so wird die Karte auf diesen Sensor zentriert.
Ebenso wird auf den Sensor zentriert, wenn von der Grafik-Darstellung auf die Kartendarstellung umgeschaltet wird.
Ein Klick auf ein Sensor-Icon lässt eine kleine Infotafel aufpoppen. Auf dieser Tafel steht die Sensornummer, der aktuelle Wert (in cpm = counts per minute) sowie ein Link. Durch Klick auf den Link erreicht man die Grafik-Ausgabe, d.h. es wird der zeitliche Verlauf der Sensorwerte dargestellt.
Die Karte kann durch Klick auf das Plus- bzw. Minus-Zeichen links oben gezoomed werden. Durch Klicken in der Karte und halten kann mit der Maus der Kartenausschnitt verschoben werden.
Mit dem **Zentrieren**-Knopf kann ein neuer Kartenmittelpunkt festgelegt werden. Es muss ein Ort eingegeben werden. Die Suche nach den Koordintaen des Ortes erfolgt online, d.h. es kann sein, dass der falsche Ort gefunden wird, dann kann die Suche verfeinert werden. Z.B wenn Stuttgart in den USA gesucht werden soll, dann kann Stuttgart,USA eingegeben werden.
Mit dem **zurück**-Knopf gelangt man zurück zur zuletzt angezeigten Grafik-Darstellung.
Da die Sensoren alle 10min Daten senden, wird auch die Information auf der Karte alle 10min erneuert.
Die Grafik eines Sensor kann auch direkt über die URL <https://test1.rexfue.de/xxxx> aufgerufen werden. xxxx ist die gewünschte Sensor-Nummer, also z.B.: <https://test1.rexfue.de/31122>.
Hier gehts zum [Impressum]('https://rexfue.de/impressum.html')

BIN
public/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

90
public/info.html Normal file
View File

@@ -0,0 +1,90 @@
<h3 id="toc_1">Allgemein</h3>
<p>Auf der Karte wird jede Zählstation mit dem Radioaktivitäts-Symbol angezeigt. Die Farbe des Symbols ändert sich mit der Zählrate, der Zusammenhang ist rechts oben in der Legende dargestellt.<br>
Hat der Sensor seit mind. 1 Stunde keine Daten mehr gesendet, so wird er dunkelgrau eingefärbt. Hat er über eine Woche nicht gesendet, wird er nicht mehr dargestellt.<br>
Die Karte ist standardmäßig auf Stuttgart ausgerichtet. Wenn der Aufruf der Webseite mit einer Stadt erfolgt (z.B: <a href="https://multigeiger.citysensor.de/Berlin">https://multigeiger.citysensor.de/Berlin</a>),
so wird die Karte auf diese Stadt zentriert. <br />
Wird statt dessen eine Sensornummer angegeben, so wird die Karte auf diesen Sensor zentriert und gleich die zugehörige Grafik angezeigt
(z.B. für den Sensor Nr. 34188 ist der Aufruf dann: <a href="https://multigeiger.citysensor.de/34188">https://multigeiger.citysensor.de/34188</a>).
</p>
<h3 id="toc_2">Bedienelemente</h3>
<ul>
<li>Die Karte kann mit dem Mausrad oder den beiden Knöpfen <strong>+/-</strong> in der linken obere Ecke ein- und ausgezoomed werden.</li>
<li>Mit gedrückter Maustaste läßt sich die Karte verschieben.</li>
</ul>
<p>Über der Karte befindet sich die Navigationsleiste mit folgenden Knöpfen/Eingabefeldern:</p>
<ul>
<li><strong>Zählrohre</strong><br>
Hier wird umgeschaltet, ob <strong>alle</strong> Sensoren oder nur die Sensoren, die ein <strong>Si22G</strong>-Zählrohr haben, angezeigt werden.
<li><strong>AKEs/Anlagen</strong><br>
Hier kann ausgewählt werden, welche Kraftwerke bzw. andere Nuklear-Anlagen angezeigt werden.
<li><strong>Wind</strong><br>
Hier kann der Wind-Layer ein- und ausgeschaltet werden. Beim Start wird die Karte <strong>ohne</strong> Wind angezeigt.
<li><strong>Legende</strong><br>
Damit wird die Legende ein- oder und ausgeblendet.
<li><strong>Ort oder Sensornummer suchen</strong><br>
In dieses Eingabefeld kann ein Ort oder eine Sensornummer eingegeben werden. Die Karte wird dann darauf zentriert.</li>
<li><strong>Info</strong><br>
Es erscheint diese Info-Seite.</li>
</ul>
<p>Ein Klick auf das Radioaktivitäts-Symbol bringt eine Info-Tafel zur Anzeige. Auf dieser Tafel stehen folgende Informationen:</p>
<ul>
<li>Sensor-Nummer</li>
<li>Typ des Zählrohres</li>
<li>Adresse des Sensors (falls in der Datenbank vorhanden)</li>
<li>Aktueller Messwert in cpm (Impulse pro Minute) und µSv/h (Micro-Sievert pro Stunde)</li>
<li>Link <strong>Grafik anzeigen</strong></li>
</ul>
<p>
Außer den Sensoren werden auch die <b>Kernkraftwerke (AKW)</b> angezeigt. Aktive AKWs werden rot, sillgelegte werden
als roter Ring und sonstige Nuklearanlagen werden in violett dargestellt.
</p>
<p>
Ein Klick auf ein AKW-Symbol bringt eine kleine Info-Tafel mit folgenden Daten zur Anzeige:
</p>
<ul>
<li>Name des Kraftwerkes</li>
<li>Baujahr (wenn bekannt)</li>
<li>Datum der Stilllegung (falls das AKW stillgelegt ist/wird)</li>
<li>Bei den anderen Anlagen die Art der Anlage</li>
</ul>
<p>Quellen: <br />Die Daten für die Kerkraftwerke stammen
aus folgenden Quellen:
<ul>
<li><a href="https://de.wikipedia.org/wiki/Liste_der_Kernkraftwerke">Wikipedia</a>(Liste der Kernkraftwerke)</li>
<li><a href="https://de.wikipedia.org/wiki/Kernenergie_nach_Ländern">Wikipedia</a>(Kernenergie nach Ländern)</li>
<li><a href="https://pris.iaea.org/pris/">Power Reactor Information System</a>(aktuelle Infos zu AKWs weltweit)</li>
<li><a href="https://atomkraftwerkeplag.wikia.org/de/wiki/Atomausstiegselbermachen_Wiki">AtomkraftwerkePlag</a>(Rechercheplattform zur Atomenergie)</li>
</ul>
<p>Besten Dank an <a href="https://www.ralf-wessels.de">Ralf Wessels</a>, der die Daten gesammelt und zur Verfügung gestellt hat.
</p>
</p>
<h3 id="toc_3">Grafik</h3>
<p>Durch Klick auf <strong>Grafik anzeigen</strong> öffnet sich ein Fenster, das den Verlauf der letzten 24 Stunden anzeigt.
Es werden alle Messwerte (Wert alle 2.5min) angezeigt und zusätzlich ein gleitender Mittelwert. Der Zeitbereich für diesen Mittelwerrt
kann über <strong>Einstellung</strong> im Bereich von 10min bis 6 Stunden geändert werden.
Zusätzlich kann über das <strong>Einstell</strong>-Menü ein Langzeitmittelwert (48h bis 120h) mit Bereichsgrenzen (+/-5% bis +/-50%)
eingeblendet werden.<br />
Über die Knöpfe <strong>-24h -12h +12h +24h</strong> kann der Zeitstrahl um die jeweilige Anzahl an Stunden verschoben werden.<br />
Der Knopf <strong>live</strong> schaltet wieder auf den Live-Mode um, d.h. es werden wieder die Daten der letzten 24 Stunden angezeigt.<br>
</p>
<p>
Über den Knopf <strong>7d</strong> kann der Verlauf einer ganzen Woche betrachtet werden.
Auch hier kann der Zeitstrahl verschoben werden, und zwar um 3 bzw. 7 Tage.
Außerdem kann über den <strong>Einstellung</strong>-Knopf festgelegt werden, über welchen Zeitraum die Werte gemittelt werden
und ob eine gleitende oder statische Mittelwertbildung durchgeführt werden soll.<br>
</p>
<p>
Über den Knopf <strong>30d</strong> wird die Darstellung der Tagesmittelwerte jeden Tages der letzten 30 Tage als Balken-Diagramm angezeigt.<br>
</p>
Durch Klick auf den Knopf <strong>Ende</strong> wird die Grafik verlassen.</p>

520
public/info.html_old Normal file

File diff suppressed because one or more lines are too long

1
public/js/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/jquery-ui-1.10.0.custom.min.js

27
public/js/customEvents.min.js vendored Executable file
View File

@@ -0,0 +1,27 @@
(function(factory){if(typeof module==='object'&&module.exports){module.exports=factory;}else{factory(Highcharts);}}(function(HC){'use strict';var UNDEFINED,DBLCLICK='dblclick',TOUCHSTART='touchstart',CLICK='click',each=HC.each,pick=HC.pick,wrap=HC.wrap,merge=HC.merge,addEvent=HC.addEvent,isTouchDevice=HC.isTouchDevice,isObject=HC.isObject,isNumber=HC.isNumber,defaultOptions=HC.getOptions().plotOptions,axisProto=HC.Axis&&HC.Axis.prototype,plotLineOrBandProto=HC.PlotLineOrBand&&HC.PlotLineOrBand.prototype,seriesTypes=HC.seriesTypes,seriesProto=HC.Series&&HC.Series.prototype,customEvents,proto,methods;function isArray(obj){return Object.prototype.toString.call(obj)==='[object Array]';}
if(plotLineOrBandProto){wrap(plotLineOrBandProto,'render',function(proceed){var defaultEvents=this.options&&this.options.events;if(defaultEvents){defaultEvents=false;}
return proceed.apply(this,Array.prototype.slice.call(arguments,1));});}
if(seriesProto){wrap(seriesProto,'init',function(proceed,chart,options){var chartOptions=chart.options,plotOptions=chartOptions.plotOptions,seriesOptions=chartOptions.plotOptions.series,userOptions=merge(seriesOptions,plotOptions[this.type],options),userOptionsEvents=userOptions&&userOptions.events,userOptionsPointEvents=userOptions&&userOptions.point&&userOptions.point.events;options.events={};options.point={events:{}};if(userOptionsEvents){options.events={legendItemClick:userOptionsEvents&&userOptionsEvents.legendItemClick};}
if(userOptionsPointEvents){options.point.events={legendItemClick:userOptionsPointEvents&&userOptionsPointEvents.legendItemClick};}
options.customEvents={series:userOptionsEvents,point:userOptionsPointEvents};proceed.apply(this,Array.prototype.slice.call(arguments,1));});}
HC.Chart.prototype.customEvent={getEventsProtoMethods:function(){return[[HC.Tick,['addLabel']],[HC.Axis,['render']],[HC.Axis,['drawCrosshair']],[HC.Chart,['setTitle']],[HC.Legend,['renderItem']],[HC.PlotLineOrBand,['render']],[HC.Series,['drawPoints','drawDataLabels']],[seriesTypes.column,['drawPoints','drawDataLabels']],[seriesTypes.bar,['drawPoints','drawDataLabels']],[seriesTypes.pie,['drawPoints','drawDataLabels']],[seriesTypes.bubble,['drawPoints','drawDataLabels']],[seriesTypes.columnrange,['drawPoints','drawDataLabels']],[seriesTypes.arearange,['drawPoints','drawDataLabels']],[seriesTypes.areasplinerange,['drawPoints','drawDataLabels']],[seriesTypes.errorbar,['drawPoints','drawDataLabels']],[seriesTypes.boxplot,['drawPoints','drawDataLabels']],[seriesTypes.flags,['drawPoints','drawDataLabels']],[seriesTypes.heatmap,['drawPoints','drawDataLabels']]];},init:function(){var eventsProtoMethods=this.getEventsProtoMethods();each(eventsProtoMethods,function(protoMethod){proto=protoMethod[0]&&protoMethod[0].prototype;methods=protoMethod[1];if(proto){each(methods,function(method){customEvents.attach(proto,method);});}});},attach:function(proto,hcMethod){wrap(proto,hcMethod,function(proceed){var eventElement={events:UNDEFINED,element:UNDEFINED},proceedObject,len,j;proceedObject=proceed.apply(this,Array.prototype.slice.call(arguments,1));eventElement=customEvents.eventElement[hcMethod].call(this);if(!eventElement.events&&!eventElement.eventsPoint){return proceedObject;}
if(eventElement.eventsPoint){len=eventElement.elementPoint.length;for(j=0;j<len;j++){if(eventElement.elementPoint[j]){var elemPoint=pick(eventElement.elementPoint[j].graphic,eventElement.elementPoint[j]);if(elemPoint&&elemPoint!==UNDEFINED){customEvents.add(elemPoint,eventElement.eventsPoint,eventElement.elementPoint[j],eventElement,true);}}}}
if(eventElement.eventsSubtitle){customEvents.add(eventElement.elementSubtitle,eventElement.eventsSubtitle,eventElement,this);}
if(eventElement.eventsStackLabel){customEvents.add(eventElement.elementStackLabel,eventElement.eventsStackLabel,eventElement,this);}
customEvents.add(eventElement.element,eventElement.events,eventElement,this);return proceedObject;});},add:function(SVGelem,events,elemObj,eventElement,isPoint){var eventObject=eventElement.eventObject||elemObj.eventObject,isSeries=elemObj.isSeries||eventElement.isSeries;if(!SVGelem||!SVGelem.element){return false;}
for(var action in events){(function(event){if(events.hasOwnProperty(event)&&!SVGelem[event]){if(isTouchDevice&&event===DBLCLICK){var tapped=false;addEvent(SVGelem.element,TOUCHSTART,function(e){e.stopPropagation();e.preventDefault();if(isSeries&&!eventObject.directTouch){var chart=eventObject.chart,normalizedEvent=chart.pointer.normalize(e);elemObj=eventObject.searchPoint(normalizedEvent,eventObject.kdDimensions===1)||elemObj;e.point=elemObj;}
if((eventObject&&!isPoint)||(isNumber(eventObject.value))){eventObject.value=elemObj.textStr;elemObj=eventObject;}
if(elemObj&&elemObj.textStr){elemObj.value=elemObj.textStr;}
if(!tapped){tapped=setTimeout(function(){tapped=null;events[CLICK].call(elemObj,e);},300);}else{clearTimeout(tapped);tapped=null;events[event].call(elemObj,e);}
return false;});}else{addEvent(SVGelem.element,event,function(e){e.stopPropagation();e.preventDefault();if(isSeries&&!eventObject.directTouch){var chart=eventObject.chart,normalizedEvent=chart.pointer.normalize(e);elemObj=eventObject.searchPoint(normalizedEvent,eventObject.kdDimensions===1)||elemObj;e.point=elemObj;}
if((eventObject&&!isPoint)||(eventObject&&isNumber(eventObject.value))){eventObject.value=elemObj.textStr||eventObject.value;elemObj=eventObject;}
if(elemObj&&elemObj.textStr){elemObj.value=elemObj.textStr;}
events[event].call(elemObj,e);return false;});}
SVGelem[event]=function(){return true;};}})(action);}},eventElement:{addLabel:function(){var parent=this.parent,axis=this.axis,axisOptions=axis.options,eventsPoint=axisOptions.labels&&axisOptions.labels.events,elementPoint=[this.label],len,i;if(parent){var step=this;while(step){if(isArray(step)){len=step.length;for(i=0;i<len;i++){elementPoint.push(step[i].label);}}else{elementPoint.push(step.label);}
step=step.parent;}}
return{eventsPoint:eventsPoint,elementPoint:elementPoint,eventObject:{axis:axis,isFirst:this.isFirst,isLast:this.isLast,chart:axis.chart,dateTimeLabelFormat:axisOptions.dateTimeLabelFormats,value:this.pos}};},setTitle:function(){var events=this.options.title&&this.options.title.events,element=this.title,eventsSubtitle=this.options.subtitle&&this.options.subtitle.events,elementSubtitle=this.subtitle;return{events:events,element:element,eventsSubtitle:eventsSubtitle,elementSubtitle:elementSubtitle};},drawDataLabels:function(){var dataLabelsGroup=this.dataLabelsGroup;return{events:dataLabelsGroup?this.options.dataLabels.events:UNDEFINED,element:dataLabelsGroup?this.dataLabelsGroup:UNDEFINED};},render:function(){var stackLabels=this.options.stackLabels,events,element,eventsPoint,elementPoint,eventsStackLabel,elementStackLabel;if(this.axisTitle){events=this.options.title.events;element=this.axisTitle;}
if(stackLabels&&stackLabels.enabled){eventsPoint=stackLabels.events;elementPoint=this.stacks;eventsStackLabel=stackLabels.events;elementStackLabel=this.stackTotalGroup;}
return{events:events,element:element,eventsPoint:eventsPoint,elementPoint:elementPoint,eventsStackLabel:eventsStackLabel,elementStackLabel:elementStackLabel};},drawPoints:function(){var op=this.options,type=this.type,events=op.customEvents?op.customEvents.series:op.events,element=this.group,eventsPoint=op.customEvents?op.customEvents.point:op.point.events,elementPoint;if(defaultOptions[type]&&defaultOptions[type].marker&&!this.bubblePadding){elementPoint=[this.markerGroup];}else{elementPoint=this.points;}
if(!this.kdTree&&!this.buildingKdTree){this.buildKDTree();}
return{events:events,element:element,eventsPoint:eventsPoint,elementPoint:elementPoint,eventObject:this,isSeries:true};},renderItem:function(){return{events:this.options.itemEvents,element:this.group};},drawCrosshair:function(){var crosshair=this.options.crosshair;return{events:crosshair&&crosshair.events,element:this.cross};}}};customEvents=HC.Chart.prototype.customEvent;customEvents.init();}));

View File

@@ -0,0 +1,37 @@
/* German initialisation for the jQuery UI date picker plugin. */
/* Written by Milian Wolff (mail@milianw.de). */
( function( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( [ "../widgets/datepicker" ], factory );
} else {
// Browser globals
factory( jQuery.datepicker );
}
}( function( datepicker ) {
datepicker.regional.de = {
closeText: "Schließen",
prevText: "&#x3C;Zurück",
nextText: "Vor&#x3E;",
currentText: "Heute",
monthNames: [ "Januar","Februar","März","April","Mai","Juni",
"Juli","August","September","Oktober","November","Dezember" ],
monthNamesShort: [ "Jan","Feb","Mär","Apr","Mai","Jun",
"Jul","Aug","Sep","Okt","Nov","Dez" ],
dayNames: [ "Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag" ],
dayNamesShort: [ "So","Mo","Di","Mi","Do","Fr","Sa" ],
dayNamesMin: [ "So","Mo","Di","Mi","Do","Fr","Sa" ],
weekHeader: "KW",
dateFormat: "dd.mm.yy",
firstDay: 1,
isRTL: false,
showMonthAfterYear: false,
yearSuffix: "" };
datepicker.setDefaults( datepicker.regional.de );
return datepicker.regional.de;
} ) );

2586
public/js/global.js Executable file

File diff suppressed because it is too large Load Diff

2
public/js/jBox.min.js vendored Executable file

File diff suppressed because one or more lines are too long

1326
public/js/jquery-ui-timepicker-addon.js vendored Normal file

File diff suppressed because it is too large Load Diff

13
public/js/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/leaflet-velocity.min.js vendored Normal file

File diff suppressed because one or more lines are too long

946
public/js/mapit.js Executable file
View File

@@ -0,0 +1,946 @@
"use strict";
// History for mapit.js
//
// V 2.6.0 2019-10-05 rxf
// - identical module für 'geiger' and for 'noise'
//
// V 2.5.0 2019-10-05 rxf
//
const Version = "2.6.0";
var map;
var marker = [];
var sBreit = 30;
var infowindow;
var first = true;
var newBounds = false;
var geocod;
var trafficLayer;
let mongoPoints = [];
let problems = [];
let icon = "";
let radius = 10;
let firstZoom = 11;
let useStgtBorder = false;
let popuptext = "";
let bounds;
let polygon; // rectangle of whole map to dim background
let refreshRate = 5; // refresh map this often [min]
let colorscale = [];
let grades = [];
let cpms=[];
let sv_factor = {};
let clickedSensor = 0;
let startDay = "";
if(!((typeof startday == 'undefined') || (startday == ""))) {
startDay = startday;
}
let type = "";
if (!((typeof typeOfSensor == 'undefined') || (typeOfSensor == ""))) {
type = typeOfSensor;
}
let curSensor = -1; // default-Sensor
if (!((typeof csid == 'undefined') || (csid == ""))) {
curSensor = csid;
$('#btnBack').show();
}
// localStorage.clear();
/* Alle Minute die ktuelle Urzeit anzeigen und
* Alle 'refreshRate' plus 15sec die Grafiken neu zeichnen
* Die Funktion wird alle Sekunde aufgerufen !
*/
let sofort = true;
(function showUhrzeit() {
var d = moment() // akt. Zeit holen
if (sofort || (d.second() == 0)) { // Wenn Minute grade um
$('#h1uhr').html(d.format('HH:mm'));
$('#h1datum').html(d.format('YYYY-MM-DD')); // dann zeit anzeigen
}
if (((d.minute() % refreshRate) == 0) && (d.second() == 15)) { // alle ganzen refreshRate Minuten, 15sec danach
console.log(refreshRate, 'Minuten um, Grafik wird erneuert');
buildMarkers(bounds);
}
sofort = false;
setTimeout(showUhrzeit,1000);
})();
plotMap(curSensor,null);
function calcPolygon(bound) {
return L.polygon([[bounds.getNorth(),bounds.getWest()],[bounds.getNorth(),bounds.getEast()],[bounds.getSouth(),bounds.getEast()],[bounds.getSouth(), bounds.getWest()]], {color:'black', fillOpacity: 0.5});;
}
/*
div.label(style="bottom: 100%;") 10+
div.label(style="bottom: 83.3%;") 5.0
div.label(style="bottom: 66.6%;") 2.0
div.label(style="bottom: 50%;") 1.0
div.label(style="bottom: 33.3%;") 0.5
div.label(style="bottom: 16.6%;") 0.2
div.label(style="bottom: 1%;") 0.1 µSv/h
*/
if (type == 'Geiger') {
colorscale = ['#d73027','#fc8d59','#fee08b','#ffffbf','#d9ef8b','#91cf60','#1a9850', '#808080'];
grades = [ 10, 5, 2, 1, 0.5, 0.2, 0.1, -999];
cpms = [1482, 741, 296, 148, 74, 30, 15, -999];
sv_factor = {'SBM-20':1/2.47, 'SBM-19': 1/9.81888, 'Si22G' : 1 };
}
function getColor(d) {
let val = parseInt(d);
for (let i = 0; i < cpms.length; i++) {
if (val >= cpms[i]) {
return (colorscale[i]);
}
}
}
function buildIcon(color) {
if(type == 'Geiger') {
let radiIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600">' +
'<circle cx="300" cy="300" r="300" fill="' + color + '"/>' +
'<circle cx="300" cy="300" r="50"/>' +
'<path stroke="#000" stroke-width="175" fill="none" stroke-dasharray="171.74" d="M382,158a164,164 0 1,1-164,0"/>' +
'</svg>';
let radiIconUrl = encodeURI("data:image/svg+xml," + radiIcon).replace(new RegExp('#', 'g'), '%23');
return radiIconUrl;
}
}
//$('#overlay').html("Das ist das DIV");
// generate map centered on Stuttgart
async function plotMap(cid, poly) {
// if sensor nbr is give, find coordinates, else use Stuttgart center
let myLatLng;
if( cid != -1) {
myLatLng = await getSensorKoords(curSensor);
} else {
let stgt = await getCoords("Stuttgart");
myLatLng = {lat: parseFloat(stgt.lat),lng: parseFloat(stgt.lon)};
}
map = L.map('map').setView(myLatLng, firstZoom);
L.tileLayer('https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
bounds = map.getBounds();
map.scrollWheelZoom.disable();
map.on('moveend', async function () {
bounds = map.getBounds();
polygon = calcPolygon(bounds);
await buildMarkers(bounds)
});
polygon = calcPolygon(bounds);
/*
var circle = L.circle([loc.coordinates[1], loc.coordinates[0]], {
radius: radius * 1000,
color: 'red',
opacity: 0.3,
// fillColor: '#f03',
fillOpacity: 0,
interactive: false,
}).addTo(map);
*/
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
let div = L.DomUtil.create('div','info legend');
let div_color = L.DomUtil.create('div', 'info legend inner',div);
div_color.innerHTML += 'µSv/h<br />';
// loop through our density intervals and generate a label with a colored square for each interval
for (var i = 0; i < grades.length-1; i++) {
div_color.innerHTML +=
'<i style="background:' + colorscale[i] + '"></i>'+
'<u>&nbsp;&nbsp;&nbsp;</u>&nbsp;&nbsp;' + grades[i] + (i==0?"+":"") + '</upan><br />';
}
div_color.innerHTML += '&nbsp;<i style="background:' + getColor(grades[grades.length-1]) + '"></i> offline';
return div;
};
legend.addTo(map);
if (useStgtBorder) {
fetchStuttgartBounds();
}
await buildMarkers(bounds);
map.on('popupopen', function() {
$('.speciallink').click(function(x) {
showGrafik(clickedSensor);
});
});
map.on('click', function() {
$('#overlay').hide();
});
}
async function buildMarkers(bounds) {
let count = 3;
let sensors;
while (count != 0) {
sensors = await fetchAktualData(bounds)
.catch(e => {
console.log(e);
sensors = null;
});
if ((sensors == null) || (sensors.length == 0)) {
showError(1, 'Daten Laden', 0);
} else {
// dialogError.dialog("close");
break;
}
count--;
}
if (count == 0) {
return;
}
for (let x of sensors.avgs) {
if (x.location == undefined) { // if there is no location defined ...
continue; // ... skip this sensor
} // otherwise create marker
let marker = L.marker([x.location[1],x.location[0]], {
name: x.id,
icon: new L.Icon({
iconUrl: buildIcon(getColor(x.cpm)),
iconSize: [25, 25]
}),
value: x.cpm,
url: '/'+x.id,
rohr: x.name,
lastseen: moment(x.lastSeen).format('YYYY-MM-DD HH:mm')
})
.on('click', e => onMarkerClick(e,true)) // define click- and
// .on('mouseover', e => onMarkerClick(e,false)) // over-handler
// .on('mouseout', e => e.target.closePopup())
.bindPopup(popuptext); // and bint the popup text
marker.addTo(map);
}
// showLastDate(sensors.lastDate);
}
async function onMarkerClick(e, click) {
let item = e.target.options;
let factor = sv_factor[item.rohr];
clickedSensor = item.name;
let popuptext = '<div id="infoTitle"><h5>Sensor: ' + item.name + '</h5>' +
'<div id="infoTable">' +
'<table><tr>';
if(item.value < 0 ) {
popuptext +='<td colspan="2"><span style="color:red;">offline</span></td></tr>' +
'<tr><td>Last seen:</td><td>'+item.lastseen+'</td>';
} else {
popuptext += '<td>' + item.value + '</td><td>cpm</td></tr>' +
'<tr><td>' + Math.round((item.value / 60 *factor)*100)/100 + '</td><td>µSv/h</td>';
}
popuptext +=
'</tr><table>' +
'<div id="infoBtn">'+
'<a href="#" class="speciallink">Grafik anzeigen</a>'+
'</div>' +
'</div>' +
'</div>';
let popup = e.target.getPopup();
popup.setContent(popuptext); // set text into popup
e.target.openPopup(); // show the popup
if(click == true) { // if we clicked
e.target.closePopup(); // show the popup
}
}
$('#btnBack').click(function() {
window.location = "/"+curSensor;
});
$('#btnHelp').click(function() {
dialogHelp.dialog("open");
});
$('#btnCent').click(function() {
// infowindow.setContent("");
// infowindow.close(); // löschen
dialogCenter.dialog("open");
});
let dialogHelp = $('#dialogWinHelpM').dialog({
autoOpen: false,
width: 800,
title: 'Info',
position: {my:'center', at: 'top', of:'#map'},
open: function() {
polygon.addTo(map);
$('#page-mask').css('visibility','visible');
$(this).load('/fsdata/helpmap')
},
close: function() {
$('#page-mask').css('visibility','hidden');
$('#btnHelp').css('background','#0099cc');
polygon.remove();
},
});
/* $(
}'#dialogWinHelpM').dialog({
autoOpen: false,
width: 800,
title: 'Info',
position: {my:'center', at: 'top', of:window},
open: function() {
$('#page-mask').css('visibility','visible');
$(this).load('/fsdata/helpmap')
},
close: function() {
$('#page-mask').css('visibility','hidden');
$('#btnHelp').css('background','#0099cc');
},
modal: true
});
*/
var dialogCenter = $('#dialogCenter').dialog({
autoOpen: false,
width: 800,
title: 'Zentrieren',
open: function() {
$('#page-mask').css('visibility','visible');
polygon.addTo(map);
$(this).load('/fsdata/centermap', function() {
$('#newmapcenter').focus();
});
},
buttons: [
{
text: "OK",
class: 'btnOK',
click: setNewCenter,
style: "margin-right:40px;",
width: 100,
},{
text: "Abbrechen",
click : function() {
dialogCenter.dialog("close");
},
style: "margin-right:40px;",
width: 100,
}
],
modal: true,
close: function() {
$('#page-mask').css('visibility','hidden');
polygon.remove();
},
});
$('.dialog').keypress(function(e) {
if (e.keyCode == 13) {
$('.btnOK').focus();
}
});
function setNewCenter() {
var town = $('#newmapcenter').val();
if ((town == "") || (town == null)) {
town = 'Stuttgart';
}
setCenter(town);
dialogCenter.dialog("close");
$('#btnCent').css('background','#0099cc');
}
/*
// Karte und die Marker erzeugen
async function initMap() { // Map initialisieren
var trafficLayer;
// 'globale' Variable
infowindow = new google.maps.InfoWindow;
geocod = new google.maps.Geocoder;
let myLatLng = await getSensorKoords(curSensor);
$('#nosensor').hide();
map = new google.maps.Map(document.getElementById('map'), {
zoom: 12, // Start-Zoom-Wert
center: myLatLng,
maxZoom: 17, // max. Zoom Level
scrollwheel: false,
});
trafficLayer = new google.maps.TrafficLayer();
*/
/* Autocenter via geoloc - geht nur mit https !!
var infoWindow = new google.maps.InfoWindow({map: map});
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var pos = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
infoWindow.setPosition(pos);
infoWindow.setContent('Location found.');
map.setCenter(pos);
}, function() {
handleLocationError(true, infoWindow, map.getCenter());
});
} else {
// Browser doesn't support Geolocation
handleLocationError(false, infoWindow, map.getCenter());
}
function handleLocationError(browserHasGeolocation, infoWindow, pos) {
infoWindow.setPosition(pos);
infoWindow.setContent(browserHasGeolocation ?
'Error: The Geolocation service failed.' :
'Error: Your browser doesn\'t support geolocation.');
}
var town = localStorage.getItem('defaultmapCenter');
if ((town == "") || (town == null)) {
town = 'Stuttgart';
}
setCenter(town);
*/
/*
$('#btnBack').click(function() {
window.location = "/"+curSensor;
});
$('#btnHelp').click(function() {
dialogHelp.dialog("open");
});
$('#btnCent').click(function() {
infowindow.setContent("");
infowindow.close(); // löschen
dialogCenter.dialog("open");
});
$('#btnTraf').click(function() {
var t = $('#btnTraf').text();
if(t == "Verkehr einblenden") {
trafficLayer.setMap(map);
$('#btnTraf').text('Verkehr ausblenden');
} else {
trafficLayer.setMap(null); // <<<<< that doesn't work !!!
$('#btnTraf').text('Verkehr einblenden');
}
});
$('#fehlersensoren').click(function(){
dialogFehlS.dialog("open");
});
let dialogFehlS = $('#dialogFehlSens').dialog({
autoOpen: false,
width: 800,
title: 'Mein Sensor fehlt',
position: {my:'center', at: 'top', of:'#map'},
open: function() {
$('#page-mask').css('visibility','visible');
$(this).load('/fsdata/fehlersensoren',function() {
$('.psensornr').html('Nr. ' + curSensor);
explainProblem(curSensor);
$('#fehlerliste').click(function(){
dialogFehlS.dialog("close");
dialogFehlL.dialog("open");
});
})
},
close: function() {
$('#page-mask').css('visibility','hidden');
$('#btnHelp').css('background','#0099cc');
},
});
function explainProblem(sensor) {
let fnd = problems.values.findIndex(x => x._id == curSensor);
if (fnd == -1)
return;
let fnbr = problems.values[fnd].problemNr;
let txtnr = problems.texte.findIndex(x => x.nr == fnbr);
let ftxt = 'Grund, warum der Sensor ' + curSensor + ' nicht angezeigt wird: <br /><b>'+
problems.texte[txtnr].txt;
$('#fehlerexplain').html(ftxt+'</b>');
}
let dialogFehlL = $('#dialogFehlList').dialog({
autoOpen: false,
width: 800,
title: 'Liste Fehlerhafter Sensoren',
position: {my:'center', at: 'top', of:'#map'},
open: function() {
$('#page-mask').css('visibility','visible');
$(this).load('/fsdata/fehlerliste')
},
close: function() {
$('#page-mask').css('visibility','hidden');
$('#btnHelp').css('background','#0099cc');
},
});
var dialogHelp = $('#dialogWinHelpM').dialog({
autoOpen: false,
width: 800,
title: 'Info',
position: {my:'center', at: 'top', of:'#map'},
open: function() {
$('#page-mask').css('visibility','visible');
$(this).load('/fsdata/helpmap')
},
close: function() {
$('#page-mask').css('visibility','hidden');
$('#btnHelp').css('background','#0099cc');
},
});
var dialogCenter = $('#dialogCenter').dialog({
autoOpen: false,
width: 800,
title: 'Zentrieren',
open: function() {
$('#page-mask').css('visibility','visible');
$(this).load('/fsdata/centermap', function() {
$('#newmapcenter').focus();
});
},
buttons: [
{
text: "OK",
class: 'btnOK',
click: setNewCenter,
style: "margin-right:40px;",
width: 100,
},{
text: "Abbrechen",
click : function() {
dialogCenter.dialog("close");
},
style: "margin-right:40px;",
width: 100,
}
],
modal: true,
close: function() {
$('#page-mask').css('visibility','hidden');
},
});
$('.dialog').keypress(function(e) {
if (e.keyCode == 13) {
$('.btnOK').focus();
}
});
// Listener für das Ändern des ZOOM-Level:
// Wenn der Zoom-Level > 15 wird, dann die Säulen abh. vom Level in der Dicke anpassen
map.addListener('zoom_changed', function() {
clearMarker();
var zl = map.getZoom();
console.log("Zoom: ", zl);
if (zl > 17) {
sBreit = 60;
for (var m = 0; m < marker.length; m++) {
marker[m].setIcon(getBalken(marker[m].werte[0], 60, 0))
}
} else if (zl > 16) {
sBreit = 50;
for (var m = 0; m < marker.length; m++) {
marker[m].setIcon(getBalken(marker[m].werte[0], 50, 0))
}
} else if (zl > 15) {
sBreit = 40;
for(var m=0; m<marker.length; m++) {
marker[m].setIcon(getBalken(marker[m].werte[0],40,0))
}
} else {
sBreit = 30;
for(var m=0; m<marker.length; m++) {
marker[m].setIcon(getBalken(marker[m].werte[0],30,0))
}
}
});
map.addListener('bounds_changed',function() {
console.log("bounds changed");
newBounds = true;
});
map.addListener('idle',function() {
var info = infowindow.getContent();
if (newBounds) {
newBounds = false;
boundBox = map.getBounds().toJSON();
first = true;
clearMarker();
// fetchProblemSensors();
fetchAktualData();
fetchStuttgartBounds();
}
if (!((info == undefined) || (info == ""))) {
var sid = infowindow.anchor.sensorid;
for(var x = 0; x < marker.length; x++) {
if (marker[x].sensorid == sid) {
infowindow.open(map,marker[x]);
break;
}
}
// console.log("Info on screen >"+info+"<");
}
});
// Alle Marker neu zeichen
function redrawMarker() {
for (var k = 0; k < marker.length; k++) {
marker[k].setMap(null) // Marker löschen
marker[k].setMap(map); // und wieder zeichnen
}
}
function setNewCenter() {
var town = $('#newmapcenter').val();
if ((town == "") || (town == null)) {
town = 'Stuttgart';
}
setCenter(town);
dialogCenter.dialog("close");
$('#btnCent').css('background','#0099cc');
}
google.maps.event.addListener(infowindow,'closeclick',function(){
infowindow.setContent("");
});
image_red = new google.maps.MarkerImage('../nuclear-red.svg',
null,null,null, new google.maps.Size(30,30));
image_green = new google.maps.MarkerImage('../nuclear-green.svg',
null,null,null, new google.maps.Size(30,30));
image_yellow = new google.maps.MarkerImage('../nuclear-yellow.svg',
null,null,null, new google.maps.Size(30,30));
colorRadio = [500,image_red,100,image_yellow,0,image_green,-1,'black'];
}
*/
// Umrechnung Koordinaten auf Adresse
function geocodeLatLng(latlon) {
geocod.geocode({'location': latlon}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
for (var i =0; i<results.length; i++) {
console.log(results[i].formatted_address)
}
console.log("DAS ist GUT:",results[2].formatted_address);
} else {
window.alert('Geocoder failed due to: ' + status);
}
});
}
async function getCoords(city) {
return $.getJSON('/mapdata/getcoord', {city: city})
.fail((jqxhr, textStatus, error) => null )
.done(docs => docs);
}
// Map auf Stadt setzen
async function setCenter(adr) {
let data = await getCoords(adr);
map.setView([parseFloat(data.lat), parseFloat(data.lon)]);
console.log(data);
}
// Aktuelle Daten vom Server holen
function fetchAktualData(box) {
let bnds = null;
if(box != null) {
bnds = [
[box.getWest(), box.getSouth()],
[box.getEast(), box.getNorth()]
];
}
return $.getJSON('/mapdata/getaktdata', {start:startDay, box:bnds})
.fail((jqxhr, textStatus, error) => {
alert("fetchAktualData: Fehler " + error); // if error, show it
})
.done(docs => docs);
}
/*
// Show the last date below tha map grafics
function showLastDate(dt) {
var ld = moment(dt);
$("#mapdate").html("Werte von " + ld.format('YYYY-MM-DD HH:mm'));
}
*/
function fetchStuttgartBounds() {
let points = [];
$.ajax({
type: "GET",
url: "/mapdata/getStuttgart",
dataType: "xml",
success: function(xml) {
$(xml).find("rtept").each(function() {
var lat = parseFloat($(this).attr("lat"));
var lon = parseFloat($(this).attr("lon"));
var p = [lat,lon];
points.push(p);
});
L.polyline(points).addTo(map);
}
});
}
/*
// die Marker erzeugen
// Übergabe
// data aktuelle Daten vom Server
function buildMarkers(data) {
let centerMarker = -1;
var lold = 0.0; // Merke für den Längengrad
// clearMarker();
marker = [];
for (var j=0,x=0; x <data.length; x++) { // alle daten durchgehen
var item = data[x];
// if(item.value10 == -5) {
// continue;
// }
// Wenn der Sensor in der Problem-Datenbank ist, dann
// diesen Sensor auslassen
// if(!allMap) {
// let fnd = problems.values.findIndex(x => x._id == item.id);
// if (fnd != -1) {
// // Problem Nr. 8 und 5 mal ausklammern
// if(item.id == 140) {
// print("140");
// }
// if (!((problems.values[fnd].problemNr == 8) || (problems.values[fnd].problemNr == 5))) {
// continue;
// }
// // }
// }
// }
var offset = 0; // deault Offset ist 0
if (item.location[0] == lold ) { // Wenn Marker auf gleicher Lönge liegen, dann
offset = 10; // enen neuen etwas nach rechts verscheiben
}
lold = item.location[0]; // und die Länge merken
let image;
for (let c=0; c<=colorRadio.length; c+=2) { // Farbzuordnung anhand der
if (item.cpm >= colorRadio[c]) { // Tafel bestimmen
image = colorRadio[c + 1];
break;
}
}
// let image = {
// url: "http://localhost:3005/mapdata/getIcon/"+color,
// size: new google.maps.Size(50, 50),
// };
var oneMarker = new google.maps.Marker({ // Marker-Objekt erzeugen
position: new google.maps.LatLng(item.location[1],item.location[0]), // mit den Koordinaten aus den daten
icon: image,
// icon: getBalken(item.cpm,sBreit,offset), // die Säule dazu
// werte: [item.value10, item.value25], // auch die Werte mit speichern
werte: [item.cpm], // auch die Werte mit speichern
sensorid: item.id, // und auch die Sensor-Nummer
url: '/'+item.id, // URL zum Aufruf der Grafik
latlon: {lat: parseFloat(item.location[1]), lng: parseFloat(item.location[0])}, // und extra nocmla die
// Koordinaten
offset: offset,
});
// if(curSensor == item.id) {
// oneMarker.icon.fillColor = 'white';
// oneMarker.icon.fillOpacity = 0.7;
// // oneMarker.ZIndex = 100;
// centerMarker = j;
// }
marker[j] = oneMarker; // diesen Marker in das Array einfogen
// removeOneMarker(x);
// Click event an den Marker binden. Wenn geklickt wird, dann ein
// Info-Window mit den Werte aufpoppen lassen.
google.maps.event.addListener(marker[j], 'click', function () {
if(this.werte[0] < 0) {
var seit = (this.werte[0] == -2) ? 'Woche' : 'Stunde';
var infoContent = '<div id="infoTitle"><h4>Sensor: ' + this.sensorid + '</h4>' +
'<div id="infoTable">' +
'<table><tr>' +
'<td>Dieser Sensor hat seit mind. </td>' +
'</tr><tr>' +
'<td>1 ' + seit + ' keinen Wert gemeldet</td>' +
'</tr></table>' +
'</div>' +
'</div>';
} else {
var infoContent = '<div id="infoTitle"><h4>Sensor: ' + this.sensorid + '</h4>' +
'<div id="infoTable">' +
'<table><tr>' +
'<td>cpm</td><td>' + this.werte[0] + '</td>' +
'</tr></table>' +
'</div>' +
'<div id="infoHref">' +
'<a href=' + this.url + '>Grafik anzeigen</a>' +
'</div>' +
'</div>';
}
if (infowindow.getContent() != "") { // ein schon offenes InfoWindow
infowindow.close(); // löschen
} // und das Neue mit den Werten
infowindow.setContent(infoContent);
infowindow.open(map, this); // am Marker anzeigen
geocodeLatLng(this.latlon);
});
if(j != centerMarker) {
marker[j].setMap(map); // dann hin zeichnen
}
j++;
}
if(centerMarker >= 0) {
marker[centerMarker].setMap(map);
marker[centerMarker].setZIndex(200);
}
}
*/
// Mit dem Array 'mongoPoints' aus der properties-Datenbank ALLe Sensor-IDs holen,
// die innerhalb (d.h. in Stuttgart) liegen.
function findStuttgartSensors() {
let mp = JSON.stringify(mongoPoints);
$.get('/mapdata/regionSensors', {points : mp }, function (data1, err) { // JSON-Daten vom Server holen
if (err != 'success') {
alert("Fehler <br />" + err); // ggf. fehler melden
} else {
console.log('Stuttgarter Sensoren:',data1);
let se = JSON.stringify(data1);
$.get('/mapdata/storeSensors', { sensors: se }, function(d,e) {
if(e != 'success') {
alert("Fehler beim Speichern der Region-Sensoren");
} else {
console.log("Sensoren gespeichert");
}
});
}
});
}
// fetch coordinates for selected sensor
// use the API
function getSensorKoords(csens) {
let p = new Promise(function(resolve,reject){
// let url = 'https://feinstaub.rexfue.de/api/getprops?sensorid='+csens;
let url = '/api/getprops?sensorid='+csens;
$.get(url, (data,err) => {
if (err != 'success') {
resolve({lat: 48.784373, lng: 9.182});
} else {
// console.log(data);
if (data.values.length == 0) {
resolve({lat: 48.780045, lng: 9.182646});
} else {
resolve({lat: data.values[0].lat, lng: data.values[0].lon});
}
}
;
});
});
return p;
}
var dialogError = $('#errorDialog').dialog({
autoOpen: false,
width: 300,
position: {my:'center', at: 'top+100px', of:window},
open: function() {
$('#page-mask').css('visibility','visible');
},
close: function() {
$('#page-mask').css('visibility','hidden');
$('#btnHelp').css('background','#0099cc');
},
title: "Fehler",
modat: true,
});
function showError(err, txt, id) {
console.log("*** Fehler: " + txt + " from id " + id);
let errtxt = "";
if (err == 1) {
errtxt = "Das Laden der Daten dauert etwas länger";
} else {
errtxt = "Unbekannter Fehler"
}
$('#errorDialog').text(errtxt);
dialogError.dialog("open");
}
function showGrafik(sid) {
$('#overlay').show();
doPlot('oneday','');
}

10
public/nodata.html Normal file
View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Leider bekommen wir z.Zt. keine Daten von sensor.community.
</body>
</html>

23
public/selnewday.html Normal file
View File

@@ -0,0 +1,23 @@
<br />
<form onkeypress="return event.keyCode != 13;">
<fieldset>
<label>
<input name = 'selDates' id='selfrei' type='radio' value='frei' />
<input class='text ui-widget-content ui-corner-all' name = 'selnewday'
id='selnewday' type='text' placeholder='Datum wählen' />
</label>
<label>
<input name = 'selDates' id='selSilvester17' type='radio' value='silvester17' />
Silvester 2017/18
</label>
<label>
<input name = 'selDates' id='selSilvester18' type='radio' value='silvester18' />
Silvester 2018/19
</label>
<label>
<input name = 'selDates' id='selheute' type='radio' value='today' />
zurück zu 'heute' (live) <br />
</label>
</fieldset>
</form>

7
public/selsensor.html Normal file
View File

@@ -0,0 +1,7 @@
<br />
<form onkeypress="return event.keyCode != 13;">
<fieldset>
<input class='text ui-widget-content ui-corner-all' name = 'selsensor' id='selsensor' type='text' />
</fieldset>
</form>

4
public/sensors.json Normal file
View File

@@ -0,0 +1,4 @@
[
{ "urlName": "rxf", "realName": "Reinhard", "showName": true,"sensor": "140", "location":"Stuttgart-West"},
{ "urlName": "lowa", "realName": "Lothar", "showName": true, "sensor": "187","location":"Stuttgart-West", "sensorpress": "13927446"}
]

34
public/settingsD.html Normal file
View File

@@ -0,0 +1,34 @@
<br />
<form onkeypress="return event.keyCode != 13;">
<fieldset>
<h6 class="setting_head">Gleitende Mittelwert-Bildung bei 24-Stunden-Darstellung über</h6>
<div id="avgselect">
<select name = 'average' id='average'>
</select>
<br /><br />
</div>
<div id="bandgap">
<input id="bandgap_in" type="checkbox" >
<label for="bandgap_in">Langzeit-Mittelwert mit Bereich anzeigen</label>
</div>
<div id="bandgapregion">
<div id="bandgapset">
<label>Zeitbereich für Langzeit-Mittelwert:
<select name="bandgapval" id="bandgapval" disabled>
<option selected>48 h</option>
<option>72 h</option>
<option>96 h</option>
<option>120 h</option>
</select>
</label>
</div>
<div id="bandgapran">
<label>Bereich in Prozent (5..50):<br />
<input id="bandgapran_in" type="number" value="15" min="5" max="50" disabled>
</label>
</div>
</div>
<input type='submit' tabindex='-1' style="position:absolute; top:-1000px">
</fieldset>
</form>

39
public/settingsW.html Normal file
View File

@@ -0,0 +1,39 @@
<br />
<form onkeypress="return event.keyCode != 13;">
<fieldset>
<h6 class="setting_head">Mittelwert-Bildung bei 7-Tage-Darstellung:</h6>
<div id="avgselect">
<input type='radio' name = 'avgsel' id='movingavg' value='false'>
</input>
<label for="avgsel">gleitend</label>&nbsp;&nbsp;&nbsp;
<input type='radio' name = 'avgsel' id='staticavg' value='true'>
</input>
<label for="lines">statisch </label>
<label id="lueber" for="average">über:</label>
<select name = 'average' id='average'>
</select>
<br /><br />
</div>
<!-- <div id="scatterlines">
<h4>Darstellung:</h4>
<input type='radio' name = 'scatter' id='scatter' value='false'>
</input>
<label for="scatter">Punktwolke</label>&nbsp;&nbsp;&nbsp;
<input type='radio' name = 'scatter' id='lines' value='true'>
</input>
<label for="lines">Linien</label><br />
</div>
<div id="logyaxis">
<h4>Y-Achse:</h4>
<input type='radio' name = 'logy' id='log' value='false'>
</input>
<label for="log">logarithmisch</label>&nbsp;&nbsp;&nbsp;
<input type='radio' name = 'logy' id='linear' value='true'>
</input>
<label for="linear">linear</label><br />
</div>
<br /><br />
--> <input type='submit' tabindex='-1' style="position:absolute; top:-1000px">
</fieldset>
</form>

1
public/spinner.svg Normal file
View File

@@ -0,0 +1 @@
<svg width='120px' height='120px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-default"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(0 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(30 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.08333333333333333s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(60 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.16666666666666666s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(90 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.25s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(120 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.3333333333333333s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(150 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.4166666666666667s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(180 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.5s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(210 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.5833333333333334s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(240 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.6666666666666666s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(270 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.75s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(300 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.8333333333333334s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#545454' transform='rotate(330 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.9166666666666666s' repeatCount='indefinite'/></rect></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

18
public/splash.html Executable file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id='splash' style="font-family: 'arial';">
<h5>Version 2.8.0</h5>
<p>Die Kraftwerke werden nun als rote Punkte dargestellt. Außerdem werden weitere Atomanlagen angezeigt.
</p>Bei der Grafik wird in der 24h-Darstellung nun jeder Wert (nicht mehr nur der 10min-Mittelwert) angezeigt und
zusätzlich ein einstellbarer gleitender Mittelwert.
<p>
</p>
Siehe auch die Info-Seite.
</div>
</body>
</html>

22
public/statistik.html Normal file
View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id='stati' style="font-family: 'arial';">
<h1>Statistik für Sensor Nr: <span id="stat_sid">140</span></h1>
<table id="stat_table">
<caption>Statistische Daten</caption>
<thead>
<tr>
<th>Zeitraum</th>
<th>Bezeichnung</th>
<th>P10</th>
<th>P2.5</th>
</tr>
</thead>
</table>
</div>
</body>
</html>

14
public/ymax.html Normal file
View File

@@ -0,0 +1,14 @@
<br />
<form onkeypress="return event.keyCode != 13;">
<fieldset>
<label for="newymax">neuer yMax-Wert</label>
<input class='text ui-widget-content ui-corner-all' name = 'newymax' id='newymax' type='number' />
<span id="underymax">Wert muss zwischen 50 und 2000 liegen.<br /> Leer bedeutet: wieder automatisch.</span>
<!-- <br />>
<label for="sensornr">Default Sensor-Nummer (wird verwendet, wenn beim Aufruf kein Sensor angegeben ist):</label>
<input class='text ui-widget-content ui-corner-all' name = 'sensornr' id='sensornr' type='text' placeholder='Sensor-Nummer' />
-->
<input type='submit' tabindex='-1' style="position:absolute; top:-1000px">
</fieldset>
</form>

860
routes/apidata.js Normal file
View File

@@ -0,0 +1,860 @@
"use strict";
const express = require('express');
const router = express.Router();
const moment = require('moment');
const mathe = require('mathjs');
const fs = require('fs');
const $ = require('jquery');
const util = require('./utilities');
// Mongo wird in app.js geöffnet und verbunden und bleibt immer verbunden !!
// Get the city for given Sensor
async function getCity(db, sensorid) {
let pcoll = db.collection("properties");
let properties = await pcoll.findOne({_id:sensorid});
let addr = "unKnown";
try {
addr = properties.location[0].address.country + " " +
properties.location[0].address.plz + " " +
properties.location[0].address.city;
}
catch(e) {
// do nothing, just skip
}
return addr;
}
/*
// API to put data into dBase
router.post('/putdata/:what', function(req,res) {
let db = req.app.get('dbase');
let cmd = req.query.cmd;
let what = req.params.what;
if (what=='problems') {
putAPIproblemdata(db, cmd, req.body)
.then((erg) => {
// console.log(erg);
res.send(erg);
});
} else {
res.send ( {error:'wrong call'})
}
});
*/
//API to read all datas from the database
router.get('/getdata', function (req, res) {
let db = req.app.get('dbase');
let sid=1;
if (!((req.query.sensorid == undefined) || (req.query.sensorid == ""))) {
sid = parseInt(req.query.sensorid);
}
let avg = req.query.avg;
let span = req.query.span;
let dt = req.query.datetime;
if(isNaN(sid)) {
getAPIdataTown(db, req.query.sensorid, avg, span, dt, res)
.then(erg => res.json(erg));
} else {
getAPIdataSensor(db, sid, avg, span, dt)
.then(erg => res.json(erg));
}
// if(req.query.sensorid == "all") {
// getAPIalldata(db, dt)
// .then(erg => res.json(erg));
});
router.get('/getprops', function (req, res) {
let db = req.app.get('dbase');
let sid=0;
if (!((req.query.sensorid == undefined) || (req.query.sensorid == ""))) {
sid = parseInt(req.query.sensorid);
}
let dt = "1900-01-01T00:00:00";
if(!((req.query.since === undefined) || (req.query.since ==""))) {
dt = req.query.since;
}
let name = ""
if(!((req.query.sensortyp === undefined) || (req.query.sensortyp ==""))) {
name = req.query.sensortyp;
}
getAPIprops(db, sid, name, dt)
.then(erg => res.json(erg));
});
router.get('/getmapsensors', function (req, res) {
let db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
let bounds = {};
bounds.south = parseFloat(req.query.south);
bounds.north = parseFloat(req.query.north);
bounds.east = parseFloat(req.query.east);
bounds.west = parseFloat(req.query.west);
bounds.poly = [];
if (req.query.poly != undefined) {
bounds.poly = JSON.parse(req.query.poly);
}
let ptype = parseInt(req.query.ptype);
let stype = req.query.stype;
let st = req.query.start;
getApiMapSensors(db, bounds, stype, ptype, st)
.then(erg => res.json(erg));
});
router.get('/getcities', (req,res) => {
let db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
let country = req.query.country;
let type = req.query.type;
if (country == undefined) {
country = 'all';
}
if (type == undefined) {
type = 'PM';
}
getApiCities(db,country.toUpperCase(),type.toUpperCase())
.then(erg => res.json(erg));
});
// Get address from coordinates using OpenStreetMap Nominatim API
router.get('/getaddress', function (req, res) {
let db = req.app.get('dbase');
let sid = 0;
if (!((req.query.sensorid == undefined) || (req.query.sensorid == ""))) {
sid = parseInt(req.query.sensorid);
}
if (sid === 0) {
res.json({address: null, err: "No sensorid provided"});
return;
}
util.getAddress(db, sid)
.then(erg => res.json(erg))
.catch(err => {
console.log("getaddress error:", err);
res.json({address: null, err: err.message});
});
});
// ***********************************************************
// putAPIproblemdata - Daten in der DB speichern
//
// Parameter:
// db: Mongo-Database
// cmd: 'start', 'end', 'data'
// data: JSON string to put into db
//
// return:
// error, if not correctly saved, else null
// ***********************************************************
/*
async function putAPIproblemdata(db, cmd, data) {
// console.log("putAPIproblemdata"," Länge: ", data.length);
let collection = db.collection('problemsensors');
if(cmd == 'end') {
return {error: 'done'};
}
if(cmd == 'data') {
let inserted;
let upd = [];
for (let i=0; i< data.length; i++){
let one = { updateOne: { "filter" : { "_id": data[i]._id}, "update": { $set: data[i]}, "upsert": true } };
upd.push(one);
}
try {
inserted = await collection.bulkWrite(upd)
// console.log("Modifiziert:", inserted.modifiedCount)
}
catch(e) {
console.log(e)
}
return {error: "OK"}
}
return { error: 'wrong command'};
}
*/
// ***********************************************************
// getAPIprobSensors - Get data for problematic sensors
//
// Parameter:
// db: Mongo-Database
//
// return:
// JSON Dokument mit den angefragten Werten
// ***********************************************************
/*
async function getAPIprobSensors(db,pnr,only,withTxt) {
let coll = db.collection('problemsensors');
let query = {_id: {$gt: 0}};
let proj = {};
let count = 0;
if(withTxt == undefined) {
withTxt = true;
}
if (pnr != 0) {
query = { $and: [ {problemNr: pnr}, {_id: {$gt: 0}} ]} ;
}
if(only) {
proj = {_id: 1};
}
let docs = await coll.find(query,proj).toArray();
if(docs != null) {
count = docs.length;
}
let texte = {};
if(withTxt) {
let tt = await coll.findOne({_id: 0});
if (tt == null) {
texte.texte = [];
}
}
let ret;
if (only) {
ret = {count: count, problemNr: pnr, values: docs, texte: texte.texte};
} else {
ret = {count: count, values: docs, texte: texte.texte};
}
if(!withTxt) {
delete ret.texte;
}
return ret
}
*/
// ***********************************************************
// getAPIdataNew - Get data direct via API for one sensor
//
// Parameter:
// db: Mongo-Database
// sid: sensor ID
// mavg: time over that to build the average [minutes]
// dauer: duration for the data [hours]
// start: starting point of 'dauer'
// end: end of 'dauer'
//
// return:
// JSON Dokument mit den angefragten Werten
// ***********************************************************
/*
async function getAPIdataNew(db,sid,mavg,dauer,start,end, gstart) {
let st = moment(start).startOf('day'); // clone start/end ..
let en = moment(end).startOf('day'); // .. and set to start of day
let retur = {sid: sid, avg: mavg, span: dauer, start: gstart};
let collection = db.collection('values');
let ergArr = [];
let values;
for (; st <= en; st.add(1, 'd')) {
let id = sid + '_' + st.format('YYYYMMDD');
try {
values = await collection.findOne({
_id: id
});
}
catch (e) {
console.log(e);
}
if(values && (values.values.length != 0)) {
ergArr.push(...values.values);
}
}
if (ergArr.length == 0) {
retur.count = 0;
retur['values'] = [];
} else {
// Bereich einschränken
let v = [];
let fnd = ergArr.findIndex(x => x.datetime >= start);
if (fnd != -1) {
v = ergArr.slice(fnd);
ergArr = v;
}
fnd = ergArr.findIndex(x => x.dateTime > end);
if (fnd != -1) {
v = ergArr.slice(-fnd);
erg.Arr = v;
}
if ((mavg === undefined) || (mavg == 1)) {
retur.count = ergArr.length;
retur['values'] = ergArr;
}
// Mittelwert berechnen
let x = util.calcMovingAverage(db, ergArr, mavg, 0, 0, true);
fnd = x.findIndex(u => u.dt >= gstart);
if((fnd == -1) && (dauer == 0)) {
let y = x.slice(-1);
x = y;
} else {
if (fnd != -1) {
let y = x.slice(fnd);
x = y;
}
}
retur.count = x.length;
retur.values = x;
}
return retur;
}
*/
// ******************************************************************
// getAPITN - Get data direct via API for all sensors in a town
//
// Parameter:
// dbase: Mongo-Database
// sensors: array of sensors
// mavg: time over that to build the average [minutes]
// dauer: duration for the data [hours]
// start: starting point of 'dauer'
// end: end of 'dauer'
// town: name of town
//
// return:
// JSON document with data for ALL sensors in town
//
// ***** Neue DB-Struktur - Versuch
//
// ********************************************************************
async function getAPITN (dbase,sensors,mavg,dauer,start,end,gstart,town) {
// Fetch for all this sensors
let los = moment(); // debug, to time it
let erg = {sid:town, avg: mavg, span: dauer, start: gstart, count: 0, sensordata: []}; // prepare object
let val;
for(let j=0; j<sensors.length; j++) { // loop thru array of sensors
try {
val = await getAPIdata(dbase,sensors[j],mavg,dauer,start,end,gstart); // get data for obe sensor
if(val.count != 0) { // if there is data
delete val.avg; // delete unnecessary elements
delete val.span;
delete val.start;
erg.sensordata.push(val); // and push data to result array
}
}
catch(e) {
console.log(e);
}
}
console.log("Zeit in getAPIdataTown:",(moment()-los)/1000,'[sec]'); // time it
console.log('Daten für',erg.sensordata.length,' Sensoren gelesen');
erg.count = erg.sensordata.length; // save count
return erg; // and return all data
}
// ******************************************************************
// getAPIdataTown - Get data direct via API for all sensors in a town
//
// Call:
// http://feinstaub.rexfue.de/api/getdata/?sensorid=stuttgart&avg=5&span=12
//
// mit:
// sensorid: Name der Stadt
// avg: Mittelwert-Bildung über xxx Minuten
// span: Zeitraum für die Mittelwertbildung in Stunden
// dt: Startzeitpunkt
//
// Parameter:
// db: Mongo-Database
// town: name of town
// avg: time over that to build the average [minutes]
// span: duration for the data [hours]
// dt: starting point of 'span'
// res: http-object to send result
//
// return:
// nothing; JSON document will be sent back
//
//
// For every town, there has to be an JSON-file with the
// sensornumbers of ervery sensor living in that town.
//
// ***** Neue DB-Struktur - Versuch
//
// ********************************************************************
async function getAPIdataTown(db, town, avg, span, dt, res) {
// get sensors for the town as array of ids
let p = parseParams(avg, span, dt);
// get sensor numbers from town-sensor-file
let sensors = [];
let tw = town.toLowerCase();
let data = fs.readFileSync(tw+'.txt');
sensors = JSON.parse(data);
return getAPITN (db,sensors,p.mavg,p.dauer,p.start,p.end,p.gstart, town);
}
// ******************************************************************
// getAPIdataSenssor - Get data direct via API for all sensors in a town
//
// Call:
// http://feinstaub.rexfue.de/api/getdata/?sensorid=140&avg=5&span=12&datetime=2018-08-ß02T20:12:00
//
// mit:
// sensorid: ID des gewümschten Sensors
// avg: Mittelwert-Bildung über xxx Minuten
// span: Zeitraum für die Mittelwertbildung in Stunden
// datetime: Startzeitpunkt
//
// Parameter:
// db: Datenbank
// sid: ID of sensor
// avg: time over that to build the average [minutes]
// span: duration for the data [hours]
// dt: starting point of 'span'
//
// return:
// nothing; JSON document will be sent back
//
//
// ***** Neue DB-Struktur - Versuch
//
// ********************************************************************
async function getAPIdataSensor(db, sid, avg, span, dt) {
let p = parseParams(avg, span, dt);
return getAPIdata(db,sid,p.mavg,p.dauer,p.start,p.end,p.gstart)
}
// *********************************************
// Get data direct via API for one sensor
//
// Call:
// http://feinstaub.rexfue.de/api?sid=1234&avg=5&span=24
//
// mit:
// sid: Sensornummer
// avg: Mittelwert-Bildung über xxx Minuten
// span: Zeitraum für die Mittelwertbildung in Stunden
//
// return:
// JSON Dokument mit den angefragten Werten
// *********************************************
async function getAPIdata(db, sid, mavg, dauer, start, end, gstart) {
let values = [];
let retur = {sid: sid, avg: mavg, span: dauer, start: gstart};
// First, determine sensor type from properties
let pcoll = db.collection("properties");
let props = await pcoll.findOne({_id: sid});
if (!props) {
retur.count = 0;
retur['values'] = [];
retur.error = 'Sensor not found';
return retur;
}
// Determine collection based on type
let collectionName;
if (props.type === 'radioactivity') {
collectionName = 'radioactivity_sensors';
} else {
// Assume THP for any other type
collectionName = 'thp_sensors';
}
let collection = db.collection(collectionName);
try {
values = await collection.find(
{
sensorid: sid,
datetime: {
$gte: new Date(start),
$lt: new Date(end)
}
},
{
projection: {_id: 0},
sort: {datetime: 1}
}
).toArray()
} catch (e) {
console.log(e);
}
if(values.length == 0) {
retur.count = 0;
retur['values'] = [];
} else {
if((mavg===undefined) || (mavg == 1)) {
retur.count = values.length;
retur['values'] = values;
}
let x = await util.calcMovingAverage(db, sid, values, mavg, true);
let fnd = x.findIndex(u => u.dt >= gstart);
if((fnd == -1) && (dauer == 0)) {
let y = x.slice(-1);
x = y;
} else {
if (fnd != -1) {
let y = x.slice(fnd);
x = y;
}
}
retur.count = x.length;
retur.values = x;
}
return retur;
}
/* ===============================================================
// PM (FEINSTAUB) FUNCTIONS REMOVED - Not needed in new DB
// ===============================================================
// *********************************************
// Get data direct via API for ALL sensor - WAS PM-SPECIFIC
//
// Call:
// http://feinstaub.rexfue.de/api?sid=all&datetime="2018-06-02T12:00Z"
//
// mit:
// dt: Zeitpunkt, für den die Daten geholt werden
// Es werden Daten <= dem Zeitpunkt geholt
//
// return:
// JSON Dokument mit den angefragten Werten
// *********************************************
async function getAPIalldata(db,dt) {
// REMOVED - Was PM-specific, not applicable to Radiation/THP sensors
return { error: 'Function removed - was PM-specific' };
}
function isPM(name) {
// REMOVED - No longer needed, only Radiation and THP sensors
return false;
}
*/
// *********************************************
// Get properties for all sensors
//
// Call:
// http://feinstaub.rexfue.de/api/getprops?sensorid=1234&since=2810-03-23&sensortyp=SDS011
//
// mit:
// sid: Sensornummer (all -> alle Sensoren)
// since: seit dem Datum (incl)
// sensortyp: Type des Sensors (z.B. SDS011 oder PM(für alle Feinstaub-Sensoren))
//
// params:
// db Datenbank
// sid Sensor-Nummer oder all
// typ Sensor-Typ
// dt Datum, ab wann gesucht werden soll
//
// return:
// JSON Dokument mit den angefragten werten
// *********************************************
async function getAPIprops(db,sid,typ,dt) {
let properties = [];
let erg = [];
let entry = {};
let pcoll = db.collection("properties");
let query = {};
if(sid == 0) {
if(typ == "") {
query = {}; // Get all sensors
} else if (typ == 'radioactivity') {
query = {type: 'radioactivity'};
} else if (typ == 'THP') {
query = {type: {$ne: 'radioactivity'}}; // Anything not radioactivity is THP
} else {
// For specific sensor type names, check in name array
query = {'name.name': typ};
}
} else {
query = { _id:sid };
}
properties = await pcoll.find(query).sort({_id: 1}).toArray();
for (let i = 0; i < properties.length; i++) {
let loclast = (properties[i].location.length)-1;
// Get current name from array
let currentName = properties[i].name;
if (Array.isArray(properties[i].name)) {
currentName = properties[i].name[properties[i].name.length - 1].name;
}
// Get last_seen from values.timestamp if available
let lastSeen = properties[i].last_seen || (properties[i].values && properties[i].values.timestamp);
if (!lastSeen) {
lastSeen = moment("1900-01-01T00:00Z");
} else if (lastSeen.$date) {
lastSeen = moment(lastSeen.$date);
} else {
lastSeen = moment(lastSeen);
}
// Build result object
let result = {
sid: properties[i]._id,
typ: currentName,
lat: properties[i].location[loclast].loc.coordinates[1],
lon: properties[i].location[loclast].loc.coordinates[0],
alt: properties[i].location[loclast].altitude,
lastSeen: lastSeen.format(),
country: properties[i].location[loclast].country
};
// Add since date if available
if (properties[i].location[loclast].since) {
if (properties[i].location[loclast].since.$date) {
result.since = moment(properties[i].location[loclast].since.$date).format();
} else {
result.since = moment(properties[i].location[loclast].since).format();
}
}
erg.push(result);
}
entry.sensortyp = typ =="" ? "all" : typ;
entry.count = erg.length;
entry.since = dt;
entry.values = erg;
return entry;
}
// *******************************************************************
// parseParams - parse the given paramaters
//
// params:
// avg: averegae time in min
// span: data range in hours
// dt: start date of data range
//
// return:
// object with:
// mavg: average time in min (default 1min, max; 1440min))
// dauer: data range in hoiurs (default 24h, max: 720h)
// start: start date/time to calculate average
// end: end of data range
// gstart: start on datarange (without avg-time)
//
// **********************************************************************
function parseParams(avg, span, dt) {
let params = {};
params.mavg = 1; // default average
if (avg !== undefined) { // if acg defined ..
params.mavg = parseInt(avg); // .. use it
}
if (params.mavg > 1440) { params.mavg = 1440;} // avgmax = 1 day
params.dauer = 24; // span default 1 day
if(span !== undefined) { // if defined ..
params.dauer = parseInt(span); // .. use it
}
if (params.dauer > 720) { params.dauer = 720;} // spanmax = 30 days
params.start = moment(); // default start -> now
params.end = moment(); // define end
if(dt != undefined) { // if defined ..
params.start = moment(dt); // .. use it ..
params.end = moment(dt).add(params.dauer,'h'); // .. and calculate new end
} else { // if not defined, calc start ..
params.start.subtract(params.dauer, 'h'); // .. from span (= dauer)
}
params.gstart = moment(params.start);
params.start.subtract(params.mavg,'m'); // start earlier to calc right average
return params;
}
// ******************************************************************
// getAPIproblemSensors - Get senosor-IDs of problematic sensor
// within map bounds
//
// Call:
// http://feinstaub.rexfue.de/api/getprobsens/?bounds=[bounds]&ptype=1&datetime=2018-08-ß02T20:12:00
//
// mit:
// bounds: corner coordinates of map or polygone for town border
// ptype: type of problem (optional)
// datetime: day for which to calculate (optional)
//
// Parameter:
// db: database
// bounds: corner coordinates of map or polygone for town border
// stype: 'PM' or 'THP' or 'TH' os 'T' or 'H' (default: 'PM')
// ptype: type of problem (undefined means ALL problems)
// st: date to calculate for
//
// return:
// array wit 24h-Average-Value for the last 24hours for every sensor
//
//
//
// ********************************************************************
async function getApiMapSensors(db, bounds, stype, ptype, st) {
// fetch list of sensor ids within bounds
let slist = [];
let collection = db.collection('properties');
let loc;
let name = 'PM';
if (stype != undefined) {
name = stype;
}
if (bounds.poly.length != 0) {
loc = {
'location.0.loc': {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [bounds.poly],
}
}
}
}
} else {
loc = {
'location.0.loc': {
$geoWithin: {
$box: [
[bounds.west, bounds.south],
[bounds.east, bounds.north]
]
}
}
}
}
let docs = await collection.find(loc, {_id: 1, name:1}).toArray(); // find all data within map borders (box)
// .toArray(async function (err, docs) {
// console.log(docs);
for (let i = docs.length - 1; i >= 0; i--) {
if (!((name == 'PM') == isPM(docs[i].name))) {
docs.splice(i, 1);
continue;
}
let erg = await getAPIdataSensor(db, docs[i]._id, 1, 24);
if (erg.values.length == 0) {
docs[i].mean = -1;
docs[i].std = 0;
docs[i].type = 'noData';
delete docs[i].name;
continue;
}
// if (!erg.values[0].hasOwnProperty('P1')) {
// continue;
// }
// console.log(erg);
let result = erg.values.map(x => x.P1);
// console.log(result);
let mean = mathe.mean(result);
let std = mathe.std(result);
console.log(mean);
docs[i].mean = Math.round(mean);
docs[i].std = std;
delete docs[i].name;
}
// console.log(docs);
// nun in docs alle Mittelwerte über die letzten 24h
// nun sortieren
let docs_sorted = docs.sort((a, b) => a.mean - b.mean);
let docs_std = docs.sort((a, b) => a.std - b.std);
// console.log(docs_sorted);
return docs_sorted;
}
function isPM(name) {
let pms = ['SDS011','PMS7003','PMS3003','PMS5003','HPM','SDS021','PPD42NS'];
if (pms.findIndex(n => n == name) != -1) {
return true;
}
return false;
}
// ******************************************************************
// getAPICities - Get all cities, containing sensors
//
//
// Call:
// http://feinstaub.rexfue.de/api/getcities/?country=de
//
// with:
// county: 2 character coutrycode to find the city (all or absent => world)
// type: PM or THP (or absent => PM)
//
// Parameter:
// db: database
// county: 2 character coutrycode to find the citie (all => world)
// pm: PM for particulate sensors, THP for temp/hum/press-sensors
//
// return:
// JSON array with cities
//
//
//
// ********************************************************************
async function getApiCities(db, country, sensorType) {
let slist = [];
let collection = db.collection('properties');
let query = {};
// Build country query - in new DB, country is directly in location, not in address
if(country != 'ALL') {
query = {'location.country': country};
}
// Filter by sensor type
let matchtype = {};
if (sensorType == 'radioactivity') {
matchtype = {type: 'radioactivity'};
} else if (sensorType == 'THP') {
matchtype = {type: {$ne: 'radioactivity'}}; // Anything not radioactivity
}
// If sensorType is not specified or something else, get all
let docs = await collection.aggregate([
{$match: matchtype},
{ $match: query},
{ $project: {
_id:1,
name:1,
location: {
'$map': {
input: '$location',
as: 'm',
in: {
country: '$$m.address.country',
city: '$$m.address.city',
plz: '$$m.address.plz'
}
}
}
}},
{ $unwind: '$location'},
{ $group: {
sensorids: { $addToSet: '$_id'},
_id: '$location.city',
plz: { $addToSet: '$location.plz'},
country: { $first: '$location.country' }
}},
{ $project: {
_id: 0,
city: '$_id',
sensors: { count: {$size: '$sensorids'}, ids : '$sensorids'},
plz: '$plz',
country: '$country',
}}
]).toArray();
// console.log(docs);
return { count: docs.length, cities: docs };
}
module.exports = router;
module.exports.api = { getCity };

134
routes/data.js Normal file
View File

@@ -0,0 +1,134 @@
var express = require('express');
var router = express.Router();
var moment = require('moment');
// Get latest 10 readings out from the database
router.get('/latest10', function(req,res) {
var collection = req.strom;
collection.find({},{limit:5, sort: { date: -1}},function(e,docs) {
res.json(docs);
});
});
// Get readings for the last hour out from the database
router.get('/onehour', function(req,res) {
var db = req.db;
var collection = db.get('strom');
var st = req.query.start;
st = st.substring(0,st.length-1);
var start = moment(st);
var end = moment(st);
start.subtract(1,'h');
collection.find({date: { $gte: new Date(start), $lt: new Date(end)}},{sort: { date: 1} },function(e,docs) {
// console.dir(docs);
res.json(docs);
});
});
router.get('/oneyear', function(req,res) {
var year = req.query.year;
if(year == undefined) {
year = moment().year();
}
var st = req.query.start;
st = st.substring(0,10);
// console.log("st: " + st);
var start = moment(st);
var db = req.db;
var collection = db.get('stromDay');
var sDat = moment("01-01-"+year, "MM-DD-YYYY"); // Start-Datum 1.1.year
var curDat = moment();
var erg = []; // hier die Werte sammeln
var last = { date: 0, wert: 0, cnt: 0};
var first=0;
// console.log("Start " + start.format());
start.subtract(30,'days'); // Daten für 30 Tage holen
// console.log("Start-30 " + start.format());
collection.find( { date: { $gte: new Date(start) }}, {sort: { date: 1} },function(err,docs){
if(err) {
console.log("StromM-Error: ");
console.dir(err);
} else {
var lang = (docs.length > 31) ? 31 : docs.length;
for(var i=0; i<lang; i++) {
// console.log(i + " " + docs[i].date);
if(last.date != 0) {
// console.log("last.date: " + last.date);
erg.push({ date: last.date, wert: (docs[i].zstand - last.wert)});
if( (docs[i].zstand - last.wert) != 0) {
last.cnt++;
}
} else {
first = docs[i].zstand;
}
last.date = docs[i].date;
last.wert = docs[i].zstand;
}
}
// console.log(erg);
var avg = (last.wert - first)/last.cnt;
res.send ({ average: avg, erg: erg });
});
/*
async.whilst(
function() { return sDat < curDat; },
function (callback) {
collection.findOne( { date: { $gte: new Date(sDat) }}, {sort: { date: 1} }, function(err,data) {
if(err) {
console.log("StromM-Error: " + err);
callback(err);
} else {
if(last.date != 0) {
erg.push({ date: last.date, wert: (data.all - last.wert)});
if( (data.all - last.wert) != 0) {
last.cnt++;
}
} else {
first = data.all;
}
last.date = data.date;
last.wert = data.all;
}
sDat.add(1, 'days'); // add 1 day of seconds
callback();
});
},
function (err) {
var avg = (last.wert - first)/last.cnt;
res.send ({ average: avg, erg: erg });
}
);
*/
});
//Get readings for the last hour out from the database
router.get('/latest10I', function(req,res) {
var collection = req.strom;
var start = moment();
// console.log("Start=" + start);
start.subtract(20,'s');
collection.find({date: { $gte: new Date(start)}},{sort: { date: 1} },function(e,docs) {
res.json(docs);
});
});
//Get the reading of the 1. of January for the current year
router.get('/zstand', function(req,res) {
var db = req.db;
var curYear = moment().year();
var collection = db.get('zaehler');
collection.findOne({'year' : curYear},function(e,docs) {
res.json(docs);
});
});
module.exports = router;

482
routes/fsdata.js Normal file
View File

@@ -0,0 +1,482 @@
"use strict";
const express = require('express');
const router = express.Router();
const moment = require('moment');
const mathe = require('mathjs');
const util = require('./utilities');
// Mongo wird in app.js geöffnet und verbunden und bleibt immer verbunden !!
let sv_factor = {'SBM-20': 1 / 2.47, 'SBM-19': 1 / 9.81888, 'Si22G': 0.081438};
//Get readings for all data out ot the database
router.get('/getfs/:week', function (req, res) {
let week = req.params.week;
let db = req.app.get('dbase');
let st = req.query.start;
let sid = parseInt(req.query.sensorid);
let sname = req.query.sensorname;
let avg = req.query.avgTime;
let live = (req.query.live == 'true');
let movingAvg = (req.query.moving=='true');
let longAVG = req.query.longAVG;
let system = req.query.os;
if (week == 'oneday') {
console.log(`Operating System = ${system}`);
getDWMData(db, sid, st, avg, live, movingAvg, 1, longAVG)
.then(erg => res.json(erg));
} else if (week == 'oneweek') {
getDWMData(db, sid, st, avg, live, movingAvg, 7)
.then(erg => res.json(erg));
} else if (week == 'onemonth') {
getDWMData(db, sid, st, 1440, false, false, 31)
.then(erg => res.json(erg));
} else if (week == 'korr') {
getSensorProperties(db,sid)
.then(erg => res.json(erg));
} else {
res.json({'error': 'MIST VERDAMMTER!!'});
}
});
// fetch name of given sensor-id
function getSensorName(db,sid) {
const p = new Promise((resolve,reject) => {
let coll = db.collection('properties');
coll.findOne({_id: parseInt(sid)})
.then(erg => {
// name is now an array, get the current (last) entry
if (erg && erg.name && Array.isArray(erg.name)) {
resolve(erg.name[erg.name.length - 1].name);
} else {
resolve(erg ? erg.name : null);
}
})
.catch(err => {
console.log('getSensorName',err);
reject(err);
});
});
return p
}
// fetch the properties for the given sensor
async function getSensorProperties(db,sid) {
let start = new Date();
console.log("Get properties for", sid,"from DB");
let sensorEntries = [{'sid':sid}];
let coll = db.collection('properties');
let properties;
try {
properties = await coll.findOne({_id: sid});
}
catch(e) {
console.log("getSensorProperties",e);
return {};
}
console.log("got properties - time:", new Date() - start);
if(properties == null) return null;
// name is now an array, get the current (last) entry
let currentName = properties.name;
if (Array.isArray(properties.name)) {
currentName = properties.name[properties.name.length - 1].name;
}
// Overwrite name field with string for frontend compatibility
properties.name = currentName;
sensorEntries[0]['name'] = currentName;
// Check if othersensors exists (might not in new DB structure)
if (!properties.othersensors || properties.othersensors.length === 0) {
properties.othersensors = sensorEntries;
return properties;
}
let mustbeobject = false;
for(let i = 0, j=1; i<properties.othersensors.length; i++) {
let es = properties.othersensors[i];
let e = {};
if (es != null) {
if ( typeof es === 'object') {
mustbeobject=true;
e.sid = es.id;
e.name = es.name;
} else {
if(mustbeobject) { continue; }
e.sid = es;
e.name = await getSensorName(db, es);
}
}
sensorEntries[j] = e;
j++;
}
sensorEntries.sort(function (a, b) {
if (a.sid < b.sid) {
return -1;
}
if (a.sid > b.sid) {
return 1;
}
return 0;
});
properties.othersensors = sensorEntries;
return properties;
}
async function readRadiationMovingAverage(db, sid, start, end, average, factor) {
let docs = [];
let collection = db.collection('radioactivity_sensors');
try {
docs = await collection.find({
sensorid: sid,
datetime: {
$gte: new Date(start),
$lt: new Date(end)
}
}, {sort: {datetime: 1}}).toArray();
} catch (e) {
console.log('readRadiationMovingAverage',e);
return [];
}
if (docs.length == 0) {
return [];
} else {
let d = await util.calcMovingAverage(db, sid, docs, average , false, factor);
return d.RAD;
}
}
async function readRadiationAverages(db, sid, start, end, average, factor) {
let docs = [];
let collection = db.collection('radioactivity_sensors');
try {
docs = await collection.aggregate([
{$match: {sensorid: sid, datetime: {$gte: new Date(start), $lt: new Date(end)}}},
{$sort: {datetime: 1}},
{
$group: {
_id: {
$toDate: {
$subtract: [
{$toLong: '$datetime'},
{$mod: [{$toLong: '$datetime'}, 1000 * 60 * average]} // aggregate every average minutes
]
}
},
cpmAvg: {$avg: '$values.counts_per_minute'},
cpmSum: {$sum: '$values.counts_per_minute'},
count: {$sum: 1}
}
},
{ $addFields: { uSvphAvg: { $multiply: ["$cpmAvg", factor]}}},
{$sort: {_id: 1}}
]).toArray();
}
catch(e) {
console.log('readRadiationAverages', e);
return [];
}
return docs;
}
async function readClimateAverages(db, sid, start, end, average) {
let docs = [];
let collection = db.collection('thp_sensors');
try {
docs = await collection.aggregate([
{$match: {sensorid: sid, datetime: {$gte: new Date(start), $lt: new Date(end)}}},
{$sort: {datetime: 1}},
{
$group: {
_id: {
$toDate: {
$subtract: [
{$toLong: '$datetime'},
{$mod: [{$toLong: '$datetime'}, 1000 * 60 * average]} // aggregate every average min
]
}
},
tempAvg: {$avg: '$values.temperature'}, // average over every 10min
humiAvg: {$avg: '$values.humidity'},
pressSeaAvg: {$avg: '$values.pressure_at_sealevel'},
count: {$sum: 1}
}
},
{$sort: {_id: 1}}
]).toArray();
}
catch(e) {
console.log('readClimateAverage', e);
return [];
}
return docs;
}
function calcTimeRange(st, range, live, avg) {
let start = moment(st);
let end = moment(st);
if(range == 1) { // one day
if (live == true) {
start.subtract(24, 'h');
start.subtract(avg, 'm');
} else {
start.subtract(avg, 'm');
end.add(24, 'h');
}
} else if (range == 7) { // one week (7 days)
if (live == true) {
start.subtract(24 * 8, 'h');
} else {
start.subtract(24 * 8, 'h');
// end.add(24 * 7, 'h');
console.log(start.format(), end.format());
}
} else if (range == 31) { // one month (31 days)
start=start.startOf('day');
end = end.startOf('day');
start.subtract(33, 'd');
} else if (range >= 48) { // 48 hours
if(live == true) {
start.subtract(range, 'h')
}
}
return { start: start, end: end };
}
// get data for one day, one week or one month from the database
async function getDWMData(db, sensorid, st, avg, live, doMoving, span, longAVG) {
let docs = [];
let ret = {radiation: [], climate: []};
// first fetch properties for this sensor
let properties = await getSensorProperties(db,sensorid);
// calculate time range
let timerange = calcTimeRange(st, span, live, avg);
for (let n = 0; n<properties.othersensors.length; n++) {
let sid = properties.othersensors[n].sid;
let sname = properties.othersensors[n].name;
try {
if (sname.startsWith("Radiation")) {
let factor = sv_factor[sname.substring(10)] / 60;
if(doMoving) {
docs = await readRadiationMovingAverage(db, sid, timerange.start, timerange.end, avg, factor);
} else {
docs = await readRadiationAverages(db, sid, timerange.start, timerange.end, avg, factor);
}
let avg48 = null;
if (longAVG != undefined) {
let tr = calcTimeRange(st, longAVG, true, avg);
let avg48_docs = await readRadiationAverages(db, sid, tr.start, tr.end, longAVG*60, factor);
avg48 = avg48_docs[0];
}
ret['radiation'] = {sid: sid, sname: sname, avg48: avg48, values: docs};
} else if (sname == "BME280") {
docs = await readClimateAverages(db, sid, timerange.start, timerange.end, 10);
if (docs.length != 0) {
ret['climate'] = {sid: sid, sname: sname, values: docs};
}
} else {
ret['error'] = "Sensor not of right type (unknown)";
}
} catch (e) {
console.log('getDayData', e);
}
}
return ret;
}
// für die Wochenanzeige die Daten als gleitenden Mittelwert über 24h durchrechnen
// ===============================================================
// PM (FEINSTAUB) FUNCTIONS REMOVED - Not needed in new DB
// ===============================================================
/*
// und in einem neuen Array übergeben
function movAvgSDSWeek(data) {
var neuData = [];
const oneDay = 3600*24;
// first convert date to timestam (in secs)
for (var i=0; i<data.length; i++) {
data[i].datetime = ( new Date(data[i].datetime)) / 1000; // the math does the convertion
}
// now calculate the moving average over 24 hours
let left=0, roll_sum=0, nd = [];
for (let right =0; right < data.length; right++) {
// if(right == 200) {
// console.log("right = 200");
// }
if(data[right].P1 != undefined) {
roll_sum += data[right].P1;
}
if(data[right].P10 != undefined) {
roll_sum += data[right].P10;
}
while( data[left].datetime <= data[right].datetime - oneDay) {
if(data[left].P1 != undefined) {
roll_sum -= data[left].P1;
}
if(data[left].P10 != undefined) {
roll_sum -= data[left].P10;
}
left += 1;
}
nd[right] = { 'P1024': roll_sum/ (right-left+1)+5};
}
for (var i=1, j=0; i< data.length; i++, j++) {
var sum1=0, sum2 = 0, cnt1 = 0, cnt2 = 0;
var di = data[i].datetime;
for (var k=i; k>=0 ; k--) {
if (data[k].datetime+oneDay < di) {
break;
}
if (data[k].P1 !== undefined) {
sum1 += data[k].P1;
cnt1++;
}
if (data[k].P2 !== undefined) {
sum2 += data[k].P2;
cnt2++;
}
if (data[k].P10 !== undefined) {
sum1 += data[k].P10;
cnt1++;
}
if (data[k].P2_5 !== undefined) {
sum2 += data[k].P2_5;
cnt2++;
}
}
neuData[j] = {'P10': sum1 / cnt1, 'P2_5': sum2 / cnt2, 'date': data[i].datetime} ;
}
// finally shrink datasize, so that max. 1000 values will be returned
var neu1 = [];
var step = Math.round(neuData.length / 500);
// if (step == 0) step = 1;
step = 1;
for (var i = 0, j=0; i < neuData.length; i+=step, j++) {
var d = neuData.slice(i,i+step)
var p1=0, p2=0;
for(var k=0; k<d.length; k++) {
if (d[k].P10 > p1) {
p1 = d[k].P10;
}
if (d[k].P2_5 > p2) {
p2 = d[k].P2_5;
}
}
neu1[j] = {'P10': p1, 'P2_5': p2, 'date': neuData[i].date*1000} ;
}
return { 'ndold': neu1, 'ndnew': nd };
}
function calcMinMaxAvgSDS(data,isp10) {
var p1=[], p2=[];
for (var i = 0; i < data.length; i++) {
if (data[i].P10 != undefined) {
p1.push(data[i].P10);
}
if (data[i].P2_5 != undefined) {
p2.push(data[i].P2_5);
}
if (data[i].P1 != undefined) {
p1.push(data[i].P1);
}
if (data[i].P2 != undefined) {
p2.push(data[i].P2);
}
}
return {
'P10_max': mathe.max(p1),
'P2_5_max': mathe.max(p2),
'P10_min': mathe.min(p1),
'P2_5_min': mathe.min(p2),
'P10_avg' : mathe.mean(p1),
'P2_5_avg' : mathe.mean(p2) };
}
*/
function calcMinMaxAvgDHT(data) {
var t=[], h=[];
for (var i=0; i<data.length; i++) {
if(data[i].temperature != undefined) {
t.push(data[i].temperature);
}
if(data[i].humidity != undefined) {
h.push(data[i].humidity);
}
}
return {
'temp_max': mathe.max(t),
'humi_max': mathe.max(h),
'temp_min': mathe.min(t),
'humi_min': mathe.min(h),
'temp_avg' : mathe.mean(t),
'humi_avg' : mathe.mean(h) };
}
async function calcMinMaxAvgBMP(db, sid, data) {
var t = [], p = [];
for (var i = 0; i < data.length; i++) {
if (data[i].temperature != undefined) {
t.push(data[i].temperature);
}
if (data[i].pressure != undefined) {
p.push(data[i].pressure);
}
}
let altitude = await util.getAltitude(db, sid);
p = util.calcSealevelPressure(p, '', altitude);
return {
'temp_max': mathe.max(t), 'press_max': mathe.max(p),
'temp_min': mathe.min(t), 'press_min': mathe.min(p),
'temp_avg': mathe.mean(t), 'press_avg': mathe.mean(p)
};
}
async function calcMinMaxAvgBME(db, sid, data) {
var t = [], h = [], p = [], sumt = 0;
for (var i = 0; i < data.length; i++) {
if (data[i].temperature != undefined) {
t.push(data[i].temperature);
}
if (data[i].humidity != undefined) {
h.push(data[i].humidity);
}
if (data[i].pressure != undefined) {
p.push(data[i].pressure);
}
}
let altitude = await util.getAltitude(db, sid);
p = util.calcSealevelPressure(p, '', altitude);
return {
'temp_max': mathe.max(t), 'humi_max': mathe.max(h), 'press_max': mathe.max(p),
'temp_min': mathe.min(t), 'humi_min': mathe.min(h), 'press_min': mathe.min(p),
'temp_avg': mathe.mean(t), 'humi_avg': mathe.mean(h), 'press_avg': mathe.mean(p)
};
}
/* PM (FEINSTAUB) FUNCTIONS REMOVED
function isPM(name) {
if ((name == "SDS011") || (name.startsWith("PPD")) || (name.startsWith("PMS"))) {
return true;
}
return false;
}
// Statistiken für den Sensor holen und übergeben - PM only
async function getStatistics(db,sensorid) {
// REMOVED - Was PM-specific
return { error: 'PM statistics removed in new DB' };
}
*/
module.exports = router;

59
routes/index.js Normal file
View File

@@ -0,0 +1,59 @@
var express = require('express');
var router = express.Router();
//Version
var pkg = require('../package.json');
var idx = 'index';
var mapit = 'mapit';
var tit = 'Geiger';
const MAINT = (process.env.MAINTENANCE=="true");
if (MAINT==true) {
idx = 'maintenance';
mapit = 'maintenance';
}
// GET home page.
router.get('/', function(req, res, next) {
res.render(mapit, {
title: tit+'-Map',
version: pkg.version,
date: pkg.date,
name: 'map',
param: req.query.addr,
stday: req.query.stday,
showAllMap: req.query.mall,
csensor: req.query.sid,
zoom:req.query.zoom,
city:req.query.city,
stype: tit,
splash: req.query.splash == 'true',
});
});
router.get('/:nam', function(req, res, next) {
var name = req.params.nam;
if(name == 'index') {
idx = 'index';
name = '140';
title += '_I';
} else {
res.render(mapit, {
title: tit+'-Map',
version : pkg.version,
date : pkg.date,
param: req.query.addr,
stday: req.query.stday,
zoom:req.query.zoom,
city:req.query.city,
csensor: name,
splash: req.query.splash == 'true',
name: 'map'});
}
});
module.exports = router;

294
routes/mapdata.js Executable file
View File

@@ -0,0 +1,294 @@
"use strict";
var express = require('express');
var router = express.Router();
var moment = require('moment');
const axios = require('axios');
let $ = require('jquery');
var fs = require('fs');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// URL to get coordinates for cities
const NOMINATIM_URL="https://nominatim.openstreetmap.org/search?format=json&limit=3&q=";
// Mongo wird in app.js geöffnet und verbunden und bleibt immer verbunden !!
// Fetch the actual out of the dbase
router.get('/getaktdata/', async function (req, res) {
var db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
let box = req.query.box;
let poly = [];
var collection = db.collection('properties'); // Using properties collection now
var aktData = []; // hier die daten sammeln
var now = moment(); // akt. Uhrzeit
var lastDate = 0;
let south=null,north=null,east=null,west=null;
let loc = {};
if(req.query.poly != undefined) {
poly = JSON.parse(req.query.poly);
}
if (!((box == "") || (box == undefined))) {
south = parseFloat(box[0][1]);
north = parseFloat(box[1][1]);
east = parseFloat(box[1][0]);
west = parseFloat(box[0][0]);
console.log("getaktdata: S=", south, " N=", north, " E=", east, " W=", west)
}
console.log("getaktdata: now fetching data from DB");
// Build geo query - location is now in location array (last element)
if(poly.length != 0) {
loc = {
'location.loc': {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [poly],
}
}
},
type: 'radioactivity' // Only radiation sensors for map
}
} else if (south !== null) {
loc = {
'location.loc': {
$geoWithin: {
$box: [
[west, south],
[east, north]
]
}
},
type: 'radioactivity' // Only radiation sensors for map
}
} else {
loc = {
type: 'radioactivity' // Only radiation sensors for map
}
}
try {
let docs = await collection.find(loc).toArray();
if (docs == null) {
console.log("getaktdata: docs==null");
res.json({"avgs": [], "lastDate": null});
return;
}
console.log("getaktdata: data fetched, length=",docs.length);
for (var i = 0; i < docs.length; i++) {
var item = docs[i];
// Skip if no values or no location
if (!item.values || !item.location || item.location.length === 0) {
continue;
}
// Get current location (last in array)
let currentLoc = item.location[item.location.length - 1];
// Get current name (last in array)
let currentName = item.name;
if (Array.isArray(item.name)) {
currentName = item.name[item.name.length - 1].name;
}
var oneAktData = {};
oneAktData['location'] = currentLoc.loc.coordinates;
oneAktData['id'] = item._id;
// Handle timestamp - might be in $date format
let timestamp = item.values.timestamp;
if (timestamp && timestamp.$date) {
timestamp = new Date(timestamp.$date);
} else {
timestamp = new Date(timestamp);
}
oneAktData['lastSeen'] = timestamp;
// Extract sensor type from name (e.g., "Radiation SBM-20" -> "SBM-20")
oneAktData['name'] = currentName.replace('Radiation ', '');
// indoor is now a number (0 or 1)
oneAktData['indoor'] = currentLoc.indoor === 1;
var dt = timestamp;
if ((now - dt) >= (7 * 24 * 3600 * 1000)) { // älter als 1 Woche ->
oneAktData['cpm'] = -2; // -2 zurückgeben
} else if ((now - dt) >= (2 * 3600 * 1000)) { // älter als 2 Stunde ->
oneAktData['cpm'] = -1; // -1 zurückgeben
} else {
oneAktData['cpm'] = -5; // bedeutet -> nicht anzeigen
if (item.values.hasOwnProperty('counts_per_minute')) {
oneAktData['cpm'] = item.values.counts_per_minute.toFixed(0); // und merken
}
if (dt > lastDate) {
lastDate = dt;
}
}
aktData.push(oneAktData); // dies ganzen Werte nun in das Array
}
res.json({"avgs": aktData, "lastDate": lastDate}); // alles bearbeitet -> Array senden
}
catch(e) {
console.log("Problem mit getaktdata", e);
res.json({"avgs": [], "lastDate": null});
return;
}
});
/* ===============================================================
// AKW (Nuclear Power Plant) FUNCTIONS - COMMENTED OUT FOR NOW
// Will be needed later, so not deleting
// ===============================================================
// Fetch all akw data out of the dbase
router.get('/getakwdata/', async function (req, res) {
const db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
let collection = db.collection('akws'); // die 'korrelation' verwenden
let erg = [];
let docs = [];
console.log("getakwdata: now fetching data from DB");
try {
docs = await collection.find().toArray(); // find all
if (docs == null) {
console.log("getakwdata: docs==null");
res.json(erg);
return;
}
console.log("getawkdata: data fetched from akws, length=",docs.length);
for (var i = 0; i < docs.length; i++) {
var item = docs[i];
var oneAktData = {};
oneAktData['location'] = {
type: 'Point',
coordinates: [item.lon, item.lat]
};
oneAktData['name'] = item.Name;
oneAktData['active'] = item.Status == 'aktiv';
oneAktData['start'] = item.Baujahr;
oneAktData['end'] = item.Stillgeleg;
oneAktData['type'] = item.Status === 'aktiv' ? 'akw_a' : 'akw_s';
oneAktData['link'] = item.Wiki_Link;
erg.push(oneAktData); // dies ganzen Werte nun in das Array
}
collection = db.collection('th1_akws');
docs = await collection.find().toArray();
if (docs == null) {
console.log("getakwdata: docs==null");
res.json(erg);
return;
}
console.log("getawkdata: data fetched from th_akws, length=", docs.length);
for (let i = 0; i < docs.length; i++) {
const item = docs[i];
let oneAktData = {};
let loc = item.geo.substr(6).split(' ');
let lon = parseFloat(loc[0]);
let lat = parseFloat(loc[1]);
oneAktData['location'] = {
type: 'Point',
coordinates: [lon, lat]
};
oneAktData['name'] = item.name;
oneAktData['typeText'] = item.types;
oneAktData['type'] = item.types == "Nuclear power plant" ? 'akw_a' : 'other';
oneAktData['link'] = item.item;
if (item.itemServiceretirement != undefined) {
oneAktData['ende'] = item.itemServiceretirement.substr(0,4);
}
if (item.itemServiceentry != undefined) {
oneAktData['begin'] = item.itemServiceentry.substr(0,4);
}
// Push only NOT 'Nuclear Power Plants' into data array
// if(item.types != 'Nuclear power plant') {
erg.push(oneAktData);
// }
}
res.json(erg);
}
catch(e) {
console.log("Problem mit getakwdata", e);
res.json({"akws": [], "research": [], "fusion": [], "waste": [],});
return;
}
});
*/
router.get('/getStuttgart/', function (req, res) {
fs.readFile('public/Stuttgart.gpx',function(err,data) {
res.send(data);
})
});
router.get('/getcoord/', function (req, res) {
getCoordinates(req.query.city)
.then(erg => res.json(erg));
});
router.get('/getIcon/:col', function (req, res) {
let color = req.params.col;
// fs.readFile('public/radioak4_30.png',function(err,data) {
fs.readFile('public/nuclear-'+color+'.svg',function(err,data) {
res.send(data);
})
});
router.get('/regionSensors/', function (req, res) {
var db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
var spoints = JSON.parse(req.query.points);
getRegionSensors(db,spoints)
.then(erg => res.json(erg));
});
async function getRegionSensors(db,p) {
let properties = [];
let pcoll = db.collection("properties");
properties = await pcoll.find({
'location.loc': {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [ p ],
}
}
},
type: 'radioactivity' // Only radiation sensors
},{_id: 1, name: 1}
).toArray();
let sids = [];
properties.forEach(x => {
sids.push(x._id);
});
console.log('Anzahl gefundene Sensoren:',sids.length);
return sids;
}
router.get('/storeSensors/', function (req, res) {
let data = req.query.sensors;
fs.writeFile('stuttgart.txt',data,(err) => {
if (err) throw(err);
console.log("Sensoren gespeichert");
});
});
async function getCoordinates(city) {
let start = moment()
let url = NOMINATIM_URL + city;
const response = await axios.get(encodeURI(url));
const data = response.data;
if(data.length !== 0) {
console.log(`Fetching of city ${city} needs ${(moment() - start) / 1000} seconds.`)
return data[0];
} else {
console.log(`City ${city} not found` )
return {lat: 0, lon: 0}
}
}
module.exports = router;

245
routes/utilities.js Normal file
View File

@@ -0,0 +1,245 @@
"use strict";
const moment = require('moment');
// *********************************************
// Calculate moving average over the data array.
//
// params:
// data: array of data
// mav: time in minutes to average
// name: name of sensor
// api: default=false, true = API -> no akt. values
//
// return:
// array with averaged values
// TODO <----- die ersten Einträge in newData mit 0 füllen bis zum Beginn des average
// *********************************************
async function calcMovingAverage(db, sid, data, mav, api, factor) {
var newDataT = [], newDataR = [];
var avgTime = mav*60; // average time in sec
let havepressure = false; // true: we have pressure
if (avgTime === 0) { // if there's nothing to average, then
avgTime = 1;
}
// first convert date to timestamp (in secs)
for (var i=0; i<data.length; i++) {
// Handle both old datetime and new datetime.$date format
if (data[i].datetime && data[i].datetime.$date) {
data[i].datetime = (new Date(data[i].datetime.$date)) / 1000;
} else {
data[i].datetime = (new Date(data[i].datetime)) / 1000;
}
}
let left=0, roll_sum3=0, roll_sum4=0, roll_sum5=0, roll_sum6=0;
// Check if we have pressure data
if(data[0] && data[0].values && data[0].values.pressure != undefined) {
havepressure = true;
} else if (data[0] && data[0].pressure != undefined) {
havepressure = true;
}
for (let right =0; right < data.length; right++) {
// Handle values object (new structure)
let temperature = data[right].values ? data[right].values.temperature : data[right].temperature;
let humidity = data[right].values ? data[right].values.humidity : data[right].humidity;
let pressure = data[right].values ? data[right].values.pressure : data[right].pressure;
let counts_per_minute = data[right].values ? data[right].values.counts_per_minute : data[right].counts_per_minute;
if (temperature != undefined) {
roll_sum3 += temperature;
}
if (humidity != undefined) {
roll_sum4 += humidity;
}
if (pressure != undefined) {
roll_sum5 += pressure;
}
if (counts_per_minute != undefined) {
roll_sum6 += counts_per_minute;
}
while (data[left].datetime <= data[right].datetime - avgTime) {
let temp_left = data[left].values ? data[left].values.temperature : data[left].temperature;
let humi_left = data[left].values ? data[left].values.humidity : data[left].humidity;
let press_left = data[left].values ? data[left].values.pressure : data[left].pressure;
let cpm_left = data[left].values ? data[left].values.counts_per_minute : data[left].counts_per_minute;
if (temp_left != undefined) {
roll_sum3 -= temp_left;
}
if (humi_left != undefined) {
roll_sum4 -= humi_left;
}
if (press_left != undefined) {
roll_sum5 -= press_left;
}
if (cpm_left != undefined) {
roll_sum6 -= cpm_left;
}
left += 1;
}
if (api == true) {
newDataT[right] = {'dt': moment.unix(data[right].datetime)};
if (roll_sum3 != 0) newDataT[right]['T'] = (roll_sum3 / (right - left + 1)).toFixed(1);
if (roll_sum4 != 0) newDataT[right]['H'] = (roll_sum4 / (right - left + 1)).toFixed(0);
if (roll_sum5 != 0) newDataT[right]['P'] = (roll_sum5 / (right - left + 1)).toFixed(2);
newDataR[right] = {'date': data[right].datetime * 1000};
if (roll_sum6 != 0) newDataR[right]['cpm'] = roll_sum6 / (right - left + 1);
} else {
newDataT[right] = {'date': data[right].datetime * 1000};
if (roll_sum3 != 0) newDataT[right]['temp_mav'] = roll_sum3 / (right - left + 1);
if (roll_sum4 != 0) newDataT[right]['humi_mav'] = roll_sum4 / (right - left + 1);
if (roll_sum5 != 0) newDataT[right]['press_mav'] = roll_sum5 / (right - left + 1);
newDataR[right] = {'_id': moment.unix(data[right].datetime).toDate()};
if (roll_sum6 != 0) {
let val = roll_sum6 / (right - left + 1);
newDataR[right]['cpmAvg'] = val;
newDataR[right]['uSvphAvg'] = val * factor;
}
}
}
if (havepressure == true) {
let altitude = await getAltitude(db, sid);
if (api == true) {
newDataT = calcSealevelPressure(newDataT, 'P', altitude);
for (let i = 0; i < newDataT.length; i++) {
newDataT[i].P = (newDataT[i].P / 100).toFixed(0);
}
} else {
newDataT = calcSealevelPressure(newDataT, 'press_mav', altitude);
}
}
if (api == true) {
// Return THP or Radiation data depending on what's available
return (roll_sum3 != 0 || roll_sum4 != 0 || roll_sum5 != 0) ? newDataT : newDataR;
}
return { 'THP' : newDataT , 'RAD' : newDataR};
}
// Berechnung des barometrischen Druckes auf Seehöhe
//
// Formel (lt. WikiPedia):
//
// p[0] = p[h] * ((T[h] / (T[h] + 0,0065 * h) ) ^-5.255)
//
// mit
// p[0] Druck auf NN (in hPa)
// p[h] gemessener Druck auf Höhe h (in m)
// T[h] gemessene Temperatur auf Höhe h in K (== t+273,15)
// h Höhe über NN in m
//
// press -> aktuelle Druck am Ort
// temp -> aktuelle Temperatur
// alti -> Höhe über NN im m
//
// NEU NEU NEU
// Formel aus dem BMP180 Datenblatt
//
// p0 = ph / pow(1.0 - (altitude/44330.0), 5.255);
//
//
//
// Rückgabe: normierter Druck auf Sehhhöhe
//
function calcSealevelPressure(data, p, alti) {
if (!((alti == 0) || (alti == undefined))) {
for (let i = 0; i < data.length; i++) {
if (p=='') {
data[i] = data[i] / Math.pow(1.0 - (alti / 44330.0), 5.255);
} else {
data[i][p] = data[i][p] / Math.pow(1.0 - (alti / 44330.0), 5.255);
}
}
}
return data
}
// Aus der 'properties'-collection die altitude für die
// übergebene sid rausholen
async function getAltitude(db,sid) {
let collection = db.collection('properties');
try {
let values = await collection.findOne({"_id":sid});
return values.location[values.location.length-1].altitude;
}
catch(e) {
console.log("GetAltitude Error",e);
return 0
}
}
// Get address from coordinates using OpenStreetMap Nominatim API
// params: db, sensorid
// returns: {address: {street, plz, city}, err}
async function getAddress(db, sid) {
const axios = require('axios');
let ret = {address: {street: "", plz: "", city: ""}, err: null};
// Get sensor properties to extract coordinates
let collection = db.collection('properties');
let props;
try {
props = await collection.findOne({"_id": sid});
if (!props || !props.location || props.location.length === 0) {
return {address: ret.address, err: "No location found for sensor"};
}
} catch(e) {
console.log("getAddress - DB Error:", e);
return {address: ret.address, err: e.message};
}
// Get coordinates from last location entry
let coord = props.location[props.location.length - 1].loc.coordinates;
let lat = coord[1];
let lon = coord[0];
// Call Nominatim API for reverse geocoding
let url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json`;
try {
const response = await axios(encodeURI(url), {
headers: {
'User-Agent': 'MultiGeiger-Web/2.9.6' // Nominatim requires User-Agent
}
});
if (response.status !== 200) {
return {address: ret.address, err: `Nominatim API returned status ${response.status}`};
}
let akt = response.data.address;
// Try to find city name in various fields
const CITY = ['city', 'town', 'village', 'suburb', 'county'];
let city = "unknown";
for (let c of CITY) {
if(akt[c] !== undefined) {
city = akt[c];
break;
}
}
ret.address = {
street: (akt.road ? akt.road : ""),
plz: (akt.postcode ? akt.postcode : ""),
city: city
};
} catch (e) {
console.log("getAddress - API Error:", e.message);
return {address: ret.address, err: e.message};
}
return ret;
}
module.exports.calcMovingAverage = calcMovingAverage;
module.exports.calcSealevelPressure = calcSealevelPressure;
module.exports.getAltitude = getAltitude;
module.exports.getAddress = getAddress;

1
stuttgart.txt Normal file
View File

@@ -0,0 +1 @@
[783,2199,444,6549,6455,1356,4740,970,287,7691,2157,2007,1448,3975,2446,2820,1282,2590,7150,7895,309,2023,6553,422,1148,793,2488,757,6890,3117,3559,6487,1026,721,6509,175,2912,789,227,675,286,649,6457,3143,1487,1553,1434,2544,2281,4508,1589,1483,140,529,3885,795,4208,1997,1715,1627,715,1092,415,6582,1599,7733,665,6655,801,4636,1703,271,535,217,2147,658,7037,7193,335,609,986,4935,1485,7007,773,2996,219,295,1186,1420,620,231,209,737,6797,6479,6414,4640,6809,6854,430,4837,962,215,6059,1122,146,5399,191,924,4718,2299,968,7651,436,4250,4313,553,547,4827,4458,1645,7003,5756,5127,1324,723,585,151,3229,3199,5929,299,549,2203,1657,3591,1635,2478,751,673,5750,4486,2700,1501,450,426,6588,1931,2870,7853,125,641,6763,671,5935,4383,7619,1739,809,5135,7100,663,466,164,3441,237,187,1477,432,2586,3066,1334,6791,7078,4460,7227,331,1378,7511,1364,727,1216,2796,424,7092,767,7295,5957,2480,7561,7187,6400,2614,438,946,6107,3034,1757,7505,956,950,50,40,48,576,4652,7497,1228,1204,7887,8149,8177,8181,8183,1949,5477,189,978,763,8258,8275,8289,8329,8343,8349,8431,8435,8458,153,8530,645,679,8584,677,5758,8739,9124,8938,8913,8867,9005,2454,9166,8881,6408,2608,9212,9302,3145,143,9430,9485,9524,9757,9759,9649,9840,9842,9868,9900,9914,9962,1300,1625,10138,5355,10311,10321,10327,10418,10477,10507,10529,10548,10546,10567,10569,10573,10675,10677,10697,10707,10847,10955,10963,11197,11272,11285,11309,11461,11137,11598,11612,11618,11651,11653,11626,11933,1941,12086,12173,12040,12294,12298,12322,12441,12531,12581,12826,13083,13173,13463,13380,13670,13674,13747,13764,13766,3847,14015,14205,14356,1519,14580,14688,14866,14963,1118,15246,15329,15356,15697,1142,15846,15795,15954]

46
views/index.pug Normal file
View File

@@ -0,0 +1,46 @@
extends layout
block content
script.
var selName='#{name}';
var parm='#{param}';
var startday='#{stday}';
var typeOfSensor='#{stype}';
var stype='#{csensor}';
var splash = '#{splash}';
#overlay
#buttons
#buttonsLeft
button.btn.btn-primary#btnmo(value="month") 30 d
button.btn.btn-primary#btnwe(value="week") 7 d
button.btn.btn-primary#btnda(autofocus="true",value="day") 24 h
//#buttonsRight
button.btn.btn-primary#btnSet(value='set') Einstellung
button.btn.btn-primary#btnMap(value='map') Karte
// button.btn.btn-primary#btnStat(value='map') Statistik
button.btn.btn-primary#btnHelp(value='help') Info
#placeholderFS_1
#placeholderTHP_1
#placeholderFS_2
#placeholderTHP_2
#placeholderFS_3
#placeholderTHP_3
#dialogWinSet.dialog
#dialogStatistik
#dialogWinHelp
#dialogNewSensor.dialog
#dialogNewDay.dialog
#dialogError
#dialogymax
#page-mask
#alarm
<iframe src="https://www.stuttgart.de/feinstaubalarm/widget/xtrasmall?noAlarm=noOutput" height="90" scrolling="no" style="border:none; width:98%"></iframe>
img#mySpinner(style='display:none' src="spinner.svg" alt="Lade Daten ...")

44
views/layout.pug Normal file
View File

@@ -0,0 +1,44 @@
doctype html
html
head
title= title
meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
link(rel='stylesheet', href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous")
link(rel='stylesheet', href='/css/jquery-ui-1.12.1/jquery-ui.min.css')
link(rel='stylesheet', href='/css/jquery-ui-timepicker-addon.css')
link(href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet")
link(rel='stylesheet', href='/fontawesome/css/all.css')
link(rel='stylesheet', href='/css/leaflet-velocity.min.css')
link(rel='stylesheet', href='/css/style.css')
link(rel='icon', href='images/favicon.ico' type='image/x-icon')
body
#wrapper
block content
script(src="/jquery.min.js")
script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous")
script(src="/js/jquery-ui.min.js")
script(src="/js/datepicker-de.js")
script(src="/js/jquery-ui-timepicker-addon.js")
link(href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet")
script(src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js")
script(src="https://cdn.jsdelivr.net/npm/highcharts@9.3.3/highcharts.js")
script(src="https://cdn.jsdelivr.net/npm/highcharts@9.3.3/highcharts-more.js")
script(src="/js/customEvents.min.js")
link(rel="stylesheet", href='/leaflet.css')
script(src='/leaflet.js')
script(src='/js/leaflet-velocity.min.js')
link(rel="stylesheet", href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css")
script(src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js")
script(src="/d3.js")
script(src="/moment.min.js")
script(src='/js/global.js')

13
views/maintenance.pug Normal file
View File

@@ -0,0 +1,13 @@
extends layout
block content
#meldung
h2 Wartung
p
| Die Seite wird zur Zeit erneuert,<br />
| Bitte probieren Sie es später nochmal.<br /><br />
#author
#mailadr
a(href="mailto:rexfue@gmail.com") mailto:rexfue@gmail.com
#versn Version: #{version} vom #{date}

131
views/mapit.pug Executable file
View File

@@ -0,0 +1,131 @@
extends layout
block content
script.
var selName='#{name}';
var startday='#{stday}';
var csid = '#{csensor}';
var fzoom = '#{zoom}';
const curversion = '#{version}'
const splash = '#{splash}';
const city = '#{city}';
header
nav.navbar.navbar-expand-sm.navbar-light.navbg
button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbarSupportContent")
span.navbar-toggler-icon
#navbarSupportContent.collapse.navbar-collapse
ul.navbar-nav.mr-auto
.btn-group#zrohr
button.btn.btn-success.mybuttons Zählrohre
button.btn.btn-success.mybuttons.dropdown-toggle.dropdown-toggle-split(type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false")
span.sr-only Zählrohre
.dropdown-menu(aria-labelledby="dropdownMenu2")
.dropdown-header Zählrohre
.custom-control.custom-radio.ml-3
input.custom-control-input.btnrohr#btnall(type="radio" value="all" name="rohre" checked)
label.custom-control-label(for="btnall") Alle Zählrohre anzeigen
.custom-control.custom-radio.ml-3
input.custom-control-input.btnrohr#btnsig(type="radio" value="sig" name="rohre")
label.custom-control-label(for="btnsig") nur Si22G anzeigen
.btn-group#kraftw
button.btn.btn-success.mybuttons AKWs/Anlagen
button.btn.btn-success.mybuttons.dropdown-toggle.dropdown-toggle-split(type="button" id="dropdownMenu3" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false")
span.sr-only AKWs/Anlagen
.dropdown-menu(aria-labelledby="dropdownMenu3")
.dropdown-header Kraftwerke anzeigen
.custom-control.custom-checkbox.ml-3
input.custom-control-input.btnakw#btnakwact(type="checkbox" checked)
label.custom-control-label(for="btnakwact") aktive
.custom-control.custom-checkbox.ml-3
input.custom-control-input.btnakw#btnakwstill(type="checkbox" checked)
label.custom-control-label(for="btnakwstill") stillgelegte
.custom-control.custom-checkbox.ml-3
input.custom-control-input.btnakw#btnakwrest(type="checkbox" checked)
label.custom-control-label(for="btnakwrest") sonstige Anlagen
.btn-group#togwind
button.btn.btn-success.mybuttons Wind
button.btn.btn-success.mybuttons.dropdown-toggle.dropdown-toggle-split(type="button" id="dropdownMenu4" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false")
span.sr-only Wind
.dropdown-menu(aria-labelledby="dropdownMenu4")
.dropdown-header Wind-Layer
.custom-control.custom-checkbox.ml-3
input.custom-control-input.btnakw#btnwind(type="checkbox")
label.custom-control-label(for="btnwind") Wind anzeigen
ul.navbar-nav
button.btn.btn-success.mybuttons#btnlegende Legende
.input-wrapper.mr-3
input.form-control#newmapcenter(type="search" placeholder="Sensor suchen" aria_label="Suche")
label.fas.fa-search.input-icon(for="newmapcenter")
button.btn.btn-success.mybuttons#btninfo Info
#map
#legendcontainer
.legend#legendcpm
#legend-inner-cpm
.gradient
.labels
.label(style="bottom: 100%;") >= 5 &micro;Sv/h
.label(style="bottom: 86%;") 2
.label(style="bottom: 72%;") 1
.label(style="bottom: 58%;") 0.5
.label(style="bottom: 44%;") 0.2
.label(style="bottom: 30%;") 0.1
.label(style="bottom: 17%;") 0.05
.label1(style="bottom: 10%;") offline
.label1(style="bottom: 2%;") indoor
#overlay
nav.navbar.navbar-expand.navbar-light.navbg
button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbarOverlayContent")
span.navbar-toggler-icon
#navbarOverlayContent.collapse.navbar-collapse
ul.navbar-nav.mr-auto
button.btn.btn-success#btnmo(value="month") 30 d
button.btn.btn-success#btnwe(value="week") 7 d
button.btn.btn-success#btnda(autofocus="true",value="day") 24 h
ul.navbar-nav
button.btn.btn-success#btnset(value='set') Einstellung
button.btn.btn-success#btnend(value='map') Ende
#loading Lade die Daten ...
#placeholderFS_1
#placeholderBME
#dialogWinSet
#author
#mailadr
a(href="mailto:rexfue@gmail.com") mailto:rexfue@gmail.com
#versn Version: #{version} vom #{date}
#dialogCenter.dialog
#dialogWinHelp
#page-mask
<!-- Modal Dialog -->
#myModal.modal.fade(role="dialog")
.modal-dialog.modal-lg(role="document")
<!-- Modal content-->
.modal-content
.modal-header
h4#modalTitle.modal-title
button.close(type="button" data-dismiss="modal") &times;
.modal-body
.modal-footer
button.btn.btn-default(type="button" data-dismiss="modal") Schließen
<!-- Modal Dialog -->
#splashModal.modal.fade(role="dialog")
.modal-dialog.modal-lg(role="document")
<!-- Modal content-->
.modal-content
.modal-header
h4#spmodalTitle.modal-title
button.close(type="button" data-dismiss="modal") &times;
.modal-body
.modal-footer
input#splashCheck(type="checkbox")
label#splashChecklabel(for='splashCheck') Nicht mehr anzeigen
button.btn.btn-default(type="button" data-dismiss="modal") Schließen

31
views/select.pug Normal file
View File

@@ -0,0 +1,31 @@
extends layout
block content
script.
var selName='#{name}';
var nameMap='#{JSON.stringify(map)}';
header
h2#fstb Feinstaub - Auswertungen
h3 Folgende Sensoren können angewählt werden:
table#sensors
thead
tr#trUebS
th Sensor-Nummer
th Namenskürzel
th Standort
tbody
each m, i in map
tr
td
a(href='/'+ m.sensor) #{m.sensor}
td= m.urlName
td= m.location
how2.
Auswahl entweder durch Anklicken der
Sensor-Nummer in der Tabelle oder direkt Aufrufen durch
Anhängen der Sensornummer an die URL (z.B.
<strong> feinstaub.rexfue.de/140</strong>).