first commit

This commit is contained in:
cowrie
2026-01-30 12:39:40 +08:00
commit a2f9e06d09
7 changed files with 173 additions and 0 deletions

12
api/Dockerfile Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,2 @@
## Air quality webserver using docker
### just run "docker compose up -d --build"

51
site/index.html Normal file
View 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>