Files
controls-web/controls-classic/test/HMI/anom.php
2026-02-17 09:29:34 -06:00

1311 lines
46 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// filepath: v:\controls\test\HMI\anom.php
include "../../session.php";
include "../../userAccess.php";
// Database connection
$servername = "192.168.0.13";
$username = "opce";
$password = "opcelasuca";
$dbname = "archive";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
if (isset($_POST['action'])) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Database connection failed: ' . $e->getMessage()]);
exit;
}
die("Connection failed: " . $e->getMessage());
}
// Handle AJAX requests
if (isset($_POST['action'])) {
// Clear any previous output and set proper headers
while (ob_get_level()) {
ob_end_clean();
}
ob_start();
header('Content-Type: application/json');
try {
switch ($_POST['action']) {
case 'get_tags':
$stmt = $pdo->prepare("SELECT DISTINCT name FROM id_names WHERE name IS NOT NULL AND name != '' ORDER BY name");
$stmt->execute();
$tags = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo json_encode(['success' => true, 'tags' => $tags]);
break;
case 'get_recent_data':
$selectedTagsJson = $_POST['tags'] ?? '[]';
$selectedTags = is_string($selectedTagsJson) ? json_decode($selectedTagsJson, true) : $selectedTagsJson;
$hours = (int)($_POST['hours'] ?? 24);
if (empty($selectedTags) || !is_array($selectedTags)) {
throw new Exception('No tags selected');
}
$placeholders = str_repeat('?,', count($selectedTags) - 1) . '?';
$sql = "SELECT
h.TimeStamp,
n.name as tag_name,
h.Value
FROM historicaldata h
INNER JOIN id_names n ON h.ID = n.idnumber
WHERE n.name IN ($placeholders)
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
ORDER BY h.TimeStamp DESC, n.name ASC
LIMIT 10000";
$params = array_merge($selectedTags, [$hours]);
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'data' => $results,
'count' => count($results)
]);
break;
case 'analyze_anomalies':
$selectedTagsJson = $_POST['tags'] ?? '[]';
$selectedTags = is_string($selectedTagsJson) ? json_decode($selectedTagsJson, true) : $selectedTagsJson;
$sensitivity = (float)($_POST['sensitivity'] ?? 2.0);
$lookbackHours = (int)($_POST['lookback_hours'] ?? 168);
$analysisHours = (int)($_POST['analysis_hours'] ?? 24);
if (empty($selectedTags) || !is_array($selectedTags)) {
throw new Exception('No tags selected');
}
$anomalies = [];
foreach ($selectedTags as $tagName) {
// Get historical baseline data (excluding recent analysis period)
$baselineStmt = $pdo->prepare("
SELECT h.Value
FROM historicaldata h
INNER JOIN id_names n ON h.ID = n.idnumber
WHERE n.name = ?
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
AND h.TimeStamp < DATE_SUB(NOW(), INTERVAL ? HOUR)
AND h.Value IS NOT NULL
ORDER BY h.TimeStamp ASC
");
$baselineStmt->execute([$tagName, $lookbackHours, $analysisHours]);
$baselineData = $baselineStmt->fetchAll(PDO::FETCH_COLUMN);
// Get recent data for analysis
$recentStmt = $pdo->prepare("
SELECT h.Value, h.TimeStamp
FROM historicaldata h
INNER JOIN id_names n ON h.ID = n.idnumber
WHERE n.name = ?
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
AND h.Value IS NOT NULL
ORDER BY h.TimeStamp ASC
");
$recentStmt->execute([$tagName, $analysisHours]);
$recentResults = $recentStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($baselineData) < 10 || count($recentResults) < 5) {
continue; // Skip if insufficient data
}
// Convert baseline data to floats
$baselineValues = array_map('floatval', $baselineData);
// Calculate statistical baseline
$mean = array_sum($baselineValues) / count($baselineValues);
// Calculate standard deviation
$variance = 0;
foreach ($baselineValues as $value) {
$variance += pow($value - $mean, 2);
}
$stdDev = sqrt($variance / (count($baselineValues) - 1)); // Sample standard deviation
// Skip if standard deviation is too small (constant values)
if ($stdDev < 0.001) {
continue;
}
$upperThreshold = $mean + ($sensitivity * $stdDev);
$lowerThreshold = $mean - ($sensitivity * $stdDev);
// Check recent values for anomalies
foreach ($recentResults as $point) {
$value = (float)$point['Value'];
$timestamp = $point['TimeStamp'];
if ($value > $upperThreshold || $value < $lowerThreshold) {
$deviation = abs($value - $mean) / $stdDev;
// Determine severity based on deviation
$severity = 'medium';
if ($deviation > $sensitivity * 2) {
$severity = 'critical';
} elseif ($deviation > $sensitivity * 1.5) {
$severity = 'high';
}
$anomalies[] = [
'tag_name' => $tagName,
'timestamp' => $timestamp,
'value' => $value,
'expected_range' => [$lowerThreshold, $upperThreshold],
'baseline_mean' => $mean,
'baseline_stddev' => $stdDev,
'deviation_factor' => $deviation,
'severity' => $severity,
'message' => sprintf(
'Value %.2f is %.1f standard deviations from normal (%.2f ± %.2f)',
$value, $deviation, $mean, $stdDev
)
];
}
}
}
// Sort anomalies by severity and timestamp
usort($anomalies, function($a, $b) {
$severityOrder = ['low' => 1, 'medium' => 2, 'high' => 3, 'critical' => 4];
$severityDiff = ($severityOrder[$b['severity']] ?? 2) - ($severityOrder[$a['severity']] ?? 2);
if ($severityDiff !== 0) return $severityDiff;
return strtotime($b['timestamp']) - strtotime($a['timestamp']);
});
echo json_encode([
'success' => true,
'anomalies' => $anomalies,
'analysis_summary' => [
'total_anomalies' => count($anomalies),
'critical_count' => count(array_filter($anomalies, function($a) { return $a['severity'] === 'critical'; })),
'high_count' => count(array_filter($anomalies, function($a) { return $a['severity'] === 'high'; })),
'medium_count' => count(array_filter($anomalies, function($a) { return $a['severity'] === 'medium'; })),
'analysis_period' => $analysisHours . ' hours',
'baseline_period' => $lookbackHours . ' hours',
'sensitivity_level' => $sensitivity
]
]);
break;
case 'get_tag_statistics':
$tagName = $_POST['tag_name'] ?? '';
$hours = (int)($_POST['hours'] ?? 168);
if (empty($tagName)) {
throw new Exception('No tag specified');
}
$stmt = $pdo->prepare("
SELECT
COUNT(*) as data_points,
AVG(h.Value) as mean_value,
STDDEV(h.Value) as std_deviation,
MIN(h.Value) as min_value,
MAX(h.Value) as max_value,
VARIANCE(h.Value) as variance
FROM historicaldata h
INNER JOIN id_names n ON h.ID = n.idnumber
WHERE n.name = ?
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
AND h.Value IS NOT NULL
");
$stmt->execute([$tagName, $hours]);
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'statistics' => $stats,
'tag_name' => $tagName,
'analysis_period' => $hours . ' hours'
]);
break;
default:
throw new Exception('Invalid action specified');
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
} catch (Error $e) {
echo json_encode(['success' => false, 'error' => 'PHP Error: ' . $e->getMessage()]);
}
ob_end_flush();
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LASUCA Controls - Anomaly Detection Dashboard</title>
<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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1a1a2e;
color: #fff;
min-height: 100vh;
}
.dashboard-header {
background: linear-gradient(135deg, #0f3460 0%, #e94560 100%);
padding: 20px;
text-align: center;
box-shadow: 0 4px 20px rgba(233, 69, 96, 0.3);
}
.dashboard-header h1 {
color: #fff;
font-size: 2.2rem;
margin: 0;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
.dashboard-header .subtitle {
color: #f1c40f;
font-size: 1.1rem;
margin-top: 8px;
font-weight: 500;
}
.container {
max-width: 1600px;
margin: 0 auto;
padding: 20px;
}
.control-panel {
background: #16213e;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
margin-bottom: 20px;
border: 1px solid #0f3460;
}
.panel-header {
background: linear-gradient(135deg, #e94560 0%, #f39c12 100%);
color: white;
padding: 15px 25px;
font-weight: bold;
font-size: 1.1rem;
border-radius: 15px 15px 0 0;
}
.control-content {
padding: 20px;
}
.control-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.control-group {
background: #1e1e3f;
padding: 15px;
border-radius: 10px;
border: 1px solid #27ae60;
}
.control-group h3 {
color: #f39c12;
margin-bottom: 12px;
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
}
.form-row {
margin-bottom: 12px;
}
.form-row label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #ecf0f1;
font-size: 0.9rem;
}
.form-row input,
.form-row select {
width: 100%;
padding: 8px 12px;
border: 2px solid #34495e;
border-radius: 6px;
background: #2c3e50;
color: #ecf0f1;
font-size: 0.9rem;
transition: all 0.3s ease;
}
.form-row input:focus,
.form-row select:focus {
outline: none;
border-color: #f39c12;
box-shadow: 0 0 0 3px rgba(243, 156, 18, 0.1);
}
.tag-selector {
max-height: 150px;
overflow-y: auto;
border: 2px solid #34495e;
border-radius: 6px;
padding: 10px;
background: #2c3e50;
}
.tag-item {
display: flex;
align-items: center;
padding: 4px 0;
border-bottom: 1px solid #34495e;
}
.tag-item:last-child {
border-bottom: none;
}
.tag-item input[type="checkbox"] {
width: auto;
margin-right: 8px;
}
.tag-item label {
margin: 0;
cursor: pointer;
flex: 1;
font-size: 0.85rem;
}
.action-buttons {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
button {
background: linear-gradient(135deg, #e94560 0%, #f39c12 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(233, 69, 96, 0.4);
}
button:disabled {
background: #34495e;
cursor: not-allowed;
transform: none;
}
button.analyze-btn {
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
}
button.clear-btn {
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
}
.dashboard-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.anomaly-alerts {
background: #16213e;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
border: 1px solid #e74c3c;
}
.alerts-header {
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
color: white;
padding: 15px 20px;
font-weight: bold;
border-radius: 15px 15px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.alert-summary {
background: #16213e;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
border: 1px solid #f39c12;
}
.summary-header {
background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%);
color: white;
padding: 15px 20px;
font-weight: bold;
border-radius: 15px 15px 0 0;
}
.alerts-container {
max-height: 400px;
overflow-y: auto;
padding: 15px;
}
.alert-item {
background: #1e1e3f;
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
border-left: 4px solid transparent;
transition: all 0.3s ease;
}
.alert-item:hover {
transform: translateX(5px);
}
.alert-item.critical {
border-left-color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
}
.alert-item.high {
border-left-color: #f39c12;
background: rgba(243, 156, 18, 0.1);
}
.alert-item.medium {
border-left-color: #f1c40f;
background: rgba(241, 196, 15, 0.1);
}
.alert-tag {
font-weight: bold;
color: #3498db;
font-size: 0.9rem;
}
.alert-message {
color: #ecf0f1;
font-size: 0.85rem;
margin: 5px 0;
}
.alert-timestamp {
color: #95a5a6;
font-size: 0.8rem;
}
.alert-severity {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: bold;
text-transform: uppercase;
}
.severity-critical {
background: #e74c3c;
color: white;
}
.severity-high {
background: #f39c12;
color: white;
}
.severity-medium {
background: #f1c40f;
color: #2c3e50;
}
.summary-stats {
padding: 20px;
}
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #34495e;
}
.stat-row:last-child {
border-bottom: none;
}
.stat-label {
color: #bdc3c7;
font-size: 0.9rem;
}
.stat-value {
font-weight: bold;
font-size: 1.1rem;
}
.stat-critical { color: #e74c3c; }
.stat-high { color: #f39c12; }
.stat-medium { color: #f1c40f; }
.stat-normal { color: #27ae60; }
.charts-container {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
.chart-container {
background: #16213e;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
border: 1px solid #3498db;
}
.chart-header {
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
color: white;
padding: 15px 20px;
font-weight: bold;
border-radius: 15px 15px 0 0;
}
.chart-wrapper {
position: relative;
height: 400px;
padding: 20px;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(26, 26, 46, 0.95);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-spinner {
width: 60px;
height: 60px;
border: 4px solid #34495e;
border-top: 4px solid #e94560;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status-message {
padding: 12px 20px;
margin-bottom: 20px;
border-radius: 8px;
font-weight: 500;
}
.status-message.success {
background: rgba(39, 174, 96, 0.2);
border: 1px solid #27ae60;
color: #2ecc71;
}
.status-message.error {
background: rgba(231, 76, 60, 0.2);
border: 1px solid #e74c3c;
color: #e74c3c;
}
.status-message.info {
background: rgba(52, 152, 219, 0.2);
border: 1px solid #3498db;
color: #3498db;
}
.auto-refresh {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.auto-refresh input[type="checkbox"] {
width: auto;
}
.debug-info {
background: #34495e;
color: #ecf0f1;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
font-family: 'Courier New', monospace;
font-size: 0.8rem;
border: 2px solid #3498db;
display: none;
}
.debug-info h4 {
color: #3498db;
margin-bottom: 10px;
}
/* Responsive Design */
@media (max-width: 1200px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.control-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.chart-wrapper {
height: 300px;
}
}
</style>
</head>
<body>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner"></div>
</div>
<!-- Header -->
<div class="dashboard-header">
<h1>🚨 LASUCA Controls - Anomaly Detection Dashboard</h1>
<p class="subtitle">Real-Time Equipment Monitoring & Predictive Analytics</p>
</div>
<div class="container">
<!-- Control Panel -->
<div class="control-panel">
<div class="panel-header">
🔧 Analysis Configuration
</div>
<div class="control-content">
<div class="control-grid">
<!-- Tag Selection -->
<div class="control-group">
<h3>🏷️ Monitor Tags</h3>
<div class="form-row">
<label>Select Equipment Tags:</label>
<div class="tag-selector" id="tagSelector">
<div style="text-align: center; padding: 20px; color: #7f8c8d;">
Loading available tags...
</div>
</div>
</div>
<div class="form-row">
<button onclick="selectAllTags()">Select All</button>
<button onclick="clearAllTags()" class="clear-btn">Clear All</button>
</div>
</div>
<!-- Analysis Settings -->
<div class="control-group">
<h3>⚙️ Detection Settings</h3>
<div class="form-row">
<label for="sensitivity">Sensitivity Level:</label>
<select id="sensitivity">
<option value="1.5">High Sensitivity (1.5σ)</option>
<option value="2.0" selected>Normal Sensitivity (2.0σ)</option>
<option value="2.5">Low Sensitivity (2.5σ)</option>
<option value="3.0">Very Low Sensitivity (3.0σ)</option>
</select>
</div>
<div class="form-row">
<label for="lookbackHours">Baseline Period:</label>
<select id="lookbackHours">
<option value="24">Last 24 Hours</option>
<option value="72">Last 3 Days</option>
<option value="168" selected>Last Week</option>
<option value="336">Last 2 Weeks</option>
<option value="720">Last Month</option>
</select>
</div>
<div class="form-row">
<label for="analysisHours">Analysis Window:</label>
<select id="analysisHours">
<option value="1">Last Hour</option>
<option value="6">Last 6 Hours</option>
<option value="24" selected>Last 24 Hours</option>
<option value="48">Last 48 Hours</option>
</select>
</div>
</div>
<!-- Auto Refresh -->
<div class="control-group">
<h3>🔄 Real-Time Monitoring</h3>
<div class="auto-refresh">
<input type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()">
<label for="autoRefresh">Auto-refresh every 30 seconds</label>
</div>
<div class="form-row">
<label>Last Analysis:</label>
<div id="lastUpdate" style="color: #f39c12; font-weight: bold;">Never</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<button onclick="analyzeAnomalies()" class="analyze-btn" id="analyzeBtn">
🔍 Detect Anomalies
</button>
<button onclick="clearResults()" class="clear-btn">
🗑️ Clear Results
</button>
</div>
</div>
</div>
<!-- Status Messages -->
<div id="statusMessages"></div>
<!-- Debug Info -->
<div id="debugInfo" class="debug-info"></div>
<!-- Dashboard Grid -->
<div class="dashboard-grid" id="dashboardGrid" style="display: none;">
<!-- Anomaly Alerts -->
<div class="anomaly-alerts">
<div class="alerts-header">
<span>🚨 Active Anomalies</span>
<span id="alertCount" class="alert-count">0</span>
</div>
<div class="alerts-container" id="alertsContainer">
<div style="text-align: center; padding: 40px; color: #7f8c8d;">
No anomalies detected
</div>
</div>
</div>
<!-- Summary Statistics -->
<div class="alert-summary">
<div class="summary-header">
📊 Analysis Summary
</div>
<div class="summary-stats" id="summaryStats">
<div class="stat-row">
<span class="stat-label">Total Anomalies:</span>
<span class="stat-value stat-normal" id="totalAnomalies">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Critical:</span>
<span class="stat-value stat-critical" id="criticalCount">0</span>
</div>
<div class="stat-row">
<span class="stat-label">High Priority:</span>
<span class="stat-value stat-high" id="highCount">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Medium Priority:</span>
<span class="stat-value stat-medium" id="mediumCount">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Analysis Period:</span>
<span class="stat-value" id="analysisPeriod">-</span>
</div>
<div class="stat-row">
<span class="stat-label">Baseline Period:</span>
<span class="stat-value" id="baselinePeriod">-</span>
</div>
</div>
</div>
</div>
<!-- Charts Container -->
<div class="charts-container" id="chartsContainer" style="display: none;">
<div class="chart-container">
<div class="chart-header">
📈 Real-Time Data with Anomaly Detection
</div>
<div class="chart-wrapper">
<canvas id="anomalyChart"></canvas>
</div>
</div>
</div>
</div>
<script>
// Global variables
let availableTags = [];
let currentAnomalies = [];
let autoRefreshInterval = null;
let anomalyChart = null;
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
loadAvailableTags();
});
// Load available tags
async function loadAvailableTags() {
try {
showLoading(true);
const formData = new FormData();
formData.append('action', 'get_tags');
const response = await fetch(window.location.href, {
method: 'POST',
body: formData
});
const responseText = await response.text();
console.log('Raw response:', responseText);
let data;
try {
data = JSON.parse(responseText);
} catch (parseError) {
console.error('JSON parse error:', parseError);
showDebugInfo('Response Parse Error', responseText);
throw new Error('Invalid JSON response from server');
}
if (data.success) {
availableTags = data.tags;
renderTagSelector();
showStatus(`${availableTags.length} equipment tags loaded successfully`, 'success');
} else {
throw new Error(data.error || 'Failed to load tags');
}
} catch (error) {
console.error('Error loading tags:', error);
showStatus('Failed to load equipment tags: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
// Render tag selector
function renderTagSelector() {
const selector = document.getElementById('tagSelector');
if (availableTags.length === 0) {
selector.innerHTML = '<div style="text-align: center; padding: 20px; color: #e74c3c;">No equipment tags found</div>';
return;
}
let html = '';
availableTags.forEach((tag, index) => {
const safeId = `tag_${index}`;
html += `
<div class="tag-item">
<input type="checkbox" id="${safeId}" value="${tag}">
<label for="${safeId}">${tag}</label>
</div>
`;
});
selector.innerHTML = html;
}
// Select/clear all tags
function selectAllTags() {
const checkboxes = document.querySelectorAll('#tagSelector input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = true);
}
function clearAllTags() {
const checkboxes = document.querySelectorAll('#tagSelector input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
}
// Get selected tags
function getSelectedTags() {
const checkboxes = document.querySelectorAll('#tagSelector input[type="checkbox"]:checked');
return Array.from(checkboxes).map(cb => cb.value);
}
// Analyze anomalies
async function analyzeAnomalies() {
try {
const selectedTags = getSelectedTags();
if (selectedTags.length === 0) {
showStatus('Please select at least one equipment tag to monitor', 'error');
return;
}
showLoading(true);
document.getElementById('analyzeBtn').disabled = true;
const formData = new FormData();
formData.append('action', 'analyze_anomalies');
formData.append('tags', JSON.stringify(selectedTags));
formData.append('sensitivity', document.getElementById('sensitivity').value);
formData.append('lookback_hours', document.getElementById('lookbackHours').value);
formData.append('analysis_hours', document.getElementById('analysisHours').value);
console.log('Sending analysis request with tags:', selectedTags);
const response = await fetch(window.location.href, {
method: 'POST',
body: formData
});
const responseText = await response.text();
console.log('Analysis response:', responseText);
let result;
try {
result = JSON.parse(responseText);
} catch (parseError) {
console.error('JSON parse error:', parseError);
showDebugInfo('Analysis Response Parse Error', responseText);
throw new Error('Invalid JSON response from server');
}
if (result.success) {
currentAnomalies = result.anomalies;
displayAnomalies(result.anomalies, result.analysis_summary);
await loadChartData(selectedTags);
showDashboard();
updateLastUpdateTime();
const summary = result.analysis_summary;
let message = `Analysis complete! Found ${summary.total_anomalies} anomalies`;
if (summary.critical_count > 0) {
message += ` (${summary.critical_count} CRITICAL)`;
}
showStatus(message, summary.critical_count > 0 ? 'error' : 'success');
} else {
throw new Error(result.error || 'Failed to analyze anomalies');
}
} catch (error) {
console.error('Error analyzing anomalies:', error);
showStatus('Failed to analyze anomalies: ' + error.message, 'error');
} finally {
showLoading(false);
document.getElementById('analyzeBtn').disabled = false;
}
}
// Display anomalies
function displayAnomalies(anomalies, summary) {
const container = document.getElementById('alertsContainer');
const alertCount = document.getElementById('alertCount');
alertCount.textContent = anomalies.length;
if (anomalies.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 40px; color: #27ae60;">
<h3>✅ All Systems Normal</h3>
<p>No anomalies detected in the analyzed period</p>
</div>
`;
} else {
let html = '';
anomalies.forEach(anomaly => {
const severityClass = `severity-${anomaly.severity}`;
html += `
<div class="alert-item ${anomaly.severity}">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<div class="alert-tag">${anomaly.tag_name}</div>
<span class="alert-severity ${severityClass}">${anomaly.severity}</span>
</div>
<div class="alert-message">${anomaly.message}</div>
<div class="alert-timestamp">📅 ${new Date(anomaly.timestamp).toLocaleString()}</div>
</div>
`;
});
container.innerHTML = html;
}
// Update summary statistics
document.getElementById('totalAnomalies').textContent = summary.total_anomalies;
document.getElementById('criticalCount').textContent = summary.critical_count;
document.getElementById('highCount').textContent = summary.high_count;
document.getElementById('mediumCount').textContent = summary.medium_count;
document.getElementById('analysisPeriod').textContent = summary.analysis_period;
document.getElementById('baselinePeriod').textContent = summary.baseline_period;
}
// Load chart data
async function loadChartData(selectedTags) {
try {
const formData = new FormData();
formData.append('action', 'get_recent_data');
formData.append('tags', JSON.stringify(selectedTags));
formData.append('hours', document.getElementById('analysisHours').value);
const response = await fetch(window.location.href, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
renderAnomalyChart(result.data, selectedTags);
} else {
console.error('Failed to load chart data:', result.error);
}
} catch (error) {
console.error('Error loading chart data:', error);
}
}
// Render anomaly chart
function renderAnomalyChart(data, selectedTags) {
const ctx = document.getElementById('anomalyChart').getContext('2d');
if (anomalyChart) {
anomalyChart.destroy();
}
// Organize data by tag
const tagData = {};
selectedTags.forEach(tag => {
tagData[tag] = [];
});
data.forEach(point => {
if (tagData[point.tag_name]) {
tagData[point.tag_name].push({
x: point.TimeStamp,
y: parseFloat(point.Value)
});
}
});
// Create datasets
const datasets = [];
const colors = ['#3498db', '#e74c3c', '#27ae60', '#f39c12', '#9b59b6', '#1abc9c'];
selectedTags.forEach((tag, index) => {
const color = colors[index % colors.length];
datasets.push({
label: tag,
data: tagData[tag] || [],
borderColor: color,
backgroundColor: color + '20',
borderWidth: 2,
fill: false,
tension: 0.1,
pointRadius: 2,
pointHoverRadius: 5
});
});
// Add anomaly points
if (currentAnomalies.length > 0) {
const anomalyPoints = currentAnomalies.map(anomaly => ({
x: anomaly.timestamp,
y: anomaly.value
}));
datasets.push({
label: 'Anomalies',
data: anomalyPoints,
backgroundColor: '#e74c3c',
borderColor: '#e74c3c',
pointRadius: 6,
pointHoverRadius: 8,
showLine: false,
pointStyle: 'triangle'
});
}
anomalyChart = new Chart(ctx, {
type: 'line',
data: { datasets: datasets },
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'time',
time: {
displayFormats: {
hour: 'MMM DD HH:mm',
day: 'MMM DD'
}
},
title: {
display: true,
text: 'Time',
color: '#ecf0f1'
},
ticks: { color: '#bdc3c7' },
grid: { color: '#34495e' }
},
y: {
title: {
display: true,
text: 'Value',
color: '#ecf0f1'
},
ticks: { color: '#bdc3c7' },
grid: { color: '#34495e' }
}
},
plugins: {
legend: {
labels: { color: '#ecf0f1' }
},
tooltip: {
mode: 'nearest',
intersect: false,
backgroundColor: '#2c3e50',
titleColor: '#ecf0f1',
bodyColor: '#ecf0f1',
borderColor: '#34495e',
borderWidth: 1
}
}
}
});
}
// Auto-refresh functionality
function toggleAutoRefresh() {
const autoRefresh = document.getElementById('autoRefresh');
if (autoRefresh.checked) {
autoRefreshInterval = setInterval(() => {
if (getSelectedTags().length > 0) {
analyzeAnomalies();
}
}, 30000); // 30 seconds
} else {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
}
// Update last update time
function updateLastUpdateTime() {
document.getElementById('lastUpdate').textContent = new Date().toLocaleString();
}
// Show dashboard
function showDashboard() {
document.getElementById('dashboardGrid').style.display = 'grid';
document.getElementById('chartsContainer').style.display = 'block';
}
// Clear results
function clearResults() {
document.getElementById('dashboardGrid').style.display = 'none';
document.getElementById('chartsContainer').style.display = 'none';
if (anomalyChart) {
anomalyChart.destroy();
anomalyChart = null;
}
currentAnomalies = [];
clearStatus();
hideDebugInfo();
document.getElementById('lastUpdate').textContent = 'Never';
// Stop auto-refresh
document.getElementById('autoRefresh').checked = false;
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
// Debug information functions
function showDebugInfo(title, content) {
const debugDiv = document.getElementById('debugInfo');
debugDiv.innerHTML = `
<h4>${title}</h4>
<pre>${content}</pre>
`;
debugDiv.style.display = 'block';
}
function hideDebugInfo() {
document.getElementById('debugInfo').style.display = 'none';
}
// Utility functions
function showLoading(show) {
document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';
}
function showStatus(message, type) {
const container = document.getElementById('statusMessages');
const div = document.createElement('div');
div.className = `status-message ${type}`;
div.textContent = message;
container.innerHTML = '';
container.appendChild(div);
if (type === 'success') {
setTimeout(() => {
if (div.parentNode === container) {
container.removeChild(div);
}
}, 5000);
}
}
function clearStatus() {
document.getElementById('statusMessages').innerHTML = '';
}
</script>
</body>
</html>