first commit

master
cheetah 2 years ago
parent 59a8ca7e51
commit b201ab7930

File diff suppressed because one or more lines are too long

@ -0,0 +1,276 @@
<!DOCTYPE html>
<html>
<head>
<title>Brandgefahren Configuration</title>
<!-- <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet"> -->
<link href="/css/materialdesignicons.min.css" rel="stylesheet">
<link href="/css/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app>
<v-app-bar app>
<v-toolbar-title>Brandgefahren Configuration</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn color="success" @click="storeConfig()">Store & Restart</v-btn>
<v-checkbox label="Expert Mode" v-model="EXPERTMODE"></v-checkbox>
</v-app-bar>
<v-content>
<v-form>
<v-tabs v-model="configTab" next-icon="mdi-arrow-right-bold-box-outline"
prev-icon="mdi-arrow-left-bold-box-outline" show-arrows>
<v-tabs-slider></v-tabs-slider>
<v-tab key="bradgefahren">Brandgefahren</v-tab>
<v-tab v-show="EXPERTMODE" key="notificationConfig">Notification Configuration</v-tab>
</v-tabs>
<v-tabs-items v-model="configTab">
<v-tab-item key="brandgefahren">
<v-container>
<v-row>
<v-btn color="primary" fab dark small icon @click="addAlarm()">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-row>
<v-row v-for="(alarmConfig, index) in configData.alarms" :key="alarmConfig._id">
<v-card>
<v-card-text>
<v-col cols="12">
<v-btn color="error" dark small fab @click="configData.alarms.splice(index, 1)" icon>
<v-icon>mdi-delete</v-icon>
</v-btn>
</v-col>
<v-col cols="12">
<v-row cols="6">
<v-select :items="alarmSchedulingMode" v-model="alarmConfig.alarmSchedulingMode"
item-text="k" item-value="v" label="Scheduling Mode"></v-select>
<v-text-field v-model="alarmConfig.alarmTime" type="time" label="Time"></v-text-field>
</v-row>
<v-row cols="6" v-show="alarmConfig.alarmSchedulingMode == 'weekly'">
<v-checkbox v-for="(WN, index) of weekDays" v-model="alarmConfig.weekDay[ index ]" :label="WN"></v-checkbox>
</v-row>
<v-row cols="6" v-show="alarmConfig.alarmSchedulingMode == 'monthlyFirstOccWeekday'">
<v-select :items="weekDaysSelect" v-model="alarmConfig.firstOccWeekday" label="First Occurance of this Weekday"></v-select>
</v-row>
<v-row cols="6" v-show="alarmConfig.alarmSchedulingMode == 'monthlyAtSpecificDate'">
<v-text-field v-model="alarmConfig.specificWeekday" label="Specific Date (xx-MM-YYYY)" type="number"></v-text-field>
</v-row>
</v-col>
<v-row cols="12">
<v-text-field v-model="alarmConfig.name" label="Name"></v-text-field>
<v-autocomplete
v-model="alarmConfig.preset"
:items="presetSearchItems"
:loading="!presetSearchItems.length > 0"
color="white"
hide-no-data
label="Profile"
placeholder="Start typing to Search"
prepend-icon="mdi-database-search"
></v-autocomplete>
</v-row>
<v-row cols="12">
<v-checkbox v-model="alarmConfig.wbxEnabled" label="WBi"></v-checkbox>
<v-autocomplete
:readonly="!alarmConfig.wbxEnabled"
v-model="alarmConfig.wbxCounty"
:items="autocompleteData.wbx.countyNames"
:loading="!autocompleteData.wbx.countyNames.length > 0"
hide-no-data
label="County"
placeholder="WBi-Land"
prepend-icon="mdi-map"
></v-autocomplete>
<v-autocomplete
:readonly="!alarmConfig.wbxEnabled"
v-model="alarmConfig.wbxStation"
:items="acWBXStations(alarmConfig)"
:loading="!autocompleteData.wbx.countyNames.length > 0"
hide-no-data
label="Station"
placeholder="WBi-Station"
prepend-icon="mdi-city"
></v-autocomplete>
</v-row>
<v-row cols="12">
<v-checkbox v-model="alarmConfig.gbxEnabled" label="GBi"></v-checkbox>
<v-autocomplete
:readonly="!alarmConfig.gbxEnabled"
v-model="alarmConfig.gbxCounty"
:items="autocompleteData.gbx.countyNames"
:loading="!autocompleteData.gbx.countyNames.length > 0"
hide-no-data
label="County"
placeholder="GBi-Land"
prepend-icon="mdi-map"
></v-autocomplete>
<v-autocomplete
:readonly="!alarmConfig.gbxEnabled"
v-model="alarmConfig.gbxStation"
:items="acGBXStations(alarmConfig)"
:loading="!autocompleteData.gbx.countyNames.length > 0"
hide-no-data
label="Station"
placeholder="GBi-Station"
prepend-icon="mdi-city"
></v-autocomplete>
</v-row>
<v-text-field v-model="alarmConfig.forecastLength" label="Days of forecast" type="number"></v-text-field>
<v-btn color="warn" @click="testTrigger(index)">
TEST (only saved config)
</v-btn>
</v-card-text>
</v-card>
</v-row>
</v-container>
</v-tab-item>
<v-tab-item key="notificationConfig">
<v-container>
<b>Routing Paramters:</b>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-text-field label="Daemon Endpoint URL" v-model="configData.pager.url">
</v-text-field>
</v-col>
</v-row>
</v-container>
</v-tab-item>
</v-tabs-items>
</v-form>
</v-content>
</v-app>
</div>
<script src="/js/moment-with-locales.min.js"></script>
<script src="/js/vue/vue.js"></script>
<script src="/js/vue/vuetify.js"></script>
<script src="/js/vue/vue-resource_1.5.1.js"></script>
<script>
new Vue({
el: '#app',
vuetify: new Vuetify(),
http: { root: '/' },
data() {
const weekDays = [
'Monday','Tuesday','Wednesday','Thursday','Friday','Saturday', 'Sunday'
]
return {
EXPERTMODE: false,
configTab: null,
alarmSchedulingMode: [
{ k: 'Weekly', v: 'weekly' },
{ k: 'Monthly at first occurance of a specific weekday', v: 'monthlyFirstOccWeekday' },
{ k: 'Monthly at specific date', v: 'monthlyAtSpecificDate' },
],
weekDays,
weekDaysSelect: weekDays.map((val,ind) => { return { value: ind, text: val } }),
configData: {
"bottoken": "",
"pager": {
"url": "",
},
"menuSupport": false,
"deliveryModes": []
},
autocompleteData: {
wbx: {counties: {}, countyNames: []},
gbx: {counties: {}, countyNames: []},
},
presetSearchItems: [],
}
},
created() {
this.loadPresets()
this.loadConfig()
this.loadAutocompletes()
},
methods: {
acWBXStations(alarmConfig) {
if (alarmConfig.wbxCounty && this.autocompleteData.wbx.countyNames.length > 0) {
if (!this.autocompleteData.wbx.counties[ alarmConfig.wbxCounty ]) return []
return this.autocompleteData.wbx.counties[ alarmConfig.wbxCounty ].stationNames
}
return []
},
acGBXStations(alarmConfig) {
if (alarmConfig.gbxCounty && this.autocompleteData.gbx.countyNames.length > 0) {
if (!this.autocompleteData.gbx.counties[ alarmConfig.gbxCounty ]) return []
return this.autocompleteData.gbx.counties[ alarmConfig.gbxCounty ].stationNames
}
return []
},
testTrigger(index) {
this.$http.get(window.location.pathname + 'api/trigger/' + index)
.then(response => {})
},
loadPresets() {
this.$http.get(window.location.pathname + 'api/deliveryPresets')
.then(response => {
this.presetSearchItems = response.body.map(x => { return {
text: x.name,
value: x.key,
}})
}, response => {
})
},
loadConfig() {
this.$http.get(window.location.pathname + 'config').then(response => {
const newConfig = response.body
newConfig.alarms = newConfig.alarms.map((x) => {
x._id = btoa(JSON.stringify(x))
return x
})
this.configData = newConfig
}, response => {
})
},
loadAutocompletes() {
this.$http.get(window.location.pathname + 'wg/autocomplete').then(response => {
this.autocompleteData = response.body
}, response => {
})
},
storeConfig() {
const storeConfig = JSON.parse(JSON.stringify(this.configData))
storeConfig.alarms = storeConfig.alarms.map(a=> {
delete a._id
return a
})
this.$http.post(window.location.pathname + 'config', storeConfig)
.then(response => {})
.then(this.$http.post(window.location.pathname + 'restart'))
.then(() => {
document.body.style = 'display:none'
setTimeout(() => window.location.reload(), 1e3)
})
},
addAlarm() {
this.configData.alarms.push({
name: "Alarm 1",
preset: null,
alarmSchedulingMode: "weekly",
alarmTime: "13:37",
weekDay: {
"0": true,
"1": true,
"2": true,
"3": true,
"4": true,
"5": true,
"6": true
},
wbxEnabled: false,
gbxEnabled: false,
forecastLength: 3,
})
},
}
})
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,264 @@
const config = require('./config.json')
const axios = require('axios')
const fs = require('fs')
const moment = require('moment')
moment.locale('yes', {
week : {
dow : 1
}
})
const jsdom = require("jsdom")
const { JSDOM } = jsdom
const express = require('express')
const appConfig = express()
appConfig.use(express.json({ limit: '1mb' }))
appConfig.use(express.static('html'))
appConfig.use(express.static(__dirname + '/node_modules/@mdi/font'))
const umlautMapGermany = {
'\u00dc': ']',
'\u00c4': '[',
'\u00d6': "\\",
'\u00fc': '}',
'\u00e4': '{',
'\u00f6': '|',
'\u00df': '~',
}
const umlautMapIntl = {
'\u00dc': 'U',
'\u00c4': 'A',
'\u00d6': 'O',
'\u00fc': 'u',
'\u00e4': 'a',
'\u00f6': 'o',
'\u00df': 'ss',
}
function itnlUmlaute(str) {
let umlautMap = umlautMapIntl
return str
.replace(/[\u00dc|\u00c4|\u00d6][a-z]/g, (a) => {
const big = umlautMap[a.slice(0, 1)];
return big.charAt(0) + big.charAt(1).toLowerCase() + a.slice(1);
})
.replace(new RegExp('['+Object.keys(umlautMap).join('|')+']',"g"),
(a) => umlautMapIntl[a]
);
}
function replaceUmlaute(str) {
let umlautMap = config.germanUmlautSupport
? umlautMapGermany
: umlautMapIntl
return str
.replace(/[\u00dc|\u00c4|\u00d6][a-z]/g, (a) => {
const big = umlautMap[a.slice(0, 1)];
return big.charAt(0) + big.charAt(1).toLowerCase() + a.slice(1);
})
.replace(new RegExp('['+Object.keys(umlautMap).join('|')+']',"g"),
(a) => umlautMap[a]
);
}
async function triggerAlert(alarmConfig) {
console.log("sending msg for ", alarmConfig.name)
if (alarmConfig.wbxEnabled) {
wbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/waldbrand.html")
wbxData = parseWettergefahren(wbxData.data)
//wbxData = JSON.parse(fs.readFileSync("./.waldbrand.json"))
//fs.writeFileSync("./.waldbrand.json", JSON.stringify(wbxData))
if (!wbxData.counties[ alarmConfig.wbxCounty ]) throw "County404"
if (!wbxData.counties[ alarmConfig.wbxCounty ].stationData[ alarmConfig.wbxStation ]) throw 'Station404'
let stationData = wbxData.counties[ alarmConfig.wbxCounty ].stationData[ alarmConfig.wbxStation ]
let dayCounter = 0
dayStrs = []
for (let dayKey of Object.keys(stationData)) {
dayStrs.push(`${ dayKey.split(' ')[0] }=${ stationData[dayKey] }`)
if (dayCounter > +alarmConfig.forecastLength) break
dayCounter++
}
await axios.post(new URL(config.pager.url).origin + '/api/message/' + (!!alarmConfig.preset ? 'preset' : 'advanced'),
Object.assign({ preset: alarmConfig.preset }, { payload: `WBI: ${ alarmConfig.wbxStation } ${ dayStrs.join('-') }` }))
}
if (alarmConfig.gbxEnabled) {
gbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/grasland.html")
gbxData = parseWettergefahren(gbxData.data)
//gbxData = JSON.parse(fs.readFileSync("./.grasland.json"))
//fs.writeFileSync("./.waldbrand.json", JSON.stringify(wbxData))
if (!gbxData.counties[ alarmConfig.gbxCounty ]) throw "County404"
if (!gbxData.counties[ alarmConfig.gbxCounty ].stationData[ alarmConfig.gbxStation ]) throw 'Station404'
let stationData = gbxData.counties[ alarmConfig.gbxCounty ].stationData[ alarmConfig.gbxStation ]
let dayCounter = 0
dayStrs = []
for (let dayKey of Object.keys(stationData)) {
dayStrs.push(`${ dayKey.split(' ')[0] }=${ stationData[dayKey] }`)
if (dayCounter >= +alarmConfig.forecastLength) break
dayCounter++
}
await axios.post(new URL(config.pager.url).origin + '/api/message/' + (!!alarmConfig.preset ? 'preset' : 'advanced'),
Object.assign({ preset: alarmConfig.preset }, { payload: `GBI: ${ alarmConfig.gbxStation } ${ dayStrs.join('-') }` }))
}
}
async function minuteCheck() {
let timeRN = moment()
//console.log('timeRN', timeRN.format("HH:mm"))
for (let alarm of config.alarms) {
let alarmTime = moment()
.hours(+alarm.alarmTime.split(':') [ 0 ])
.minutes(+alarm.alarmTime.split(':') [ 1 ])
let secDiff = alarmTime.diff(timeRN, 'seconds')
//
//console.log(alarmTime.format("HH:mm"), secDiff)
if (alarmTime.format("HH:mm") == timeRN.format("HH:mm")) {
//console.log("TRIGGER", alarm.alarmSchedulingMode)
let alarmTrigger = false
switch (alarm.alarmSchedulingMode) {
case 'monthlyAtSpecificDate':
alarmTrigger = (timeRN.date() == alarmTime.specificWeekday)
break;
case 'monthlyFirstOccWeekday':
// wish Date = alarmTime.firstOccWeekday
let thisMonthFirstWeekDayOccurance = -1
let monthFirstDay = moment().startOf('month')
while (monthFirstDay.weekday() != alarm.firstOccWeekday) monthFirstDay.add(1, 'day')
thisMonthFirstWeekDayOccurance = monthFirstDay.date()
//console.log(monthFirstDay.date(), monthFirstDay.weekday())
//console.log('monthly first occurance for', alarm.firstOccWeekday, 'is', thisMonthFirstWeekDayOccurance)
alarmTrigger = (timeRN.date() == thisMonthFirstWeekDayOccurance)
break;
case 'weekly':
for (let weekDayKey of Object.keys(alarm.weekDay)) {
//console.log(weekDayKey, alarm.weekDay)
if (+weekDayKey === +timeRN.weekday()) {
//console.log('weekday match', 'result is', alarm.weekDay[weekDayKey] )
alarmTrigger = alarm.weekDay[weekDayKey]
break;
}
}
break;
}
if (alarmTrigger === true) {
console.log("TRIGGER!!!!!")
await triggerAlert(alarm)
/*await axios.post(new URL(config.pager.url).origin + '/api/message/' + (!!alarm.preset ? 'preset' : 'advanced'), Object.assign( !!alarm.preset
? { preset: alarm.preset }
: { ...alarm.params } // backward compatibility
, { payload: alarm.payload }))
*/
}
}
}
}
function parseWettergefahren(rawHTML) {
const wbiResponse = {
counties: {},
countyNames: [],
}
const { window } = new JSDOM(rawHTML)
const sections = window.document.querySelectorAll("section[role='contentinfo']")
for (let b of sections) {
if ( b.getAttribute("aria-label") == "Legende" ) continue
regionName = b.getAttribute("aria-label")
wbiResponse.countyNames.push(regionName)
tableHead = b.querySelector("thead")
tableRows = b.querySelectorAll("tr")
let headers = {}, values = {}
let i = 0
for (let k of tableHead.querySelectorAll("th")) {
headers[ i ] = k.textContent
i++
}
for (let k of tableRows) {
let i = 0
let first = ''
let l = 0
for (let j of k.querySelectorAll("td")) {
if (first == '') { first = j.textContent; if(first == "Stationsname") break; values[first] = {} }
else values[ first ][headers[ l ]] = j.textContent
l++
}
i++
}
wbiResponse.counties[regionName] = {
stationNames: Object.keys(values),
stationData: values,
}
}
return wbiResponse
}
async function main() {
let now = moment()
//await test()
//table class="tab_rand"
await minuteCheck()
setTimeout(() => setInterval(minuteCheck, 60e3), now.diff(moment().add(1, 'minute'), 'seconds') * 1e3 + 5e3)
}
main()
appConfig.get('/wg/autocomplete', async (req, res) => {
if (req.query.refresh) {
console.log("refreshing cached autocomplete jsons")
try { fs.unlinkSync("./.waldbrand.json") } catch (e) {}
try { fs.unlinkSync("./.grasland.json") } catch (e) {}
}
if (fs.existsSync("./.waldbrand.json") == false) {
wbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/waldbrand.html")
fs.writeFileSync("./.waldbrand.json", JSON.stringify(parseWettergefahren(wbxData.data)))
}
if (fs.existsSync("./.grasland.json") == false) {
gbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/grasland.html")
fs.writeFileSync("./.grasland.json", JSON.stringify(parseWettergefahren(gbxData.data)))
}
const WBX = fs.readFileSync("./.waldbrand.json")
const GBX = fs.readFileSync("./.grasland.json")
return res.json({
gbx: JSON.parse(GBX),
wbx: JSON.parse(WBX),
})
})
appConfig.get('/api/trigger/:index', async (req, res) => {
let i = 0
for (let alarm of config.alarms) {
if (i == +req.params.index) await triggerAlert(alarm)
i++
}
})
/** CONFIG Routes */
appConfig.get('/config', async (req, res) => {
return res.json(JSON.parse(fs.readFileSync('config.json')))
})
appConfig.get('/api/deliveryPresets', async (req, res) => {
const presets = await axios.get(new URL(config.pager.url).origin + '/api/deliveryPresets')
return res.json(presets.data)
})
appConfig.post('/config', async (req, res) => {
if (!(!!req.body.alarms)) return res.status(403).json(false)
if (!(!!req.body.pager)) return res.status(403).json(false)
console.log(req.body)
fs.writeFileSync('config.json', JSON.stringify(req.body, null, "\t"))
return res.json(true)
})
appConfig.post('/restart', (req, res) => {
process.exit(1)
})
appConfig.listen(3100, '0.0.0.0' || config.host || '127.0.0.1')

@ -7,5 +7,14 @@
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "", "author": "",
"license": "ISC" "license": "ISC",
"dependencies": {
"@mdi/font": "^7.0.96",
"axios": "^0.21.4",
"express": "^4.17.1",
"feed-reader": "^6.1.2",
"jsdom": "^22.1.0",
"moment": "^2.29.4",
"socket.io-client": "^4.4.1"
}
} }

Loading…
Cancel
Save