commit 2df1f79617c14c943b0567110b470e723cb8d37a Author: rxf Date: Sat Jul 25 11:35:41 2026 +0000 Initial monorepo: readin, sensorapi, noise (Node22, npm ci, axios1.x, i18next-fs-backend) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d0e3bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +log/ +*.bak +*.bak-* +.DS_Store +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..1380af0 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# sensor-stack + +Monorepo fuer den noisesensors-Stack (esprimo, Dockge: /opt/stacks/esprimo/noisesensors). + +- **readin/** – Sensordaten von sensor.community -> InfluxDB/MongoDB (Cron). Image `docker.citysensor.de/readin` +- **sensorapi/** – Backend-API (liest Influx/Mongo). Image `docker.citysensor.de/sensorapi` +- **noise/** – Web-Frontend (laerm_web, nutzt sensorapi). Image `docker.citysensor.de/noise` + +Deploy je Service: `cd && ./deploy.sh` (nativer amd64-Build -> Registry), +dann `cd /opt/stacks/esprimo/noisesensors && docker compose up -d `. diff --git a/noise/.dockerignore b/noise/.dockerignore new file mode 100644 index 0000000..22c134b --- /dev/null +++ b/noise/.dockerignore @@ -0,0 +1,9 @@ +build_and_copy.sh +Dockerfile_noise +.vscode +node_modules +log +.env* +.gitignore +deploy.sh +Readme.* \ No newline at end of file diff --git a/noise/.env.example b/noise/.env.example new file mode 100644 index 0000000..fd86a67 --- /dev/null +++ b/noise/.env.example @@ -0,0 +1,10 @@ +# API Configuration +API_KEY=your_api_key_here +APIHOST=http://localhost:3005 + +# MongoDB Configuration (if needed) +MONGO_ROOT_USERNAME=admin +MONGO_ROOT_PASSWORD=your_password_here +LOCALDIR=/path/to/local/dir +REPO_USER=your_username +REPO_PASSWORD=your_password diff --git a/noise/.gitignore b/noise/.gitignore new file mode 100644 index 0000000..313d17a --- /dev/null +++ b/noise/.gitignore @@ -0,0 +1,4 @@ +node_modules +log +.env +Readme.pdf \ No newline at end of file diff --git a/noise/.vscode/launch.json b/noise/.vscode/launch.json new file mode 100644 index 0000000..58940ee --- /dev/null +++ b/noise/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + // 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": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/bin/www.js", + "env": { + "PORT": "3003", + "APIHOST": "http://localhost:3005", + // "MONGOHOST": "reception", + // "MONGOUSRP": "admin:mongo4noise", + // "MONGOAUTH": "true", + // "INFLUXHOST": "reception", + // "INFLUXTOKEN": "gHGGgjaK0lmM6keMa01JeuDpqOE_vRq8UimsU4QKb2miI5BDh2PfWynEbwKizdJapXy8jVbTat5mVZLQTNmSdw==", + } + } + ] +} \ No newline at end of file diff --git a/noise/Dockerfile_noise b/noise/Dockerfile_noise new file mode 100644 index 0000000..0e13baa --- /dev/null +++ b/noise/Dockerfile_noise @@ -0,0 +1,19 @@ +FROM node:22-alpine + +ADD package.json /tmp/package.json +ADD package-lock.json /tmp/package-lock.json +RUN cd /tmp && npm ci +RUN mkdir -p /opt/app && cp -a /tmp/node_modules /tmp/package.json /opt/app/ +WORKDIR /opt/app +ADD . /opt/app/ + +RUN apk add --no-cache tzdata +ENV TZ=Europe/Berlin +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN deluser --remove-home node + +EXPOSE 3000 + +CMD ["npm", "start"] + diff --git a/noise/Readme.md b/noise/Readme.md new file mode 100644 index 0000000..87a55da --- /dev/null +++ b/noise/Readme.md @@ -0,0 +1,28 @@ +# Develop und Deploy der Noise-Anwendung +rxf 2025-08-08 +## Develop local ( auf esprimo !) +* via VS die APP **sensorapi** auf dem *esprimo* starten +* als **mongo** direkt die auf *ionos* verwenden +* in VS die app **laerm_web_sensorapi** editieren und starten +* im Browser aufrufen: **esprimo:3000** + +## Deploy auf *ionos* +* überprüfen, ob **portainer** richtig läuft, ggf. neu installieren +* prüfen, ob alle Container richtig laufen, vor Allem der **watchtower**, ggf. das Stack neu starten +* in VS (auf esprimo) das script **deploy.sh** aufrufen +* auf **ionos** sollte nun via **watchtower** die neue Version geladen werden (spätestens nach 1 Minute) +** nun im Browser **https://noise.fuerst-stuttgart.de** aufrufen und testen + +## Deploy auf *rbsensors* +* wenn **deplay.sh** noch nicht gestartet wurde (via VS auf esprimo), diese aufrufen. +* Portainer auf rbsensors aufrufen: **192.168.51.22:9000** +* über **Images** das neueste Image (**noise:latest**) vom Repository *Reinhard* holen +* das Stack **noisesensor** aufrufen +* den Container **noise** löschen +* via *edit* das Stack neu starten +* und nun im Browser mit **laerm.rbsensors.de** testen + +| Datum | Version | Bemerkung | +|-------|---------|---------| +| 2025-08-08 | 1.0.1 | Typos verbessert | +| 2024-12-20| 1.0.0 | erste Version | diff --git a/noise/app.js b/noise/app.js new file mode 100644 index 0000000..3d7e85b --- /dev/null +++ b/noise/app.js @@ -0,0 +1,77 @@ +import 'dotenv/config' +import createError from 'http-errors' +import logger from 'morgan' +import express from 'express' +import cookieParser from 'cookie-parser' +import path from 'path' +import { fileURLToPath } from 'url' +import i18next from 'i18next' +import i18nextMiddleware from 'i18next-http-middleware' +import Backend from 'i18next-fs-backend' + +const app = express() + +export let url2call + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +import * as indexRouter from './routes/index.js' +import apiRouter from './routes/api.js' + +i18next + .use(Backend) + .use(i18nextMiddleware.LanguageDetector) + .init({ + backend: { + loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json' + }, + fallbackLng: 'de', + debug: false, + preload: ['de', 'en'] + }); + +app.use(i18nextMiddleware.handle(i18next)); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); + +app.get('*', (req, res, next) => { + const subdom = req.headers.host.split('.') + if (subdom[0] === 'laerm') { + subdom[0] = 'noise' + } +// app.set('category',subdom[0]) + app.set('category','noise') + next() +}) + +app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); +app.use(express.static(path.join(__dirname,'node_modules'))); + +app.use('/api', apiRouter) +app.use('/srv', apiRouter) +app.use('/', indexRouter.router); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + next(createError(404)); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +export default app diff --git a/noise/bin/www.js b/noise/bin/www.js new file mode 100755 index 0000000..49ab556 --- /dev/null +++ b/noise/bin/www.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +import app from '../app.js' +import Debug from 'debug' +import http from 'http' +const debug = Debug('laerm-web-sensorapi:server') + + +/* var app = require('../app'); +var debug = require('debug')('laerm-web-sensorapi:server'); +var http = require('http'); +*/ + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/noise/build_and_copy.sh b/noise/build_and_copy.sh new file mode 100755 index 0000000..ec13d9b --- /dev/null +++ b/noise/build_and_copy.sh @@ -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=noise +name=noise + +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 --no-cache -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' diff --git a/noise/charts/preparecharts.js b/noise/charts/preparecharts.js new file mode 100644 index 0000000..ae6c2f6 --- /dev/null +++ b/noise/charts/preparecharts.js @@ -0,0 +1,565 @@ +// Prepare all data to plot the charts +import * as utils from '../charts/utilities.js' +import { translate as trans } from '../routes/index.js' + +/********************************************** + * Live-Data + * * + * @param params + * @param values + * @returns {{options: {plotOptions: {series: {marker: {enabled: boolean}, turboThreshold: number, animation: boolean}}, xAxis: {gridLineWidth: number, type: string, title: {text: string}, labels: {formatter: function(): (string|*)}}, legend: {layout: string, borderWidth: number, align: string, enabled: boolean}, subtitle: {align: string}, tooltip: {backgroundColor: number, valueDecimals: number, borderRadius: number, useHTML: boolean, borderWidth: number}, title: {useHTML: boolean, style: {fontSize: string}, align: string}, chart: {backgroundColor: {linearGradient: number[], stops: [[number,string],[number,string]]}, spacingTop: number, borderWidth: string, spacingLeft: number, type: string, spacingRight: number, events: {selection: function(*): void}, height: number}}, params}} + * + ***********************************************/ +export const liveData = (params, values, req) => { + let series1 = [] + let series2 = [] + let series3 = [] + let infoTafel + + // save the current (actual) values here + let aktVal = {} + + + // Put values into the arrays + let cnt = 0 + values.forEach((x) => { + let dat = new Date(x.datetime).getTime() + series1.push([dat, x.LAeq]) + series2.push([dat, x.LA_max]) + series3.push([dat, x.LA_min]) // put data and value into series array + }) + + if (values.length != 0) { + const lastidx = values.length - 1 + // Aktuelle Werte speichern + aktVal['LAeq'] = values[lastidx].LAeq + aktVal['LAMax'] = values[lastidx].LA_max + aktVal['LAMin'] = values[lastidx].LA_min + + // InfoTafel füllen + infoTafel = + '' + + '' + + '' + + '' + infoTafel += + '' + + '' + infoTafel += + '' + + '' + infoTafel += + '
' + trans("ActualValues") + '
LAeq' + (aktVal.LAeq).toFixed(1) + 'dbA
LAmin' + (aktVal.LAMin).toFixed(0) + 'dbA
LAmax' + (aktVal.LAMax).toFixed(0) + 'dbA
' + + '' + } + let txtMeldung = false + + + // Plot-Options + let options = utils.createGlobObtions('dd.LL HH:mm:ss') + + let series_LAeq = { + name: 'LAeq', + data: series1, + color: utils.colors.eq, + yAxis: 0, + zIndex: 1, + marker: { + enabled: false, + symbol: 'square', + }, + visible: true, + fillColor: { + linearGradient: [0, 600, 0, 0], + stops: [ + [0, 'rgb(238,223,236)'], + [1, 'rgb(238,223,236)'] + ] + }, + } + + let series_LAMax = { + name: 'LAmax', + data: series2, + color: utils.colors.max, + yAxis: 0, + zIndex: 0, + marker: { + enabled: false, + symbol: 'square', + }, + visible: true, + fillColor: { + linearGradient: [0, 600, 0, 0], + stops: [ + [0, 'rgb(255,233,236)'], + [1, 'rgb(255,233,236)'] + ] + }, +// fillOpacity: 0.2, + } + + let series_LAMin = { + name: 'LAmin', + data: series3, + color: utils.colors.min, + yAxis: 0, + zIndex: 6, + marker: { + enabled: false, + symbol: 'square', + }, + visible: true, + fillColor: { + linearGradient: [0, 600, 0, 0], + stops: [ + [0, 'rgb(219,216,217)'], + [1, 'rgb(219,216,217)'] + ] + }, + } + + let yAxis_LAeq = { // 1 + title: { + text: 'dbA', + style: { + color: 'black' + } + }, + max: utils.noise_ymax, +// min: utils.noise_ymin, + min: 10, +// opposite: true, + tickAmount: 11, + useHTML: true, + } + + options.series = [] + options.yAxis = [] + options.series[0] = series_LAeq + options.series[1] = series_LAMin + options.title.text = trans('NoiseLevelsLast24Hours') + options.subtitle.useHTML = true + options.subtitle.text = `${trans("25minlogAverageValues")}
 ` + options.series[2] = series_LAMax +// options.yAxis[2] = yAxis_LAMin + options.yAxis[0] = yAxis_LAeq +// options.yAxis[1] = yAxis_LAMax + options.chart.zoomType = 'x' + options.chart.type = 'area' + options.chart.resetZoomButton= { + position: { + align: 'left', + verticalAlign: 'bottom', + x: 20, + y: -50, + }, + relativeTo: 'chart' + } + + // options.tooltip.formatter = function () { + // let y = Math.round(this.y * 100) / 100 + // return '
' + + // DateTime.fromMillis(this.x, {zone: 'utc'}).toFormat("dd.LL - HH:mm:ss") + '
' + + // '● ' + + // this.series.name + ':  ' + y + '
' + // } + + + let noDataTafel = '
' + + 'Für heute liegen leider keine Daten vor!
Bitte den Sensor überprüfen!\'
' + + '
' + + + let navy = 20 + let navbreit = 55 + let navtxt = ['-24h', '-12h', 'live', '+12h', '+24h'] + let navtime = [-24, -12, 0, 12, 24] + + return { options: options, params: params, info: {class: 'infoTafel', text:infoTafel}} +} + + +/******************************************** + * havg - Data + * + * @param params + * @param values + * @returns {{options: {plotOptions: {series: {marker: {enabled: boolean}, turboThreshold: number, animation: boolean}}, xAxis: {gridLineWidth: number, type: string, title: {text: string}, labels: {formatter: function(): (string|*)}}, legend: {layout: string, borderWidth: number, align: string, enabled: boolean}, subtitle: {align: string}, tooltip: {backgroundColor: number, valueDecimals: number, borderRadius: number, useHTML: boolean, borderWidth: number}, title: {useHTML: boolean, style: {fontSize: string}, align: string}, chart: {backgroundColor: {linearGradient: number[], stops: [[number,string],[number,string]]}, spacingTop: number, borderWidth: string, spacingLeft: number, type: string, spacingRight: number, events: {selection: function(*): void}, height: number}}, params, options2: any}} + * + ********************************************/ +export const havgData = (params, values) => { + let series1 = [] + let series2 = [] + // Put values into the series + let cnt = 0 + values.forEach((x) => { + if (x.n_AVG != -1) { + let dat = new Date(x.datetime).getTime() // retrieve the date + series1.push([dat, x.n_AVG]) + series2.push([dat, x.peakcount]) + } + }) + + // Plot-Options + let options = utils.createGlobObtions() + options.xAxis.tickInterval = 6 * 3600 * 1000 +// options.xAxis.plotBands = utils.calcWeekends(data, true) + options.xAxis.minTickInterval = 3600 * 1000 + + options.yAxis = [ + { + title: { + text: 'dbA', + useHTML: true, + }, + min: utils.noise_ymin, + tickAmount: 10, + gridLineColor: 'lightgray' + }, { + title: { + text: 'Counts', + useHTML: true, + }, + tickAmount: 10, + allowDecimals: false, + gridLineColor: 'lightgray', + opposite: true + } + ] + options.series = [ + { + name: `${trans('MeanValueLAeq')}`, + data: series1, + color: utils.colors.eq, + type: 'column', + zIndex: 2, + yAxis: 0 + }, { + name: `${trans('Peaks')}`, + data: series2, + color: utils.colors.peaks, + type: 'column', + yAxis: 1, + zIndex: 2 + } + ] + // options.plotOptions.column = { + // pointWidth: 8, + // groupPadding: 0.5 + // } + options.plotOptions.column = { + pointWidth: 8, + groupPadding: 0.1 + } + + options.title.text = `${trans('HourlyMeanValues')} / ${trans('PeaksOver')} ${params.peak} dbA` + options.subtitle.text = `${trans('HourlyMeanValuesOfLAeqForEachFullHour')} / ${trans('NumberOfPeaksPerHour')}
 ` + options.subtitle.useHTML = true + options.chart.zoomType = 'x' + options.chart.resetZoomButton= { + position: { + align: 'left', + x: 20, + y: 300, + }, + relativeTo: 'chart' + } + + return { options: options, params: params} + +} + +/******************************************** + * davg - Data + * + * @param params + * @param values + * @returns {{options: {plotOptions: {series: {marker: {enabled: boolean}, turboThreshold: number, animation: boolean}}, xAxis: {gridLineWidth: number, type: string, title: {text: string}, labels: {formatter: function(): (string|*)}}, legend: {layout: string, borderWidth: number, align: string, enabled: boolean}, subtitle: {align: string}, tooltip: {backgroundColor: number, valueDecimals: number, borderRadius: number, useHTML: boolean, borderWidth: number}, title: {useHTML: boolean, style: {fontSize: string}, align: string}, chart: {backgroundColor: {linearGradient: number[], stops: [[number,string],[number,string]]}, spacingTop: number, borderWidth: string, spacingLeft: number, type: string, spacingRight: number, events: {selection: function(*): void}, height: number}}, params, options2: any}} + * + ********************************************/ + +export const davgData = (params, values) => { + let series1 = [] + let series2 = [] + + // Put values into the serieses + let cnt = 0 + values.forEach((x) => { + if (x.n_AVG != -1) { + let dat = new Date(x.datetime).getTime() // retrieve the date + series1.push([dat, x.n_AVG]) + series2.push([dat, x.peakcount]) + } + }) + // Plot-Options + let options = utils.createGlobObtions() + // options.xAxis.plotBands = utils.calcWeekends(values, true) + options.xAxis.tickInterval = 24 * 3600 * 1000 + options.xAxis.title.text = 'Date' + // options.xAxis.labels = { + // formatter: function () { + // return this.axis.defaultLabelFormatter.call(this); + // } + // } + + options.chart.type = 'column' + + options.yAxis = [ + { + title: { + text: 'dbA', + useHTML: true, + }, + min: utils.noise_ymin, + tickAmount: 10, + gridLineColor: 'lightgray', + }, { + title: { + text: 'Counts', + useHTML: true, + }, + tickAmount: 10, + allowDecimals: false, + gridLineColor: 'lightgray', + opposite: true + } + ] + + options.series = [ + { + name: `${trans('MeanValueLAeq')}`, + data: series1, + color: utils.colors.eq, + type: 'column', + zIndex: 2, + yAxis: 0 + },{ + name: `${trans('Peaks')}`, + data: series2, + color: utils.colors.peaks, + type: 'column', + yAxis: 1, + zIndex: 2, + } + ] + options.plotOptions.column = { + pointWidth: 20, + groupPadding: 0.1 + } + options.title.text = `${trans('DailyMeanValues')} / ${trans('PeaksOver')} ${params.peak} dbA` + options.subtitle.text = `${trans('DailyMeanValuesOfLAeqForEachFullDay')} / ${trans('NumberOfPeaksPerDay')}
 ` + options.subtitle.useHTML = true + options.chart.zoomType = 'x' + options.chart.resetZoomButton = { + position: { + align: 'left', +// verticalAlign: 'bottom', + x: 20, + y: 300, + }, + relativeTo: 'chart' + } + + return {options: options, params: params} + +} + +/******************************************** + * dayNight - Data + * + * @param params + * @param values + * @returns {{options: {plotOptions: {series: {marker: {enabled: boolean}, turboThreshold: number, animation: boolean}}, xAxis: {gridLineWidth: number, type: string, title: {text: string}, labels: {formatter: function(): (string|*)}}, legend: {layout: string, borderWidth: number, align: string, enabled: boolean}, subtitle: {align: string}, tooltip: {backgroundColor: number, valueDecimals: number, borderRadius: number, useHTML: boolean, borderWidth: number}, title: {useHTML: boolean, style: {fontSize: string}, align: string}, chart: {backgroundColor: {linearGradient: number[], stops: [[number,string],[number,string]]}, spacingTop: number, borderWidth: string, spacingLeft: number, type: string, spacingRight: number, events: {selection: function(*): void}, height: number}}, params}} + * + ********************************************/ +export const dayNightData = (params, values) => { + + let series1 = [] + let series2 = [] + // Put values into the serieses + let cnt = 0 + values.forEach((x) => { + if (x.n_dayAVG > 0) { + let dat = new Date(x.datetime).getTime() // retrieve the date + series1.push([dat, x.n_dayAVG]) + series2.push([dat, x.n_nightAVG]) + } + }) + // Plot-Options + let options = utils.createGlobObtions() +// options.xAxis.plotBands = utils.calcWeekends(data, true) + options.xAxis.tickInterval = 24 * 3600 * 1000 + options.xAxis.title.text = trans('Date') + // options.xAxis.labels = { + // formatter: function () { + // return this.axis.defaultLabelFormatter.call(this); + // } + // } + + options.chart.type = 'column' + options.yAxis = { + title: { + text: 'dbA', + useHTML: true, + }, + min: utils.noise_ymin, + tickAmount: 10, + gridLineColor: 'lightgray', + } + options.series = [ + { + name: `${trans('Day')}`, + data: series1, + color: utils.colors.eq, + type: 'column', + zIndex: 2, + }, + { + name: `${trans('Night')}`, + data: series2, + color: utils.colors.max, + type: 'column', + zIndex: 2, + }, + ] + options.plotOptions.series = { + animation: false + } + options.plotOptions.column = { + pointWidth: 20, + groupPadding: 0.3 + } + options.title.text = trans('AverageValuesDuringTheDayAndNightHours') + options.subtitle.text = `${trans('DailyMeanValuesOfLAeqFrom6h00To22h00')}
${trans('NightlyMeanValuesOfLAeqFrom22h00To6h00')}
 ` + options.subtitle.useHTML = true + options.chart.zoomType = 'x' + options.chart.resetZoomButton = { + position: { + align: 'left', +// verticalAlign: 'bottom', + x: 20, + y: 300, + }, + relativeTo: 'chart' + } + return {options: options, params: params} +} + +/*********************************** + Lden - Data + * + * @param params + * @param values + * @returns {{options: {plotOptions: {series: {marker: {enabled: boolean}, turboThreshold: number, animation: boolean}}, xAxis: {gridLineWidth: number, type: string, title: {text: string}, labels: {formatter: function(): (string|*)}}, legend: {layout: string, borderWidth: number, align: string, enabled: boolean}, subtitle: {align: string}, tooltip: {backgroundColor: number, valueDecimals: number, borderRadius: number, useHTML: boolean, borderWidth: number}, title: {useHTML: boolean, style: {fontSize: string}, align: string}, chart: {backgroundColor: {linearGradient: number[], stops: [[number,string],[number,string]]}, spacingTop: number, borderWidth: string, spacingLeft: number, type: string, spacingRight: number, events: {selection: function(*): void}, height: number}}, params}} + **********************************/ +export const ldenData = (params, values) => { + + let series1 = [] + + // Put values into the series + let cnt = 0 + values.forEach((x) => { + if (!((x.lden === -1) || (x.datetime === undefined))) { + let dat = new Date(x.datetime).getTime() // retrieve the date + series1.push([dat, x.lden]) + } + }) + // Plot-Options + let options = utils.createGlobObtions() + +// options.xAxis.plotBands = utils.calcWeekends(data, true) + options.xAxis.tickInterval = 24 * 3600 * 1000 + options.xAxis.title.text = trans('Date') + options.xAxis.labels = { + formatter: function () { + let lbl = this.axis.defaultLabelFormatter.call(this); + this.value += 86400000; + let lbl1 = this.axis.defaultLabelFormatter.call(this); + return parseInt(lbl) + '/' + lbl1; + } + } + options.chart.type = 'column' + options.yAxis = { + title: { + text: 'dbA', + useHTML: true, + }, + min: utils.noise_ymin, + tickAmount: 10, + gridLineColor: 'lightgray', + } + options.series = [ + { + name: 'Lden', + data: series1, + color: utils.colors.eq, + type: 'column', + zIndex: 2, + } + ] + options.plotOptions.series = { + animation: false + } + options.plotOptions.column = { + pointWidth: 20, + groupPadding: 0.1 + } + options.title.text = trans('LDEN-Index') + options.subtitle.text = `${trans('DailyMeanValuesOfLDEN-Index')}
 ` + options.subtitle.useHTML = true + options.chart.zoomType = 'x' + options.chart.resetZoomButton = { + position: { + align: 'left', +// verticalAlign: 'bottom', + x: 20, + y: 300, + }, + relativeTo: 'chart' + } + /* options.tooltip.formatter = function () { + let d = DateTime.fromMillis(this.x, {zone: 'utc'}) + let d1 = d.plus({days: 1}) + let txt = d.toFormat('dd') + '/' + d1.toFormat('dd.LL') + return '
' + + '  ' + txt + '
' + + '● ' + + this.series.name + ':  ' + + Highcharts.numberFormat(this.y, 1) + + '
'; + } + + */ + return {options: options, params: params} +} + +/*********************************** + Map - Data + * + * @param params + * @param values + * @returns {{options: {plotOptions: {series: {marker: {enabled: boolean}, turboThreshold: number, animation: boolean}}, xAxis: {gridLineWidth: number, type: string, title: {text: string}, labels: {formatter: function(): (string|*)}}, legend: {layout: string, borderWidth: number, align: string, enabled: boolean}, subtitle: {align: string}, tooltip: {backgroundColor: number, valueDecimals: number, borderRadius: number, useHTML: boolean, borderWidth: number}, title: {useHTML: boolean, style: {fontSize: string}, align: string}, chart: {backgroundColor: {linearGradient: number[], stops: [[number,string],[number,string]]}, spacingTop: number, borderWidth: string, spacingLeft: number, type: string, spacingRight: number, events: {selection: function(*): void}, height: number}}, params}} + **********************************/ +export const mapData = (params, values) => { + let actsensors = 0 + for (let x of values) { + if (x.value >= 0) { + actsensors++ + } + } + return { + ...params, + values: values, + actsensors: `${trans("ActiveSensors")} ${actsensors}`, + registered: `(${trans("registered")} ${params.count})`, + valfromtxt: trans("ValuesFrom"), + popuptxt: { + offline: trans('offline'), + sensor: trans('Sensor'), + lastseen: trans('LastSeen'), + showchart: trans('ShowChart') + } + } +} + diff --git a/noise/charts/utilities.js b/noise/charts/utilities.js new file mode 100644 index 0000000..3e6c114 --- /dev/null +++ b/noise/charts/utilities.js @@ -0,0 +1,151 @@ +// Utility routine for plotting the data + +import { translate as trans } from '../routes/index.js' + +let language = 'en' + +export const colors = {'eq': '#0000FF', 'max': '#FF0000', 'min': '#008000', 'peaks': '#DAA520'}; +export const noise_ymin = 20; +export const noise_ymax = 120; + +export function createGlobObtions() { + // Options, die für alle Plots identisch sind + let globObject = { + chart: { + spacingRight: 20, + spacingLeft: 20, + spacingTop: 25, + backgroundColor: { + linearGradient: [0, 400, 0, 0], + stops: [ + [0, '#eee'],//[0, '#ACD0AA'], //[0, '#A18D99'], // [0, '#886A8B'], // [0, '#F2D0B5'], + [1, '#fff'] + ] + }, + type: 'line', + borderWidth: '2', + // events: { + // selection: function (event) { + // if (event.xAxis) { + // doUpdate = false; + // } else { + // doUpdate = true; + // } + // } + // } + }, + title: { + align: 'left', + style: {'fontSize': '25px'}, + useHTML: true, + }, + subtitle: { + align: 'left', + }, + tooltip: { + valueDecimals: 1, + backgroundColor: '#ffffff', + borderWidth: 0, + borderRadius: 0, + useHTML: true, + }, + xAxis: { + type: 'datetime', + title: { + text: trans('datetime'), + }, + gridLineWidth: 2, + labels: { + formatter: function () { + let v = this.axis.defaultLabelFormatter.call(this); + if (v.indexOf(':') == -1) { + return '' + v + ''; + } else { + return v; + } + } + }, + }, + legend: { + enabled: true, + layout: 'horizontal', +// verticalAlign: 'top', + borderWidth: 1, + align: 'center', + }, + plotOptions: { + series: { + animation: false, + turboThreshold: 0, + marker: { + enabled: false, + }, + }, + } + }; + return globObject; +} + +export function calcWeekends(data, isyear) { +/* let weekend = []; + let oldDay = 8; + for (let i = 0; i < data.length; i++) { + let mom = moment(data[i].date); + if (isyear) { + mom = moment(data[i]._id) + } + let day = mom.day(); + let st = mom.startOf('day'); + if (day != oldDay) { + if (day == 6) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } else if (day == 0) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } + oldDay = day; + } + } + return weekend; +*/} + +export function calcDays(data, isyear) { + let days = []; + if (data.length == 0) { + return days + } + let oldday = moment(data[0].date).day(); + if (isyear) { + oldday = moment(data[0]._id).day(); + } + for (let i = 0; i < data.length; i++) { + let m = moment(data[i].date); + if (isyear) { + m = moment(data[i]._id); + } + let tag = m.day() + if (tag != oldday) { + m.startOf('day'); + days.push({color: 'lightgray', value: m.valueOf(), width: 1, zIndex: 2}); + oldday = tag; + } + } + return days; +}; + +export const setLanguage = (lng) => { + language = lng +} + +export const getLanguage = () => { + return language +} diff --git a/noise/deploy.sh b/noise/deploy.sh new file mode 100755 index 0000000..a044a5e --- /dev/null +++ b/noise/deploy.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +# Angepasst aus dem readin-/sensorapi-deploy.sh (urspr. /Projekte/Logbuch/deploy.sh) +# Nur linux/amd64, nativer Build auf esprimo (amd64) -> kein buildx/qemu, +# eigener Dockerfile-Name (Dockerfile_noise). Image-Name: noise. + +REGISTRY="docker.citysensor.de" +IMAGE_NAME="noise" +TAG="${1:-latest}" +PLATFORM="linux/amd64" +DOCKERFILE="Dockerfile_noise" +FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}" +BUILD_DATE=$(date +%d.%m.%Y) + +echo "==========================================" +echo "noise (laerm_web) Deploy Script (amd64)" +echo "==========================================" +echo "Registry: ${REGISTRY}" +echo "Image: ${IMAGE_NAME}" +echo "Tag: ${TAG}" +echo "Platform: ${PLATFORM}" +echo "Dockerfile: ${DOCKERFILE}" +echo "Build-Datum: ${BUILD_DATE}" +echo "==========================================" +echo "" + +# Hinweis: Login wird uebersprungen, weil die Credentials fuer ${REGISTRY} +# bereits in ~/.docker/config.json liegen. +# Bei Bedarf manuell: docker login ${REGISTRY} + +echo ">>> Baue Image (nur ${PLATFORM}, nativ) ..." +docker build \ + --platform "${PLATFORM}" \ + -f "${DOCKERFILE}" \ + -t "${FULL_IMAGE}" \ + . +echo "" + +echo ">>> Pushe ${FULL_IMAGE} ..." +docker push "${FULL_IMAGE}" +echo "" + +# Falls ein anderer Tag als 'latest' gebaut wurde, zusaetzlich latest setzen+pushen +if [ "${TAG}" != "latest" ]; then + echo ">>> Tagge zusaetzlich als :latest ..." + docker tag "${FULL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:latest" + docker push "${REGISTRY}/${IMAGE_NAME}:latest" + echo "" +fi + +echo "==========================================" +echo "Build + Push fertig: ${FULL_IMAGE}" +echo "==========================================" +echo "" +echo "Danach Stack aktualisieren:" +echo " cd /opt/stacks/esprimo/noisesensors && docker compose up -d noise" diff --git a/noise/docs/Readme.md b/noise/docs/Readme.md new file mode 100644 index 0000000..29b8609 --- /dev/null +++ b/noise/docs/Readme.md @@ -0,0 +1,5 @@ +####Neues Sensor-API + +* Programm **sensor_web_api**. Läuft auf Port 3000 +* Aufruf über: **laerm.test.me:3000** (oder feinstaub.test... oder multigeiger.test...) +* Zugriff auf die DB mit dem Programm **SensorApi**. Dieses reagiert auf Port 3005 und muss im Hintergrund laufen! diff --git a/noise/docs/aktchanges b/noise/docs/aktchanges new file mode 100644 index 0000000..0a77e88 --- /dev/null +++ b/noise/docs/aktchanges @@ -0,0 +1,5 @@ +Changes ab AUG-23 + +- i18n in /:sid in idex.js rein +- Messung und Anzeige auf Console der Ladezeit der 5 Pages im Hintergrund + diff --git a/noise/locales/de/translation.json b/noise/locales/de/translation.json new file mode 100644 index 0000000..aba3505 --- /dev/null +++ b/noise/locales/de/translation.json @@ -0,0 +1,53 @@ +{ + "NoiseMeasurement": "Lärm-Messung", + "Settings": "Einstellungen", + "Info": "Info", + "StartDate": "Start Datum", + "NumberOfDays": "So viele Tage anzeigen", + "Only_for_live_chart": "Nur bei 'live' Grafik", + "Count_peaks_over": "Zähle Spitzen über", + "Close": "Schließen", + "Save_changes": "Änderungen speichern", + "Language": "Sprache", + "Map": "Karte", + "Live": "Live", + "Hour_AVG": "Stundenmittel", + "Day_AVG": "Tagesmittel", + "Day_Night": "Tag/Nacht-Werte", + "ActualValues": "Aktuelle Werte", + "NoiseLevelsLast24Hours": "Schallpegel der letzten 24 Stunden", + "ValuesFrom": "Werte vom", + "ActiveSensors": "Aktive Sensoren:", + "registered": "registriert:", + "ShowChart": "Grafik anzeigen", + "Sensor": "Sensor", + "offline": "offline", + "LastSeen": "Letzter Kontakt", + "datetime": "Datum/Zeit", + "25minlogAverageValues": "2.5min log. Mittelwerte", + "HourlyMeanValues": "Stündliche Mittelwerte", + "PeaksOver": "Spitzen über", + "HourlyMeanValuesOfLAeqForEachFullHour": "Stunden-Mittelwerte von LAeq jeweils für eine volle Stunde", + "NumberOfPeaksPerHour": "Anzahl der Spitzen pro Stunde", + "DailyMeanValues": "Tages-Mittelwerte", + "DailyMeanValuesOfLAeqForEachFullDay": "Tages-Mittelwerte von LAeq jeweils für einen vollen Tag", + "NumberOfPeaksPerDay": "Anzahl der Spitzen pro Tag", + "AverageValuesDuringTheDayAndNightHours": "Mittelwerte während der Tag- und Nachtstunden", + "DailyMeanValuesOfLAeqFrom6h00To22h00": "Tages-Mittelwerte des LAeq jeweils von 6:00 bis 22:00 Uhr", + "NightlyMeanValuesOfLAeqFrom22h00To6h00": "Nacht-Mittelwerte des LAeq jeweils von 22:00 bis 6:00 Uhr", + "LDEN-Index": "LDEN-Index", + "DailyMeanValuesOfLDEN-Index": "Tages-Mittelwerte des LDEN-Index", + "Date": "Datum", + "Version": "Version", + "from": "vom", + "RemoveFromMap": "Sensoren nicht anzeigen, die nicht gesendet haben seit", + "weeks": "Wochen", + "ESYSCALL": "Fehler vom Serveraufruf: ", + "Error": "Fehler", + "MeanValueLAeq": "Mittelwert LAeq", + "Peaks": "Spitzen", + "Day": "Tag", + "Night": "Nacht", + "CenterMap": "Karte zentrieren auf", + "MyLocation": "Mein Standort" +} \ No newline at end of file diff --git a/noise/locales/dev/translation.json b/noise/locales/dev/translation.json new file mode 100644 index 0000000..45f152c --- /dev/null +++ b/noise/locales/dev/translation.json @@ -0,0 +1,47 @@ +{ + "NoiseMeasurement": "Lärm-Messung", + "Settings": "Einstellungen", + "Info": "Info", + "StartDate": "Start Datum", + "NumberOfDays": "So viele Tage anzeigen", + "Only_for_live_chart": "Nur bei 'live' Grafik", + "Count_peaks_over": "Zähle Spitzen über", + "Close": "Schließen", + "Save_changes": "Änderungen speichern", + "Language": "Sprache", + "Map": "Karte", + "Live": "Live", + "Hour_AVG": "Stundenmittel", + "Day_AVG": "Tagesmittel", + "Day_Night": "Tag/Nacht-Werte", + "ActualValues": "Aktuelle Werte", + "NoiseLevelsLast24Hours": "Schallpegel der letzten 24 Stunden", + "ValuesFrom": "Werte vom", + "ActiveSensors": "Aktive Sensoren:", + "registered": "registriert:", + "ShowChart": "Grafik anzeigen", + "Sensor": "Sensor", + "offline": "offline", + "LastSeen": "Letzter Kontakt", + "datetime": "Datum/Zeit", + "25minlogAverageValues": "2.5min log. Mittelwerte", + "HourlyMeanValues": "Stündliche Mittelwerte", + "PeaksOver": "Spitzen über", + "HourlyMeanValuesOfLAeqForEachFullHour": "Stunden-Mittelwerte von LAeq jeweils für eine volle Stunde", + "NumberOfPeaksPerHour": "Anzahl der Spitzen pro Stunde", + "DailyMeanValues": "Tages-Mittelwerte", + "DailyMeanValuesOfLAeqForEachFullDay": "Tages-Mittelwerte von LAeq jeweils für einen vollen Tag", + "NumberOfPeaksPerDay": "Anzahl der Spitzen pro Tag", + "AverageValuesDuringTheDayAndNightHours": "Mittelwerte während der Tag- und Nachtstunden", + "DailyMeanValuesOfLAeqFrom6h00To22h00": "Tages-Mittelwerte des LAeq jeweils von 6:00 bis 22:00 Uhr", + "NightlyMeanValuesOfLAeqFrom22h00To6h00": "Nacht-Mittelwerte des LAeq jeweils von 22:00 bis 6:00 Uhr", + "LDEN-Index": "LDEN-Index", + "DailyMeanValuesOfLDEN-Index": "Tages-Mittelwerte des LDEN-Index", + "Date": "Datum", + "Version": "Version", + "from": "vom", + "RemoveFromMap": "Sensoren nicht anzeigen, die nicht gesendet haben seit", + "weeks": "Wochen", + "CenterMap": "Center map on" + +} \ No newline at end of file diff --git a/noise/locales/en/translation.json b/noise/locales/en/translation.json new file mode 100644 index 0000000..17ab654 --- /dev/null +++ b/noise/locales/en/translation.json @@ -0,0 +1,53 @@ +{ + "NoiseMeasurement": "Noise Measurement", + "Settings": "Settings", + "Info": "Info", + "StartDate": "Start date", + "NumberOfDays": "Show so many days", + "Only_for_live_chart": "Only for 'live' chart", + "Count_peaks_over": "Count peaks over", + "Close": "Close", + "Save_changes": "Save changes", + "Language": "Language", + "Map": "Map", + "Live": "Live", + "Hour_AVG": "Hour_AVG", + "Day_AVG": "Day_AVG", + "Day_Night": "Day_Night", + "ActualValues": "Actual Values", + "NoiseLevelsLast24Hours": "Noise levels last 24 hours", + "ValuesFrom": "Values from ", + "ActiveSensors": "Active Sensors:", + "registered": "registered:", + "ShowChart": "Show chart", + "Sensor": "Sensor", + "offline": "offline", + "LastSeen": "Last seen", + "datetime": "Date/Time", + "25minlogAverageValues": "2.5min log average values", + "HourlyMeanValues": "Hourly mean values", + "PeaksOver": "Peaks over", + "HourlyMeanValuesOfLAeqForEachFullHour": "Hourly mean values of LAeq for each full hour", + "NumberOfPeaksPerHour": "Number of peaks per hour", + "DailyMeanValues": "Daily mean values", + "DailyMeanValuesOfLAeqForEachFullDay": "Daily mean values of LAeq for each full day", + "NumberOfPeaksPerDay": "Number of peaks per day", + "AverageValuesDuringTheDayAndNightHours": "Average values during the day and night hours", + "DailyMeanValuesOfLAeqFrom6h00To22h00": "Daily mean values of LAeq from 6h00 to 22h00", + "NightlyMeanValuesOfLAeqFrom22h00To6h00": "Nightly mean values of LAeq from 22h00 to 6h00", + "LDEN-Index": "LDEN-Index", + "DailyMeanValuesOfLDEN-Index": "Daily mean values of LDEN-Index", + "Date": "Date", + "Version": "Version", + "from": "from", + "RemoveFromMap": "Don't show sensors that have not sent for", + "weeks": "weeks", + "ESYSCALL": "Error from Syscall: ", + "Error": "Error", + "MeanValueLAeq": "Mean value LAeq", + "Peaks": "Peaks", + "Day": "Day", + "Night": "Night", + "CenterMap": "Center map on", + "MyLocation": "My Location" +} diff --git a/noise/package-lock.json b/noise/package-lock.json new file mode 100644 index 0000000..28e5697 --- /dev/null +++ b/noise/package-lock.json @@ -0,0 +1,2117 @@ +{ + "name": "laerm-web-sensorapi", + "version": "3.3.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "laerm-web-sensorapi", + "version": "3.3.1", + "dependencies": { + "axios": "^1.3.4", + "cookie-parser": "~1.4.4", + "cors": "^2.8.5", + "debug": "~2.6.9", + "dotenv": "^17.2.3", + "express": "^4.18.2", + "http-errors": "~1.6.3", + "i18next": "^22.4.14", + "i18next-express-middleware": "^2.0.0", + "i18next-fs-backend": "^2.0.0", + "i18next-http-middleware": "^3.3.0", + "luxon": "^3.3.0", + "morgan": "^1.10.1", + "pug": "^3.0.2", + "sass": "^1.58.3", + "spin.js": "^4.1.2" + }, + "bin": { + "www": "bin/www.js" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookies": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", + "integrity": "sha512-VX3KKCWrKjPnCwF78P60rnMfy1piVeObnjuKO4rBUyiSRYDgY/EqXbw3jX/uXmmdAPmayVJqdbLfJ0BRSfRZFg==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.1", + "keygrip": "~1.0.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookies/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/i18next": { + "version": "22.5.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.1.tgz", + "integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, + "node_modules/i18next-express-middleware": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/i18next-express-middleware/-/i18next-express-middleware-2.0.0.tgz", + "integrity": "sha512-TGlSkYsQHikggv4mIp5B+CiXsZzwbpHaZgmOkRNGStLOdKHABH5cHr136g2PC1+p2VPMf3y3UoQZ1TfPfVOrgg==", + "deprecated": "replaced by i18next-http-middleware", + "license": "MIT", + "dependencies": { + "cookies": "0.7.1" + } + }, + "node_modules/i18next-fs-backend": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.6.tgz", + "integrity": "sha512-mYGu6Nt8RIp3X/U8Y+Gej1wo5xmYWmGKLqBGMCC2OCAou5rW5epeHgHmVcw20mJs9Z9+DAPHIxQPNCgFyPRMeg==", + "license": "MIT" + }, + "node_modules/i18next-http-middleware": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.9.7.tgz", + "integrity": "sha512-k4Aa39thnQXGox5ZeKvlOwbDsFz+iGxtPJITWm6xmmErNZOeGMXIoH9XwK3ITUq7lqHaywnIH9gLwOraU4jJvQ==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "license": "MIT" + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/keygrip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", + "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/morgan": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.11.0.tgz", + "integrity": "sha512-zSkVu3t18r39pw4ixfBKvfZi3y2UOqr7d4WYwcj3m8nXpEQK4rPO6GLzs/CExoRgmX3y9EjmmcXqv6jq0SK46g==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.4.1", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pug": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", + "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.4", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", + "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", + "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spin.js": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/spin.js/-/spin.js-4.1.2.tgz", + "integrity": "sha512-ua/yEpxEwyEUWs57tMQYdik/KJ12sQRyMXjSlK/Ai927aEUDVY3FXUi4ml4VvlLCTQNIjC6tHyjSLBrJzFAqMA==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "license": "MIT" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + } + } +} diff --git a/noise/package.json b/noise/package.json new file mode 100644 index 0000000..fd76aa9 --- /dev/null +++ b/noise/package.json @@ -0,0 +1,31 @@ +{ + "name": "laerm-web-sensorapi", + "version": "3.3.1", + "date": "2026-06-30 18:00 UTC", + "private": true, + "scripts": { + "start": "node bin/www.js >>/var/log/noise.log 2>&1" + }, + "type": "module", + "bin": { + "www": "./bin/www.js" + }, + "dependencies": { + "axios": "^1.3.4", + "cookie-parser": "~1.4.4", + "cors": "^2.8.5", + "debug": "~2.6.9", + "dotenv": "^17.2.3", + "express": "^4.18.2", + "http-errors": "~1.6.3", + "i18next": "^22.4.14", + "i18next-express-middleware": "^2.0.0", + "i18next-http-middleware": "^3.3.0", + "i18next-fs-backend": "^2.0.0", + "luxon": "^3.3.0", + "morgan": "^1.10.1", + "pug": "^3.0.2", + "sass": "^1.58.3", + "spin.js": "^4.1.2" + } +} diff --git a/noise/public/images/favicon.ico b/noise/public/images/favicon.ico new file mode 100644 index 0000000..a5b85b0 Binary files /dev/null and b/noise/public/images/favicon.ico differ diff --git a/noise/public/javascripts/chart_utilities.js b/noise/public/javascripts/chart_utilities.js new file mode 100644 index 0000000..aa4ce2b --- /dev/null +++ b/noise/public/javascripts/chart_utilities.js @@ -0,0 +1,276 @@ +// Utility routine for plotting the data + +import {DateTime} from './luxon.min.js' +export const colors = {'eq': '#0000FF', 'max': '#FF0000', 'min': '#008000', 'peaks': '#DAA520'}; + +// Defaults für die Mittelwertbildungen +let txtMeldung = false; // falls keine Daten da sind, Text melden +const avgTime = 30; // defaul average time für particulate matter +let doUpdate = true; // update every 5 min +let optSidsArray = []; // Arrray der letzten 5 Einträge +let nbrofdaysforavg = 8; // Stundenmittel Anzahl Tagte (default = 5) +let nbrofdaysfordaynight = 30; // use 30 days or day/night graphic +let stucols = 1; // Anzeige nur Balken (StundenMittel) +let activeTab = 'maptab'; // active TAB ID + +export const noise_ymin = 30; // lowest value on y-axis for noise +export const noise_ymax = 120; // highest value on y-axis for noise +export let peaklim = 70; // threshold for peak count + +export const url = '/srv' + +export function createGlobObtions() { + // Options, die für alle Plots identisch sind + let globObject = { + chart: { + accessibility: { + enabled: false + }, + height: 600, +// width: 1000, + spacingRight: 20, + spacingLeft: 20, + spacingTop: 25, + backgroundColor: { + linearGradient: [0, 400, 0, 0], + stops: [ + [0, '#eee'],//[0, '#ACD0AA'], //[0, '#A18D99'], // [0, '#886A8B'], // [0, '#F2D0B5'], + [1, '#fff'] + ] + }, + type: 'line', + borderWidth: '2', + useHTML: true, + events: { + selection: function (event) { + if (event.xAxis) { + doUpdate = false; + } else { + doUpdate = true; + } + } + } + }, + title: { +// text: 'Feinstaub über 1 Tag', + align: 'left', + style: {'fontSize': '25px'}, + useHTML: true, + }, + subtitle: { +// text: 'Gemessene Werte und ' + avgTime + 'min-gleitende Mittelwerte', + align: 'left', + }, + tooltip: { + valueDecimals: 1, + backgroundColor: 0, + borderWidth: 0, + borderRadius: 0, + useHTML: true, + }, + labels: { + useHTML: true + }, + xAxis: { + type: 'datetime', + title: { + text: 'date/time', + }, + gridLineWidth: 2, + labels: { + formatter: function () { + let v = this.axis.defaultLabelFormatter.call(this); + if (v.indexOf(':') == -1) { + return '' + v + ''; + } else { + return v; + } + } + }, + }, + legend: { + enabled: true, + layout: 'horizontal', +// verticalAlign: 'top', + borderWidth: 1, + align: 'center', + }, + plotOptions: { + series: { + animation: false, + turboThreshold: 0, + marker: { + enabled: false, + }, + }, + } + }; + return globObject; +} + +export function calcWeekends(data, isyear) { + let weekend = []; + let oldDay = 8; + for (let i = 0; i < data.length; i++) { + let mom = moment(data[i].date); + if (isyear) { + mom = moment(data[i]._id) + } + let day = mom.day(); + let st = mom.startOf('day'); + if (day != oldDay) { + if (day == 6) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } else if (day == 0) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } + oldDay = day; + } + } + return weekend; +} + +export function calcDays(data, isyear) { + let days = []; + if (data.length == 0) { + return days + } + let oldday = moment(data[0].date).day(); + if (isyear) { + oldday = moment(data[0]._id).day(); + } + for (let i = 0; i < data.length; i++) { + let m = moment(data[i].date); + if (isyear) { + m = moment(data[i]._id); + } + let tag = m.day() + if (tag != oldday) { + m.startOf('day'); + days.push({color: 'lightgray', value: m.valueOf(), width: 1, zIndex: 2}); + oldday = tag; + } + } + return days; +}; + +export async function addSensorID2chart(chart, sensor, width) { + let indoor = sensor.indoor || false + let addr = await addAddress(sensor.sid) + chart.renderer.label( + `Sensor:  ${sensor.sid} ${indoor ? ' (indoor)' : ''}

${addr.street}
${addr.plz} ${addr.city}`, + width / 2 - 150, 70) + .css({ + fontSize: '14pt', + 'font-weight': 'bold', + }) + .attr({ + zIndex: 3, + }).add(); +} + +export const showError = (err) => { + const dialog = document.getElementById('dialogError') + const body = dialog.querySelector('.dialog-body') + body.innerHTML = err + dialog.showModal() +} + +export const showReset = () => { + const dialog = document.getElementById('dialogReset') + dialog.showModal() +} + +// Setup dialog close buttons +document.addEventListener('DOMContentLoaded', () => { + const errorClose = document.querySelector('#dialogError .dialog-close') + if (errorClose) { + errorClose.addEventListener('click', () => { + document.getElementById('dialogError').close() + }) + } + + const resetClose = document.querySelector('#dialogReset .dialog-close') + if (resetClose) { + resetClose.addEventListener('click', () => { + document.getElementById('dialogReset').close() + }) + } + + const resetOk = document.querySelector('#btnResetOk') + if (resetOk) { + resetOk.addEventListener('click', () => { + document.getElementById('dialogReset').close() + }) + } +}) + +// remove the taps (if shownig the map) +export const removeTabs = () => { + document.querySelector('#navi').style.display = 'none' +} + +// show thw tabs again +export const showTabs = () => { + if (activeTab !== 'maptab') { + document.querySelector('#navi').style.display = 'block' + } +} + +export const setCurrentTab = (tab) => { + activeTab = tab +} + +export const getCurrentTab = () => { + return activeTab +} + +export const fetchfromserver = async (url) => { + const ret = await fetch(encodeURI(url)) + .catch(e => { + showError(e) + }); +// return await ret.json() + let x = await ret.json() + return x +} + +export const addAddress = async (sid) => { + let url = `/srv/getaddress?sensorid=${sid}` + let erg = await fetchfromserver(url) + if (!erg.err) { + return erg.address + } else { + return "no address" + } +} + +export const cityCoords = async (city) => { + let url = `/srv/getcitycoords?city=${city}` + let erg = await fetchfromserver(url) + if (!erg.err) { + return erg.coords + } else { + return "no coordinates" + } +} + +// table to distribute the different charts +export const ttIndex = Object.freeze({ + map: 0, + live: 1, + havg: 2, + davg: 3, + daynight: 4, + lden: 5 +}) diff --git a/noise/public/javascripts/checklang.js b/noise/public/javascripts/checklang.js new file mode 100644 index 0000000..7dbb47c --- /dev/null +++ b/noise/public/javascripts/checklang.js @@ -0,0 +1,15 @@ +// set correct language (will be called by chglang.pug) +let currentLang +const navLang = navigator.language.slice(0,2) +const selectedLang = localStorage.getItem('curlang') +if (!selectedLang) { + currentLang = navLang +} else { + currentLang = selectedLang +} +if (sensorid !== '') { + window.location.href = `/${sensorid}?lng=${currentLang}` +} else { + window.location.href = `?lng=${currentLang}` +} + diff --git a/noise/public/javascripts/datetime.js b/noise/public/javascripts/datetime.js new file mode 100644 index 0000000..086ef84 --- /dev/null +++ b/noise/public/javascripts/datetime.js @@ -0,0 +1,30 @@ +// all date and time functions +import { DateTime } from './luxon.min.js' +import {loadAll} from './showcharts.js' +import {getCurrentTab} from "./chart_utilities.js"; + + +// Show date and time (every minute) +// Will be called every second +export const showDate = (sofort, params) => { + let d = DateTime.now() + if(sofort || d.second === 0) { + document.querySelector('#h1datum').innerHTML = d.toFormat('yyyy-MM-dd HH:mm') + } + if (((d.minute % params.refreshRate) == 0) && (d.second == 15)) { // alle ganzen refreshRate Minuten, 15sec danach + let start = 1 + console.log(params.refreshRate, 'Minuten um, Grafik wird erneuert'); + let ct = getCurrentTab() + if (ct !== 'maptab') { + loadAll(params, start) + } + } +} + +export const formatJSDate = (d) => { + return DateTime.fromJSDate(d).toFormat('yyyy-LL-dd HH:mm:ss') +} + +export const formatISODate = (d) => { + return DateTime.fromISO(d).toFormat('yyyy-LL-dd HH:mm:ss') +} diff --git a/noise/public/javascripts/global.js b/noise/public/javascripts/global.js new file mode 100644 index 0000000..d7edd60 --- /dev/null +++ b/noise/public/javascripts/global.js @@ -0,0 +1,303 @@ +// Client side javascript +"use strict" + +import * as map from './map.js' +import * as dt from './datetime.js' +import {loadAll, showChart, setNoUTC, tabtable} from './showcharts.js' +import { DateTime } from './luxon.min.js' +import {ttIndex, setCurrentTab, getCurrentTab, showError, fetchfromserver, cityCoords} from "./chart_utilities.js"; +import * as spin from './spinner.js' + + +(async () => { + + // global variables + const params = { // set defaults + center: {name: 'Stuttgart', coords: [48.778, 9.180]}, + zoom: 10, + span: 1, + peak: 70, + weeks: 4, + datetime: '', + seldate: '', + refreshRate: 5, + sid: -1 + } + + let minDate = new Date(2023, 0, 1) + + const setting = [ + { + typ: 'maptab', + show: ['ccity', 'odth'], + val: ['centercity', 'olderthan'], + param: [["center","name"], 'weeks'] + }, + { + typ: 'livetab', + show: ['stday', 'nbday'], + val: ['startday', 'nbrofdays'], + param: ['seldate', 'span'] + },{ + typ: 'houravgtab', + show: ['stday','pklim'], + val: ['startday', 'peaklim'], + param: ['seldate', 'peak'] + },{ + typ: 'dayavgtab', + show: ['stday','pklim'], + val: ['startday', 'peaklim'], + param: ['seldate', 'peak'] + },{ + typ: 'daynighttab', + show: ['stday'], + val: ['startday'], + param: ['seldate'] + },{ + typ: 'ldentab', + show: ['stday'], + val: ['startday'], + param: ['seldate'] + } + ] + // END global variables + + // localStorage.clear() + + + // Check new map center + const checkNewCenter = (oldC, newC) => { + if((oldC[0] !== newC[0]) || (oldC[1] !== newC[1])) { + map.setNewCenter(newC) + } + } + + // Register events + // Button 'My Location' pressed + document.addEventListener('DOMContentLoaded', () => { + const btnMyLocation = document.querySelector('#btnMyLocation'); + if (btnMyLocation) { + btnMyLocation.addEventListener('click', () => { + map.goToMyLocation(); + }); + } + }); + + // Button 'Save' pressed + document.querySelector('#btnSave').addEventListener('click', async () => { + let curtab = getCurrentTab() + for(let i = 0; i < setting.length; i++) { + if(setting[i].typ === getCurrentTab()) { + for(let j = 0; j < setting[i].show.length; j++) { + let elem = document.getElementById(setting[i].val[j]) + let val = elem.value + if(elem.type === 'checkbox') { + val = elem.checked + } + let x = setting[i].param[j] + console.log('x: ', x) + if ( Array.isArray(x)) { + params[setting[i].param[j][0]][setting[i].param[j][1]] = val + } else { + params[setting[i].param[j]] = val + } + } + break + } + } + if (curtab !== 'maptab') { + let starttime + if ((params.seldate === 'today') || (params.seldate === 'heute') || (DateTime.now().toFormat('yyyy-MM-dd') === params.seldate)) { + starttime = '' + } else { + starttime = DateTime.fromFormat(params.seldate, 'yyyy-MM-dd').toUTC().toISO() + } + params.datetime = starttime + } else { + const oldCoords = params.center.coords + params.center.coords = await cityCoords(params.center.name) + checkNewCenter(oldCoords, params.center.coords) + localStorage.setItem('centercity',JSON.stringify(params.center)) + } + let newlng = document.querySelector('#sellan input:checked').id + let oldlng = localStorage.getItem('curlang') + localStorage.setItem('curlang', newlng) + if(newlng !== oldlng) { + location.reload() + } + document.getElementById('dialogSettings').close() + spin.spinner.spin(spin.spindiv) + await loadAll(params,0, curtab) + spin.spinner.stop() + // ToDo: + // Load ALL incl. MAP und LIVE hier, d.h. an loadAll einen zusätzlichen Parameter übergeben + // if(params.sid !== undefined) { + // await loadAll(params) + // } + }) + + // Btn 'Settings' pressed + document.querySelector('#btnSet').addEventListener('click', () => { + const curtab = getCurrentTab() + const dialog = document.getElementById('dialogSettings') + + // Set max and min dates for date input + const today = new Date().toISOString().split('T')[0] + const minDateStr = minDate.toISOString().split('T')[0] + const startdayInput = document.getElementById('startday') + startdayInput.max = today + startdayInput.min = minDateStr + + // Convert seldate format if needed + let seldate = params.seldate + if (seldate === 'today' || seldate === 'heute') { + seldate = today + } + + startdayInput.value = seldate + document.getElementById('nbrofdays').value = params.span + document.getElementById('peaklim').value = params.peak + document.getElementById('olderthan').value = params.weeks + const centercity = JSON.parse(localStorage.getItem('centercity')) + document.getElementById('centercity').value = centercity.name + + // select different infos depending on the selected tab + // first clear all + document.querySelector('#ccity').style.display = 'none' + document.querySelector('#stday').style.display = 'none' + document.querySelector('#nbday').style.display = 'none' + document.querySelector('#pklim').style.display = 'none' + document.querySelector('#odth').style.display = 'none' + for(let i = 0; i < setting.length; i++) { + if(setting[i].typ === curtab) { + for(let j = 0; j < setting[i].show.length; j++) { + document.querySelector(`#${setting[i].show[j]}`).style.display = 'inline' + } + break + } + } + dialog.showModal() + }) + + // Close button for settings dialog + document.querySelector('#dialogSettings .dialog-close').addEventListener('click', () => { + document.getElementById('dialogSettings').close() + }) + + // Close button (secondary button) + document.querySelector('#btnClose').addEventListener('click', () => { + document.getElementById('dialogSettings').close() + }) + + const getSensorType = async (sid) => { + let url = `/srv/getoneproperty?sensorid=${sid}` + const data = await fetchfromserver(url) + return data.props + } + + // main function: + // show the map if called w/o any parameter + // if called with a sensor-ID, show the live chart for this sensor + async function main() { + setNoUTC() // no UTC for HighCharts + // set correct language + let currentLang = localStorage.getItem('curlang') + if ((currentLang === null) || (currentLang === '')) { + currentLang = 'de' + localStorage.setItem('curlang', currentLang) + } + let centercity + try { + centercity = JSON.parse(localStorage.getItem('centercity')) + } catch (e) { + centercity = params.center + } + if((centercity === null) || (centercity.name === '')) { + centercity = params.center + } + localStorage.setItem('centercity', JSON.stringify(centercity)) + params.center = centercity + if(currentLang === 'en') { + document.querySelector('#en').checked = true + params.seldate = 'today' + } else { + document.querySelector('#de').checked = true + params.seldate = 'heute' + } + console.log(currentLang) + if (parseInt(sysparams.csid) !== -1) { + history.pushState({}, '', `/${sysparams.csid}`) + } else { + history.pushState({}, '', '/') + } + + dt.showDate(true, params) + // and show date/time every minute + setInterval(() => dt.showDate(false, params), 1000) + + // initialise tabs + const triggerTabList = [].slice.call(document.querySelectorAll('.tab-button')) + triggerTabList.forEach(function (triggerEl) { + triggerEl.addEventListener('click', function (event) { + event.preventDefault() + + // Remove active from all tabs + triggerTabList.forEach(tab => tab.classList.remove('active')) + document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')) + + // Add active to clicked tab + triggerEl.classList.add('active') + const targetId = triggerEl.getAttribute('data-target') + document.getElementById(targetId).classList.add('active') + + let newtab = event.currentTarget.id + setCurrentTab(newtab) + let tab = tabtable.find(tab => tab.id === newtab) + if (document.querySelector(`#${tab.id}err`).innerHTML !== '') { // if there is an error message, clear it + showError(document.querySelector(`#${tab.id}err`).innerHTML) + } + if (newtab === 'maptab') { + map.showMap(params) + } + }) + }) + const csid = parseInt(sysparams.csid) + if(csid !== -1) { + spin.spinner.spin(spin.spindiv) + // check, if sid is of right type (noise) + const props = await getSensorType(csid) + if (props.type !== 'noise') { + // wrong sensor type + spin.spinner.stop() + showError('Sensor ID ' + csid + ' is not of type noise') + return + } + params.center.coords = [props.location[0].loc.coordinates[1], props.location[0].loc.coordinates[0]] + params.sid = csid + let t = triggerTabList[1] + + // Switch to live tab + triggerTabList.forEach(tab => tab.classList.remove('active')) + document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')) + t.classList.add('active') + document.getElementById('t_live').classList.add('active') + + setCurrentTab('livetab') + let err = await showChart(params, ttIndex.live) + spin.spinner.stop() + if (err.error) { + let errtxt = document.querySelector(`#${tabtable[ttIndex.live].id}err`).innerHTML + if (errtxt !== '') { + showError(errtxt) + } + } + await loadAll(params) + } else { + // show the map + map.showMap(params) + } + } + + main().catch(console.error) + +})() \ No newline at end of file diff --git a/noise/public/javascripts/leaflet.markercluster.js b/noise/public/javascripts/leaflet.markercluster.js new file mode 100644 index 0000000..67c52dc --- /dev/null +++ b/noise/public/javascripts/leaflet.markercluster.js @@ -0,0 +1,3 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e.Leaflet=e.Leaflet||{},e.Leaflet.markercluster=e.Leaflet.markercluster||{}))}(this,function(e){"use strict";var t=L.MarkerClusterGroup=L.FeatureGroup.extend({options:{maxClusterRadius:80,iconCreateFunction:null,clusterPane:L.Marker.prototype.options.pane,spiderfyOnMaxZoom:!0,showCoverageOnHover:!0,zoomToBoundsOnClick:!0,singleMarkerMode:!1,disableClusteringAtZoom:null,removeOutsideVisibleBounds:!0,animate:!0,animateAddingMarkers:!1,spiderfyDistanceMultiplier:1,spiderLegPolylineOptions:{weight:1.5,color:"#222",opacity:.5},chunkedLoading:!1,chunkInterval:200,chunkDelay:50,chunkProgress:null,polygonOptions:{}},initialize:function(e){L.Util.setOptions(this,e),this.options.iconCreateFunction||(this.options.iconCreateFunction=this._defaultIconCreateFunction),this._featureGroup=L.featureGroup(),this._featureGroup.addEventParent(this),this._nonPointGroup=L.featureGroup(),this._nonPointGroup.addEventParent(this),this._inZoomAnimation=0,this._needsClustering=[],this._needsRemoving=[],this._currentShownBounds=null,this._queue=[],this._childMarkerEventHandlers={dragstart:this._childMarkerDragStart,move:this._childMarkerMoved,dragend:this._childMarkerDragEnd};var t=L.DomUtil.TRANSITION&&this.options.animate;L.extend(this,t?this._withAnimation:this._noAnimation),this._markerCluster=t?L.MarkerCluster:L.MarkerClusterNonAnimated},addLayer:function(e){if(e instanceof L.LayerGroup)return this.addLayers([e]);if(!e.getLatLng)return this._nonPointGroup.addLayer(e),this.fire("layeradd",{layer:e}),this;if(!this._map)return this._needsClustering.push(e),this.fire("layeradd",{layer:e}),this;if(this.hasLayer(e))return this;this._unspiderfy&&this._unspiderfy(),this._addLayer(e,this._maxZoom),this.fire("layeradd",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons();var t=e,i=this._zoom;if(e.__parent)for(;t.__parent._zoom>=i;)t=t.__parent;return this._currentShownBounds.contains(t.getLatLng())&&(this.options.animateAddingMarkers?this._animationAddLayer(e,t):this._animationAddLayerNonAnimated(e,t)),this},removeLayer:function(e){return e instanceof L.LayerGroup?this.removeLayers([e]):e.getLatLng?this._map?e.__parent?(this._unspiderfy&&(this._unspiderfy(),this._unspiderfyLayer(e)),this._removeLayer(e,!0),this.fire("layerremove",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),e.off(this._childMarkerEventHandlers,this),this._featureGroup.hasLayer(e)&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow()),this):this:(!this._arraySplice(this._needsClustering,e)&&this.hasLayer(e)&&this._needsRemoving.push({layer:e,latlng:e._latlng}),this.fire("layerremove",{layer:e}),this):(this._nonPointGroup.removeLayer(e),this.fire("layerremove",{layer:e}),this)},addLayers:function(e,t){if(!L.Util.isArray(e))return this.addLayer(e);var i,n=this._featureGroup,r=this._nonPointGroup,s=this.options.chunkedLoading,o=this.options.chunkInterval,a=this.options.chunkProgress,h=e.length,l=0,u=!0;if(this._map){var _=(new Date).getTime(),d=L.bind(function(){for(var c=(new Date).getTime();h>l;l++){if(s&&0===l%200){var p=(new Date).getTime()-c;if(p>o)break}if(i=e[l],i instanceof L.LayerGroup)u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length;else if(i.getLatLng){if(!this.hasLayer(i)&&(this._addLayer(i,this._maxZoom),t||this.fire("layeradd",{layer:i}),i.__parent&&2===i.__parent.getChildCount())){var f=i.__parent.getAllChildMarkers(),m=f[0]===i?f[1]:f[0];n.removeLayer(m)}}else r.addLayer(i),t||this.fire("layeradd",{layer:i})}a&&a(l,h,(new Date).getTime()-_),l===h?(this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds)):setTimeout(d,this.options.chunkDelay)},this);d()}else for(var c=this._needsClustering;h>l;l++)i=e[l],i instanceof L.LayerGroup?(u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length):i.getLatLng?this.hasLayer(i)||c.push(i):r.addLayer(i);return this},removeLayers:function(e){var t,i,n=e.length,r=this._featureGroup,s=this._nonPointGroup,o=!0;if(!this._map){for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):(this._arraySplice(this._needsClustering,i),s.removeLayer(i),this.hasLayer(i)&&this._needsRemoving.push({layer:i,latlng:i._latlng}),this.fire("layerremove",{layer:i}));return this}if(this._unspiderfy){this._unspiderfy();var a=e.slice(),h=n;for(t=0;h>t;t++)i=a[t],i instanceof L.LayerGroup?(this._extractNonGroupLayers(i,a),h=a.length):this._unspiderfyLayer(i)}for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):i.__parent?(this._removeLayer(i,!0,!0),this.fire("layerremove",{layer:i}),r.hasLayer(i)&&(r.removeLayer(i),i.clusterShow&&i.clusterShow())):(s.removeLayer(i),this.fire("layerremove",{layer:i}));return this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds),this},clearLayers:function(){return this._map||(this._needsClustering=[],this._needsRemoving=[],delete this._gridClusters,delete this._gridUnclustered),this._noanimationUnspiderfy&&this._noanimationUnspiderfy(),this._featureGroup.clearLayers(),this._nonPointGroup.clearLayers(),this.eachLayer(function(e){e.off(this._childMarkerEventHandlers,this),delete e.__parent},this),this._map&&this._generateInitialClusters(),this},getBounds:function(){var e=new L.LatLngBounds;this._topClusterLevel&&e.extend(this._topClusterLevel._bounds);for(var t=this._needsClustering.length-1;t>=0;t--)e.extend(this._needsClustering[t].getLatLng());return e.extend(this._nonPointGroup.getBounds()),e},eachLayer:function(e,t){var i,n,r,s=this._needsClustering.slice(),o=this._needsRemoving;for(this._topClusterLevel&&this._topClusterLevel.getAllChildMarkers(s),n=s.length-1;n>=0;n--){for(i=!0,r=o.length-1;r>=0;r--)if(o[r].layer===s[n]){i=!1;break}i&&e.call(t,s[n])}this._nonPointGroup.eachLayer(e,t)},getLayers:function(){var e=[];return this.eachLayer(function(t){e.push(t)}),e},getLayer:function(e){var t=null;return e=parseInt(e,10),this.eachLayer(function(i){L.stamp(i)===e&&(t=i)}),t},hasLayer:function(e){if(!e)return!1;var t,i=this._needsClustering;for(t=i.length-1;t>=0;t--)if(i[t]===e)return!0;for(i=this._needsRemoving,t=i.length-1;t>=0;t--)if(i[t].layer===e)return!1;return!(!e.__parent||e.__parent._group!==this)||this._nonPointGroup.hasLayer(e)},zoomToShowLayer:function(e,t){"function"!=typeof t&&(t=function(){});var i=function(){!e._icon&&!e.__parent._icon||this._inZoomAnimation||(this._map.off("moveend",i,this),this.off("animationend",i,this),e._icon?t():e.__parent._icon&&(this.once("spiderfied",t,this),e.__parent.spiderfy()))};e._icon&&this._map.getBounds().contains(e.getLatLng())?t():e.__parent._zoomt;t++)n=this._needsRemoving[t],n.newlatlng=n.layer._latlng,n.layer._latlng=n.latlng;for(t=0,i=this._needsRemoving.length;i>t;t++)n=this._needsRemoving[t],this._removeLayer(n.layer,!0),n.layer._latlng=n.newlatlng;this._needsRemoving=[],this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds(),this._map.on("zoomend",this._zoomEnd,this),this._map.on("moveend",this._moveEnd,this),this._spiderfierOnAdd&&this._spiderfierOnAdd(),this._bindEvents(),i=this._needsClustering,this._needsClustering=[],this.addLayers(i,!0)},onRemove:function(e){e.off("zoomend",this._zoomEnd,this),e.off("moveend",this._moveEnd,this),this._unbindEvents(),this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim",""),this._spiderfierOnRemove&&this._spiderfierOnRemove(),delete this._maxLat,this._hideCoverage(),this._featureGroup.remove(),this._nonPointGroup.remove(),this._featureGroup.clearLayers(),this._map=null},getVisibleParent:function(e){for(var t=e;t&&!t._icon;)t=t.__parent;return t||null},_arraySplice:function(e,t){for(var i=e.length-1;i>=0;i--)if(e[i]===t)return e.splice(i,1),!0},_removeFromGridUnclustered:function(e,t){for(var i=this._map,n=this._gridUnclustered,r=Math.floor(this._map.getMinZoom());t>=r&&n[t].removeObject(e,i.project(e.getLatLng(),t));t--);},_childMarkerDragStart:function(e){e.target.__dragStart=e.target._latlng},_childMarkerMoved:function(e){if(!this._ignoreMove&&!e.target.__dragStart){var t=e.target._popup&&e.target._popup.isOpen();this._moveChild(e.target,e.oldLatLng,e.latlng),t&&e.target.openPopup()}},_moveChild:function(e,t,i){e._latlng=t,this.removeLayer(e),e._latlng=i,this.addLayer(e)},_childMarkerDragEnd:function(e){var t=e.target.__dragStart;delete e.target.__dragStart,t&&this._moveChild(e.target,t,e.target._latlng)},_removeLayer:function(e,t,i){var n=this._gridClusters,r=this._gridUnclustered,s=this._featureGroup,o=this._map,a=Math.floor(this._map.getMinZoom());t&&this._removeFromGridUnclustered(e,this._maxZoom);var h,l=e.__parent,u=l._markers;for(this._arraySplice(u,e);l&&(l._childCount--,l._boundsNeedUpdate=!0,!(l._zoomt?"small":100>t?"medium":"large",new L.DivIcon({html:"
"+t+"
",className:"marker-cluster"+i,iconSize:new L.Point(40,40)})},_bindEvents:function(){var e=this._map,t=this.options.spiderfyOnMaxZoom,i=this.options.showCoverageOnHover,n=this.options.zoomToBoundsOnClick;(t||n)&&this.on("clusterclick",this._zoomOrSpiderfy,this),i&&(this.on("clustermouseover",this._showCoverage,this),this.on("clustermouseout",this._hideCoverage,this),e.on("zoomend",this._hideCoverage,this))},_zoomOrSpiderfy:function(e){for(var t=e.layer,i=t;1===i._childClusters.length;)i=i._childClusters[0];i._zoom===this._maxZoom&&i._childCount===t._childCount&&this.options.spiderfyOnMaxZoom?t.spiderfy():this.options.zoomToBoundsOnClick&&t.zoomToBounds(),e.originalEvent&&13===e.originalEvent.keyCode&&this._map._container.focus()},_showCoverage:function(e){var t=this._map;this._inZoomAnimation||(this._shownPolygon&&t.removeLayer(this._shownPolygon),e.layer.getChildCount()>2&&e.layer!==this._spiderfied&&(this._shownPolygon=new L.Polygon(e.layer.getConvexHull(),this.options.polygonOptions),t.addLayer(this._shownPolygon)))},_hideCoverage:function(){this._shownPolygon&&(this._map.removeLayer(this._shownPolygon),this._shownPolygon=null)},_unbindEvents:function(){var e=this.options.spiderfyOnMaxZoom,t=this.options.showCoverageOnHover,i=this.options.zoomToBoundsOnClick,n=this._map;(e||i)&&this.off("clusterclick",this._zoomOrSpiderfy,this),t&&(this.off("clustermouseover",this._showCoverage,this),this.off("clustermouseout",this._hideCoverage,this),n.off("zoomend",this._hideCoverage,this))},_zoomEnd:function(){this._map&&(this._mergeSplitClusters(),this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds())},_moveEnd:function(){if(!this._inZoomAnimation){var e=this._getExpandedVisibleBounds();this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),this._zoom,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,Math.round(this._map._zoom),e),this._currentShownBounds=e}},_generateInitialClusters:function(){var e=Math.ceil(this._map.getMaxZoom()),t=Math.floor(this._map.getMinZoom()),i=this.options.maxClusterRadius,n=i;"function"!=typeof i&&(n=function(){return i}),null!==this.options.disableClusteringAtZoom&&(e=this.options.disableClusteringAtZoom-1),this._maxZoom=e,this._gridClusters={},this._gridUnclustered={};for(var r=e;r>=t;r--)this._gridClusters[r]=new L.DistanceGrid(n(r)),this._gridUnclustered[r]=new L.DistanceGrid(n(r));this._topClusterLevel=new this._markerCluster(this,t-1)},_addLayer:function(e,t){var i,n,r=this._gridClusters,s=this._gridUnclustered,o=Math.floor(this._map.getMinZoom());for(this.options.singleMarkerMode&&this._overrideMarkerIcon(e),e.on(this._childMarkerEventHandlers,this);t>=o;t--){i=this._map.project(e.getLatLng(),t);var a=r[t].getNearObject(i);if(a)return a._addChild(e),e.__parent=a,void 0;if(a=s[t].getNearObject(i)){var h=a.__parent;h&&this._removeLayer(a,!1);var l=new this._markerCluster(this,t,a,e);r[t].addObject(l,this._map.project(l._cLatLng,t)),a.__parent=l,e.__parent=l;var u=l;for(n=t-1;n>h._zoom;n--)u=new this._markerCluster(this,n,u),r[n].addObject(u,this._map.project(a.getLatLng(),n));return h._addChild(u),this._removeFromGridUnclustered(a,t),void 0}s[t].addObject(e,i)}this._topClusterLevel._addChild(e),e.__parent=this._topClusterLevel},_refreshClustersIcons:function(){this._featureGroup.eachLayer(function(e){e instanceof L.MarkerCluster&&e._iconNeedsUpdate&&e._updateIcon()})},_enqueue:function(e){this._queue.push(e),this._queueTimeout||(this._queueTimeout=setTimeout(L.bind(this._processQueue,this),300))},_processQueue:function(){for(var e=0;ee?(this._animationStart(),this._animationZoomOut(this._zoom,e)):this._moveEnd()},_getExpandedVisibleBounds:function(){return this.options.removeOutsideVisibleBounds?L.Browser.mobile?this._checkBoundsMaxLat(this._map.getBounds()):this._checkBoundsMaxLat(this._map.getBounds().pad(1)):this._mapBoundsInfinite},_checkBoundsMaxLat:function(e){var t=this._maxLat;return void 0!==t&&(e.getNorth()>=t&&(e._northEast.lat=1/0),e.getSouth()<=-t&&(e._southWest.lat=-1/0)),e},_animationAddLayerNonAnimated:function(e,t){if(t===e)this._featureGroup.addLayer(e);else if(2===t._childCount){t._addToMap();var i=t.getAllChildMarkers();this._featureGroup.removeLayer(i[0]),this._featureGroup.removeLayer(i[1])}else t._updateIcon()},_extractNonGroupLayers:function(e,t){var i,n=e.getLayers(),r=0;for(t=t||[];r=0;i--)o=h[i],n.contains(o._latlng)||r.removeLayer(o)}),this._forceLayout(),this._topClusterLevel._recursivelyBecomeVisible(n,t),r.eachLayer(function(e){e instanceof L.MarkerCluster||!e._icon||e.clusterShow()}),this._topClusterLevel._recursively(n,e,t,function(e){e._recursivelyRestoreChildPositions(t)}),this._ignoreMove=!1,this._enqueue(function(){this._topClusterLevel._recursively(n,e,s,function(e){r.removeLayer(e),e.clusterShow()}),this._animationEnd()})},_animationZoomOut:function(e,t){this._animationZoomOutSingle(this._topClusterLevel,e-1,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e,this._getExpandedVisibleBounds())},_animationAddLayer:function(e,t){var i=this,n=this._featureGroup;n.addLayer(e),t!==e&&(t._childCount>2?(t._updateIcon(),this._forceLayout(),this._animationStart(),e._setPos(this._map.latLngToLayerPoint(t.getLatLng())),e.clusterHide(),this._enqueue(function(){n.removeLayer(e),e.clusterShow(),i._animationEnd()})):(this._forceLayout(),i._animationStart(),i._animationZoomOutSingle(t,this._map.getMaxZoom(),this._zoom)))}},_animationZoomOutSingle:function(e,t,i){var n=this._getExpandedVisibleBounds(),r=Math.floor(this._map.getMinZoom());e._recursivelyAnimateChildrenInAndAddSelfToMap(n,r,t+1,i);var s=this;this._forceLayout(),e._recursivelyBecomeVisible(n,i),this._enqueue(function(){if(1===e._childCount){var o=e._markers[0];this._ignoreMove=!0,o.setLatLng(o.getLatLng()),this._ignoreMove=!1,o.clusterShow&&o.clusterShow()}else e._recursively(n,i,r,function(e){e._recursivelyRemoveChildrenFromMap(n,r,t+1)});s._animationEnd()})},_animationEnd:function(){this._map&&(this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim","")),this._inZoomAnimation--,this.fire("animationend")},_forceLayout:function(){L.Util.falseFn(document.body.offsetWidth)}}),L.markerClusterGroup=function(e){return new L.MarkerClusterGroup(e)};var i=L.MarkerCluster=L.Marker.extend({options:L.Icon.prototype.options,initialize:function(e,t,i,n){L.Marker.prototype.initialize.call(this,i?i._cLatLng||i.getLatLng():new L.LatLng(0,0),{icon:this,pane:e.options.clusterPane}),this._group=e,this._zoom=t,this._markers=[],this._childClusters=[],this._childCount=0,this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._bounds=new L.LatLngBounds,i&&this._addChild(i),n&&this._addChild(n)},getAllChildMarkers:function(e,t){e=e||[];for(var i=this._childClusters.length-1;i>=0;i--)this._childClusters[i].getAllChildMarkers(e);for(var n=this._markers.length-1;n>=0;n--)t&&this._markers[n].__dragStart||e.push(this._markers[n]);return e},getChildCount:function(){return this._childCount},zoomToBounds:function(e){for(var t,i=this._childClusters.slice(),n=this._group._map,r=n.getBoundsZoom(this._bounds),s=this._zoom+1,o=n.getZoom();i.length>0&&r>s;){s++;var a=[];for(t=0;ts?this._group._map.setView(this._latlng,s):o>=r?this._group._map.setView(this._latlng,o+1):this._group._map.fitBounds(this._bounds,e)},getBounds:function(){var e=new L.LatLngBounds;return e.extend(this._bounds),e},_updateIcon:function(){this._iconNeedsUpdate=!0,this._icon&&this.setIcon(this)},createIcon:function(){return this._iconNeedsUpdate&&(this._iconObj=this._group.options.iconCreateFunction(this),this._iconNeedsUpdate=!1),this._iconObj.createIcon()},createShadow:function(){return this._iconObj.createShadow()},_addChild:function(e,t){this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._setClusterCenter(e),e instanceof L.MarkerCluster?(t||(this._childClusters.push(e),e.__parent=this),this._childCount+=e._childCount):(t||this._markers.push(e),this._childCount++),this.__parent&&this.__parent._addChild(e,!0)},_setClusterCenter:function(e){this._cLatLng||(this._cLatLng=e._cLatLng||e._latlng)},_resetBounds:function(){var e=this._bounds;e._southWest&&(e._southWest.lat=1/0,e._southWest.lng=1/0),e._northEast&&(e._northEast.lat=-1/0,e._northEast.lng=-1/0)},_recalculateBounds:function(){var e,t,i,n,r=this._markers,s=this._childClusters,o=0,a=0,h=this._childCount;if(0!==h){for(this._resetBounds(),e=0;e=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())},function(e){var i,n,r=e._childClusters;for(i=r.length-1;i>=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())})},_recursivelyAnimateChildrenInAndAddSelfToMap:function(e,t,i,n){this._recursively(e,n,t,function(r){r._recursivelyAnimateChildrenIn(e,r._group._map.latLngToLayerPoint(r.getLatLng()).round(),i),r._isSingleParent()&&i-1===n?(r.clusterShow(),r._recursivelyRemoveChildrenFromMap(e,t,i)):r.clusterHide(),r._addToMap()})},_recursivelyBecomeVisible:function(e,t){this._recursively(e,this._group._map.getMinZoom(),t,null,function(e){e.clusterShow()})},_recursivelyAddChildrenToMap:function(e,t,i){this._recursively(i,this._group._map.getMinZoom()-1,t,function(n){if(t!==n._zoom)for(var r=n._markers.length-1;r>=0;r--){var s=n._markers[r];i.contains(s._latlng)&&(e&&(s._backupLatlng=s.getLatLng(),s.setLatLng(e),s.clusterHide&&s.clusterHide()),n._group._featureGroup.addLayer(s))}},function(t){t._addToMap(e)})},_recursivelyRestoreChildPositions:function(e){for(var t=this._markers.length-1;t>=0;t--){var i=this._markers[t];i._backupLatlng&&(i.setLatLng(i._backupLatlng),delete i._backupLatlng)}if(e-1===this._zoom)for(var n=this._childClusters.length-1;n>=0;n--)this._childClusters[n]._restorePosition();else for(var r=this._childClusters.length-1;r>=0;r--)this._childClusters[r]._recursivelyRestoreChildPositions(e)},_restorePosition:function(){this._backupLatlng&&(this.setLatLng(this._backupLatlng),delete this._backupLatlng)},_recursivelyRemoveChildrenFromMap:function(e,t,i,n){var r,s;this._recursively(e,t-1,i-1,function(e){for(s=e._markers.length-1;s>=0;s--)r=e._markers[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())},function(e){for(s=e._childClusters.length-1;s>=0;s--)r=e._childClusters[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())})},_recursively:function(e,t,i,n,r){var s,o,a=this._childClusters,h=this._zoom;if(h>=t&&(n&&n(this),r&&h===i&&r(this)),t>h||i>h)for(s=a.length-1;s>=0;s--)o=a[s],o._boundsNeedUpdate&&o._recalculateBounds(),e.intersects(o._bounds)&&o._recursively(e,t,i,n,r)},_isSingleParent:function(){return this._childClusters.length>0&&this._childClusters[0]._childCount===this._childCount}});L.Marker.include({clusterHide:function(){var e=this.options.opacity;return this.setOpacity(0),this.options.opacity=e,this},clusterShow:function(){return this.setOpacity(this.options.opacity)}}),L.DistanceGrid=function(e){this._cellSize=e,this._sqCellSize=e*e,this._grid={},this._objectPoint={}},L.DistanceGrid.prototype={addObject:function(e,t){var i=this._getCoord(t.x),n=this._getCoord(t.y),r=this._grid,s=r[n]=r[n]||{},o=s[i]=s[i]||[],a=L.Util.stamp(e);this._objectPoint[a]=t,o.push(e)},updateObject:function(e,t){this.removeObject(e),this.addObject(e,t)},removeObject:function(e,t){var i,n,r=this._getCoord(t.x),s=this._getCoord(t.y),o=this._grid,a=o[s]=o[s]||{},h=a[r]=a[r]||[];for(delete this._objectPoint[L.Util.stamp(e)],i=0,n=h.length;n>i;i++)if(h[i]===e)return h.splice(i,1),1===n&&delete a[r],!0},eachObject:function(e,t){var i,n,r,s,o,a,h,l=this._grid;for(i in l){o=l[i];for(n in o)for(a=o[n],r=0,s=a.length;s>r;r++)h=e.call(t,a[r]),h&&(r--,s--)}},getNearObject:function(e){var t,i,n,r,s,o,a,h,l=this._getCoord(e.x),u=this._getCoord(e.y),_=this._objectPoint,d=this._sqCellSize,c=null;for(t=u-1;u+1>=t;t++)if(r=this._grid[t])for(i=l-1;l+1>=i;i++)if(s=r[i])for(n=0,o=s.length;o>n;n++)a=s[n],h=this._sqDist(_[L.Util.stamp(a)],e),(d>h||d>=h&&null===c)&&(d=h,c=a);return c},_getCoord:function(e){var t=Math.floor(e/this._cellSize);return isFinite(t)?t:e},_sqDist:function(e,t){var i=t.x-e.x,n=t.y-e.y;return i*i+n*n}},function(){L.QuickHull={getDistant:function(e,t){var i=t[1].lat-t[0].lat,n=t[0].lng-t[1].lng;return n*(e.lat-t[0].lat)+i*(e.lng-t[0].lng)},findMostDistantPointFromBaseLine:function(e,t){var i,n,r,s=0,o=null,a=[];for(i=t.length-1;i>=0;i--)n=t[i],r=this.getDistant(n,e),r>0&&(a.push(n),r>s&&(s=r,o=n));return{maxPoint:o,newPoints:a}},buildConvexHull:function(e,t){var i=[],n=this.findMostDistantPointFromBaseLine(e,t);return n.maxPoint?(i=i.concat(this.buildConvexHull([e[0],n.maxPoint],n.newPoints)),i=i.concat(this.buildConvexHull([n.maxPoint,e[1]],n.newPoints))):[e[0]]},getConvexHull:function(e){var t,i=!1,n=!1,r=!1,s=!1,o=null,a=null,h=null,l=null,u=null,_=null;for(t=e.length-1;t>=0;t--){var d=e[t];(i===!1||d.lat>i)&&(o=d,i=d.lat),(n===!1||d.latr)&&(h=d,r=d.lng),(s===!1||d.lng=0;t--)e=i[t].getLatLng(),n.push(e);return L.QuickHull.getConvexHull(n)}}),L.MarkerCluster.include({_2PI:2*Math.PI,_circleFootSeparation:25,_circleStartAngle:0,_spiralFootSeparation:28,_spiralLengthStart:11,_spiralLengthFactor:5,_circleSpiralSwitchover:9,spiderfy:function(){if(this._group._spiderfied!==this&&!this._group._inZoomAnimation){var e,t=this.getAllChildMarkers(null,!0),i=this._group,n=i._map,r=n.latLngToLayerPoint(this._latlng);this._group._unspiderfy(),this._group._spiderfied=this,t.length>=this._circleSpiralSwitchover?e=this._generatePointsSpiral(t.length,r):(r.y+=10,e=this._generatePointsCircle(t.length,r)),this._animationSpiderfy(t,e)}},unspiderfy:function(e){this._group._inZoomAnimation||(this._animationUnspiderfy(e),this._group._spiderfied=null)},_generatePointsCircle:function(e,t){var i,n,r=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+e),s=r/this._2PI,o=this._2PI/e,a=[];for(s=Math.max(s,35),a.length=e,i=0;e>i;i++)n=this._circleStartAngle+i*o,a[i]=new L.Point(t.x+s*Math.cos(n),t.y+s*Math.sin(n))._round();return a},_generatePointsSpiral:function(e,t){var i,n=this._group.options.spiderfyDistanceMultiplier,r=n*this._spiralLengthStart,s=n*this._spiralFootSeparation,o=n*this._spiralLengthFactor*this._2PI,a=0,h=[];for(h.length=e,i=e;i>=0;i--)e>i&&(h[i]=new L.Point(t.x+r*Math.cos(a),t.y+r*Math.sin(a))._round()),a+=s/r+5e-4*i,r+=o/a;return h},_noanimationUnspiderfy:function(){var e,t,i=this._group,n=i._map,r=i._featureGroup,s=this.getAllChildMarkers(null,!0);for(i._ignoreMove=!0,this.setOpacity(1),t=s.length-1;t>=0;t--)e=s[t],r.removeLayer(e),e._preSpiderfyLatlng&&(e.setLatLng(e._preSpiderfyLatlng),delete e._preSpiderfyLatlng),e.setZIndexOffset&&e.setZIndexOffset(0),e._spiderLeg&&(n.removeLayer(e._spiderLeg),delete e._spiderLeg);i.fire("unspiderfied",{cluster:this,markers:s}),i._ignoreMove=!1,i._spiderfied=null}}),L.MarkerClusterNonAnimated=L.MarkerCluster.extend({_animationSpiderfy:function(e,t){var i,n,r,s,o=this._group,a=o._map,h=o._featureGroup,l=this._group.options.spiderLegPolylineOptions;for(o._ignoreMove=!0,i=0;i=0;i--)a=u.layerPointToLatLng(t[i]),n=e[i],n._preSpiderfyLatlng=n._latlng,n.setLatLng(a),n.clusterShow&&n.clusterShow(),p&&(r=n._spiderLeg,s=r._path,s.style.strokeDashoffset=0,r.setStyle({opacity:m}));this.setOpacity(.3),l._ignoreMove=!1,setTimeout(function(){l._animationEnd(),l.fire("spiderfied",{cluster:h,markers:e})},200)},_animationUnspiderfy:function(e){var t,i,n,r,s,o,a=this,h=this._group,l=h._map,u=h._featureGroup,_=e?l._latLngToNewLayerPoint(this._latlng,e.zoom,e.center):l.latLngToLayerPoint(this._latlng),d=this.getAllChildMarkers(null,!0),c=L.Path.SVG;for(h._ignoreMove=!0,h._animationStart(),this.setOpacity(1),i=d.length-1;i>=0;i--)t=d[i],t._preSpiderfyLatlng&&(t.closePopup(),t.setLatLng(t._preSpiderfyLatlng),delete t._preSpiderfyLatlng,o=!0,t._setPos&&(t._setPos(_),o=!1),t.clusterHide&&(t.clusterHide(),o=!1),o&&u.removeLayer(t),c&&(n=t._spiderLeg,r=n._path,s=r.getTotalLength()+.1,r.style.strokeDashoffset=s,n.setStyle({opacity:0})));h._ignoreMove=!1,setTimeout(function(){var e=0;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&e++;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&(t.clusterShow&&t.clusterShow(),t.setZIndexOffset&&t.setZIndexOffset(0),e>1&&u.removeLayer(t),l.removeLayer(t._spiderLeg),delete t._spiderLeg);h._animationEnd(),h.fire("unspiderfied",{cluster:a,markers:d})},200)}}),L.MarkerClusterGroup.include({_spiderfied:null,unspiderfy:function(){this._unspiderfy.apply(this,arguments)},_spiderfierOnAdd:function(){this._map.on("click",this._unspiderfyWrapper,this),this._map.options.zoomAnimation&&this._map.on("zoomstart",this._unspiderfyZoomStart,this),this._map.on("zoomend",this._noanimationUnspiderfy,this),L.Browser.touch||this._map.getRenderer(this)},_spiderfierOnRemove:function(){this._map.off("click",this._unspiderfyWrapper,this),this._map.off("zoomstart",this._unspiderfyZoomStart,this),this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._map.off("zoomend",this._noanimationUnspiderfy,this),this._noanimationUnspiderfy() +},_unspiderfyZoomStart:function(){this._map&&this._map.on("zoomanim",this._unspiderfyZoomAnim,this)},_unspiderfyZoomAnim:function(e){L.DomUtil.hasClass(this._map._mapPane,"leaflet-touching")||(this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy(e))},_unspiderfyWrapper:function(){this._unspiderfy()},_unspiderfy:function(e){this._spiderfied&&this._spiderfied.unspiderfy(e)},_noanimationUnspiderfy:function(){this._spiderfied&&this._spiderfied._noanimationUnspiderfy()},_unspiderfyLayer:function(e){e._spiderLeg&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow(),e.setZIndexOffset&&e.setZIndexOffset(0),this._map.removeLayer(e._spiderLeg),delete e._spiderLeg)}}),L.MarkerClusterGroup.include({refreshClusters:function(e){return e?e instanceof L.MarkerClusterGroup?e=e._topClusterLevel.getAllChildMarkers():e instanceof L.LayerGroup?e=e._layers:e instanceof L.MarkerCluster?e=e.getAllChildMarkers():e instanceof L.Marker&&(e=[e]):e=this._topClusterLevel.getAllChildMarkers(),this._flagParentsIconsNeedUpdate(e),this._refreshClustersIcons(),this.options.singleMarkerMode&&this._refreshSingleMarkerModeMarkers(e),this},_flagParentsIconsNeedUpdate:function(e){var t,i;for(t in e)for(i=e[t].__parent;i;)i._iconNeedsUpdate=!0,i=i.__parent},_refreshSingleMarkerModeMarkers:function(e){var t,i;for(t in e)i=e[t],this.hasLayer(i)&&i.setIcon(this._overrideMarkerIcon(i))}}),L.Marker.include({refreshIconOptions:function(e,t){var i=this.options.icon;return L.setOptions(i,e),this.setIcon(i),t&&this.__parent&&this.__parent._group.refreshClusters(this),this}}),e.MarkerClusterGroup=t,e.MarkerCluster=i}); +//# sourceMappingURL=leaflet.markercluster.js.map \ No newline at end of file diff --git a/noise/public/javascripts/leaflet.markercluster.js.map b/noise/public/javascripts/leaflet.markercluster.js.map new file mode 100644 index 0000000..a4b459c --- /dev/null +++ b/noise/public/javascripts/leaflet.markercluster.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/MarkerClusterGroup.js","../src/MarkerCluster.js","../src/MarkerOpacity.js","../src/DistanceGrid.js","../src/MarkerCluster.QuickHull.js","../src/MarkerCluster.Spiderfier.js","../src/MarkerClusterGroup.Refresh.js"],"names":[],"mappings":"0PAIO,IAAI,GAAqB,EAAE,mBAAqB,EAAE,aAAa,QAErE,SACC,iBAAkB,GAClB,mBAAoB,KACpB,YAAa,EAAE,OAAO,UAAU,QAAQ,KAExC,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAElB,wBAAyB,KAIzB,4BAA4B,EAK5B,SAAS,EAIT,sBAAsB,EAGtB,2BAA4B,EAG5B,0BAA4B,OAAQ,IAAK,MAAO,OAAQ,QAAS,IAGjE,gBAAgB,EAChB,cAAe,IACf,WAAY,GACZ,cAAe,KAGf,mBAGD,WAAY,SAAU,GACrB,EAAE,KAAK,WAAW,KAAM,GACnB,KAAK,QAAQ,qBACjB,KAAK,QAAQ,mBAAqB,KAAK,4BAGxC,KAAK,cAAgB,EAAE,eACvB,KAAK,cAAc,eAAe,MAElC,KAAK,eAAiB,EAAE,eACxB,KAAK,eAAe,eAAe,MAEnC,KAAK,iBAAmB,EACxB,KAAK,oBACL,KAAK,kBAEL,KAAK,oBAAsB,KAE3B,KAAK,UAEL,KAAK,2BACJ,UAAa,KAAK,sBAClB,KAAQ,KAAK,kBACb,QAAW,KAAK,oBAIjB,IAAI,GAAU,EAAE,QAAQ,YAAc,KAAK,QAAQ,OACnD,GAAE,OAAO,KAAM,EAAU,KAAK,eAAiB,KAAK,cAEpD,KAAK,eAAiB,EAAU,EAAE,cAAgB,EAAE,0BAGrD,SAAU,SAAU,GAEnB,GAAI,YAAiB,GAAE,WACtB,MAAO,MAAK,WAAW,GAIxB,KAAK,EAAM,UAGV,MAFA,MAAK,eAAe,SAAS,GAC7B,KAAK,KAAK,YAAc,MAAO,IACxB,IAGR,KAAK,KAAK,KAGT,MAFA,MAAK,iBAAiB,KAAK,GAC3B,KAAK,KAAK,YAAc,MAAO,IACxB,IAGR,IAAI,KAAK,SAAS,GACjB,MAAO,KAMJ,MAAK,aACR,KAAK,cAGN,KAAK,UAAU,EAAO,KAAK,UAC3B,KAAK,KAAK,YAAc,MAAO,IAG/B,KAAK,iBAAiB,qBAEtB,KAAK,uBAGL,IAAI,GAAe,EACf,EAAc,KAAK,KACvB,IAAI,EAAM,SACT,KAAO,EAAa,SAAS,OAAS,GACrC,EAAe,EAAa,QAW9B,OAPI,MAAK,oBAAoB,SAAS,EAAa,eAC9C,KAAK,QAAQ,qBAChB,KAAK,mBAAmB,EAAO,GAE/B,KAAK,8BAA8B,EAAO,IAGrC,MAGR,YAAa,SAAU,GAEtB,MAAI,aAAiB,GAAE,WACf,KAAK,cAAc,IAItB,EAAM,UAMN,KAAK,KAQL,EAAM,UAIP,KAAK,cACR,KAAK,cACL,KAAK,iBAAiB,IAIvB,KAAK,aAAa,GAAO,GACzB,KAAK,KAAK,eAAiB,MAAO,IAGlC,KAAK,iBAAiB,qBAEtB,KAAK,wBAEL,EAAM,IAAI,KAAK,0BAA2B,MAEtC,KAAK,cAAc,SAAS,KAC/B,KAAK,cAAc,YAAY,GAC3B,EAAM,aACT,EAAM,eAID,MA1BC,OARF,KAAK,aAAa,KAAK,iBAAkB,IAAU,KAAK,SAAS,IACrE,KAAK,eAAe,MAAO,MAAO,EAAO,OAAQ,EAAM,UAExD,KAAK,KAAK,eAAiB,MAAO,IAC3B,OAVP,KAAK,eAAe,YAAY,GAChC,KAAK,KAAK,eAAiB,MAAO,IAC3B,OA0CT,UAAW,SAAU,EAAa,GACjC,IAAK,EAAE,KAAK,QAAQ,GACnB,MAAO,MAAK,SAAS,EAGtB,IAQI,GARA,EAAK,KAAK,cACV,EAAM,KAAK,eACX,EAAU,KAAK,QAAQ,eACvB,EAAgB,KAAK,QAAQ,cAC7B,EAAgB,KAAK,QAAQ,cAC7B,EAAI,EAAY,OAChB,EAAS,EACT,GAAgB,CAGpB,IAAI,KAAK,KAAM,CACd,GAAI,IAAU,GAAK,OAAQ,UACvB,EAAU,EAAE,KAAK,WAEpB,IADA,GAAI,IAAQ,GAAK,OAAQ,UACT,EAAT,EAAY,IAAU,CAC5B,GAAI,GAA4B,IAAjB,EAAS,IAAW,CAElC,GAAI,IAAU,GAAK,OAAQ,UAAY,CACvC,IAAI,EAAU,EACb,MAYF,GARA,EAAI,EAAY,GAQZ,YAAa,GAAE,WACd,IACH,EAAc,EAAY,QAC1B,GAAgB,GAEjB,KAAK,uBAAuB,EAAG,GAC/B,EAAI,EAAY,WAKjB,IAAK,EAAE,WAQP,IAAI,KAAK,SAAS,KAIlB,KAAK,UAAU,EAAG,KAAK,UAClB,GACJ,KAAK,KAAK,YAAc,MAAO,IAI5B,EAAE,UAC8B,IAA/B,EAAE,SAAS,iBAAuB,CACrC,GAAI,GAAU,EAAE,SAAS,qBACrB,EAAc,EAAQ,KAAO,EAAI,EAAQ,GAAK,EAAQ,EAC1D,GAAG,YAAY,QArBhB,GAAI,SAAS,GACR,GACJ,KAAK,KAAK,YAAc,MAAO,IAwB9B,GAEH,EAAc,EAAQ,GAAG,GAAK,OAAQ,UAAY,GAI/C,IAAW,GAGd,KAAK,iBAAiB,qBAEtB,KAAK,wBAEL,KAAK,iBAAiB,6BAA6B,KAAM,KAAK,MAAO,KAAK,sBAE1E,WAAW,EAAS,KAAK,QAAQ,aAEhC,KAEH,SAIA,KAFA,GAAI,GAAkB,KAAK,iBAEX,EAAT,EAAY,IAClB,EAAI,EAAY,GAGZ,YAAa,GAAE,YACd,IACH,EAAc,EAAY,QAC1B,GAAgB,GAEjB,KAAK,uBAAuB,EAAG,GAC/B,EAAI,EAAY,QAKZ,EAAE,UAKH,KAAK,SAAS,IAIlB,EAAgB,KAAK,GARpB,EAAI,SAAS,EAWhB,OAAO,OAIR,aAAc,SAAU,GACvB,GAAI,GAAG,EACH,EAAI,EAAY,OAChB,EAAK,KAAK,cACV,EAAM,KAAK,eACX,GAAgB,CAEpB,KAAK,KAAK,KAAM,CACf,IAAK,EAAI,EAAO,EAAJ,EAAO,IAClB,EAAI,EAAY,GAGZ,YAAa,GAAE,YACd,IACH,EAAc,EAAY,QAC1B,GAAgB,GAEjB,KAAK,uBAAuB,EAAG,GAC/B,EAAI,EAAY,SAIjB,KAAK,aAAa,KAAK,iBAAkB,GACzC,EAAI,YAAY,GACZ,KAAK,SAAS,IACjB,KAAK,eAAe,MAAO,MAAO,EAAG,OAAQ,EAAE,UAEhD,KAAK,KAAK,eAAiB,MAAO,IAEnC,OAAO,MAGR,GAAI,KAAK,YAAa,CACrB,KAAK,aAGL,IAAI,GAAe,EAAY,QAC3B,EAAK,CACT,KAAK,EAAI,EAAO,EAAJ,EAAQ,IACnB,EAAI,EAAa,GAGb,YAAa,GAAE,YAClB,KAAK,uBAAuB,EAAG,GAC/B,EAAK,EAAa,QAInB,KAAK,iBAAiB,GAIxB,IAAK,EAAI,EAAO,EAAJ,EAAO,IAClB,EAAI,EAAY,GAGZ,YAAa,GAAE,YACd,IACH,EAAc,EAAY,QAC1B,GAAgB,GAEjB,KAAK,uBAAuB,EAAG,GAC/B,EAAI,EAAY,QAIZ,EAAE,UAMP,KAAK,aAAa,GAAG,GAAM,GAC3B,KAAK,KAAK,eAAiB,MAAO,IAE9B,EAAG,SAAS,KACf,EAAG,YAAY,GACX,EAAE,aACL,EAAE,iBAXH,EAAI,YAAY,GAChB,KAAK,KAAK,eAAiB,MAAO,IAuBpC,OAPA,MAAK,iBAAiB,qBAEtB,KAAK,wBAGL,KAAK,iBAAiB,6BAA6B,KAAM,KAAK,MAAO,KAAK,qBAEnE,MAIR,YAAa,WA6BZ,MAzBK,MAAK,OACT,KAAK,oBACL,KAAK,wBACE,MAAK,oBACL,MAAK,kBAGT,KAAK,wBACR,KAAK,yBAIN,KAAK,cAAc,cACnB,KAAK,eAAe,cAEpB,KAAK,UAAU,SAAU,GACxB,EAAO,IAAI,KAAK,0BAA2B,YACpC,GAAO,UACZ,MAEC,KAAK,MAER,KAAK,2BAGC,MAIR,UAAW,WACV,GAAI,GAAS,GAAI,GAAE,YAEf,MAAK,kBACR,EAAO,OAAO,KAAK,iBAAiB,QAGrC,KAAK,GAAI,GAAI,KAAK,iBAAiB,OAAS,EAAG,GAAK,EAAG,IACtD,EAAO,OAAO,KAAK,iBAAiB,GAAG,YAKxC,OAFA,GAAO,OAAO,KAAK,eAAe,aAE3B,GAIR,UAAW,SAAU,EAAQ,GAC5B,GAEC,GAAmB,EAAG,EAFnB,EAAU,KAAK,iBAAiB,QACnC,EAAgB,KAAK,cAOtB,KAJI,KAAK,kBACR,KAAK,iBAAiB,mBAAmB,GAGrC,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IAAK,CAGzC,IAFA,GAAoB,EAEf,EAAI,EAAc,OAAS,EAAG,GAAK,EAAG,IAC1C,GAAI,EAAc,GAAG,QAAU,EAAQ,GAAI,CAC1C,GAAoB,CACpB,OAIE,GACH,EAAO,KAAK,EAAS,EAAQ,IAI/B,KAAK,eAAe,UAAU,EAAQ,IAIvC,UAAW,WACV,GAAI,KAIJ,OAHA,MAAK,UAAU,SAAU,GACxB,EAAO,KAAK,KAEN,GAIR,SAAU,SAAU,GACnB,GAAI,GAAS,IAUb,OARA,GAAK,SAAS,EAAI,IAElB,KAAK,UAAU,SAAU,GACpB,EAAE,MAAM,KAAO,IAClB,EAAS,KAIJ,GAIR,SAAU,SAAU,GACnB,IAAK,EACJ,OAAO,CAGR,IAAI,GAAG,EAAU,KAAK,gBAEtB,KAAK,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IACpC,GAAI,EAAQ,KAAO,EAClB,OAAO,CAKT,KADA,EAAU,KAAK,eACV,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IACpC,GAAI,EAAQ,GAAG,QAAU,EACxB,OAAO,CAIT,UAAU,EAAM,UAAY,EAAM,SAAS,SAAW,OAAS,KAAK,eAAe,SAAS,IAI7F,gBAAiB,SAAU,EAAO,GAET,kBAAb,KACV,EAAW,aAGZ,IAAI,GAAa,YACX,EAAM,QAAS,EAAM,SAAS,OAAW,KAAK,mBAClD,KAAK,KAAK,IAAI,UAAW,EAAY,MACrC,KAAK,IAAI,eAAgB,EAAY,MAEjC,EAAM,MACT,IACU,EAAM,SAAS,QACzB,KAAK,KAAK,aAAc,EAAU,MAClC,EAAM,SAAS,aAKd,GAAM,OAAS,KAAK,KAAK,YAAY,SAAS,EAAM,aAEvD,IACU,EAAM,SAAS,MAAQ,KAAK,MAAM,KAAK,KAAK,QAEtD,KAAK,KAAK,GAAG,UAAW,EAAY,MACpC,KAAK,KAAK,MAAM,EAAM,eAEtB,KAAK,KAAK,GAAG,UAAW,EAAY,MACpC,KAAK,GAAG,eAAgB,EAAY,MACpC,EAAM,SAAS,iBAKjB,MAAO,SAAU,GAChB,KAAK,KAAO,CACZ,IAAI,GAAG,EAAG,CAEV,KAAK,SAAS,KAAK,KAAK,cACvB,KAAM,8BAaP,KAVA,KAAK,cAAc,MAAM,GACzB,KAAK,eAAe,MAAM,GAErB,KAAK,eACT,KAAK,2BAGN,KAAK,QAAU,EAAI,QAAQ,IAAI,WAAW,aAGrC,EAAI,EAAG,EAAI,KAAK,eAAe,OAAY,EAAJ,EAAO,IAClD,EAAQ,KAAK,eAAe,GAC5B,EAAM,UAAY,EAAM,MAAM,QAC9B,EAAM,MAAM,QAAU,EAAM,MAG7B,KAAK,EAAI,EAAG,EAAI,KAAK,eAAe,OAAY,EAAJ,EAAO,IAClD,EAAQ,KAAK,eAAe,GAC5B,KAAK,aAAa,EAAM,OAAO,GAC/B,EAAM,MAAM,QAAU,EAAM,SAE7B,MAAK,kBAGL,KAAK,MAAQ,KAAK,MAAM,KAAK,KAAK,OAClC,KAAK,oBAAsB,KAAK,4BAEhC,KAAK,KAAK,GAAG,UAAW,KAAK,SAAU,MACvC,KAAK,KAAK,GAAG,UAAW,KAAK,SAAU,MAEnC,KAAK,kBACR,KAAK,mBAGN,KAAK,cAGL,EAAI,KAAK,iBACT,KAAK,oBACL,KAAK,UAAU,GAAG,IAInB,SAAU,SAAU,GACnB,EAAI,IAAI,UAAW,KAAK,SAAU,MAClC,EAAI,IAAI,UAAW,KAAK,SAAU,MAElC,KAAK,gBAGL,KAAK,KAAK,SAAS,UAAY,KAAK,KAAK,SAAS,UAAU,QAAQ,wBAAyB,IAEzF,KAAK,qBACR,KAAK,4BAGC,MAAK,QAGZ,KAAK,gBACL,KAAK,cAAc,SACnB,KAAK,eAAe,SAEpB,KAAK,cAAc,cAEnB,KAAK,KAAO,MAGb,iBAAkB,SAAU,GAE3B,IADA,GAAI,GAAU,EACP,IAAY,EAAQ,OAC1B,EAAU,EAAQ,QAEnB,OAAO,IAAW,MAInB,aAAc,SAAU,EAAS,GAChC,IAAK,GAAI,GAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IACxC,GAAI,EAAQ,KAAO,EAElB,MADA,GAAQ,OAAO,EAAG,IACX,GAWV,2BAA4B,SAAU,EAAQ,GAK7C,IAJA,GAAI,GAAM,KAAK,KACX,EAAkB,KAAK,iBAC1B,EAAU,KAAK,MAAM,KAAK,KAAK,cAEzB,GAAK,GACN,EAAgB,GAAG,aAAa,EAAQ,EAAI,QAAQ,EAAO,YAAa,IADzD,OAOtB,sBAAuB,SAAU,GAChC,EAAE,OAAO,YAAc,EAAE,OAAO,SAGjC,kBAAmB,SAAU,GAC5B,IAAK,KAAK,cAAgB,EAAE,OAAO,YAAa,CAC/C,GAAI,GAAc,EAAE,OAAO,QAAU,EAAE,OAAO,OAAO,QAErD,MAAK,WAAW,EAAE,OAAQ,EAAE,UAAW,EAAE,QAErC,GACH,EAAE,OAAO,cAKZ,WAAY,SAAU,EAAO,EAAM,GAClC,EAAM,QAAU,EAChB,KAAK,YAAY,GAEjB,EAAM,QAAU,EAChB,KAAK,SAAS,IAGf,oBAAqB,SAAU,GAC9B,GAAI,GAAY,EAAE,OAAO,kBAClB,GAAE,OAAO,YACZ,GACH,KAAK,WAAW,EAAE,OAAQ,EAAW,EAAE,OAAO,UAOhD,aAAc,SAAU,EAAQ,EAAwB,GACvD,GAAI,GAAe,KAAK,cACvB,EAAkB,KAAK,iBACvB,EAAK,KAAK,cACV,EAAM,KAAK,KACX,EAAU,KAAK,MAAM,KAAK,KAAK,aAG5B,IACH,KAAK,2BAA2B,EAAQ,KAAK,SAI9C,IAEC,GAFG,EAAU,EAAO,SACpB,EAAU,EAAQ,QAMnB,KAFA,KAAK,aAAa,EAAS,GAEpB,IACN,EAAQ,cACR,EAAQ,mBAAoB,IAExB,EAAQ,MAAQ,KAGT,GAA0B,EAAQ,aAAe,GAE3D,EAAc,EAAQ,SAAS,KAAO,EAAS,EAAQ,SAAS,GAAK,EAAQ,SAAS,GAGtF,EAAa,EAAQ,OAAO,aAAa,EAAS,EAAI,QAAQ,EAAQ,SAAU,EAAQ,QACxF,EAAgB,EAAQ,OAAO,UAAU,EAAa,EAAI,QAAQ,EAAY,YAAa,EAAQ,QAGnG,KAAK,aAAa,EAAQ,SAAS,eAAgB,GACnD,EAAQ,SAAS,SAAS,KAAK,GAC/B,EAAY,SAAW,EAAQ,SAE3B,EAAQ,QAEX,EAAG,YAAY,GACV,GACJ,EAAG,SAAS,KAId,EAAQ,kBAAmB,EAG5B,EAAU,EAAQ,eAGZ,GAAO,UAGf,cAAe,SAAU,EAAI,GAC5B,KAAO,GAAK,CACX,GAAI,IAAO,EACV,OAAO,CAER,GAAM,EAAI,WAEX,OAAO,GAIR,KAAM,SAAU,EAAM,EAAM,GAC3B,GAAI,GAAQ,EAAK,gBAAiB,GAAE,cAAe,CAElD,GAAI,EAAK,eAAiB,KAAK,cAAc,EAAK,MAAM,MAAO,EAAK,cAAc,eACjF,MAED,GAAO,UAAY,EAGpB,EAAE,aAAa,UAAU,KAAK,KAAK,KAAM,EAAM,EAAM,IAItD,QAAS,SAAU,EAAM,GACxB,MAAO,GAAE,aAAa,UAAU,QAAQ,KAAK,KAAM,EAAM,IAAc,EAAE,aAAa,UAAU,QAAQ,KAAK,KAAM,UAAY,EAAM,IAItI,2BAA4B,SAAU,GACrC,GAAI,GAAa,EAAQ,gBAErB,EAAI,kBASR,OAPC,IADgB,GAAb,EACE,QACkB,IAAb,EACL,SAEA,QAGC,GAAI,GAAE,SAAU,KAAM,cAAgB,EAAa,gBAAiB,UAAW,iBAAmB,EAAG,SAAU,GAAI,GAAE,MAAM,GAAI,OAGvI,YAAa,WACZ,GAAI,GAAM,KAAK,KACX,EAAoB,KAAK,QAAQ,kBACjC,EAAsB,KAAK,QAAQ,oBACnC,EAAsB,KAAK,QAAQ,qBAGnC,GAAqB,IACxB,KAAK,GAAG,eAAgB,KAAK,gBAAiB,MAI3C,IACH,KAAK,GAAG,mBAAoB,KAAK,cAAe,MAChD,KAAK,GAAG,kBAAmB,KAAK,cAAe,MAC/C,EAAI,GAAG,UAAW,KAAK,cAAe,QAIxC,gBAAiB,SAAU,GAI1B,IAHA,GAAI,GAAU,EAAE,MACZ,EAAgB,EAE2B,IAAxC,EAAc,eAAe,QACnC,EAAgB,EAAc,eAAe,EAG1C,GAAc,QAAU,KAAK,UAChC,EAAc,cAAgB,EAAQ,aACtC,KAAK,QAAQ,kBAGb,EAAQ,WACE,KAAK,QAAQ,qBACvB,EAAQ,eAIL,EAAE,eAA6C,KAA5B,EAAE,cAAc,SACtC,KAAK,KAAK,WAAW,SAIvB,cAAe,SAAU,GACxB,GAAI,GAAM,KAAK,IACX,MAAK,mBAGL,KAAK,eACR,EAAI,YAAY,KAAK,eAElB,EAAE,MAAM,gBAAkB,GAAK,EAAE,QAAU,KAAK,cACnD,KAAK,cAAgB,GAAI,GAAE,QAAQ,EAAE,MAAM,gBAAiB,KAAK,QAAQ,gBACzE,EAAI,SAAS,KAAK,kBAIpB,cAAe,WACV,KAAK,gBACR,KAAK,KAAK,YAAY,KAAK,eAC3B,KAAK,cAAgB,OAIvB,cAAe,WACd,GAAI,GAAoB,KAAK,QAAQ,kBACpC,EAAsB,KAAK,QAAQ,oBACnC,EAAsB,KAAK,QAAQ,oBACnC,EAAM,KAAK,MAER,GAAqB,IACxB,KAAK,IAAI,eAAgB,KAAK,gBAAiB,MAE5C,IACH,KAAK,IAAI,mBAAoB,KAAK,cAAe,MACjD,KAAK,IAAI,kBAAmB,KAAK,cAAe,MAChD,EAAI,IAAI,UAAW,KAAK,cAAe,QAIzC,SAAU,WACJ,KAAK,OAGV,KAAK,sBAEL,KAAK,MAAQ,KAAK,MAAM,KAAK,KAAK,OAClC,KAAK,oBAAsB,KAAK,8BAGjC,SAAU,WACT,IAAI,KAAK,iBAAT,CAIA,GAAI,GAAY,KAAK,2BAErB,MAAK,iBAAiB,kCAAkC,KAAK,oBAAqB,KAAK,MAAM,KAAK,KAAK,cAAe,KAAK,MAAO,GAClI,KAAK,iBAAiB,6BAA6B,KAAM,KAAK,MAAM,KAAK,KAAK,OAAQ,GAEtF,KAAK,oBAAsB,IAI5B,yBAA0B,WACzB,GAAI,GAAU,KAAK,KAAK,KAAK,KAAK,cACjC,EAAU,KAAK,MAAM,KAAK,KAAK,cAC/B,EAAS,KAAK,QAAQ,iBACtB,EAAW,CAKU,mBAAX,KACV,EAAW,WAAc,MAAO,KAGY,OAAzC,KAAK,QAAQ,0BAChB,EAAU,KAAK,QAAQ,wBAA0B,GAElD,KAAK,SAAW,EAChB,KAAK,iBACL,KAAK,mBAGL,KAAK,GAAI,GAAO,EAAS,GAAQ,EAAS,IACzC,KAAK,cAAc,GAAQ,GAAI,GAAE,aAAa,EAAS,IACvD,KAAK,iBAAiB,GAAQ,GAAI,GAAE,aAAa,EAAS,GAI3D,MAAK,iBAAmB,GAAI,MAAK,eAAe,KAAM,EAAU,IAIjE,UAAW,SAAU,EAAO,GAC3B,GAGI,GAAa,EAHb,EAAe,KAAK,cACpB,EAAkB,KAAK,iBAC1B,EAAU,KAAK,MAAM,KAAK,KAAK,aAUhC,KAPI,KAAK,QAAQ,kBAChB,KAAK,oBAAoB,GAG1B,EAAM,GAAG,KAAK,0BAA2B,MAGlC,GAAQ,EAAS,IAAQ,CAC/B,EAAc,KAAK,KAAK,QAAQ,EAAM,YAAa,EAGnD,IAAI,GAAU,EAAa,GAAM,cAAc,EAC/C,IAAI,EAGH,MAFA,GAAQ,UAAU,GAClB,EAAM,SAAW,EACjB,MAKD,IADA,EAAU,EAAgB,GAAM,cAAc,GACjC,CACZ,GAAI,GAAS,EAAQ,QACjB,IACH,KAAK,aAAa,GAAS,EAK5B,IAAI,GAAa,GAAI,MAAK,eAAe,KAAM,EAAM,EAAS,EAC9D,GAAa,GAAM,UAAU,EAAY,KAAK,KAAK,QAAQ,EAAW,SAAU,IAChF,EAAQ,SAAW,EACnB,EAAM,SAAW,CAGjB,IAAI,GAAa,CACjB,KAAK,EAAI,EAAO,EAAG,EAAI,EAAO,MAAO,IACpC,EAAa,GAAI,MAAK,eAAe,KAAM,EAAG,GAC9C,EAAa,GAAG,UAAU,EAAY,KAAK,KAAK,QAAQ,EAAQ,YAAa,GAO9E,OALA,GAAO,UAAU,GAGjB,KAAK,2BAA2B,EAAS,GAEzC,OAID,EAAgB,GAAM,UAAU,EAAO,GAIxC,KAAK,iBAAiB,UAAU,GAChC,EAAM,SAAW,KAAK,kBASvB,sBAAuB,WACtB,KAAK,cAAc,UAAU,SAAU,GAClC,YAAa,GAAE,eAAiB,EAAE,kBACrC,EAAE,iBAML,SAAU,SAAU,GACnB,KAAK,OAAO,KAAK,GACZ,KAAK,gBACT,KAAK,cAAgB,WAAW,EAAE,KAAK,KAAK,cAAe,MAAO,OAGpE,cAAe,WACd,IAAK,GAAI,GAAI,EAAG,EAAI,KAAK,OAAO,OAAQ,IACvC,KAAK,OAAO,GAAG,KAAK,KAErB,MAAK,OAAO,OAAS,EACrB,aAAa,KAAK,eAClB,KAAK,cAAgB,MAItB,oBAAqB,WACpB,GAAI,GAAU,KAAK,MAAM,KAAK,KAAK,MAGnC,MAAK,gBAED,KAAK,MAAQ,GAAW,KAAK,oBAAoB,WAAW,KAAK,8BACpE,KAAK,kBAEL,KAAK,iBAAiB,kCAAkC,KAAK,oBAAqB,KAAK,MAAM,KAAK,KAAK,cAAe,KAAK,MAAO,KAAK,6BAEvI,KAAK,iBAAiB,KAAK,MAAO,IAExB,KAAK,MAAQ,GACvB,KAAK,kBAEL,KAAK,kBAAkB,KAAK,MAAO,IAEnC,KAAK,YAKP,0BAA2B,WAC1B,MAAK,MAAK,QAAQ,2BAEP,EAAE,QAAQ,OACb,KAAK,mBAAmB,KAAK,KAAK,aAGnC,KAAK,mBAAmB,KAAK,KAAK,YAAY,IAAI,IALjD,KAAK,oBAkBd,mBAAoB,SAAU,GAC7B,GAAI,GAAS,KAAK,OAWlB,OATe,UAAX,IACC,EAAO,YAAc,IACxB,EAAO,WAAW,IAAM,KAErB,EAAO,aAAe,IACzB,EAAO,WAAW,KAAO,MAIpB,GAIR,8BAA+B,SAAU,EAAO,GAC/C,GAAI,IAAe,EAClB,KAAK,cAAc,SAAS,OACtB,IAA+B,IAA3B,EAAW,YAAmB,CACxC,EAAW,WAEX,IAAI,GAAU,EAAW,oBACzB,MAAK,cAAc,YAAY,EAAQ,IACvC,KAAK,cAAc,YAAY,EAAQ,QAEvC,GAAW,eAWb,uBAAwB,SAAU,EAAO,GACxC,GAEI,GAFA,EAAS,EAAM,YACf,EAAI,CAKR,KAFA,EAAS,MAEF,EAAI,EAAO,OAAQ,IACzB,EAAQ,EAAO,GAEX,YAAiB,GAAE,WACtB,KAAK,uBAAuB,EAAO,GAIpC,EAAO,KAAK,EAGb,OAAO,IASR,oBAAqB,SAAU,GAC9B,GAAI,GAAO,EAAM,QAAQ,KAAO,KAAK,QAAQ,oBAC5C,cAAe,WACd,MAAO,IAER,mBAAoB,WACnB,OAAQ,KAIV,OAAO,KAKT,GAAE,mBAAmB,SACpB,mBAAoB,GAAI,GAAE,aAAa,GAAI,GAAE,QAAQ,KAAW,KAAW,GAAI,GAAE,OAAO,IAAU,QAGnG,EAAE,mBAAmB,SACpB,cAEC,gBAAiB,aAGjB,iBAAkB,SAAU,EAAmB,GAC9C,KAAK,iBAAiB,kCAAkC,KAAK,oBAAqB,KAAK,MAAM,KAAK,KAAK,cAAe,GACtH,KAAK,iBAAiB,6BAA6B,KAAM,EAAc,KAAK,6BAG5E,KAAK,KAAK,iBAEX,kBAAmB,SAAU,EAAmB,GAC/C,KAAK,iBAAiB,kCAAkC,KAAK,oBAAqB,KAAK,MAAM,KAAK,KAAK,cAAe,GACtH,KAAK,iBAAiB,6BAA6B,KAAM,EAAc,KAAK,6BAG5E,KAAK,KAAK,iBAEX,mBAAoB,SAAU,EAAO,GACpC,KAAK,8BAA8B,EAAO,KAI5C,gBAEC,gBAAiB,WAChB,KAAK,KAAK,SAAS,WAAa,wBAChC,KAAK,oBAGN,iBAAkB,SAAU,EAAmB,GAC9C,GAGI,GAHA,EAAS,KAAK,4BACd,EAAK,KAAK,cACb,EAAU,KAAK,MAAM,KAAK,KAAK,aAGhC,MAAK,aAAc,EAGnB,KAAK,iBAAiB,aAAa,EAAQ,EAAmB,EAAS,SAAU,GAChF,GAEI,GAFA,EAAW,EAAE,QACb,EAAW,EAAE,QAkBjB,KAfK,EAAO,SAAS,KACpB,EAAW,MAGR,EAAE,mBAAqB,EAAoB,IAAM,GACpD,EAAG,YAAY,GACf,EAAE,6BAA6B,KAAM,EAAc,KAGnD,EAAE,cACF,EAAE,6BAA6B,EAAU,EAAc,IAKnD,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IACpC,EAAI,EAAQ,GACP,EAAO,SAAS,EAAE,UACtB,EAAG,YAAY,KAMlB,KAAK,eAGL,KAAK,iBAAiB,0BAA0B,EAAQ,GAExD,EAAG,UAAU,SAAU,GAChB,YAAa,GAAE,gBAAkB,EAAE,OACxC,EAAE,gBAKJ,KAAK,iBAAiB,aAAa,EAAQ,EAAmB,EAAc,SAAU,GACrF,EAAE,kCAAkC,KAGrC,KAAK,aAAc,EAGnB,KAAK,SAAS,WAEb,KAAK,iBAAiB,aAAa,EAAQ,EAAmB,EAAS,SAAU,GAChF,EAAG,YAAY,GACf,EAAE,gBAGH,KAAK,mBAIP,kBAAmB,SAAU,EAAmB,GAC/C,KAAK,wBAAwB,KAAK,iBAAkB,EAAoB,EAAG,GAG3E,KAAK,iBAAiB,6BAA6B,KAAM,EAAc,KAAK,6BAE5E,KAAK,iBAAiB,kCAAkC,KAAK,oBAAqB,KAAK,MAAM,KAAK,KAAK,cAAe,EAAmB,KAAK,8BAG/I,mBAAoB,SAAU,EAAO,GACpC,GAAI,GAAK,KACL,EAAK,KAAK,aAEd,GAAG,SAAS,GACR,IAAe,IACd,EAAW,YAAc,GAE5B,EAAW,cACX,KAAK,eACL,KAAK,kBAEL,EAAM,QAAQ,KAAK,KAAK,mBAAmB,EAAW,cACtD,EAAM,cAEN,KAAK,SAAS,WACb,EAAG,YAAY,GACf,EAAM,cAEN,EAAG,oBAIJ,KAAK,eAEL,EAAG,kBACH,EAAG,wBAAwB,EAAY,KAAK,KAAK,aAAc,KAAK,WAOxE,wBAAyB,SAAU,EAAS,EAAmB,GAC9D,GAAI,GAAS,KAAK,4BACjB,EAAU,KAAK,MAAM,KAAK,KAAK,aAGhC,GAAQ,6CAA6C,EAAQ,EAAS,EAAoB,EAAG,EAE7F,IAAI,GAAK,IAGT,MAAK,eACL,EAAQ,0BAA0B,EAAQ,GAI1C,KAAK,SAAS,WAGb,GAA4B,IAAxB,EAAQ,YAAmB,CAC9B,GAAI,GAAI,EAAQ,SAAS,EAEzB,MAAK,aAAc,EACnB,EAAE,UAAU,EAAE,aACd,KAAK,aAAc,EACf,EAAE,aACL,EAAE,kBAGH,GAAQ,aAAa,EAAQ,EAAc,EAAS,SAAU,GAC7D,EAAE,kCAAkC,EAAQ,EAAS,EAAoB,IAG3E,GAAG,mBAIL,cAAe,WACV,KAAK,OACR,KAAK,KAAK,SAAS,UAAY,KAAK,KAAK,SAAS,UAAU,QAAQ,wBAAyB,KAE9F,KAAK,mBACL,KAAK,KAAK,iBAKX,aAAc,WAIb,EAAE,KAAK,QAAQ,SAAS,KAAK,gBAI/B,EAAE,mBAAqB,SAAU,GAChC,MAAO,IAAI,GAAE,mBAAmB,GC51C1B,IAAI,GAAgB,EAAE,cAAgB,EAAE,OAAO,QACrD,QAAS,EAAE,KAAK,UAAU,QAE1B,WAAY,SAAU,EAAO,EAAM,EAAG,GAErC,EAAE,OAAO,UAAU,WAAW,KAAK,KAAM,EAAK,EAAE,UAAY,EAAE,YAAe,GAAI,GAAE,OAAO,EAAG,IACjF,KAAM,KAAM,KAAM,EAAM,QAAQ,cAE5C,KAAK,OAAS,EACd,KAAK,MAAQ,EAEb,KAAK,YACL,KAAK,kBACL,KAAK,YAAc,EACnB,KAAK,kBAAmB,EACxB,KAAK,mBAAoB,EAEzB,KAAK,QAAU,GAAI,GAAE,aAEjB,GACH,KAAK,UAAU,GAEZ,GACH,KAAK,UAAU,IAKjB,mBAAoB,SAAU,EAAc,GAC3C,EAAe,KAEf,KAAK,GAAI,GAAI,KAAK,eAAe,OAAS,EAAG,GAAK,EAAG,IACpD,KAAK,eAAe,GAAG,mBAAmB,EAG3C,KAAK,GAAI,GAAI,KAAK,SAAS,OAAS,EAAG,GAAK,EAAG,IAC1C,GAAuB,KAAK,SAAS,GAAG,aAG5C,EAAa,KAAK,KAAK,SAAS,GAGjC,OAAO,IAIR,cAAe,WACd,MAAO,MAAK,aAIb,aAAc,SAAU,GASvB,IARA,GAKC,GALG,EAAgB,KAAK,eAAe,QACvC,EAAM,KAAK,OAAO,KAClB,EAAa,EAAI,cAAc,KAAK,SACpC,EAAO,KAAK,MAAQ,EACpB,EAAU,EAAI,UAIR,EAAc,OAAS,GAAK,EAAa,GAAM,CACrD,GACA,IAAI,KACJ,KAAK,EAAI,EAAG,EAAI,EAAc,OAAQ,IACrC,EAAc,EAAY,OAAO,EAAc,GAAG,eAEnD,GAAgB,EAGb,EAAa,EAChB,KAAK,OAAO,KAAK,QAAQ,KAAK,QAAS,GACf,GAAd,EACV,KAAK,OAAO,KAAK,QAAQ,KAAK,QAAS,EAAU,GAEjD,KAAK,OAAO,KAAK,UAAU,KAAK,QAAS,IAI3C,UAAW,WACV,GAAI,GAAS,GAAI,GAAE,YAEnB,OADA,GAAO,OAAO,KAAK,SACZ,GAGR,YAAa,WACZ,KAAK,kBAAmB,EACpB,KAAK,OACR,KAAK,QAAQ,OAKf,WAAY,WAKX,MAJI,MAAK,mBACR,KAAK,SAAW,KAAK,OAAO,QAAQ,mBAAmB,MACvD,KAAK,kBAAmB,GAElB,KAAK,SAAS,cAEtB,aAAc,WACb,MAAO,MAAK,SAAS,gBAItB,UAAW,SAAU,EAAM,GAE1B,KAAK,kBAAmB,EAExB,KAAK,mBAAoB,EACzB,KAAK,kBAAkB,GAEnB,YAAgB,GAAE,eAChB,IACJ,KAAK,eAAe,KAAK,GACzB,EAAK,SAAW,MAEjB,KAAK,aAAe,EAAK,cAEpB,GACJ,KAAK,SAAS,KAAK,GAEpB,KAAK,eAGF,KAAK,UACR,KAAK,SAAS,UAAU,GAAM,IAShC,kBAAmB,SAAU,GACvB,KAAK,WAET,KAAK,SAAW,EAAM,UAAY,EAAM,UAU1C,aAAc,WACb,GAAI,GAAS,KAAK,OAEd,GAAO,aACV,EAAO,WAAW,IAAM,IACxB,EAAO,WAAW,IAAM,KAErB,EAAO,aACV,EAAO,WAAW,KAAO,IACzB,EAAO,WAAW,KAAO,MAI3B,mBAAoB,WACnB,GAKI,GAAG,EAAO,EAAa,EALvB,EAAU,KAAK,SACf,EAAgB,KAAK,eACrB,EAAS,EACT,EAAS,EACT,EAAa,KAAK,WAItB,IAAmB,IAAf,EAAJ,CAQA,IAHA,KAAK,eAGA,EAAI,EAAG,EAAI,EAAQ,OAAQ,IAC/B,EAAc,EAAQ,GAAG,QAEzB,KAAK,QAAQ,OAAO,GAEpB,GAAU,EAAY,IACtB,GAAU,EAAY,GAIvB,KAAK,EAAI,EAAG,EAAI,EAAc,OAAQ,IACrC,EAAQ,EAAc,GAGlB,EAAM,mBACT,EAAM,qBAGP,KAAK,QAAQ,OAAO,EAAM,SAE1B,EAAc,EAAM,SACpB,EAAa,EAAM,YAEnB,GAAU,EAAY,IAAM,EAC5B,GAAU,EAAY,IAAM,CAG7B,MAAK,QAAU,KAAK,SAAW,GAAI,GAAE,OAAO,EAAS,EAAY,EAAS,GAG1E,KAAK,mBAAoB,IAI1B,UAAW,SAAU,GAChB,IACH,KAAK,cAAgB,KAAK,QAC1B,KAAK,UAAU,IAEhB,KAAK,OAAO,cAAc,SAAS,OAGpC,8BAA+B,SAAU,EAAQ,EAAQ,GACxD,KAAK,aAAa,EAAQ,KAAK,OAAO,KAAK,aAAc,EAAU,EAClE,SAAU,GACT,GACC,GAAG,EADA,EAAU,EAAE,QAEhB,KAAK,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IACpC,EAAI,EAAQ,GAGR,EAAE,QACL,EAAE,QAAQ,GACV,EAAE,gBAIL,SAAU,GACT,GACC,GAAG,EADA,EAAgB,EAAE,cAEtB,KAAK,EAAI,EAAc,OAAS,EAAG,GAAK,EAAG,IAC1C,EAAK,EAAc,GACf,EAAG,QACN,EAAG,QAAQ,GACX,EAAG,kBAOR,6CAA8C,SAAU,EAAQ,EAAY,EAAmB,GAC9F,KAAK,aAAa,EAAQ,EAAc,EACvC,SAAU,GACT,EAAE,8BAA8B,EAAQ,EAAE,OAAO,KAAK,mBAAmB,EAAE,aAAa,QAAS,GAI7F,EAAE,mBAAqB,EAAoB,IAAM,GACpD,EAAE,cACF,EAAE,kCAAkC,EAAQ,EAAY,IAExD,EAAE,cAGH,EAAE,eAKL,0BAA2B,SAAU,EAAQ,GAC5C,KAAK,aAAa,EAAQ,KAAK,OAAO,KAAK,aAAc,EAAW,KAAM,SAAU,GACnF,EAAE,iBAIJ,6BAA8B,SAAU,EAAU,EAAW,GAC5D,KAAK,aAAa,EAAQ,KAAK,OAAO,KAAK,aAAe,EAAG,EAC5D,SAAU,GACT,GAAI,IAAc,EAAE,MAKpB,IAAK,GAAI,GAAI,EAAE,SAAS,OAAS,EAAG,GAAK,EAAG,IAAK,CAChD,GAAI,GAAK,EAAE,SAAS,EAEf,GAAO,SAAS,EAAG,WAIpB,IACH,EAAG,cAAgB,EAAG,YAEtB,EAAG,UAAU,GACT,EAAG,aACN,EAAG,eAIL,EAAE,OAAO,cAAc,SAAS,MAGlC,SAAU,GACT,EAAE,UAAU,MAKf,kCAAmC,SAAU,GAE5C,IAAK,GAAI,GAAI,KAAK,SAAS,OAAS,EAAG,GAAK,EAAG,IAAK,CACnD,GAAI,GAAK,KAAK,SAAS,EACnB,GAAG,gBACN,EAAG,UAAU,EAAG,qBACT,GAAG,eAIZ,GAAI,EAAY,IAAM,KAAK,MAE1B,IAAK,GAAI,GAAI,KAAK,eAAe,OAAS,EAAG,GAAK,EAAG,IACpD,KAAK,eAAe,GAAG,uBAGxB,KAAK,GAAI,GAAI,KAAK,eAAe,OAAS,EAAG,GAAK,EAAG,IACpD,KAAK,eAAe,GAAG,kCAAkC,IAK5D,iBAAkB,WACb,KAAK,gBACR,KAAK,UAAU,KAAK,qBACb,MAAK,gBAKd,kCAAmC,SAAU,EAAgB,EAAY,EAAW,GACnF,GAAI,GAAG,CACP,MAAK,aAAa,EAAgB,EAAa,EAAG,EAAY,EAC7D,SAAU,GAET,IAAK,EAAI,EAAE,SAAS,OAAS,EAAG,GAAK,EAAG,IACvC,EAAI,EAAE,SAAS,GACV,GAAiB,EAAa,SAAS,EAAE,WAC7C,EAAE,OAAO,cAAc,YAAY,GAC/B,EAAE,aACL,EAAE,gBAKN,SAAU,GAET,IAAK,EAAI,EAAE,eAAe,OAAS,EAAG,GAAK,EAAG,IAC7C,EAAI,EAAE,eAAe,GAChB,GAAiB,EAAa,SAAS,EAAE,WAC7C,EAAE,OAAO,cAAc,YAAY,GAC/B,EAAE,aACL,EAAE,kBAcR,aAAc,SAAU,EAAiB,EAAkB,EAAiB,EAAiB,GAC5F,GAEI,GAAG,EAFH,EAAgB,KAAK,eACrB,EAAO,KAAK,KAYhB,IATwB,GAApB,IACC,GACH,EAAgB,MAEb,GAAoB,IAAS,GAChC,EAAiB,OAIR,EAAP,GAAkC,EAAP,EAC9B,IAAK,EAAI,EAAc,OAAS,EAAG,GAAK,EAAG,IAC1C,EAAI,EAAc,GACd,EAAE,mBACL,EAAE,qBAEC,EAAgB,WAAW,EAAE,UAChC,EAAE,aAAa,EAAiB,EAAkB,EAAiB,EAAiB,IAOxF,gBAAiB,WAEhB,MAAO,MAAK,eAAe,OAAS,GAAK,KAAK,eAAe,GAAG,cAAgB,KAAK,cC1YvF,GAAE,OAAO,SACR,YAAa,WACZ,GAAI,GAAS,KAAK,QAAQ,OAG1B,OAFA,MAAK,WAAW,GAChB,KAAK,QAAQ,QAAU,EAChB,MAGR,YAAa,WACZ,MAAO,MAAK,WAAW,KAAK,QAAQ,YChBtC,EAAE,aAAe,SAAU,GAC1B,KAAK,UAAY,EACjB,KAAK,YAAc,EAAW,EAC9B,KAAK,SACL,KAAK,iBAGN,EAAE,aAAa,WAEd,UAAW,SAAU,EAAK,GACzB,GAAI,GAAI,KAAK,UAAU,EAAM,GACzB,EAAI,KAAK,UAAU,EAAM,GACzB,EAAO,KAAK,MACZ,EAAM,EAAK,GAAK,EAAK,OACrB,EAAO,EAAI,GAAK,EAAI,OACpB,EAAQ,EAAE,KAAK,MAAM,EAEzB,MAAK,aAAa,GAAS,EAE3B,EAAK,KAAK,IAGX,aAAc,SAAU,EAAK,GAC5B,KAAK,aAAa,GAClB,KAAK,UAAU,EAAK,IAIrB,aAAc,SAAU,EAAK,GAC5B,GAKI,GAAG,EALH,EAAI,KAAK,UAAU,EAAM,GACzB,EAAI,KAAK,UAAU,EAAM,GACzB,EAAO,KAAK,MACZ,EAAM,EAAK,GAAK,EAAK,OACrB,EAAO,EAAI,GAAK,EAAI,MAKxB,WAFO,MAAK,aAAa,EAAE,KAAK,MAAM,IAEjC,EAAI,EAAG,EAAM,EAAK,OAAY,EAAJ,EAAS,IACvC,GAAI,EAAK,KAAO,EAQf,MANA,GAAK,OAAO,EAAG,GAEH,IAAR,SACI,GAAI,IAGL,GAMV,WAAY,SAAU,EAAI,GACzB,GAAI,GAAG,EAAG,EAAG,EAAK,EAAK,EAAM,EACzB,EAAO,KAAK,KAEhB,KAAK,IAAK,GAAM,CACf,EAAM,EAAK,EAEX,KAAK,IAAK,GAGT,IAFA,EAAO,EAAI,GAEN,EAAI,EAAG,EAAM,EAAK,OAAY,EAAJ,EAAS,IACvC,EAAU,EAAG,KAAK,EAAS,EAAK,IAC5B,IACH,IACA,OAOL,cAAe,SAAU,GACxB,GAEI,GAAG,EAAG,EAAG,EAAK,EAAM,EAAK,EAAK,EAF9B,EAAI,KAAK,UAAU,EAAM,GACzB,EAAI,KAAK,UAAU,EAAM,GAEzB,EAAc,KAAK,aACnB,EAAgB,KAAK,YACrB,EAAU,IAEd,KAAK,EAAI,EAAI,EAAQ,EAAI,GAAT,EAAY,IAE3B,GADA,EAAM,KAAK,MAAM,GAGhB,IAAK,EAAI,EAAI,EAAQ,EAAI,GAAT,EAAY,IAE3B,GADA,EAAO,EAAI,GAGV,IAAK,EAAI,EAAG,EAAM,EAAK,OAAY,EAAJ,EAAS,IACvC,EAAM,EAAK,GACX,EAAO,KAAK,QAAQ,EAAY,EAAE,KAAK,MAAM,IAAO,IACzC,EAAP,GACK,GAAR,GAAqC,OAAZ,KACzB,EAAgB,EAChB,EAAU,EAOhB,OAAO,IAGR,UAAW,SAAU,GACpB,GAAI,GAAQ,KAAK,MAAM,EAAI,KAAK,UAChC,OAAO,UAAS,GAAS,EAAQ,GAGlC,QAAS,SAAU,EAAG,GACrB,GAAI,GAAK,EAAG,EAAI,EAAE,EACd,EAAK,EAAG,EAAI,EAAE,CAClB,OAAO,GAAK,EAAK,EAAK,ICzFvB,WACA,EAAE,WAQD,WAAY,SAAU,EAAK,GAC1B,GAAI,GAAK,EAAG,GAAG,IAAM,EAAG,GAAG,IAC1B,EAAK,EAAG,GAAG,IAAM,EAAG,GAAG,GACxB,OAAQ,IAAM,EAAI,IAAM,EAAG,GAAG,KAAO,GAAM,EAAI,IAAM,EAAG,GAAG,MAU5D,iCAAkC,SAAU,EAAU,GACrD,GAGC,GAAG,EAAI,EAHJ,EAAO,EACV,EAAQ,KACR,IAGD,KAAK,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IACpC,EAAK,EAAQ,GACb,EAAI,KAAK,WAAW,EAAI,GAEpB,EAAI,IACP,EAAU,KAAK,GAKZ,EAAI,IACP,EAAO,EACP,EAAQ,GAIV,QAAS,SAAU,EAAO,UAAW,IAWtC,gBAAiB,SAAU,EAAU,GACpC,GAAI,MACH,EAAI,KAAK,iCAAiC,EAAU,EAErD,OAAI,GAAE,UACL,EACC,EAAoB,OACnB,KAAK,iBAAiB,EAAS,GAAI,EAAE,UAAW,EAAE,YAEpD,EACC,EAAoB,OACnB,KAAK,iBAAiB,EAAE,SAAU,EAAS,IAAK,EAAE,cAI5C,EAAS,KAWnB,cAAe,SAAU,GAExB,GAKC,GALG,GAAS,EAAO,GAAS,EAC5B,GAAS,EAAO,GAAS,EACzB,EAAW,KAAM,EAAW,KAC5B,EAAW,KAAM,EAAW,KAC5B,EAAQ,KAAM,EAAQ,IAGvB,KAAK,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IAAK,CACzC,GAAI,GAAK,EAAQ,IACb,KAAW,GAAS,EAAG,IAAM,KAChC,EAAW,EACX,EAAS,EAAG,MAET,KAAW,GAAS,EAAG,IAAM,KAChC,EAAW,EACX,EAAS,EAAG,MAET,KAAW,GAAS,EAAG,IAAM,KAChC,EAAW,EACX,EAAS,EAAG,MAET,KAAW,GAAS,EAAG,IAAM,KAChC,EAAW,EACX,EAAS,EAAG,KAIV,IAAW,GACd,EAAQ,EACR,EAAQ,IAER,EAAQ,EACR,EAAQ,EAGT,IAAI,MAAQ,OAAO,KAAK,iBAAiB,EAAO,GAAQ,GACnD,KAAK,iBAAiB,EAAO,GAAQ,GAC1C,OAAO,QAKV,EAAE,cAAc,SACf,cAAe,WACd,GAEC,GAAG,EAFA,EAAe,KAAK,qBACvB,IAGD,KAAK,EAAI,EAAa,OAAS,EAAG,GAAK,EAAG,IACzC,EAAI,EAAa,GAAG,YACpB,EAAO,KAAK,EAGb,OAAO,GAAE,UAAU,cAAc,MC/JnC,EAAE,cAAc,SAEf,KAAgB,EAAV,KAAK,GACX,sBAAuB,GACvB,kBAAmB,EAEnB,sBAAwB,GACxB,mBAAoB,GACpB,oBAAqB,EAErB,wBAAyB,EAGzB,SAAU,WACT,GAAI,KAAK,OAAO,cAAgB,OAAQ,KAAK,OAAO,iBAApD,CAIA,GAIC,GAJG,EAAe,KAAK,mBAAmB,MAAM,GAChD,EAAQ,KAAK,OACb,EAAM,EAAM,KACZ,EAAS,EAAI,mBAAmB,KAAK,QAGtC,MAAK,OAAO,cACZ,KAAK,OAAO,YAAc,KAItB,EAAa,QAAU,KAAK,wBAC/B,EAAY,KAAK,sBAAsB,EAAa,OAAQ,IAE5D,EAAO,GAAK,GACZ,EAAY,KAAK,sBAAsB,EAAa,OAAQ,IAG7D,KAAK,mBAAmB,EAAc,KAGvC,WAAY,SAAU,GAEjB,KAAK,OAAO,mBAGhB,KAAK,qBAAqB,GAE1B,KAAK,OAAO,YAAc,OAG3B,sBAAuB,SAAU,EAAO,GACvC,GAIC,GAAG,EAJA,EAAgB,KAAK,OAAO,QAAQ,2BAA6B,KAAK,uBAAyB,EAAI,GACtG,EAAY,EAAgB,KAAK,KACjC,EAAY,KAAK,KAAO,EACxB,IAOD,KAJA,EAAY,KAAK,IAAI,EAAW,IAEhC,EAAI,OAAS,EAER,EAAI,EAAO,EAAJ,EAAW,IACtB,EAAQ,KAAK,kBAAoB,EAAI,EACrC,EAAI,GAAK,GAAI,GAAE,MAAM,EAAS,EAAI,EAAY,KAAK,IAAI,GAAQ,EAAS,EAAI,EAAY,KAAK,IAAI,IAAQ,QAG1G,OAAO,IAGR,sBAAuB,SAAU,EAAO,GACvC,GAMC,GANG,EAA6B,KAAK,OAAO,QAAQ,2BACpD,EAAY,EAA6B,KAAK,mBAC9C,EAAa,EAA6B,KAAK,sBAC/C,EAAe,EAA6B,KAAK,oBAAsB,KAAK,KAC5E,EAAQ,EACR,IAMD,KAHA,EAAI,OAAS,EAGR,EAAI,EAAO,GAAK,EAAG,IAGf,EAAJ,IACH,EAAI,GAAK,GAAI,GAAE,MAAM,EAAS,EAAI,EAAY,KAAK,IAAI,GAAQ,EAAS,EAAI,EAAY,KAAK,IAAI,IAAQ,UAE1G,GAAS,EAAa,EAAgB,KAAJ,EAClC,GAAa,EAAe,CAE7B,OAAO,IAGR,uBAAwB,WACvB,GAIC,GAAG,EAJA,EAAQ,KAAK,OAChB,EAAM,EAAM,KACZ,EAAK,EAAM,cACX,EAAe,KAAK,mBAAmB,MAAM,EAM9C,KAHA,EAAM,aAAc,EAEpB,KAAK,WAAW,GACX,EAAI,EAAa,OAAS,EAAG,GAAK,EAAG,IACzC,EAAI,EAAa,GAEjB,EAAG,YAAY,GAEX,EAAE,qBACL,EAAE,UAAU,EAAE,0BACP,GAAE,oBAEN,EAAE,iBACL,EAAE,gBAAgB,GAGf,EAAE,aACL,EAAI,YAAY,EAAE,kBACX,GAAE,WAIX,GAAM,KAAK,gBACV,QAAS,KACT,QAAS,IAEV,EAAM,aAAc,EACpB,EAAM,YAAc,QAKtB,EAAE,yBAA2B,EAAE,cAAc,QAC5C,mBAAoB,SAAU,EAAc,GAC3C,GAIC,GAAG,EAAG,EAAK,EAJR,EAAQ,KAAK,OAChB,EAAM,EAAM,KACZ,EAAK,EAAM,cACX,EAAa,KAAK,OAAO,QAAQ,wBAOlC,KAJA,EAAM,aAAc,EAIf,EAAI,EAAG,EAAI,EAAa,OAAQ,IACpC,EAAS,EAAI,mBAAmB,EAAU,IAC1C,EAAI,EAAa,GAGjB,EAAM,GAAI,GAAE,UAAU,KAAK,QAAS,GAAS,GAC7C,EAAI,SAAS,GACb,EAAE,WAAa,EAGf,EAAE,mBAAqB,EAAE,QACzB,EAAE,UAAU,GACR,EAAE,iBACL,EAAE,gBAAgB,KAGnB,EAAG,SAAS,EAEb,MAAK,WAAW,IAEhB,EAAM,aAAc,EACpB,EAAM,KAAK,cACV,QAAS,KACT,QAAS,KAIX,qBAAsB,WACrB,KAAK,4BAKP,EAAE,cAAc,SAEf,mBAAoB,SAAU,EAAc,GAC3C,GASC,GAAG,EAAG,EAAK,EAAS,EAAW,EAT5B,EAAK,KACR,EAAQ,KAAK,OACb,EAAM,EAAM,KACZ,EAAK,EAAM,cACX,EAAkB,KAAK,QACvB,EAAe,EAAI,mBAAmB,GACtC,EAAM,EAAE,KAAK,IACb,EAAa,EAAE,UAAW,KAAK,OAAO,QAAQ,0BAC9C,EAAkB,EAAW,OAuB9B,KApBwB,SAApB,IACH,EAAkB,EAAE,mBAAmB,UAAU,QAAQ,yBAAyB,SAG/E,GAEH,EAAW,QAAU,EAGrB,EAAW,WAAa,EAAW,WAAa,IAAM,+BAGtD,EAAW,QAAU,EAGtB,EAAM,aAAc,EAKf,EAAI,EAAG,EAAI,EAAa,OAAQ,IACpC,EAAI,EAAa,GAEjB,EAAS,EAAI,mBAAmB,EAAU,IAG1C,EAAM,GAAI,GAAE,UAAU,EAAiB,GAAS,GAChD,EAAI,SAAS,GACb,EAAE,WAAa,EAIX,IACH,EAAU,EAAI,MACd,EAAY,EAAQ,iBAAmB,GACvC,EAAQ,MAAM,gBAAkB,EAChC,EAAQ,MAAM,iBAAmB,GAI9B,EAAE,iBACL,EAAE,gBAAgB,KAEf,EAAE,aACL,EAAE,cAIH,EAAG,SAAS,GAER,EAAE,SACL,EAAE,QAAQ,EAQZ,KAJA,EAAM,eACN,EAAM,kBAGD,EAAI,EAAa,OAAS,EAAG,GAAK,EAAG,IACzC,EAAS,EAAI,mBAAmB,EAAU,IAC1C,EAAI,EAAa,GAGjB,EAAE,mBAAqB,EAAE,QACzB,EAAE,UAAU,GAER,EAAE,aACL,EAAE,cAIC,IACH,EAAM,EAAE,WACR,EAAU,EAAI,MACd,EAAQ,MAAM,iBAAmB,EAEjC,EAAI,UAAU,QAAS,IAGzB,MAAK,WAAW,IAEhB,EAAM,aAAc,EAEpB,WAAW,WACV,EAAM,gBACN,EAAM,KAAK,cACV,QAAS,EACT,QAAS,KAER,MAGJ,qBAAsB,SAAU,GAC/B,GAOC,GAAG,EAAG,EAAK,EAAS,EAAW,EAP5B,EAAK,KACR,EAAQ,KAAK,OACb,EAAM,EAAM,KACZ,EAAK,EAAM,cACX,EAAe,EAAc,EAAI,uBAAuB,KAAK,QAAS,EAAY,KAAM,EAAY,QAAU,EAAI,mBAAmB,KAAK,SAC1I,EAAe,KAAK,mBAAmB,MAAM,GAC7C,EAAM,EAAE,KAAK,GAQd,KALA,EAAM,aAAc,EACpB,EAAM,kBAGN,KAAK,WAAW,GACX,EAAI,EAAa,OAAS,EAAG,GAAK,EAAG,IACzC,EAAI,EAAa,GAGZ,EAAE,qBAKP,EAAE,aAGF,EAAE,UAAU,EAAE,0BACP,GAAE,mBAGT,GAAgB,EACZ,EAAE,UACL,EAAE,QAAQ,GACV,GAAgB,GAEb,EAAE,cACL,EAAE,cACF,GAAgB,GAEb,GACH,EAAG,YAAY,GAIZ,IACH,EAAM,EAAE,WACR,EAAU,EAAI,MACd,EAAY,EAAQ,iBAAmB,GACvC,EAAQ,MAAM,iBAAmB,EACjC,EAAI,UAAU,QAAS,KAIzB,GAAM,aAAc,EAEpB,WAAW,WAEV,GAAI,GAAuB,CAC3B,KAAK,EAAI,EAAa,OAAS,EAAG,GAAK,EAAG,IACzC,EAAI,EAAa,GACb,EAAE,YACL,GAKF,KAAK,EAAI,EAAa,OAAS,EAAG,GAAK,EAAG,IACzC,EAAI,EAAa,GAEZ,EAAE,aAIH,EAAE,aACL,EAAE,cAEC,EAAE,iBACL,EAAE,gBAAgB,GAGf,EAAuB,GAC1B,EAAG,YAAY,GAGhB,EAAI,YAAY,EAAE,kBACX,GAAE,WAEV,GAAM,gBACN,EAAM,KAAK,gBACV,QAAS,EACT,QAAS,KAER,QAKL,EAAE,mBAAmB,SAEpB,YAAa,KAEb,WAAY,WACX,KAAK,YAAY,MAAM,KAAM,YAG9B,iBAAkB,WACjB,KAAK,KAAK,GAAG,QAAS,KAAK,mBAAoB,MAE3C,KAAK,KAAK,QAAQ,eACrB,KAAK,KAAK,GAAG,YAAa,KAAK,qBAAsB,MAGtD,KAAK,KAAK,GAAG,UAAW,KAAK,uBAAwB,MAEhD,EAAE,QAAQ,OACd,KAAK,KAAK,YAAY,OAOxB,oBAAqB,WACpB,KAAK,KAAK,IAAI,QAAS,KAAK,mBAAoB,MAChD,KAAK,KAAK,IAAI,YAAa,KAAK,qBAAsB,MACtD,KAAK,KAAK,IAAI,WAAY,KAAK,oBAAqB,MACpD,KAAK,KAAK,IAAI,UAAW,KAAK,uBAAwB,MAItD,KAAK;EAKN,qBAAsB,WAChB,KAAK,MAIV,KAAK,KAAK,GAAG,WAAY,KAAK,oBAAqB,OAGpD,oBAAqB,SAAU,GAE1B,EAAE,QAAQ,SAAS,KAAK,KAAK,SAAU,sBAI3C,KAAK,KAAK,IAAI,WAAY,KAAK,oBAAqB,MACpD,KAAK,YAAY,KAGlB,mBAAoB,WAEnB,KAAK,eAGN,YAAa,SAAU,GAClB,KAAK,aACR,KAAK,YAAY,WAAW,IAI9B,uBAAwB,WACnB,KAAK,aACR,KAAK,YAAY,0BAKnB,iBAAkB,SAAU,GACvB,EAAM,aACT,KAAK,cAAc,YAAY,GAE3B,EAAM,aACT,EAAM,cAGH,EAAM,iBACT,EAAM,gBAAgB,GAGvB,KAAK,KAAK,YAAY,EAAM,kBACrB,GAAM,eC/chB,EAAE,mBAAmB,SASpB,gBAAiB,SAAU,GAoB1B,MAnBK,GAEM,YAAkB,GAAE,mBAC9B,EAAS,EAAO,iBAAiB,qBACvB,YAAkB,GAAE,WAC9B,EAAS,EAAO,QACN,YAAkB,GAAE,cAC9B,EAAS,EAAO,qBACN,YAAkB,GAAE,SAC9B,GAAU,IARV,EAAS,KAAK,iBAAiB,qBAUhC,KAAK,4BAA4B,GACjC,KAAK,wBAGD,KAAK,QAAQ,kBAChB,KAAK,gCAAgC,GAG/B,MAQR,4BAA6B,SAAU,GACtC,GAAI,GAAI,CAGR,KAAK,IAAM,GAOV,IADA,EAAS,EAAO,GAAI,SACb,GACN,EAAO,kBAAmB,EAC1B,EAAS,EAAO,UAWnB,gCAAiC,SAAU,GAC1C,GAAI,GAAI,CAER,KAAK,IAAM,GACV,EAAQ,EAAO,GAGX,KAAK,SAAS,IAEjB,EAAM,QAAQ,KAAK,oBAAoB,OAM3C,EAAE,OAAO,SAQR,mBAAoB,SAAU,EAAS,GACtC,GAAI,GAAO,KAAK,QAAQ,IAcxB,OAZA,GAAE,WAAW,EAAM,GAEnB,KAAK,QAAQ,GAMT,GAA2B,KAAK,UACnC,KAAK,SAAS,OAAO,gBAAgB,MAG/B","file":"dist/leaflet.markercluster.js"} \ No newline at end of file diff --git a/noise/public/javascripts/logit.js b/noise/public/javascripts/logit.js new file mode 100644 index 0000000..2453df0 --- /dev/null +++ b/noise/public/javascripts/logit.js @@ -0,0 +1,12 @@ +import { DateTime } from './luxon.min.js' + +export function logit(str) { + let s = `${DateTime.now().toISO()} => ${str}`; + console.log(s); +} + +export function logerror(str) { + let s = `${DateTime.utc().toISO()} => *** ERROR *** ${str}`; + console.log(s); +} + diff --git a/noise/public/javascripts/luxon.min.js b/noise/public/javascripts/luxon.min.js new file mode 100644 index 0000000..30ced14 --- /dev/null +++ b/noise/public/javascripts/luxon.min.js @@ -0,0 +1 @@ +class e extends Error{}class z extends e{constructor(e){super("Invalid DateTime: "+e.toMessage())}}class q extends e{constructor(e){super("Invalid Interval: "+e.toMessage())}}class A extends e{constructor(e){super("Invalid Duration: "+e.toMessage())}}class j extends e{}class _ extends e{constructor(e){super("Invalid unit "+e)}}class o extends e{}class r extends e{constructor(){super("Zone is an abstract class")}}var t="numeric",n="short",s="long";const U={year:t,month:t,day:t},$={year:t,month:n,day:t},H={year:t,month:n,day:t,weekday:n},W={year:t,month:s,day:t},R={year:t,month:s,day:t,weekday:s},J={hour:t,minute:t},Y={hour:t,minute:t,second:t},P={hour:t,minute:t,second:t,timeZoneName:n},G={hour:t,minute:t,second:t,timeZoneName:s},B={hour:t,minute:t,hourCycle:"h23"},Q={hour:t,minute:t,second:t,hourCycle:"h23"},K={hour:t,minute:t,second:t,hourCycle:"h23",timeZoneName:n},X={hour:t,minute:t,second:t,hourCycle:"h23",timeZoneName:s},ee={year:t,month:t,day:t,hour:t,minute:t},te={year:t,month:t,day:t,hour:t,minute:t,second:t},re={year:t,month:n,day:t,hour:t,minute:t},ne={year:t,month:n,day:t,hour:t,minute:t,second:t},se={year:t,month:n,day:t,weekday:n,hour:t,minute:t},ie={year:t,month:s,day:t,hour:t,minute:t,timeZoneName:n},ae={year:t,month:s,day:t,hour:t,minute:t,second:t,timeZoneName:n},oe={year:t,month:s,day:t,weekday:s,hour:t,minute:t,timeZoneName:s},ue={year:t,month:s,day:t,weekday:s,hour:t,minute:t,second:t,timeZoneName:s};function O(e){return void 0===e}function c(e){return"number"==typeof e}function le(e){return"number"==typeof e&&e%1==0}function ce(e){return"[object Date]"===Object.prototype.toString.call(e)}function he(){try{return"undefined"!=typeof Intl&&!!Intl.RelativeTimeFormat}catch(e){return!1}}function de(e){return Array.isArray(e)?e:[e]}function me(e,r,n){if(0!==e.length)return e.reduce((e,t)=>{t=[r(t),t];return e&&n(e[0],t[0])===e[0]?e:t},null)[1]}function h(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function u(e,t,r){return le(e)&&t<=e&&e<=r}function l(e,t=2){let r;return r=e<0?"-"+(""+-e).padStart(t,"0"):(""+e).padStart(t,"0")}function d(e){if(!O(e)&&null!==e&&""!==e)return parseInt(e,10)}function m(e){if(!O(e)&&null!==e&&""!==e)return parseFloat(e)}function fe(e){if(!O(e)&&null!==e&&""!==e)return e=1e3*parseFloat("0."+e),Math.floor(e)}function ye(e,t,r=!1){const n=10**t,s=r?Math.trunc:Math.round;return s(e*n)/n}function ge(e){return e%4==0&&(e%100!=0||e%400==0)}function we(e){return ge(e)?366:365}function ve(e,t){var r,n=(n=t-1)-(r=12)*Math.floor(n/r)+1;return 2==n?ge(e+(t-n)/12)?29:28:[31,null,31,30,31,30,31,31,30,31,30,31][n-1]}function pe(e){let t=Date.UTC(e.year,e.month-1,e.day,e.hour,e.minute,e.second,e.millisecond);return e.year<100&&0<=e.year&&(t=new Date(t)).setUTCFullYear(t.getUTCFullYear()-1900),+t}function Te(e){var t=(e+Math.floor(e/4)-Math.floor(e/100)+Math.floor(e/400))%7,e=e-1,e=(e+Math.floor(e/4)-Math.floor(e/100)+Math.floor(e/400))%7;return 4==t||3==e?53:52}function Se(e){return 99"timezonename"===e.type.toLowerCase());return n?n.value:null}function be(e,t){let r=parseInt(e,10);Number.isNaN(r)&&(r=0);e=parseInt(t,10)||0,t=r<0||Object.is(r,-0)?-e:e;return 60*r+t}function ke(e){var t=Number(e);if("boolean"==typeof e||""===e||Number.isNaN(t))throw new o("Invalid unit value "+e);return t}function Me(e,t){const r={};for(const s in e){var n;!h(e,s)||null!=(n=e[s])&&(r[t(s)]=ke(n))}return r}function Ne(e,t){var r=Math.trunc(Math.abs(e/60)),n=Math.trunc(Math.abs(e%60)),s=0<=e?"+":"-";switch(t){case"short":return s+l(r,2)+":"+l(n,2);case"narrow":return s+r+(0(e[t]=r[t],e),{});var r}n=/[A-Za-z_+-]{1,256}(?::?\/[A-Za-z0-9_+-]{1,256}(?:\/[A-Za-z0-9_+-]{1,256})?)?/;const Ee=["January","February","March","April","May","June","July","August","September","October","November","December"],Ve=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],Ie=["J","F","M","A","M","J","J","A","S","O","N","D"];function xe(e){switch(e){case"narrow":return[...Ie];case"short":return[...Ve];case"long":return[...Ee];case"numeric":return["1","2","3","4","5","6","7","8","9","10","11","12"];case"2-digit":return["01","02","03","04","05","06","07","08","09","10","11","12"];default:return null}}const Ce=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],Fe=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],Ze=["M","T","W","T","F","S","S"];function Le(e){switch(e){case"narrow":return[...Ze];case"short":return[...Fe];case"long":return[...Ce];case"numeric":return["1","2","3","4","5","6","7"];default:return null}}const ze=["AM","PM"],qe=["Before Christ","Anno Domini"],Ae=["BC","AD"],je=["B","A"];function _e(e){switch(e){case"narrow":return[...je];case"short":return[...Ae];case"long":return[...qe];default:return null}}function Ue(e){return ze[e.hour<12?0:1]}function $e(e,t){return Le(t)[e.weekday-1]}function He(e,t){return xe(t)[e.month-1]}function We(e,t){return _e(t)[e.year<0?0:1]}function Re(e,t,r="always",n=!1){var s={years:["year","yr."],quarters:["quarter","qtr."],months:["month","mo."],weeks:["week","wk."],days:["day","day","days"],hours:["hour","hr."],minutes:["minute","min."],seconds:["second","sec."]},i=-1===["hours","minutes","seconds"].indexOf(e);if("auto"===r&&i){var a="days"===e;switch(t){case 1:return a?"tomorrow":"next "+s[e][0];case-1:return a?"yesterday":"last "+s[e][0];case 0:return a?"today":"this "+s[e][0]}}var r=Object.is(t,-0)||t<0,i=Math.abs(t),t=1===i,o=s[e],n=n?!t&&o[2]||o[1]:t?s[e][0]:e;return r?i+` ${n} ago`:`in ${i} `+n}function Je(e,t){let r="";for(const n of e)n.literal?r+=n.val:r+=t(n.val);return r}const Ye={D:U,DD:$,DDD:W,DDDD:R,t:J,tt:Y,ttt:P,tttt:G,T:B,TT:Q,TTT:K,TTTT:X,f:ee,ff:re,fff:ie,ffff:oe,F:te,FF:ne,FFF:ae,FFFF:ue};class f{static create(e,t={}){return new f(e,t)}static parseFormat(t){let r=null,n="",s=!1;const i=[];for(let e=0;ethis.loc.extract(r,e,t),i=e=>r.isOffsetFixed&&0===r.offset&&e.allowZ?"Z":r.isValid?r.zone.formatOffset(r.ts,e.format):"",a=()=>n?Ue(r):s({hour:"numeric",hourCycle:"h12"},"dayperiod"),o=(e,t)=>n?He(r,e):s(t?{month:e}:{month:e,day:"numeric"},"month"),u=(e,t)=>n?$e(r,e):s(t?{weekday:e}:{weekday:e,month:"long",day:"numeric"},"weekday"),l=e=>{var t=f.macroTokenToFormatOpts(e);return t?this.formatWithSystemDefault(r,t):e},c=e=>n?We(r,e):s({era:e},"era");return Je(f.parseFormat(e),e=>{switch(e){case"S":return this.num(r.millisecond);case"u":case"SSS":return this.num(r.millisecond,3);case"s":return this.num(r.second);case"ss":return this.num(r.second,2);case"uu":return this.num(Math.floor(r.millisecond/10),2);case"uuu":return this.num(Math.floor(r.millisecond/100));case"m":return this.num(r.minute);case"mm":return this.num(r.minute,2);case"h":return this.num(r.hour%12==0?12:r.hour%12);case"hh":return this.num(r.hour%12==0?12:r.hour%12,2);case"H":return this.num(r.hour);case"HH":return this.num(r.hour,2);case"Z":return i({format:"narrow",allowZ:this.opts.allowZ});case"ZZ":return i({format:"short",allowZ:this.opts.allowZ});case"ZZZ":return i({format:"techie",allowZ:this.opts.allowZ});case"ZZZZ":return r.zone.offsetName(r.ts,{format:"short",locale:this.loc.locale});case"ZZZZZ":return r.zone.offsetName(r.ts,{format:"long",locale:this.loc.locale});case"z":return r.zoneName;case"a":return a();case"d":return t?s({day:"numeric"},"day"):this.num(r.day);case"dd":return t?s({day:"2-digit"},"day"):this.num(r.day,2);case"c":return this.num(r.weekday);case"ccc":return u("short",!0);case"cccc":return u("long",!0);case"ccccc":return u("narrow",!0);case"E":return this.num(r.weekday);case"EEE":return u("short",!1);case"EEEE":return u("long",!1);case"EEEEE":return u("narrow",!1);case"L":return t?s({month:"numeric",day:"numeric"},"month"):this.num(r.month);case"LL":return t?s({month:"2-digit",day:"numeric"},"month"):this.num(r.month,2);case"LLL":return o("short",!0);case"LLLL":return o("long",!0);case"LLLLL":return o("narrow",!0);case"M":return t?s({month:"numeric"},"month"):this.num(r.month);case"MM":return t?s({month:"2-digit"},"month"):this.num(r.month,2);case"MMM":return o("short",!1);case"MMMM":return o("long",!1);case"MMMMM":return o("narrow",!1);case"y":return t?s({year:"numeric"},"year"):this.num(r.year);case"yy":return t?s({year:"2-digit"},"year"):this.num(r.year.toString().slice(-2),2);case"yyyy":return t?s({year:"numeric"},"year"):this.num(r.year,4);case"yyyyyy":return t?s({year:"numeric"},"year"):this.num(r.year,6);case"G":return c("short");case"GG":return c("long");case"GGGGG":return c("narrow");case"kk":return this.num(r.weekYear.toString().slice(-2),2);case"kkkk":return this.num(r.weekYear,4);case"W":return this.num(r.weekNumber);case"WW":return this.num(r.weekNumber,2);case"o":return this.num(r.ordinal);case"ooo":return this.num(r.ordinal,3);case"q":return this.num(r.quarter);case"qq":return this.num(r.quarter,2);case"X":return this.num(Math.floor(r.ts/1e3));case"x":return this.num(r.ts);default:return l(e)}})}formatDurationFromString(e,t){const r=e=>{switch(e[0]){case"S":return"millisecond";case"s":return"second";case"m":return"minute";case"h":return"hour";case"d":return"day";case"w":return"week";case"M":return"month";case"y":return"year";default:return null}},n=f.parseFormat(t),s=n.reduce((e,{literal:t,val:r})=>t?e:e.concat(r),[]),i=e.shiftTo(...s.map(r).filter(e=>e));return Je(n,(a=i,e=>{var t=r(e);return t?this.num(a.get(t),e.length):e}));var a}}class y{constructor(e,t){this.reason=e,this.explanation=t}toMessage(){return this.explanation?this.reason+": "+this.explanation:this.reason}}class i{get type(){throw new r}get name(){throw new r}get ianaName(){return this.name}get isUniversal(){throw new r}offsetName(e,t){throw new r}formatOffset(e,t){throw new r}offset(e){throw new r}equals(e){throw new r}get isValid(){throw new r}}let Pe=null;class Ge extends i{static get instance(){return Pe=null===Pe?new Ge:Pe}get type(){return"system"}get name(){return(new Intl.DateTimeFormat).resolvedOptions().timeZone}get isUniversal(){return!1}offsetName(e,{format:t,locale:r}){return Oe(e,t,r)}formatOffset(e,t){return Ne(this.offset(e),t)}offset(e){return-new Date(e).getTimezoneOffset()}equals(e){return"system"===e.type}get isValid(){return!0}}let Be={};function Qe(e){return Be[e]||(Be[e]=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:e,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",era:"short"})),Be[e]}const Ke={year:0,month:1,day:2,era:3,hour:4,minute:5,second:6};function Xe(e,t){var e=e.format(t).replace(/\u200E/g,""),[,t,e,r,n,s,i,a]=/(\d+)\/(\d+)\/(\d+) (AD|BC),? (\d+):(\d+):(\d+)/.exec(e);return[r,t,e,n,s,i,a]}function et(e,t){var r=e.formatToParts(t);const n=[];for(let e=0;eDate.now(),it="system",at=null,ot=null,ut=null,lt;class k{static get now(){return st}static set now(e){st=e}static set defaultZone(e){it=e}static get defaultZone(){return b(it,Ge.instance)}static get defaultLocale(){return at}static set defaultLocale(e){at=e}static get defaultNumberingSystem(){return ot}static set defaultNumberingSystem(e){ot=e}static get defaultOutputCalendar(){return ut}static set defaultOutputCalendar(e){ut=e}static get throwOnInvalid(){return lt}static set throwOnInvalid(e){lt=e}static resetCaches(){M.resetCache(),w.resetCache()}}let ct={};function ht(e,t={}){var r=JSON.stringify([e,t]);let n=ct[r];return n||(n=new Intl.ListFormat(e,t),ct[r]=n),n}let dt={};function mt(e,t={}){var r=JSON.stringify([e,t]);let n=dt[r];return n||(n=new Intl.DateTimeFormat(e,t),dt[r]=n),n}let ft={};function yt(e,t={}){var r=JSON.stringify([e,t]);let n=ft[r];return n||(n=new Intl.NumberFormat(e,t),ft[r]=n),n}let gt={};function wt(e,t={}){const{base:r,...n}=t;var s=JSON.stringify([e,n]);let i=gt[s];return i||(i=new Intl.RelativeTimeFormat(e,t),gt[s]=i),i}let vt=null;function pt(){return vt=vt||(new Intl.DateTimeFormat).resolvedOptions().locale}function Tt(e){var r=e.indexOf("-u-");if(-1===r)return[e];{let t;r=e.substring(0,r);try{t=mt(e).resolvedOptions()}catch(e){t=mt(r).resolvedOptions()}var{numberingSystem:e,calendar:n}=t;return[r,e,n]}}function St(e,t,r){return(r||t)&&(e+="-u",r&&(e+="-ca-"+r),t&&(e+="-nu-"+t)),e}function Ot(t){const r=[];for(let e=1;e<=12;e++){var n=L.utc(2016,e,1);r.push(t(n))}return r}function bt(t){const r=[];for(let e=1;e<=7;e++){var n=L.utc(2016,11,13+e);r.push(t(n))}return r}function kt(e,t,r,n,s){e=e.listingMode(r);return"error"===e?null:("en"===e?n:s)(t)}function Mt(e){return(!e.numberingSystem||"latn"===e.numberingSystem)&&("latn"===e.numberingSystem||!e.locale||e.locale.startsWith("en")||"latn"===new Intl.DateTimeFormat(e.intl).resolvedOptions().numberingSystem)}class Nt{constructor(e,t,r){this.padTo=r.padTo||0,this.floor=r.floor||!1;const{padTo:n,floor:s,...i}=r;if(!t||0{const t=n?{month:r,day:"numeric"}:{month:r},e=n?"format":"standalone";return this.monthsCache[e][r]||(this.monthsCache[e][r]=Ot(e=>this.extract(e,t,"month"))),this.monthsCache[e][r]})}weekdays(r,n=!1,e=!0){return kt(this,r,e,Le,()=>{const t=n?{weekday:r,year:"numeric",month:"long",day:"numeric"}:{weekday:r},e=n?"format":"standalone";return this.weekdaysCache[e][r]||(this.weekdaysCache[e][r]=bt(e=>this.extract(e,t,"weekday"))),this.weekdaysCache[e][r]})}meridiems(e=!0){return kt(this,void 0,e,()=>ze,()=>{if(!this.meridiemCache){const t={hour:"numeric",hourCycle:"h12"};this.meridiemCache=[L.utc(2016,11,13,9),L.utc(2016,11,13,19)].map(e=>this.extract(e,t,"dayperiod"))}return this.meridiemCache})}eras(e,t=!0){return kt(this,e,t,_e,()=>{const t={era:e};return this.eraCache[e]||(this.eraCache[e]=[L.utc(-40,1,1),L.utc(2017,1,1)].map(e=>this.extract(e,t,"era"))),this.eraCache[e]})}extract(e,t,r){const n=this.dtFormatter(e,t),s=n.formatToParts(),i=s.find(e=>e.type.toLowerCase()===r);return i?i.value:null}numberFormatter(e={}){return new Nt(this.intl,e.forceSimple||this.fastNumbers,e)}dtFormatter(e,t={}){return new Dt(e,this.intl,t)}relFormatter(e={}){return new Et(this.intl,this.isEnglish(),e)}listFormatter(e={}){return ht(this.intl,e)}isEnglish(){return"en"===this.locale||"dev-us"===this.locale.toLowerCase()||new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("dev-us")}equals(e){return this.locale===e.locale&&this.numberingSystem===e.numberingSystem&&this.outputCalendar===e.outputCalendar}}function a(...e){e=e.reduce((e,t)=>e+t.source,"");return RegExp(`^${e}$`)}function g(...e){return i=>e.reduce(([e,t,r],n)=>{var[n,r,s]=n(i,r);return[{...e,...n},r||t,s]},[{},null,1]).slice(0,2)}function p(e,...t){if(null!=e)for(var[r,n]of t){r=r.exec(e);if(r)return n(r)}return[null,null]}function Vt(...s){return(e,t)=>{const r={};let n;for(n=0;nvoid 0!==e&&(t||e&&l)?-e:e;return[{years:c(m(t)),months:c(m(r)),weeks:c(m(n)),days:c(m(s)),hours:c(m(i)),minutes:c(m(a)),seconds:c(m(o),"-0"===o),milliseconds:c(fe(u),e)}]}const jt={GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function _t(e,t,r,n,s,i,a){const o={year:2===t.length?Se(d(t)):d(t),month:Ve.indexOf(r)+1,day:d(n),hour:d(s),minute:d(i)};return a&&(o.second=d(a)),e&&(o.weekday=3O(n[t])?e:(e&&wr(r,n,e,n,t),t),null)}class V{constructor(e){var t="longterm"===e.conversionAccuracy||!1;this.values=e.values,this.loc=e.loc||M.create(),this.conversionAccuracy=t?"longterm":"casual",this.invalid=e.invalid||null,this.matrix=t?yr:mr,this.isLuxonDuration=!0}static fromMillis(e,t){return V.fromObject({milliseconds:e},t)}static fromObject(e,t={}){if(null==e||"object"!=typeof e)throw new o("Duration.fromObject: argument expected to be an object, got "+(null===e?"null":typeof e));return new V({values:Me(e,V.normalizeUnit),loc:M.fromObject(t),conversionAccuracy:t.conversionAccuracy})}static fromDurationLike(e){if(c(e))return V.fromMillis(e);if(V.isDuration(e))return e;if("object"==typeof e)return V.fromObject(e);throw new o(`Unknown duration argument ${e} of type `+typeof e)}static fromISO(e,t){var[r]=ir(e);return r?V.fromObject(r,t):V.invalid("unparsable",`the input "${e}" can't be parsed as ISO 8601`)}static fromISOTime(e,t){var[r]=or(e);return r?V.fromObject(r,t):V.invalid("unparsable",`the input "${e}" can't be parsed as ISO 8601`)}static invalid(e,t=null){if(!e)throw new o("need to specify a reason the Duration is invalid");e=e instanceof y?e:new y(e,t);if(k.throwOnInvalid)throw new A(e);return new V({invalid:e})}static normalizeUnit(e){var t={year:"years",years:"years",quarter:"quarters",quarters:"quarters",month:"months",months:"months",week:"weeks",weeks:"weeks",day:"days",days:"days",hour:"hours",hours:"hours",minute:"minutes",minutes:"minutes",second:"seconds",seconds:"seconds",millisecond:"milliseconds",milliseconds:"milliseconds"}[e&&e.toLowerCase()];if(t)return t;throw new _(e)}static isDuration(e){return e&&e.isLuxonDuration||!1}get locale(){return this.isValid?this.loc.locale:null}get numberingSystem(){return this.isValid?this.loc.numberingSystem:null}toFormat(e,t={}){t={...t,floor:!1!==t.round&&!1!==t.floor};return this.isValid?f.create(this.loc,t).formatDurationFromString(this,e):"Invalid Duration"}toHuman(r={}){var e=D.map(e=>{var t=this.values[e];return O(t)?null:this.loc.numberFormatter({style:"unit",unitDisplay:"long",...r,unit:e.slice(0,-1)}).format(t)}).filter(e=>e);return this.loc.listFormatter({type:"conjunction",style:r.listStyle||"narrow",...r}).format(e)}toObject(){return this.isValid?{...this.values}:{}}toISO(){if(!this.isValid)return null;let e="P";return 0!==this.years&&(e+=this.years+"Y"),0===this.months&&0===this.quarters||(e+=this.months+3*this.quarters+"M"),0!==this.weeks&&(e+=this.weeks+"W"),0!==this.days&&(e+=this.days+"D"),0===this.hours&&0===this.minutes&&0===this.seconds&&0===this.milliseconds||(e+="T"),0!==this.hours&&(e+=this.hours+"H"),0!==this.minutes&&(e+=this.minutes+"M"),0===this.seconds&&0===this.milliseconds||(e+=ye(this.seconds+this.milliseconds/1e3,3)+"S"),"P"===e&&(e+="T0S"),e}toISOTime(e={}){if(!this.isValid)return null;var t=this.toMillis();if(t<0||864e5<=t)return null;e={suppressMilliseconds:!1,suppressSeconds:!1,includePrefix:!1,format:"extended",...e};const r=this.shiftTo("hours","minutes","seconds","milliseconds");let n="basic"===e.format?"hhmm":"hh:mm",s=(e.suppressSeconds&&0===r.seconds&&0===r.milliseconds||(n+="basic"===e.format?"ss":":ss",e.suppressMilliseconds&&0===r.milliseconds||(n+=".SSS")),r.toFormat(n));return s=e.includePrefix?"T"+s:s}toJSON(){return this.toISO()}toString(){return this.toISO()}toMillis(){return this.as("milliseconds")}valueOf(){return this.toMillis()}plus(e){if(!this.isValid)return this;const t=V.fromDurationLike(e),r={};for(const n of D)(h(t.values,n)||h(this.values,n))&&(r[n]=t.get(n)+this.get(n));return E(this,{values:r},!0)}minus(e){if(!this.isValid)return this;const t=V.fromDurationLike(e);return this.plus(t.negate())}mapUnits(e){if(!this.isValid)return this;const t={};for(const r of Object.keys(this.values))t[r]=ke(e(this.values[r],r));return E(this,{values:t},!0)}get(e){return this[V.normalizeUnit(e)]}set(e){return this.isValid?E(this,{values:{...this.values,...Me(e,V.normalizeUnit)}}):this}reconfigure({locale:e,numberingSystem:t,conversionAccuracy:r}={}){const n=this.loc.clone({locale:e,numberingSystem:t}),s={loc:n};return r&&(s.conversionAccuracy=r),E(this,s)}as(e){return this.isValid?this.shiftTo(e).get(e):NaN}normalize(){if(!this.isValid)return this;var e=this.toObject();return vr(this.matrix,e),E(this,{values:e},!0)}shiftTo(...e){if(!this.isValid)return this;if(0===e.length)return this;e=e.map(e=>V.normalizeUnit(e));const t={},r={},n=this.toObject();let s;for(const a of D)if(0<=e.indexOf(a)){s=a;let e=0;for(const o in r)e+=this.matrix[o][a]*r[o],r[o]=0;c(n[a])&&(e+=n[a]);var i=Math.trunc(e);t[a]=i,r[a]=(1e3*e-1e3*i)/1e3;for(const u in n)D.indexOf(u)>D.indexOf(a)&&wr(this.matrix,n,u,t,a)}else c(n[a])&&(r[a]=n[a]);for(const l in r)0!==r[l]&&(t[s]+=l===s?r[l]:r[l]/this.matrix[s][l]);return E(this,{values:t},!0).normalize()}negate(){if(!this.isValid)return this;const e={};for(const t of Object.keys(this.values))e[t]=0===this.values[t]?0:-this.values[t];return E(this,{values:e},!0)}get years(){return this.isValid?this.values.years||0:NaN}get quarters(){return this.isValid?this.values.quarters||0:NaN}get months(){return this.isValid?this.values.months||0:NaN}get weeks(){return this.isValid?this.values.weeks||0:NaN}get days(){return this.isValid?this.values.days||0:NaN}get hours(){return this.isValid?this.values.hours||0:NaN}get minutes(){return this.isValid?this.values.minutes||0:NaN}get seconds(){return this.isValid?this.values.seconds||0:NaN}get milliseconds(){return this.isValid?this.values.milliseconds||0:NaN}get isValid(){return null===this.invalid}get invalidReason(){return this.invalid?this.invalid.reason:null}get invalidExplanation(){return this.invalid?this.invalid.explanation:null}equals(e){if(!this.isValid||!e.isValid)return!1;if(!this.loc.equals(e.loc))return!1;for(const n of D)if(t=this.values[n],r=e.values[n],!(void 0===t||0===t?void 0===r||0===r:t===r))return!1;var t,r;return!0}}const pr="Invalid Interval";function Tr(e,t){return e&&e.isValid?t&&t.isValid?te}isBefore(e){return!!this.isValid&&this.e<=e}contains(e){return!!this.isValid&&(this.s<=e&&this.e>e)}set({start:e,end:t}={}){return this.isValid?I.fromDateTimes(e||this.s,t||this.e):this}splitAt(...e){if(!this.isValid)return[];const t=e.map(Sn).filter(e=>this.contains(e)).sort(),r=[];let n=this["s"],s=0;for(;n+this.e?this.e:i;r.push(I.fromDateTimes(n,i)),n=i,s+=1}return r}splitBy(e){const t=V.fromDurationLike(e);if(!this.isValid||!t.isValid||0===t.as("milliseconds"))return[];let r=this["s"],n=1,s;const i=[];for(;re*n));s=+a>+this.e?this.e:a,i.push(I.fromDateTimes(r,s)),r=s,n+=1}return i}divideEqually(e){return this.isValid?this.splitBy(this.length()/e).slice(0,e):[]}overlaps(e){return this.e>e.s&&this.s=e.e)}equals(e){return!(!this.isValid||!e.isValid)&&(this.s.equals(e.s)&&this.e.equals(e.e))}intersection(e){if(!this.isValid)return this;var t=(this.s>e.s?this:e).s,e=(this.ee.e?this:e).e;return I.fromDateTimes(t,e)}static merge(e){const[t,r]=e.sort((e,t)=>e.s-t.s).reduce(([e,t],r)=>t?t.overlaps(r)||t.abutsStart(r)?[e,t.union(r)]:[e.concat([t]),r]:[e,r],[[],null]);return r&&t.push(r),t}static xor(e){let t=null,r=0;const n=[],s=e.map(e=>[{time:e.s,type:"s"},{time:e.e,type:"e"}]),i=Array.prototype.concat(...s),a=i.sort((e,t)=>e.time-t.time);for(const o of a)r+="s"===o.type?1:-1,t=1===r?o.time:(t&&+t!=+o.time&&n.push(I.fromDateTimes(t,o.time)),null);return I.merge(n)}difference(...e){return I.xor([this].concat(e)).map(e=>this.intersection(e)).filter(e=>e&&!e.isEmpty())}toString(){return this.isValid?`[${this.s.toISO()} – ${this.e.toISO()})`:pr}toISO(e){return this.isValid?this.s.toISO(e)+"/"+this.e.toISO(e):pr}toISODate(){return this.isValid?this.s.toISODate()+"/"+this.e.toISODate():pr}toISOTime(e){return this.isValid?this.s.toISOTime(e)+"/"+this.e.toISOTime(e):pr}toFormat(e,{separator:t=" – "}={}){return this.isValid?""+this.s.toFormat(e)+t+this.e.toFormat(e):pr}toDuration(e,t){return this.isValid?this.e.diff(this.s,e,t):V.invalid(this.invalidReason)}mapEndpoints(e){return I.fromDateTimes(e(this.s),e(this.e))}}class Sr{static hasDST(e=k.defaultZone){const t=L.now().setZone(e).set({month:12});return!e.isUniversal&&t.offset!==t.set({month:6}).offset}static isValidIANAZone(e){return w.isValidZone(e)}static normalizeZone(e){return b(e,k.defaultZone)}static months(e="long",{locale:t=null,numberingSystem:r=null,locObj:n=null,outputCalendar:s="gregory"}={}){return(n||M.create(t,r,s)).months(e)}static monthsFormat(e="long",{locale:t=null,numberingSystem:r=null,locObj:n=null,outputCalendar:s="gregory"}={}){return(n||M.create(t,r,s)).months(e,!0)}static weekdays(e="long",{locale:t=null,numberingSystem:r=null,locObj:n=null}={}){return(n||M.create(t,r,null)).weekdays(e)}static weekdaysFormat(e="long",{locale:t=null,numberingSystem:r=null,locObj:n=null}={}){return(n||M.create(t,r,null)).weekdays(e,!0)}static meridiems({locale:e=null}={}){return M.create(e).meridiems()}static eras(e="short",{locale:t=null}={}){return M.create(t,null,"gregory").eras(e)}static features(){return{relative:he()}}}function Or(e,t){var r=e=>e.toUTC(0,{keepLocalTime:!0}).startOf("day").valueOf(),t=r(t)-r(e);return Math.floor(V.fromMillis(t).as("days"))}function br(e,t,r,n){let[s,i,a,o]=function(t,r,e){var n,s;const i={};let a,o;for([n,s]of[["years",(e,t)=>t.year-e.year],["quarters",(e,t)=>t.quarter-e.quarter],["months",(e,t)=>t.month-e.month+12*(t.year-e.year)],["weeks",(e,t)=>{e=Or(e,t);return(e-e%7)/7}],["days",Or]])if(0<=e.indexOf(n)){a=n;let e=s(t,r);(o=t.plus({[n]:e}))>r?(t=t.plus({[n]:e-1}),--e):t=o,i[n]=e}return[t,i,o,a]}(e,t,r);e=t-s,r=r.filter(e=>0<=["hours","minutes","seconds","milliseconds"].indexOf(e)),0===r.length&&(a=ae){return{regex:e,deser:([e])=>t(function(t){let r=parseInt(t,10);if(isNaN(r)){r="";for(let e=0;ee.findIndex(e=>xr(t)===xr(e))+r}}function Cr(e,t){return{regex:e,deser:([,e,t])=>be(e,t),groups:t}}function Fr(e){return{regex:e,deser:([e])=>e}}const Zr={year:{"2-digit":"yy",numeric:"yyyyy"},month:{numeric:"M","2-digit":"MM",short:"MMM",long:"MMMM"},day:{numeric:"d","2-digit":"dd"},weekday:{short:"EEE",long:"EEEE"},dayperiod:"a",dayPeriod:"a",hour:{numeric:"h","2-digit":"hh"},minute:{numeric:"m","2-digit":"mm"},second:{numeric:"s","2-digit":"ss"}};let Lr=null;function zr(e,t){if(e.literal)return e;const s=f.macroTokenToFormatOpts(e.val);if(!s)return e;const r=f.create(t,s),n=r.formatDateTimeParts(Lr=Lr||L.fromMillis(1555555555555)),i=n.map(t=>{{var r=s,{type:t,value:n}=t;if("literal"===t)return{literal:!0,val:n};n=r[t];let e=Zr[t];return(e="object"==typeof e?e[n]:e)?{literal:!1,val:e}:void 0}});return i.includes(void 0)?e:i}function qr(g,e,t){t=f.parseFormat(t),i=g;const r=Array.prototype.concat(...t.map(e=>zr(e,i))),n=r.map(e=>{{var t=e,r=g;const n=x(r),s=x(r,"{2}"),i=x(r,"{3}"),a=x(r,"{4}"),o=x(r,"{6}"),u=x(r,"{1,2}"),l=x(r,"{1,3}"),c=x(r,"{1,6}"),h=x(r,"{1,9}"),d=x(r,"{2,4}"),m=x(r,"{4,6}"),f=e=>({regex:RegExp(e.val.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")),deser:([e])=>e,literal:!0}),y=(e=>{if(t.literal)return f(e);switch(e.val){case"G":return F(r.eras("short",!1),0);case"GG":return F(r.eras("long",!1),0);case"y":return C(c);case"yy":return C(d,Se);case"yyyy":return C(a);case"yyyyy":return C(m);case"yyyyyy":return C(o);case"M":return C(u);case"MM":return C(s);case"MMM":return F(r.months("short",!0,!1),1);case"MMMM":return F(r.months("long",!0,!1),1);case"L":return C(u);case"LL":return C(s);case"LLL":return F(r.months("short",!1,!1),1);case"LLLL":return F(r.months("long",!1,!1),1);case"d":return C(u);case"dd":return C(s);case"o":return C(l);case"ooo":return C(i);case"HH":return C(s);case"H":return C(u);case"hh":return C(s);case"h":return C(u);case"mm":return C(s);case"m":case"q":return C(u);case"qq":return C(s);case"s":return C(u);case"ss":return C(s);case"S":return C(l);case"SSS":return C(i);case"u":return Fr(h);case"uu":return Fr(u);case"uuu":return C(n);case"a":return F(r.meridiems(),0);case"kkkk":return C(a);case"kk":return C(d,Se);case"W":return C(u);case"WW":return C(s);case"E":case"c":return C(n);case"EEE":return F(r.weekdays("short",!1,!1),1);case"EEEE":return F(r.weekdays("long",!1,!1),1);case"ccc":return F(r.weekdays("short",!0,!1),1);case"cccc":return F(r.weekdays("long",!0,!1),1);case"Z":case"ZZ":return Cr(new RegExp(`([+-]${u.source})(?::(${s.source}))?`),2);case"ZZZ":return Cr(new RegExp(`([+-]${u.source})(${s.source})?`),2);case"z":return Fr(/[a-z_+-/]{1,256}?/i);default:return f(e)}})(t)||{invalidReason:Dr};return y.token=t,y}}),s=n.find(e=>e.invalidReason);var i;if(s)return{input:e,tokens:r,invalidReason:s.invalidReason};var[t,a]=[`^${(t=n).map(e=>e.regex).reduce((e,t)=>`${e}(${t.source})`,"")}$`,t],t=RegExp(t,"i"),[a,o]=function(e,t,r){const n=e.match(t);if(n){const s={};let e=1;for(const i in r)if(h(r,i)){const a=r[i],o=a.groups?a.groups+1:1;!a.literal&&a.token&&(s[a.token.val[0]]=a.deser(n.slice(e,e+o))),e+=o}return[n,s]}return[n,{}]}(e,t,a),[u,l,c]=o?function(n){let e=null,t;return O(n.z)||(e=w.create(n.z)),O(n.Z)||(e=e||new v(n.Z),t=n.Z),O(n.q)||(n.M=3*(n.q-1)+1),O(n.h)||(n.h<12&&1===n.a?n.h+=12:12===n.h&&0===n.a&&(n.h=0)),0===n.G&&n.y&&(n.y=-n.y),O(n.u)||(n.S=fe(n.u)),[Object.keys(n).reduce((e,t)=>{var r=(e=>{switch(e){case"S":return"millisecond";case"s":return"second";case"m":return"minute";case"h":case"H":return"hour";case"d":return"day";case"o":return"ordinal";case"L":case"M":return"month";case"y":return"year";case"E":case"c":return"weekday";case"W":return"weekNumber";case"k":return"weekYear";case"q":return"quarter";default:return null}})(t);return r&&(e[r]=n[t]),e},{}),e,t]}(o):[null,null,void 0];if(h(o,"a")&&h(o,"H"))throw new j("Can't include meridiem when specifying 24-hour format");return{input:e,tokens:r,regex:t,rawMatches:a,matches:o,result:u,zone:l,specificOffset:c}}function Ar(e,t,r){var{result:e,zone:t,specificOffset:r,invalidReason:n}=qr(e,t,r);return[e,t,r,n]}const jr=[0,31,59,90,120,151,181,212,243,273,304,334],_r=[0,31,60,91,121,152,182,213,244,274,305,335];function Z(e,t){return new y("unit out of range",`you specified ${t} (of type ${typeof t}) as a ${e}, which is invalid`)}function Ur(e,t,r){const n=new Date(Date.UTC(e,t-1,r));e<100&&0<=e&&n.setUTCFullYear(n.getUTCFullYear()-1900);t=n.getUTCDay();return 0===t?7:t}function $r(e,t,r){return r+(ge(e)?_r:jr)[t-1]}function Hr(e,t){const r=ge(e)?_r:jr,n=r.findIndex(e=>eTe(t)?(a=t+1,i=1):a=t,{weekYear:a,weekNumber:i,weekday:r,...De(e)}}function Rr(e){var{weekYear:t,weekNumber:r,weekday:n}=e,s=Ur(t,1,4),i=we(t);let a=7*r+n-s-3,o;a<1?(o=t-1,a+=we(o)):a>i?(o=t+1,a-=we(t)):o=t;var{month:r,day:n}=Hr(o,a);return{year:o,month:r,day:n,...De(e)}}function Jr(e){var{year:t,month:r,day:n}=e;return{year:t,ordinal:$r(t,r,n),...De(e)}}function Yr(e){var{year:t,ordinal:r}=e,{month:r,day:n}=Hr(t,r);return{year:t,month:r,day:n,...De(e)}}function Pr(e){var t=le(e.weekYear),r=u(e.weekNumber,1,Te(e.weekYear)),n=u(e.weekday,1,7);return t?r?!n&&Z("weekday",e.weekday):Z("week",e.week):Z("weekYear",e.weekYear)}function Gr(e){var t=le(e.year),r=u(e.ordinal,1,we(e.year));return t?!r&&Z("ordinal",e.ordinal):Z("year",e.year)}function Br(e){var t=le(e.year),r=u(e.month,1,12),n=u(e.day,1,ve(e.year,e.month));return t?r?!n&&Z("day",e.day):Z("month",e.month):Z("year",e.year)}function Qr(e){var{hour:e,minute:t,second:r,millisecond:n}=e,s=u(e,0,23)||24===e&&0===t&&0===r&&0===n,i=u(t,0,59),a=u(r,0,59),o=u(n,0,999);return s?i?a?!o&&Z("millisecond",n):Z("second",r):Z("minute",t):Z("hour",e)}const Kr="Invalid DateTime";function Xr(e){return new y("unsupported zone",`the zone "${e.name}" is not supported`)}function en(e){return null===e.weekData&&(e.weekData=Wr(e.c)),e.weekData}function tn(e,t){e={ts:e.ts,zone:e.zone,c:e.c,o:e.o,loc:e.loc,invalid:e.invalid};return new L({...e,...t,old:e})}function rn(e,t,r){let n=e-60*t*1e3;var s=r.offset(n);if(t===s)return[n,t];n-=60*(s-t)*1e3;t=r.offset(n);return s===t?[n,s]:[e-60*Math.min(s,t)*1e3,Math.max(s,t)]}function nn(e,t){e+=60*t*1e3;const r=new Date(e);return{year:r.getUTCFullYear(),month:r.getUTCMonth()+1,day:r.getUTCDate(),hour:r.getUTCHours(),minute:r.getUTCMinutes(),second:r.getUTCSeconds(),millisecond:r.getUTCMilliseconds()}}function sn(e,t,r){return rn(pe(e),t,r)}function an(e,t){var r=e.o,n=e.c.year+Math.trunc(t.years),s=e.c.month+Math.trunc(t.months)+3*Math.trunc(t.quarters),n={...e.c,year:n,month:s,day:Math.min(e.c.day,ve(n,s))+Math.trunc(t.days)+7*Math.trunc(t.weeks)},s=V.fromObject({years:t.years-Math.trunc(t.years),quarters:t.quarters-Math.trunc(t.quarters),months:t.months-Math.trunc(t.months),weeks:t.weeks-Math.trunc(t.weeks),days:t.days-Math.trunc(t.days),hours:t.hours,minutes:t.minutes,seconds:t.seconds,milliseconds:t.milliseconds}).as("milliseconds");let[i,a]=rn(pe(n),r,e.zone);return 0!==s&&(i+=s,a=e.zone.offset(i)),{ts:i,o:a}}function on(e,t,r,n,s,i){var{setZone:a,zone:o}=r;if(e&&0!==Object.keys(e).length){const u=t||o,l=L.fromObject(e,{...r,zone:u,specificOffset:i});return a?l:l.setZone(o)}return L.invalid(new y("unparsable",`the input "${s}" can't be parsed as `+n))}function un(e,t,r=!0){return e.isValid?f.create(M.create("en-US"),{allowZ:r,forceSimple:!0}).formatDateTimeFromString(e,t):null}function ln(e,t){var r=9999{e=ye(e,i||s.calendary?0:2,!0);const r=n.loc.clone(s).relFormatter(s);return r.format(e,t)},r=e=>s.calendary?n.hasSame(t,e)?0:n.startOf(e).diff(t.startOf(e),e).get(e):n.diff(t,e).get(e);if(s.unit)return e(r(s.unit),s.unit);for(const o of s.units){var a=r(o);if(1<=Math.abs(a))return e(a,o)}return e(nthis.set({month:1,day:1}).offset||this.offset>this.set({month:5}).offset)}get isInLeapYear(){return ge(this.year)}get daysInMonth(){return ve(this.year,this.month)}get daysInYear(){return this.isValid?we(this.year):NaN}get weeksInWeekYear(){return this.isValid?Te(this.weekYear):NaN}resolvedLocaleOptions(e={}){var{locale:e,numberingSystem:t,calendar:r}=f.create(this.loc.clone(e),e).resolvedOptions(this);return{locale:e,numberingSystem:t,outputCalendar:r}}toUTC(e=0,t={}){return this.setZone(v.instance(e),t)}toLocal(){return this.setZone(k.defaultZone)}setZone(t,{keepLocalTime:r=!1,keepCalendarTime:n=!1}={}){if((t=b(t,k.defaultZone)).equals(this.zone))return this;if(t.isValid){let e=this.ts;return(r||n)&&(r=t.offset(this.ts),n=this.toObject(),[e]=sn(n,r,t)),tn(this,{ts:e,zone:t})}return L.invalid(Xr(t))}reconfigure({locale:e,numberingSystem:t,outputCalendar:r}={}){e=this.loc.clone({locale:e,numberingSystem:t,outputCalendar:r});return tn(this,{loc:e})}setLocale(e){return this.reconfigure({locale:e})}set(e){if(!this.isValid)return this;var e=Me(e,wn),t=!O(e.weekYear)||!O(e.weekNumber)||!O(e.weekday),r=!O(e.ordinal),n=!O(e.year),s=!O(e.month)||!O(e.day),i=e.weekYear||e.weekNumber;if((n||s||r)&&i)throw new j("Can't mix weekYear/weekNumber units with year/month/day or ordinals");if(s&&r)throw new j("Can't mix ordinal dates with month/day");let a;t?a=Rr({...Wr(this.c),...e}):O(e.ordinal)?(a={...this.toObject(),...e},O(e.day)&&(a.day=Math.min(ve(a.year,a.month),a.day))):a=Yr({...Jr(this.c),...e});var[n,i]=sn(a,this.o,this.zone);return tn(this,{ts:n,o:i})}plus(e){return this.isValid?tn(this,an(this,V.fromDurationLike(e))):this}minus(e){return this.isValid?tn(this,an(this,V.fromDurationLike(e).negate())):this}startOf(e){if(!this.isValid)return this;const t={},r=V.normalizeUnit(e);switch(r){case"years":t.month=1;case"quarters":case"months":t.day=1;case"weeks":case"days":t.hour=0;case"hours":t.minute=0;case"minutes":t.second=0;case"seconds":t.millisecond=0}return"weeks"===r&&(t.weekday=1),"quarters"===r&&(e=Math.ceil(this.month/3),t.month=3*(e-1)+1),this.set(t)}endOf(e){return this.isValid?this.plus({[e]:1}).startOf(e).minus(1):this}toFormat(e,t={}){return this.isValid?f.create(this.loc.redefaultToEN(t)).formatDateTimeFromString(this,e):Kr}toLocaleString(e=U,t={}){return this.isValid?f.create(this.loc.clone(t),e).formatDateTime(this):Kr}toLocaleParts(e={}){return this.isValid?f.create(this.loc.clone(e),e).formatDateTimeParts(this):[]}toISO({format:e="extended",suppressSeconds:t=!1,suppressMilliseconds:r=!1,includeOffset:n=!0,extendedZone:s=!1}={}){if(!this.isValid)return null;var e="extended"===e,i=ln(this,e);return(i+="T")+cn(this,e,t,r,n,s)}toISODate({format:e="extended"}={}){return this.isValid?ln(this,"extended"===e):null}toISOWeekDate(){return un(this,"kkkk-'W'WW-c")}toISOTime({suppressMilliseconds:e=!1,suppressSeconds:t=!1,includeOffset:r=!0,includePrefix:n=!1,extendedZone:s=!1,format:i="extended"}={}){return this.isValid?(n?"T":"")+cn(this,"extended"===i,t,e,r,s):null}toRFC2822(){return un(this,"EEE, dd LLL yyyy HH:mm:ss ZZZ",!1)}toHTTP(){return un(this.toUTC(),"EEE, dd LLL yyyy HH:mm:ss 'GMT'")}toSQLDate(){return this.isValid?ln(this,!0):null}toSQLTime({includeOffset:e=!0,includeZone:t=!1,includeOffsetSpace:r=!0}={}){let n="HH:mm:ss.SSS";return(t||e)&&(r&&(n+=" "),t?n+="z":e&&(n+="ZZ")),un(this,n,!0)}toSQL(e={}){return this.isValid?this.toSQLDate()+" "+this.toSQLTime(e):null}toString(){return this.isValid?this.toISO():Kr}valueOf(){return this.toMillis()}toMillis(){return this.isValid?this.ts:NaN}toSeconds(){return this.isValid?this.ts/1e3:NaN}toUnixInteger(){return this.isValid?Math.floor(this.ts/1e3):NaN}toJSON(){return this.toISO()}toBSON(){return this.toJSDate()}toObject(e={}){if(!this.isValid)return{};const t={...this.c};return e.includeConfig&&(t.outputCalendar=this.outputCalendar,t.numberingSystem=this.loc.numberingSystem,t.locale=this.loc.locale),t}toJSDate(){return new Date(this.isValid?this.ts:NaN)}diff(e,t="milliseconds",r={}){if(!this.isValid||!e.isValid)return V.invalid("created by diffing an invalid DateTime");r={locale:this.locale,numberingSystem:this.numberingSystem,...r};const n=de(t).map(V.normalizeUnit),s=e.valueOf()>this.valueOf(),i=s?this:e,a=s?e:this,o=br(i,a,n,r);return s?o.negate():o}diffNow(e="milliseconds",t={}){return this.diff(L.now(),e,t)}until(e){return this.isValid?I.fromDateTimes(this,e):this}hasSame(e,t){if(!this.isValid)return!1;var r=e.valueOf();const n=this.setZone(e.zone,{keepLocalTime:!0});return n.startOf(t)<=r&&r<=n.endOf(t)}equals(e){return this.isValid&&e.isValid&&this.valueOf()===e.valueOf()&&this.zone.equals(e.zone)&&this.loc.equals(e.loc)}toRelative(e={}){if(!this.isValid)return null;var t=e.base||L.fromObject({},{zone:this.zone}),r=e.padding?thise.valueOf(),Math.min);throw new o("min requires all arguments be DateTimes")}static max(...e){if(e.every(L.isDateTime))return me(e,e=>e.valueOf(),Math.max);throw new o("max requires all arguments be DateTimes")}static fromFormatExplain(e,t,r={}){var{locale:r=null,numberingSystem:n=null}=r;return qr(M.fromOpts({locale:r,numberingSystem:n,defaultToEN:!0}),e,t)}static fromStringExplain(e,t,r={}){return L.fromFormatExplain(e,t,r)}static get DATE_SHORT(){return U}static get DATE_MED(){return $}static get DATE_MED_WITH_WEEKDAY(){return H}static get DATE_FULL(){return W}static get DATE_HUGE(){return R}static get TIME_SIMPLE(){return J}static get TIME_WITH_SECONDS(){return Y}static get TIME_WITH_SHORT_OFFSET(){return P}static get TIME_WITH_LONG_OFFSET(){return G}static get TIME_24_SIMPLE(){return B}static get TIME_24_WITH_SECONDS(){return Q}static get TIME_24_WITH_SHORT_OFFSET(){return K}static get TIME_24_WITH_LONG_OFFSET(){return X}static get DATETIME_SHORT(){return ee}static get DATETIME_SHORT_WITH_SECONDS(){return te}static get DATETIME_MED(){return re}static get DATETIME_MED_WITH_SECONDS(){return ne}static get DATETIME_MED_WITH_WEEKDAY(){return se}static get DATETIME_FULL(){return ie}static get DATETIME_FULL_WITH_SECONDS(){return ae}static get DATETIME_HUGE(){return oe}static get DATETIME_HUGE_WITH_SECONDS(){return ue}}function Sn(e){if(L.isDateTime(e))return e;if(e&&e.valueOf&&c(e.valueOf()))return L.fromJSDate(e);if(e&&"object"==typeof e)return L.fromObject(e);throw new o(`Unknown datetime argument: ${e}, of type `+typeof e)}s="2.5.2";export{L as DateTime,V as Duration,v as FixedOffsetZone,w as IANAZone,Sr as Info,I as Interval,nt as InvalidZone,k as Settings,Ge as SystemZone,s as VERSION,i as Zone}; \ No newline at end of file diff --git a/noise/public/javascripts/map.js b/noise/public/javascripts/map.js new file mode 100644 index 0000000..d41b0bf --- /dev/null +++ b/noise/public/javascripts/map.js @@ -0,0 +1,99 @@ +// all function related to the map + +import * as utils from "./chart_utilities.js" +import * as mapn from './map_noise.js' +import * as mapu from './map_utilities.js' + +const mapparams = { + map: null, + bounds: null, + popuptext: '', + clickedSensor: 0, + APIURL: '/srv/getmapdata?', + MAXZOOM: 19 +} + +export async function showMap(params) { + if(sysparams.category === 'noise') { + utils.removeTabs() + if(mapparams.map && mapparams.map.remove) { + mapparams.map.off(); + mapparams.map.remove(); + } + } + mapparams.map = L.map('map', {scrollWheelZoom: false}).setView(params.center.coords, parseInt(params.zoom)) + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: mapparams.MAXZOOM, + attribution: '© OpenStreetMap' + }).addTo(mapparams.map) + mapparams.bounds = mapparams.map.getBounds(); + + mapparams.map.on('moveend', async function () { + mapparams.bounds = mapparams.map.getBounds() + await buildMarkers(params, mapparams) + }); + + if(sysparams.category === 'noise'){ + mapn.buildLegend(mapparams) + } + + let lastdate = await buildMarkers(params, mapparams) + mapu.showLastDate(lastdate, mapparams); +} + +const buildMarkers = async (params, mapparams) => { + return mapn.buildMarkers(params, mapparams) +} + +export const setNewCenter = (center) => { + mapparams.map.setView(center) +} + +export const goToMyLocation = () => { + if (!navigator.geolocation) { + alert('Geolocation wird von diesem Browser nicht unterstützt'); + return; + } + + navigator.geolocation.getCurrentPosition( + (position) => { + const lat = position.coords.latitude; + const lon = position.coords.longitude; + const accuracy = position.coords.accuracy; + + // Karte zur aktuellen Position zentrieren + mapparams.map.setView([lat, lon], 15); + + // Optional: Marker an aktueller Position setzen + const myLocationMarker = L.marker([lat, lon], { + icon: L.icon({ + iconUrl: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCI+PGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iOCIgZmlsbD0iIzQyODVGNCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIzIi8+PGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTUiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzQyODVGNCIgc3Ryb2tlLXdpZHRoPSIyIiBvcGFjaXR5PSIwLjMiLz48L3N2Zz4=', + iconSize: [40, 40], + iconAnchor: [20, 20] + }) + }).addTo(mapparams.map); + + myLocationMarker.bindPopup(`📍 Ihr Standort
Genauigkeit: ±${Math.round(accuracy)}m`).openPopup(); + }, + (error) => { + let errorMsg = 'Position konnte nicht ermittelt werden.'; + switch(error.code) { + case error.PERMISSION_DENIED: + errorMsg = 'Bitte erlauben Sie den Zugriff auf Ihren Standort.'; + break; + case error.POSITION_UNAVAILABLE: + errorMsg = 'Standortinformationen sind nicht verfügbar.'; + break; + case error.TIMEOUT: + errorMsg = 'Die Anfrage ist abgelaufen.'; + break; + } + alert(errorMsg); + }, + { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 + } + ); +} diff --git a/noise/public/javascripts/map_noise.js b/noise/public/javascripts/map_noise.js new file mode 100644 index 0000000..8490655 --- /dev/null +++ b/noise/public/javascripts/map_noise.js @@ -0,0 +1,210 @@ + +import * as dt from './datetime.js' +import * as utils from "./chart_utilities.js" +import { showChart, loadAll} from './showcharts.js' +import * as mapu from './map_utilities.js' +import * as spin from './spinner.js' + +export const colorscale = ['#d53e4f', '#fc8d59', '#fee08b', '#e6f598', '#99d594', '#3288bd', '#808080']; +const dba = [100, 80, 60, 40, 20, 0, -999]; + +export function getColor(d) { + let val = parseInt(d); + for (let i = 0; i < dba.length; i++) { + if (val >= dba[i]) { + return (colorscale[i]); + } + } +} + +export const buildLegend = (mpp) => { + let legend = L.control({position: 'bottomright'}) + legend.onAdd = function () { + let div = L.DomUtil.create('div', 'info legend') + let div_color = L.DomUtil.create('div', 'info legend inner', div) + div_color.innerHTML += 'dbA
'; + // loop through our density intervals and generate a label with a colored square for each interval + for (let i = 0; i < dba.length - 1; i++) { + div_color.innerHTML += + '' + + '  ' + dba[i] + (i == 0 ? '+' : '') + '
' + } + div_color.innerHTML += '  offline' + return div + }; + legend.addTo(mpp.map) +} + +export async function buildMarkers(params, mpp) { + const url = mpp.APIURL + `type=noise&box=${mpp.bounds.toBBoxString()}` + let sensors = await utils.fetchfromserver(url) + if (!sensors.err) { + let markers = L.markerClusterGroup({ + spiderfyOnMaxZoom: true, + showCoverageOnHover: false, + zoomToBoundsOnClick: true, + // disableClusteringAtZoom: 14, + iconCreateFunction: function (cluster) { + let mymarkers = cluster.getAllChildMarkers(); + let color = getMedian(mymarkers); + return new L.Icon({ + iconUrl: buildIcon(color, cluster.getChildCount(), 0), + iconSize: [35, 35] + }); + }, + }); + + for (let x of sensors.values) { + if ((params.weeks > 0) && (x.weeks > params.weeks)) { + continue + } + if (x.value <= -5) { + continue + } + let marker = L.marker([x.location[1], x.location[0]], { + icon: new L.Icon({ + iconUrl: buildIcon(getColor(parseInt(x.value)), 0, x.indoor), + iconSize: [35, 35], + }), + name: x.id, + value: x.value, + url: '/graph?sid=' + x.id, + lastseen: dt.formatISODate(x.lastseen), + indoor: x.indoor + }) + .on('click', e => onMarkerClick(e, true, sensors.popuptxt, params, mpp)) // define click- and + markers.addLayer(marker); + } + mpp.map.addLayer(markers); + return sensors.lastdate + } else { + utils.showError(sensors.err) + } +} + + +function buildIcon(color, n, indoor) { + let x = 100; + if (n < 10) { + x = 200; + } else if (n < 100) { + x = 150; + } + let txtColor = "black"; + if (color == colorscale[5]) { + txtColor = "white" + } + let icon = '' + + ''; + if (n !== 0) { + icon += + '' + n + ''; + } + if(indoor) { + icon += '' + } + icon += ''; + return encodeURI("data:image/svg+xml," + icon).replace(new RegExp('#', 'g'), '%23'); +} + + +async function bauPopupText(item, txts) { + let addr = '' + let offlinetext = ` +${txts.offline} +${txts.lastseen}:${item.lastseen}` + + let normaltext = ` +LA_max:${item.value}` + let popuptext = ` +
+

${txts.sensor}: ${item.name}

+
${addr}
+
+ + + ${item.value < 0 ? offlinetext : normaltext} +
${item.indoor==1 ? "indoor" : ""}
+ +
+
` +return popuptext +} + +async function onMarkerClick(e, click, txts, params, mpp) { + let item = e.target.options; + let popuptext = await bauPopupText(item, txts) + e.target.bindPopup(popuptext).openPopup(); // show the popup + let addr = await utils.addAddress(item.name) + document.querySelector('#infoTitle .addr').innerHTML = `${addr.street}
${addr.plz} ${addr.city}` + let link = document.querySelector('.speciallink') + link.addEventListener('click', async function (ev) { + console.log(`Event clicked: ${mpp.clickedSensor}`) + params.sid = item.name + history.pushState({}, '', `/${params.sid}`) + utils.setCurrentTab('livetab') + spin.spinner.spin(spin.spindiv) + let ok = await showChart(params, utils.ttIndex.live) + spin.spinner.stop() + if (ok) { + // Switch to live tab + let triggerEl = document.querySelector(`#livetab`) + document.querySelectorAll('.tab-button').forEach(tab => tab.classList.remove('active')) + document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')) + triggerEl.classList.add('active') + document.getElementById('t_live').classList.add('active') + + params.center.coords = [e.latlng.lat, e.latlng.lng] + ev.preventDefault() + await loadAll(params) + } + }) +} + + +// With all Markers in cluster (markers) calculate the median +// of the values. With this median fetch the color and return it. +// If there are 'offline' sensors (value == -1) strip then before +// calculating the median. If there are only offline sensor, return +// color of value==-1 (dark gray). +function getMedian(markers, value) { + markers.sort(function (a, b) { // first sort, the lowest first + let y1 = a.options.value; + let y2 = b.options.value; + if (y1 < y2) { + return -1; + } + if (y2 < y1) { + return 1; + } + return 0; + }); + // console.log(markers); + let i = 0; // now find the 'offlines' (value == -1) + for (i = 0; i < markers.length; i++) { + if (markers[i].options.value > 0) { + break; + } + } + markers.splice(0, i); // remove these from array + let lang = markers.length; + if (lang > 1) { // + if ((lang % 2) == 1) { // uneven -> + return getColor(markers[(Math.floor(lang / 2))].options.value); // median is in the middle + } else { // evaen -> + lang = lang / 2; // median is mean of both middle values + // console.log(lang); + let wert = (markers[lang - 1].options.value + + markers[lang].options.value) / 2; + return getColor(wert); + } + } else if (lang == 1) { // only one marker -> return its color + return getColor(markers[0].options.value); + } + return getColor(-1); // only offlines +} + diff --git a/noise/public/javascripts/map_utilities.js b/noise/public/javascripts/map_utilities.js new file mode 100644 index 0000000..e6d17b3 --- /dev/null +++ b/noise/public/javascripts/map_utilities.js @@ -0,0 +1,17 @@ +import * as utils from "./chart_utilities.js" +import * as dt from './datetime.js' + +// Show the last date below tha map grafics +export async function showLastDate(ld, mpp) { + let url + if(sysparams) { + url = mpp.APIURL + 'type=noise' + } else { + url = mpp.APIURL + 'type=radiactivity' + } + let sensors = await utils.fetchfromserver(url) + document.querySelector('#mapdate').innerText = `${sensors.valfromtxt} ${dt.formatISODate(ld).slice(0,-3)}` + document.querySelector('#actsensors').innerText = `${sensors.actsensors} ${sensors.registered}` +// $('#actsensors').html(`${erg.count} aktive Sensoren (angemeldet ${allsens})`) +} + diff --git a/noise/public/javascripts/showcharts.js b/noise/public/javascripts/showcharts.js new file mode 100644 index 0000000..ea20b99 --- /dev/null +++ b/noise/public/javascripts/showcharts.js @@ -0,0 +1,141 @@ +import * as utils from "./chart_utilities.js"; +import { DateTime } from './luxon.min.js' +import * as map from "./map.js"; +import {fetchfromserver, showError} from "./chart_utilities.js"; +import * as spin from './spinner.js' + +export const tabtable = [ + {id: 'maptab', type: 'map', container: 'map', func: map.showMap, div: null, dformat: null}, + {id: 'livetab', type: 'live', container: 'dlive', func: showChart, div: 2, dformat: "dd.LL - HH:mm:ss"}, + {id: 'houravgtab', type: 'havg', container: 'dhour', func: showChart, div: 1, dformat: "dd.LL - HH'h'"}, + {id: 'dayavgtab', type: 'davg', container: 'dday', func: showChart, div: 1, dformat: "dd.LL"}, + {id: 'daynighttab', type: 'daynight', container: 'ddaynight', func: showChart, div: 1, dformat: "dd.LL"}, + {id: 'ldentab', type: 'lden', container: 'dlden', func: showChart, div: 1, dformat: "dd.LL"}, +] + + +export async function showChart(params, index) { + let container = tabtable[index].container + let typ = tabtable[index].type + let id = tabtable[index].id + function form() { + for (let item of tabtable) { + if (item.type === typ) { + const d = DateTime.fromMillis(this.x) + const d1 = d.plus({days: 1}) + let fmt = d.toFormat(item.dformat) + if (((typ === 'daynight') && (this.series.name.startsWith('N'))) || (typ === 'lden')) { + fmt = `${d.toFormat('dd')}/${d1.toFormat('dd.LL')}` + } + if (this.series.name === 'Peaks') { + item.div = 0 + } + return '
' + + fmt + '
' + + '● ' + + this.series.name + ':  ' + + Highcharts.numberFormat(this.y, item.div) + + '
' + } + } + } + + function xformat() { + if (typ === 'lden') { + let lbl = this.axis.defaultLabelFormatter.call(this); + this.value += 86400000; + let lbl1 = this.axis.defaultLabelFormatter.call(this); + return parseInt(lbl) + '/' + lbl1; + } else if ((typ === 'live') || (typ === 'havg')) { + let v = this.axis.defaultLabelFormatter.call(this); + if (v.indexOf(':') == -1) { + return '' + v + ''; + } else { + return v; + } + } else { + return this.axis.defaultLabelFormatter.call(this); + } + } + let url = `/srv/getsensordata?sensorid=${params.sid}&data=${typ}&datetime=${params.datetime}` + if (typ === 'live') { + url += `&span=${params.span}` + } else if ((typ === 'havg') || (typ === 'davg')) { + url += `&peak=${params.peak}` + } + console.log(`fetch ${typ} from server ${url}`) + + let errdiv = document.querySelector(`#${id}err`) + errdiv.innerHTML = '' + + let erg = await fetchfromserver(url) + if(sysparams.category === 'noise') { + utils.showTabs() + } + if (!erg.err) { + erg.options.xAxis.labels.formatter = xformat + erg.options.tooltip.formatter = form + let chart = Highcharts.chart(container, erg.options, function (chart) { + utils.addSensorID2chart(chart, { + sid: erg.params.sid, + indoor: erg.params.indoor + }, document.getElementById(container).offsetWidth) + if(erg.info) { + let wbreit = window.innerWidth + let cbreit = chart.chartWidth + let infoposx = wbreit - ((wbreit-cbreit)/2) -200 + let infoposy = chart.plotTop - 80 + chart.renderer.label( + erg.info.text, + infoposx, + infoposy, + 'rect', 0, 0, true) + .css({ + fontSize: '10pt', + color: 'green' + }) + .attr({ + zIndex: 5, + }).add(); + } + }) + return {error: false} + } else { + errdiv.innerHTML = erg.err + return {error: true} + } +} + +export const loadAll = async (params, start = 2, curtab = '') => { + console.log('now load all in Background') + let messzeit = DateTime.now() + if (start == 0) { + map.showMap(params) + start++ + } + if (params.sid !== -1) { + for (let i = start; i < tabtable.length; i++) { + let err = await showChart(params, i) + if(tabtable[i].id === curtab) { + spin.spinner.stop() + let errtxt = document.querySelector(`#${tabtable[i].id}err`).innerHTML + if (errtxt !== '') { + showError(errtxt) + } + } + } + let dauer = DateTime.now().diff(messzeit,['seconds']).toObject().seconds + console.log(`now all loaded. Zeitdauer: ${dauer}`) + } +} + +export const setNoUTC = () => { + Highcharts.setOptions({ + time: { + useUTC: false // Don't use UTC on the charts + }, + accessibility: { // supress warning concerning accessability + enabled: false + } + }); +} \ No newline at end of file diff --git a/noise/public/javascripts/spinner.js b/noise/public/javascripts/spinner.js new file mode 100644 index 0000000..9f9e229 --- /dev/null +++ b/noise/public/javascripts/spinner.js @@ -0,0 +1,27 @@ +// Aufbau eine Spinner, der während des Ladens von AJAX-Requests angezeigt wird + +import { Spinner } from '/spin.js/spin.js' + +const spinneropts = { + lines: 13, // The number of lines to draw + length: 38, // The length of each line + width: 17, // The line thickness + radius: 45, // The radius of the inner circle + scale: 1, // Scales overall size of the spinner + corners: 1, // Corner roundness (0..1) + speed: 1, // Rounds per second + rotate: 0, // The rotation offset + animation: 'spinner-line-fade-quick', // The CSS animation name for the lines + direction: 1, // 1: clockwise, -1: counterclockwise + color: '#404040', // CSS color or array of colors + fadeColor: 'transparent', // CSS color or array of colors + top: '50%', // Top position relative to parent + left: '50%', // Left position relative to parent + shadow: '0 0 1px transparent', // Box-shadow for the lines + zIndex: 2000000000, // The z-index (defaults to 2e9) + className: 'spinner', // The CSS class to assign to the spinner + position: 'absolute', // Element positioning + }; + +export const spinner = new Spinner(spinneropts) +export const spindiv = document.getElementById('spinner') \ No newline at end of file diff --git a/noise/public/stylesheets/MarkerCluster.Default.css b/noise/public/stylesheets/MarkerCluster.Default.css new file mode 100644 index 0000000..bbc8c9f --- /dev/null +++ b/noise/public/stylesheets/MarkerCluster.Default.css @@ -0,0 +1,60 @@ +.marker-cluster-small { + background-color: rgba(181, 226, 140, 0.6); + } +.marker-cluster-small div { + background-color: rgba(110, 204, 57, 0.6); + } + +.marker-cluster-medium { + background-color: rgba(241, 211, 87, 0.6); + } +.marker-cluster-medium div { + background-color: rgba(240, 194, 12, 0.6); + } + +.marker-cluster-large { + background-color: rgba(253, 156, 115, 0.6); + } +.marker-cluster-large div { + background-color: rgba(241, 128, 23, 0.6); + } + + /* IE 6-8 fallback colors */ +.leaflet-oldie .marker-cluster-small { + background-color: rgb(181, 226, 140); + } +.leaflet-oldie .marker-cluster-small div { + background-color: rgb(110, 204, 57); + } + +.leaflet-oldie .marker-cluster-medium { + background-color: rgb(241, 211, 87); + } +.leaflet-oldie .marker-cluster-medium div { + background-color: rgb(240, 194, 12); + } + +.leaflet-oldie .marker-cluster-large { + background-color: rgb(253, 156, 115); + } +.leaflet-oldie .marker-cluster-large div { + background-color: rgb(241, 128, 23); +} + +.marker-cluster { + background-clip: padding-box; + border-radius: 20px; + } +.marker-cluster div { + width: 30px; + height: 30px; + margin-left: 5px; + margin-top: 5px; + + text-align: center; + border-radius: 15px; + font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; + } +.marker-cluster span { + line-height: 30px; + } \ No newline at end of file diff --git a/noise/public/stylesheets/MarkerCluster.css b/noise/public/stylesheets/MarkerCluster.css new file mode 100644 index 0000000..c60d71b --- /dev/null +++ b/noise/public/stylesheets/MarkerCluster.css @@ -0,0 +1,14 @@ +.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow { + -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in; + -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in; + -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in; + transition: transform 0.3s ease-out, opacity 0.3s ease-in; +} + +.leaflet-cluster-spider-leg { + /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */ + -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in; + -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in; + -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in; + transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in; +} diff --git a/noise/public/stylesheets/style.css b/noise/public/stylesheets/style.css new file mode 100644 index 0000000..47e7e1f --- /dev/null +++ b/noise/public/stylesheets/style.css @@ -0,0 +1,487 @@ +body { + font-family: Verdana, "Lucida Sans Unicode", sans-serif; + font-size: 16px; + width: 100%; + margin-left: auto; + margin-right: auto; + height: 100%; +} + +html { + height: 100%; +} + +#wrapper { + margin: auto; + width: 100%; + text-align: center; + margin-top: 5px; + height: 100%; + position: relative; +} + +a { + color: #0000EE; +} + +header { + background-color: lightgray; +} +header #hline1 { + margin: 0px 1vw 5px 1vw; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; +} +header #h1name { + font-size: 148%; + clear: both; + float: left; + text-align: left; +} +header #buttonsRight { + float: right; + margin-right: 1%; +} +header #buttonsRight #btnSet { + margin-right: 20px; +} +header #buttonsRight #btnHelp { + margin-right: 20px; +} +header #buttonsRight #h1datum { + float: right; + margin-top: 5px; +} + +#fenster { + margin-left: 0.5vw; + background: white; + padding-top: 5px; + width: 99vw; +} +#fenster #navi { + float: left; + margin-left: 0.5vw; +} + +#map { + margin-left: 0.5vw; + margin-top: -50px; + width: 98vw; + height: 80vh; + border-radius: 10px; + border: solid 1px seagreen; + overflow: hidden; + z-index: 1; + position: relative; +} +#map #infoTable table, #map #infoTable tr, #map #infoTable td { + margin: auto; + text-align: center; + border: none !important; +} +#map #infoBtn { + margin-top: 10px; + text-align: center; +} +#map #btnMyLocation { + position: absolute; + top: 80px; + right: 10px; + z-index: 1000; + width: 40px; + height: 40px; + padding: 0; + border: 2px solid rgba(0,0,0,0.2); + border-radius: 4px; + background-color: white; + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + cursor: pointer; + font-size: 20px; + line-height: 1; +} +#map #btnMyLocation:hover { + background-color: #f4f4f4; +} +#map #btnMyLocation:active { + box-shadow: 0 1px 3px rgba(0,0,0,0.4); +} + +#mapdateactsens { + width: 98vw; + font-size: 70%; + padding: 10px 0 10px 0; + overflow: auto; +} +#mapdateactsens #mapdate, #mapdateactsens #actsensors { + width: 33%; + float: left; +} +#mapdateactsens #actsensors { + text-align: left; + margin-left: 1vw; +} + +footer { + background-color: lightgray; +} +footer #author { + font-size: 80%; + width: 98vw; + margin: auto; + padding-bottom: 30px; + padding-top: 10px; + height: 50px; +} +footer #author #mailadr { + float: left; +} +footer #author #versn { + float: right; +} + +.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; +} + +.legend { + line-height: 18px; + color: #555; + font-size: 110%; +} + +.legend i { + width: 18px; + height: 18px; + float: left; + margin-right: 0px; + opacity: 1; +} + +.leglabel { + padding-top: 20px; +} + +.ndmarker { + position: absolute; + font-size: 24px; +} + +.tab-pane { + margin-top: 44px; +} + +#dlive, #dhour, #dday, #ddaynight, #dlden { + width: 100%; + margin: auto; + height: 80vh; +} + +#dialogError { + color: red; +} + +.modal-content { + border: red solid 2px; +} + +.highcharts-tooltip { + zIndex: 5; +} + +.settings { + text-align: left; +} + +.thelabels { + width: 50%; + float: left; +} + +.theInputs { + float: left; +} + +.rows { + width: 100%; + display: block; +} + +.column { + width: 100%; + display: inline-block; + margin-bottom: 15px; +} + +#startday { + width: 40%; +} + +#nbrofdays, #peaklim, #olderthan { + width: 13%; +} + +.addit { + width: 20%; + border: 1px solid blue; +} + +.klein { + font-size: 80%; +} + +.infoTafel th, td { + margin: auto; + text-align: left; + padding: 0 10px; + border: 1px solid black; + background-color: rgba(238, 238, 238, 0.6); +} + +.infoTafel th { + text-align: center; +} + +.infoTafel tr { + height: 20px; +} + +@media only screen and (max-width: 700px) { + .infoTafel, .legend { + display: none; + } +} + +#stday, #nbday, #pklim, #odth { + display: none; +} + +#first { + display: flex; + align-items: center; + justify-content: center; + text-align: center; +} + +.addr { + font-weight: bold; + margin-bottom: 10px; +} + +.bigger { + font-size: 120%; + font-weight: bold; +} + +.highcharts-container { + margin-top: -10px; +} + +.errdiv { + display: none; +} + +/* Button Styles */ +button { + padding: 8px 16px; + border: 1px solid #ccc; + border-radius: 4px; + background-color: #f8f9fa; + color: #333; + cursor: pointer; + font-size: 14px; + font-family: inherit; +} + +button:hover { + background-color: #e9ecef; +} + +button:active { + background-color: #dee2e6; +} + +#btnSet, #btnHelp { + background-color: #2D6AF8; + color: white; + border-color: #2D6AF8; +} + +#btnSet:hover, #btnHelp:hover { + background-color: #1e5ae8; +} + +#btnMyLocation { + background-color: white; +} + +/* Tabs */ +.tabs { + display: flex; + gap: 2px; + background-color: #f1f1f1; + border-bottom: 1px solid #ccc; + padding: 0; + margin: 0 0.5vw; +} + +.tab-button { + background-color: #2D6AF8; + color: white; + border: none; + border-radius: 4px 4px 0 0; + padding: 10px 20px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s; +} + +.tab-button:hover { + background-color: #1e5ae8; +} + +.tab-button.active { + background-color: white; + color: #2D6AF8; + border-bottom: 2px solid white; +} + +.tab-content { + display: block; +} + +.tab-pane { + display: none; +} + +.tab-pane.active { + display: block; +} + +/* Dialog Styles */ +dialog { + border: 1px solid #ccc; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 0; + max-width: 600px; + width: 90%; +} + +dialog::backdrop { + background-color: rgba(0, 0, 0, 0.5); +} + +.dialog-content { + display: flex; + flex-direction: column; +} + +.dialog-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + border-bottom: 1px solid #dee2e6; + background-color: #f8f9fa; +} + +.dialog-title { + margin: 0; + font-size: 1.25rem; + font-weight: 500; +} + +.dialog-close { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + color: #000; + opacity: 0.5; +} + +.dialog-close:hover { + opacity: 1; + background-color: rgba(0, 0, 0, 0.05); + border-radius: 4px; +} + +.dialog-body { + padding: 20px; + overflow-y: auto; + max-height: 60vh; +} + +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 20px; + border-top: 1px solid #dee2e6; + background-color: #f8f9fa; +} + +.dialog-button { + padding: 8px 16px; + border: 1px solid #ccc; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +.dialog-button.primary { + background-color: #2D6AF8; + color: white; + border-color: #2D6AF8; +} + +.dialog-button.primary:hover { + background-color: #1e5ae8; +} + +.dialog-button.secondary { + background-color: #6c757d; + color: white; + border-color: #6c757d; +} + +.dialog-button.secondary:hover { + background-color: #5a6268; +} + +/* Radio buttons */ +.radio-group { + display: flex; + gap: 15px; +} + +.radio-label { + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; +} + +.radio-label input[type="radio"] { + margin: 0; + cursor: pointer; +} + +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/noise/public/stylesheets/style.css.map b/noise/public/stylesheets/style.css.map new file mode 100644 index 0000000..901595e --- /dev/null +++ b/noise/public/stylesheets/style.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["style.sass","style.css"],"names":[],"mappings":"AAYA;EACE,uDAAA;EACA,eAAA;EACA,WAAA;EACA,iBAAA;EACA,kBAAA;EACA,YAAA;ACXF;;ADcA;EACE,YAAA;ACXF;;ADaA;EACE,YAAA;EACA,WAAA;EACA,kBAAA;EAEA,eAAA;EACA,YAAA;EACA,kBAAA;ACXF;;ADaA;EACE,cAAA;ACVF;;ADYA;EACE,2BA9BoB;ACqBtB;ADWE;EACE,uBAAA;EACA,iBAAA;EACA,oBAAA;EACA,cAAA;ACTJ;ADWE;EACE,eAAA;EACA,WAAA;EACA,WAAA;EACA,gBAAA;ACTJ;ADWE;EACE,YAAA;EACA,gBAAA;ACTJ;ADUI;EACE,kBAAA;ACRN;ADSI;EACE,kBAAA;ACPN;ADQI;EACE,YAAA;EACA,eAAA;ACNN;;ADQA;EACE,kBAAA;EACA,iBAAA;EACA,gBAAA;EACA,WAAA;ACLF;ADOE;EACE,WAAA;EACA,kBAAA;ACLJ;;ADOA;EACE,kBAAA;EACA,iBAAA;EACA,WAAA;EACA,YAAA;EACA,mBA7Ec;EA8Ed,0BAAA;EACA,gBAAA;EACA,UAAA;ACJF;ADME;EACE,YAAA;EACA,kBAAA;EACA,uBAAA;ACJJ;ADKE;EACE,gBAAA;EACA,kBAAA;ACHJ;;ADMA;EACE,WAAA;EACA,cAAA;EACA,sBAAA;EACA,cAAA;ACHF;ADKE;EACE,UAAA;EACA,WAAA;ACHJ;ADIE;EACE,gBAAA;EACA,gBAAA;ACFJ;;ADIA;EACE,2BAlGoB;ACiGtB;ADGE;EACE,cAAA;EACA,WAAA;EACA,YAAA;EACA,oBAAA;EACA,iBAAA;EACA,YAAA;ACDJ;ADGI;EACE,WAAA;ACDN;ADGI;EACE,YAAA;ACDN;;ADIA;EACE,gBAAA;EACA,uCAAA;EACA,oCAAA;EACA,uCAAA;EACA,kBAAA;ACDF;;ADGA;EACE,eAAA;EACA,WAAA;ACAF;;ADEA;EACE,iBAAA;EACA,WAAA;EACA,eAAA;ACCF;;ADCA;EACE,WAAA;EACA,YAAA;EACA,WAAA;EACA,iBAAA;EACA,UAAA;ACEF;;ADAA;EACE,iBAAA;ACGF;;ADDA;EACE,kBAAA;EACA,eAAA;ACIF;;ADFA;EACE,gBAAA;ACKF;;ADHA;EACE,WAAA;EACA,YAAA;EACA,YAAA;ACMF;;ADJA;EACE,UAAA;ACOF;;ADLA;EACE,qBAAA;ACQF;;ADNA;EACE,SAAA;ACSF;;ADNA;EACE,gBAAA;ACSF;;ADPA;EACE,UAAA;EACA,WAAA;ACUF;;ADPA;EAEE,WAAA;ACSF;;ADNA;EACE,WAAA;EACA,cAAA;ACSF;;ADPA;EACE,WAAA;EACA,qBAAA;EACA,mBAAA;ACUF;;ADRA;EACE,UAAA;ACWF;;ADTA;EACE,UAAA;ACYF;;ADVA;EACE,UAAA;EACA,sBAAA;ACaF;;ADXA;EACE,cAAA;ACcF;;ADZA;EACE,YAAA;EACA,gBAAA;EACA,eAAA;EACA,uBAAA;EACA,0CAAA;ACeF;;ADbA;EACE,kBAAA;ACgBF;;ADdA;EACE,YAAA;ACiBF;;ADfA;EACE;IACE,aAAA;ECkBF;AACF;ADjBA;EACE,yBArNa;EAsNb,WAAA;ACmBF;;ADjBA;EACE,cAzNa;EA0Nb,yBAAA;ACoBF;;ADjBA;EACE,aAAA;ACoBF;;ADfA;EACE,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,kBAAA;ACkBF;;ADhBA;EACE,iBAAA;EACA,mBAAA;ACmBF;;ADjBA;EACE,eAAA;EACA,iBAAA;ACoBF;;ADlBA;EACE,iBAAA;ACqBF;;ADnBA;EACE,aAAA;ACsBF","file":"style.css"} \ No newline at end of file diff --git a/noise/public/stylesheets/style.sass b/noise/public/stylesheets/style.sass new file mode 100644 index 0000000..717ec42 --- /dev/null +++ b/noise/public/stylesheets/style.sass @@ -0,0 +1,257 @@ +$corner-radius: 10px +$border-color: seagreen +$background: rgb(146, 154, 146) +$divcolor: rgb(162, 227, 162) +$table-stripes: rgb(157, 179, 157) +$fieldset-color: rgb(208, 235, 208) +$button-color: darkslategrey +$header_footer_color: lightgray +$checkboxsize: 1.5 +$inboxwidth: 70px +$navtab-color: #2D6AF8 + +body + font-family: Verdana, 'Lucida Sans Unicode', sans-serif + font-size: 16px + width: 100% + margin-left: auto + margin-right: auto + height: 100% + // background-color: rgba(0,0,0,0.9) + +html + height: 100% + +#wrapper + margin: auto + width: 100% + text-align: center + // background-color: #e9e9e9 + margin-top: 5px + height: 100% + position: relative + +a + color: #0000EE + +header + background-color: $header_footer_color + + #hline1 + margin: 0px 1vw 5px 1vw + padding-top: 10px + padding-bottom: 10px + overflow: auto + + #h1name + font-size: 148% + clear: both + float: left + text-align: left + + #buttonsRight + float: right + margin-right: 1% + #btnSet + margin-right: 20px + #btnHelp + margin-right: 20px + #h1datum + float: right + margin-top: 5px + +#fenster + margin-left: 0.5vw + background: white + padding-top: 5px + width: 99vw + + #navi + float: left + margin-left: 0.5vw + +#map + margin-left: 0.5vw + margin-top: -50px + width: 98vw + height: 80vh + border-radius: $corner-radius + border: solid 1px $border-color + overflow: hidden + z-index: 1 + + #infoTable table, #infoTable tr, #infoTable td + margin: auto + text-align: center + border: none !important + #infoBtn + margin-top: 10px + text-align: center + + +#mapdateactsens + width: 98vw + font-size: 70% + padding: 10px 0 10px 0 + overflow: auto + + #mapdate, #actsensors + width: 33% + float: left + #actsensors + text-align: left + margin-left: 1vw + +footer + background-color: $header_footer_color + + #author + font-size: 80% + width: 98vw + margin: auto + padding-bottom: 30px + padding-top: 10px + height: 50px + + #mailadr + float: left + + #versn + float: right + + +.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 + +.legend + line-height: 18px + color: #555 + font-size: 110% + +.legend i + width: 18px + height: 18px + float: left + margin-right: 0px + opacity: 1 + +.leglabel + padding-top: 20px + +.ndmarker + position: absolute + font-size: 24px + +.tab-pane + margin-top: 44px + +#dlive, #dhour, #dday, #ddaynight, #dlden + width: 100% + margin: auto + height: 80vh + +#dialogError + color: red + +.modal-content + border: red solid 2px + +.highcharts-tooltip + zIndex: 5 + + +.settings + text-align: left + +.thelabels + width: 50% + float: left +// border: 1px solid green + +.theInputs + //width: 60% + float: left +// border: 1px red solid + +.rows + width: 100% + display: block + +.column + width: 100% + display: inline-block + margin-bottom: 15px + +#startday + width: 40% + +#nbrofdays, #peaklim, #olderthan + width: 13% + +.addit + width: 20% + border: 1px solid blue + +.klein + font-size: 80% + +.infoTafel th,td + margin: auto + text-align: left + padding: 0 10px + border: 1px solid black + background-color: rgba(238,238,238,0.6) + +.infoTafel th + text-align: center + +.infoTafel tr + height: 20px + +@media only screen and (max-width: 700px) + .infoTafel, .legend + display: none + +.nav-tabs .nav-item .nav-link + background-color: $navtab-color + color: #FFF + +.nav-tabs .nav-item .nav-link.active + color: $navtab-color + background-color: #FFFFFF + +// for settings: set all to 'none' +#stday, #nbday, #pklim, #odth + display: none + + + +// for first page to show 'Loading ...' +#first + display: flex + align-items: center + justify-content: center + text-align: center + +.addr + font-weight: bold + margin-bottom: 10px + +.bigger + font-size: 120% + font-weight: bold + +.highcharts-container + margin-top: -10px + +.errdiv + display: none + diff --git a/noise/routes/api.js b/noise/routes/api.js new file mode 100644 index 0000000..24c9202 --- /dev/null +++ b/noise/routes/api.js @@ -0,0 +1,72 @@ +import express from 'express' +import axios from 'axios' +import * as prep from '../charts/preparecharts.js' +import { getLanguage } from '../charts/utilities.js' +import { translate as trans } from '../routes/index.js' + +const router = express.Router() + +const disttable = [ + {type: 'live', func: prep.liveData }, + {type: 'havg', func: prep.havgData }, + {type: 'davg', func: prep.davgData }, + {type: 'daynight', func: prep.dayNightData }, + {type: 'lden', func: prep.ldenData }, + {type: 'map', func: prep.mapData }, +] + +let APIHOST = process.env.APIHOST || 'http://localhost:3005' +const API_KEY = process.env.API_KEY + + +/* GET home page. */ +router.get('/:cmd', async function(req, res, next) { +// req.i18n.changeLanguage(req.query.lng) + const calledAs = req.baseUrl + const cmd = req.params.cmd + const pretty = (req.query.pretty !== undefined) + const lng = getLanguage() + + let url = APIHOST + '/api' + req.originalUrl.slice(4) + `&lng=${lng}` + try { + const response = await axios.get(encodeURI(url) , { + headers: {'X-API-Key': API_KEY} + }); + if (response.status !== 200) { + res.json({err: `${trans('ESYSCALL')} status=${response.status}`}) + } + if(response.data.err) { + res.json(response.data) + return + } + // if called as '/api' then directly return the data + if ((calledAs === '/api') || (cmd === 'getoneproperty') || (cmd === 'getaddress') || (cmd === 'getcitycoords')) { + if(pretty) { + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.send(JSON.stringify(response.data,null,2)) + } else { + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.json(response.data) + } + return + } + const options = response.data.options + for(let x of disttable) { + if (x.type === options.data) { + let ret = x.func(options, response.data.values, req) + res.json(ret) + break; + } + } + } catch (e) { + res.json({err: `${trans('ESYSCALL')} ${e}`}) + } +}) + +export default router + + + +// ToDo +// Hier : getsensordata und getmapdata unterbringen und damit den externen Server aufrufen + diff --git a/noise/routes/index.js b/noise/routes/index.js new file mode 100644 index 0000000..88d4520 --- /dev/null +++ b/noise/routes/index.js @@ -0,0 +1,69 @@ +import express from 'express' +import pkg from '../package.json' with { type: "json" } +import { setLanguage } from '../charts/utilities.js' + +const router = express.Router() +const { name, version, date } = pkg + +const renderOpts = (sid, cat) => { + return { + title: 'Noise', + version: version, + date: date.slice(0, 10), + csensor: sid, + category: cat + } +} + +let i18n; + +const translate = (x) => { + return i18n.t(x) +} + +const getIndex = (cat) => { + return 'index_noise' +} + + +/* GET home page. If there is no parameter 'lng=xx', check stored language and call +te page again with correct language (chglang.js)) +*/ +router.get('/', function (req, res, next) { + // req.i18n.changeLanguage('de') + if (req.query.lng === undefined) { + res.render('chglang', {sensorid: ''}) + } else { + i18n = req.i18n + let opts = renderOpts(-1, req.app.get('category')) + setLanguage(req.query.lng) + res.render(getIndex(opts.category), opts) + } +}) + +// +// router.get('/', function(req, res, next) { +// // req.i18n.changeLanguage('de') +// res.render('index', renderOpts(-1)) +// }) + + +router.get('/:sid', async function (req, res, next) { + const sid = req.params.sid + let opts + if (req.query.lng === undefined) { + res.render('chglang', { sensorid: sid }) + } else { + i18n = req.i18n + if (isNaN(sid)) { + opts = renderOpts(-1, req.app.get('category')) + } else { + opts = renderOpts(sid, req.app.get('category')) + } + res.render(getIndex(opts.category), opts) + } +}) + +//export default router + +export { router, translate } \ No newline at end of file diff --git a/noise/views/chglang.pug b/noise/views/chglang.pug new file mode 100644 index 0000000..0fb43fa --- /dev/null +++ b/noise/views/chglang.pug @@ -0,0 +1,13 @@ +doctype html +html + head + title= title + meta(name="viewport" content="width=device-width, initial-scale=1" charset="utf-8") + link(rel='stylesheet', href='/stylesheets/style.css') + script. + const sensorid = '#{sensorid}'; + + body + #first + h1 Loading... + script(type="module" src="/javascripts/checklang.js") \ No newline at end of file diff --git a/noise/views/error.pug b/noise/views/error.pug new file mode 100644 index 0000000..51ec12c --- /dev/null +++ b/noise/views/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/noise/views/index_noise.pug b/noise/views/index_noise.pug new file mode 100644 index 0000000..cdb52ec --- /dev/null +++ b/noise/views/index_noise.pug @@ -0,0 +1,114 @@ +extends layout + +block content + header + #hline1 + #h1name #{t("NoiseMeasurement")} + #buttonsRight + button#btnSet(value='set') #{t("Settings")} + // button#btnHelp(value='help') #{t("Info")} + #h1datum + + #fenster + #spinner + #navi + .tabs#tablist + button.tab-button.active#maptab(data-target="t_map" type="button" role="tab" aria-controls="map" aria-selected="true") #{t("Map")} + button.tab-button#livetab(data-target="t_live" type="button" role="tab" aria-controls="live") #{t("Live")} + button.tab-button#houravgtab(data-target="t_houravg" type="button" role="tab" aria-controls="houravg") #{t("Hour_AVG")} + button.tab-button#dayavgtab(data-target="t_dayavg" type="button" role="tab" aria-controls="dayavg") #{t("Day_AVG")} + button.tab-button#daynighttab(data-target="t_daynight" type="button" role="tab" aria-controls="daynight") #{t("Day_Night")} + button.tab-button#ldentab(data-target="t_lden" type="button" role="tab" aria-controls="lden") LDEN-Index + .tab-content#thetabs + .tab-pane.active#t_map(role="tabpanel") + #map + // button#btnMyLocation(type="button" title=t("MyLocation")) + span 📍 + #maptaberr.errdiv + #mapdateactsens + #actsensors + #mapdate + .tab-pane#t_live(role="tabpanel") + #dlive + #livetaberr.errdiv + .tab-pane#t_houravg(role="tabpanel") + #dhour + #houravgtaberr.errdiv + .tab-pane#t_dayavg(role="tabpanel") + #dday + #dayavgtaberr.errdiv + .tab-pane#t_daynight(role="tabpanel") + #ddaynight + #daynighttaberr.errdiv + .tab-pane#t_lden(role="tabpanel") + #dlden + #ldentaberr.errdiv + + + // Error-Dialog + dialog#dialogError + .dialog-content + .dialog-header + h3.dialog-title #{t("Error")} + button.dialog-close(type="button" aria-label="Close") × + .dialog-body + .dialog-footer + + + // Settings-Dialog + dialog#dialogSettings + .dialog-content + .dialog-header + h3.dialog-title #{t("Settings")} + button.dialog-close(type="button" aria-label="Close") × + .dialog-body.settings + .rows + .column#ccity + label.thelabels(for='city') #{t("CenterMap")}: + input.theInputs#centercity(name="centercity") + .column#stday + label.thelabels(for='startday') #{t("StartDate")}: + input.theInputs#startday(type='date' name='startday') + .column + .column#nbday + label.thelabels(for='nbrofdays') + | #{t("NumberOfDays")}:
+ input.theInputs#nbrofdays(type='number' name='nbrofdays' min="1" max="10") + span.klein   (max. 10) + .column#pklim + label.thelabels(for='peaklim') + | #{t("Count_peaks_over")} + input.theInputs#peaklim(type='number' name='peaklim' min="10" max="130") + |   dbA + span.klein   (max. 130) + .column#odth + label.thelabels(for='olderthan') + | #{t("RemoveFromMap")} + input.theInputs#olderthan(type='number' name='olderthan' min="0" max="52") + |   #{t("weeks")} + span.klein   (max. 52) + .column + .column#sellan + label.thelabels + | #{t("Language")}: + .radio-group + label.radio-label + input#de(type="radio" name="lanbut") + span Deutsch + label.radio-label + input#en(type="radio" name="lanbut" checked) + span English + .dialog-footer + button.dialog-button.secondary#btnClose(type="button") #{t("Close")} + button.dialog-button.primary#btnSave(type="button") #{t("Save_changes")} + + + // Reset-Dialog + dialog#dialogReset + .dialog-content + .dialog-header + h3.dialog-title Info + button.dialog-close(type="button" aria-label="Close") × + .dialog-body + .dialog-footer + button.dialog-button.primary#btnResetOk(type="button") OK diff --git a/noise/views/layout.pug b/noise/views/layout.pug new file mode 100644 index 0000000..2332bff --- /dev/null +++ b/noise/views/layout.pug @@ -0,0 +1,37 @@ +doctype html +html + head + title= title + meta(name="viewport" content="width=device-width, initial-scale=1" charset="utf-8") + link(rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" + integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" + crossorigin="") + link(rel='stylesheet', href='/stylesheets/MarkerCluster.css') + link(rel='stylesheet', href='/spin.js/spin.css') + + + script. + const sysparams = { + version: '#{version}', + date: '#{date}', + csid: '#{csensor}', + category: '#{category}' + }; + + link(rel='stylesheet', href='/stylesheets/style.css') + + body + #wrapper + block content + footer + #author + #mailadr + a(href="mailto:rexfue@gmail.com") mailto:rexfue@gmail.com + #versn #{t('Version')}: #{version} #{t('from')} #{date} + + script(src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" + integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" + crossorigin="") + script(src="/javascripts/leaflet.markercluster.js") + script(src="https://code.highcharts.com/highcharts.js") + script(type="module" src="/javascripts/global.js") \ No newline at end of file diff --git a/readin/.dockerignore b/readin/.dockerignore new file mode 100644 index 0000000..4786954 --- /dev/null +++ b/readin/.dockerignore @@ -0,0 +1,10 @@ +node-modules +.gitignore +.dockerignore +build_and_copy.sh +docker-compose.yml +Dockerfile* +INFLUXSERVER +TRANSFERE +data + diff --git a/readin/.gitignore b/readin/.gitignore new file mode 100644 index 0000000..a7dc966 --- /dev/null +++ b/readin/.gitignore @@ -0,0 +1,9 @@ +data/ +node_modules +.idea +.env +log +docs/Readme.md_x +docs/Readme_tmp.html + + diff --git a/readin/Dockerfile_readin b/readin/Dockerfile_readin new file mode 100644 index 0000000..925c5b0 --- /dev/null +++ b/readin/Dockerfile_readin @@ -0,0 +1,28 @@ +FROM node:22-alpine + +ADD package.json /tmp/package.json +ADD package-lock.json /tmp/package-lock.json +RUN cd /tmp && npm ci +RUN mkdir -p /opt/app && cp -a /tmp/node_modules /tmp/package.json /opt/app/ + +WORKDIR /opt/app +ADD . /opt/app +ADD crontab.tmp /opt/app + +RUN mkdir -p data + +#RUN apk add busybox-initscripts +#RUN apk add --no-cache tzdata +#ENV TZ Europe/Berlin +#RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN crontab crontab.tmp +RUN deluser --remove-home node + +RUN touch cmds.sh \ + && echo 'crond -f' >>cmds.sh + +CMD sh ./cmds.sh + + + diff --git a/readin/Dockerfile_timeseries b/readin/Dockerfile_timeseries new file mode 100644 index 0000000..6cb39ee --- /dev/null +++ b/readin/Dockerfile_timeseries @@ -0,0 +1,27 @@ +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 +ADD crontab.tmp /opt/app + +RUN mkdir -p data + +#RUN apk add busybox-initscripts +#RUN apk add --no-cache tzdata +#ENV TZ Europe/Berlin +#RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN crontab crontab.tmp +RUN deluser --remove-home node + +RUN touch cmds.sh \ + && echo 'crond -f' >>cmds.sh + +CMD sh ./cmds.sh + + + diff --git a/readin/build_and_copy.sh b/readin/build_and_copy.sh new file mode 100755 index 0000000..8d4a210 --- /dev/null +++ b/readin/build_and_copy.sh @@ -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=readin +name=readin + +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' diff --git a/readin/crontab.tmp b/readin/crontab.tmp new file mode 100644 index 0000000..60f118f --- /dev/null +++ b/readin/crontab.tmp @@ -0,0 +1,2 @@ +3-59/5 * * * * cd /opt/app && npm start + diff --git a/readin/deploy.sh b/readin/deploy.sh new file mode 100755 index 0000000..dbe3a12 --- /dev/null +++ b/readin/deploy.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +# Angepasst aus /Projekte/Logbuch/deploy.sh +# Unterschiede: nur linux/amd64, nativer Build auf esprimo (amd64) -> kein buildx/qemu, +# eigener Dockerfile-Name (Dockerfile_readin). + +REGISTRY="docker.citysensor.de" +IMAGE_NAME="readin" +TAG="${1:-latest}" +PLATFORM="linux/amd64" +DOCKERFILE="Dockerfile_readin" +FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}" +BUILD_DATE=$(date +%d.%m.%Y) + +echo "==========================================" +echo "readin Deploy Script (amd64)" +echo "==========================================" +echo "Registry: ${REGISTRY}" +echo "Image: ${IMAGE_NAME}" +echo "Tag: ${TAG}" +echo "Platform: ${PLATFORM}" +echo "Dockerfile: ${DOCKERFILE}" +echo "Build-Datum: ${BUILD_DATE}" +echo "==========================================" +echo "" + +# Hinweis: Login wird uebersprungen, weil die Credentials fuer ${REGISTRY} +# bereits in ~/.docker/config.json liegen (Container laufen bereits aus dieser Registry). +# Bei Bedarf manuell: docker login ${REGISTRY} + +echo ">>> Baue Image (nur ${PLATFORM}, nativ) ..." +docker build \ + --platform "${PLATFORM}" \ + -f "${DOCKERFILE}" \ + -t "${FULL_IMAGE}" \ + . +echo "" + +echo ">>> Pushe ${FULL_IMAGE} ..." +docker push "${FULL_IMAGE}" +echo "" + +# Falls ein anderer Tag als 'latest' gebaut wurde, zusaetzlich latest setzen+pushen +if [ "${TAG}" != "latest" ]; then + echo ">>> Tagge zusaetzlich als :latest ..." + docker tag "${FULL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:latest" + docker push "${REGISTRY}/${IMAGE_NAME}:latest" + echo "" +fi + +echo "==========================================" +echo "Build + Push fertig: ${FULL_IMAGE}" +echo "==========================================" +echo "" +echo "Danach Stack aktualisieren:" +echo " cd /opt/stacks/esprimo/noisesensors && docker compose up -d readin" diff --git a/readin/docker-compose.yml b/readin/docker-compose.yml new file mode 100644 index 0000000..0c23be2 --- /dev/null +++ b/readin/docker-compose.yml @@ -0,0 +1,55 @@ +version: '3.9' + +volumes: + mongo_vol: + influx_vol: + +services: + timeseries: + image: timeseries + environment: + DEVELOP: "true" + MONGOHOST: mongodb + MONGOUSRP: "rexfue:noise4mongo" + MONGOAUTH: "true" + INFLUXHOST: influxdb + INFLUXTOKEN: "q35XUBaElzcy8dDd9HF2_mpeHvYCampZg_9mJNP5jeBQRopq3EWIzNTZ555QLSIXhZC05RXCoFgjiaT7VzyNkQ==" + TYP: "[\"noise\", \"thp\"]" + STORE: "mongo" + volumes: + - ${PWD}/log:/var/log + container_name: timeseries + restart: unless-stopped + + mongodb: + image: mongo:6 + volumes: + - ${PWD}/entries:/docker-entrypoint-initdb.d + - mongo_vol:/data/db + ports: + - "27017:27017" + container_name: mongodb + environment: + - MONGO_INITDB_DATABASE=sensor_data + - MONGO_INITDB_ROOT_USERNAME=${MONGO_ROOT_USERNAME} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_ROOT_PASSWORD} + restart: unless-stopped + + influxdb: + image: influxdb:2.0 + ports: + - '8086:8086' + volumes: + - influx_vol:/var/lib/influxdb2 + environment: + DOCKER_INFLUXDB_INIT_MODE: setup + DOCKER_INFLUXDB_INIT_USERNAME: ${DOCKER_INFLUXDB_INIT_USERNAME} + DOCKER_INFLUXDB_INIT_PASSWORD: ${DOCKER_INFLUXDB_INIT_PASSWORD} + DOCKER_INFLUXDB_INIT_ORG: citysensor + DOCKER_INFLUXDB_INIT_BUCKET: sensor_data + DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${DOCKER_INFLUXTOKEN} + restart: + unless-stopped + container_name: influxdb + + diff --git a/readin/fetchnewdata.js b/readin/fetchnewdata.js new file mode 100644 index 0000000..d0567ea --- /dev/null +++ b/readin/fetchnewdata.js @@ -0,0 +1,141 @@ +// Einlesen aller Sensordaten von sensor.comunity und abspeicher in einer Inlux- bzw. einer Mongo-DB + +// Ausgehend von Version 1.x.x werden hier nun die Daten nach den Sensortypen getrennt gespeichert +// und zwar einstellbar in einer Mongo-DB oder in einer Influx-DB (oder auch in beide). + +// Version: +// +// V 2.0.0 2023-10-13 rxf +// - Erste Version mit der Trennung nach Typen +// +// V 2.0.1 2023-10-15 rxf +// - Auswahl der Typen via Commandline (-t) oder Environment TYP + +// TYP: Ist ein Array von Strings, wenn nur einzelne Typen gespeichert werden sollen, also z.B.: +// ['pm', 'noise'] +const TYP = process.env.TYP || '' +const STORE = (process.env.STORE || 'both').toLowerCase() // 'mongo' | 'influx' | 'both' + +import { doReadfromAPI as readin, statistics } from './readdata.js' +import { constructDBaseEntries as parse} from './parse.js' + +import * as mongo from './mongo.js' +import * as influx from './influx_post.js' +import { logit, logerror } from './logit.js' +import { DateTime } from 'luxon' +import fs from 'fs' +import mod_getopt from 'posix-getopt' +import pkg from './package.json' with { type: "json" } + + +// import nodeSchedule from 'node-schedule' + +const fetchNewData = async (args) => { + let client + let start = DateTime.now(); + try { + client = await mongo.connectMongo() + } catch(e) { + logerror(`Connect to Mongo ${e}`); + logit(`Programmend - Error in connecting mongo\n`) + process.exit(-1); + } + try { + // read data from API or from disk + let dat = await readin() + if (dat.length !== 0) { + // parse the data + let [props, data, idata] = await parse(client, dat, args) + + // write sensor data to mongoDB + if(args.mongo) { + for (const [k,v] of Object.entries(data)) { + if(v.length !== 0) { + await mongo.writeDataArray(client, k+'_sensors', v) + } + } + } + + // write sensor data to influxDB + if(args.influx) { + await influx.influxWrite(idata) + } + + // write properties to mongoDB + await mongo.bulkWrite(client, mongo.property_coll, props) + } + } catch (e) { + logerror(`Catch in main ${e}`); + } finally { + statistics.totalTime = DateTime.now().diff(start,['seconds']).toObject().seconds + await mongo.writeStatistic(client, statistics) + await mongo.closeMongo(client) + } +} + +// Parse command line options +function parse_cmdline(argv) { + let parser = new mod_getopt.BasicParser('i(influx)m(mongo)t:(typ)h(help)v(version)',argv); + let option; + let ret = {influx: STORE === 'both' || STORE === 'influx', mongo: STORE === 'both' || STORE === 'mongo', typ: TYP} + while((option = parser.getopt()) !== undefined) { + switch(option.option) { + case 'i': + ret.mongo = false + break; + + case 'm': + ret.influx = false + break; + + case 't': + let x = option.optarg.trim() + let y = [] + if(x[0] === '[') { + y = JSON.parse(x) + } else { + y.push(x) + } + ret.typ = y + break; + + case 'v': + console.log(`Version: ${pkg.version} from ${pkg.date}`); + console.log(); + process.exit(); + break; + + case 'h': + console.log("Usage: node fetchnewdata.js [-i] [-m] [-t [typ, typ, ..]] [-v] [-h]"); + console.log("Params:"); + console.log(" -i use only InfluxDB to store the data (default use both, InfluxDB and MongoDB))"); + console.log(" -m use only MongoDB to store the data (default use both, InfluxDB and MongoDB)"); + console.log(" -t [ sensorType, ..]: if given, only those sensors will be used (ex: laerm) default: all"); + console.log(" MUST BE AN ARRAY!; allowed types: 'pm', 'noise', 'radiactivity', 'thp', 'gps'.") + console.log(" -v version: show version"); + console.log(" -h this help text"); + console.log("All parameters are optional."); + console.log(); + process.exit(); + break; + + default: + break; + } + } + logit(JSON.stringify(ret)); + return ret; +} + + + +const main = async () => { + const json = JSON.parse(fs.readFileSync('package.json', 'utf8')) + logit(`Programmstart V ${json.version} vom ${json.date}.`); + let args = parse_cmdline(process.argv); + await fetchNewData(args) + logit(`Program end - running time: ${statistics.totalTime} sec\n\n`) +} + +main().catch(console.error) + diff --git a/readin/influx_post.js b/readin/influx_post.js new file mode 100644 index 0000000..938b22f --- /dev/null +++ b/readin/influx_post.js @@ -0,0 +1,100 @@ +// Access to influxDB vie HTTP + +import axios from 'axios' +import { logit, logerror } from './logit.js' +import { DateTime } from 'luxon' +import { statistics } from'./readdata.js' + +let DEVELOP = process.env.DEVELOP || 'false' + +let INFLUXHOST = process.env.INFLUXHOST || "localhost" +let INFLUXPORT = process.env.INFLUXPORT || 8086 +let INFLUXTOKEN = process.env.INFLUXTOKEN || 'empty' +let INFLUXDATABUCKET = process.env.INFLUXDATABUCKET || "sensor_data" +let INFLUXORG = process.env.INFLUXORG || "citysensor" + +const INFLUXURL_READ = `http://${INFLUXHOST}:${INFLUXPORT}/api/v2/query?org=${INFLUXORG}` +const INFLUXURL_WRITE = `http://${INFLUXHOST}:${INFLUXPORT}/api/v2/write?org=${INFLUXORG}&bucket=${INFLUXDATABUCKET}&precision=ms` + +export const influxRead = async (query) => { + let start = DateTime.now() + let data = [] + try { + let ret = await axios({ + method: 'post', + url: INFLUXURL_READ, + data: query, + headers: { + Authorization: `Token ${INFLUXTOKEN}`, + Accept: 'application/csv', + 'Content-type': 'application/vnd.flux' + }, + timeout: 10000, + }) + if (ret.status != 200) { + logerror(`doReadfromAPI Status: ${ret.status}`) + } + data = ret.data + } catch (e) { + logerror(`doReadfromAPI ${e}`) + } + logit(`ReadIn-Time: ${start.diffNow('seconds').toObject().seconds * -1} sec`) + return data +} + + +export const influxWrite = async (data) => { + let start = DateTime.now() + let ret + logit(INFLUXURL_WRITE) + if (DEVELOP === 'true') { + logit(`Token: ${INFLUXTOKEN}`) + } + try { + ret = await axios({ + method: 'post', + url: INFLUXURL_WRITE, + data: data, + headers: { + Authorization: `Token ${INFLUXTOKEN}`, + Accept: 'application/json', + 'Content-Type': 'text/plain; charset=utf-8' + }, + timeout: 10000, + }) + if (ret.status != 204) { + logerror(`doWrite2API Status: ${ret.status}`) + } + } catch (e) { + logerror(`doWrite2API ${e}`) + } + let statname = `writeInfluxData[sensor_data]Time` + statistics[statname] = DateTime.now().diff(start, ['seconds']).toObject().seconds + logit(`Influx-Write-Time: ${start.diffNow('seconds').toObject().seconds * -1} sec`) + return ret +} + +/* +async function main() { + let data = ` + pm,sid=140 P1=12,P2=13 + pm,sid=142 P1=42,P2=13 + pm,sid=143 P1=43,P2=13 + pm,sid=144 P1=44,P2=13 + thp,sid=141 temperature=23.5,humidity=48,pressure=998 + ` + let ret = await influxWrite(data) + process.exit() + + let query = `from(bucket:"sensor_data") +|> range(start: -1mo) +|> filter(fn: (r) => r._measurement == "pm") +|> filter(fn: (r) => r.sid == "140") +` + let erg = await influxRead(query) + console.log(erg) +} + + +main().catch(console.error) +*/ diff --git a/readin/logit.js b/readin/logit.js new file mode 100644 index 0000000..19d8b28 --- /dev/null +++ b/readin/logit.js @@ -0,0 +1,12 @@ +import { DateTime} from 'luxon' + +export function logit(str) { + let s = `${DateTime.now().toISO()} => ${str}`; + console.log(s); +} + +export function logerror(str) { + let s = `${DateTime.utc().toISO()} => *** ERROR *** ${str}`; + console.log(s); +} + diff --git a/readin/mongo.js b/readin/mongo.js new file mode 100644 index 0000000..f90fa27 --- /dev/null +++ b/readin/mongo.js @@ -0,0 +1,202 @@ +/* Interface for MongoDB +*/ +import { MongoClient } from 'mongodb' +import { logit, logerror } from './logit.js' +import { statistics } from './readdata.js' +import { DateTime } from 'luxon' + +let DEVELOP = process.env.DEVELOP || 'false' + +let MONGOHOST = process.env.MONGOHOST || 'localhost' +let MONGOPORT = process.env.MONGOPORT || 27017 +let MONGOAUTH = process.env.MONGOAUTH || 'false' +let MONGOUSRP = process.env.MONGOUSRP || '' +let MONGOBASE = process.env.MONGOBASE || 'sensor_data' + + +let MONGO_URL = 'mongodb://' + MONGOHOST + ':' + MONGOPORT; // URL to mongo database +if (MONGOAUTH == 'true') { + MONGO_URL = 'mongodb://' + MONGOUSRP + '@' + MONGOHOST + ':' + MONGOPORT + '/?authSource=admin'; // URL to mongo database +} + +export const property_coll = 'properties' + +const addandshowstatistics = (client, text, field, start) => { + statistics[field] = DateTime.now().diff(start, ['seconds']).toObject().seconds + logit(`Write ${text} to mongoDB: Time: ${statistics[field]} sec.`) +} + +export const connectMongo = async () => { + try { + if(DEVELOP === 'true') { + logit(`Try to connect to ${MONGO_URL}`) + } else { + logit(`Try to connect to ${'mongodb://' + MONGOHOST + ':' + MONGOPORT}`) + } + let client = await MongoClient.connect(MONGO_URL, { useNewUrlParser: true, useUnifiedTopology: true }) + if ( DEVELOP === 'true') { + logit(`Mongodbase connected to ${MONGO_URL}`) + } else { + logit('Mongodbase connected') + } + return client + } + catch (error) { + throw (error) + } +} + +export const closeMongo = async (client) => { + client.close() +} + + +export const getallProperties = async (client) => { + return await client.db(MONGOBASE).collection(property_coll) + .find().sort({ _id: 1 }).toArray() + +} + +export const checkOneproperty = async (client, sid) => { + return await client.db(MONGOBASE).collection("properties") + .findOne({ _id: sid }) +} + +export const writeOneproperty = async (client, prop) => { + try { + let result = await client.db(MONGOBASE).collection("properties") + .insertOne(prop) + } catch (e) { + if (e.code == 11000) { + return false + } else { + throw (e) + } + } + return true +} + +export const writeProperties = async (client, props) => { + let result + let startAll = DateTime.now(); + let start + let coll = client.db(MONGOBASE).collection("properties") + if (props.new.length !== 0) { + start = DateTime.now(); + try { + result = await coll.insertMany(props.new) + } catch (e) { + logerror(`Write properties new ${e}`) + } + logit(`Write ${props.new.length} properties NEW to mongoDB: Result: ${result.acknowledged}, Time: ${start.diffNow('seconds').toObject().seconds * -1} sec.`) + } + if (props.loc.length !== 0) { + start = DateTime.now() + try { + for (let item of props.loc) { + result = await coll.updateOne({ _id: item._id }, { + $set: { location_id: item.location_id }, + $push: { location: { $each: [item.location[0]], $position: 0 } } + }) + } + } catch (e) { + logerror(`Write properties location ${e}`) + } + logit(`Write ${props.loc.length} properties LOC to mongoDB: Result: ${result.acknowledged}, Time: ${start.diffNow('seconds').toObject().seconds * -1} sec.`) + } + if (props.sname.length !== 0) { + start = DateTime.now() + try { + for (let item of props.sname) { + result = await coll.updateOne({ _id: item._id }, { + $push: { name: { $each: [item.name[0]], $position: 0 } } + }) + } + } catch (e) { + logerror(`Write properties samename ${e}`) + } + logit(`Write ${props.sname.length} properties NAME to mongoDB: Result: ${result.acknowledged}, Time: ${start.diffNow('seconds').toObject().seconds * -1} sec.`) + } + addandshowstatistics(client, 'properties', 'writePropsTime', startAll) +} + +export const writeDataArray = async (client, coll, data) => { + let result + let start = DateTime.now(); + try { + result = await client.db(MONGOBASE).collection(coll) + .insertMany(data, { ordered: false }) + } catch (e) { + if (e.code !== 11000) { + console.error(e) + } + } + let statname = `writeMongoData[${coll}]Time` + addandshowstatistics(client, `${data.length} entries for ${coll}`, `writeMongoData[${coll}]Time`, start) + +// statistics[statname] = DateTime.now().diff(start, ['seconds']).toObject().seconds +// logit(`Write Data for ${coll} to mongoDB: Time: ${statistics[statname]} sec.`) +} + +export const writeStatistic = async (client, stat) => { + let result + let start = DateTime.now(); + let entry = { timestamp: new Date(), ...stat } + try { + result = await client.db(MONGOBASE).collection("statistics") + .insertOne(entry) + } catch (e) { + console.error(e) + } + addandshowstatistics(client, `statistics`, `writeStatisticTime`, start) +} + +export const dropColl = async (client, coll) => { + let start = DateTime.now() + let result + try { + result = await client.db(MONGOBASE).collection(coll).drop() + } catch (e) { + console.error(e) + } + logit(`Drop collection ${coll}: Result: ${result}, Time: ${start.diffNow('second').toObject().seconds * -1} sec.`) +} + + +export const createIndex = async (client, coll) => { + let result + let start = DateTime.now() + try { + result = await client.db(MONGOBASE).collection(coll).createIndex({ "location.loc": "2dsphere" }) + } catch (e) { + console.error(e) + } + logit(`Create-Index: Result: ${result}, Time: ${start.diffNow('second').toObject().seconds * -1} sec.`) +} + +export const bulkUpdateMapdata = async (client, data) => { + let start = DateTime.now() + let result + try { + result = await client.db(MONGOBASE).collection("mapdata") + .bulkWrite(data, { ordered: false }) + } catch (e) { + console.error(e) + } + logit(`Write MapData: Result: ${result}, Time: ${start.diffNow('second').toObject().seconds * -1} sec.`) + return result +} + + +export const bulkWrite = async (client, coll, data) => { + let start = DateTime.now() + let result + try { + result = await client.db(MONGOBASE).collection(coll) + .bulkWrite(data, { ordered: false }) + } catch (e) { + console.error(e) + } + addandshowstatistics(client, `Data for ${coll}`, `writeMongoProperties[${coll}]Time`, start) + return result +} diff --git a/readin/package-lock.json b/readin/package-lock.json new file mode 100644 index 0000000..5bb4c50 --- /dev/null +++ b/readin/package-lock.json @@ -0,0 +1,1020 @@ +{ + "name": "timeseries_mongo", + "version": "2.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "timeseries_mongo", + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "axios": "^1.12.0", + "luxon": "^2.3.1", + "mongodb": "^4.4.1", + "posix-getopt": "^1.2.1" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.977.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.1.tgz", + "integrity": "sha512-KVtQRtc00ES/y+Sc3vYXeP6pCIcNlBJCZOwvqSy8ZpVGmbM5+IG+AfhuTKQ2oXmIVqZJewaGMMpzPkywC6xg0w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@aws-sdk/xml-builder": "^3.972.37", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.29.8", + "@smithy/signature-v4": "^5.6.9", + "@smithy/types": "^4.16.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.972.60", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.60.tgz", + "integrity": "sha512-16Fxa3veKIO6nHJr+vN76z3BkSU7725lzruc5S2SQSTUTbK4TXtmi8033WQljZj4k1+383UsMaNOHN6B76fK5A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/nested-clients": "^3.997.35", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.61", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.61.tgz", + "integrity": "sha512-qihs2ekMb89Nxd2JenCgVFhjbkb3EIo7HEBCBzyZACKVJdrLUZBLOmAE3xr0Sayml8n/jZSzwO/IufIiIzO7PQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.63.tgz", + "integrity": "sha512-yfozsS8wkWZEi/n6IsrodcFKBWZ0iNAezhJbTReMNc0z1Px17qdeAeuL1/wziCAmCZyXiW7QzP75ggJkBQv8jQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/fetch-http-handler": "^5.6.10", + "@smithy/node-http-handler": "^4.9.10", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.6.tgz", + "integrity": "sha512-jGLTW1bj148GL/6/IMlfY2fMYS9FtHOG+NahkFD4y0qkzYudNUahelxryY68/HGMslYuHClk1XaS/3b3eJzEkg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/credential-provider-env": "^3.972.61", + "@aws-sdk/credential-provider-http": "^3.972.63", + "@aws-sdk/credential-provider-login": "^3.972.68", + "@aws-sdk/credential-provider-process": "^3.972.61", + "@aws-sdk/credential-provider-sso": "^3.973.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.67", + "@aws-sdk/nested-clients": "^3.997.35", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/credential-provider-imds": "^4.4.13", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.68", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.68.tgz", + "integrity": "sha512-w6tNci6g7RqFpLhj1f5xseBvaNojb4Pkgp5Jp5apl9hrJtaf2AA+rX9+qlhlWUK6kcyAFYPA7emO+55zj+S98Q==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/nested-clients": "^3.997.35", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.72.tgz", + "integrity": "sha512-blQ7F5QGzylnzeh5549zQLoCAiMHkXFLjFovEMaVy4b2X8JhUu+u9NXro1hyK95YHdVFNmBHKs2hIHtZchxKlQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.61", + "@aws-sdk/credential-provider-http": "^3.972.63", + "@aws-sdk/credential-provider-ini": "^3.973.6", + "@aws-sdk/credential-provider-process": "^3.972.61", + "@aws-sdk/credential-provider-sso": "^3.973.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.67", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/credential-provider-imds": "^4.4.13", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.61", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.61.tgz", + "integrity": "sha512-xzRuj+fUVO4nkafKQJVKAF97kGpeQbfjuwmRrtGZNf42/1dkmcz6o7dswBy7alY0htQn5sCL1GWQYEykviWZkA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.5.tgz", + "integrity": "sha512-fZRjjWhLFelsDoOYjqShQTrIGYC3Pf9Mx9Czf+1ikfQDgktxjze33dVo1q1/ZQ+T0qbtejVoHNHrfD5aJVpv/w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/nested-clients": "^3.997.35", + "@aws-sdk/token-providers": "3.1095.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.67.tgz", + "integrity": "sha512-FTNZ05gkPBA6CKbU3N4zPgybV+stdazwMOya75CmGdcJL7p8Fw/BdHP8WVxJd0mvzyPK2cg/C3gli58Ir4HgCw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/nested-clients": "^3.997.35", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.1095.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1095.0.tgz", + "integrity": "sha512-hcL4iILqep+AieTjJ+eYpBoThcirhlr0rzOqyDKSlFeV15qy8FnvwZFlNmVUFFwHnZjvQz5M9MsRe4XbC6eDfw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.60", + "@aws-sdk/credential-provider-env": "^3.972.61", + "@aws-sdk/credential-provider-http": "^3.972.63", + "@aws-sdk/credential-provider-ini": "^3.973.6", + "@aws-sdk/credential-provider-login": "^3.972.68", + "@aws-sdk/credential-provider-node": "^3.972.72", + "@aws-sdk/credential-provider-process": "^3.972.61", + "@aws-sdk/credential-provider-sso": "^3.973.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.67", + "@aws-sdk/nested-clients": "^3.997.35", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/credential-provider-imds": "^4.4.13", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.35", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.35.tgz", + "integrity": "sha512-2MJfseVG/aXvIyOIBlYA/Oaf6qFDdsu4D8RKsEUdOQpVuLaor0BdxIBBtJLBNQQEe6Ku3YMvLljwb1MwVUpzRw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/signature-v4-multi-region": "^3.996.42", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/fetch-http-handler": "^5.6.10", + "@smithy/node-http-handler": "^4.9.10", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.42.tgz", + "integrity": "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@smithy/signature-v4": "^5.6.9", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1095.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1095.0.tgz", + "integrity": "sha512-65SudS6y4nzaYHybtqcpm3sHe5jLhdMn68HRKS1nUx690BtQeaAQOoujQ+dpOjBATIVGVgKKjEP8tR+U06QJQA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.977.0", + "@aws-sdk/nested-clients": "^3.997.35", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.37.tgz", + "integrity": "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.13.tgz", + "integrity": "sha512-E3Sv4eCYAlKYUTx8S3ioQcDUscOif+8zZ5OnW1IzJ+Tt+EO+ke8mn+Y3FX6N1H79picwbdOavVOb1jPi2EOyrg==", + "license": "MIT", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@smithy/core": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.30.0.tgz", + "integrity": "sha512-dl2yRglDxfzH9uJ4fSo4zTaAHa0zH7+V7BZMRWy8hEYIKT1BiqMUK/CN6T3ADQ3kbA5N1tmUulroJ2UtONS7Kw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.14.tgz", + "integrity": "sha512-QgbuahIb2qxQeZQvNK0sw3aF3JH5zwH8j2lLp5DUasVXexGGMWULAR+7z0omPXFolCP/m5wN9M5lm9EGdSviTQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/core": "^3.30.0", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.6.11", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.11.tgz", + "integrity": "sha512-o0Zkj1nKqJAoq+a+BrkhU39tRftMNjLwpc/z06Frfl43wpbHrJMaSAVZE4vTqlxtVkNaGaT0bIDxOp7tkFTuQQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/core": "^3.30.0", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.9.11", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.11.tgz", + "integrity": "sha512-slbzbz8taEOzoXv/9y34YNBoE+ZHmddLykCgjDAjvMAsu2nM5s2Gzwa5OGF721tD8s+CKeFUBds5lSj9lcbuDg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/core": "^3.30.0", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.6.10", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.10.tgz", + "integrity": "sha512-EXhWePm3SXJAX38npIy4TXL2Aex/OVgCClTjelN2QHw/U+8CQUH8C7AaxsVzQcYieYPseywn48s89DmvuGjiAg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/core": "^3.30.0", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT", + "optional": true + }, + "node_modules/bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "license": "Apache-2.0", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ip-address": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", + "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/luxon": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.2.tgz", + "integrity": "sha512-Yg7/RDp4nedqmLgyH0LwgGRvMEKVzKbUdkBYyCosbHgJ+kaOUx0qzSiSatVc3DFygnirTPYnMM2P5dg2uH1WvA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT", + "optional": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mongodb": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", + "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", + "license": "Apache-2.0", + "dependencies": { + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0" + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/posix-getopt": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/posix-getopt/-/posix-getopt-1.2.1.tgz", + "integrity": "sha512-BbGTiH8MOWAuc6h5yITkSn9k3HP4+QOCV9t6I5F62OrH7zqTHRo08QNsgELRreTBxcvRhbSpMoUnAx77Dz4yUA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/readin/package.json b/readin/package.json new file mode 100644 index 0000000..6a84be4 --- /dev/null +++ b/readin/package.json @@ -0,0 +1,21 @@ +{ + "name": "timeseries_mongo", + "version": "2.0.2", + "date": "2023-12-17", + "description": "", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node fetchnewdata.js >>/var/log/readin.log 2>&1" + }, + "type": "module", + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^1.12.0", + "luxon": "^2.3.1", + "mongodb": "^4.4.1", + "posix-getopt": "^1.2.1" + } +} diff --git a/readin/parse.js b/readin/parse.js new file mode 100644 index 0000000..334d788 --- /dev/null +++ b/readin/parse.js @@ -0,0 +1,243 @@ +// import logit from './logit.js' +import { DateTime } from 'luxon' +import * as mongo from './mongo.js' +import { statistics } from'./readdata.js' +import { logit, logerror } from './logit.js' + +let actualProps = [] +let newProps = [] + +// Check lat/lon and convert to float +function checkLatLon(w) { + let loc = 0.0; + if (!((w === undefined) || (w == null) || (w == ''))) { + try { + loc = parseFloat(w) + } catch (e) { + logerror(`Math error with lat/lon, ${e}`) + } + } + return loc +} + +// Check, if altitude is there. If so, use it, else use 0 +function checkAltitude(alt) { + let altitude = 0; + if(!((alt === undefined) || (alt === ''))) { + try { + altitude = Math.floor(parseFloat(alt)) + } catch (e) { + logerror(`Math error with altitude, ${e}`) + } + } + return altitude +} + +// binary search fir sensor id in property array +const binarySearch = (arr, element, x) => { + let start = 0, end = arr.length - 1 + // Iterate while start not meets end + while (start <= end) { + // Find the mid index + let mid = Math.floor((start + end) / 2) + // If element is present at mid, return True + if (arr[mid][element] === x) return mid + // Else look in left or right half accordingly + else if (arr[mid][element] < x) + start = mid + 1 + else + end = mid - 1 + } + return -1 +} + + +const checkProperties = (item, mapvalues, typ, dt) => { + let now = DateTime.utc().toJSDate() + let entry + // read entry from actualprops + let idx = binarySearch(actualProps, '_id', item.sensor.id) + if (idx === -1) { // not in properties => new sensor + entry = buildNewEntry(item, typ, dt, now) // so build a new entry + } else { + entry = actualProps[idx] // get actual properties + // check for change of location + if (entry.location[0].id !== item.location.id) { // have got a new location + if (newProps.findIndex((obj) => { + return (obj.replaceOne.replacement.location[0].id === item.location.id) + }) === -1) { + const newloc = { + loc: { + type: "Point", + coordinates: [ + checkLatLon(item.location.longitude), + checkLatLon(item.location.latitude) + ] + }, + id: item.location.id, + altitude: checkAltitude(item.location.altitude), + since: now, + exact_loc: item.location.exact_location, + indoor: item.location.indoor, + country: item.location.country + } + entry.location.splice(0, 0, newloc) // insert new location at pos 0 in location array + } + } else { // same location check for change in indoor or exact_location + if (entry.location[0].indoor !== item.location.indoor) { + entry.location[0].indoor = item.location.indoor // update indoor + } + if (entry.location[0].exact_loc !== item.location.exact_location) { + entry.location[0].exact_loc = item.location.exact_location // update exact_location + } + if (entry.location[0].country === '') { + entry.location[0].country = item.location.country // update country + } + } + // Check für new name + if (entry.name[0].name !== item.sensor.sensor_type.name) { // have got a new name + if (newProps.findIndex((obj) => { + return (obj.replaceOne.replacement.name[0].name === item.sensor.sensor_type.name) + }) === -1) { + let newname = { + name: item.sensor.sensor_type.name, + since: now + } + entry.name.splice(0, 0, newname) + } + } + } + // set new mapvalues + entry.values = mapvalues + delete entry.location_id + delete entry.last_seen + delete entry.since + // push this entry to the new proerties array + newProps.push({replaceOne: { filter: {_id: entry._id}, replacement: entry, upsert: true}}) +} + + +const buildNewEntry = (item, typ, dt, now) => { + return { + _id: item.sensor.id, + type: typ, + name: [{ + name: item.sensor.sensor_type.name, + since: now, + }], + location: [{ + loc: { + type: "Point", + coordinates: [ + checkLatLon(item.location.longitude), + checkLatLon(item.location.latitude) + ] + }, + id: item.location.id, + altitude: checkAltitude(item.location.altitude), + since: now, + exact_loc: item.location.exact_location, + indoor: item.location.indoor, + country: item.location.country + }] + } +} + +const types = { + P1: 'pm', P2: 'pm', P0: 'pm', + temperature: 'thp', humidity: 'thp', pressure: 'thp', + noise_LAeq: 'noise', noise_LA_max: 'noise', noise_LA_min: 'noise', + counts_per_minute: 'radioactivity', + lat: 'gps' +}; + + +function getType(typ) { + if(typ in types) { + return types[typ]; + } else { + return 'unknown' + } +} + +export const constructDBaseEntries = async (client, body, args) => { + logit(`Number of entries: ${body.length}`) + logit('Parsing ...') + let start = DateTime.now() + let allLines = { + pm: [], thp: [], noise: [], radioactivity: [], gps: [], unknown: [] + } + let datalines = '' + actualProps = await mongo.getallProperties(client) + try { + for (let item of body) { // check all entries + const dt = item.timestamp.split(' ') + const datetime = new Date(dt[0] + 'T' + dt[1] + 'Z') // extract date of entry as utc + let values = {} + let ival = '' // fetch values + let typ = 'unknown' + let mapvalue = {} + for (let v of item.sensordatavalues) { // for all values + let vtyp = v.value_type; // extract value type + if (typ === 'unknown') { // extract measurement type + typ = getType(vtyp) + } + if(typ === 'noise') { + vtyp = vtyp.slice(6) + } + let val = v.value; // and value + let x + try { + x = parseFloat(val); // convert value to float + if (Number.isNaN(x)) { + x = -9999.9 // default if value is invalid or unknown + } + } catch (err) { + console.log('Math parse float error on value'); + x = -9999.9 // default if value is invalid or unknown + } + values[vtyp] = x + ival += `${vtyp}=${x},` + // if noise sensor precalculate pow10 + if (vtyp == 'LAeq') { + let e10 = Math.pow(10, x / 10) + values.E10tel_eq = e10 + ival += `E10tel_eq=${e10},` + mapvalue.E10tel_eq = e10 + } + mapvalue[vtyp] = x + } + let store = true + if(args.typ) { + if(!args.typ.includes(typ)) { + store = false + } + } + if (store) { + allLines[typ].push({sensorid: item.sensor.id, datetime: datetime, values: values}) + datalines += `${typ},sid=${item.sensor.id} ${ival.slice(0,-1)} ${datetime.getTime()}\n` + mapvalue.timestamp = datetime + checkProperties(item, mapvalue, typ, datetime); // check if new or new location or new sensor + } + } + } catch (e) { + logerror(`constructDBaseEntries ${e}`); + } + // sort allLines on sensorID + + for (const [k,v] of Object.entries(allLines)) { + allLines[k].sort((a, b) => { + if (a.sensorid < b.sensorid) { + return -1 + } else if (a.sensorid > b.sensorid) { + return 1 + } else { + return 0 + } + }) + } + statistics.parseTime = DateTime.now().diff(start, ['seconds']).toObject().seconds + logit(`Parse time: ${statistics.parseTime} sec`) + return [newProps, allLines, datalines] +} + diff --git a/readin/readdata.js b/readin/readdata.js new file mode 100644 index 0000000..878776c --- /dev/null +++ b/readin/readdata.js @@ -0,0 +1,51 @@ +import axios from 'axios' +import * as fs from 'fs' +import { logit, logerror} from'./logit.js' +import { DateTime } from 'luxon' +const API_URL = 'https://api.sensor.community/static/v1/data.json'; // URL to API on 'luftdaten.info' +const SAVE_NAME = './data/aktdata.json'; // filename for actual data + +let LIVE = process.env.LIVE || 'true' +export let statistics = {}; + +export const doReadfromAPI = async () => { + logit(`LIVE = ${LIVE}`) + let start = DateTime.now() + let data = [] + if (LIVE === 'true') { + logit(`Start Reading from API`) + let body + for(let count = 1; count <= 3; count++) { + try { + logit(`Try - ${count}`) + let ret = await axios(API_URL, {timeout: 10000}) + if (ret.status != 200) { + continue + } + data = ret.data + saveDatatoFile(SAVE_NAME, JSON.stringify(data)) + break + } catch (e) { + logerror(`doReadfromAPI ${e}`) + } + } + } else { + logit('Start using data on disk') + data = readDatafromFile(SAVE_NAME) + } + statistics.readInTime = start.diffNow('seconds').toObject().seconds * -1 + statistics.entries = data.length + logit(`ReadIn-Time: ${statistics.readInTime} sec`) + return data +} + + +// die Daten in eimnr Datei zwischenspeichern +function saveDatatoFile(fn, data) { + fs.writeFileSync(fn, data) +} + +// Daten wieder vom File lesen +function readDatafromFile(fn) { + return JSON.parse(fs.readFileSync(fn)) +} diff --git a/readin/ts_rotate b/readin/ts_rotate new file mode 100644 index 0000000..b26c703 --- /dev/null +++ b/readin/ts_rotate @@ -0,0 +1,9 @@ +/var/log/timeseries.log { + daily + rotate 7 + delaycompress + compress + notifempty + missingok +} + diff --git a/sensorapi/.dockerignore b/sensorapi/.dockerignore new file mode 100644 index 0000000..4a9fb4f --- /dev/null +++ b/sensorapi/.dockerignore @@ -0,0 +1,9 @@ +build_and_copy.sh +Dockerfile_sensorapi +docker-compose.yml +.vscode +log +mocks +test +node_modules +doc diff --git a/sensorapi/.gitignore b/sensorapi/.gitignore new file mode 100644 index 0000000..d441c00 --- /dev/null +++ b/sensorapi/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.idea +.env* +.DS_Store +sensorapi.log diff --git a/sensorapi/.vscode/launch.json b/sensorapi/.vscode/launch.json new file mode 100644 index 0000000..960e366 --- /dev/null +++ b/sensorapi/.vscode/launch.json @@ -0,0 +1,75 @@ +{ + // 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": "Launch reception", + "skipFiles": [ + "/**" + ], + "env": { + "DEBUG": "sensorapi:*", + "INFLUXHOST": "192.168.178.190", + "INFLUXTOKEN": "gHGGgjaK0lmM6keMa01JeuDpqOE_vRq8UimsU4QKb2miI5BDh2PfWynEbwKizdJapXy8jVbTat5mVZLQTNmSdw==", + "MONGOHOST": "192.168.178.190", + "MONGOAUTH": "true", + "MONGOUSRP": "admin:mongo4noise", +// "DBASE": "influx", + }, + "program": "${workspaceFolder}/bin/www.js" + }, + { + "type": "node", + "request": "launch", + "name": "Launch esprimo", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/bin/www.js", + "env": { + "DEBUG": "sensorapi:*", + "MONGOUSRP": "admin:mongo4noise", + "MONGOPORT": "27037", + "MONGOHOST": "217.72.203.152", + //"MONGOUSRP": "rexfue:5g2h4j3XC$$C$§442dcdsvDCx", + "MONGOAUTH": "true", + "INFLUXTOKEN": "q35XUBaElzcy8dDd9HF2_mpeHvYCampZg_9mJNP5jeBQRopq3EWIzNTZ555QLSIXhZC05RXCoFgjiaT7VzyNkQ==", + "DEVELOP": "true", + } + }, + { + "type": "node", + "request": "launch", + "name": "Ralf", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/bin/www.js", + "env": { + "MONGOHOST": "192.168.51.22", + "MONGOAUTH": "true", + "MONGOUSRP": "rexfue:s25BMmW2gg", + } + }, + { + "type": "node", + "request": "launch", + "name": "Launch localhost", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/bin/www.js", + "env": { + "INFLUXHOST": "localhost", + "INFLUXTOKEN": "Pt7cDkKS1pAI2a0qsAhfSY97EVsfeNwJxo-ZdiNvfwC4kBiqxmoj7WbR7XkNRr23YELydv_9HXrN2SMofq9vhQ==", + "MONGOHOST": "localhost", + "MONGOPORT": "27017", + "DEBUG": "sensorapi:*" + } + } + ] +} \ No newline at end of file diff --git a/sensorapi/Dockerfile_sensorapi b/sensorapi/Dockerfile_sensorapi new file mode 100644 index 0000000..bc24c99 --- /dev/null +++ b/sensorapi/Dockerfile_sensorapi @@ -0,0 +1,24 @@ +FROM node:22-alpine + +ADD package.json /tmp/package.json +ADD package-lock.json /tmp/package-lock.json +RUN cd /tmp && npm ci +RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/ + +WORKDIR /opt/app +ADD . /opt/app + +# RUN apk add busybox-initscripts +RUN apk add --no-cache tzdata +ENV TZ Europe/Berlin +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN deluser --remove-home node + +RUN touch cmds.sh \ + && echo 'npm start' >>cmds.sh + +CMD sh ./cmds.sh + + + diff --git a/sensorapi/actions/data4map.js b/sensorapi/actions/data4map.js new file mode 100644 index 0000000..7ce7d64 --- /dev/null +++ b/sensorapi/actions/data4map.js @@ -0,0 +1,190 @@ +// Fetch the actual (= newest) data out of the dbase to show it on the map +import {DateTime} from "luxon" +import * as mongo from "../databases/mongo.js" +import { returnOnError } from "../utilities/reporterror.js" + + +// Default distance for center search ( in km) +const DEFAULT_DISTANCE = 10 + +// Value to use fpr map +const value4map = [ + { typ: 'noise', value: 'LA_max'}, + { typ: 'pm', value: 'P1'}, + { typ: 'thp', value: 'temperature'}, + { typ: 'radioactivity', value: 'counts_per_minute'}, +] + +// get value for map from tabel +const getValue4Map = (t) => { + for(let x of value4map) { + if(x.typ == t) { + return x.value + } + } + return '' +} + +// Relations between types and value type +const vtype2measurement = { + P1: 'pm', P2: 'pm', P0: 'pm', + temperature: 'thp', humidity: 'thp', pressure: 'thp', + LAeq: 'noise', + counts_per_minute: 'radioactivity' +}; + + + +// find first value type from measurement +const getfieldfromtype = (typ) => { + for (const [key, value] of Object.entries(vtype2measurement)) { + if (value === typ) { + return key + } + } + return ' ' +} + + +// read the last entries from the influx database +const readLastDates = async (typ) => { + let ret = {values: [], err: null} + let query = ` + from(bucket: "sensor_data") +|> range(start: -2h) +|> filter(fn: (r) => r._measurement == "${typ}" and r._field == "${getValue4Map(typ)}") +|> last() +|> group() +|> map(fn: (r) => ({r with sid: int(v: r.sid)})) +|> sort(columns: ["sid"]) +|> keep(columns: ["_time","sid","_value"]) +` + return await fetchFromInflux(ret, query) +} + +export var getData4map = async (params) => { + let start = DateTime.now() + let ret = {err: null} + + // ***** This function will (at the moment) only be called by internal routines, so there is no need to check the parameters ! + + const typ = params.type + let poly = [] + let south = null, north = null, east = null, west = null, center = null + let distance = DEFAULT_DISTANCE + if(params.box !== undefined) { + let val = params.box.split(',') + for (let i = 0; i < val.length; i++) { + val[i] = parseFloat(val[i]) + } + south = parseFloat(val[1]) + north = parseFloat(val[3]) + east = parseFloat(val[2]) + west = parseFloat(val[0]) +// logit(`getData4map: S=${south} N=${north} E=${east} W=${west}`) + } + if (!((params.poly === undefined) || (params.poly === ' '))){ + poly = JSON.parse(params.poly) + } + if (params.center !== undefined) { + center = params.center + if ((params.distance !== undefined) && + (params.distance >= 1) && (params.distance <= 1000)) { + distance = params.distance + } + } + const aktData = [] + let lastDate = 0 + let query = {type: typ} + + // if polyline or box were given, set query + if (poly.length !== 0) { // polyline given + query.location = { + $geoWithin: { + $geometry: { + type: "Polygon", + coordinates: [poly], + } + } + } + } else if (south !== null) { // box given + query["location.loc"] = { + $geoWithin: { + $box: [ + [west, south], + [east, north] + ] + } + } + } else if (center !== null) { // center point given + query["location.loc"] = { + $nearSphere: { + $geometry: { + type: "Point", + coordinates: center + }, + $maxDistance: distance * 1000 + } + } + } + try { + // fetch mapdata from mongodb + let { properties, err } = await mongo.getallProperties(mongo.properties_collection, query) + if(err) { + return returnOnError(ret, 'NOPROPSFOUND', getData4map.name) + } + let v4map = getValue4Map(typ) + for (let sensor of properties) { + let oneAktData = {} + if (sensor.values !== undefined) { + oneAktData = { + location: sensor.location[0].loc.coordinates, + id: sensor._id, + name: sensor.name[0].name, + indoor: sensor.location[0].indoor, + lastseen: sensor.values.timestamp + } + let now = new Date().getTime() + if(oneAktData.lastseen !== '') { + let diff = now - oneAktData.lastseen.getTime() + if (diff >= 365 * 24 * 3600 * 1000) { + oneAktData.value = -4 + } else if (diff >= 30 * 24 * 3600 * 1000) { + oneAktData.value = -3 + } else if (diff >= 7 * 24 * 3600 * 1000) { + oneAktData.value = -2 + } else if (diff >= 2 * 3600 * 1000) { + oneAktData.value = -1 + } else { + if (sensor.values !== undefined) { + oneAktData.value = Math.round(sensor.values[v4map] * 100) / 100 + } + } + let weeks = Math.round(diff / (7 * 24 * 3600 * 1000)) + oneAktData.weeks = weeks + } + if (sensor.values.timestamp > lastDate) { + lastDate = sensor.values.timestamp + } + } else { + oneAktData.value = -5 + oneAktData.weeks = 0 + oneAktData.lastseen = '' + } + aktData.push(oneAktData) + } + ret = { + err: null, + options: { + lastdate: lastDate, + count: aktData.length, + data: 'map' + }, + values: aktData + } + return ret + } + catch(e) { + return returnOnError(ret, `catch\n${e}`, getData4map.name) + } +} diff --git a/sensorapi/actions/getAKWData.js b/sensorapi/actions/getAKWData.js new file mode 100644 index 0000000..b7384e5 --- /dev/null +++ b/sensorapi/actions/getAKWData.js @@ -0,0 +1,62 @@ +// get data for one sensor + +import {DateTime} from "luxon" +import { logit, logerror } from '../utilities/logit.js' +import * as mongo from "../databases/mongo.js" +import {reportError, returnOnError} from "../utilities/reporterror.js" +import {csv2Json} from "../utilities/csv2json.js" + + +// Fetch all akw data out of the dbase +// router.get('/getakwdata/', async function (req, res) { +export const getakwdata = async (options) => { + let data = {err: null, ...options, count: 0, values: []} + let erg = [] + try { + let rawAKWs = await mongo.readAKWs(options) + if (rawAKWs.err) { + return returnOnError(date, rawAKWs.err, getakwdata.name) + } + for (let item of rawAKWs.values.akws) { + 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 + } + for (let item of rawAKWs.values.th1_akws) { + 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); + } + erg.push(oneAktData); + } + data.values = erg + data.count = erg.length + } catch (e) { + return returnOnError(data, e, getakwdata.name) + } + return data +} + diff --git a/sensorapi/actions/getaddress.js b/sensorapi/actions/getaddress.js new file mode 100644 index 0000000..eecf128 --- /dev/null +++ b/sensorapi/actions/getaddress.js @@ -0,0 +1,80 @@ +import {returnOnError} from "../utilities/reporterror.js" +import axios from 'axios' +import {logit} from "../utilities/logit.js" +import {getOneProperty} from "./getproperties.js" +import { response } from "express" + +const NOMINATIM_URL = `https://nominatim.openstreetmap.org/reverse?lat=${'xx'}&lon=${'yy'}&format=json` +const NOMINATIM_CITY_URL = `https://nominatim.openstreetmap.org/?q="${'xx'}"&format=json` + +export const getCityCoords = async (params) => { + let ret = {coords: [], city: params.city, err: null} + let url = NOMINATIM_CITY_URL.replace('xx', params.city) +// let url = 'https://nominatim.openstreetmap.org/?q="K%C3%B6ln"&format=json' + try { + const response = await axios(url) + if (response.status !== 200) { + return returnOnError(ret, 'RESPSTATUS', getCityCoord.name, response.status) + } + ret.coords = [response.data[0].lat,response.data[0].lon] + logit(JSON.stringify(ret.coords)) + } catch (e) { + return returnOnError(ret, e, getCityCoords.name) + } + return ret +} + +export const getAddress = async (params) => { + let ret = {address: "", err: null} + let {props, err} = await getOneProperty(params) + if (err) { + return returnOnError(ret, err, getAddress.name) + } + let coord = props.location[0].loc.coordinates + let url = NOMINATIM_URL.replace('xx', coord[1]).replace('yy', coord[0]) + try { + const response = await axios(encodeURI(url),{ + headers: { + 'User-Agent': 'Firefox 132.0.1' + } + }); + if (response.status !== 200) { + return returnOnError(ret, 'RESPSTATUS', getAddress.name, response.status) + } + let akt = response.data.address +// logit(JSON.stringify(akt)) + 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 = `${(akt.road ? akt.road : "")} ${(akt.postcode ? akt.postcode : "")} ${city}`; + ret.address = { + street: `${(akt.road ? akt.road : "")}`, + plz: `${(akt.postcode ? akt.postcode : "")}`, + city: `${city}`} + } catch (e) { + return returnOnError(ret, e, getAddress.name) + } + return ret +} +/* + let addr = "Addr"; + try { + let ret = await $.get("api/getprops?sensorid=" + marker.options.name); + if(ret.values[0].address.city == null) { + addr += " unbekannt !" + } else { + let akt = ret.values[0].address; + addr = (akt.street ? akt.street : "") + "  " + (akt.plz ? akt.plz : "") + " " + akt.city; + } + } catch (e) { + console.log("onMarker - getpops", e) + } + console.log("addr:", addr); + return addr; + + */ diff --git a/sensorapi/actions/getproperties.js b/sensorapi/actions/getproperties.js new file mode 100644 index 0000000..e3087eb --- /dev/null +++ b/sensorapi/actions/getproperties.js @@ -0,0 +1,48 @@ +// Fetch the properties for the given sensor + +import * as mongo from "../databases/mongo.js" +import {returnOnError} from "../utilities/reporterror.js" +import checkParams from "../utilities/checkparams.js" + +let readProperties = mongo.readProperties +let readChipData = mongo.readChipData + +// Read properties for sensorid and properties for all other sensors on same location +export const getOneProperty = async (params) => { + let properties = {err: null, props: {}, chip: {}} + let {opts, err} = checkParams(params, {mandatory:[{name:'sensorid', type: 'int'}], optional:[]}) + if (err) { + return returnOnError(properties, err, getOneProperty.name) + } + // read 'chip'-data (special for noise sensors) + const chipdata = await readChipData(opts.sensorid) + if (chipdata.err === null) { + properties.chip = chipdata + } + let sensorEntries = []; + try { + let pp = await readProperties({sid: opts.sensorid}); // read for given sensorID + if ((pp.properties == null) || (pp.error)) { + return returnOnError(properties, 'NOPROPSREAD', getOneProperty.name, opts.sensorid) + } + // now find sensors with same location + let query = {"location.0.id": pp.properties.location[0].id} + let others = await readProperties(query) + if (others.err) { + return returnOnError(properties, 'NOPROPSREAD', getOneProperty.name, others.errortext) + } + if (others.properties.length > 0) { + for (const x of others.properties) { + if(x._id === pp.properties._id) { + continue + } + sensorEntries.push({name: x.name[0].name, sid: x._id}) + } + } + properties.props = pp.properties + properties.props.othersensors = sensorEntries; + } catch (e) { + return returnOnError(properties, e, getOneProperty.name) + } + return properties +} diff --git a/sensorapi/actions/getsensorData.js b/sensorapi/actions/getsensorData.js new file mode 100644 index 0000000..c8e5ece --- /dev/null +++ b/sensorapi/actions/getsensorData.js @@ -0,0 +1,308 @@ +// get data for one sensor + +const DBASE = process.env.DBASE || 'mongo' +import {DateTime} from "luxon" +import * as influx from "../databases/influx.js" +import * as mongo from "../databases/mongo.js" +import {returnOnError} from "../utilities/reporterror.js" +import {csv2Json} from "../utilities/csv2json.js" +import checkParams from "../utilities/checkparams.js" +import {getOneProperty} from "./getproperties.js" +import {getNoiseData} from "../sensorspecials/noise.js" +import {getgeigerData} from "../sensorspecials/geigeract.js" + +// Possible params for the different sensor types +const noiseParams = [ + {name:'data', type: 'string', default: 'live'}, + {name: 'span', type: 'int', default: 1}, + {name: 'daystart', type: 'bool', default: null}, + {name: 'peak', type: 'int', default: 70}, + {name: 'since', type: 'date', default: '1900-01-01T00:00:00Z'}, + {name: 'box', type: 'array', default: null}, + {name: 'out', type: 'string', default: ''}, + {name: 'csv', type: 'bool', default: false}, + {name: 'long', type: 'bool', default: false}, + {name: 'sort', type: 'int', default: 1}, + {name: 'last_seen', type: 'date', default: '1900-01-01T00:00:00Z'}, + {name: 'datetime', type: 'date', default: null} +] +const thpParams = [] +const pmParams = [] +const geigerParams = [ + {name: 'what', type: 'string', default: 'day'}, + {name: 'span', type: 'int', default: 1}, + {name: 'daystart', type: 'bool', default: null}, + {name: 'avg', type: 'int', default: 1}, + {name: 'since', type: 'date', default: '1900-01-01T00:00:00Z'}, + {name: 'box', type: 'array', default: null}, + {name: 'out', type: 'string', default: ''}, + {name: 'csv', type: 'bool', default: false}, + {name: 'long', type: 'bool', default: false}, + {name: 'sort', type: 'int', default: 1}, + {name: 'last_seen', type: 'date', default: '1900-01-01T00:00:00Z'}, + {name: 'datetime', type: 'date', default: null}, + {name: 'moving', type: 'bool', default: false}, +] + +// >>>>>>>>>>>>>>>>>>>>> DUMMIES +async function getPmData(opts) { + +} +async function getThpData(opts) { + +} +// <<<<<<<<<<<<<<<<<<<<< DUMMIES + +// Table for the different sensor types +const sensorTypeTable = [ + { typ: 'thp', possibleParams: thpParams, func: getThpData}, + { typ: 'noise', possibleParams: noiseParams, func: getNoiseData}, + { typ: 'pm', possibleParams: pmParams, func: getPmData}, + { typ: 'radioactivity', possibleParams: geigerParams, func: getgeigerData} +] + +/* Units: +* span -> days +* avg -> minutes +*/ + +// ********************************************* +// calcRange +// +// Calculate the date/time range ro read the data from +// +// If 'datetime' is not given, use 'now()' as end and 'now()' - span as start +// If 'datetime' is given, use it as start and 'datetime'+span as end +// If 'daystart'==true, change start and end so, that they begin at 00:00:00h +// if 'avg' has a value, move start backward ba 'avg' minutes +// +// params: +// opts: Object with different options, specially +// datetime, avg, daystart, span +// +// return: +// Object with start and stop in ISO-Format +// ********************************************* +export const calcRange = (opts) => { + let start, end + let ret = { start: start, stop: end, err: null} + if((opts.datetime === null) || (opts.datetime === undefined) || (opts.datetime === '')) { + start = end = DateTime.local().toUTC() + start = start.minus({days: opts.span}) + } else { + start = end = DateTime.fromISO(opts.datetime).toUTC() + end = end.plus({days: opts.span}) + } + if(opts.daystart) { + start = start.startOf("day") + end = end.startOf("day") + } +// start = start.toUTC() +// end = end.toUTC() + // if(opts.avg !== undefined) { + // start = start.minus({minutes: opts.avg}) + // } + ret.start = `start: ${start.toISO()}` + ret.stop = `stop: ${end.toISO()}` + return ret +} + + +// ********************************************* +// getSensorData +// +// Depending on the parameter 'sensorid', distribute to the special routines for +// the sensors +// +// params: +// params: all parameters from the url +// +// return: +// Returns from the special routines +// ********************************************* +// export const getSensorData = async (params) => { +export async function getSensorData(params) { + let ret = {err: null} + let {opts, err} = checkParams(params, { // check for sensorid + mandatory: [{name: 'sensorid', type: 'int'}], + optional: [] + }) + if (err) { + return returnOnError(ret, err, getSensorData.name) + } + // with the sensorid get the type of this sensor + let erg = await getOneProperty({sensorid: opts.sensorid}) + if (erg.err) { + return returnOnError(ret, erg.err, getSensorData.name) + } + + // distribute to the right routine + for(let item of sensorTypeTable) { + if(item.typ === erg.props.type) { + ret = await item.func(params, item.possibleParams, erg.props) // get the values from database + return ret + } + } + return returnOnError(ret, 'CMNDUNKNOWN', getActData.name) +} + + + + +// export const getActData = async (opts) => { +export async function getActData(opts) { + if (DBASE === 'mongo') { + return await mongo.fetchActData(opts) + } else if (DBASE === 'influx') { + return await influx.fetchActData(opts) + } + return {err: 'DBASEUNKNOWN', values: []} +} + + +// ..../api/getavgdata? +// ToDo: check UTC !!!! +export var getAvgData = async (params) => { + let ret = {data: {count: 0, values: []}, err: null} + let {opts, err} = checkParams(params, + { + mandatory: + [ + {name: 'sensorid', type: 'int'}, + ], + optional: + [ + {name: 'span', type: 'int', default: 1}, + {name: 'datetime', type: 'date', default: null}, + {name: 'avg', type: 'int', default: 10}, + {name: 'moving', type: 'bool', default: true}, + ] + } + ) + if (err) { + return returnOnError(ret, err, getActdata.name) + } + let {start, stop} = calcRange(opts) + if (stop === '') { + ret.data.start = DateTime.now().toUTC().minus({days: `${opts.span}`}).toFormat("yyyy-LL-dd'T'HH:mm:ss'Z'") + } else { + ret.data.start = DateTime.fromISO(opts.datetime, {zone: 'utc'}).toISO() + } + ret.data.span = opts.span + ret.data.moving = opts.moving + ret.data.avg = opts.avg + + let every = opts.moving ? '150s' : `${opts.avg}m` + let period = `${opts.avg}m` + let query = ` + from(bucket: "sensor_data") +|> range(${start}, ${stop}) +|> filter(fn: (r) => r.sid == "${opts.sensorid}") +|> timedMovingAverage(every: ${every}, period: ${period}) +|> keep(columns: ["_time","_field","_value"]) +|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") + ` + return await fetchFromInflux(ret, query) +} + +// ..../api/getlongavg?sensorid=123&span=2 +export var getLongAvg = async (params) => { + let ret = {data: {count: 0, values: []}, err: null} + let {opts, err} = checkParams(params, + { + mandatory: + [ + {name: 'sensorid', type: 'int'}, + ], + optional: + [ + {name: 'span', type: 'int', default: 2}, + ] + } + ) + if (err) { + return returnOnError(ret, err, getActdata.name) + } + ret.data.span = opts.span + let query = ` + from(bucket: "sensor_data") +|> range(start: -${opts.span}d) +|> filter(fn: (r) => r.sid == "${opts.sensorid}") +|> mean() +|> drop(columns: ["_start","_measurement", "sid"]) +|> pivot(rowKey:["_stop"], columnKey: ["_field"], valueColumn: "_value") + ` + return await fetchFromInflux(ret, query) +} + + + + +// ********************************************* +// function getMAPaktData() { +// +// Get the actual data from the database within the bounds for the given sensor typ +// +// params: +// opt: opt.box => region for which to find sensor data +// opt.typ: type of sensor +// +// return +// JSON +// { avgs: [ +// { location: [ 9.00, 48.80 ], id: 29174, lastSeen: "2019-10-23T12:03:00.000Z", max: "65" } +// { location: [ 9.10, 49.80 ], id: 28194, lastSeen: "2019-10-22T16:03:00.000Z", max: "45" } +// ......... +// ], lastDate: "2019-10-29T15:05:59.000Z" } +// +// ********************************************* +export const getMAPaktData = async (opt) => { + let box = opt.box + if ((box == "") || (box == undefined)) { + return {"avgs": [], "lastDate": null} + } + let south = parseFloat(box[0][1]) + let north = parseFloat(box[1][1]) + let east = parseFloat(box[1][0]) + let west = parseFloat(box[0][0]) + let aktData = [] + let lastDate = 0 + let loc = { + location: { + $geoWithin: { + $box: [ + [west, south], + [east, north] + ] + } + }, + typ: opt.typ, + } + let docs = await mongo.readMapdata(loc,0) +// console.log(docs) + for (let i = 0;i < docs.length; i++) { + let item = docs[i] + let oneAktData = {} + oneAktData['location'] = item.location.coordinates + oneAktData['id'] = item._id // ID des Sensors holen + oneAktData['lastSeen'] = item.values.datetime + oneAktData['indoor'] = item.indoor + let dati = item.values.datetime + let dt = new Date(dati) + if ((now - dt) >= 31 * 24 * 3600 * 1000) { // älter als 1 Monat -> + oneAktData['noise_max'] = -2 // -2 zurückgeben + } else if ((now - dt) >= 3600 * 1000) { // älter als 1 Stunde -> + oneAktData['noise_max'] = -1 // -1 zurückgeben + } else { + oneAktData['noise_max'] = -5 // bedutet -> nicht anzeigen + if (item.values.hasOwnProperty('LA_max')) { + oneAktData['noise_max'] = item.values.LA_max.toFixed(0) // und merken + } + if (dati > lastDate) { + lastDate = dati + } + } + aktData.push(oneAktData) // dies ganzen Werte nun in das Array + } + return {"avgs": aktData, "lastDate": lastDate} // alles bearbeitet _> Array senden +} diff --git a/sensorapi/app.js b/sensorapi/app.js new file mode 100644 index 0000000..aa47566 --- /dev/null +++ b/sensorapi/app.js @@ -0,0 +1,66 @@ +import createError from 'http-errors' +import logger from 'morgan' +import express from 'express' +import cookieParser from 'cookie-parser' +import cors from 'cors' +import i18next from 'i18next' +import i18nextMiddleware from 'i18next-http-middleware' +import Backend from 'i18next-fs-backend' +import {logit} from "./utilities/logit.js" +import pkg from './package.json' with { type: "json" } + + +const app = express() + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +import indexRouter from './routes/index.js' +import { apiRouter } from './routes/api.js' +import {fileURLToPath} from "url"; +import path from "path"; + + +i18next + .use(Backend) + .use(i18nextMiddleware.LanguageDetector) + .init({ + backend: { + loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json' + }, + fallbackLng: 'de', + debug: false, + preload: ['de', 'en'] + }); + +app.use(i18nextMiddleware.handle(i18next)); + +app.use(cors()) +app.use(logger('dev')) +app.use(express.json()) +app.use(express.urlencoded({ extended: true })) +app.use(cookieParser()) + +app.use('/', indexRouter) +app.use('/api', apiRouter) + + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + next(createError(404)) +}) + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message + res.locals.error = req.app.get('env') === 'development' ? err : {} + + // render the error page + res.status(err.status || 500) + res.send(`ERROR: ${err.status}, ${err.stack}`) +}) + +logit(`Start of Program Version: ${pkg.version} vom ${pkg.date}`) + +export default app diff --git a/sensorapi/bin/www.js b/sensorapi/bin/www.js new file mode 100644 index 0000000..98160d6 --- /dev/null +++ b/sensorapi/bin/www.js @@ -0,0 +1,93 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +import app from '../app.js' +import Debug from 'debug' +import http from 'http' + +const debug = Debug('sensorapi:*') + +/** + * Get port from environment and store in Express. + */ + +const port = normalizePort(process.env.PORT || '3005'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +const server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + const port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + const addr = server.address(); + const bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); + console.log('Listening on ' + bind); +} diff --git a/sensorapi/build_and_copy.sh b/sensorapi/build_and_copy.sh new file mode 100755 index 0000000..1f07fb5 --- /dev/null +++ b/sensorapi/build_and_copy.sh @@ -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=sensorapi +name=sensorapi + +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' diff --git a/sensorapi/databases/influx.js b/sensorapi/databases/influx.js new file mode 100644 index 0000000..94fa11d --- /dev/null +++ b/sensorapi/databases/influx.js @@ -0,0 +1,166 @@ +// Access to influxDB vie HTTP + +import axios from 'axios' +import { DateTime } from 'luxon' +// import csvParse from 'csv-parser' +import { logit, logerror } from '../utilities/logit.js' +import {returnOnError} from "../utilities/reporterror.js"; +import {csv2Json} from "../utilities/csv2json.js"; + +let INFLUXHOST = process.env.INFLUXHOST || "localhost" +let INFLUXPORT = process.env.INFLUXPORT || 8086 +let INFLUXTOKEN = process.env.INFLUXTOKEN || "" + //"rklEClT22KfdXZhA47eyJhbqcvekb8bcKCqlUG7n72uDSmR2xGvif0CmGJe0WQtXB96y29mmt-9BdsgWA5npfg==" + //"BNR6cGdb006O1T6hQkGcfB8tgH-UPO6QkOPToeAvrP7LATJbCuWi1wYf3HBpVdZQEBxHxNSrNenZsOSMogX-lg==" +let INFLUXDATABUCKET = process.env.INFLUXDATABUCKET || "sensor_data" +let INFLUXORG = process.env.INFLUXORG || "citysensor" + +const INFLUXURL_READ = `http://${INFLUXHOST}:${INFLUXPORT}/api/v2/query?org=${INFLUXORG}` +const INFLUXURL_WRITE = `http://${INFLUXHOST}:${INFLUXPORT}/api/v2/write?org=${INFLUXORG}&bucket=${INFLUXDATABUCKET}` + +const influxRead = async (query) => { + let start = DateTime.now() + logit(`ReadInflux from ${INFLUXURL_READ}`) + let erg = { values: [], err: null} + try { + let ret = await axios({ + method: 'post', + url: INFLUXURL_READ, + data: query, + headers: { + Authorization: `Token ${INFLUXTOKEN}`, + Accept: 'application/csv', + 'Content-type': 'application/vnd.flux' + }, + timeout: 10000, + }) + if (ret.status !== 200) { + return returnOnError(erg, RESPSTATUS, influxRead.name, ret.status) + } + erg.values = ret.data + } catch (e) { + return returnOnError(erg, e, influxRead.name) + } +// logit(`Influx read time: ${start.diffNow('seconds').toObject().seconds * -1} sec`) + return erg +} + + +const influxWrite = async (data) => { + let start = DateTime.now() + let ret + try { + ret = await axios({ + method: 'post', + url: INFLUXURL_WRITE, + data: data, + headers: { + Authorization: `Token ${INFLUXTOKEN}`, + Accept: 'application/json', + 'Content-Type': 'text/plain; charset=utf-8' + }, + timeout: 10000, + }) + if (ret.status !== 204) { + logerror(`doWrite2API Status: ${ret.status}`) + } + } catch (e) { + logerror(`doWrite2API ${e}`) + } + logit(`Influx-Write-Time: ${start.diffNow('seconds').toObject().seconds * -1} sec`) + return ret +} + +const fetchFromInflux = async (ret, query) => { + let { values, err} = await influxRead(query) + if(err) { + if(err.toString().includes('400')) { + return returnOnError(ret, 'SYNTAXURL', fetchFromInflux.name) + } else { + return returnOnError(ret, err, fetchFromInflux.name) + } + } + if (values.length <= 2) { + return returnOnError(ret, 'NODATA', fetchFromInflux.name) + } + ret.values = csv2Json(values) + return ret +} + +export const fetchActData = async (opts) => { + let ret = {err: null, values: []} + let sorting = '' + if(opts.sort) { + if (opts.sort === 1) { + sorting = '|> sort(columns: ["_time"], desc: false)' + } else if (opts.sort === -1) { + sorting = '|> sort(columns: ["_time"], desc: true)' + } + } + // build the flux query + let query = ` + from(bucket: "sensor_data") +|> range(${opts.start}, ${opts.stop}) +|> filter(fn: (r) => r.sid == "${opts.sensorid}") +${sorting} +|> keep(columns: ["_time","_field","_value"]) +|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") +` + return await fetchFromInflux(ret, query) +} + +export const fetchNoiseAVGData = async (opts) => { + let ret = {err: null, values: []} + let small = '|> keep(columns: ["_time", "peakcount", "n_AVG"])' + if (opts.long) { + small = '' + } + let queryAVG = ` +import "math" +threshold = ${opts.peak} + +data = from(bucket: "sensor_data") + |> range(${opts.start}, ${opts.stop}) + |> filter(fn: (r) => r["sid"] == "${opts.sensorid}") +e10 = data + |> filter(fn: (r) => r._field == "E10tel_eq") + |> aggregateWindow(every: 1h, fn: mean, createEmpty: false) + |> map(fn: (r) => ({r with _value: (10.0 * math.log10(x: r._value))})) + |> keep(columns: ["_time","_field","_value"]) + |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") + |> rename(columns: {"E10tel_eq" : "n_AVG"}) +ecnt = data + |> filter(fn: (r) => r._field == "E10tel_eq") + |> aggregateWindow(every: 1h, fn: count, createEmpty: false) + |> keep(columns: ["_time","_field","_value"]) + |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") + |> rename(columns: {"E10tel_eq" : "count"}) +esum = data + |> filter(fn: (r) => r._field == "E10tel_eq") + |> aggregateWindow(every: 1h, fn: sum, createEmpty: false) + |> keep(columns: ["_time","_field","_value"]) + |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") + |> rename(columns: {"E10tel_eq" : "n_sum"}) +peak = data + |> filter(fn: (r) => r._field == "LA_max") + |> aggregateWindow( + every: 1h, + fn: (column, tables=<-) => tables + |> reduce( + identity: {peakcount: 0.0}, + fn: (r, accumulator) => ({ + peakcount: if r._value >= threshold then + accumulator.peakcount + 1.0 + else + accumulator.peakcount + 0.0, + }), + ), + ) + |> keep(columns: ["_time","peakcount"]) + part1 = join( tables: {e10: e10, ecnt: ecnt}, on: ["_time"]) + part2 = join( tables: {esum: esum, peak: peak}, on: ["_time"]) + join( tables: {P1: part1, P2: part2}, on: ["_time"]) + ${small} +` + return await fetchFromInflux(ret, queryAVG) +} diff --git a/sensorapi/databases/mongo.js b/sensorapi/databases/mongo.js new file mode 100644 index 0000000..d4046b3 --- /dev/null +++ b/sensorapi/databases/mongo.js @@ -0,0 +1,424 @@ +/* Interface for MongoDB +*/ +import { MongoClient } from 'mongodb' +import { logit, logerror } from '../utilities/logit.js' +import { DateTime } from 'luxon' +import {returnOnError} from "../utilities/reporterror.js"; + +// const nodemailer = require('nodemailer'); + +let MONGOHOST = process.env.MONGOHOST; +let MONGOPORT = process.env.MONGOPORT; +let MONGOAUTH = process.env.MONGOAUTH; +let MONGOUSRP = process.env.MONGOUSRP; +let MONGOBASE = process.env.MONGOBASE; + +if (MONGOHOST === undefined) { MONGOHOST = 'localhost';} +if (MONGOPORT === undefined) { MONGOPORT = 27017; } +if (MONGOAUTH === undefined) { MONGOAUTH = 'false'; } +if (MONGOBASE === undefined) { MONGOBASE = 'sensor_data'; } + +let MONGO_URL = 'mongodb://'+MONGOHOST+':'+MONGOPORT; // URL to mongo database +if (MONGOAUTH === 'true') { +// MONGO_URL = 'mongodb://'+MONGOUSRP+'@' + MONGOHOST + ':' + MONGOPORT + '/?authSource=' + MONGOBASE; // URL to mongo database + MONGO_URL = 'mongodb://'+MONGOUSRP+'@' + MONGOHOST + ':' + MONGOPORT + '/?authSource=admin'; // URL to mongo database +} + +export const properties_collection = 'properties' + +export const connectMongo = async () => { + try { +// logit(`Try to connect to ${MONGO_URL}`) + let client = await MongoClient.connect(MONGO_URL) +// logit(`Mongodbase connected to ${MONGO_URL}`) + return client + } + catch(error){ + throw(error) + } +} + +const listDatabases = async (client) => { + let databasesList = await client.db().admin().listDatabases(); + + console.log("Databases:"); + databasesList.databases.forEach(db => console.log(` - ${db.name}`)); +} + +/* *************************************************** +// READ routines +******************************************************/ + +// Read properties from the database +export const readProperties = async (query, limit = 0) => { + let ret = {err: null, properties: null} + let client = await connectMongo() + try { + if ("sid" in query) { // if sid is given, read property for sid + ret.properties = await client.db(MONGOBASE).collection(properties_collection).findOne({_id: query.sid}) + } else { // otherwise read props corresponding to query + ret.properties = await client.db(MONGOBASE).collection(properties_collection).find(query).limit(limit).toArray() + } + } catch (e) { + ret.err = e + } + finally { + client.close() + } + return ret +} + +export const readChipData = async (sid) => { + let ret = { err: null, chipdata: null} + let client = await connectMongo() + try { + ret.chipdata = await client.db(MONGOBASE).collection('prop_flux').findOne({_id: sid},{projection: {chip: 1, _id: 0}}) + } catch (e) { + ret.err = e + } + finally { + client.close() + } + return ret +} + + +// read mapdata from database +export const readMapdata = async (query, limit) => { + let ret = {err: null, mapdata: []} + let client = await connectMongo() + try { + ret.mapdata = await client.db(MONGOBASE).collection("mapdata").find(query).limit(limit).toArray() + } catch (e) { + ret.err = e + } + finally { + client.close() + } + return ret +} + + +export const getallProperties = async (coll, query) => { + let ret = {err: null, properties: []} + let client = await connectMongo() + try { + ret.properties = await client.db(MONGOBASE).collection(coll) + .find(query).toArray() + } catch (e) { + ret.err = e + } + finally { + client.close() + } + return ret +} + + +export const getOneproperty = async (sid) => { + let ret = {error: false} + let client = await connectMongo() + try { + ret.properties = await client.db(MONGOBASE).collection(properties_collection) + .findOne({_id: sid}) + } catch (e) { + ret = {error: true, errortext: e} + } + finally { + client.close() + } + return ret +} + + +export const readAKWs = async (options) => { + let ret = {values: { akws: [], th1_akws: []}, err: null} + let erg = [] + let client = await connectMongo() + try { + let docs = await client.db(MONGOBASE).collection("akws") + .find().toArray() + if(docs == null) { + return returnOnError(ret, 'akws - docs == null', readAKWs.name) + } + logit(`getawkdata: data fetched from akws, length= ${docs.length}`); + ret.values.akws = docs + let docs1 = await client.db(MONGOBASE).collection("th1_akws") + .find().toArray() + if(docs1 == null) { + return returnOnError(ret, 'th1_akws - docs == null', readAKWs.name) + } + logit(`getawkdata: data fetched from th1_akws, length= ${docs1.length}`) + ret.values.th1_akws = docs1 + } catch (e) { + return returnOnError(ret, e, readAKWs.name) + } + finally { + client.close() + } + return ret +} + +export const fetchActData = async (opts) => { + let ret = {err: null, values: []} + let start = opts.start.slice(7) + let end = opts.stop.slice(6) + start = DateTime.fromISO(start).toJSDate() + end = DateTime.fromISO(end).toJSDate() + let query = {sensorid: opts.sensorid, datetime: {$gte: start, $lt: end}} + let options = { projection: {_id: 0, values: 1, datetime: 1}, sort: {datetime: 1}} + let client = await connectMongo() + try { + // ret.values = await client.db(MONGOBASE).collection('noise_sensors') + // .find(query, options).toArray() + ret.values = await client.db(MONGOBASE).collection('noise_sensors').aggregate([ + {$match: query}, + {$sort: { datetime: 1}}, + // {$replaceWith: + // { + // '$values.LA_min': '$values.noise_LA_min' + // } + // }, + {$replaceWith: + { + datetime: {$dateToString: {format: '%Y-%m-%dT%H:%M:%SZ', date: '$datetime'}}, + LA_min: '$values.LA_min', + LA_minx: '$values.noise_LA_min', + LA_max: '$values.LA_max', + LAeq: '$values.LAeq', + E10tel_eq: '$values.E10tel_eq' } + }, + // {$project: { + // datetime: {$dateToString: {format: '%Y-%m-%dT%H:%M:%SZ', date: '$datetime'}}, + // _id: 0, values:1 + // }}, + ]).toArray() + } + catch(e) { + ret.err = e + } + finally { + client.close() + } + return ret +} +/* +Try to connect to mongodb://rexfue:s25BMmW2gg@192.168.51.22:27017 +Try to connect to mongodb://rexfue:s25BMmW2gg@192.168.51.22:27017 +*/ + + + + +/* + let docs = await collection.find( + { datetime: + { $gte: start.toDate(), $lt: end.toDate() } + }, + { projection: + {_id:0, E_eq:0, E_mx:0, E_mi:0, E10tel_mx:0, E10tel_mi:0}, sort: {datetime: sort} + }, + ).toArray(); +*/ + +export const fetchgeigerAVGData = async (opts) => { + let docs = [] + let ret = {err: null, values: []} + let start = opts.start.slice(7) + let end = opts.stop.slice(6) + start = DateTime.fromISO(start).toJSDate() + end = DateTime.fromISO(end).toJSDate() + let datRange = {sensorid: opts.sensorid, datetime: {$gte: start, $lt: end}} + let sorting = {datetime: opts.sort}; + let client = await connectMongo() + try { + if(opts.moving) { + docs = await client.db(MONGOBASE).collection('sensors').aggregate([ + { + $sort: sorting + }, // sort by date + { + $match: {sensorid: opts.sensorid} + }, // select only values for given sensor + { + $match: datRange + }, // select only values in give data range + { + $setWindowFields: { + sortBy: {datetime: 1}, + output: { + cpm_avg: { + $avg: "$values.counts_per_minute", + window: { + range: [-60, 0], + unit: "minute" + } + } + } + } + }, + { + $project: {_id:0, cpm_avg: 1, datetime:1, uSvph_avg: { $multiply: ["$cpm_avg", opts.factor]}} + }, + { + $sort: {datetime: 1} + } + ]).toArray(); + } else { + docs = await client.db(MONGOBASE).collection('sensors').aggregate([ + { + $sort: sorting + }, // sort by date + { + $match: {sensorid: opts.sensorid} + }, // select only values for given sensor + { + $match: datRange + }, // select only values in give data range + { $group: { + _id: {$dateTrunc: { + date: "$datetime", + unit: "minute", + binSize: 60 + }}, + cpm_avg: {$avg: "$values.counts_per_minute"}, // calculate the average + } + }, + { $addFields: { datetime: "$_id"}}, // change '_id' to 'datetime + { + $project: {_id:0, uSvph_avg: { $multiply: ["$cpm_avg", opts.factor]}, datetime: 1, cpm_avg: 1} + }, + { + $sort: {datetime: 1} + } + ]).toArray(); + } + } catch(e) { // if there was an error + ret.err = e // log it to console + } + finally { + client.close() + } + ret.values = docs + return ret +} + +export const fetchNoiseAVGData = async (opts) => { + let docs = [] + let ret = {err: null, values: []} + let start = opts.start.slice(7) + let end = opts.stop.slice(6) + start = DateTime.fromISO(start).toJSDate() + end = DateTime.fromISO(end).toJSDate() + let peak = opts.peak; // threshold for peak count + let datRange = {sensorid: opts.sensorid, datetime: {$gte: start, $lt: end}} + let sorting = {datetime: opts.sort}; + let grpId = {$dateToString: {format: '%Y-%m-%dT%H:00:00Z', date: '$datetime'}} + let client = await connectMongo() + try { + docs = await client.db(MONGOBASE).collection('noise_sensors').aggregate([ + {$sort: sorting}, // sort by date + {$match: datRange}, // select only values in give data range + { + $group: { + _id: grpId, + n_average: {$avg: "$values.E10tel_eq"}, // calculate the average + n_sum: {$sum: "$values.E10tel_eq"}, // calculate the sum + peakcount: {$sum: {$cond: [{$gte: ["$values.LA_max", peak]}, 1, 0]}}, // count peaks + count: {$sum: 1}, // count entries + } + }, + {$sort: {_id: 1}}, // sort by result dates + { $addFields: { datetime: "$_id"}}, // change '_id' to 'date' + {$project: opts.long ? { _id:0, n_AVG: { $multiply: [10, {$log10: "$n_average"}]}, datetime:1, peakcount:1, count:1, n_sum:1} : + {_id:0, n_AVG: { $multiply: [10, {$log10: "$n_average"}]}, datetime:1, peakcount:1}} + ]).toArray(); // return not all fields, depending on 'long' + } catch(e) { // if there was an error + ret.err = e // log it to console + } + finally { + client.close() + } + ret.values = docs + return ret +} + +export const fetchAVGData = async (opts) => { +} +/* +// ********************************************* +// getAverageData +// +// Calculate different values per hour +// average of E10tel_eq ( E10tel_eq => 10 ^(LAeq/10) ) +// sum of E10tel_eq, to calculate day, night and eveniung averages +// count, how many values are used for average/sum +// paeakcount, how many values of LAmax are over defined peak value in every hour +// +// params: +// db: Database +// opt: different options (see further down) +// +// return +// depending an calling parameter 'what', not all values will be sent in 'values' +// JSON +// {[ +// { datetime: "2019-10-23T00:00:00Z" , n_AVG: 67.22, n_sum: 32783, count: 24, peakcount: 6 }. +// { datetime: "2019-10-23T01:00:00Z" , n_AVG: 52.89, n_sum: 23561, count: 26, peakcount: 5 }. +// ......... +// ]} +// +// ********************************************* +async function getAverageData(db,opt) { + let start = opt.start; + let end = opt.end; // start and ent time for aggregation + let docs = []; // collect data here + const collection = db.collection('data_' + opt.sid);; + let span = opt.span // date range in days + let peak = opt.peak; // threshold for peak count + let long = opt.long; // true => give extra output + let nbrOfHours = opt.end.diff(opt.start,'hours') + 24; + let datRange = {datetime: {$gte: opt.start.toDate(), $lt: opt.end.toDate()}}; + let sorting = {datetime: opt.sort}; + let grpId = {$dateToString: {format: '%Y-%m-%dT%H:00:00Z', date: '$datetime'}}; + try { + docs = await collection.aggregate([ + {$sort: sorting}, // sort by date + {$match: datRange}, // select only values in give data range + { + $group: { + _id: grpId, + n_average: {$avg: '$E10tel_eq'}, // calculate the average + n_sum: {$sum: '$E10tel_eq'}, // calculate the sum + peakcount: {$sum: {$cond: [{$gte: ["$LA_max", peak]}, 1, 0]}}, // count peaks + count: {$sum: 1}, // count entries + } + }, + {$sort: {_id: 1}}, // sort by result dates + { $addFields: { datetime: "$_id"}}, // change '_id' to 'date' + {$project: opt.long ? { _id:0, n_AVG: { $multiply: [10, {$log10: "$n_average"}]}, datetime:1, peakcount:1, count:1, n_sum:1} : + {_id:0, n_AVG: { $multiply: [10, {$log10: "$n_average"}]}, datetime:1, peakcount:1}} + ]).toArray(); // return not all fields, depending on 'long' + } catch(e) { // if there was an error + console.log(e); // log it to console + } + // To easily extract the values, we copy the data from docs into a new array, so that the + // hour in an element in docs becomes the index into the new array (for every new day this + // index will be incremented by 24). Missing values are marked by: {n_sum=-1, n_AVG=-1}. + let hoursArr = new Array(nbrOfHours); // generate new array + let emptyValues = opt.long ? {n_sum: -1, n_AVG:-1} : {n_AVG:-1}; + hoursArr.fill(emptyValues); // fill with 'empty' value + let startDay = moment.utc(docs[0].datetime).date(); // calc first day + let k = 0; + for (let i=0; i kein buildx/qemu, +# eigener Dockerfile-Name (Dockerfile_sensorapi). + +REGISTRY="docker.citysensor.de" +IMAGE_NAME="sensorapi" +TAG="${1:-latest}" +PLATFORM="linux/amd64" +DOCKERFILE="Dockerfile_sensorapi" +FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}" +BUILD_DATE=$(date +%d.%m.%Y) + +echo "==========================================" +echo "sensorapi Deploy Script (amd64)" +echo "==========================================" +echo "Registry: ${REGISTRY}" +echo "Image: ${IMAGE_NAME}" +echo "Tag: ${TAG}" +echo "Platform: ${PLATFORM}" +echo "Dockerfile: ${DOCKERFILE}" +echo "Build-Datum: ${BUILD_DATE}" +echo "==========================================" +echo "" + +# Hinweis: Login wird uebersprungen, weil die Credentials fuer ${REGISTRY} +# bereits in ~/.docker/config.json liegen. +# Bei Bedarf manuell: docker login ${REGISTRY} + +echo ">>> Baue Image (nur ${PLATFORM}, nativ) ..." +docker build \ + --platform "${PLATFORM}" \ + -f "${DOCKERFILE}" \ + -t "${FULL_IMAGE}" \ + . +echo "" + +echo ">>> Pushe ${FULL_IMAGE} ..." +docker push "${FULL_IMAGE}" +echo "" + +# Falls ein anderer Tag als 'latest' gebaut wurde, zusaetzlich latest setzen+pushen +if [ "${TAG}" != "latest" ]; then + echo ">>> Tagge zusaetzlich als :latest ..." + docker tag "${FULL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:latest" + docker push "${REGISTRY}/${IMAGE_NAME}:latest" + echo "" +fi + +echo "==========================================" +echo "Build + Push fertig: ${FULL_IMAGE}" +echo "==========================================" +echo "" +echo "Danach Stack aktualisieren:" +echo " cd /opt/stacks/esprimo/noisesensors && docker compose up -d sensorapi" diff --git a/sensorapi/doc/Planung.jpg b/sensorapi/doc/Planung.jpg new file mode 100644 index 0000000..c0787b5 Binary files /dev/null and b/sensorapi/doc/Planung.jpg differ diff --git a/sensorapi/doc/Planung.md b/sensorapi/doc/Planung.md new file mode 100644 index 0000000..64dbab9 --- /dev/null +++ b/sensorapi/doc/Planung.md @@ -0,0 +1,23 @@ +##Planung, Ideen und Entscheidungen + +###Websocket +Da Websockets nur dann Vorteile bringen, wenn der Server von sich aus was 'zu sagen' hat, wird in diesem Projekt auf +Websockets verzichtet und der Verkher zwischen der API und dem GUI über normale HTTP-Calls ausgeführt. + +###Planung +![Planung](planung.jpg "Text to show on mouseover"). +####Ergänzungen zur Grafik (2022-05-26) + * Für die 3 GUIs je ein extra Container, so wie jetzt auch (Feinstaub, Geiger, Laerm) + * ab sofort (d.h. möglichst bald) die **influx-** und die **mongo-**Databases parallel laufen lassen + * nach ca. 1 Monat **nur noch mongo** verwenden + * + + + + +####Versions-History + +|Version | Datum | Author +|--------|------|------- +|1.0 | 2022-05-27 | rxf + diff --git a/sensorapi/doc/Readne.md b/sensorapi/doc/Readne.md new file mode 100644 index 0000000..0357693 --- /dev/null +++ b/sensorapi/doc/Readne.md @@ -0,0 +1,6 @@ +# Sensor-API + +### Allgemein +* **ALLE** Zeiten werden in UTC ausgegeben. + Wird beim Aufruf die Zeit im ISO-Format **ohne** Zonenbezeichnung eingegeben, wird sie in UTC umgerechnet und auch so ausgegeben. Wenn die Eingabe schon UTC ist (Z ale letztes Zeichen) dann bleibt es so. + diff --git a/sensorapi/docker-compose.yml b/sensorapi/docker-compose.yml new file mode 100644 index 0000000..3d39bda --- /dev/null +++ b/sensorapi/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.9' + +services: + node: + image: sensorapi + environment: + DEVELOP: "true" + MONGOHOST: ${MONGOHOST} + MONGOPORT: ${MONGOPORT} + INFLUXHOST: ${INFLUXHOST} + INFLUXTOKEN: ${INFLUXTOKEN} + LIVE: "true" + PORT: 3004 + ports: + - "3004:3004" + volumes: + - ${PWD}/log/sensorapi:/var/log + container_name: node_sensorapi + restart: unless-stopped diff --git a/sensorapi/locales/de/translation.json b/sensorapi/locales/de/translation.json new file mode 100644 index 0000000..bd7b225 --- /dev/null +++ b/sensorapi/locales/de/translation.json @@ -0,0 +1,16 @@ +{ + "CMNDUNKNOWN": "Unbekanntes Kommando", + "NOTHING": "Nichts anzuzeigen", + "PARAMNONUM": "Parameter \"xxx\" ist keine Zahl", + "NOTYP": "KeinTyp angegeben", + "NOSENSFOUND": "Kein passender Sensor in den Properties gefunden", + "NOMANDPARAM": "Nitewendiger Parameter xxx nicht angegeben", + "NOPROPSREAD": "Keine properties für Sensor xxx eingelesen", + "NOPARAMETER": "Keine Parameter übergene", + "RESPSTATUS": "Rückgabe-Status: xxx", + "NODATA": "keine Daten gefunden", + "SYNTAXURL": "Syntax Fehler beim Aufruf der URL", + "WRONGTYPE": "Sensor xxx ist kein yyy Sensor", + "NOLASTDATES": "Probleme bein abholen der letzten Daten", + "NOPROPSFOUND": "Properties Collection nicht gefunden" +} diff --git a/sensorapi/locales/en/translation.json b/sensorapi/locales/en/translation.json new file mode 100644 index 0000000..d701d6d --- /dev/null +++ b/sensorapi/locales/en/translation.json @@ -0,0 +1,16 @@ +{ + "CMNDUNKNOWN": "Command not known", + "NOTHING": "Nothing to show", + "PARAMNONUM": "Parameter 'xxx' is not a number", + "NOTYP": "No type given", + "NOSENSFOUND": "No suitable sensors found in properties", + "NOMANDPARAM": "Mandatory parameter xxx not given", + "NOPROPSREAD": "No properties read for sensor xxx", + "NOPARAMETER": "No parameter given", + "RESPSTATUS": "Returned status: xxxx", + "NODATA": "No data found", + "SYNTAXURL": "Syntax error in calling url!", + "WRONGTYPE": "Sensor xxx is not of type yyy", + "NOLASTDATES": "Problems fetching last dates from database", + "NOPROPSFOUND": "Properties collection not found" +} diff --git a/sensorapi/mocks/mongo_mock.js b/sensorapi/mocks/mongo_mock.js new file mode 100644 index 0000000..dc1a2b4 --- /dev/null +++ b/sensorapi/mocks/mongo_mock.js @@ -0,0 +1,74 @@ +// Gibt für eine Property-Anfrage immer den 140er Sensor zurück +// rxf 2022-05-25 + +export const readProperties = async (query, limit = 0) => { + let ret = {error: false} + if ("sid" in query) { // if sid is given, read property for sid + ret.values = { + "_id" : 140, + "location_id" : 65, + "name" : "SDS011", + "since" : "2021-04-21T09:44:12.888Z", + "location" : [ + { + "loc" : + { + "type" : "Point", + "coordinates" : [ 9.16, 48.778 ] + }, + "id" : 65, + "altitude" : 282, + "since" : "2021-04-21T09:44:12.888Z", + "address" : { }, + "exact_loc" : 0, + "indoor" : 0 + } + ] + } + } else { // otherwise read props corresponding to query + ret.values = [ + { + "_id" : 140, + "location_id" : 65, + "name" : "SDS011", + "since" : "2021-04-21T09:44:12.888Z", + "location" : [ + { + "loc" : + { + "type" : "Point", + "coordinates" : [ 9.16, 48.778 ] + }, + "id" : 65, + "altitude" : 282, + "since" : "2021-04-21T09:44:12.888Z", + "address" : { }, + "exact_loc" : 0, + "indoor" : 0 + } + ] + },{ + "_id" : 141, + "location_id" : 65, + "name" : "BME280", + "since" : "2021-04-21T09:44:12.888Z", + "location" : [ + { + "loc" : + { + "type" : "Point", + "coordinates" : [ 9.16, 48.778 ] + }, + "id" : 65, + "altitude" : 282, + "since" : "2021-04-21T09:44:12.888Z", + "address" : { }, + "exact_loc" : 0, + "indoor" : 0 + } + ] + } + ] + } + return ret +} diff --git a/sensorapi/package-lock.json b/sensorapi/package-lock.json new file mode 100644 index 0000000..df48f47 --- /dev/null +++ b/sensorapi/package-lock.json @@ -0,0 +1,2915 @@ +{ + "name": "sensorapi", + "version": "1.4.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sensorapi", + "version": "1.4.2", + "dependencies": { + "@influxdata/influxdb-client": "^1.35.0", + "@influxdata/influxdb-client-apis": "^1.35.0", + "axios": "^1.12.2", + "cookie-parser": "~1.4.7", + "cors": "^2.8.5", + "debug": "~4.4.3", + "express": "^5.1.0", + "http-errors": "~2.0.0", + "i18next": "^25.5.2", + "i18next-fs-backend": "^2.0.0", + "i18next-http-middleware": "^3.8.0", + "luxon": "^3.7.2", + "mongodb": "^6.19.0", + "morgan": "~1.10.1", + "pug": "^3.0.3", + "ws": "^8.18.3" + }, + "bin": { + "www": "bin/www.js" + }, + "devDependencies": { + "mocha": "^11.7.2" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@influxdata/influxdb-client": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@influxdata/influxdb-client/-/influxdb-client-1.35.0.tgz", + "integrity": "sha512-woWMi8PDpPQpvTsRaUw4Ig+nOGS/CWwAwS66Fa1Vr/EkW+NEwxI8YfPBsdBMn33jK2Y86/qMiiuX/ROHIkJLTw==", + "license": "MIT" + }, + "node_modules/@influxdata/influxdb-client-apis": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@influxdata/influxdb-client-apis/-/influxdb-client-apis-1.35.0.tgz", + "integrity": "sha512-+7h6smVPHYBge2rNKgYh/5k+SriYvPMsoJDfbUiQt1vJtpWnElwgBDLDl7Cr6d9XPC+FCI9GP4GQEMv7y8WxdA==", + "license": "MIT", + "peerDependencies": { + "@influxdata/influxdb-client": "*" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.13.tgz", + "integrity": "sha512-E3Sv4eCYAlKYUTx8S3ioQcDUscOif+8zZ5OnW1IzJ+Tt+EO+ke8mn+Y3FX6N1H79picwbdOavVOb1jPi2EOyrg==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/i18next": { + "version": "25.10.10", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.10.10.tgz", + "integrity": "sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-fs-backend": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.6.tgz", + "integrity": "sha512-mYGu6Nt8RIp3X/U8Y+Gej1wo5xmYWmGKLqBGMCC2OCAou5rW5epeHgHmVcw20mJs9Z9+DAPHIxQPNCgFyPRMeg==", + "license": "MIT" + }, + "node_modules/i18next-http-middleware": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.9.7.tgz", + "integrity": "sha512-k4Aa39thnQXGox5ZeKvlOwbDsFz+iGxtPJITWm6xmmErNZOeGMXIoH9XwK3ITUq7lqHaywnIH9gLwOraU4jJvQ==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mongodb": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz", + "integrity": "sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pug": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", + "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.4", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", + "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/sensorapi/package.json b/sensorapi/package.json new file mode 100644 index 0000000..ac5adbe --- /dev/null +++ b/sensorapi/package.json @@ -0,0 +1,36 @@ +{ + "name": "sensorapi", + "version": "1.4.2", + "date": "2023-11-29 16:00 UTC", + "private": true, + "scripts": { + "start": "node ./bin/www.js >>/var/log/sensorapi.log 2>&1", + "dev": "node ./bin/www.js", + "test": "mocha ./test/test.js" + }, + "type": "module", + "bin": { + "www": "./bin/www.js" + }, + "dependencies": { + "@influxdata/influxdb-client": "^1.35.0", + "@influxdata/influxdb-client-apis": "^1.35.0", + "axios": "^1.12.2", + "cookie-parser": "~1.4.7", + "cors": "^2.8.5", + "debug": "~4.4.3", + "express": "^5.1.0", + "http-errors": "~2.0.0", + "i18next": "^25.5.2", + "i18next-http-middleware": "^3.8.0", + "i18next-fs-backend": "^2.0.0", + "luxon": "^3.7.2", + "mongodb": "^6.19.0", + "morgan": "~1.10.1", + "pug": "^3.0.3", + "ws": "^8.18.3" + }, + "devDependencies": { + "mocha": "^11.7.2" + } +} diff --git a/sensorapi/routes/api.js b/sensorapi/routes/api.js new file mode 100644 index 0000000..4a18f9c --- /dev/null +++ b/sensorapi/routes/api.js @@ -0,0 +1,54 @@ +import express from 'express' + +import {getData4map} from "../actions/data4map.js" +import * as getData from "../actions/getsensorData.js"; +import * as getProps from "../actions/getproperties.js"; +import * as getAKWs from "../actions/getAKWData.js"; +import * as holAddr from "../actions/getaddress.js"; + +export const apiRouter = express.Router(); + +const cmdTable = [ + {cmd: 'getactdata', func: getData.getActData}, + {cmd: 'getlongavg', func: getData.getLongAvg}, + {cmd: 'getavgdata', func: getData.getAvgData}, + {cmd: 'getoneproperty', func: getProps.getOneProperty}, + {cmd: 'getakwdata', func: getAKWs.getakwdata}, + {cmd: 'getaddress', func: holAddr.getAddress}, + {cmd: 'getcitycoords', func: holAddr.getCityCoords}, + {cmd: 'getsensordata', func: getData.getSensorData}, + {cmd: 'getmapdata', func: getData4map} +] + +let i18n; + +export const translate = (x) => { + return i18n.t(x) +} + +export const dispatchCommand = async (cmd, table, params, res) => { + let notfound = true + for (let c of table) { + if (c.cmd === cmd) { + notfound = false + let erg = await c.func(params) + if (typeof erg === 'string') { + res.type('text') + res.send(erg) + } else { + res.json(erg) + } + } + } + if (notfound) { + res.json({err: translate('CMNDUNKNOWN')}) + } +} + +// normal routes called from javascript client +apiRouter.get('/:cmd', async (req, res) => { + const params = req.query + params.chart = false + i18n = req.i18n + await dispatchCommand(req.params.cmd, cmdTable, params, res) +}) diff --git a/sensorapi/routes/index.js b/sensorapi/routes/index.js new file mode 100644 index 0000000..edec9e8 --- /dev/null +++ b/sensorapi/routes/index.js @@ -0,0 +1,12 @@ +import express from 'express' +import { translate as trans } from '../routes/api.js' + +const router = express.Router(); + +/* GET home page. */ + +router.get('/', function(req, res, next) { + res.status(200).json({message: trans('NOTHING')}) +}); + +export default router diff --git a/sensorapi/sensorspecials/geigeract.js b/sensorapi/sensorspecials/geigeract.js new file mode 100644 index 0000000..be04164 --- /dev/null +++ b/sensorapi/sensorspecials/geigeract.js @@ -0,0 +1,152 @@ +// Data preparation for fetching geigeractivity data +// rxf 2022-06-24 + +import {returnOnError} from "../utilities/reporterror.js"; +import { calcRange, getActData, getAvgData, getLongAvg } from "../actions/getsensorData.js" +import checkParams from "../utilities/checkparams.js"; +import { setoptionfromtable } from "../utilities/chartoptions.js" +import {DateTime} from 'luxon' +import { fetchgeigerAVGData } from "../databases/mongo.js"; +import { translate as trans } from '../routes/api.js' + + + +const geigeractFilter = (data, opts, actual) => { + let erg = {} + erg.sid = opts.sensorid + erg.sname = opts.sname + erg.values = [] + for (let x of data.values) { + let entry = {} + entry.datetime = x.datetime + if(actual) { + entry.cpm = x.counts_per_minute, + entry.uSvh = x.counts_per_minute / 60 * opts.factor + } else { + entry.cpmAvg = x.counts_per_minute, + entry.uSvhAvg = x.counts_per_minute / 60 * opts.factor + } + erg.values.push(entry) + } + return erg +} + +const getgeigerDWMData = async (opts) => { + if(opts.avg === 1) { + const erg = await getActData(opts) + return erg + } else { + const erg = await fetchgeigerAVGData(opts) + return erg + } +} + +const factorTable = [ + {name: 'SBM-20', factor: 1 / 2.47}, + {name: 'SBM-19', factor: 1 / 9.81888}, + {name: 'Si22G', factor: 0.081438}, + {name: 'J306', factor: 0.06536} +] + +const getfactor = (name) => { + const typ = name.slice((10)) + for(const x of factorTable) { + if (x.name === typ) { + return 1 / 60 * x.factor + } + } + return 1 +} + + +export const getgeigerData = async (params, possibles, props) => { + let ret = {err: null} + + let {opts, erro} = checkParams(params, { + mandatory:[ + {name:'sensorid', type: 'int'}, + ], + optional: possibles + }) + // To be compatible with old API: + if (opts.out === 'csv') { + opts.csv = true + } + if (erro) { + return returnOnError(ret, erro, getNoiseData.name) + } + + opts.factor = getfactor(props.name[0].name) + opts.span = setoptionfromtable(opts.span, 1) + opts.daystart = setoptionfromtable(opts.daystart, false) + opts.avg = setoptionfromtable(opts.avg, 1) + opts.moving = setoptionfromtable(opts.moving, false) + let {start, stop} = calcRange(opts) // calc time range + opts.start = start + opts.stop = stop + let erg = await getgeigerDWMData(opts) // get the data + ret = { + err: erg.err, + options: { + sid: opts.sensorid, + indoor: props.location[0].indoor, + span: opts.span, + start: DateTime.fromISO(opts.start.slice(7)), + data: opts.data, + count: erg.values.length, + avg: opts.avg, + moving: opts.moving + }, + values: erg.values, + } + if(ret.values.length === 0) { + ret.err = trans('NODATA') + } + return ret +} + +/* + + + let erg = { err: null, data: {}} + + let params = { + sensorid: opts.sensorid, + avg: opts.avg, + datetime: opts.start + } + if (opts.what === 'oneday') { + params.span = 1 + } else if (opts.what === 'oneweek') { + params.span = 7 + } else { + params.span = 31 + params.moving = false + params.avg = 1440 + } + let { data, err } = await getAvgData(params) + if (err != null) { + return returnOnError(erg, err, getgeigerData.name) + } + erg.data.geigermovavg = geigeractFilter(data, opts, false) + if (opts.what === 'oneday') { + const { data, err} = await getActData(params) + if (err != null) { + return returnOnError(erg, err, getgeigerData.name) + } + erg.data.geigeractual = geigeractFilter(data, opts, true) + } + if(opts.climatesid && ((opts.what === 'oneday') || (opts.what === 'oneweek'))) { + params.sensorid = opts.climatesid + params.avg = 10 + const { data, err} = await getAvgData(params) + if (err != null) { + return returnOnError(erg, err, getgeigerData.name) + } + data.sid = opts.climatesid + data.sname = opts.climatesname + erg.data.climate = data + } + return erg +} +*/ diff --git a/sensorapi/sensorspecials/noise.js b/sensorapi/sensorspecials/noise.js new file mode 100644 index 0000000..6d37807 --- /dev/null +++ b/sensorapi/sensorspecials/noise.js @@ -0,0 +1,499 @@ +// Data preparation for fetching noise data +// rxf 2023-03-05 +const DBASE = process.env.DBASE || 'mongo' + +import {returnOnError} from "../utilities/reporterror.js"; +import { getActData, getAvgData, getLongAvg, calcRange} from "../actions/getsensorData.js" +import checkParams from "../utilities/checkparams.js"; +import {DateTime} from 'luxon' +import { translate as trans } from '../routes/api.js' +import * as influx from "../databases/influx.js" +import * as mongo from "../databases/mongo.js" +import { setoptionfromtable } from "../utilities/chartoptions.js" + +export const getNoiseData = async (params, possibles, props) => { + let ret = {err: null} + + let {opts, err} = checkParams(params, { + mandatory:[ + {name:'sensorid', type: 'int'}, + ], + optional: possibles + }) + // To be compatible with old API: + if (opts.out === 'csv') { + opts.csv = true + } + if (err) { + return returnOnError(ret, err, getNoiseData.name) + } + // execute function depending on given 'data' + for(let x of whatTable) { + if (x.what === opts.data) { + opts.span = setoptionfromtable(opts.span, x.span) + opts.daystart = setoptionfromtable(opts.daystart, x.daystart) + let {start, stop} = calcRange(opts) // calc time range + opts.start = start + opts.stop = stop + let erg = await x.func(opts) // get the data + if (opts.csv === true) { + ret = erg + } else { + ret = { + err: erg.err, + options: { + sid: opts.sensorid, + indoor: props.location[0].indoor, + span: opts.span, + start: DateTime.fromISO(opts.start.slice(7)).toUTC().toFormat("yyyy-LL-dd'T'HH:mm:ss'Z'"), + data: opts.data, + peak: opts.peak, + count: erg.values.length, + }, + values: erg.values, + } + if (!x.peak) { + delete ret.options.peak + } + if(ret.values.length === 0) { + ret.err = trans('NODATA') + } + } + return ret + } + } + return returnOnError(ret, 'CMNDUNKNOWN', getNoiseData.name) +} + + +// ********************************************* +// getLiveData +// +// Get all actual data from database. Values are stored every 2.5min +// +// params: +// db: Database +// opt: different options (see further down) +// +// return: +// JSON: +// { sid: 29212, span: 1, start: "2019-10-23T00:00", count: 381, values: [ +// { datetime: "2019-10-22T22:05:34.000Z", LAeq: 42.22, LA_min: 39.91, LA_max: 45.18, E10tel_eq: 16672.47212551061 }, +// { datetime: "2019-10-22T22:07:59.000Z", LAeq: 53.72, LA_min: 39.97, LA_max: 63.54, E10tel_eq: 235504.9283896009 }, +// ......... +// ]} +// CSV +// datetime,LAeq,LAmax,LAmin,"10^(LAeq/10)" +// 2019-10-22T22:05:34.000Z,42.22,45.18,39.91,16672.47212551061 +// 2019-10-22T22:07:59.000Z,53.72,63.54,39.97,235504.9283896009 +// 2019-10-22T22:15:16.000Z,44.02,48.99,42.14,25234.807724805756 +// .... +// +// ********************************************* +const getLiveData = async (opts) => { + const erg = await getActData(opts) + if (opts.csv) { + let csvStr = "datetime,LAeq,LAmax,LAmin,10^(LAeq/10)\n" + if (!erg.err) { + for (let item of erg.values) { + if (item.n_AVG != -1) { + csvStr += item.datetime + ',' + + item.LAeq + ',' + + item.LA_max + ',' + + item.LA_min+ ',' + + item.E10tel_eq + '\n' + } + } + } + return csvStr + } + return erg +} + + +// ********************************************* +// gethavgData +// +// Get average per hour, default: 5 days +// +// params: +// db: Database +// opt: different options (see further down) +// +// return: +// JSON: +// { sid: 29212, span: 5, start: "2019-11-01T23:00:00Z", average: 'hour', peak: 70, count: 120, values: [ +// { datetime: "2019-10-22T23:00:00.000Z", n_AVG: 58.27, peakcount: 3 }, +// { datetime: "2019-10-23T00:00:00.000Z", n_AVG: 45.77, peakcount: 4 }, +// { datetime: "2019-10-23T01:00:00.000Z", n_AVG: 62.34, peakcount: 6 }, +// ......... +// ]} + +// CSV: +// datetime,n_AVG,peakcount +// 2019-10-22T23:00:00.000Z,58.27,3 +// 2019-10-23T00:00:00.000Z,45.77,4 +// 2019-10-23T01:00:00.000Z,62.34,6 +// .... +// +// ********************************************* +const gethavgData = async (opts, props) => { + let erg = await getNoiseAVGData(opts) + if (opts.csv) { + let csvStr = "datetime,n_AVG,peakcount\n" + if (!erg.err) { + for (let item of erg.values) { + if (item.n_AVG != -1) { + csvStr += item.datetime + ',' + item.n_AVG + ',' + item.peakcount + '\n' + } + } + } + return csvStr + } + return {err: erg.err, values: erg.values} +} + + +// ********************************************* +// getdavgData +// +// Get average per day , default: 30 days +// +// params: +// db: Database +// opt: different options (see further down) +// +// return: +// JSON: +// { sid: 29212, span: 30, start: "2019-10-23T00:00", average: 'day', peak: 70, count: 30, values: [ +// { datetime: "2019-10-22T23:00:00.000Z", n_AVG: 58.27, peakcount: 300 }, +// { datetime: "2019-10-23T23:00:00.000Z", n_AVG: 62.34, peakcount: 245 }, +// ......... +// ]} +// +// CSV: +// datetime,n_AVG,peakcount +// 2019-10-22T23:00:00.000Z,58.27,300 +// 2019-10-23T23:00:00.000Z,62.34,245 +// .... +// +// ********************************************* +async function getdavgData(opts) { + opts.long = true; + let erg = await getNoiseAVGData(opts); + let val = []; + let csvStr = 'datetime,n_AVG,peakcount\n'; + if (!erg.err) { + for (let i = 0; i < erg.values.length; i += 24) { + let sum = 0; + let count = 0; + let pk = 0; + let werte = {}; + for (let k = 0; k < 24; k++) { + const item = erg.values[i + k] + if ((item != null) && (item.n_sum != -1)) { + sum += item.n_sum; + count += item.count; + pk += item.peakcount; + if (werte.datetime === undefined) { + let dt = DateTime.fromISO(item.datetime) + werte.datetime = dt.startOf('day').toFormat("yyyy-LL-dd'T'HH:mm:ss'Z'") + } + } + } + if (count === 0) { + werte.n_AVG = -1 + } else { + werte.n_AVG = 10 * Math.log10(sum / count); + } + werte.peakcount = pk; + if (opts.csv) { + csvStr += werte.datetime + ',' + werte.n_AVG + ',' + werte.peakcount + '\n' + } else { + val.push(werte); + } + } + } + if (opts.csv) { + return csvStr; + } + return {err: erg.err, values: val} +} + +// addDatetime +// add the actual date to wert, if werte is undefined +const addDatetime = (werte, item) => { + if (werte.datetime === undefined) { + let dt = DateTime.fromISO(item.datetime) + werte.datetime = dt.startOf('day').toFormat("yyyy-LL-dd'T'HH:mm:ss'Z'") + } +} + + + +// ********************************************* +// getdaynightData +// +// Get average for day (6h00 - 22h00) and night (22h00 - 6h00) separated +// Use the hour average calculation, which brings the sum and the count for every hour +// then add these values up for the desired time range and calculate the average. +// +// The night-value of the last day is always 0, because the night is not complete (day is +// over at 24:00 and the night lasts til 6:00) +// +// params: +// db: Database +// opt: different options (see further down) +// +// return +// JSON +// { sid: 29212, span: 30, start: "2019-09-29", count: 30, values: [ +// { date: "2019-09-29", n_dayAVG: 49.45592437272605, n_nightAVG: 53.744277577490614 }, +// { date: "2019-09-30", n_dayAVG: 51.658169450663465, n_nightAVG: 47.82407695888631 }, +// ......... +// ]} +// CSV +// datetime,n_dayAVG,n_nightAVG +// 2019-09-29,49.45592437272605,53.744277577490614 +// 2019-09-30,51.658169450663465,47.82407695888631 +// .... +// +// ********************************************* +async function getdaynightData(opts) { + opts.long = true; + let erg = await getNoiseAVGData(opts); + let val = []; + let csvStr = 'datetime,n_dayAVG,n_nightAVG\n'; + if (!erg.err) { + let done = false; + let dt; + // The received hourly data array always (!!) starts at 0h00 local (!) time. + // So to calculate day values, we skip the first 6 hour and start from there + // now we add 16 hour for day and following 8 hour for night + const length = erg.values.length + for (let i = 6; i < length;) { + let dsum = 0, dcnt = 0; + let nsum = 0, ncnt = 0; + let werte = {}; + for (let k = 0; k < 16; k++, i++) { + if ( i < length) { + const item = erg.values[i] + if (item.n_sum != -1) { + addDatetime(werte, item) + dsum += item.n_sum; + dcnt += item.count; + } + } + } + if (i < (length - 8)) { + const item = erg.values[i] + for (let k = 0; k < 8; k++, i++) { + if (i < length) { + if (item.n_sum != -1) { + addDatetime(werte, item) + nsum += item.n_sum; + ncnt += item.count; + } + } + } + } else { + done = true; + } + if (dcnt != 0) { + werte.n_dayAVG = 10 * Math.log10(dsum / dcnt); + } else { + werte.n_dayAVG = 0; + } + if (ncnt != 0) { + werte.n_nightAVG = 10 * Math.log10(nsum / ncnt); + } else { + werte.n_nightAVG = 0; + } + if (opts.csv) { + csvStr += werte.datetime + ',' + werte.n_dayAVG + ',' + werte.n_nightAVG + '\n' + } else { + val.push(werte); + } + if (done) { + break; + } + } + } + if (opts.csv) { + return csvStr; + } + return {err: erg.err, values: val} +} + + +// ********************************************* +// getLdenData +// +// Use hour averages to calculate the LDEN. +// Formula: +// LDEN = 10 * log10 ( 1/24 ( (12 * 10^(Lday/10)) + (4*10^((Levn+5)/10) + (8*10^((Lnight+10)/10)) ) +// +// params: +// db: Database +// sid: sensor number +// opt: different options (see further down) +// +// return: +// JSON: +// { sid: 29212, span: 30, start: "2019-09-29", count: 30, values: [ +// { lden: 59.53553743437777, date: "2019-09-29" }, +// { lden: 55.264733497513554, date: "2019-09-30" }, +// ......... +// ]} +// CSV +// datetime,lden +// 2019-09-29,59.53553743437777 +// 2019-09-30,55.264733497513554 +// .... +// +// ********************************************* +async function getLdenData(opts) { + opts.long = true; + let erg = await getNoiseAVGData(opts); + let val = []; + let csvStr = 'datetime,lden\n'; + if (!erg.err) { + let done = false; + const calcAVG = (sum, cnt) => { + if (cnt != 0) { + return (10 * Math.log10(sum / cnt)); + } else { + return 0; + } + } + + // The received hourly data array always (!!) starts at 0h00 local (!) time. + // So to calculate day values, we skip the first 6 hour and start from there + // now we add 12 hour for day and following 4 hour for evening and + // additional 8 hours for night + const length = erg.values.length + for (let i = 6; i < length;) { + let dsum = 0, dcnt = 0; + let nsum = 0, ncnt = 0; + let esum = 0, ecnt = 0; + let werte = {}; + let dayAVG = 0, evnAVG = 0, nightAVG = 0; + for (let k = 0; k < 12; k++, i++) { + if ( i < length) { + const item = erg.values[i] + if (item.n_sum != -1) { + addDatetime(werte, item) + dsum += item.n_sum; + dcnt += item.count; + } + } + } + for (let k = 0; k < 4; k++, i++) { + if ( i < length) { + const item = erg.values[i] + if (item.n_sum != -1) { + addDatetime(werte, item) + esum += item.n_sum; + ecnt += item.count; + } + } + } + if (i < (erg.values.length - 8)) { + for (let k = 0; k < 8; k++, i++) { + if (i < length) { + const item = erg.values[i] + if (item.n_sum != -1) { + addDatetime(werte, item) + nsum += item.n_sum; + ncnt += item.count; + } + } + } + } else { + done = true; + } + dayAVG = calcAVG(dsum, dcnt); + evnAVG = calcAVG(esum, ecnt); + nightAVG = calcAVG(nsum, ncnt); + // Calculate LDEN: + let day = 12 * Math.pow(10, dayAVG / 10); // ... and calculate the LDEN values following ... + let evn = 4 * Math.pow(10, (evnAVG + 5) / 10); // ... the LDEN formaula (see function description) + let night = 8 * Math.pow(10, (nightAVG + 10) / 10); + werte.lden = 10 * Math.log10((day + evn + night) / 24); + if (opts.csv) { + csvStr += werte.datetime + ',' + werte.lden + '\n' + } else { + val.push(werte); + } + if (done) { + break; + } + } + } + if (opts.csv) { + return csvStr; + } + return {err: erg.err, values: val} +} + + +const getAPIprops = (opt) => { + +} + +const getNoiseAVGData = async (opts) => { + let ret + if (DBASE === 'mongo') { + ret = await mongo.fetchNoiseAVGData(opts) + } else if (DBASE === 'influx') { + ret = await influx.fetchNoiseAVGData(opts) + // Influx stores the average from 00:00h to 01:00h as 01:00h, so we have to shift the time 1 hour back + for (let x=0; x < ret.values.length; x++) { + ret.values[x].datetime = DateTime.fromISO(ret.values[x].datetime).toUTC().minus({hours:1}).toFormat("yyyy-LL-dd'T'HH:mm:ss'Z'") + } + } else { + ret.err = 'DBASEUNKNOWN' + } + + if(ret.err) { + return returnOnError(ret, ret.err, getNoiseAVGData.name) + } + if(ret.values.length === 0) { + return returnOnError(ret, 'NODATA', getNoiseAVGData.name) + } + // The times are always the END of the period (so: period from 00:00h to 01:00h -> time is 01:00) + + // To easily extract the values, we copy the data from docs into a new array, so that the + // hour in an element in docs becomes the index into the new array (for every new day this + // index will be incremented by 24). Missing values are marked by: {n_sum=-1, n_AVG=-1}. + // For havg add the missed hours to the arry + let emptyValues = {n_AVG: -1} + if (opts.long) { + emptyValues.n_sum = -1 + } + const misshours = DateTime.fromISO(ret.values[0].datetime).toUTC().get('hour') + let hoursArr = new Array(opts.span * 24 + misshours); // generate new array + hoursArr.fill(emptyValues) // fill array with 'empty' values + let startDay = DateTime.fromISO(ret.values[0].datetime).toUTC().get('day') // calc first day + let k = 0 + for (let d of ret.values) { // loop through docs + let stunde = DateTime.fromISO(d.datetime).toUTC().get('hour') // get current hour + let day = DateTime.fromISO(d.datetime).toUTC().get('day') // get current day + if (day != startDay) { // if date has changed + k += 24 // increment index by 24 + startDay = day + } + hoursArr[k + stunde] = d // copy date into hourArray + } + return { err: ret.err, values: hoursArr} +} + +const whatTable = [ + {'what':'live', 'span': 1, 'daystart': false, peak: false, 'func': getLiveData}, + {'what':'havg', 'span': 7, 'daystart': false, peak: true, 'func': gethavgData}, + {'what':'davg', 'span': 30, 'daystart': true, peak: true, 'func': getdavgData}, + {'what':'daynight', 'span': 30, 'daystart': true, peak: false, 'func': getdaynightData}, + {'what':'lden', 'span': 30, 'daystart': true, peak: false, 'func': getLdenData}, + {'what':'props', 'span': 0, 'daystart': true, peak:false, 'func': getAPIprops}, +]; + diff --git a/sensorapi/test/test.js b/sensorapi/test/test.js new file mode 100644 index 0000000..183f1c0 --- /dev/null +++ b/sensorapi/test/test.js @@ -0,0 +1,29 @@ +import {getData4map} from '../actions/data4map.js' +import assert from 'assert/strict' + +describe("get data for map - test", function() { + const box = { + "east": 9.322391662597672, + "north": 48.86726810417461, + "south": 48.69057640500091, + "west": 8.99760833740236 + } + + it("geigeractivity should return at least 4 elements", async function() { + const erg = await getData4map( + { + "type": "radioactivity", + "box": box, + test: true + }) + assert.ok(erg.count >= 4) + }) + it("pm should return at least 270 elements", async function() { + const erg = await getData4map( + { + "type": "pm", + "box": box + }) + assert.ok(erg.count >= 270) + }) +}) diff --git a/sensorapi/utilities/chartoptions.js b/sensorapi/utilities/chartoptions.js new file mode 100644 index 0000000..01ddc6f --- /dev/null +++ b/sensorapi/utilities/chartoptions.js @@ -0,0 +1,148 @@ +// Utility routine for plotting the data + +export const colors = {'eq': '#0000FF', 'max': '#FF0000', 'min': '#008000', 'peaks': '#DAA520'}; +export const noise_ymin = 30; + +export function createGlobObtions() { + // Options, die für alle Plots identisch sind + let globObject = { + chart: { + spacingRight: 20, + spacingLeft: 20, + spacingTop: 25, + backgroundColor: { + linearGradient: [0, 400, 0, 0], + stops: [ + [0, '#eee'],//[0, '#ACD0AA'], //[0, '#A18D99'], // [0, '#886A8B'], // [0, '#F2D0B5'], + [1, '#fff'] + ] + }, + type: 'line', + borderWidth: '2', + // events: { + // selection: function (event) { + // if (event.xAxis) { + // doUpdate = false; + // } else { + // doUpdate = true; + // } + // } + // } + }, + title: { + align: 'left', + style: {'fontSize': '25px'}, + useHTML: true, + }, + subtitle: { + align: 'left', + }, + tooltip: { + valueDecimals: 1, + backgroundColor: '#ffffff', + borderWidth: 0, + borderRadius: 0, + useHTML: true, + }, + xAxis: { + type: 'datetime', + title: { + text: 'date/time', + }, + gridLineWidth: 2, + labels: { + formatter: function () { + let v = this.axis.defaultLabelFormatter.call(this); + if (v.indexOf(':') == -1) { + return '' + v + ''; + } else { + return v; + } + } + }, + }, + legend: { + enabled: true, + layout: 'horizontal', +// verticalAlign: 'top', + borderWidth: 1, + align: 'center', + }, + plotOptions: { + series: { + animation: false, + turboThreshold: 0, + marker: { + enabled: false, + }, + }, + } + }; + return globObject; +} + +export function calcWeekends(data, isyear) { +/* let weekend = []; + let oldDay = 8; + for (let i = 0; i < data.length; i++) { + let mom = moment(data[i].date); + if (isyear) { + mom = moment(data[i]._id) + } + let day = mom.day(); + let st = mom.startOf('day'); + if (day != oldDay) { + if (day == 6) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } else if (day == 0) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } + oldDay = day; + } + } + return weekend; +*/} + +export function calcDays(data, isyear) { + let days = []; + if (data.length == 0) { + return days + } + let oldday = moment(data[0].date).day(); + if (isyear) { + oldday = moment(data[0]._id).day(); + } + for (let i = 0; i < data.length; i++) { + let m = moment(data[i].date); + if (isyear) { + m = moment(data[i]._id); + } + let tag = m.day() + if (tag != oldday) { + m.startOf('day'); + days.push({color: 'lightgray', value: m.valueOf(), width: 1, zIndex: 2}); + oldday = tag; + } + } + return days; +}; + +export const setoptionfromtable = (opt,tabval) => { + let ret = opt + if ((opt === null) || (opt === '') || (opt === undefined) || (opt < tabval)){ + ret = tabval + } + return ret +} + + diff --git a/sensorapi/utilities/checkparams.js b/sensorapi/utilities/checkparams.js new file mode 100644 index 0000000..ad4af59 --- /dev/null +++ b/sensorapi/utilities/checkparams.js @@ -0,0 +1,54 @@ +// parse the params from http call + +import {returnOnError} from "./reporterror.js" + +const checkParams = (params, mo) => { + let o = {opts: {}, err: null} + if ((mo.mandatory.length !== 0) && (params === undefined)) { + return returnOnError(o, 'NOPARAMETER', checkParams.name ) + } + for (let p of mo.mandatory) { + if (!(p.name in params)) { + return returnOnError(o, 'NOMANDPARAM', checkParams.name, p.name) + } + if (p.type === 'int') { + let x = parseInt(params[p.name]) + if (isNaN(x)) { + return returnOnError(o, 'PARAMNONUM', checkParams.name, p.name) + } else { + o.opts[p.name] = x + continue + } + } else if (p.type === 'float') { + let x = parseFloat(params[p.name]) + if (isNaN(x)) { + return returnOnError(o, 'PARAMNONUM', checkParams.name, p.name) + } else { + o.opts[p.name] = x + continue + } + } + o.opts[p.name] = params[p.name] + } + for(let p of mo.optional) { + if (!(p.name in params)) { + o.opts[p.name] = p.default + } else { + if (p.type === 'int') { + let x = parseInt(params[p.name]) + if (isNaN(x)) { + o.opts[p.name] = p.default + } else { + o.opts[p.name] = x + } + } else if (p.type === 'bool') { + o.opts[p.name] = params[p.name] === 'true' + } else { + o.opts[p.name] = params[p.name] + } + } + } + return o +} + +export default checkParams diff --git a/sensorapi/utilities/csv2json.js b/sensorapi/utilities/csv2json.js new file mode 100644 index 0000000..4ce9bfe --- /dev/null +++ b/sensorapi/utilities/csv2json.js @@ -0,0 +1,35 @@ +// convert influx csv output to JSON + +export function csv2Json(str, delimiter = ",") { + // slice from start of text to the first \n index + // use split to create an array from string by delimiter + const headers = str.slice(0, str.indexOf("\r\n")).split(delimiter).slice(3); + let x = headers.findIndex((x) => (x === '_time') || (x === '_stop')) + if (x != -1) { + headers[x] = 'datetime' + } + // slice from \n index + 1 to the end of the text + // use split to create an array of each csv value row + const rows = str.slice(str.indexOf("\r\n") + 2).split("\r\n").slice(0,-2) + + // Map the rows + // split values from each row into an array + // use headers.reduce to create an object + // object properties derived from headers:values + // the object passed as an element of the array + const arr = rows.map(function (row) { + const values = row.split(delimiter).slice(3); + const el = headers.reduce(function (object, header, index) { + if(header !== 'datetime') { + object[header] = parseFloat(values[index]); + } else { + object[header] = values[index]; + } + return object; + }, {}); + return el; + }); + + // return the array + return arr; +} diff --git a/sensorapi/utilities/logit.js b/sensorapi/utilities/logit.js new file mode 100644 index 0000000..7a4e0c8 --- /dev/null +++ b/sensorapi/utilities/logit.js @@ -0,0 +1,16 @@ +import { DateTime} from 'luxon' + +const MOCHA_TEST = process.env.MOCHA_TEST || false + +export function logit(str) { + if(MOCHA_TEST) return + let s = `${DateTime.now().toISO()} => ${str}`; + console.log(s); +} + +export function logerror(str) { + if(MOCHA_TEST) return + let s = `${DateTime.toISO()} => *** ERROR *** ${str}`; + console.log(s); +} + diff --git a/sensorapi/utilities/reporterror.js b/sensorapi/utilities/reporterror.js new file mode 100644 index 0000000..c3a34c9 --- /dev/null +++ b/sensorapi/utilities/reporterror.js @@ -0,0 +1,22 @@ +import {logit} from "./logit.js"; +import { translate as trans } from '../routes/api.js' + +export const reportError = (message, errortext) => { + message.error = true + message.errortext = errortext + return message +} + +export const returnOnError = (pr, error, name, p1='', p2='') => { + error = trans(error) + if (error.indexOf('xxx') !== -1) { + error = error.replace('xxx', p1) + } + if (error.indexOf('yyy') !== -1) { + error = error.replace('yyy', p1) + } + pr.err = error + logit(`${name}: ${error}`) + return pr +} +