first commit
This commit is contained in:
12
api/Dockerfile
Normal file
12
api/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm init -y && npm install express
|
||||||
|
|
||||||
|
COPY server.js .
|
||||||
|
COPY data.json .
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
7
api/data.json
Normal file
7
api/data.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"CO2": 2.43,
|
||||||
|
"CO": 0.56,
|
||||||
|
"BZ": 3.11,
|
||||||
|
"AQ": 100,
|
||||||
|
"updated_at": "2026-01-30T03:48:16.033Z"
|
||||||
|
}
|
||||||
63
api/server.js
Normal file
63
api/server.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = 3000;
|
||||||
|
const DATA_FILE = "./data.json";
|
||||||
|
|
||||||
|
// Helper: read data
|
||||||
|
function readData() {
|
||||||
|
return JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: write data
|
||||||
|
function writeData(data) {
|
||||||
|
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// GET /api/data → read values
|
||||||
|
// ============================
|
||||||
|
app.get("/data", (req, res) => {
|
||||||
|
try {
|
||||||
|
const data = readData();
|
||||||
|
res.json(data);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: "Gagal membaca data" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===========================================
|
||||||
|
// GET /api/update?co2=&co=&bz=&aq=
|
||||||
|
// ===========================================
|
||||||
|
app.get("/update", (req, res) => {
|
||||||
|
const { co2, co, bz, aq } = req.query;
|
||||||
|
|
||||||
|
if (!co2 || !co || !bz || !aq) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: "Parameter wajib: co2, co, bz, aq"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newData = {
|
||||||
|
CO2: Number(co2),
|
||||||
|
CO: Number(co),
|
||||||
|
BZ: Number(bz),
|
||||||
|
AQ: Number(aq),
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeData(newData);
|
||||||
|
res.json({
|
||||||
|
message: "Data berhasil diperbarui",
|
||||||
|
data: newData
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: "Gagal menyimpan data" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`API berjalan di port ${PORT}`);
|
||||||
|
});
|
||||||
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: air-quality-web
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- ./site:/usr/share/nginx/html:ro
|
||||||
|
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
api:
|
||||||
|
build: ./api
|
||||||
|
container_name: air-quality-api
|
||||||
|
volumes:
|
||||||
|
- ./api/data.json:/app/data.json
|
||||||
|
restart: unless-stopped
|
||||||
17
nginx/default.conf
Normal file
17
nginx/default.conf
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://api:3000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
readme.md
Normal file
2
readme.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
## Air quality webserver using docker
|
||||||
|
### just run "docker compose up -d --build"
|
||||||
51
site/index.html
Normal file
51
site/index.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Sensor Kualitas Udara</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e5e7eb;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: #1e293b;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>🌱 Sensor Kualitas Udara</h1>
|
||||||
|
|
||||||
|
<div class="card">CO₂: <span id="co2">-</span></div>
|
||||||
|
<div class="card">CO: <span id="co">-</span></div>
|
||||||
|
<div class="card">BZ: <span id="bz">-</span></div>
|
||||||
|
<div class="card">AQ: <span id="aq">-</span></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/data");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
document.getElementById("co2").textContent = data.CO2 + 'ppm';
|
||||||
|
document.getElementById("co").textContent = data.CO + 'ppm';
|
||||||
|
document.getElementById("bz").textContent = data.BZ + 'ppm';
|
||||||
|
document.getElementById("aq").textContent = data.AQ + '%';
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch data", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
setInterval(fetchData, 3000); // refresh every 3 seconds
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user