Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- index.html +347 -42
index.html
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
@@ -116,8 +117,15 @@
|
|
| 116 |
}
|
| 117 |
|
| 118 |
@keyframes fadeIn {
|
| 119 |
-
from {
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
}
|
| 122 |
|
| 123 |
.loading-spinner {
|
|
@@ -130,7 +138,9 @@
|
|
| 130 |
}
|
| 131 |
|
| 132 |
@keyframes spin {
|
| 133 |
-
to {
|
|
|
|
|
|
|
| 134 |
}
|
| 135 |
|
| 136 |
.sidebar {
|
|
@@ -146,8 +156,45 @@
|
|
| 146 |
transform: translateX(0);
|
| 147 |
}
|
| 148 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
</style>
|
| 150 |
</head>
|
|
|
|
| 151 |
<body class="min-h-screen">
|
| 152 |
<!-- Header -->
|
| 153 |
<header class="bg-white shadow-sm border-b">
|
|
@@ -158,7 +205,8 @@
|
|
| 158 |
<i class="fas fa-bars text-xl"></i>
|
| 159 |
</button>
|
| 160 |
<div class="flex items-center space-x-3">
|
| 161 |
-
<div
|
|
|
|
| 162 |
<i class="fas fa-chart-line text-white text-sm"></i>
|
| 163 |
</div>
|
| 164 |
<span class="text-xl font-bold text-gray-800">Zabbix Event Analyzer</span>
|
|
@@ -170,7 +218,8 @@
|
|
| 170 |
<i class="fas fa-robot text-blue-500"></i>
|
| 171 |
<span>Powered by Ollama</span>
|
| 172 |
</div>
|
| 173 |
-
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank"
|
|
|
|
| 174 |
<i class="fas fa-code"></i>
|
| 175 |
<span>Built with anycoder</span>
|
| 176 |
</a>
|
|
@@ -248,13 +297,66 @@
|
|
| 248 |
<!-- Main Content -->
|
| 249 |
<main class="flex-1 overflow-y-auto">
|
| 250 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
<!-- Stats Cards -->
|
| 252 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
| 253 |
<div class="card p-6">
|
| 254 |
<div class="flex items-center justify-between">
|
| 255 |
<div>
|
| 256 |
<p class="text-sm font-medium text-gray-500">Total Events</p>
|
| 257 |
-
<p class="text-3xl font-bold text-gray-800 mt-1">1,248</p>
|
| 258 |
</div>
|
| 259 |
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
|
| 260 |
<i class="fas fa-bell text-blue-600 text-xl"></i>
|
|
@@ -270,7 +372,7 @@
|
|
| 270 |
<div class="flex items-center justify-between">
|
| 271 |
<div>
|
| 272 |
<p class="text-sm font-medium text-gray-500">Critical Events</p>
|
| 273 |
-
<p class="text-3xl font-bold text-gray-800 mt-1">42</p>
|
| 274 |
</div>
|
| 275 |
<div class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center">
|
| 276 |
<i class="fas fa-exclamation-triangle text-red-600 text-xl"></i>
|
|
@@ -286,7 +388,7 @@
|
|
| 286 |
<div class="flex items-center justify-between">
|
| 287 |
<div>
|
| 288 |
<p class="text-sm font-medium text-gray-500">Analyzed Events</p>
|
| 289 |
-
<p class="text-3xl font-bold text-gray-800 mt-1">876</p>
|
| 290 |
</div>
|
| 291 |
<div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
|
| 292 |
<i class="fas fa-brain text-green-600 text-xl"></i>
|
|
@@ -302,7 +404,7 @@
|
|
| 302 |
<div class="flex items-center justify-between">
|
| 303 |
<div>
|
| 304 |
<p class="text-sm font-medium text-gray-500">Active Triggers</p>
|
| 305 |
-
<p class="text-3xl font-bold text-gray-800 mt-1">18</p>
|
| 306 |
</div>
|
| 307 |
<div class="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
|
| 308 |
<i class="fas fa-bolt text-purple-600 text-xl"></i>
|
|
@@ -323,10 +425,16 @@
|
|
| 323 |
<div class="p-6 border-b">
|
| 324 |
<div class="flex items-center justify-between">
|
| 325 |
<h2 class="text-lg font-semibold text-gray-800">Recent Events</h2>
|
| 326 |
-
<
|
| 327 |
-
<
|
| 328 |
-
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
</div>
|
| 331 |
</div>
|
| 332 |
|
|
@@ -346,8 +454,10 @@
|
|
| 346 |
</span>
|
| 347 |
<span class="text-sm text-gray-500">2023-11-15 14:32:17</span>
|
| 348 |
</div>
|
| 349 |
-
<h3 class="font-semibold text-gray-800 mt-2">High CPU load on
|
| 350 |
-
|
|
|
|
|
|
|
| 351 |
<div class="mt-3 flex flex-wrap gap-2">
|
| 352 |
<span class="px-2 py-1 bg-gray-100 text-xs rounded-full">web-server-01</span>
|
| 353 |
<span class="px-2 py-1 bg-gray-100 text-xs rounded-full">CPU</span>
|
|
@@ -382,11 +492,13 @@
|
|
| 382 |
|
| 383 |
<div class="p-6">
|
| 384 |
<div id="analysis-placeholder" class="text-center py-12">
|
| 385 |
-
<div
|
|
|
|
| 386 |
<i class="fas fa-brain text-blue-600 text-2xl"></i>
|
| 387 |
</div>
|
| 388 |
<h3 class="text-gray-800 font-medium">Select an event to analyze</h3>
|
| 389 |
-
<p class="text-gray-500 text-sm mt-1">Click the "Analyze" button on any event to get
|
|
|
|
| 390 |
</div>
|
| 391 |
|
| 392 |
<div id="analysis-result" class="hidden">
|
|
@@ -398,7 +510,9 @@
|
|
| 398 |
<div class="space-y-4">
|
| 399 |
<div>
|
| 400 |
<p class="text-sm font-medium text-gray-500 mb-1">Root Cause</p>
|
| 401 |
-
<p class="text-gray-800" id="analysis-root-cause">The high CPU load is
|
|
|
|
|
|
|
| 402 |
</div>
|
| 403 |
|
| 404 |
<div>
|
|
@@ -432,8 +546,10 @@
|
|
| 432 |
<div class="pt-4 border-t">
|
| 433 |
<p class="text-sm font-medium text-gray-500 mb-2">Additional Context</p>
|
| 434 |
<div class="space-y-2 text-sm text-gray-600" id="analysis-context">
|
| 435 |
-
<p>Similar events occurred 3 times in the last week during peak hours.
|
| 436 |
-
<
|
|
|
|
|
|
|
| 437 |
<p>No recent deployment changes that would explain this behavior.</p>
|
| 438 |
</div>
|
| 439 |
</div>
|
|
@@ -459,9 +575,24 @@
|
|
| 459 |
</div>
|
| 460 |
</div>
|
| 461 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
<script>
|
| 463 |
// Mock data for events
|
| 464 |
-
|
| 465 |
{
|
| 466 |
id: 1,
|
| 467 |
timestamp: "2023-11-15 14:32:17",
|
|
@@ -545,23 +676,64 @@
|
|
| 545 |
const analysisPlaceholder = document.getElementById('analysis-placeholder');
|
| 546 |
const analysisResult = document.getElementById('analysis-result');
|
| 547 |
const loadingOverlay = document.getElementById('loading-overlay');
|
|
|
|
| 548 |
const menuBtn = document.getElementById('menu-btn');
|
| 549 |
const sidebar = document.getElementById('sidebar');
|
| 550 |
const refreshBtn = document.getElementById('refresh-events');
|
| 551 |
const loadMoreBtn = document.getElementById('load-more');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 552 |
|
| 553 |
// Initialize the app
|
| 554 |
function init() {
|
| 555 |
renderEvents();
|
| 556 |
setupEventListeners();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
}
|
| 558 |
|
| 559 |
// Render events to the timeline
|
| 560 |
function renderEvents() {
|
| 561 |
eventsContainer.innerHTML = '';
|
| 562 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
// For demo purposes, we'll show all events
|
| 564 |
-
|
| 565 |
const eventElement = createEventElement(event);
|
| 566 |
eventsContainer.appendChild(eventElement);
|
| 567 |
});
|
|
@@ -623,21 +795,166 @@
|
|
| 623 |
|
| 624 |
// Refresh events button
|
| 625 |
refreshBtn.addEventListener('click', () => {
|
| 626 |
-
// In a real app, this would fetch new data
|
| 627 |
showNotification("Events refreshed", "success");
|
| 628 |
});
|
| 629 |
|
| 630 |
// Load more events button
|
| 631 |
loadMoreBtn.addEventListener('click', () => {
|
| 632 |
-
// In a real app, this would load more events
|
| 633 |
showNotification("No more events to load", "info");
|
| 634 |
});
|
| 635 |
|
| 636 |
// Apply filters button
|
| 637 |
document.getElementById('apply-filters').addEventListener('click', () => {
|
| 638 |
-
// In a real app, this would apply the filters
|
| 639 |
showNotification("Filters applied", "success");
|
| 640 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
}
|
| 642 |
|
| 643 |
// Simulate analyzing an event with Ollama
|
|
@@ -648,7 +965,7 @@
|
|
| 648 |
// Hide placeholder and show analysis after a delay (simulating API call)
|
| 649 |
setTimeout(() => {
|
| 650 |
// Find the event
|
| 651 |
-
const event =
|
| 652 |
|
| 653 |
// Update the analysis result with mock data
|
| 654 |
document.getElementById('analysis-root-cause').textContent = mockAnalysis.rootCause;
|
|
@@ -686,6 +1003,9 @@
|
|
| 686 |
analyzeBtn.classList.add('text-green-600', 'cursor-default');
|
| 687 |
analyzeBtn.removeAttribute('data-event-id');
|
| 688 |
|
|
|
|
|
|
|
|
|
|
| 689 |
showNotification("Event analyzed successfully", "success");
|
| 690 |
}, 1500);
|
| 691 |
}
|
|
@@ -697,23 +1017,8 @@
|
|
| 697 |
|
| 698 |
// Show notification
|
| 699 |
function showNotification(message, type) {
|
| 700 |
-
// In a real app, you might use a proper notification system
|
| 701 |
const notification = document.createElement('div');
|
| 702 |
notification.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg text-white ${
|
| 703 |
type === 'success' ? 'bg-green-600' :
|
| 704 |
type === 'error' ? 'bg-red-600' :
|
| 705 |
-
type === 'warning' ? 'bg-yellow-600' : 'bg-blue-600'
|
| 706 |
-
}`;
|
| 707 |
-
notification.textContent = message;
|
| 708 |
-
document.body.appendChild(notification);
|
| 709 |
-
|
| 710 |
-
setTimeout(() => {
|
| 711 |
-
notification.remove();
|
| 712 |
-
}, 3000);
|
| 713 |
-
}
|
| 714 |
-
|
| 715 |
-
// Initialize the app when DOM is loaded
|
| 716 |
-
document.addEventListener('DOMContentLoaded', init);
|
| 717 |
-
</script>
|
| 718 |
-
</body>
|
| 719 |
-
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
+
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
| 117 |
}
|
| 118 |
|
| 119 |
@keyframes fadeIn {
|
| 120 |
+
from {
|
| 121 |
+
opacity: 0;
|
| 122 |
+
transform: translateY(10px);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
to {
|
| 126 |
+
opacity: 1;
|
| 127 |
+
transform: translateY(0);
|
| 128 |
+
}
|
| 129 |
}
|
| 130 |
|
| 131 |
.loading-spinner {
|
|
|
|
| 138 |
}
|
| 139 |
|
| 140 |
@keyframes spin {
|
| 141 |
+
to {
|
| 142 |
+
transform: rotate(360deg);
|
| 143 |
+
}
|
| 144 |
}
|
| 145 |
|
| 146 |
.sidebar {
|
|
|
|
| 156 |
transform: translateX(0);
|
| 157 |
}
|
| 158 |
}
|
| 159 |
+
|
| 160 |
+
/* CSV Upload Styles */
|
| 161 |
+
.file-upload-area {
|
| 162 |
+
border: 2px dashed #d1d5db;
|
| 163 |
+
border-radius: 8px;
|
| 164 |
+
transition: all 0.3s ease;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.file-upload-area:hover {
|
| 168 |
+
border-color: var(--primary);
|
| 169 |
+
background-color: #f0f9ff;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.file-upload-area.active {
|
| 173 |
+
border-color: var(--secondary);
|
| 174 |
+
background-color: #d1fae5;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.file-list-item {
|
| 178 |
+
animation: slideIn 0.3s ease-out;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
@keyframes slideIn {
|
| 182 |
+
from {
|
| 183 |
+
opacity: 0;
|
| 184 |
+
transform: translateX(-20px);
|
| 185 |
+
}
|
| 186 |
+
to {
|
| 187 |
+
opacity: 1;
|
| 188 |
+
transform: translateX(0);
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.progress-bar {
|
| 193 |
+
transition: width 0.3s ease;
|
| 194 |
+
}
|
| 195 |
</style>
|
| 196 |
</head>
|
| 197 |
+
|
| 198 |
<body class="min-h-screen">
|
| 199 |
<!-- Header -->
|
| 200 |
<header class="bg-white shadow-sm border-b">
|
|
|
|
| 205 |
<i class="fas fa-bars text-xl"></i>
|
| 206 |
</button>
|
| 207 |
<div class="flex items-center space-x-3">
|
| 208 |
+
<div
|
| 209 |
+
class="w-8 h-8 bg-gradient-to-r from-blue-500 to-green-500 rounded-md flex items-center justify-center">
|
| 210 |
<i class="fas fa-chart-line text-white text-sm"></i>
|
| 211 |
</div>
|
| 212 |
<span class="text-xl font-bold text-gray-800">Zabbix Event Analyzer</span>
|
|
|
|
| 218 |
<i class="fas fa-robot text-blue-500"></i>
|
| 219 |
<span>Powered by Ollama</span>
|
| 220 |
</div>
|
| 221 |
+
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank"
|
| 222 |
+
class="text-sm text-gray-500 hover:text-gray-700 flex items-center space-x-1">
|
| 223 |
<i class="fas fa-code"></i>
|
| 224 |
<span>Built with anycoder</span>
|
| 225 |
</a>
|
|
|
|
| 297 |
<!-- Main Content -->
|
| 298 |
<main class="flex-1 overflow-y-auto">
|
| 299 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
| 300 |
+
<!-- CSV Upload Section -->
|
| 301 |
+
<div class="card mb-8">
|
| 302 |
+
<div class="p-6">
|
| 303 |
+
<div class="flex items-center justify-between mb-4">
|
| 304 |
+
<h2 class="text-lg font-semibold text-gray-800">Import Zabbix Data</h2>
|
| 305 |
+
<button id="upload-csv-btn" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors flex items-center space-x-2">
|
| 306 |
+
<i class="fas fa-file-upload"></i>
|
| 307 |
+
<span>Upload CSV</span>
|
| 308 |
+
</button>
|
| 309 |
+
</div>
|
| 310 |
+
|
| 311 |
+
<div id="csv-upload-area" class="file-upload-area p-8 text-center cursor-pointer">
|
| 312 |
+
<input type="file" id="csv-file-input" accept=".csv" class="hidden">
|
| 313 |
+
<div class="mb-4">
|
| 314 |
+
<i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-2"></i>
|
| 315 |
+
<p class="text-gray-600">Drag & drop CSV file here or click to browse</p>
|
| 316 |
+
<p class="text-sm text-gray-500 mt-1">Supports Zabbix event export format</p>
|
| 317 |
+
</div>
|
| 318 |
+
</div>
|
| 319 |
+
|
| 320 |
+
<div id="file-preview" class="hidden mt-6">
|
| 321 |
+
<h3 class="text-sm font-medium text-gray-700 mb-3">Selected File:</h3>
|
| 322 |
+
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-md">
|
| 323 |
+
<div class="flex items-center">
|
| 324 |
+
<i class="fas fa-file-csv text-blue-600 text-xl mr-3"></i>
|
| 325 |
+
<div>
|
| 326 |
+
<p id="file-name" class="font-medium text-gray-800"></p>
|
| 327 |
+
<p id="file-size" class="text-sm text-gray-500"></p>
|
| 328 |
+
</div>
|
| 329 |
+
</div>
|
| 330 |
+
<div class="flex items-center space-x-3">
|
| 331 |
+
<button id="parse-csv-btn" class="text-blue-600 hover:text-blue-800">
|
| 332 |
+
<i class="fas fa-play mr-1"></i> Parse
|
| 333 |
+
</button>
|
| 334 |
+
<button id="cancel-upload-btn" class="text-gray-500 hover:text-gray-700">
|
| 335 |
+
<i class="fas fa-times"></i>
|
| 336 |
+
</button>
|
| 337 |
+
</div>
|
| 338 |
+
</div>
|
| 339 |
+
|
| 340 |
+
<div id="parse-progress" class="mt-4 hidden">
|
| 341 |
+
<div class="flex items-center justify-between mb-1">
|
| 342 |
+
<span class="text-sm font-medium text-gray-700">Parsing CSV...</span>
|
| 343 |
+
<span id="parse-percentage" class="text-sm font-medium text-gray-700">0%</span>
|
| 344 |
+
</div>
|
| 345 |
+
<div class="w-full bg-gray-200 rounded-full h-2">
|
| 346 |
+
<div id="progress-bar" class="progress-bar bg-blue-600 h-2 rounded-full" style="width: 0%"></div>
|
| 347 |
+
</div>
|
| 348 |
+
</div>
|
| 349 |
+
</div>
|
| 350 |
+
</div>
|
| 351 |
+
</div>
|
| 352 |
+
|
| 353 |
<!-- Stats Cards -->
|
| 354 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
| 355 |
<div class="card p-6">
|
| 356 |
<div class="flex items-center justify-between">
|
| 357 |
<div>
|
| 358 |
<p class="text-sm font-medium text-gray-500">Total Events</p>
|
| 359 |
+
<p class="text-3xl font-bold text-gray-800 mt-1" id="total-events">1,248</p>
|
| 360 |
</div>
|
| 361 |
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
|
| 362 |
<i class="fas fa-bell text-blue-600 text-xl"></i>
|
|
|
|
| 372 |
<div class="flex items-center justify-between">
|
| 373 |
<div>
|
| 374 |
<p class="text-sm font-medium text-gray-500">Critical Events</p>
|
| 375 |
+
<p class="text-3xl font-bold text-gray-800 mt-1" id="critical-events">42</p>
|
| 376 |
</div>
|
| 377 |
<div class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center">
|
| 378 |
<i class="fas fa-exclamation-triangle text-red-600 text-xl"></i>
|
|
|
|
| 388 |
<div class="flex items-center justify-between">
|
| 389 |
<div>
|
| 390 |
<p class="text-sm font-medium text-gray-500">Analyzed Events</p>
|
| 391 |
+
<p class="text-3xl font-bold text-gray-800 mt-1" id="analyzed-events">876</p>
|
| 392 |
</div>
|
| 393 |
<div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
|
| 394 |
<i class="fas fa-brain text-green-600 text-xl"></i>
|
|
|
|
| 404 |
<div class="flex items-center justify-between">
|
| 405 |
<div>
|
| 406 |
<p class="text-sm font-medium text-gray-500">Active Triggers</p>
|
| 407 |
+
<p class="text-3xl font-bold text-gray-800 mt-1" id="active-triggers">18</p>
|
| 408 |
</div>
|
| 409 |
<div class="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
|
| 410 |
<i class="fas fa-bolt text-purple-600 text-xl"></i>
|
|
|
|
| 425 |
<div class="p-6 border-b">
|
| 426 |
<div class="flex items-center justify-between">
|
| 427 |
<h2 class="text-lg font-semibold text-gray-800">Recent Events</h2>
|
| 428 |
+
<div class="flex items-center space-x-3">
|
| 429 |
+
<button id="refresh-events" class="text-blue-600 hover:text-blue-800 flex items-center space-x-1">
|
| 430 |
+
<i class="fas fa-sync-alt"></i>
|
| 431 |
+
<span>Refresh</span>
|
| 432 |
+
</button>
|
| 433 |
+
<button id="export-events" class="text-green-600 hover:text-green-800 flex items-center space-x-1">
|
| 434 |
+
<i class="fas fa-file-export"></i>
|
| 435 |
+
<span>Export</span>
|
| 436 |
+
</button>
|
| 437 |
+
</div>
|
| 438 |
</div>
|
| 439 |
</div>
|
| 440 |
|
|
|
|
| 454 |
</span>
|
| 455 |
<span class="text-sm text-gray-500">2023-11-15 14:32:17</span>
|
| 456 |
</div>
|
| 457 |
+
<h3 class="font-semibold text-gray-800 mt-2">High CPU load on
|
| 458 |
+
web-server-01</h3>
|
| 459 |
+
<p class="text-sm text-gray-600 mt-1">CPU load has been over 90%
|
| 460 |
+
for the last 5 minutes</p>
|
| 461 |
<div class="mt-3 flex flex-wrap gap-2">
|
| 462 |
<span class="px-2 py-1 bg-gray-100 text-xs rounded-full">web-server-01</span>
|
| 463 |
<span class="px-2 py-1 bg-gray-100 text-xs rounded-full">CPU</span>
|
|
|
|
| 492 |
|
| 493 |
<div class="p-6">
|
| 494 |
<div id="analysis-placeholder" class="text-center py-12">
|
| 495 |
+
<div
|
| 496 |
+
class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
| 497 |
<i class="fas fa-brain text-blue-600 text-2xl"></i>
|
| 498 |
</div>
|
| 499 |
<h3 class="text-gray-800 font-medium">Select an event to analyze</h3>
|
| 500 |
+
<p class="text-gray-500 text-sm mt-1">Click the "Analyze" button on any event to get
|
| 501 |
+
AI-powered insights</p>
|
| 502 |
</div>
|
| 503 |
|
| 504 |
<div id="analysis-result" class="hidden">
|
|
|
|
| 510 |
<div class="space-y-4">
|
| 511 |
<div>
|
| 512 |
<p class="text-sm font-medium text-gray-500 mb-1">Root Cause</p>
|
| 513 |
+
<p class="text-gray-800" id="analysis-root-cause">The high CPU load is
|
| 514 |
+
likely caused by a sudden spike in web traffic combined with inefficient
|
| 515 |
+
database queries in the application.</p>
|
| 516 |
</div>
|
| 517 |
|
| 518 |
<div>
|
|
|
|
| 546 |
<div class="pt-4 border-t">
|
| 547 |
<p class="text-sm font-medium text-gray-500 mb-2">Additional Context</p>
|
| 548 |
<div class="space-y-2 text-sm text-gray-600" id="analysis-context">
|
| 549 |
+
<p>Similar events occurred 3 times in the last week during peak hours.
|
| 550 |
+
</p>
|
| 551 |
+
<p>The database server shows correlated high load during these events.
|
| 552 |
+
</p>
|
| 553 |
<p>No recent deployment changes that would explain this behavior.</p>
|
| 554 |
</div>
|
| 555 |
</div>
|
|
|
|
| 575 |
</div>
|
| 576 |
</div>
|
| 577 |
|
| 578 |
+
<!-- CSV Processing Overlay -->
|
| 579 |
+
<div id="csv-processing-overlay" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
| 580 |
+
<div class="bg-white rounded-lg p-6 w-96 text-center">
|
| 581 |
+
<div class="flex justify-center mb-4">
|
| 582 |
+
<div class="w-16 h-16 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
|
| 583 |
+
</div>
|
| 584 |
+
<h3 class="text-lg font-semibold text-gray-800 mb-2" id="csv-processing-title">Processing CSV File</h3>
|
| 585 |
+
<p class="text-gray-600 mb-4" id="csv-processing-message">Parsing Zabbix events from the uploaded file...</p>
|
| 586 |
+
<div class="w-full bg-gray-200 rounded-full h-3 mb-2">
|
| 587 |
+
<div id="csv-processing-progress" class="bg-blue-600 h-3 rounded-full" style="width: 0%"></div>
|
| 588 |
+
</div>
|
| 589 |
+
<p class="text-sm text-gray-500" id="csv-processing-percentage">0% Complete</p>
|
| 590 |
+
</div>
|
| 591 |
+
</div>
|
| 592 |
+
|
| 593 |
<script>
|
| 594 |
// Mock data for events
|
| 595 |
+
let eventsData = [
|
| 596 |
{
|
| 597 |
id: 1,
|
| 598 |
timestamp: "2023-11-15 14:32:17",
|
|
|
|
| 676 |
const analysisPlaceholder = document.getElementById('analysis-placeholder');
|
| 677 |
const analysisResult = document.getElementById('analysis-result');
|
| 678 |
const loadingOverlay = document.getElementById('loading-overlay');
|
| 679 |
+
const csvProcessingOverlay = document.getElementById('csv-processing-overlay');
|
| 680 |
const menuBtn = document.getElementById('menu-btn');
|
| 681 |
const sidebar = document.getElementById('sidebar');
|
| 682 |
const refreshBtn = document.getElementById('refresh-events');
|
| 683 |
const loadMoreBtn = document.getElementById('load-more');
|
| 684 |
+
const uploadCsvBtn = document.getElementById('upload-csv-btn');
|
| 685 |
+
const csvUploadArea = document.getElementById('csv-upload-area');
|
| 686 |
+
const csvFileInput = document.getElementById('csv-file-input');
|
| 687 |
+
const filePreview = document.getElementById('file-preview');
|
| 688 |
+
const fileName = document.getElementById('file-name');
|
| 689 |
+
const fileSize = document.getElementById('file-size');
|
| 690 |
+
const parseCsvBtn = document.getElementById('parse-csv-btn');
|
| 691 |
+
const cancelUploadBtn = document.getElementById('cancel-upload-btn');
|
| 692 |
+
const parseProgress = document.getElementById('parse-progress');
|
| 693 |
+
const progressBar = document.getElementById('progress-bar');
|
| 694 |
+
const parsePercentage = document.getElementById('parse-percentage');
|
| 695 |
+
const csvProcessingProgress = document.getElementById('csv-processing-progress');
|
| 696 |
+
const csvProcessingPercentage = document.getElementById('csv-processing-percentage');
|
| 697 |
+
const csvProcessingTitle = document.getElementById('csv-processing-title');
|
| 698 |
+
const csvProcessingMessage = document.getElementById('csv-processing-message');
|
| 699 |
+
|
| 700 |
+
// Stats elements
|
| 701 |
+
const totalEventsEl = document.getElementById('total-events');
|
| 702 |
+
const criticalEventsEl = document.getElementById('critical-events');
|
| 703 |
+
const analyzedEventsEl = document.getElementById('analyzed-events');
|
| 704 |
+
const activeTriggersEl = document.getElementById('active-triggers');
|
| 705 |
|
| 706 |
// Initialize the app
|
| 707 |
function init() {
|
| 708 |
renderEvents();
|
| 709 |
setupEventListeners();
|
| 710 |
+
updateStats();
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
// Update statistics
|
| 714 |
+
function updateStats() {
|
| 715 |
+
const totalEvents = eventsData.length;
|
| 716 |
+
const criticalEvents = eventsData.filter(e => e.severity === 'disaster' || e.severity === 'high').length;
|
| 717 |
+
const analyzedEvents = eventsData.filter(e => e.analyzed).length;
|
| 718 |
+
const activeTriggers = Math.floor(totalEvents * 0.1); // Mock calculation
|
| 719 |
+
|
| 720 |
+
totalEventsEl.textContent = totalEvents.toLocaleString();
|
| 721 |
+
criticalEventsEl.textContent = criticalEvents.toLocaleString();
|
| 722 |
+
analyzedEventsEl.textContent = analyzedEvents.toLocaleString();
|
| 723 |
+
activeTriggersEl.textContent = activeTriggers.toLocaleString();
|
| 724 |
}
|
| 725 |
|
| 726 |
// Render events to the timeline
|
| 727 |
function renderEvents() {
|
| 728 |
eventsContainer.innerHTML = '';
|
| 729 |
|
| 730 |
+
// Sort events by timestamp (newest first)
|
| 731 |
+
const sortedEvents = [...eventsData].sort((a, b) => {
|
| 732 |
+
return new Date(b.timestamp) - new Date(a.timestamp);
|
| 733 |
+
});
|
| 734 |
+
|
| 735 |
// For demo purposes, we'll show all events
|
| 736 |
+
sortedEvents.forEach(event => {
|
| 737 |
const eventElement = createEventElement(event);
|
| 738 |
eventsContainer.appendChild(eventElement);
|
| 739 |
});
|
|
|
|
| 795 |
|
| 796 |
// Refresh events button
|
| 797 |
refreshBtn.addEventListener('click', () => {
|
|
|
|
| 798 |
showNotification("Events refreshed", "success");
|
| 799 |
});
|
| 800 |
|
| 801 |
// Load more events button
|
| 802 |
loadMoreBtn.addEventListener('click', () => {
|
|
|
|
| 803 |
showNotification("No more events to load", "info");
|
| 804 |
});
|
| 805 |
|
| 806 |
// Apply filters button
|
| 807 |
document.getElementById('apply-filters').addEventListener('click', () => {
|
|
|
|
| 808 |
showNotification("Filters applied", "success");
|
| 809 |
});
|
| 810 |
+
|
| 811 |
+
// CSV Upload functionality
|
| 812 |
+
uploadCsvBtn.addEventListener('click', () => {
|
| 813 |
+
csvFileInput.click();
|
| 814 |
+
});
|
| 815 |
+
|
| 816 |
+
csvUploadArea.addEventListener('click', () => {
|
| 817 |
+
csvFileInput.click();
|
| 818 |
+
});
|
| 819 |
+
|
| 820 |
+
// Drag and drop functionality
|
| 821 |
+
csvUploadArea.addEventListener('dragover', (e) => {
|
| 822 |
+
e.preventDefault();
|
| 823 |
+
csvUploadArea.classList.add('active');
|
| 824 |
+
});
|
| 825 |
+
|
| 826 |
+
csvUploadArea.addEventListener('dragleave', () => {
|
| 827 |
+
csvUploadArea.classList.remove('active');
|
| 828 |
+
});
|
| 829 |
+
|
| 830 |
+
csvUploadArea.addEventListener('drop', (e) => {
|
| 831 |
+
e.preventDefault();
|
| 832 |
+
csvUploadArea.classList.remove('active');
|
| 833 |
+
|
| 834 |
+
if (e.dataTransfer.files.length) {
|
| 835 |
+
handleFileSelect(e.dataTransfer.files[0]);
|
| 836 |
+
}
|
| 837 |
+
});
|
| 838 |
+
|
| 839 |
+
csvFileInput.addEventListener('change', (e) => {
|
| 840 |
+
if (e.target.files.length) {
|
| 841 |
+
handleFileSelect(e.target.files[0]);
|
| 842 |
+
}
|
| 843 |
+
});
|
| 844 |
+
|
| 845 |
+
parseCsvBtn.addEventListener('click', () => {
|
| 846 |
+
parseCsvFile();
|
| 847 |
+
});
|
| 848 |
+
|
| 849 |
+
cancelUploadBtn.addEventListener('click', () => {
|
| 850 |
+
resetFileUpload();
|
| 851 |
+
});
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
+
// Handle file selection
|
| 855 |
+
function handleFileSelect(file) {
|
| 856 |
+
if (file.type !== 'text/csv' && !file.name.endsWith('.csv')) {
|
| 857 |
+
showNotification("Please upload a valid CSV file", "error");
|
| 858 |
+
return;
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
filePreview.classList.remove('hidden');
|
| 862 |
+
fileName.textContent = file.name;
|
| 863 |
+
fileSize.textContent = formatFileSize(file.size);
|
| 864 |
+
}
|
| 865 |
+
|
| 866 |
+
// Format file size
|
| 867 |
+
function formatFileSize(bytes) {
|
| 868 |
+
if (bytes === 0) return '0 Bytes';
|
| 869 |
+
|
| 870 |
+
const k = 1024;
|
| 871 |
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
| 872 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 873 |
+
|
| 874 |
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
// Parse CSV file
|
| 878 |
+
function parseCsvFile() {
|
| 879 |
+
const file = csvFileInput.files[0];
|
| 880 |
+
if (!file) return;
|
| 881 |
+
|
| 882 |
+
// Show processing overlay
|
| 883 |
+
csvProcessingOverlay.classList.remove('hidden');
|
| 884 |
+
csvProcessingTitle.textContent = "Processing CSV File";
|
| 885 |
+
csvProcessingMessage.textContent = "Parsing Zabbix events from the uploaded file...";
|
| 886 |
+
|
| 887 |
+
// Simulate parsing progress
|
| 888 |
+
let progress = 0;
|
| 889 |
+
const interval = setInterval(() => {
|
| 890 |
+
progress += 5;
|
| 891 |
+
csvProcessingProgress.style.width = `${progress}%`;
|
| 892 |
+
csvProcessingPercentage.textContent = `${progress}% Complete`;
|
| 893 |
+
|
| 894 |
+
if (progress >= 100) {
|
| 895 |
+
clearInterval(interval);
|
| 896 |
+
// Simulate successful parsing
|
| 897 |
+
setTimeout(() => {
|
| 898 |
+
processMockCsvData();
|
| 899 |
+
csvProcessingOverlay.classList.add('hidden');
|
| 900 |
+
resetFileUpload();
|
| 901 |
+
showNotification("CSV file processed successfully! 12 new events imported.", "success");
|
| 902 |
+
}, 500);
|
| 903 |
+
}
|
| 904 |
+
}, 100);
|
| 905 |
+
}
|
| 906 |
+
|
| 907 |
+
// Process mock CSV data (simulating real CSV parsing)
|
| 908 |
+
function processMockCsvData() {
|
| 909 |
+
// Add some mock events to simulate CSV import
|
| 910 |
+
const newEvents = [
|
| 911 |
+
{
|
| 912 |
+
id: eventsData.length + 1,
|
| 913 |
+
timestamp: "2023-11-16 08:15:22",
|
| 914 |
+
severity: "high",
|
| 915 |
+
name: "Disk I/O latency on database server",
|
| 916 |
+
description: "Disk response time exceeds 100ms threshold",
|
| 917 |
+
host: "db-primary-01",
|
| 918 |
+
tags: ["db-primary-01", "Disk", "Performance"],
|
| 919 |
+
analyzed: false
|
| 920 |
+
},
|
| 921 |
+
{
|
| 922 |
+
id: eventsData.length + 2,
|
| 923 |
+
timestamp: "2023-11-16 07:42:11",
|
| 924 |
+
severity: "warning",
|
| 925 |
+
name: "High network traffic on firewall",
|
| 926 |
+
description: "Network throughput exceeds 80% of capacity",
|
| 927 |
+
host: "firewall-01",
|
| 928 |
+
tags: ["firewall-01", "Network", "Traffic"],
|
| 929 |
+
analyzed: false
|
| 930 |
+
},
|
| 931 |
+
{
|
| 932 |
+
id: eventsData.length + 3,
|
| 933 |
+
timestamp: "2023-11-16 07:18:04",
|
| 934 |
+
severity: "average",
|
| 935 |
+
name: "Memory usage warning on app server",
|
| 936 |
+
description: "Memory usage at 75% capacity",
|
| 937 |
+
host: "app-server-03",
|
| 938 |
+
tags: ["app-server-03", "Memory", "Performance"],
|
| 939 |
+
analyzed: false
|
| 940 |
+
}
|
| 941 |
+
];
|
| 942 |
+
|
| 943 |
+
// Add new events to the data
|
| 944 |
+
eventsData.push(...newEvents);
|
| 945 |
+
|
| 946 |
+
// Update the UI
|
| 947 |
+
renderEvents();
|
| 948 |
+
updateStats();
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
// Reset file upload UI
|
| 952 |
+
function resetFileUpload() {
|
| 953 |
+
csvFileInput.value = '';
|
| 954 |
+
filePreview.classList.add('hidden');
|
| 955 |
+
parseProgress.classList.add('hidden');
|
| 956 |
+
progressBar.style.width = '0%';
|
| 957 |
+
parsePercentage.textContent = '0%';
|
| 958 |
}
|
| 959 |
|
| 960 |
// Simulate analyzing an event with Ollama
|
|
|
|
| 965 |
// Hide placeholder and show analysis after a delay (simulating API call)
|
| 966 |
setTimeout(() => {
|
| 967 |
// Find the event
|
| 968 |
+
const event = eventsData.find(e => e.id === eventId);
|
| 969 |
|
| 970 |
// Update the analysis result with mock data
|
| 971 |
document.getElementById('analysis-root-cause').textContent = mockAnalysis.rootCause;
|
|
|
|
| 1003 |
analyzeBtn.classList.add('text-green-600', 'cursor-default');
|
| 1004 |
analyzeBtn.removeAttribute('data-event-id');
|
| 1005 |
|
| 1006 |
+
// Update stats
|
| 1007 |
+
updateStats();
|
| 1008 |
+
|
| 1009 |
showNotification("Event analyzed successfully", "success");
|
| 1010 |
}, 1500);
|
| 1011 |
}
|
|
|
|
| 1017 |
|
| 1018 |
// Show notification
|
| 1019 |
function showNotification(message, type) {
|
|
|
|
| 1020 |
const notification = document.createElement('div');
|
| 1021 |
notification.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg text-white ${
|
| 1022 |
type === 'success' ? 'bg-green-600' :
|
| 1023 |
type === 'error' ? 'bg-red-600' :
|
| 1024 |
+
type === 'warning' ? 'bg-yellow-600' : 'bg-blue-600'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|