Files
laermsensor-stack/noise/public/javascripts/global.js
T
admin 10cd964ede Stadtsuche in den Einstellungen repariert (graue Karte)
Wer auf dem Kartenreiter die Einstellungen oeffnete und eine Stadt eingab,
bekam danach nur noch ein graues Fenster. Drei Fehler wirkten zusammen:

1. getCityCoords() rief Nominatim ohne eigenen User-Agent auf. Nominatim
   beantwortet den axios-Default mit 403, und zwar fuer jede Stadt. Die
   Schwesterfunktion getAddress() setzt seit jeher einen User-Agent -
   deshalb funktionierten die Adressen an den Sensoren, nur die
   Stadtsuche nicht. Beide nutzen jetzt dieselbe Konstante.

2. cityCoords() lieferte im Fehlerfall den String "no coordinates". Der
   landete als Kartenzentrum in L.map().setView(), Leaflet warf "Invalid
   LatLng object" - und weil showMap() die alte Karte vorher schon
   entfernt hat, blieb der Container leer. Daher grau statt Fehlermeldung.
   cityCoords() liefert jetzt {coords, err}; schlaegt die Suche fehl,
   bleibt das bisherige Zentrum stehen und der Text wird angezeigt.

3. Der Proxy in noise/routes/api.js kodierte die bereits prozentkodierte
   req.originalUrl ein zweites Mal. 'Goettingen' kam als 'G%c3%b6ttingen'
   an. Das fiel bisher nicht auf, weil schon Punkt 1 alles abfing.

Ausserdem: Tippfehler getCityCoord.name, fehlendes encodeURI bei der
Stadtsuche, Koordinaten als Zahl statt String, und eine eigene Meldung
(ENOCITY), wenn Nominatim den Ort nicht kennt.

Geprueft ueber die volle Kette noise -> sensorapi -> Nominatim:
Stuttgart, Goettingen (Umlaut), Bad Cannstatt (Leerzeichen) liefern
Koordinaten, ein erfundener Ort eine saubere Meldung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:03:27 +00:00

315 lines
12 KiB
JavaScript

// 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()
let cityErr = null // Meldung erst nach dem Schliessen des Dialogs zeigen
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
const erg = await cityCoords(params.center.name)
if (erg.coords === null) {
// Ort nicht aufloesbar: altes Zentrum behalten. Ohne das landet
// ein ungueltiger Wert in setView() und die Karte bleibt grau.
cityErr = erg.err
params.center = JSON.parse(localStorage.getItem('centercity'))
} else {
params.center.coords = erg.coords
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()
if (cityErr) {
showError(cityErr)
}
// 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)
})()