source: templates/detailed_dashboard.html@ 640ed89

Last change on this file since 640ed89 was 640ed89, checked in by istevanoska <ilinastevanoska@…>, 7 months ago

Initial commit

  • Property mode set to 100644
File size: 33.9 KB
Line 
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">
6 <title>Sysmon Detailed Dashboard</title>
7 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8 <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
9 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
10 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
11 <style>
12 body { background-color: #f5f7fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
13 .navbar { box-shadow: 0 2px 4px rgba(0,0,0,.1); }
14 .card { border: none; box-shadow: 0 2px 4px rgba(0,0,0,.05); margin-bottom: 20px; }
15 .card-header { background-color: #fff; border-bottom: 1px solid #eaeaea; }
16 .stat-card { transition: all 0.3s; border-left: 4px solid; }
17 .stat-card:hover { transform: translateY(-3px); box-shadow: 0 5px 15px rgba(0,0,0,.1); }
18 .event-item { border-left: 3px solid; padding: 10px; margin-bottom: 8px; background: white; border-radius: 4px; }
19 .event-dns { border-left-color: #3498db; }
20 .event-process { border-left-color: #2ecc71; }
21 .event-network { border-left-color: #e74c3c; }
22 .event-file { border-left-color: #f39c12; }
23 .badge { font-size: 0.75em; }
24 .tab-content { padding-top: 20px; }
25 .nav-tabs .nav-link { border: none; color: #6c757d; }
26 .nav-tabs .nav-link.active { color: #0d6efd; border-bottom: 2px solid #0d6efd; }
27 .search-box { margin-bottom: 20px; }
28 .process-badge { background-color: #e8f4fc; color: #3498db; }
29 .dns-badge { background-color: #e8f6f3; color: #2ecc71; }
30 .network-badge { background-color: #fdedec; color: #e74c3c; }
31 .file-badge { background-color: #fef9e7; color: #f39c12; }
32 .computer-online { color: #27ae60; }
33 .computer-offline { color: #e74c3c; }
34 .suspicious { background-color: #fdedec !important; }
35 .table-hover tbody tr:hover { background-color: rgba(0,0,0,.02); }
36 </style>
37</head>
38<body>
39 <nav class="navbar navbar-expand-lg navbar-light bg-white">
40 <div class="container-fluid">
41 <span class="navbar-brand mb-0 h1">
42 <i class="fas fa-shield-alt text-primary"></i>
43 Sysmon Detailed Dashboard
44 </span>
45 <div class="navbar-text">
46 <span id="currentTime"></span>
47 <span class="badge bg-success ms-2" id="onlineBadge">0 Online</span>
48 </div>
49 </div>
50 </nav>
51
52 <div class="container-fluid mt-4">
53 <!-- Stats Cards -->
54 <div class="row mb-4">
55 <div class="col-xl-2 col-md-4 col-sm-6">
56 <div class="card stat-card border-left-primary">
57 <div class="card-body">
58 <div class="d-flex justify-content-between">
59 <div>
60 <h6 class="text-muted">Total Events</h6>
61 <h3 id="totalEvents">0</h3>
62 </div>
63 <div class="align-self-center">
64 <i class="fas fa-database fa-2x text-primary"></i>
65 </div>
66 </div>
67 </div>
68 </div>
69 </div>
70 <div class="col-xl-2 col-md-4 col-sm-6">
71 <div class="card stat-card border-left-success">
72 <div class="card-body">
73 <div class="d-flex justify-content-between">
74 <div>
75 <h6 class="text-muted">Computers</h6>
76 <h3><span id="onlineComputers">0</span>/<span id="totalComputers">0</span></h3>
77 </div>
78 <div class="align-self-center">
79 <i class="fas fa-desktop fa-2x text-success"></i>
80 </div>
81 </div>
82 </div>
83 </div>
84 </div>
85 <div class="col-xl-2 col-md-4 col-sm-6">
86 <div class="card stat-card border-left-info">
87 <div class="card-body">
88 <div class="d-flex justify-content-between">
89 <div>
90 <h6 class="text-muted">DNS Queries</h6>
91 <h3 id="dnsCount">0</h3>
92 </div>
93 <div class="align-self-center">
94 <i class="fas fa-globe fa-2x text-info"></i>
95 </div>
96 </div>
97 </div>
98 </div>
99 </div>
100 <div class="col-xl-2 col-md-4 col-sm-6">
101 <div class="card stat-card border-left-warning">
102 <div class="card-body">
103 <div class="d-flex justify-content-between">
104 <div>
105 <h6 class="text-muted">Processes</h6>
106 <h3 id="processCount">0</h3>
107 </div>
108 <div class="align-self-center">
109 <i class="fas fa-cogs fa-2x text-warning"></i>
110 </div>
111 </div>
112 </div>
113 </div>
114 </div>
115 <div class="col-xl-2 col-md-4 col-sm-6">
116 <div class="card stat-card border-left-danger">
117 <div class="card-body">
118 <div class="d-flex justify-content-between">
119 <div>
120 <h6 class="text-muted">Network</h6>
121 <h3 id="networkCount">0</h3>
122 </div>
123 <div class="align-self-center">
124 <i class="fas fa-wifi fa-2x text-danger"></i>
125 </div>
126 </div>
127 </div>
128 </div>
129 </div>
130 <div class="col-xl-2 col-md-4 col-sm-6">
131 <div class="card stat-card border-left-secondary">
132 <div class="card-body">
133 <div class="d-flex justify-content-between">
134 <div>
135 <h6 class="text-muted">Last Update</h6>
136 <h6 id="lastUpdate">Just now</h6>
137 </div>
138 <div class="align-self-center">
139 <i class="fas fa-clock fa-2x text-secondary"></i>
140 </div>
141 </div>
142 </div>
143 </div>
144 </div>
145 </div>
146
147 <!-- Tabs for different data types -->
148 <ul class="nav nav-tabs" id="myTab" role="tablist">
149 <li class="nav-item" role="presentation">
150 <button class="nav-link active" id="events-tab" data-bs-toggle="tab" data-bs-target="#events" type="button">
151 <i class="fas fa-list"></i> All Events
152 </button>
153 </li>
154 <li class="nav-item" role="presentation">
155 <button class="nav-link" id="dns-tab" data-bs-toggle="tab" data-bs-target="#dns" type="button">
156 <i class="fas fa-globe"></i> DNS Queries
157 </button>
158 </li>
159 <li class="nav-item" role="presentation">
160 <button class="nav-link" id="processes-tab" data-bs-toggle="tab" data-bs-target="#processes" type="button">
161 <i class="fas fa-cogs"></i> Processes
162 </button>
163 </li>
164 <li class="nav-item" role="presentation">
165 <button class="nav-link" id="network-tab" data-bs-toggle="tab" data-bs-target="#network" type="button">
166 <i class="fas fa-wifi"></i> Network
167 </button>
168 </li>
169 <li class="nav-item" role="presentation">
170 <button class="nav-link" id="files-tab" data-bs-toggle="tab" data-bs-target="#files" type="button">
171 <i class="fas fa-file"></i> Files
172 </button>
173 </li>
174 <li class="nav-item" role="presentation">
175 <button class="nav-link" id="windows-tab" data-bs-toggle="tab" data-bs-target="#windows" type="button">
176 <i class="fas fa-windows"></i> Windows Events
177 </button>
178 </li>
179 <li class="nav-item" role="presentation">
180 <button class="nav-link" id="computers-tab" data-bs-toggle="tab" data-bs-target="#computers" type="button">
181 <i class="fas fa-desktop"></i> Computers
182 </button>
183 </li>
184 </ul>
185
186 <div class="tab-content" id="myTabContent">
187 <!-- All Events Tab -->
188 <div class="tab-pane fade show active" id="events" role="tabpanel">
189 <div class="card">
190 <div class="card-header">
191 <h5 class="mb-0">Recent Events</h5>
192 <div class="search-box">
193 <input type="text" class="form-control" id="eventSearch" placeholder="Search events...">
194 </div>
195 </div>
196 <div class="card-body">
197 <div id="eventsList">
198 <div class="text-center py-5">
199 <div class="spinner-border text-primary" role="status">
200 <span class="visually-hidden">Loading...</span>
201 </div>
202 <p class="mt-2">Loading events...</p>
203 </div>
204 </div>
205 </div>
206 </div>
207 </div>
208
209 <!-- DNS Tab -->
210 <div class="tab-pane fade" id="dns" role="tabpanel">
211 <div class="row">
212 <div class="col-md-8">
213 <div class="card">
214 <div class="card-header">
215 <h5 class="mb-0">Recent DNS Queries</h5>
216 </div>
217 <div class="card-body">
218 <div class="table-responsive">
219 <table class="table table-hover">
220 <thead>
221 <tr>
222 <th>Time</th>
223 <th>Computer</th>
224 <th>Domain</th>
225 <th>Result</th>
226 <th>Process</th>
227 </tr>
228 </thead>
229 <tbody id="dnsTable">
230 </tbody>
231 </table>
232 </div>
233 </div>
234 </div>
235 </div>
236 <div class="col-md-4">
237 <div class="card">
238 <div class="card-header">
239 <h5 class="mb-0">Top Domains</h5>
240 </div>
241 <div class="card-body">
242 <div id="topDomains">
243 </div>
244 </div>
245 </div>
246 </div>
247 </div>
248 </div>
249
250 <!-- Processes Tab -->
251 <div class="tab-pane fade" id="processes" role="tabpanel">
252 <div class="row">
253 <div class="col-md-8">
254 <div class="card">
255 <div class="card-header">
256 <h5 class="mb-0">Recent Process Executions</h5>
257 </div>
258 <div class="card-body">
259 <div class="table-responsive">
260 <table class="table table-hover">
261 <thead>
262 <tr>
263 <th>Time</th>
264 <th>Computer</th>
265 <th>Process</th>
266 <th>User</th>
267 <th>Command Line</th>
268 <th>Parent</th>
269 </tr>
270 </thead>
271 <tbody id="processesTable">
272 </tbody>
273 </table>
274 </div>
275 </div>
276 </div>
277 </div>
278 <div class="col-md-4">
279 <div class="card">
280 <div class="card-header">
281 <h5 class="mb-0">Most Executed</h5>
282 </div>
283 <div class="card-body">
284 <div id="topProcesses">
285 </div>
286 </div>
287 </div>
288 </div>
289 </div>
290 </div>
291
292 <!-- Network Tab -->
293 <div class="tab-pane fade" id="network" role="tabpanel">
294 <div class="row">
295 <div class="col-md-8">
296 <div class="card">
297 <div class="card-header">
298 <h5 class="mb-0">Network Connections</h5>
299 </div>
300 <div class="card-body">
301 <div class="table-responsive">
302 <table class="table table-hover">
303 <thead>
304 <tr>
305 <th>Time</th>
306 <th>Computer</th>
307 <th>Process</th>
308 <th>Source</th>
309 <th>Destination</th>
310 <th>Protocol</th>
311 </tr>
312 </thead>
313 <tbody id="networkTable">
314 </tbody>
315 </table>
316 </div>
317 </div>
318 </div>
319 </div>
320 <div class="col-md-4">
321 <div class="card">
322 <div class="card-header">
323 <h5 class="mb-0">Top Destinations</h5>
324 </div>
325 <div class="card-body">
326 <div id="topDestinations">
327 </div>
328 </div>
329 </div>
330 </div>
331 </div>
332 </div>
333
334 <!-- Files Tab -->
335 <div class="tab-pane fade" id="files" role="tabpanel">
336 <div class="card">
337 <div class="card-header">
338 <h5 class="mb-0">File Operations</h5>
339 </div>
340 <div class="card-body">
341 <div class="table-responsive">
342 <table class="table table-hover">
343 <thead>
344 <tr>
345 <th>Time</th>
346 <th>Computer</th>
347 <th>Process</th>
348 <th>File</th>
349 <th>Type</th>
350 <th>Location</th>
351 </tr>
352 </thead>
353 <tbody id="filesTable">
354 </tbody>
355 </table>
356 </div>
357 </div>
358 </div>
359 </div>
360
361 <!-- Windows Events Tab -->
362 <div class="tab-pane fade" id="windows" role="tabpanel">
363 <div class="card">
364 <div class="card-header">
365 <h5 class="mb-0">Windows Events</h5>
366 </div>
367 <div class="card-body">
368 <div class="table-responsive">
369 <table class="table table-hover">
370 <thead>
371 <tr>
372 <th>Time</th>
373 <th>Computer</th>
374 <th>Event ID</th>
375 <th>Source</th>
376 <th>Process</th>
377 <th>Files</th>
378 <th>Message</th>
379 </tr>
380 </thead>
381 <tbody id="windowsTable">
382 </tbody>
383 </table>
384 </div>
385 </div>
386 </div>
387 </div>
388
389 <!-- Computers Tab -->
390 <div class="tab-pane fade" id="computers" role="tabpanel">
391 <div class="card">
392 <div class="card-header">
393 <h5 class="mb-0">Connected Computers</h5>
394 </div>
395 <div class="card-body">
396 <div class="table-responsive">
397 <table class="table table-hover">
398 <thead>
399 <tr>
400 <th>Computer</th>
401 <th>User</th>
402 <th>IP Address</th>
403 <th>Status</th>
404 <th>OS</th>
405 <th>Last Seen</th>
406 <th>Events</th>
407 </tr>
408 </thead>
409 <tbody id="computersTable">
410 </tbody>
411 </table>
412 </div>
413 </div>
414 </div>
415 </div>
416 </div>
417 </div>
418
419 <!-- Bootstrap JS -->
420 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
421
422 <script>
423 // Initialize
424 $(document).ready(function() {
425 updateTime();
426 loadAllData();
427
428 // Auto-refresh every 30 seconds
429 setInterval(loadAllData, 30000);
430 setInterval(updateTime, 1000);
431
432 // Tab change handler
433 $('#myTab button').on('click', function() {
434 const tabId = $(this).attr('data-bs-target').substring(1);
435 if (tabId === 'dns') loadDNSData();
436 else if (tabId === 'processes') loadProcessData();
437 else if (tabId === 'network') loadNetworkData();
438 else if (tabId === 'files') loadFilesData();
439 else if (tabId === 'windows') loadWindowsData();
440 else if (tabId === 'computers') loadComputersData();
441 });
442
443 // Search functionality
444 $('#eventSearch').on('keyup', function() {
445 filterEvents($(this).val());
446 });
447 });
448
449 function updateTime() {
450 const now = new Date();
451 $('#currentTime').text(now.toLocaleString());
452 }
453
454 function loadAllData() {
455 loadStats();
456 loadEvents();
457 }
458
459 function loadStats() {
460 $.get('/api/stats', function(data) {
461 $('#totalEvents').text(data.total_events.toLocaleString());
462 $('#totalComputers').text(data.total_computers);
463 $('#onlineComputers').text(data.online_computers);
464 $('#onlineBadge').text(data.online_computers + ' Online');
465 $('#dnsCount').text(data.dns_queries.toLocaleString());
466 $('#processCount').text(data.process_creations.toLocaleString());
467 $('#networkCount').text(data.network_connections.toLocaleString());
468 $('#lastUpdate').text(new Date().toLocaleTimeString());
469 });
470 }
471
472 function loadEvents() {
473 $.get('/api/detailed_events?limit=50', function(data) {
474 let html = '';
475 data.events.forEach(event => {
476 const time = formatTime(event.timestamp);
477 const computer = event.computer || 'Unknown';
478
479 // Determine event type and styling
480 let badgeClass = 'secondary';
481 let icon = 'fas fa-info-circle';
482
483 if (event.event_id === 22) {
484 badgeClass = 'dns-badge';
485 icon = 'fas fa-globe';
486 } else if (event.event_id === 1) {
487 badgeClass = 'process-badge';
488 icon = 'fas fa-cogs';
489 } else if (event.event_id === 3) {
490 badgeClass = 'network-badge';
491 icon = 'fas fa-wifi';
492 } else if (event.event_id === 11 || event.event_id === 23 || event.event_id === 26) {
493 badgeClass = 'file-badge';
494 icon = 'fas fa-file';
495 }
496
497 // Check if suspicious
498 const isSuspicious = (event.event_id === 1 &&
499 (event.process_path && (
500 event.process_path.toLowerCase().includes('\temp\') ||
501 event.process_path.toLowerCase().includes('\appdata\')
502 ))) ||
503 (event.event_id === 3 && [4444, 1337, 31337, 6667].includes(event.dest_port));
504
505 html += `
506 <div class="event-item ${isSuspicious ? 'suspicious' : ''}">
507 <div class="d-flex justify-content-between">
508 <div>
509 <span class="badge ${badgeClass} me-2">
510 <i class="${icon} me-1"></i>${event.event_type}
511 </span>
512 <small class="text-muted">${time}</small>
513 <span class="badge bg-light text-dark ms-2">${computer}</span>
514 </div>
515 </div>
516 <div class="mt-2">
517 ${event.display_text || event.event_type}
518 </div>
519 ${event.command_line ? `<div class="mt-1"><small class="text-muted">${event.command_line.substring(0, 100)}...</small></div>` : ''}
520 </div>
521 `;
522 });
523
524 $('#eventsList').html(html);
525 });
526 }
527
528 function loadDNSData() {
529 $.get('/api/dns_queries?limit=50', function(data) {
530 // DNS Table
531 let tableHtml = '';
532 data.dns_queries.forEach(query => {
533 tableHtml += `
534 <tr>
535 <td>${formatTime(query.timestamp)}</td>
536 <td>${query.computer}</td>
537 <td><strong>${query.dns_query}</strong></td>
538 <td>${query.first_result || 'N/A'}</td>
539 <td><span class="badge bg-light text-dark">${query.process_name}</span></td>
540 </tr>
541 `;
542 });
543 $('#dnsTable').html(tableHtml);
544
545 // Top Domains
546 let domainsHtml = '';
547 data.top_domains.forEach(domain => {
548 domainsHtml += `
549 <div class="mb-2">
550 <div class="d-flex justify-content-between">
551 <span><strong>${domain.dns_query}</strong></span>
552 <span class="badge bg-primary">${domain.query_count}</span>
553 </div>
554 <div class="text-muted small">
555 <i class="fas fa-desktop me-1"></i>${domain.computers.split(',').length} computers
556 </div>
557 </div>
558 `;
559 });
560 $('#topDomains').html(domainsHtml);
561 });
562 }
563
564 function loadProcessData() {
565 $.get('/api/process_creations?limit=50', function(data) {
566 // Processes Table
567 let tableHtml = '';
568 data.process_creations.forEach(proc => {
569 const isSuspicious = proc.is_suspicious ? 'table-warning' : '';
570 tableHtml += `
571 <tr class="${isSuspicious}">
572 <td>${formatTime(proc.timestamp)}</td>
573 <td>${proc.computer}</td>
574 <td><strong>${proc.process_short}</strong></td>
575 <td>${proc.process_user || 'N/A'}</td>
576 <td><small class="text-muted">${proc.command_line ? proc.command_line.substring(0, 80) + '...' : 'N/A'}</small></td>
577 <td>${proc.parent_short || 'N/A'}</td>
578 </tr>
579 `;
580 });
581 $('#processesTable').html(tableHtml);
582
583 // Top Processes
584 let processesHtml = '';
585 data.top_processes.forEach(proc => {
586 processesHtml += `
587 <div class="mb-2">
588 <div class="d-flex justify-content-between">
589 <span><strong>${proc.process_short}</strong></span>
590 <span class="badge bg-success">${proc.execution_count}</span>
591 </div>
592 <div class="text-muted small">
593 <i class="fas fa-user me-1"></i>${proc.user_count} users
594 <i class="fas fa-desktop ms-2 me-1"></i>${proc.computer_count} computers
595 </div>
596 </div>
597 `;
598 });
599 $('#topProcesses').html(processesHtml);
600 });
601 }
602
603 function loadNetworkData() {
604 $.get('/api/network_connections?limit=50', function(data) {
605 // Network Table
606 let tableHtml = '';
607 data.network_connections.forEach(conn => {
608 const isSuspicious = conn.is_suspicious ? 'table-danger' : '';
609 tableHtml += `
610 <tr class="${isSuspicious}">
611 <td>${formatTime(conn.timestamp)}</td>
612 <td>${conn.computer}</td>
613 <td><span class="badge bg-light text-dark">${conn.process_short}</span></td>
614 <td>${conn.source_ip || 'N/A'}:${conn.source_port || ''}</td>
615 <td><strong>${conn.dest_ip}:${conn.dest_port}</strong></td>
616 <td>${conn.protocol || 'N/A'}</td>
617 </tr>
618 `;
619 });
620 $('#networkTable').html(tableHtml);
621
622 // Top Destinations
623 let destHtml = '';
624 data.top_destinations.forEach(dest => {
625 destHtml += `
626 <div class="mb-2">
627 <div class="d-flex justify-content-between">
628 <span><strong>${dest.dest_ip}:${dest.dest_port}</strong></span>
629 <span class="badge bg-info">${dest.connection_count}</span>
630 </div>
631 <div class="text-muted small">
632 ${dest.top_processes ? dest.top_processes.join(', ') : 'N/A'}
633 </div>
634 </div>
635 `;
636 });
637 $('#topDestinations').html(destHtml);
638 });
639 }
640
641 function loadFilesData() {
642 $.get('/api/file_operations?limit=50', function(data) {
643 let tableHtml = '';
644 data.file_operations.forEach(file => {
645 let fileType = '';
646 if (file.is_exe) fileType = '<span class="badge bg-danger">EXE</span>';
647 else if (file.is_dll) fileType = '<span class="badge bg-warning">DLL</span>';
648 else if (file.is_script) fileType = '<span class="badge bg-secondary">Script</span>';
649
650 const isSuspicious = file.is_suspicious ? 'table-warning' : '';
651
652 tableHtml += `
653 <tr class="${isSuspicious}">
654 <td>${formatTime(file.timestamp)}</td>
655 <td>${file.computer}</td>
656 <td><span class="badge bg-light text-dark">${file.process_short}</span></td>
657 <td><strong>${file.file_name}</strong></td>
658 <td>${fileType}</td>
659 <td><small class="text-muted">${file.file_path}</small></td>
660 </tr>
661 `;
662 });
663 $('#filesTable').html(tableHtml);
664 });
665 }
666
667 function loadWindowsData() {
668 $.get('/api/windows_events?limit=50', function(data) {
669 let tableHtml = '';
670 data.windows_events.forEach(event => {
671 let files = [];
672 if (event.file_created) files.push(`Created: ${event.file_created}`);
673 if (event.file_deleted) files.push(`Deleted: ${event.file_deleted}`);
674 if (event.file_modified) files.push(`Modified: ${event.file_modified}`);
675
676 tableHtml += `
677 <tr>
678 <td>${formatTime(event.timestamp)}</td>
679 <td>${event.computer}</td>
680 <td><span class="badge bg-secondary">${event.event_id}</span></td>
681 <td>${event.source}</td>
682 <td><strong>${event.process_name || 'N/A'}</strong></td>
683 <td>${files.join('<br>') || 'N/A'}</td>
684 <td><small class="text-muted">${event.message.substring(0, 100)}...</small></td>
685 </tr>
686 `;
687 });
688 $('#windowsTable').html(tableHtml);
689 });
690 }
691
692 function loadComputersData() {
693 $.get('/api/computers', function(data) {
694 let tableHtml = '';
695 data.computers.forEach(comp => {
696 const statusClass = comp.status === 'online' ? 'computer-online' : 'computer-offline';
697 const statusIcon = comp.status === 'online' ? '●' : '○';
698
699 tableHtml += `
700 <tr>
701 <td><strong>${comp.computer_name}</strong></td>
702 <td>${comp.user || 'N/A'}</td>
703 <td>${comp.ip_address || 'N/A'}</td>
704 <td class="${statusClass}">
705 <i class="fas fa-circle me-1"></i>${comp.status}
706 </td>
707 <td><small>${comp.os || 'N/A'}</small></td>
708 <td>${formatTime(comp.last_seen)}</td>
709 <td><span class="badge bg-primary">${comp.event_count || 0}</span></td>
710 </tr>
711 `;
712 });
713 $('#computersTable').html(tableHtml);
714 });
715 }
716
717 function filterEvents(searchTerm) {
718 $('.event-item').each(function() {
719 const text = $(this).text().toLowerCase();
720 if (text.includes(searchTerm.toLowerCase())) {
721 $(this).show();
722 } else {
723 $(this).hide();
724 }
725 });
726 }
727
728 function formatTime(timestamp) {
729 if (!timestamp) return 'N/A';
730 const date = new Date(timestamp);
731 return date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
732 }
733 </script>
734</body>
735</html>
Note: See TracBrowser for help on using the repository browser.