initial commit
This commit is contained in:
commit
19772073ae
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
7
.idea/jsLibraryMappings.xml
generated
Normal file
7
.idea/jsLibraryMappings.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="PROJECT" libraries="{chart.js}" />
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="ASK" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/pm25.iml" filepath="$PROJECT_DIR$/.idea/pm25.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
13
.idea/pm25.iml
generated
Normal file
13
.idea/pm25.iml
generated
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="chart.js" level="application" />
|
||||
</component>
|
||||
</module>
|
11
.idea/runConfigurations/bin_www.xml
generated
Normal file
11
.idea/runConfigurations/bin_www.xml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="bin/www" type="NodeJSConfigurationType" path-to-js-file="bin/www" working-dir="$PROJECT_DIR$">
|
||||
<envs>
|
||||
<env name="DEBUG" value="pm25:*" />
|
||||
</envs>
|
||||
<EXTENSION ID="com.jetbrains.nodejs.run.NodeStartBrowserRunConfigurationExtension">
|
||||
<browser url="http://localhost:3000/" />
|
||||
</EXTENSION>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM node:18
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app/
|
||||
VOLUME /app/
|
||||
|
||||
RUN npm install -g npm@latest
|
||||
|
||||
COPY ./package.json ./
|
||||
RUN yarn
|
||||
|
||||
EXPOSE 8081
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV MONGODB_URL="mongodb://db:27017/pm25"
|
||||
ENV PORT=8081
|
||||
CMD DEBUG=pm25:* node main.js
|
||||
|
39
app.js
Normal file
39
app.js
Normal file
@ -0,0 +1,39 @@
|
||||
const createError = require( "http-errors" );
|
||||
const express = require( "express" );
|
||||
const path = require( "path" );
|
||||
const cookieParser = require( "cookie-parser" );
|
||||
const logger = require( "morgan" );
|
||||
|
||||
const indexRouter = require( "./routes/index" );
|
||||
|
||||
const app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'pug');
|
||||
|
||||
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('/', indexRouter);
|
||||
|
||||
// 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');
|
||||
});
|
||||
|
||||
module.exports = app;
|
21
bin/dbHandler.js
Normal file
21
bin/dbHandler.js
Normal file
@ -0,0 +1,21 @@
|
||||
const mongoose = require("mongoose");
|
||||
function connect( )
|
||||
{
|
||||
return new Promise( async (resolve, reject) => {
|
||||
console.log("Connecting to DB...")
|
||||
let url = process.env.MONGODB_URL || "mongodb://localhost:27017/pm25";
|
||||
try {
|
||||
mongoose.connect(url, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true
|
||||
})
|
||||
resolve();
|
||||
console.log("Connected!");
|
||||
} catch( e )
|
||||
{
|
||||
reject(e);
|
||||
}
|
||||
} )
|
||||
|
||||
}
|
||||
module.exports = {connect}
|
90
bin/www
Executable file
90
bin/www
Executable file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const app = require('../app');
|
||||
const debug = require('debug')('pm25:server');
|
||||
const http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
const port = normalizePort(process.env.PORT || '8081');
|
||||
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) {
|
||||
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);
|
||||
}
|
16
log.txt
Normal file
16
log.txt
Normal file
@ -0,0 +1,16 @@
|
||||
yarn run v1.22.19
|
||||
$ node ./bin/www
|
||||
[0mGET / [36m304 [0m454.863 ms - -[0m
|
||||
[0mGET /stylesheets/style.css [36m304 [0m4.045 ms - -[0m
|
||||
[0mGET /javascripts/main.js [36m304 [0m1.168 ms - -[0m
|
||||
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
|
||||
yarn run v1.22.19
|
||||
$ node main.js
|
||||
Starting PM2.5 Sensor Server
|
||||
Connecting to DB...
|
||||
Connected!
|
||||
yarn run v1.22.19
|
||||
$ node main.js
|
||||
Starting PM2.5 Sensor Server
|
||||
Connecting to DB...
|
||||
Connected!
|
14
main.js
Normal file
14
main.js
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
const dbHandler = require("./bin/dbHandler");
|
||||
|
||||
console.log("Starting PM2.5 Sensor Server");
|
||||
|
||||
|
||||
async function main()
|
||||
{
|
||||
await dbHandler.connect();
|
||||
|
||||
require("./bin/www"); // Starting Webserver
|
||||
}
|
||||
main();
|
||||
|
13
models/PMEntry.js
Normal file
13
models/PMEntry.js
Normal file
@ -0,0 +1,13 @@
|
||||
const mongoose = require( "mongoose" );
|
||||
|
||||
// PM2.5 = 2.10, PM10 = 5.30
|
||||
const schema = new mongoose.Schema( {
|
||||
pm25: { type: Number, required: true },
|
||||
pm10:{ type: Number, required: true },
|
||||
sensor_name: {type: String}
|
||||
}, { timestamps: true } );
|
||||
const Model = mongoose.model(
|
||||
"PMEntry",
|
||||
schema,
|
||||
);
|
||||
module.exports = Model;
|
14
models/Status.js
Normal file
14
models/Status.js
Normal file
@ -0,0 +1,14 @@
|
||||
const mongoose = require( "mongoose" );
|
||||
|
||||
// PM2.5 = 2.10, PM10 = 5.30
|
||||
const schema = new mongoose.Schema( {
|
||||
error: { type: Boolean, required: true },
|
||||
sleeping: { type: Boolean, required: true },
|
||||
ip: { type: String, required: true },
|
||||
|
||||
}, { timestamps: true } );
|
||||
const Model = mongoose.model(
|
||||
"Status",
|
||||
schema,
|
||||
);
|
||||
module.exports = Model;
|
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "pm25",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^4.3.0",
|
||||
"chartjs-plugin-annotation": "^3.0.0",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"mongoose": "^7.1.1",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "2.0.0-beta11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.82.1",
|
||||
"webpack-cli": "^5.1.1"
|
||||
}
|
||||
}
|
200
public/javascripts/main.js
Normal file
200
public/javascripts/main.js
Normal file
@ -0,0 +1,200 @@
|
||||
const lastStatus = document.getElementById("lastStatus");
|
||||
|
||||
const pm10 = document.getElementById("pm10");
|
||||
const pm25 = document.getElementById("pm25");
|
||||
|
||||
|
||||
// Define color ranges for the different value ranges
|
||||
const colorRanges = {
|
||||
good: 'rgba(154, 204, 91, 0.5)',
|
||||
moderate: 'rgba(255, 235, 59, 0.5)',
|
||||
unhealthySensitive: 'rgba(255, 152, 0, 0.5)',
|
||||
unhealthy: 'rgba(255, 59, 59, 0.5)',
|
||||
veryUnhealthy: 'rgba(143, 63, 151, 0.5)',
|
||||
hazardous: 'rgba(126, 10, 2, 0.5)',
|
||||
};
|
||||
|
||||
// Define the annotations
|
||||
const annotations = {
|
||||
annotations:
|
||||
[
|
||||
{
|
||||
type: 'box',
|
||||
yMin: 0,
|
||||
yMax: 12,
|
||||
backgroundColor: colorRanges.good,
|
||||
},
|
||||
{
|
||||
type: 'box',
|
||||
|
||||
yMin: 12.1,
|
||||
yMax: 35.4,
|
||||
backgroundColor: colorRanges.moderate,
|
||||
},
|
||||
{
|
||||
type: 'box',
|
||||
|
||||
yMin: 35.5,
|
||||
yMax: 55.4,
|
||||
backgroundColor: colorRanges.unhealthySensitive,
|
||||
},
|
||||
{
|
||||
type: 'box',
|
||||
|
||||
yMin: 55.5,
|
||||
yMax: 150.4,
|
||||
backgroundColor: colorRanges.unhealthy,
|
||||
},
|
||||
{
|
||||
type: 'box',
|
||||
|
||||
yMin: 150.5,
|
||||
yMax: 250.4,
|
||||
backgroundColor: colorRanges.veryUnhealthy,
|
||||
},
|
||||
{
|
||||
type: 'box',
|
||||
yMin: 250.5,
|
||||
yMax: 500.4,
|
||||
backgroundColor: colorRanges.hazardous,
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
// Initialisiere das Chart-Objekt
|
||||
var ctx = document.getElementById('chart').getContext('2d');
|
||||
var chart = new Chart(ctx, {
|
||||
// Art des Diagramms
|
||||
type: 'line',
|
||||
|
||||
// Daten
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'PM2.5',
|
||||
data: [],
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgba(255,99,132,1)',
|
||||
borderWidth: 2,
|
||||
fill: false
|
||||
}, {
|
||||
label: 'PM10',
|
||||
data: [],
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 2,
|
||||
fill: false
|
||||
}]
|
||||
},
|
||||
|
||||
// Konfigurationsoptionen
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Feinstaubwerte'
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
/*type: 'time',
|
||||
time: {
|
||||
unit: 'hour',
|
||||
displayFormats: {
|
||||
hour: 'MMM D, hA'
|
||||
}
|
||||
},*/
|
||||
display: true,
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'µg/m³'
|
||||
},
|
||||
beginAtZero: true,
|
||||
suggestedMin: 0,
|
||||
suggestedMax: 10
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
annotation: annotations
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// Funktion zum Hinzufügen neuer Datenpunkte
|
||||
function addData(time, pm25, pm10) {
|
||||
// Füge die neuen Datenpunkte hinzu
|
||||
chart.data.labels.push(time);
|
||||
chart.data.datasets[0].data.push(pm25);
|
||||
chart.data.datasets[1].data.push(pm10);
|
||||
|
||||
// Begrenze die Anzahl der Datenpunkte auf 100 oder die letzten 12 Stunden
|
||||
var maxPoints = 60;
|
||||
var maxTime = new Date();
|
||||
maxTime.setHours(maxTime.getHours() - 12);
|
||||
|
||||
while (chart.data.labels.length > maxPoints || (chart.data.labels.length > 0 && new Date(chart.data.labels[0]) < maxTime)) {
|
||||
chart.data.labels.shift();
|
||||
chart.data.datasets[0].data.shift();
|
||||
chart.data.datasets[1].data.shift();
|
||||
}
|
||||
|
||||
// Aktualisiere das Chart
|
||||
chart.update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
setInterval(( ) => {
|
||||
//addData(new Date().toLocaleTimeString(), 1+Math.floor(Math.random() *2), 3+Math.floor(Math.random() * 2));
|
||||
loadValues();
|
||||
loadStatus();
|
||||
}, 2000);
|
||||
|
||||
|
||||
function httpGetAsync(theUrl)
|
||||
{
|
||||
return new Promise( (resolve) => {
|
||||
const xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = function() {
|
||||
if (xmlHttp.readyState === 4 && xmlHttp.status === 200)
|
||||
resolve(JSON.parse(xmlHttp.responseText));
|
||||
}
|
||||
xmlHttp.open("GET", theUrl, true); // true for asynchronous
|
||||
xmlHttp.send(null);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let last_request = new Date();
|
||||
last_request.setDate(last_request.getDate()-1);
|
||||
|
||||
function loadValues()
|
||||
{
|
||||
return new Promise( async () => {
|
||||
let data = await httpGetAsync("/data/" + last_request.getTime() );
|
||||
for( let entry of data )
|
||||
{
|
||||
addData(new Date(entry.createdAt).toLocaleTimeString(), entry.pm25, entry.pm10)
|
||||
pm25.innerText = "" + (Math.round(entry.pm25*100)/100);
|
||||
pm10.innerText = "" + (Math.round(entry.pm10*100)/100);
|
||||
}
|
||||
last_request = new Date();
|
||||
})
|
||||
}
|
||||
function loadStatus()
|
||||
{
|
||||
return new Promise( async () => {
|
||||
let data = await httpGetAsync("/status" );
|
||||
if( data.sleeping )
|
||||
lastStatus.innerHTML = "Letzter Status:<br>Schläft | " + (new Date(data.createdAt).toLocaleTimeString()) + "<br>Status: " + (data["error"] ? "Fehler!" : "OK");
|
||||
else
|
||||
lastStatus.innerHTML = "Letzter Status:<br>Misst | " + (new Date(data.createdAt).toLocaleTimeString()) + "<br>Status: " + (data["error"] ? "Fehler!" : "OK");
|
||||
})
|
||||
}
|
||||
loadStatus();
|
||||
loadValues();
|
28
public/javascripts/main2.js
Normal file
28
public/javascripts/main2.js
Normal file
@ -0,0 +1,28 @@
|
||||
const options = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
|
||||
datasets: [{
|
||||
label: '# of Votes',
|
||||
data: [12, 19, 3, 5, 2, 3],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
annotation: {
|
||||
annotations: {
|
||||
box1: {
|
||||
type: "box",
|
||||
yMin: 5,
|
||||
yMax: 10,
|
||||
backgroundColor: "rgba(255, 99, 132, 0.25)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = document.getElementById('chart').getContext('2d');
|
||||
const chart = new Chart(ctx, options);
|
15218
public/javascripts/web.bundle.js
Normal file
15218
public/javascripts/web.bundle.js
Normal file
File diff suppressed because one or more lines are too long
28
public/stylesheets/style.css
Normal file
28
public/stylesheets/style.css
Normal file
@ -0,0 +1,28 @@
|
||||
body {
|
||||
font: 1.3em "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
|
||||
canvas {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: -1%;
|
||||
}
|
||||
|
||||
#chart {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#lastStatus {
|
||||
font-size: 0.8em;
|
||||
}
|
64
routes/index.js
Normal file
64
routes/index.js
Normal file
@ -0,0 +1,64 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const PMEntry = require("../models/PMEntry");
|
||||
const Status = require("../models/Status");
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'PM2.5 & PM10' });
|
||||
});
|
||||
|
||||
|
||||
|
||||
router.get('/data/:timestamp', async (req,res) => {
|
||||
let date = new Date(req.params.timestamp);
|
||||
|
||||
try {
|
||||
let entries = await PMEntry.find({ updatedAt: {$gt: req.params.timestamp} });
|
||||
res.json(entries);
|
||||
} catch( e )
|
||||
{
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
router.post("/push", async(req, res) => {
|
||||
console.log(req.body);
|
||||
res.status(200);
|
||||
res.send("ok");
|
||||
res.end();
|
||||
|
||||
let entry = new PMEntry();
|
||||
entry.pm10 = req.body.pm10;
|
||||
entry.pm25 = req.body.pm25;
|
||||
|
||||
if(entry.pm10 > 500 || entry.pm25 > 500 )
|
||||
return;
|
||||
|
||||
await entry.save();
|
||||
|
||||
});
|
||||
|
||||
|
||||
router.get("/status", async(req,res) => {
|
||||
let status = await Status.findOne({}).sort({createdAt: -1});
|
||||
res.json(status);
|
||||
});
|
||||
|
||||
router.post("/status", async(req,res) => {
|
||||
console.log(req.body);
|
||||
|
||||
let status = new Status();
|
||||
status.error = req.body.error;
|
||||
status.ip = req.body.ip;
|
||||
status.sleeping = req.body.sleeping;
|
||||
|
||||
await status.save();
|
||||
res.status(200);
|
||||
res.end();
|
||||
});
|
||||
|
||||
module.exports = router;
|
6
views/error.pug
Normal file
6
views/error.pug
Normal file
@ -0,0 +1,6 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
7
views/index.pug
Normal file
7
views/index.pug
Normal file
@ -0,0 +1,7 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1 PM2.5 [<span id="pm25"></span>] & PM10 [<span id="pm10"></span>]
|
||||
p#lastStatus
|
||||
div(style="width:100%; height:75vh;")
|
||||
canvas#chart
|
10
views/layout.pug
Normal file
10
views/layout.pug
Normal file
@ -0,0 +1,10 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
script(src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.4.0/chart.js")
|
||||
script(src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/1.0.2/chartjs-plugin-annotation.js")
|
||||
body
|
||||
block content
|
||||
script(src="/javascripts/main.js")
|
27
webpack.config.js
Normal file
27
webpack.config.js
Normal file
@ -0,0 +1,27 @@
|
||||
const path = require( "path" );
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
entry: {
|
||||
web: "./public/javascripts/main.js",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
//exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".js"],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].bundle.js",
|
||||
path: path.resolve( __dirname, "public/javascripts/" ),
|
||||
},
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user