304 lines
12 KiB
JavaScript
304 lines
12 KiB
JavaScript
const fetchInterval = 500;
|
|
|
|
// State
|
|
let chart;
|
|
let activeSessionData = [];
|
|
let history = [];
|
|
let currentPage = 1;
|
|
let fullDataPage = 1;
|
|
let lastAcqStatus = 'waiting';
|
|
|
|
// DOM Elements
|
|
const progressBar = document.getElementById('progressBar');
|
|
const progressContainer = document.getElementById('progressContainer');
|
|
const timerDisplay = document.getElementById('timerDisplay');
|
|
const tableBody = document.getElementById('measurementsTable');
|
|
const detailPlaceholder = document.getElementById('detailPlaceholder');
|
|
const detailContent = document.getElementById('detailContent');
|
|
const fullDataTableBody = document.getElementById('fullDataTableBody');
|
|
const historyTableBody = document.getElementById('historyTableBody');
|
|
const pageInfo = document.getElementById('pageInfo');
|
|
const prevBtn = document.getElementById('prevPage');
|
|
const nextBtn = document.getElementById('nextPage');
|
|
const fullPrevBtn = document.getElementById('fullPrevPage');
|
|
const fullNextBtn = document.getElementById('fullNextPage');
|
|
const liveDataContainer = document.getElementById('liveDataContainer');
|
|
const historyDetailContainer = document.getElementById('historyDetailContainer');
|
|
const selectedData = document.getElementById('selectedData');
|
|
const plcIpDisplay = document.getElementById('plcIpDisplay');
|
|
const connStatusDot = document.getElementById('connStatusDot');
|
|
const latencyList = document.getElementById('latencyList');
|
|
const acquisitionStatusList = document.getElementById('acquisitionStatusList');
|
|
|
|
// Initialize Chart.js
|
|
function initChart() {
|
|
const ctx = document.getElementById('liveChart').getContext('2d');
|
|
chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
datasets: [
|
|
{ label: 'Value (mm)', data: [], borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, pointRadius: 0, borderWidth: 2, tension: 0 },
|
|
{ label: 'UCL', data: [], borderColor: '#facc15', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] },
|
|
{ label: 'LCL', data: [], borderColor: '#facc15', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] },
|
|
{ label: 'Target Value', data: [], borderColor: '#22c55e', borderWidth: 1.5, pointRadius: 0, fill: false, tension: 0 }
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: { type: 'linear', min: 0, max: 10000, title: { display: true, text: 'Position (mm)' } },
|
|
y: { min: -3000, max: 3000 }
|
|
},
|
|
animation: false
|
|
}
|
|
});
|
|
}
|
|
|
|
// Fetch data from Flask and update UI
|
|
async function updateData() {
|
|
try {
|
|
const response = await fetch('/data');
|
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
const data = await response.json();
|
|
|
|
if (data) {
|
|
// Update Connectivity Status
|
|
if (data.connected !== undefined) {
|
|
connStatusDot.className = data.connected
|
|
? 'w-2 h-2 bg-green-400 rounded-full mr-2 animate-pulse'
|
|
: 'w-2 h-2 bg-red-600 rounded-full mr-2';
|
|
plcIpDisplay.textContent = data.plc_ip || "Disconnected";
|
|
}
|
|
|
|
// Update Latencies
|
|
if (data.latencies) {
|
|
const totalLatency = Object.values(data.latencies).reduce((a, b) => a + b, 0);
|
|
latencyList.textContent = `Latency: ${totalLatency.toFixed(2)}ms`;
|
|
}
|
|
|
|
// Update acquisition status text
|
|
if (data.acq_status !== undefined) {
|
|
acquisitionStatusList.textContent = `• Mode: ${data.acq_status}`;
|
|
if (data.acq_status === "Acquisition") {
|
|
acquisitionStatusList.parentElement.classList.replace('text-gray-600', 'text-blue-400');
|
|
} else {
|
|
acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-gray-600');
|
|
}
|
|
}
|
|
|
|
// Handle Live Chart Update
|
|
if (data.db901 && data.db901.params) {
|
|
const acq = data.db901.params.acquisizione;
|
|
const fine = data.db901.params.fine_acq;
|
|
const pronti = data.db901.params.pronti;
|
|
const limits = data.limits || { ucl: 212, lcl: 208, target: 0 };
|
|
|
|
if (acq && !fine) {
|
|
// MODE: ACQUISITION
|
|
// Reset chart if we just switched from waiting
|
|
if (lastAcqStatus === 'Finished') {
|
|
chart.data.datasets.forEach(ds => ds.data = []);
|
|
lastAcqStatus = acq
|
|
}
|
|
|
|
if (data.db901.params.x_val !== undefined && data.db901.params.y_val !== undefined) {
|
|
const x = data.db901.params.x_val;
|
|
const y = data.db901.params.y_val;
|
|
|
|
chart.data.datasets[0].data.push({ x, y });
|
|
chart.data.datasets[1].data.push({ x, y: limits.ucl });
|
|
chart.data.datasets[2].data.push({ x, y: limits.lcl });
|
|
chart.data.datasets[3].data.push({ x, y: limits.target });
|
|
|
|
if (chart.data.datasets[0].data.length > 100) {
|
|
chart.data.datasets.forEach(ds => ds.data.shift());
|
|
}
|
|
chart.update('none');
|
|
}
|
|
} else if (fine && pronti) {
|
|
// MODE: FINISHED
|
|
lastAcqStatus = acq
|
|
acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-green-600');
|
|
|
|
if (data.db900 && data.db900.misure && data.db900.misure.length > 0) {
|
|
activeSessionData = data.db900.misure.map(m => ({
|
|
x: m.x,
|
|
y: m.y,
|
|
ts: m.ts || new Date()
|
|
}));
|
|
|
|
chart.data.datasets.forEach(ds => ds.data = []);
|
|
activeSessionData.forEach(p => {
|
|
chart.data.datasets[0].data.push({ x: p.x, y: p.y });
|
|
chart.data.datasets[1].data.push({ x: p.x, y: limits.ucl });
|
|
chart.data.datasets[2].data.push({ x: p.x, y: limits.lcl });
|
|
chart.data.datasets[3].data.push({ x: p.x, y: limits.target });
|
|
});
|
|
|
|
chart.update();
|
|
|
|
liveDataContainer.classList.remove('hidden');
|
|
historyDetailContainer.classList.add('hidden');
|
|
detailPlaceholder.classList.add('hidden');
|
|
detailContent.classList.remove('hidden');
|
|
renderFullSessionTable();
|
|
}
|
|
} else {
|
|
acquisitionStatusList.textContent = `• Mode: Polling (5Hz)`;
|
|
acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-gray-600');
|
|
}
|
|
}
|
|
lastAcqStatus = data.acq_status;
|
|
}
|
|
} catch (err) {
|
|
console.error("Error updating data:", err);
|
|
}
|
|
}
|
|
|
|
// Fetch history
|
|
async function fetchHistory() {
|
|
try {
|
|
const response = await fetch('/history');
|
|
if (response.ok) {
|
|
history = await response.json();
|
|
renderHistoryTable();
|
|
}
|
|
} catch (err) {
|
|
console.error("Error fetching history:", err);
|
|
}
|
|
}
|
|
|
|
function renderHistoryTable() {
|
|
historyTableBody.innerHTML = '';
|
|
if (history.length === 0) {
|
|
historyTableBody.innerHTML = '<tr><td colspan="4" class="p-2 text-center text-gray-400">No sessions recorded</td></tr>';
|
|
return;
|
|
}
|
|
|
|
const start = (currentPage - 1) * 10;
|
|
const end = start + 10;
|
|
const pageData = history.slice(start, end);
|
|
|
|
pageData.forEach((item, i) => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td class="p-2 border-b">${item.filename}</td>
|
|
<td class="p-2 border-b">${item.articolo} / ${item.cod_produzione}</td>
|
|
<td class="p-2 border-b">${new Date().toLocaleDateString()}</td>
|
|
<td class="p-2 border-b">
|
|
<button class="bg-blue-600 text-white px-2 py-1 rounded text-[10px] hover:bg-blue-700">View</button>
|
|
</td>
|
|
`;
|
|
row.onclick = () => loadHistoryFile(item.file_path);
|
|
historyTableBody.appendChild(row);
|
|
});
|
|
|
|
pageInfo.innerText = `Page ${currentPage} of ${Math.ceil(history.length / 10)}`;
|
|
prevBtn.disabled = currentPage === 1;
|
|
nextBtn.disabled = currentPage * 10 >= history.length;
|
|
}
|
|
|
|
async function loadHistoryFile(filePath) {
|
|
try {
|
|
const response = await fetch(`/history/${filePath}`);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
activeSessionData = data.misure.map(m => ({
|
|
x: m.x,
|
|
y: m.y,
|
|
ts: m.ts || new Date()
|
|
}));
|
|
|
|
chart.data.datasets.forEach(ds => ds.data = []);
|
|
activeSessionData.forEach(p => {
|
|
chart.data.datasets[0].data.push({ x: p.x, y: p.y });
|
|
chart.data.datasets[1].data.push({ x: p.x, y: 212 });
|
|
chart.data.datasets[2].data.push({ x: p.x, y: 208 });
|
|
chart.data.datasets[3].data.push({ x: p.x, y: 0 });
|
|
});
|
|
chart.update();
|
|
|
|
liveDataContainer.classList.remove('hidden');
|
|
historyDetailContainer.classList.remove('hidden');
|
|
detailPlaceholder.classList.add('hidden');
|
|
detailContent.classList.remove('hidden');
|
|
|
|
renderFullSessionTable();
|
|
selectedData.innerText = `Session Loaded: ${data.articolo} | ${data.cod_produzione} | ${data.num_certificato}`;
|
|
}
|
|
} catch (err) {
|
|
console.error("Error loading history file:", err);
|
|
}
|
|
}
|
|
|
|
function renderFullSessionTable() {
|
|
fullDataTableBody.innerHTML = '';
|
|
if (activeSessionData.length === 0) return;
|
|
|
|
const start = (fullDataPage - 1) * 50;
|
|
const end = start + 50;
|
|
const pageData = activeSessionData.slice(start, end);
|
|
|
|
pageData.forEach((p, i) => {
|
|
const row = document.createElement('tr');
|
|
const diff = Math.abs(p.y - 0);
|
|
let status = 'OK';
|
|
let colorClass = 'text-green-600 border-green-600';
|
|
|
|
if (diff > 3000) {
|
|
status = 'ERROR';
|
|
colorClass = 'text-red-600 border-red-600';
|
|
} else if (diff > 2000) {
|
|
status = 'WARNING';
|
|
colorClass = 'text-yellow-600 border-yellow-600';
|
|
}
|
|
|
|
row.innerHTML = `
|
|
<td class="p-2 border-b">${start + i + 1}</td>
|
|
<td class="p-2 border-b">${p.ts.toLocaleTimeString()}</td>
|
|
<td class="p-2 border-b font-mono">${p.y.toFixed(2)}</td>
|
|
<td class="p-2 border-b">
|
|
<span class="px-1 py-0.5 bg-white rounded-full text-[9px] font-bold border ${colorClass}">${status}</span>
|
|
</td>
|
|
`;
|
|
fullDataTableBody.appendChild(row);
|
|
});
|
|
|
|
fullPageInfo.innerText = `Page ${fullDataPage} of ${Math.ceil(activeSessionData.length / 50)}`;
|
|
fullPrevBtn.disabled = fullDataPage === 1;
|
|
fullNextBtn.disabled = fullDataPage * 50 >= activeSessionData.length;
|
|
}
|
|
|
|
function updateTable() {
|
|
if (tableBody.innerHTML === '') {
|
|
tableBody.innerHTML = '<tr><td colspan="4" class="p-2 text-center text-gray-400">Initializing...</td></tr>';
|
|
}
|
|
}
|
|
|
|
function toggleDetailVisibility() {
|
|
if (isAcquiring || currentMode === 'history') {
|
|
liveDataContainer.classList.remove('hidden');
|
|
historyDetailContainer.classList.add('hidden');
|
|
if (isAcquiring) {
|
|
activeSessionData = [...dataPoints];
|
|
renderFullSessionTable();
|
|
}
|
|
} else {
|
|
liveDataContainer.classList.add('hidden');
|
|
historyDetailContainer.classList.add('hidden');
|
|
detailPlaceholder.classList.remove('hidden');
|
|
}
|
|
}
|
|
// Initialization
|
|
initChart();
|
|
fetchHistory();
|
|
setInterval(updateData, fetchInterval);
|
|
updateData();
|
|
|
|
// Pagination Listeners
|
|
prevBtn.onclick = () => { if (currentPage > 1) { currentPage--; renderHistoryTable(); } };
|
|
nextBtn.onclick = () => { if (currentPage * 10 < history.length) { currentPage++; renderHistoryTable(); } };
|
|
fullPrevBtn.onclick = () => { if (fullDataPage > 1) { fullDataPage--; renderFullSessionTable(); } };
|
|
fullNextBtn.onclick = () => { if (fullDataPage * itemsPerPage < activeSessionData.length) { fullDataPage++; renderFullSessionTable(); } };
|