This commit is contained in:
sebseb7
2025-12-20 21:17:48 +01:00
parent 3da9be5c1b
commit 12be2c7bf9
4 changed files with 222 additions and 88 deletions

View File

@@ -1,13 +1,13 @@
document.addEventListener('DOMContentLoaded', init);
// State
let currentRange = 'day';
let chartInstances = {};
// Determine API Base URL from script location (robust for subpaths)
const scriptPath = document.currentScript ? document.currentScript.src : window.location.href;
const API_BASE = scriptPath.substring(0, scriptPath.lastIndexOf('/') + 1) + 'api/';
// Store device info globally to simplify reload
let filteredDevices = [];
// Grouped: { "ControllerName": [ {dev info...}, {dev info...} ] }
let groupedDevices = {};
let currentRange = 'day';
let chartInstances = {};
async function init() {
setupControls();
await loadDevices();
@@ -23,80 +23,123 @@ function setupControls() {
loadData();
});
});
// Device Select
document.getElementById('deviceSelect').addEventListener('change', loadData);
}
/**
* Fetch and populate device list
* Fetch and setup device containers (Grouped by Controller)
*/
async function loadDevices() {
try {
const res = await fetch(`${API_BASE}devices`);
const devices = await res.json();
const rawDevices = await res.json();
const container = document.getElementById('devicesContainer');
container.innerHTML = '';
const select = document.getElementById('deviceSelect');
select.innerHTML = '';
if (devices.length === 0) {
const opt = document.createElement('option');
opt.text = "No devices found";
select.add(opt);
if (rawDevices.length === 0) {
container.innerHTML = '<p>No devices found.</p>';
return;
}
devices.forEach((dev, index) => {
const opt = document.createElement('option');
const label = `${dev.dev_name} - ${dev.port_name || 'Port ' + dev.port}`;
opt.text = label;
opt.value = JSON.stringify({ devName: dev.dev_name, port: dev.port, portName: dev.port_name });
select.add(opt);
// Group by Controller Name
groupedDevices = rawDevices.reduce((acc, dev) => {
if (!acc[dev.dev_name]) acc[dev.dev_name] = [];
acc[dev.dev_name].push(dev);
return acc;
}, {});
// Auto-select first device
if (index === 0) {
select.value = opt.value;
}
});
// Create Section per Controller
for (const [controllerName, ports] of Object.entries(groupedDevices)) {
const safeControllerName = controllerName.replace(/\s+/g, '_');
// Trigger initial load
if (select.value) {
loadData();
const section = document.createElement('div');
section.className = 'controller-section';
// Generate Ports HTML
let portsHtml = '';
ports.forEach(port => {
const safePortId = `${controllerName}_${port.port}`.replace(/\s+/g, '_');
const isLight = port.port_name && port.port_name.toLowerCase().includes('light');
const levelTitle = isLight ? 'Brightness' : 'Fan Speed';
portsHtml += `
<div class="port-card">
<h4>${port.port_name || 'Port ' + port.port}</h4>
<div class="canvas-container small">
<canvas id="level_${safePortId}"></canvas>
</div>
</div>
`;
});
section.innerHTML = `
<div class="controller-header">
<h2>${controllerName}</h2>
</div>
<div class="environment-row">
<div class="chart-wrapper wide">
<h3>Environment (Temp / Humidity)</h3>
<div class="canvas-container">
<canvas id="env_${safeControllerName}"></canvas>
</div>
</div>
</div>
<div class="ports-grid">
${portsHtml}
</div>
`;
container.appendChild(section);
}
// Trigger initial data load
loadData();
} catch (err) {
console.error("Failed to load devices", err);
}
}
/**
* Fetch data and render charts
* Fetch data and render
*/
async function loadData() {
const select = document.getElementById('deviceSelect');
if (!select.value) return;
for (const [controllerName, ports] of Object.entries(groupedDevices)) {
const safeControllerName = controllerName.replace(/\s+/g, '_');
const { devName, port, portName } = JSON.parse(select.value);
// 1. Fetch Environment Data (Use first port as representative source)
if (ports.length > 0) {
const firstPort = ports[0];
try {
const res = await fetch(`${API_BASE}history?devName=${encodeURIComponent(controllerName)}&port=${firstPort.port}&range=${currentRange}`);
const data = await res.json();
renderEnvChart(safeControllerName, data);
} catch (err) {
console.error(`Failed to load env data for ${controllerName}`, err);
}
}
// Update Titles based on type
const isLight = portName && portName.toLowerCase().includes('light');
document.getElementById('levelFanTitle').innerText = isLight ? 'Brightness Levels' : 'Fan Speed Levels';
// 2. Fetch Level Data for EACH port
for (const port of ports) {
const safePortId = `${controllerName}_${port.port}`.replace(/\s+/g, '_');
try {
const res = await fetch(`${API_BASE}history?devName=${encodeURIComponent(controllerName)}&port=${port.port}&range=${currentRange}`);
const data = await res.json();
try {
const res = await fetch(`${API_BASE}history?devName=${encodeURIComponent(devName)}&port=${port}&range=${currentRange}`);
const data = await res.json();
renderCharts(data, isLight);
} catch (err) {
console.error("Failed to load history", err);
const isLight = port.port_name && port.port_name.toLowerCase().includes('light');
renderLevelChart(safePortId, data, isLight);
} catch (err) {
console.error(`Failed to load level data for ${controllerName}:${port.port}`, err);
}
}
}
}
function renderCharts(data, isLight) {
function renderEnvChart(safeName, data) {
const labels = data.map(d => new Date(d.timestamp).toLocaleString());
const ctx = document.getElementById(`env_${safeName}`).getContext('2d');
// 1. Temp & Humidity
const tempCtx = document.getElementById('tempHumChart').getContext('2d');
updateChart('tempHum', tempCtx, labels, [
updateChart(`env_${safeName}`, ctx, labels, [
{
label: 'Temperature (°C)',
data: data.map(d => d.temp_c),
@@ -129,25 +172,17 @@ function renderCharts(data, isLight) {
}
}
});
}
// 2. VPD
const vpdCtx = document.getElementById('vpdChart').getContext('2d');
updateChart('vpd', vpdCtx, labels, [{
label: 'VPD (kPa)',
data: data.map(d => d.vpd),
borderColor: '#4bc0c0',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: true
}]);
// 3. Levels (Fan/Light)
const levelCtx = document.getElementById('levelChart').getContext('2d');
function renderLevelChart(safeId, data, isLight) {
const labels = data.map(d => new Date(d.timestamp).toLocaleString());
const ctx = document.getElementById(`level_${safeId}`).getContext('2d');
const levelLabel = isLight ? 'Brightness' : 'Fan Speed';
const levelColor = isLight ? '#ffcd56' : '#9966ff'; // Yellow for light, Purple for fan
const levelColor = isLight ? '#ffcd56' : '#9966ff';
updateChart('level', levelCtx, labels, [{
updateChart(`level_${safeId}`, ctx, labels, [{
label: levelLabel,
data: data.map(d => d.fan_speed), // fan_speed used for generic level in DB
data: data.map(d => d.fan_speed),
borderColor: levelColor,
backgroundColor: levelColor,
stepped: true
@@ -187,3 +222,5 @@ function updateChart(id, ctx, labels, datasets, extraOptions = {}) {
}
});
}
document.addEventListener('DOMContentLoaded', init);