From 433b7c4681b379b943a6184e308744bb4a03ff6c Mon Sep 17 00:00:00 2001 From: Baney Date: Tue, 22 Apr 2025 00:01:38 -0400 Subject: [PATCH] First Commit --- .env | 10 +++ package.json | 6 +- public/favicon.ico | Bin 0 -> 15406 bytes public/heatmap.html | 20 +++--- public/image/logo.png | Bin 0 -> 37478 bytes public/input.html | 36 ++++++----- public/scripts/heatmap.js | 75 +++++++++++++++-------- public/scripts/input.js | 30 ++++++--- public/styles.css | 56 +++++++++++------ public/trends.html | 13 ++-- server.js | 124 +++++++++++++++++++++++++------------- 11 files changed, 248 insertions(+), 122 deletions(-) create mode 100644 public/favicon.ico create mode 100644 public/image/logo.png diff --git a/.env b/.env index e69de29..56d681d 100644 --- a/.env +++ b/.env @@ -0,0 +1,10 @@ +# Server port +PORT=3000 + +# MariaDB connection +DB_CLIENT=mysql +DB_HOST=localhost +DB_PORT=3306 +DB_USER=your_db_user +DB_PASSWORD=your_db_password +DB_NAME=warehouse_heatmap \ No newline at end of file diff --git a/package.json b/package.json index 6b2faf7..35b3037 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ }, "dependencies": { "express": "^4.18.2", - "sqlite3": "^5.1.6", + "mysql2": "^3.2.0", + "knex": "^2.4.2", "body-parser": "^1.20.2" } - } - \ No newline at end of file + } \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bc341abc4664c9b8dd5948dbb193d7276d75e1d5 GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/public/heatmap.html b/public/heatmap.html index a4ef415..c2d9e75 100644 --- a/public/heatmap.html +++ b/public/heatmap.html @@ -3,24 +3,26 @@ + - Heat Map + Warehouse Heat Map
- - Warehouse Heat Map - - - + + | Fuego - Heat Tracker + + +
-
+
+
+
+
- - \ No newline at end of file diff --git a/public/image/logo.png b/public/image/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c27109ce55f42278e2738f5872ebfd5d29241fbb GIT binary patch literal 37478 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.z^qv;0yOA-9l2ny8)CFI&LI*m&(WVp?+L_aa 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 GefPhzw`9=( literal 0 HcmV?d00001 diff --git a/public/input.html b/public/input.html index dd2fd28..bf64a1d 100644 --- a/public/input.html +++ b/public/input.html @@ -3,29 +3,37 @@ + - Log Reading + Log Dual Readings -
- - Warehouse Heat Logger - - - + + | Fuego - Heat Tracker + + +
-

Enter Trailer Reading

+

Enter Inbound & Outbound Readings

- - - - - +
+ Inbound + + + +
+
+ Outbound + + + +
+
- + \ No newline at end of file diff --git a/public/scripts/heatmap.js b/public/scripts/heatmap.js index c6c3395..896f90a 100644 --- a/public/scripts/heatmap.js +++ b/public/scripts/heatmap.js @@ -1,24 +1,51 @@ -// Map setup -const map = L.map('heatmap-container').setView([0, 0], 1); -L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); - -// Dock door coordinates config -const coords = {}; -for (let d=106; d<=138; d++) coords[d] = [0, d-122]; -for (let d=142; d<=201; d++) coords[d] = [ -1, d-172]; -for (let d=202; d<=210; d++) coords[d] = [ 1, d-206]; - -// Color scale -function getColor(h) { - const pct = (h - 70) / 30; - return `rgba(${255},${Math.round(255*(1-pct))},0,0.7)`; -} - -// Render markers & listen SSE -fetch('/api/readings').then(r=>r.json()).then(all => all.forEach(plot)); -function plot({dockDoor, heatIndex}) { - const [lat, lon] = coords[dockDoor]; - L.rectangle([[lat-0.1, lon-0.1],[lat+0.1, lon+0.1]],{color:getColor(heatIndex)}).addTo(map); -} - -new EventSource('/api/stream').addEventListener('new-reading', e => plot(JSON.parse(e.data))); +// door ranges +const doors = [ + ...Array.from({length: 138 - 124 + 1}, (_, i) => 124 + i), + ...Array.from({length: 201 - 142 + 1}, (_, i) => 142 + i), + ...Array.from({length: 209 - 202 + 1}, (_, i) => 202 + i), + ]; + + // NOAA heat‐index formula + function getColorFromHI(H) { + const pct = Math.min(Math.max((H - 70) / 30, 0), 1); + const r = 255; + const g = Math.round(255 * (1 - pct)); + return `rgba(${r},${g},0,0.8)`; + } + + function createGrid() { + const row = document.getElementById('dock-row'); + doors.forEach(d => { + const sq = document.createElement('div'); + sq.className = 'dock-square'; + sq.dataset.door = d; + row.appendChild(sq); + }); + } + + // apply a new reading to its square + function colorize(door, hi) { + const sq = document.querySelector(`.dock-square[data-door="${door}"]`); + if (!sq) return; + sq.style.background = getColorFromHI(hi); + } + + async function init() { + createGrid(); + // initial fill + const all = await fetch('/api/readings').then(r=>r.json()); + // pick latest per door + const latest = {}; + all.forEach(r => { latest[r.dockDoor] = r.heatIndex; }); + Object.entries(latest).forEach(([door, hi]) => colorize(door, hi)); + + // subscribe SSE + const es = new EventSource('/api/stream'); + es.addEventListener('new-reading', e => { + const { dockDoor, heatIndex } = JSON.parse(e.data); + colorize(dockDoor, heatIndex); + }); + } + + document.addEventListener('DOMContentLoaded', init); + \ No newline at end of file diff --git a/public/scripts/input.js b/public/scripts/input.js index df0848e..7936441 100644 --- a/public/scripts/input.js +++ b/public/scripts/input.js @@ -1,13 +1,29 @@ const form = document.getElementById('reading-form'); +const inDoor = document.getElementById('inboundDoor'); +const outDoor = document.getElementById('outboundDoor'); +const inTemp = document.getElementById('inboundTemp'); +const outTemp = document.getElementById('outboundTemp'); +const inHum = document.getElementById('inboundHum'); +const outHum = document.getElementById('outboundHum'); + +// Auto-set direction fields (readonly) if you want display +// omitted here since direction hidden in dual-input + form.addEventListener('submit', e => { e.preventDefault(); - const data = { - dockDoor: +document.getElementById('dockDoor').value, - timestamp: document.getElementById('timestamp').value, - temperature: +document.getElementById('temperature').value, - humidity: +document.getElementById('humidity').value, + const payload = { + inbound: { + dockDoor: +inDoor.value, + temperature: +inTemp.value, + humidity: +inHum.value + }, + outbound: { + dockDoor: +outDoor.value, + temperature: +outTemp.value, + humidity: +outHum.value + } }; fetch('/api/readings', { - method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(res => res.json()).then(() => form.reset()); -}); +}); \ No newline at end of file diff --git a/public/styles.css b/public/styles.css index 4249c77..a466360 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1,3 +1,5 @@ +/* Favicon support (no CSS needed) */ + /* BINS Project Header & Buttons */ .main-header { background-color: #232F3E; @@ -7,37 +9,55 @@ position: fixed; top: 0; left: 0; right: 0; width: 100%; z-index: 1000; } - .logo { height: 40px; margin-right: 1rem; } - .header-title { color: #fff; font-size: 1.2rem; font-weight: bold; margin-right: auto; } - .nav-btn { - background-color: #FF9900; color: #111; - border: none; border-radius: 5px; - padding: 0.5rem 1rem; margin-left: 0.5rem; - cursor: pointer; font-weight: bold; + .main-header .logo { + height: 40px; + margin-right: 1rem; + } + .main-header .header-title { + color: #fff; + font-size: 1.2rem; + font-weight: bold; + margin-right: auto; + } + .main-header .nav-btn { + background-color: #FF9900; + color: #111; + border: none; + border-radius: 5px; + padding: 0.5rem 1rem; + margin-left: 0.5rem; + cursor: pointer; + font-weight: bold; + } + .main-header .nav-btn:hover { + background-color: #e48f00; } - .nav-btn:hover { background-color: #e48f00; } .page-container { margin-top: 70px; width: 95%; margin: 0 auto; padding: 1rem; - background-color: #fff; border-radius: 6px; + background-color: #fff; + border-radius: 6px; } .form-input, .form-textarea { - width: 90%; margin: 0.5rem auto; - display: block; padding: 0.5rem; - border: 1px solid #ccc; border-radius: 6px; + width: 90%; margin: 0.5rem auto; display: block; + padding: 0.5rem; border: 1px solid #ccc; border-radius: 6px; } .big-button { background-color: #28a745; color: white; - padding: 0.75rem 1.5rem; - border: none; border-radius: 6px; + padding: 0.75rem 1.5rem; border: none; border-radius: 6px; cursor: pointer; font-size: 1rem; margin: 1rem auto; display: block; } .big-button:hover { background-color: #218838; } .hidden { display: none; } - /* Existing styles */ - body { font-family: Arial, sans-serif; margin: 20px; } - #heatmap-container { height: 500px; width: 100%; } - canvas { max-width: 600px; margin: 20px auto; display: block; } \ No newline at end of file + /* Diagram styling */ + .diagram-container { width: 95%; margin: 0 auto; text-align: center; } + .dock-row { display: grid; grid-template-columns: repeat(83, 1fr); gap: 4px; margin-bottom: 8px; } + .dock-square { width: 100%; padding-top: 100%; position: relative; background: #ddd; border-radius: 2px; } + .dock-square::after { content: attr(data-door); position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.6); color: #333; } + .warehouse { width: 95%; height: 200px; margin: 0 auto; background: #ccc; border-radius: 6px; } + + /* Existing element styles */ + body { font-family: Arial, sans-serif; margin: 20px; } \ No newline at end of file diff --git a/public/trends.html b/public/trends.html index 304c74c..e6169f4 100644 --- a/public/trends.html +++ b/public/trends.html @@ -3,17 +3,18 @@ + - Trend Graphs + | Fuego - Heat Tracker
- - Temperature & Heat Index Trends - - - + + | Fuego - Heat Tracker + + +
diff --git a/server.js b/server.js index 56ac5a0..606a794 100644 --- a/server.js +++ b/server.js @@ -1,25 +1,40 @@ +require('dotenv').config(); const express = require('express'); -const sqlite3 = require('sqlite3').verbose(); const bodyParser = require('body-parser'); const path = require('path'); +const knex = require('knex'); +const axios = require('axios'); + +// Initialize MariaDB connection via Knex +const db = knex({ + client: process.env.DB_CLIENT, + connection: { + host: process.env.DB_HOST, + port: process.env.DB_PORT, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME + } +}); const app = express(); const PORT = process.env.PORT || 3000; +const slackWebhook = process.env.SLACK_WEBHOOK_URL; -// Initialize SQLite database -const db = new sqlite3.Database('./readings.db'); -db.serialize(() => { - db.run(` - CREATE TABLE IF NOT EXISTS readings ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - dockDoor INTEGER, - timestamp TEXT, - temperature REAL, - humidity REAL, - heatIndex REAL - ) - `); -}); +// Create table if not exists, now with direction +(async () => { + if (!await db.schema.hasTable('readings')) { + await db.schema.createTable('readings', table => { + table.increments('id').primary(); + table.integer('dockDoor'); + table.string('direction'); + table.timestamp('timestamp'); + table.float('temperature'); + table.float('humidity'); + table.float('heatIndex'); + }); + } +})(); // Compute heat index (NOAA formula) function computeHeatIndex(T, R) { @@ -30,11 +45,18 @@ function computeHeatIndex(T, R) { return Math.round(HI * 100) / 100; } -// Middleware & static +// Determine direction based on door number +function getDirection(door) { + door = Number(door); + if (door >= 124 && door <= 138) return 'Inbound'; + if (door >= 142 && door <= 201) return 'Outbound'; + if (door >= 202 && door <= 209) return 'Inbound'; + return 'Unknown'; +} + app.use(bodyParser.json()); app.use(express.static(path.join(__dirname, 'public'))); -// SSE clients let clients = []; app.get('/api/stream', (req, res) => { res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' }); @@ -47,38 +69,58 @@ function broadcast(event, data) { clients.forEach(res => res.write(payload)); } -// APIs -app.post('/api/readings', (req, res) => { - const { dockDoor, timestamp, temperature, humidity } = req.body; - const heatIndex = computeHeatIndex(temperature, humidity); - db.run( - `INSERT INTO readings (dockDoor, timestamp, temperature, humidity, heatIndex) VALUES (?, ?, ?, ?, ?)`, - [dockDoor, timestamp, temperature, humidity, heatIndex], - function(err) { - if (err) return res.status(500).json({ error: err.message }); - const reading = { id: this.lastID, dockDoor, timestamp, temperature, humidity, heatIndex }; - broadcast('new-reading', reading); - res.json(reading); +app.post('/api/readings', async (req, res) => { + try { + const { inbound, outbound } = req.body; // each: {dockDoor,temperature,humidity} + const timestamp = new Date(); + const entries = [inbound, outbound].map(r => { + const direction = getDirection(r.dockDoor); + const heatIndex = computeHeatIndex(r.temperature, r.humidity); + return { ...r, direction, timestamp, heatIndex }; + }); + // Insert both + const ids = await db('readings').insert(entries); + const saved = entries.map((e, i) => ({ id: ids[i], ...e })); + + // Broadcast and respond + saved.forEach(reading => broadcast('new-reading', reading)); + + // Slack notification with both + if (slackWebhook) { + const textLines = saved.map(r => + `Door *${r.dockDoor}* (${r.direction}) – Temp: ${r.temperature}°F, Humidity: ${r.humidity}%, HI: ${r.heatIndex}` + ); + await axios.post(slackWebhook, { text: 'New dual readings:\n' + textLines.join('\n') }); } - ); + res.json(saved); + } catch (err) { + console.error('Error saving readings or sending Slack:', err); + res.status(500).json({ error: err.message }); + } }); -app.get('/api/readings', (req, res) => { - db.all(`SELECT * FROM readings ORDER BY timestamp ASC`, (err, rows) => { - if (err) return res.status(500).json({ error: err.message }); +app.get('/api/readings', async (req, res) => { + try { + const rows = await db('readings').orderBy('timestamp', 'asc'); res.json(rows); - }); + } catch (err) { + res.status(500).json({ error: err.message }); + } }); -app.get('/api/export', (req, res) => { - db.all(`SELECT * FROM readings ORDER BY timestamp ASC`, (err, rows) => { - if (err) return res.status(500).send(err.message); +app.get('/api/export', async (req, res) => { + try { + const rows = await db('readings').orderBy('timestamp', 'asc'); res.setHeader('Content-disposition', 'attachment; filename=readings.csv'); res.set('Content-Type', 'text/csv'); - res.write('id,dockDoor,timestamp,temperature,humidity,heatIndex\n'); - rows.forEach(r => res.write(`${r.id},${r.dockDoor},${r.timestamp},${r.temperature},${r.humidity},${r.heatIndex}\n`)); + res.write('id,dockDoor,direction,timestamp,temperature,humidity,heatIndex\n'); + rows.forEach(r => + res.write(`${r.id},${r.dockDoor},${r.direction},${r.timestamp},${r.temperature},${r.humidity},${r.heatIndex}\n`) + ); res.end(); - }); + } catch (err) { + res.status(500).send(err.message); + } }); -app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); +app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); \ No newline at end of file