Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Zabbix Event Analyzer with Ollama</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #3b82f6; | |
| --primary-dark: #1d4ed8; | |
| --secondary: #10b981; | |
| --background: #f8fafc; | |
| --card: #ffffff; | |
| --text: #1e293b; | |
| --text-light: #64748b; | |
| --border: #e2e8f0; | |
| --danger: #ef4444; | |
| --warning: #f59e0b; | |
| --info: #06b6d4; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background-color: var(--background); | |
| color: var(--text); | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| } | |
| .card { | |
| background: var(--card); | |
| border-radius: 12px; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| border: 1px solid var(--border); | |
| } | |
| .severity-badge { | |
| padding: 4px 12px; | |
| border-radius: 20px; | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .severity-disaster { | |
| background-color: #fef2f2; | |
| color: #991b1b; | |
| } | |
| .severity-high { | |
| background-color: #fee2e2; | |
| color: #b91c1c; | |
| } | |
| .severity-average { | |
| background-color: #fef3c7; | |
| color: #b45309; | |
| } | |
| .severity-warning { | |
| background-color: #fef9c3; | |
| color: #a16207; | |
| } | |
| .severity-info { | |
| background-color: #dbeafe; | |
| color: #1e40af; | |
| } | |
| .severity-not-classified { | |
| background-color: #f3f4f6; | |
| color: #6b7280; | |
| } | |
| .event-timeline { | |
| position: relative; | |
| padding-left: 20px; | |
| } | |
| .event-timeline::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| width: 2px; | |
| background: linear-gradient(to bottom, var(--primary), var(--secondary)); | |
| border-radius: 2px; | |
| } | |
| .event-item { | |
| position: relative; | |
| margin-bottom: 24px; | |
| } | |
| .event-item::before { | |
| content: ''; | |
| position: absolute; | |
| left: -26px; | |
| top: 0; | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| background: var(--card); | |
| border: 2px solid var(--primary); | |
| } | |
| .analysis-result { | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .loading-spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid rgba(255, 255, 255, 0.3); | |
| border-radius: 50%; | |
| border-top-color: white; | |
| animation: spin 1s ease-in-out infinite; | |
| } | |
| @keyframes spin { | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| .sidebar { | |
| transition: transform 0.3s ease-in-out; | |
| } | |
| @media (max-width: 768px) { | |
| .sidebar { | |
| transform: translateX(-100%); | |
| } | |
| .sidebar.open { | |
| transform: translateX(0); | |
| } | |
| } | |
| /* CSV Upload Styles */ | |
| .file-upload-area { | |
| border: 2px dashed #d1d5db; | |
| border-radius: 8px; | |
| transition: all 0.3s ease; | |
| } | |
| .file-upload-area:hover { | |
| border-color: var(--primary); | |
| background-color: #f0f9ff; | |
| } | |
| .file-upload-area.active { | |
| border-color: var(--secondary); | |
| background-color: #d1fae5; | |
| } | |
| .file-list-item { | |
| animation: slideIn 0.3s ease-out; | |
| } | |
| @keyframes slideIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen"> | |
| <!-- Header --> | |
| <header class="bg-white shadow-sm border-b"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex justify-between items-center h-16"> | |
| <div class="flex items-center"> | |
| <button id="menu-btn" class="md:hidden p-2 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100"> | |
| <i class="fas fa-bars text-xl"></i> | |
| </button> | |
| <div class="flex items-center space-x-3"> | |
| <div | |
| class="w-8 h-8 bg-gradient-to-r from-blue-500 to-green-500 rounded-md flex items-center justify-center"> | |
| <i class="fas fa-chart-line text-white text-sm"></i> | |
| </div> | |
| <span class="text-xl font-bold text-gray-800">Zabbix Event Analyzer</span> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="hidden md:flex items-center space-x-2 text-sm text-gray-600"> | |
| <i class="fas fa-robot text-blue-500"></i> | |
| <span>Powered by Ollama</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" | |
| class="text-sm text-gray-500 hover:text-gray-700 flex items-center space-x-1"> | |
| <i class="fas fa-code"></i> | |
| <span>Built with anycoder</span> | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="flex"> | |
| <!-- Sidebar --> | |
| <aside id="sidebar" class="sidebar w-64 bg-white border-r flex-shrink-0 md:block"> | |
| <div class="p-4 border-b"> | |
| <h3 class="font-semibold text-gray-800">Filters</h3> | |
| </div> | |
| <div class="p-4 space-y-6"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Severity</label> | |
| <div class="space-y-2"> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" checked> | |
| <span class="ml-2 text-sm text-gray-600">Disaster</span> | |
| </label> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" checked> | |
| <span class="ml-2 text-sm text-gray-600">High</span> | |
| </label> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" checked> | |
| <span class="ml-2 text-sm text-gray-600">Average</span> | |
| </label> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" checked> | |
| <span class="ml-2 text-sm text-gray-600">Warning</span> | |
| </label> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" checked> | |
| <span class="ml-2 text-sm text-gray-600">Information</span> | |
| </label> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" checked> | |
| <span class="ml-2 text-sm text-gray-600">Not classified</span> | |
| </label> | |
| </div> | |
| </div> | |
| <div> | |
| <label for="time-range" class="block text-sm font-medium text-gray-700 mb-2">Time Range</label> | |
| <select id="time-range" class="w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="1h">Last 1 hour</option> | |
| <option value="6h" selected>Last 6 hours</option> | |
| <option value="12h">Last 12 hours</option> | |
| <option value="24h">Last 24 hours</option> | |
| <option value="7d">Last 7 days</option> | |
| <option value="30d">Last 30 days</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="host-filter" class="block text-sm font-medium text-gray-700 mb-2">Host</label> | |
| <input type="text" id="host-filter" placeholder="Filter by host..." class="w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label for="event-filter" class="block text-sm font-medium text-gray-700 mb-2">Event Name</label> | |
| <input type="text" id="event-filter" placeholder="Filter by event name..." class="w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <button id="apply-filters" class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors"> | |
| Apply Filters | |
| </button> | |
| </div> | |
| </aside> | |
| <!-- Main Content --> | |
| <main class="flex-1 overflow-y-auto"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
| <!-- CSV Upload Section --> | |
| <div class="card mb-8"> | |
| <div class="p-6"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-lg font-semibold text-gray-800">Import Zabbix Data</h2> | |
| <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"> | |
| <i class="fas fa-file-upload"></i> | |
| <span>Upload CSV</span> | |
| </button> | |
| </div> | |
| <div id="csv-upload-area" class="file-upload-area p-8 text-center cursor-pointer"> | |
| <input type="file" id="csv-file-input" accept=".csv" class="hidden"> | |
| <div class="mb-4"> | |
| <i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-2"></i> | |
| <p class="text-gray-600">Drag & drop CSV file here or click to browse</p> | |
| <p class="text-sm text-gray-500 mt-1">Supports Zabbix event export format</p> | |
| </div> | |
| </div> | |
| <div id="file-preview" class="hidden mt-6"> | |
| <h3 class="text-sm font-medium text-gray-700 mb-3">Selected File:</h3> | |
| <div class="flex items-center justify-between p-3 bg-gray-50 rounded-md"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-file-csv text-blue-600 text-xl mr-3"></i> | |
| <div> | |
| <p id="file-name" class="font-medium text-gray-800"></p> | |
| <p id="file-size" class="text-sm text-gray-500"></p> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-3"> | |
| <button id="parse-csv-btn" class="text-blue-600 hover:text-blue-800"> | |
| <i class="fas fa-play mr-1"></i> Parse | |
| </button> | |
| <button id="cancel-upload-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="parse-progress" class="mt-4 hidden"> | |
| <div class="flex items-center justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-700">Parsing CSV...</span> | |
| <span id="parse-percentage" class="text-sm font-medium text-gray-700">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2"> | |
| <div id="progress-bar" class="progress-bar bg-blue-600 h-2 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stats Cards --> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> | |
| <div class="card p-6"> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <p class="text-sm font-medium text-gray-500">Total Events</p> | |
| <p class="text-3xl font-bold text-gray-800 mt-1" id="total-events">1,248</p> | |
| </div> | |
| <div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-bell text-blue-600 text-xl"></i> | |
| </div> | |
| </div> | |
| <p class="text-sm text-green-600 mt-2 flex items-center"> | |
| <i class="fas fa-arrow-up mr-1"></i> | |
| 12% from yesterday | |
| </p> | |
| </div> | |
| <div class="card p-6"> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <p class="text-sm font-medium text-gray-500">Critical Events</p> | |
| <p class="text-3xl font-bold text-gray-800 mt-1" id="critical-events">42</p> | |
| </div> | |
| <div class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-exclamation-triangle text-red-600 text-xl"></i> | |
| </div> | |
| </div> | |
| <p class="text-sm text-red-600 mt-2 flex items-center"> | |
| <i class="fas fa-arrow-down mr-1"></i> | |
| 5% from yesterday | |
| </p> | |
| </div> | |
| <div class="card p-6"> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <p class="text-sm font-medium text-gray-500">Analyzed Events</p> | |
| <p class="text-3xl font-bold text-gray-800 mt-1" id="analyzed-events">876</p> | |
| </div> | |
| <div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-brain text-green-600 text-xl"></i> | |
| </div> | |
| </div> | |
| <p class="text-sm text-green-600 mt-2 flex items-center"> | |
| <i class="fas fa-arrow-up mr-1"></i> | |
| 22% from yesterday | |
| </p> | |
| </div> | |
| <div class="card p-6"> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <p class="text-sm font-medium text-gray-500">Active Triggers</p> | |
| <p class="text-3xl font-bold text-gray-800 mt-1" id="active-triggers">18</p> | |
| </div> | |
| <div class="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-bolt text-purple-600 text-xl"></i> | |
| </div> | |
| </div> | |
| <p class="text-sm text-purple-600 mt-2 flex items-center"> | |
| <i class="fas fa-arrow-up mr-1"></i> | |
| 8% from yesterday | |
| </p> | |
| </div> | |
| </div> | |
| <!-- Main Content Area --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Events Timeline --> | |
| <div class="lg:col-span-2"> | |
| <div class="card"> | |
| <div class="p-6 border-b"> | |
| <div class="flex items-center justify-between"> | |
| <h2 class="text-lg font-semibold text-gray-800">Recent Events</h2> | |
| <div class="flex items-center space-x-3"> | |
| <button id="refresh-events" class="text-blue-600 hover:text-blue-800 flex items-center space-x-1"> | |
| <i class="fas fa-sync-alt"></i> | |
| <span>Refresh</span> | |
| </button> | |
| <button id="export-events" class="text-green-600 hover:text-green-800 flex items-center space-x-1"> | |
| <i class="fas fa-file-export"></i> | |
| <span>Export</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-6"> | |
| <div class="event-timeline"> | |
| <!-- Event items will be populated by JavaScript --> | |
| <div id="events-container" class="space-y-6"> | |
| <!-- Sample event item (will be generated by JS) --> | |
| <div class="event-item"> | |
| <div class="bg-gray-50 rounded-lg p-4"> | |
| <div class="flex items-start justify-between"> | |
| <div class="flex-1"> | |
| <div class="flex items-center space-x-3"> | |
| <span class="severity-badge severity-high"> | |
| <i class="fas fa-exclamation-circle"></i> | |
| <span>High</span> | |
| </span> | |
| <span class="text-sm text-gray-500">2023-11-15 14:32:17</span> | |
| </div> | |
| <h3 class="font-semibold text-gray-800 mt-2">High CPU load on | |
| web-server-01</h3> | |
| <p class="text-sm text-gray-600 mt-1">CPU load has been over 90% | |
| for the last 5 minutes</p> | |
| <div class="mt-3 flex flex-wrap gap-2"> | |
| <span class="px-2 py-1 bg-gray-100 text-xs rounded-full">web-server-01</span> | |
| <span class="px-2 py-1 bg-gray-100 text-xs rounded-full">CPU</span> | |
| <span class="px-2 py-1 bg-gray-100 text-xs rounded-full">Performance</span> | |
| </div> | |
| </div> | |
| <button class="analyze-btn text-blue-600 hover:text-blue-800 px-3 py-1 rounded-md text-sm font-medium" data-event-id="1"> | |
| <i class="fas fa-brain mr-1"></i> Analyze | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 flex justify-center"> | |
| <button id="load-more" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-md"> | |
| Load More Events | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Analysis Panel --> | |
| <div class="lg:col-span-1"> | |
| <div class="card h-full"> | |
| <div class="p-6 border-b"> | |
| <h2 class="text-lg font-semibold text-gray-800">AI Analysis</h2> | |
| <p class="text-sm text-gray-500">Powered by Ollama</p> | |
| </div> | |
| <div class="p-6"> | |
| <div id="analysis-placeholder" class="text-center py-12"> | |
| <div | |
| class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-brain text-blue-600 text-2xl"></i> | |
| </div> | |
| <h3 class="text-gray-800 font-medium">Select an event to analyze</h3> | |
| <p class="text-gray-500 text-sm mt-1">Click the "Analyze" button on any event to get | |
| AI-powered insights</p> | |
| </div> | |
| <div id="analysis-result" class="hidden"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h3 class="font-semibold text-gray-800">Analysis Results</h3> | |
| <span class="text-sm text-gray-500" id="analysis-time">Just now</span> | |
| </div> | |
| <div class="space-y-4"> | |
| <div> | |
| <p class="text-sm font-medium text-gray-500 mb-1">Root Cause</p> | |
| <p class="text-gray-800" id="analysis-root-cause">The high CPU load is | |
| likely caused by a sudden spike in web traffic combined with inefficient | |
| database queries in the application.</p> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-500 mb-1">Recommended Actions</p> | |
| <ul class="space-y-2 text-gray-800" id="analysis-actions"> | |
| <li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>Check web server access logs for traffic patterns</span> | |
| </li> | |
| <li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>Optimize slow database queries</span> | |
| </li> | |
| <li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>Consider adding more web server instances</span> | |
| </li> | |
| </ul> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-500 mb-1">Confidence Level</p> | |
| <div class="flex items-center space-x-2"> | |
| <div class="w-full bg-gray-200 rounded-full h-2"> | |
| <div class="bg-blue-600 h-2 rounded-full" style="width: 85%"></div> | |
| </div> | |
| <span class="text-sm font-medium text-gray-800">85%</span> | |
| </div> | |
| </div> | |
| <div class="pt-4 border-t"> | |
| <p class="text-sm font-medium text-gray-500 mb-2">Additional Context</p> | |
| <div class="space-y-2 text-sm text-gray-600" id="analysis-context"> | |
| <p>Similar events occurred 3 times in the last week during peak hours. | |
| </p> | |
| <p>The database server shows correlated high load during these events. | |
| </p> | |
| <p>No recent deployment changes that would explain this behavior.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Loading Overlay --> | |
| <div id="loading-overlay" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50"> | |
| <div class="bg-white rounded-lg p-6 w-80 text-center"> | |
| <div class="flex justify-center mb-4"> | |
| <div class="loading-spinner"></div> | |
| </div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-2">Analyzing Event</h3> | |
| <p class="text-gray-600">Ollama is processing the event data and generating insights...</p> | |
| <p class="text-sm text-gray-500 mt-2">This may take a few seconds</p> | |
| </div> | |
| </div> | |
| <!-- CSV Processing Overlay --> | |
| <div id="csv-processing-overlay" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50"> | |
| <div class="bg-white rounded-lg p-6 w-96 text-center"> | |
| <div class="flex justify-center mb-4"> | |
| <div class="w-16 h-16 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div> | |
| </div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-2" id="csv-processing-title">Processing CSV File</h3> | |
| <p class="text-gray-600 mb-4" id="csv-processing-message">Parsing Zabbix events from the uploaded file...</p> | |
| <div class="w-full bg-gray-200 rounded-full h-3 mb-2"> | |
| <div id="csv-processing-progress" class="bg-blue-600 h-3 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <p class="text-sm text-gray-500" id="csv-processing-percentage">0% Complete</p> | |
| </div> | |
| </div> | |
| <script> | |
| // Mock data for events | |
| let eventsData = [ | |
| { | |
| id: 1, | |
| timestamp: "2023-11-15 14:32:17", | |
| severity: "high", | |
| name: "High CPU load on web-server-01", | |
| description: "CPU load has been over 90% for the last 5 minutes", | |
| host: "web-server-01", | |
| tags: ["web-server-01", "CPU", "Performance"], | |
| analyzed: false | |
| }, | |
| { | |
| id: 2, | |
| timestamp: "2023-11-15 13:45:22", | |
| severity: "disaster", | |
| name: "Database connection failed", | |
| description: "Primary database server is not responding to connection requests", | |
| host: "db-primary-01", | |
| tags: ["db-primary-01", "Database", "Connection"], | |
| analyzed: false | |
| }, | |
| { | |
| id: 3, | |
| timestamp: "2023-11-15 12:10:08", | |
| severity: "average", | |
| name: "Disk space warning on backup server", | |
| description: "Backup storage is at 85% capacity", | |
| host: "backup-server-01", | |
| tags: ["backup-server-01", "Storage", "Disk"], | |
| analyzed: false | |
| }, | |
| { | |
| id: 4, | |
| timestamp: "2023-11-15 11:22:45", | |
| severity: "warning", | |
| name: "High memory usage on API server", | |
| description: "Memory usage exceeds 90% threshold", | |
| host: "api-server-02", | |
| tags: ["api-server-02", "Memory", "Performance"], | |
| analyzed: false | |
| }, | |
| { | |
| id: 5, | |
| timestamp: "2023-11-15 10:15:33", | |
| severity: "info", | |
| name: "Scheduled maintenance completed", | |
| description: "Nightly maintenance tasks finished successfully", | |
| host: "monitoring-server", | |
| tags: ["monitoring-server", "Maintenance", "Scheduled"], | |
| analyzed: false | |
| }, | |
| { | |
| id: 6, | |
| timestamp: "2023-11-15 09:05:12", | |
| severity: "high", | |
| name: "Network latency detected", | |
| description: "Increased latency between data centers", | |
| host: "network-core-01", | |
| tags: ["network-core-01", "Network", "Latency"], | |
| analyzed: false | |
| } | |
| ]; | |
| // Mock analysis data | |
| const mockAnalysis = { | |
| rootCause: "The high CPU load is likely caused by a sudden spike in web traffic combined with inefficient database queries in the application.", | |
| actions: [ | |
| "Check web server access logs for traffic patterns", | |
| "Optimize slow database queries", | |
| "Consider adding more web server instances" | |
| ], | |
| confidence: 85, | |
| context: [ | |
| "Similar events occurred 3 times in the last week during peak hours.", | |
| "The database server shows correlated high load during these events.", | |
| "No recent deployment changes that would explain this behavior." | |
| ] | |
| }; | |
| // DOM Elements | |
| const eventsContainer = document.getElementById('events-container'); | |
| const analysisPlaceholder = document.getElementById('analysis-placeholder'); | |
| const analysisResult = document.getElementById('analysis-result'); | |
| const loadingOverlay = document.getElementById('loading-overlay'); | |
| const csvProcessingOverlay = document.getElementById('csv-processing-overlay'); | |
| const menuBtn = document.getElementById('menu-btn'); | |
| const sidebar = document.getElementById('sidebar'); | |
| const refreshBtn = document.getElementById('refresh-events'); | |
| const loadMoreBtn = document.getElementById('load-more'); | |
| const uploadCsvBtn = document.getElementById('upload-csv-btn'); | |
| const csvUploadArea = document.getElementById('csv-upload-area'); | |
| const csvFileInput = document.getElementById('csv-file-input'); | |
| const filePreview = document.getElementById('file-preview'); | |
| const fileName = document.getElementById('file-name'); | |
| const fileSize = document.getElementById('file-size'); | |
| const parseCsvBtn = document.getElementById('parse-csv-btn'); | |
| const cancelUploadBtn = document.getElementById('cancel-upload-btn'); | |
| const parseProgress = document.getElementById('parse-progress'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const parsePercentage = document.getElementById('parse-percentage'); | |
| const csvProcessingProgress = document.getElementById('csv-processing-progress'); | |
| const csvProcessingPercentage = document.getElementById('csv-processing-percentage'); | |
| const csvProcessingTitle = document.getElementById('csv-processing-title'); | |
| const csvProcessingMessage = document.getElementById('csv-processing-message'); | |
| // Stats elements | |
| const totalEventsEl = document.getElementById('total-events'); | |
| const criticalEventsEl = document.getElementById('critical-events'); | |
| const analyzedEventsEl = document.getElementById('analyzed-events'); | |
| const activeTriggersEl = document.getElementById('active-triggers'); | |
| // Initialize the app | |
| function init() { | |
| renderEvents(); | |
| setupEventListeners(); | |
| updateStats(); | |
| } | |
| // Update statistics | |
| function updateStats() { | |
| const totalEvents = eventsData.length; | |
| const criticalEvents = eventsData.filter(e => e.severity === 'disaster' || e.severity === 'high').length; | |
| const analyzedEvents = eventsData.filter(e => e.analyzed).length; | |
| const activeTriggers = Math.floor(totalEvents * 0.1); // Mock calculation | |
| totalEventsEl.textContent = totalEvents.toLocaleString(); | |
| criticalEventsEl.textContent = criticalEvents.toLocaleString(); | |
| analyzedEventsEl.textContent = analyzedEvents.toLocaleString(); | |
| activeTriggersEl.textContent = activeTriggers.toLocaleString(); | |
| } | |
| // Render events to the timeline | |
| function renderEvents() { | |
| eventsContainer.innerHTML = ''; | |
| // Sort events by timestamp (newest first) | |
| const sortedEvents = [...eventsData].sort((a, b) => { | |
| return new Date(b.timestamp) - new Date(a.timestamp); | |
| }); | |
| // For demo purposes, we'll show all events | |
| sortedEvents.forEach(event => { | |
| const eventElement = createEventElement(event); | |
| eventsContainer.appendChild(eventElement); | |
| }); | |
| } | |
| // Create an event element | |
| function createEventElement(event) { | |
| const eventDiv = document.createElement('div'); | |
| eventDiv.className = 'event-item'; | |
| eventDiv.innerHTML = ` | |
| <div class="bg-gray-50 rounded-lg p-4"> | |
| <div class="flex items-start justify-between"> | |
| <div class="flex-1"> | |
| <div class="flex items-center space-x-3"> | |
| <span class="severity-badge severity-${event.severity}"> | |
| <i class="fas fa-exclamation-circle"></i> | |
| <span>${capitalizeFirstLetter(event.severity)}</span> | |
| </span> | |
| <span class="text-sm text-gray-500">${event.timestamp}</span> | |
| </div> | |
| <h3 class="font-semibold text-gray-800 mt-2">${event.name}</h3> | |
| <p class="text-sm text-gray-600 mt-1">${event.description}</p> | |
| <div class="mt-3 flex flex-wrap gap-2"> | |
| ${event.tags.map(tag => ` | |
| <span class="px-2 py-1 bg-gray-100 text-xs rounded-full">${tag}</span> | |
| `).join('')} | |
| </div> | |
| </div> | |
| <button class="analyze-btn text-blue-600 hover:text-blue-800 px-3 py-1 rounded-md text-sm font-medium" data-event-id="${event.id}"> | |
| <i class="fas fa-brain mr-1"></i> Analyze | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| return eventDiv; | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Mobile menu toggle | |
| menuBtn.addEventListener('click', () => { | |
| sidebar.classList.toggle('open'); | |
| }); | |
| // Close sidebar when clicking outside on mobile | |
| document.addEventListener('click', (e) => { | |
| if (window.innerWidth < 768 && !sidebar.contains(e.target) && !menuBtn.contains(e.target)) { | |
| sidebar.classList.remove('open'); | |
| } | |
| }); | |
| // Analyze button click handler (event delegation) | |
| eventsContainer.addEventListener('click', (e) => { | |
| if (e.target.closest('.analyze-btn')) { | |
| const eventId = parseInt(e.target.closest('.analyze-btn').dataset.eventId); | |
| analyzeEvent(eventId); | |
| } | |
| }); | |
| // Refresh events button | |
| refreshBtn.addEventListener('click', () => { | |
| showNotification("Events refreshed", "success"); | |
| }); | |
| // Load more events button | |
| loadMoreBtn.addEventListener('click', () => { | |
| showNotification("No more events to load", "info"); | |
| }); | |
| // Apply filters button | |
| document.getElementById('apply-filters').addEventListener('click', () => { | |
| showNotification("Filters applied", "success"); | |
| }); | |
| // CSV Upload functionality | |
| uploadCsvBtn.addEventListener('click', () => { | |
| csvFileInput.click(); | |
| }); | |
| csvUploadArea.addEventListener('click', () => { | |
| csvFileInput.click(); | |
| }); | |
| // Drag and drop functionality | |
| csvUploadArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| csvUploadArea.classList.add('active'); | |
| }); | |
| csvUploadArea.addEventListener('dragleave', () => { | |
| csvUploadArea.classList.remove('active'); | |
| }); | |
| csvUploadArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| csvUploadArea.classList.remove('active'); | |
| if (e.dataTransfer.files.length) { | |
| handleFileSelect(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| csvFileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length) { | |
| handleFileSelect(e.target.files[0]); | |
| } | |
| }); | |
| parseCsvBtn.addEventListener('click', () => { | |
| parseCsvFile(); | |
| }); | |
| cancelUploadBtn.addEventListener('click', () => { | |
| resetFileUpload(); | |
| }); | |
| } | |
| // Handle file selection | |
| function handleFileSelect(file) { | |
| if (file.type !== 'text/csv' && !file.name.endsWith('.csv')) { | |
| showNotification("Please upload a valid CSV file", "error"); | |
| return; | |
| } | |
| filePreview.classList.remove('hidden'); | |
| fileName.textContent = file.name; | |
| fileSize.textContent = formatFileSize(file.size); | |
| } | |
| // Format file size | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // Parse CSV file | |
| function parseCsvFile() { | |
| const file = csvFileInput.files[0]; | |
| if (!file) return; | |
| // Show processing overlay | |
| csvProcessingOverlay.classList.remove('hidden'); | |
| csvProcessingTitle.textContent = "Processing CSV File"; | |
| csvProcessingMessage.textContent = "Parsing Zabbix events from the uploaded file..."; | |
| // Simulate parsing progress | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += 5; | |
| csvProcessingProgress.style.width = `${progress}%`; | |
| csvProcessingPercentage.textContent = `${progress}% Complete`; | |
| if (progress >= 100) { | |
| clearInterval(interval); | |
| // Simulate successful parsing | |
| setTimeout(() => { | |
| processMockCsvData(); | |
| csvProcessingOverlay.classList.add('hidden'); | |
| resetFileUpload(); | |
| showNotification("CSV file processed successfully! 12 new events imported.", "success"); | |
| }, 500); | |
| } | |
| }, 100); | |
| } | |
| // Process mock CSV data (simulating real CSV parsing) | |
| function processMockCsvData() { | |
| // Add some mock events to simulate CSV import | |
| const newEvents = [ | |
| { | |
| id: eventsData.length + 1, | |
| timestamp: "2023-11-16 08:15:22", | |
| severity: "high", | |
| name: "Disk I/O latency on database server", | |
| description: "Disk response time exceeds 100ms threshold", | |
| host: "db-primary-01", | |
| tags: ["db-primary-01", "Disk", "Performance"], | |
| analyzed: false | |
| }, | |
| { | |
| id: eventsData.length + 2, | |
| timestamp: "2023-11-16 07:42:11", | |
| severity: "warning", | |
| name: "High network traffic on firewall", | |
| description: "Network throughput exceeds 80% of capacity", | |
| host: "firewall-01", | |
| tags: ["firewall-01", "Network", "Traffic"], | |
| analyzed: false | |
| }, | |
| { | |
| id: eventsData.length + 3, | |
| timestamp: "2023-11-16 07:18:04", | |
| severity: "average", | |
| name: "Memory usage warning on app server", | |
| description: "Memory usage at 75% capacity", | |
| host: "app-server-03", | |
| tags: ["app-server-03", "Memory", "Performance"], | |
| analyzed: false | |
| } | |
| ]; | |
| // Add new events to the data | |
| eventsData.push(...newEvents); | |
| // Update the UI | |
| renderEvents(); | |
| updateStats(); | |
| } | |
| // Reset file upload UI | |
| function resetFileUpload() { | |
| csvFileInput.value = ''; | |
| filePreview.classList.add('hidden'); | |
| parseProgress.classList.add('hidden'); | |
| progressBar.style.width = '0%'; | |
| parsePercentage.textContent = '0%'; | |
| } | |
| // Simulate analyzing an event with Ollama | |
| function analyzeEvent(eventId) { | |
| // Show loading overlay | |
| loadingOverlay.classList.remove('hidden'); | |
| // Hide placeholder and show analysis after a delay (simulating API call) | |
| setTimeout(() => { | |
| // Find the event | |
| const event = eventsData.find(e => e.id === eventId); | |
| // Update the analysis result with mock data | |
| document.getElementById('analysis-root-cause').textContent = mockAnalysis.rootCause; | |
| const actionsList = document.getElementById('analysis-actions'); | |
| actionsList.innerHTML = mockAnalysis.actions.map(action => ` | |
| <li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>${action}</span> | |
| </li> | |
| `).join(''); | |
| document.getElementById('analysis-time').textContent = "Just now"; | |
| document.getElementById('analysis-context').innerHTML = mockAnalysis.context.map(item => ` | |
| <p>${item}</p> | |
| `).join(''); | |
| // Update confidence level | |
| const confidenceBar = analysisResult.querySelector('.bg-blue-600'); | |
| confidenceBar.style.width = `${mockAnalysis.confidence}%`; | |
| confidenceBar.nextElementSibling.textContent = `${mockAnalysis.confidence}%`; | |
| // Show analysis result | |
| analysisPlaceholder.classList.add('hidden'); | |
| analysisResult.classList.remove('hidden'); | |
| // Hide loading overlay | |
| loadingOverlay.classList.add('hidden'); | |
| // Mark event as analyzed | |
| event.analyzed = true; | |
| const analyzeBtn = document.querySelector(`.analyze-btn[data-event-id="${eventId}"]`); | |
| analyzeBtn.innerHTML = '<i class="fas fa-check-circle mr-1"></i> Analyzed'; | |
| analyzeBtn.classList.remove('text-blue-600', 'hover:text-blue-800'); | |
| analyzeBtn.classList.add('text-green-600', 'cursor-default'); | |
| analyzeBtn.removeAttribute('data-event-id'); | |
| // Update stats | |
| updateStats(); | |
| showNotification("Event analyzed successfully", "success"); | |
| }, 1500); | |
| } | |
| // Helper function to capitalize first letter | |
| function capitalizeFirstLetter(string) { | |
| return string.charAt(0).toUpperCase() + string.slice(1); | |
| } | |
| // Show notification | |
| function showNotification(message, type) { | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg text-white ${ | |
| type === 'success' ? 'bg-green-600' : | |
| type === 'error' ? 'bg-red-600' : | |
| type === 'warning' ? 'bg-yellow-600' : 'bg-blue-600' |