Language switching works
app.js - call to index.js changed public/javascripts/global.js - no more show version - no more 'showReset routes/index.js - added 'translate'-function views/index.pug - texts changed for translation views/layout.pug - show version and date public/javascripts/map.js - added text from server charts/preparecharts.js - translate texts locales/[en|de]/translation.json - new translations charts/urils.js - added translations package.json "version": "3.0.2", "date": "2023-04-19 11:00 UTC",
This commit is contained in:
@@ -15,7 +15,7 @@ export let url2call
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
import indexRouter from './routes/index.js'
|
||||
import * as indexRouter from './routes/index.js'
|
||||
import apiRouter from './routes/api.js'
|
||||
|
||||
i18next
|
||||
@@ -26,7 +26,7 @@ i18next
|
||||
loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json'
|
||||
},
|
||||
fallbackLng: 'en',
|
||||
// debug: true,
|
||||
debug: true,
|
||||
preload: ['en', 'de']
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/api', apiRouter)
|
||||
app.use('/', indexRouter);
|
||||
app.use('/', indexRouter.router);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
|
||||
+33
-14
@@ -1,5 +1,6 @@
|
||||
// Prepare all data to plot the charts
|
||||
import * as utils from '../charts/utilities.js'
|
||||
import { translate as trans } from '../routes/index.js'
|
||||
|
||||
/**********************************************
|
||||
* Live-Data
|
||||
@@ -38,7 +39,7 @@ export const liveData = (params, values, req) => {
|
||||
// InfoTafel füllen
|
||||
infoTafel =
|
||||
'<table class="infoTafel"><tr >' +
|
||||
'<th colspan="3">' + req.t("ActualValues") + '</th>' +
|
||||
'<th colspan="3">' + trans("ActualValues") + '</th>' +
|
||||
'</tr><tr>' +
|
||||
'<td>LAeq</td><td>' + (aktVal.LAeq).toFixed(1) + '</td><td>dbA</td>'
|
||||
infoTafel +=
|
||||
@@ -137,9 +138,9 @@ export const liveData = (params, values, req) => {
|
||||
options.yAxis = []
|
||||
options.series[0] = series_LAeq
|
||||
options.series[1] = series_LAMin
|
||||
options.title.text = req.i18n.t('Noise_levels_last_24_hours')
|
||||
options.title.text = trans('NoiseLevelsLast24Hours')
|
||||
options.subtitle.useHTML = true
|
||||
options.subtitle.text = '2.5min log. average values <br />  '
|
||||
options.subtitle.text = `${trans("25minlogAverageValues")}<br /> `
|
||||
options.series[2] = series_LAMax
|
||||
// options.yAxis[2] = yAxis_LAMin
|
||||
options.yAxis[0] = yAxis_LAeq
|
||||
@@ -252,8 +253,8 @@ export const havgData = (params, values) => {
|
||||
groupPadding: 0.1
|
||||
}
|
||||
|
||||
options.title.text = 'Hourly mean values / Peaks over ' + params.peak + ' dbA'
|
||||
options.subtitle.text = 'Hourly mean values of LAeq for each full hour / Number of peaks per hour'
|
||||
options.title.text = `${trans('HourlyMeanValues')} / ${trans('PeaksOver')} ${params.peak} dbA`
|
||||
options.subtitle.text = `${trans('HourlyMeanValuesOfLAeqForEachFullHour')} / ${trans('NumberOfPeaksPerHour')}<br /> `
|
||||
options.subtitle.useHTML = true
|
||||
options.chart.zoomType = 'x'
|
||||
options.chart.resetZoomButton= {
|
||||
@@ -346,8 +347,8 @@ export const davgData = (params, values) => {
|
||||
pointWidth: 20,
|
||||
groupPadding: 0.1
|
||||
}
|
||||
options.title.text = 'Daily mean values / Peaks over ' + params.peak + ' dbA'
|
||||
options.subtitle.text = 'Daily mean values of LAeq for each full day / Number of peaks per day'
|
||||
options.title.text = `${trans('DailyMeanValues')} / ${trans('PeaksOver')} ${params.peak} dbA`
|
||||
options.subtitle.text = `${trans('DailyMeanValuesOfLAeqForEachFullDay')} / ${trans('NumberOfPeaksPerDay')}<br /> `
|
||||
options.subtitle.useHTML = true
|
||||
options.chart.zoomType = 'x'
|
||||
options.chart.resetZoomButton = {
|
||||
@@ -389,7 +390,7 @@ export const dayNightData = (params, values) => {
|
||||
let options = utils.createGlobObtions()
|
||||
// options.xAxis.plotBands = utils.calcWeekends(data, true)
|
||||
options.xAxis.tickInterval = 24 * 3600 * 1000
|
||||
options.xAxis.title.text = 'Date'
|
||||
options.xAxis.title.text = trans('Date')
|
||||
// options.xAxis.labels = {
|
||||
// formatter: function () {
|
||||
// return this.axis.defaultLabelFormatter.call(this);
|
||||
@@ -429,8 +430,8 @@ export const dayNightData = (params, values) => {
|
||||
pointWidth: 20,
|
||||
groupPadding: 0.3
|
||||
}
|
||||
options.title.text = 'Average values during the day and night hours'
|
||||
options.subtitle.text = 'Daily mean values of LAeq from 6h00 to 22h00 <br /> Nightly mean values of LAeq from 22h00 to 6h00'
|
||||
options.title.text = trans('AverageValuesDuringTheDayAndNightHours')
|
||||
options.subtitle.text = `${trans('DailyMeanValuesOfLAeqFrom6h00To22h00')}<br />${trans('NightlyMeanValuesOfLAeqFrom22h00To6h00')}<br /> `
|
||||
options.subtitle.useHTML = true
|
||||
options.chart.zoomType = 'x'
|
||||
options.chart.resetZoomButton = {
|
||||
@@ -469,7 +470,7 @@ export const ldenData = (params, values) => {
|
||||
|
||||
// options.xAxis.plotBands = utils.calcWeekends(data, true)
|
||||
options.xAxis.tickInterval = 24 * 3600 * 1000
|
||||
options.xAxis.title = 'Date'
|
||||
options.xAxis.title.text = trans('Date')
|
||||
options.xAxis.labels = {
|
||||
formatter: function () {
|
||||
let lbl = this.axis.defaultLabelFormatter.call(this);
|
||||
@@ -504,8 +505,8 @@ export const ldenData = (params, values) => {
|
||||
pointWidth: 20,
|
||||
groupPadding: 0.1
|
||||
}
|
||||
options.title.text = 'L<sub>DEN</sub>-Index'
|
||||
options.subtitle.text = 'Daily mean values of L<sub>DEN</sub>\-Index'
|
||||
options.title.text = trans('LDEN-Index')
|
||||
options.subtitle.text = `${trans('DailyMeanValuesOfLDEN-Index')}<br /> `
|
||||
options.subtitle.useHTML = true
|
||||
options.chart.zoomType = 'x'
|
||||
options.chart.resetZoomButton = {
|
||||
@@ -541,6 +542,24 @@ export const ldenData = (params, 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) => {
|
||||
return {...params, values: 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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
// Utility routine for plotting the data
|
||||
|
||||
import { translate as trans } from '../routes/index.js'
|
||||
|
||||
export const colors = {'eq': '#0000FF', 'max': '#FF0000', 'min': '#008000', 'peaks': '#DAA520'};
|
||||
export const noise_ymin = 30;
|
||||
|
||||
@@ -47,7 +49,7 @@ export function createGlobObtions() {
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
title: {
|
||||
text: 'date/time',
|
||||
text: trans('datetime'),
|
||||
},
|
||||
gridLineWidth: 2,
|
||||
labels: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"Noise_Measurement": "Lärm-Messung",
|
||||
"NoiseMeasurement": "Lärm-Messung",
|
||||
"Settings": "Einstellungen",
|
||||
"Info": "Info",
|
||||
"Start_date": "Start Datum",
|
||||
"Number_of_days": "Anzahl Tage",
|
||||
"StartDate": "Start Datum",
|
||||
"NumberOfDays": "Anzahl Tage",
|
||||
"Only_for_live_chart": "Nur bei 'live' Grafik",
|
||||
"Count_peaks_over": "Zähle Spitzen über",
|
||||
"Close": "Schließen",
|
||||
@@ -14,7 +14,30 @@
|
||||
"Hour_AVG": "Stundenmittel",
|
||||
"Day_AVG": "Tagesmittel",
|
||||
"Day_Night": "Tag/Nacht-Werte",
|
||||
"doReset": "Bitte die Seite neu laden um die Sprache zu übernehmen",
|
||||
"ActualValues": "Aktuelle Werte",
|
||||
"Noise_levels_last_24_hours": "Schallpegel der letzten 24 Stunden"
|
||||
"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": "L<sub>DEN</sub>-Index",
|
||||
"DailyMeanValuesOfLDEN-Index": "Tages-Mittelwerte des L<sub>DEN</sub>-Index",
|
||||
"Date": "Datum",
|
||||
"Version": "Version",
|
||||
"from": "vom"
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"Noise_Measurement": "Noise Measurement",
|
||||
"NoiseMeasurement": "Noise Measurement",
|
||||
"Settings": "Settings",
|
||||
"Info": "Info",
|
||||
"Start_date": "Start date:",
|
||||
"Number_of_days": "Number of days",
|
||||
"StartDate": "Start date:",
|
||||
"NumberOfDays": "Number of days",
|
||||
"Only_for_live_chart": "Only for 'live' chart",
|
||||
"Count_peaks_over": "Count peaks over",
|
||||
"Close": "Close",
|
||||
@@ -14,8 +14,30 @@
|
||||
"Hour_AVG": "Hour_AVG",
|
||||
"Day_AVG": "Day_AVG",
|
||||
"Day_Night": "Day_Night",
|
||||
"doReset": "Please reload page to change language.",
|
||||
"ActualValues": "Actual Values",
|
||||
"Noise_levels_last_24_hours": "Noise levels last 24 hours"
|
||||
|
||||
}
|
||||
"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": "L<sub>DEN</sub>-Index",
|
||||
"DailyMeanValuesOfLDEN-Index": "Daily mean values of L<sub>DEN</sub>-Index",
|
||||
"Date": "Date",
|
||||
"Version": "Version",
|
||||
"from": "from"
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "laerm-web-sensorapi",
|
||||
"version": "3.0.1",
|
||||
"date": "2023-04-17 13:00 UTC",
|
||||
"version": "3.0.2",
|
||||
"date": "2023-04-19 11:00 UTC",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node bin/www.js >>log/laerm_web.log 2>&1"
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as dt from './datetime.js'
|
||||
import {logerror, logit} from './logit.js'
|
||||
import {tabtable, loadAll, showChart} from './showcharts.js'
|
||||
import { DateTime } from './luxon.min.js'
|
||||
import {showReset} from "./chart_utilities.js";
|
||||
|
||||
(async () => {
|
||||
|
||||
@@ -104,7 +103,7 @@ import {showReset} from "./chart_utilities.js";
|
||||
tabTrigger.show()
|
||||
currentTab = event.currentTarget.id
|
||||
if (currentTab === 'maptab') {
|
||||
map.showMap(params)
|
||||
map.showMap(params, sysparams)
|
||||
document.querySelector('#sellan').style.display = 'inline'
|
||||
}
|
||||
})
|
||||
@@ -114,10 +113,6 @@ import {showReset} from "./chart_utilities.js";
|
||||
// and show date/time every minute
|
||||
setInterval(() => dt.showDate(false), 1000)
|
||||
|
||||
// show version and version-date
|
||||
document.querySelector('#versn').innerHTML =
|
||||
`Version: ${sysparams.version} vom ${sysparams.date.slice(0,10)}`
|
||||
|
||||
const csid = parseInt(sysparams.csid)
|
||||
if(csid !== -1) {
|
||||
let triggerEl = document.querySelector('#livetab')
|
||||
@@ -130,7 +125,7 @@ import {showReset} from "./chart_utilities.js";
|
||||
} else {
|
||||
// show the map
|
||||
document.querySelector('#sellan').style.display = 'inline'
|
||||
map.showMap(params)
|
||||
map.showMap(params,sysparams)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const colorscale = ['#d53e4f', '#fc8d59', '#fee08b', '#e6f598', '#99d594', '#328
|
||||
const dba = [100, 80, 60, 40, 20, 0, -999];
|
||||
const APIURL = '/api/getmapdata?'
|
||||
|
||||
export async function showMap(params) {
|
||||
export async function showMap(params, sysparams) {
|
||||
utils.removeTabs()
|
||||
if(map && map.remove) {
|
||||
map.off();
|
||||
@@ -205,7 +205,7 @@ export async function buildMarkers() {
|
||||
lastseen: dt.formatISODate(x.lastseen),
|
||||
indoor: x.indoor
|
||||
})
|
||||
.on('click', e => onMarkerClick(e, true)) // define click- and
|
||||
.on('click', e => onMarkerClick(e, true, sensors.popuptxt)) // define click- and
|
||||
.bindPopup(popuptext); // and bint the popup text
|
||||
markers.addLayer(marker);
|
||||
}
|
||||
@@ -217,27 +217,27 @@ export async function buildMarkers() {
|
||||
}
|
||||
|
||||
|
||||
async function onMarkerClick(e, click) {
|
||||
async function onMarkerClick(e, click, txts) {
|
||||
let item = e.target.options;
|
||||
clickedSensor = item.name;
|
||||
clickedcoords = e.latlng
|
||||
|
||||
let offlinetext = `
|
||||
<tr><td colspan="2"><span style="color:red;">offline</span></td></tr>
|
||||
<tr><td>Last seen:</td><td>${item.lastseen}</td></tr>`
|
||||
<tr><td colspan="2"><span style="color:red;">${txts.offline}</span></td></tr>
|
||||
<tr><td>${txts.lastseen}:</td><td>${item.lastseen}</td></tr>`
|
||||
let normaltext = `
|
||||
<tr></tr><tr><td>LA_max:</td><td>${item.value}</td></tr>`
|
||||
|
||||
let popuptext = `
|
||||
<div id="infoTitle">
|
||||
<h4>Sensor: ${item.name}</h4>
|
||||
<h4>${txts.sensor}: ${item.name}</h4>
|
||||
<div id="infoTable">
|
||||
<table>
|
||||
<tr><td colspan="2"><span style="color:limegreen;">${item.indoor==1 ? "indoor" : ""}</span></td></tr>
|
||||
${item.value < 0 ? offlinetext : normaltext}
|
||||
</table>
|
||||
<div id="infoBtn">
|
||||
<a href="#" class="speciallink">Show chart</a>
|
||||
<a href="#" class="speciallink">${txts.showchart}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
@@ -273,13 +273,7 @@ async function showLastDate(ld) {
|
||||
console.log(e)
|
||||
});
|
||||
let sensors = await ret.json()
|
||||
let actsensors = 0
|
||||
for (let x of sensors.values) {
|
||||
if (x.value >= 0) {
|
||||
actsensors++
|
||||
}
|
||||
}
|
||||
document.querySelector('#mapdate').innerText = 'Values from ' + dt.formatISODate(ld).slice(0,-3)
|
||||
document.querySelector('#actsensors').innerText = `Active sensors: ${actsensors} (registered: ${sensors.count})`
|
||||
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})`)
|
||||
}
|
||||
|
||||
+12
-3
@@ -8,11 +8,16 @@ const renderOpts = (sid) => {
|
||||
return {
|
||||
title: name,
|
||||
version: version,
|
||||
date: date,
|
||||
date: date.slice(0,10),
|
||||
csensor: sid
|
||||
}
|
||||
}
|
||||
|
||||
let i18n;
|
||||
|
||||
const translate = (x) => {
|
||||
return i18n.t(x)
|
||||
}
|
||||
/* GET home page. If there is no parameter 'lng=xx', checke stored language and call
|
||||
te page again wird correct language (chglang.js))
|
||||
*/
|
||||
@@ -21,7 +26,9 @@ router.get('/', function(req, res, next) {
|
||||
if(req.query.lng === undefined) {
|
||||
res.render('chglang')
|
||||
} else {
|
||||
res.render('index', renderOpts(-1))
|
||||
i18n = req.i18n
|
||||
let opts = renderOpts(-1)
|
||||
res.render('index', opts)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -41,4 +48,6 @@ router.get('/:sid', async function(req, res, next) {
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
//export default router
|
||||
|
||||
export { router, translate }
|
||||
+4
-4
@@ -10,7 +10,7 @@ block content
|
||||
|
||||
header
|
||||
#hline1
|
||||
#h1name #{t("Noise_Measurement")}
|
||||
#h1name #{t("NoiseMeasurement")}
|
||||
#h1datum
|
||||
|
||||
#fenster
|
||||
@@ -75,11 +75,11 @@ block content
|
||||
.modal-body.settings
|
||||
.rows
|
||||
.column
|
||||
label.thelabels(for='startday') #{t("Start_date")}:
|
||||
label.thelabels(for='startday') #{t("StartDate")}:
|
||||
input.theInputs#startday(name='startday' )
|
||||
.column
|
||||
label.thelabels(for='nbrofdays')
|
||||
| #{t("Number_of_days")}:<br />
|
||||
| #{t("NumberOfDays")}:<br />
|
||||
span#klein (#{t("Only_for_live_chart")})
|
||||
input.theInputs#nbrofdays(type = 'number' name = 'nbrofdays')
|
||||
| (max. 10)
|
||||
@@ -110,7 +110,7 @@ block content
|
||||
h5.modal-title.dialogErrorTitle Info
|
||||
button(type="button" class="close" data-bs-dismiss="modal" aria-label="Close")
|
||||
span(aria-hidden="true") ×
|
||||
.modal-body #{t("doReset")}
|
||||
.modal-body
|
||||
.modal-footer
|
||||
//button(type="button" class="btn btn-secondary" data-bs-dismiss="modal") Close
|
||||
button(type="button" class="btn btn-primary" data-bs-dismiss="modal") OK
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ html
|
||||
date: '#{date}',
|
||||
csid: '#{csensor}'
|
||||
}
|
||||
|
||||
let urlParams = {
|
||||
// drawlines: '#{drawlines}' === 'true',
|
||||
// starttime: '#{starttime}',
|
||||
@@ -39,7 +40,8 @@ html
|
||||
#author
|
||||
#mailadr
|
||||
a(href="mailto:rexfue@gmail.com") mailto:rexfue@gmail.com
|
||||
#versn
|
||||
#versn #{t('Version')}: #{version} #{t('from')} #{date}
|
||||
|
||||
script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
|
||||
crossorigin="anonymous")
|
||||
|
||||
Reference in New Issue
Block a user