944 lines
34 KiB
PHP
944 lines
34 KiB
PHP
<?php include "../../session.php";
|
|
include "../../userAccess.php"; ?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>LASUCA Controls - Multi-Chart Real-Time Dashboard</title>
|
|
<!-- Chart.js imports -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.1/dist/chartjs-adapter-moment.min.js"></script>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.css" />
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js"></script>
|
|
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
background: #2c3e50;
|
|
color: #fff;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.dashboard-header {
|
|
background: #34495e;
|
|
padding: 15px 20px;
|
|
border-bottom: 2px solid #3498db;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1000;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.dashboard-header h1 {
|
|
color: #3498db;
|
|
font-size: 2rem;
|
|
margin: 0;
|
|
text-align: center;
|
|
}
|
|
|
|
.global-controls {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 20px;
|
|
padding: 15px;
|
|
background: #34495e;
|
|
border-bottom: 1px solid #3498db;
|
|
}
|
|
|
|
.global-control-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.global-control-group label {
|
|
font-weight: bold;
|
|
color: #ecf0f1;
|
|
}
|
|
|
|
.dashboard-container {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
grid-template-rows: 1fr 1fr;
|
|
gap: 15px;
|
|
padding: 15px;
|
|
height: calc(100vh - 140px);
|
|
min-height: 600px;
|
|
}
|
|
|
|
.chart-panel {
|
|
background: #ecf0f1;
|
|
border-radius: 5px;
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.chart-header {
|
|
background: #3498db;
|
|
color: white;
|
|
padding: 12px 15px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
min-height: 50px;
|
|
}
|
|
|
|
.chart-title {
|
|
font-weight: bold;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.chart-status {
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.8rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.chart-status.running {
|
|
background: #27ae60;
|
|
color: white;
|
|
}
|
|
|
|
.chart-status.stopped {
|
|
background: #e74c3c;
|
|
color: white;
|
|
}
|
|
|
|
.chart-controls {
|
|
padding: 12px 15px;
|
|
background: #bdc3c7;
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
border-bottom: 1px solid #95a5a6;
|
|
}
|
|
|
|
.control-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
.control-group label {
|
|
font-size: 0.85rem;
|
|
font-weight: bold;
|
|
color: #2c3e50;
|
|
min-width: 60px;
|
|
}
|
|
|
|
select, button {
|
|
padding: 6px 10px;
|
|
border: 1px solid #95a5a6;
|
|
border-radius: 4px;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
select {
|
|
background: white;
|
|
min-width: 140px;
|
|
}
|
|
|
|
button {
|
|
background: #3498db;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: background 0.3s;
|
|
}
|
|
|
|
button:hover:not(:disabled) {
|
|
background: #2980b9;
|
|
}
|
|
|
|
button:disabled {
|
|
background: #95a5a6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
button.start-btn {
|
|
background: #27ae60;
|
|
}
|
|
|
|
button.start-btn:hover:not(:disabled) {
|
|
background: #219a52;
|
|
}
|
|
|
|
button.stop-btn {
|
|
background: #e74c3c;
|
|
}
|
|
|
|
button.stop-btn:hover:not(:disabled) {
|
|
background: #c0392b;
|
|
}
|
|
|
|
.chart-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
|
|
gap: 8px;
|
|
padding: 10px 15px;
|
|
background: #ecf0f1;
|
|
border-bottom: 1px solid #bdc3c7;
|
|
}
|
|
|
|
.stat-box {
|
|
background: #fff;
|
|
padding: 8px;
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
border: 1px solid #bdc3c7;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 1rem;
|
|
font-weight: bold;
|
|
color: #3498db;
|
|
}
|
|
|
|
.stat-value-secondary {
|
|
font-size: 1rem;
|
|
font-weight: bold;
|
|
color: #27ae60;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.7rem;
|
|
color: #7f8c8d;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.chart-container {
|
|
flex: 1;
|
|
padding: 10px;
|
|
background: white;
|
|
position: relative;
|
|
min-height: 200px;
|
|
}
|
|
|
|
.chart-container canvas {
|
|
width: 100% !important;
|
|
height: 100% !important;
|
|
}
|
|
|
|
/* Global control styling */
|
|
.global-controls select,
|
|
.global-controls button {
|
|
padding: 8px 12px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.global-controls button {
|
|
background: #e67e22;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.global-controls button:hover {
|
|
background: #d35400;
|
|
}
|
|
|
|
/* Responsive design */
|
|
@media (max-width: 1200px) {
|
|
.dashboard-container {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: repeat(4, 1fr);
|
|
height: auto;
|
|
min-height: 2400px;
|
|
}
|
|
|
|
.chart-panel {
|
|
min-height: 500px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.chart-controls {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.control-group {
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.global-controls {
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="dashboard-header">
|
|
<h1>LASUCA Controls - Real-Time Multi-Chart Dashboard</h1>
|
|
</div>
|
|
|
|
<div class="global-controls">
|
|
<div class="global-control-group">
|
|
<label>Update Interval:</label>
|
|
<select id="globalUpdateInterval">
|
|
<option value="5000" selected>5 Seconds</option>
|
|
<option value="10000">10 Seconds</option>
|
|
<option value="30000">30 Seconds</option>
|
|
</select>
|
|
</div>
|
|
<div class="global-control-group">
|
|
<label>Time Window:</label>
|
|
<select id="globalTimeWindow">
|
|
<option value="10">10 Minutes</option>
|
|
<option value="15" selected>15 Minutes</option>
|
|
<option value="30">30 Minutes</option>
|
|
<option value="60">1 Hour</option>
|
|
<!--<option value="60">60 Minutes</option>-->
|
|
</select>
|
|
</div>
|
|
<button onclick="startAllCharts()">Start All Charts</button>
|
|
<button onclick="stopAllCharts()">Stop All Charts</button>
|
|
<button onclick="clearAllCharts()">Clear All Charts</button>
|
|
</div>
|
|
|
|
<div class="dashboard-container">
|
|
<!-- Chart 1 -->
|
|
<div class="chart-panel" id="panel1">
|
|
<div class="chart-header">
|
|
<div class="chart-title" id="title1">Chart 1</div>
|
|
<div class="chart-status stopped" id="status1">STOPPED</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<div class="control-group">
|
|
<label>Primary:</label>
|
|
<select id="tagSelect1_1">
|
|
<option value="">Loading...</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Secondary:</label>
|
|
<select id="tagSelect1_2">
|
|
<option value="">Optional...</option>
|
|
</select>
|
|
</div>
|
|
<button class="start-btn" onclick="startChart(1)">Start</button>
|
|
<button class="stop-btn" onclick="stopChart(1)">Stop</button>
|
|
<button onclick="clearChart(1)">Clear</button>
|
|
</div>
|
|
<div class="chart-stats">
|
|
<div class="stat-box">
|
|
<div id="current1_1" class="stat-value">--</div>
|
|
<div class="stat-label">Current 1</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="current1_2" class="stat-value-secondary">--</div>
|
|
<div class="stat-label">Current 2</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="points1" class="stat-value">0</div>
|
|
<div class="stat-label">Points</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="update1" class="stat-value">--</div>
|
|
<div class="stat-label">Updated</div>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart1"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chart 2 -->
|
|
<div class="chart-panel" id="panel2">
|
|
<div class="chart-header">
|
|
<div class="chart-title" id="title2">Chart 2</div>
|
|
<div class="chart-status stopped" id="status2">STOPPED</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<div class="control-group">
|
|
<label>Primary:</label>
|
|
<select id="tagSelect2_1">
|
|
<option value="">Loading...</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Secondary:</label>
|
|
<select id="tagSelect2_2">
|
|
<option value="">Optional...</option>
|
|
</select>
|
|
</div>
|
|
<button class="start-btn" onclick="startChart(2)">Start</button>
|
|
<button class="stop-btn" onclick="stopChart(2)">Stop</button>
|
|
<button onclick="clearChart(2)">Clear</button>
|
|
</div>
|
|
<div class="chart-stats">
|
|
<div class="stat-box">
|
|
<div id="current2_1" class="stat-value">--</div>
|
|
<div class="stat-label">Current 1</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="current2_2" class="stat-value-secondary">--</div>
|
|
<div class="stat-label">Current 2</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="points2" class="stat-value">0</div>
|
|
<div class="stat-label">Points</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="update2" class="stat-value">--</div>
|
|
<div class="stat-label">Updated</div>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart2"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chart 3 -->
|
|
<div class="chart-panel" id="panel3">
|
|
<div class="chart-header">
|
|
<div class="chart-title" id="title3">Chart 3</div>
|
|
<div class="chart-status stopped" id="status3">STOPPED</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<div class="control-group">
|
|
<label>Primary:</label>
|
|
<select id="tagSelect3_1">
|
|
<option value="">Loading...</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Secondary:</label>
|
|
<select id="tagSelect3_2">
|
|
<option value="">Optional...</option>
|
|
</select>
|
|
</div>
|
|
<button class="start-btn" onclick="startChart(3)">Start</button>
|
|
<button class="stop-btn" onclick="stopChart(3)">Stop</button>
|
|
<button onclick="clearChart(3)">Clear</button>
|
|
</div>
|
|
<div class="chart-stats">
|
|
<div class="stat-box">
|
|
<div id="current3_1" class="stat-value">--</div>
|
|
<div class="stat-label">Current 1</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="current3_2" class="stat-value-secondary">--</div>
|
|
<div class="stat-label">Current 2</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="points3" class="stat-value">0</div>
|
|
<div class="stat-label">Points</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="update3" class="stat-value">--</div>
|
|
<div class="stat-label">Updated</div>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart3"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chart 4 -->
|
|
<div class="chart-panel" id="panel4">
|
|
<div class="chart-header">
|
|
<div class="chart-title" id="title4">Chart 4</div>
|
|
<div class="chart-status stopped" id="status4">STOPPED</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<div class="control-group">
|
|
<label>Primary:</label>
|
|
<select id="tagSelect4_1">
|
|
<option value="">Loading...</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Secondary:</label>
|
|
<select id="tagSelect4_2">
|
|
<option value="">Optional...</option>
|
|
</select>
|
|
</div>
|
|
<button class="start-btn" onclick="startChart(4)">Start</button>
|
|
<button class="stop-btn" onclick="stopChart(4)">Stop</button>
|
|
<button onclick="clearChart(4)">Clear</button>
|
|
</div>
|
|
<div class="chart-stats">
|
|
<div class="stat-box">
|
|
<div id="current4_1" class="stat-value">--</div>
|
|
<div class="stat-label">Current 1</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="current4_2" class="stat-value-secondary">--</div>
|
|
<div class="stat-label">Current 2</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="points4" class="stat-value">0</div>
|
|
<div class="stat-label">Points</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div id="update4" class="stat-value">--</div>
|
|
<div class="stat-label">Updated</div>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart4"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Global variables for multi-chart management
|
|
const charts = {};
|
|
const chartData = {};
|
|
const updateTimers = {};
|
|
const chartStates = {};
|
|
let globalTimeWindow = 15;
|
|
let globalUpdateInterval = 5000;
|
|
let availableTags = [];
|
|
|
|
// Chart configuration template
|
|
function getChartConfig(chartId) {
|
|
return {
|
|
type: 'line',
|
|
data: {
|
|
datasets: [{
|
|
label: 'Primary Tag',
|
|
data: [],
|
|
borderColor: '#3498db',
|
|
backgroundColor: 'rgba(52, 152, 219, 0.1)',
|
|
borderWidth: 2,
|
|
fill: false,
|
|
tension: 0.1,
|
|
pointRadius: 0,
|
|
pointHoverRadius: 0,
|
|
hitRadius: 0,
|
|
yAxisID: 'y1'
|
|
}, {
|
|
label: 'Secondary Tag',
|
|
data: [],
|
|
borderColor: '#27ae60',
|
|
backgroundColor: 'rgba(39, 174, 96, 0.1)',
|
|
borderWidth: 2,
|
|
fill: false,
|
|
tension: 0.1,
|
|
pointRadius: 0,
|
|
pointHoverRadius: 0,
|
|
hitRadius: 0,
|
|
yAxisID: 'y2',
|
|
hidden: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
animation: {
|
|
duration: 200
|
|
},
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
},
|
|
scales: {
|
|
x: {
|
|
type: 'time',
|
|
time: {
|
|
unit: 'minute',
|
|
displayFormats: {
|
|
second: 'HH:mm:ss',
|
|
minute: 'HH:mm'
|
|
}
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: `Last ${globalTimeWindow} Minutes`,
|
|
font: { size: 10 }
|
|
},
|
|
min: function() {
|
|
return moment().subtract(globalTimeWindow, 'minutes').toDate();
|
|
},
|
|
max: function() {
|
|
return moment().toDate();
|
|
},
|
|
ticks: {
|
|
font: { size: 9 }
|
|
}
|
|
},
|
|
y1: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'left',
|
|
title: {
|
|
display: true,
|
|
text: 'Primary',
|
|
color: '#3498db',
|
|
font: { size: 10 }
|
|
},
|
|
grid: {
|
|
drawOnChartArea: true,
|
|
},
|
|
ticks: {
|
|
color: '#3498db',
|
|
font: { size: 9 }
|
|
}
|
|
},
|
|
y2: {
|
|
type: 'linear',
|
|
display: false,
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: 'Secondary',
|
|
color: '#27ae60',
|
|
font: { size: 10 }
|
|
},
|
|
grid: {
|
|
drawOnChartArea: false,
|
|
},
|
|
ticks: {
|
|
color: '#27ae60',
|
|
font: { size: 9 }
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top',
|
|
labels: {
|
|
font: { size: 10 },
|
|
usePointStyle: true
|
|
}
|
|
},
|
|
tooltip: {
|
|
titleFont: { size: 11 },
|
|
bodyFont: { size: 10 },
|
|
callbacks: {
|
|
title: function(context) {
|
|
return moment(context[0].parsed.x).format('HH:mm:ss');
|
|
},
|
|
label: function(context) {
|
|
return `${context.dataset.label}: ${context.parsed.y.toFixed(2)}`;
|
|
}
|
|
}
|
|
},
|
|
zoom: {
|
|
pan: {
|
|
enabled: true,
|
|
modifierKey: 'ctrl',
|
|
mode: 'x'
|
|
},
|
|
zoom: {
|
|
wheel: { enabled: true, modifierKey: 'shift' },
|
|
pinch: { enabled: true },
|
|
mode: 'x'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Initialize all charts
|
|
function initializeCharts() {
|
|
for (let i = 1; i <= 4; i++) {
|
|
const ctx = document.getElementById(`chart${i}`).getContext('2d');
|
|
charts[i] = new Chart(ctx, getChartConfig(i));
|
|
chartData[i] = { primary: [], secondary: [] };
|
|
chartStates[i] = { running: false, timer: null };
|
|
}
|
|
}
|
|
|
|
// Load tags for all dropdowns
|
|
async function loadTags() {
|
|
console.log('Loading tags for all charts...');
|
|
try {
|
|
const response = await fetch('get_tags.php');
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.tags) {
|
|
availableTags = data.tags;
|
|
|
|
// Populate all select boxes
|
|
for (let i = 1; i <= 4; i++) {
|
|
const select1 = document.getElementById(`tagSelect${i}_1`);
|
|
const select2 = document.getElementById(`tagSelect${i}_2`);
|
|
|
|
select1.innerHTML = '<option value="">Select primary tag...</option>';
|
|
select2.innerHTML = '<option value="">Select secondary tag...</option>';
|
|
|
|
availableTags.forEach(tag => {
|
|
const option1 = document.createElement('option');
|
|
option1.value = tag.name;
|
|
option1.textContent = tag.name;
|
|
select1.appendChild(option1);
|
|
|
|
const option2 = document.createElement('option');
|
|
option2.value = tag.name;
|
|
option2.textContent = tag.name;
|
|
select2.appendChild(option2);
|
|
});
|
|
}
|
|
console.log('Tags loaded for all charts');
|
|
} else {
|
|
console.error('Failed to load tags');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading tags:', error);
|
|
}
|
|
}
|
|
|
|
// Fetch data for a specific chart
|
|
async function fetchChartData(chartId) {
|
|
const tag1 = document.getElementById(`tagSelect${chartId}_1`).value;
|
|
const tag2 = document.getElementById(`tagSelect${chartId}_2`).value;
|
|
|
|
if (!tag1) return;
|
|
|
|
try {
|
|
const endTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
|
const startTime = moment().subtract(globalTimeWindow, 'minutes').format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
// Fetch primary tag data
|
|
const data1 = await fetchTagData(tag1);
|
|
if (data1) {
|
|
chartData[chartId].primary = data1;
|
|
charts[chartId].data.datasets[0].data = data1;
|
|
charts[chartId].data.datasets[0].label = tag1;
|
|
}
|
|
|
|
// Fetch secondary tag data if selected
|
|
if (tag2 && tag2 !== tag1) {
|
|
const data2 = await fetchTagData(tag2);
|
|
if (data2) {
|
|
chartData[chartId].secondary = data2;
|
|
charts[chartId].data.datasets[1].data = data2;
|
|
charts[chartId].data.datasets[1].label = tag2;
|
|
charts[chartId].data.datasets[1].hidden = false;
|
|
charts[chartId].options.scales.y2.display = true;
|
|
}
|
|
} else {
|
|
chartData[chartId].secondary = [];
|
|
charts[chartId].data.datasets[1].data = [];
|
|
charts[chartId].data.datasets[1].hidden = true;
|
|
charts[chartId].options.scales.y2.display = false;
|
|
}
|
|
|
|
// Update time axis
|
|
charts[chartId].options.scales.x.min = moment().subtract(globalTimeWindow, 'minutes').toDate();
|
|
charts[chartId].options.scales.x.max = moment().toDate();
|
|
charts[chartId].options.scales.x.title.text = `Last ${globalTimeWindow} Minutes`;
|
|
|
|
charts[chartId].update('none');
|
|
updateChartStats(chartId);
|
|
updateChartTitle(chartId);
|
|
|
|
} catch (error) {
|
|
console.error(`Error fetching data for chart ${chartId}:`, error);
|
|
}
|
|
}
|
|
|
|
// Fetch data for individual tag
|
|
async function fetchTagData(tagName) {
|
|
try {
|
|
const endTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
|
const startTime = moment().subtract(globalTimeWindow, 'minutes').format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
const url = `realtime_data.php?tag=${encodeURIComponent(tagName)}&limit=7200&start_time=${encodeURIComponent(startTime)}&end_time=${encodeURIComponent(endTime)}`;
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.data && data.data.length > 0) {
|
|
return data.data.map(point => ({
|
|
x: new Date(point.x),
|
|
y: point.y
|
|
}));
|
|
}
|
|
return [];
|
|
} catch (error) {
|
|
console.error(`Error fetching data for ${tagName}:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Update chart statistics
|
|
function updateChartStats(chartId) {
|
|
const primary = chartData[chartId].primary;
|
|
const secondary = chartData[chartId].secondary;
|
|
|
|
// Primary tag stats
|
|
if (primary.length > 0) {
|
|
const current = primary[primary.length - 1].y;
|
|
document.getElementById(`current${chartId}_1`).textContent = current.toFixed(2);
|
|
} else {
|
|
document.getElementById(`current${chartId}_1`).textContent = '--';
|
|
}
|
|
|
|
// Secondary tag stats
|
|
if (secondary.length > 0) {
|
|
const current = secondary[secondary.length - 1].y;
|
|
document.getElementById(`current${chartId}_2`).textContent = current.toFixed(2);
|
|
} else {
|
|
document.getElementById(`current${chartId}_2`).textContent = '--';
|
|
}
|
|
|
|
// General stats
|
|
document.getElementById(`points${chartId}`).textContent = primary.length + secondary.length;
|
|
document.getElementById(`update${chartId}`).textContent = moment().format('HH:mm:ss');
|
|
}
|
|
|
|
// Update chart title
|
|
function updateChartTitle(chartId) {
|
|
const tag1 = document.getElementById(`tagSelect${chartId}_1`).value;
|
|
const tag2 = document.getElementById(`tagSelect${chartId}_2`).value;
|
|
|
|
let title = `Chart ${chartId}`;
|
|
if (tag1) {
|
|
title += ` - ${tag1}`;
|
|
if (tag2) {
|
|
title += ` & ${tag2}`;
|
|
}
|
|
}
|
|
|
|
document.getElementById(`title${chartId}`).textContent = title;
|
|
}
|
|
|
|
// Start individual chart
|
|
function startChart(chartId) {
|
|
const tag1 = document.getElementById(`tagSelect${chartId}_1`).value;
|
|
if (!tag1) {
|
|
alert(`Please select a primary tag for Chart ${chartId}`);
|
|
return;
|
|
}
|
|
|
|
if (chartStates[chartId].running) return;
|
|
|
|
chartStates[chartId].running = true;
|
|
document.getElementById(`status${chartId}`).textContent = 'RUNNING';
|
|
document.getElementById(`status${chartId}`).className = 'chart-status running';
|
|
|
|
// Initial fetch
|
|
fetchChartData(chartId);
|
|
|
|
// Start timer
|
|
chartStates[chartId].timer = setInterval(() => {
|
|
fetchChartData(chartId);
|
|
}, globalUpdateInterval);
|
|
|
|
console.log(`Started Chart ${chartId}`);
|
|
}
|
|
|
|
// Stop individual chart
|
|
function stopChart(chartId) {
|
|
if (!chartStates[chartId].running) return;
|
|
|
|
chartStates[chartId].running = false;
|
|
document.getElementById(`status${chartId}`).textContent = 'STOPPED';
|
|
document.getElementById(`status${chartId}`).className = 'chart-status stopped';
|
|
|
|
if (chartStates[chartId].timer) {
|
|
clearInterval(chartStates[chartId].timer);
|
|
chartStates[chartId].timer = null;
|
|
}
|
|
|
|
console.log(`Stopped Chart ${chartId}`);
|
|
}
|
|
|
|
// Clear individual chart
|
|
function clearChart(chartId) {
|
|
stopChart(chartId);
|
|
|
|
chartData[chartId] = { primary: [], secondary: [] };
|
|
charts[chartId].data.datasets[0].data = [];
|
|
charts[chartId].data.datasets[1].data = [];
|
|
charts[chartId].data.datasets[1].hidden = true;
|
|
charts[chartId].options.scales.y2.display = false;
|
|
charts[chartId].update();
|
|
|
|
// Reset stats
|
|
document.getElementById(`current${chartId}_1`).textContent = '--';
|
|
document.getElementById(`current${chartId}_2`).textContent = '--';
|
|
document.getElementById(`points${chartId}`).textContent = '0';
|
|
document.getElementById(`update${chartId}`).textContent = '--';
|
|
|
|
// Reset title
|
|
document.getElementById(`title${chartId}`).textContent = `Chart ${chartId}`;
|
|
}
|
|
|
|
// Global controls
|
|
function startAllCharts() {
|
|
for (let i = 1; i <= 4; i++) {
|
|
const tag1 = document.getElementById(`tagSelect${i}_1`).value;
|
|
if (tag1) {
|
|
startChart(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
function stopAllCharts() {
|
|
for (let i = 1; i <= 4; i++) {
|
|
stopChart(i);
|
|
}
|
|
}
|
|
|
|
function clearAllCharts() {
|
|
for (let i = 1; i <= 4; i++) {
|
|
clearChart(i);
|
|
}
|
|
}
|
|
|
|
// Handle global setting changes
|
|
document.getElementById('globalUpdateInterval').addEventListener('change', function() {
|
|
globalUpdateInterval = parseInt(this.value);
|
|
console.log(`Global update interval changed to ${globalUpdateInterval}ms`);
|
|
|
|
// Update all running charts
|
|
for (let i = 1; i <= 4; i++) {
|
|
if (chartStates[i].running) {
|
|
stopChart(i);
|
|
setTimeout(() => startChart(i), 100);
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('globalTimeWindow').addEventListener('change', function() {
|
|
globalTimeWindow = parseInt(this.value);
|
|
console.log(`Global time window changed to ${globalTimeWindow} minutes`);
|
|
|
|
// Update all charts
|
|
for (let i = 1; i <= 4; i++) {
|
|
charts[i].options.scales.x.title.text = `Last ${globalTimeWindow} Minutes`;
|
|
if (chartStates[i].running) {
|
|
fetchChartData(i);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initialize everything on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeCharts();
|
|
loadTags();
|
|
console.log('Multi-chart dashboard initialized');
|
|
|
|
Sortable.create(document.querySelector('.dashboard-container'), {
|
|
animation: 150,
|
|
handle: '.chart-header',
|
|
draggable: '.chart-panel'
|
|
});
|
|
});
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', function() {
|
|
stopAllCharts();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|