mirror of
https://github.com/selfhosters-cc/container-census.git
synced 2026-01-28 17:38:29 -06:00
1325 lines
69 KiB
HTML
1325 lines
69 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Container Census</title>
|
||
<link rel="stylesheet" href="styles.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script>
|
||
</head>
|
||
<body>
|
||
<!-- Top Navigation Bar -->
|
||
<header class="top-navbar">
|
||
<div class="top-navbar-left">
|
||
<button id="sidebarToggle" class="sidebar-toggle-btn" aria-label="Toggle sidebar">
|
||
<span>☰</span>
|
||
</button>
|
||
<div class="top-navbar-logo">
|
||
<h1>🐳 Census</h1>
|
||
<span id="versionBadge" class="version-badge">v0.0.0</span>
|
||
</div>
|
||
</div>
|
||
<div class="top-navbar-right">
|
||
<button id="scanBtn" class="top-navbar-icon-btn" title="Trigger Scan" aria-label="Trigger Scan">
|
||
<span id="scanBtnIcon">🔄</span>
|
||
</button>
|
||
<button id="submitTelemetryBtn" class="top-navbar-icon-btn" title="Submit Telemetry" aria-label="Submit Telemetry">
|
||
<span>📡</span>
|
||
</button>
|
||
<div class="help-menu-container">
|
||
<button id="helpMenuBtn" class="top-navbar-icon-btn" title="Help" aria-label="Help">
|
||
<span>❓</span>
|
||
</button>
|
||
<div id="helpDropdown" class="help-dropdown">
|
||
<button class="help-menu-item" onclick="startOnboardingTour()">
|
||
<span>🎓</span> Take Tour
|
||
</button>
|
||
<button class="help-menu-item" onclick="showChangelogModal()">
|
||
<span>📋</span> What's New
|
||
</button>
|
||
<a href="https://github.com/selfhosters-cc/container-census" target="_blank" class="help-menu-item">
|
||
<span>📚</span> Documentation
|
||
</a>
|
||
<a href="https://github.com/selfhosters-cc/container-census/issues" target="_blank" class="help-menu-item">
|
||
<span>💬</span> Give Feedback
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<button class="top-navbar-icon-btn" onclick="switchTab('settings', true)" title="Settings (S)" aria-label="Settings">
|
||
<span>⚙️</span>
|
||
</button>
|
||
<div class="notification-bell-container">
|
||
<button id="notificationBell" class="notification-bell" aria-label="Notifications">
|
||
<span>🔔</span>
|
||
<span id="notificationBadge" class="notification-count-badge"></span>
|
||
</button>
|
||
<div id="notificationDropdown" class="notification-dropdown">
|
||
<div class="notification-dropdown-header">
|
||
<h4>Notifications</h4>
|
||
<button id="markAllRead" class="btn-text">Mark all read</button>
|
||
</div>
|
||
<div id="notificationList" class="notification-list">
|
||
<div class="notification-empty">No notifications</div>
|
||
</div>
|
||
<div class="notification-dropdown-footer">
|
||
<button id="viewAllNotifications" class="btn-text">View all</button>
|
||
<button id="clearNotifications" class="btn-text">Clear all</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="app-layout">
|
||
<!-- Sidebar -->
|
||
<aside class="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="sidebar-logo">
|
||
Container Census
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quick Stats in Sidebar -->
|
||
<div class="sidebar-stats">
|
||
<div class="sidebar-stat-item">
|
||
<span class="sidebar-stat-icon">🖥️</span>
|
||
<div class="sidebar-stat-content">
|
||
<div class="sidebar-stat-value" id="totalHosts">-</div>
|
||
<div class="sidebar-stat-label">Hosts</div>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-stat-item">
|
||
<span class="sidebar-stat-icon">📦</span>
|
||
<div class="sidebar-stat-content">
|
||
<div class="sidebar-stat-value" id="totalContainers">-</div>
|
||
<div class="sidebar-stat-label">Total</div>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-stat-item">
|
||
<span class="sidebar-stat-icon">✅</span>
|
||
<div class="sidebar-stat-content">
|
||
<div class="sidebar-stat-value" id="runningContainers">-</div>
|
||
<div class="sidebar-stat-label">Running</div>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-stat-item">
|
||
<span class="sidebar-stat-icon">🕐</span>
|
||
<div class="sidebar-stat-content">
|
||
<div class="sidebar-stat-value" id="lastScan">-</div>
|
||
<div class="sidebar-stat-label">Last Scan</div>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-stat-item clickable" onclick="switchTab('security', true)" title="Click to view Security tab">
|
||
<span class="sidebar-stat-icon">🚨</span>
|
||
<div class="sidebar-stat-content">
|
||
<div class="sidebar-stat-value" id="criticalVulns">-</div>
|
||
<div class="sidebar-stat-label">Critical Vulns</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Navigation -->
|
||
<nav class="sidebar-nav">
|
||
<button class="nav-item active" data-tab="containers" data-shortcut="1">
|
||
<span class="nav-icon">📦</span>
|
||
<span class="nav-label">Containers</span>
|
||
<span class="nav-badge" id="containersBadge"></span>
|
||
<span class="nav-shortcut">1</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="monitoring" data-shortcut="2">
|
||
<span class="nav-icon">📊</span>
|
||
<span class="nav-label">Monitoring</span>
|
||
<span class="nav-badge" id="monitoringBadge"></span>
|
||
<span class="nav-shortcut">2</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="images" data-shortcut="3">
|
||
<span class="nav-icon">🖼️</span>
|
||
<span class="nav-label">Images</span>
|
||
<span class="nav-badge" id="imagesBadge"></span>
|
||
<span class="nav-shortcut">3</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="security" data-shortcut="4">
|
||
<span class="nav-icon">🛡️</span>
|
||
<span class="nav-label">Security</span>
|
||
<span class="nav-badge" id="securityBadge"></span>
|
||
<span class="nav-shortcut">4</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="graph" data-shortcut="5">
|
||
<span class="nav-icon">🕸️</span>
|
||
<span class="nav-label">Graph</span>
|
||
<span class="nav-shortcut">5</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="hosts" data-shortcut="6">
|
||
<span class="nav-icon">🖥️</span>
|
||
<span class="nav-label">Hosts</span>
|
||
<span class="nav-badge" id="hostsBadge"></span>
|
||
<span class="nav-shortcut">6</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="history" data-shortcut="7">
|
||
<span class="nav-icon">📜</span>
|
||
<span class="nav-label">History</span>
|
||
<span class="nav-shortcut">7</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="activity" data-shortcut="8">
|
||
<span class="nav-icon">📋</span>
|
||
<span class="nav-label">Activity Log</span>
|
||
<span class="nav-badge" id="activityBadge"></span>
|
||
<span class="nav-shortcut">8</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="reports" data-shortcut="9">
|
||
<span class="nav-icon">📈</span>
|
||
<span class="nav-label">Reports</span>
|
||
<span class="nav-shortcut">9</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="notifications" data-shortcut="0">
|
||
<span class="nav-icon">🔔</span>
|
||
<span class="nav-label">Notifications</span>
|
||
<span class="nav-badge" id="notificationsSidebarBadge"></span>
|
||
<span class="nav-shortcut">0</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- Actions -->
|
||
<div class="sidebar-actions">
|
||
<label class="auto-refresh-toggle">
|
||
<input type="checkbox" id="autoRefresh" checked>
|
||
<span>Auto-refresh (30s)</span>
|
||
</label>
|
||
<div id="telemetrySchedule" class="telemetry-schedule"></div>
|
||
<div id="lastUpdated" class="last-updated"></div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Main Content -->
|
||
<main class="main-content">
|
||
|
||
<div class="filters" id="filtersBar">
|
||
<input type="text" id="searchInput" placeholder="Search..." class="search-input">
|
||
<select id="hostFilter" class="filter-select">
|
||
<option value="">All Hosts</option>
|
||
</select>
|
||
<select id="stateFilter" class="filter-select" style="display: none;">
|
||
<option value="">All States</option>
|
||
<option value="running">Running</option>
|
||
<option value="exited">Exited</option>
|
||
<option value="paused">Paused</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div id="containersTab" class="tab-content active">
|
||
<div class="containers-section">
|
||
<h2>Containers</h2>
|
||
<div id="containersBody" class="containers-cards-container">
|
||
<div class="loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="monitoringTab" class="tab-content">
|
||
<div class="monitoring-section">
|
||
<h2>Resource Monitoring</h2>
|
||
<div class="monitoring-filters">
|
||
<select id="monitoringHostFilter" class="filter-select">
|
||
<option value="">All Hosts</option>
|
||
</select>
|
||
</div>
|
||
<div id="monitoringGrid" class="monitoring-grid">
|
||
<div class="loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="imagesTab" class="tab-content">
|
||
<div class="images-section">
|
||
<h2>Images</h2>
|
||
<div id="imagesTable" class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Host</th>
|
||
<th>Repository</th>
|
||
<th>Tag</th>
|
||
<th>Image ID</th>
|
||
<th>Size</th>
|
||
<th>Created</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="imagesBody">
|
||
<tr>
|
||
<td colspan="7" class="loading">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="securityTab" class="tab-content">
|
||
<div class="security-section-modern">
|
||
<div class="security-header-modern">
|
||
<div class="security-title-group">
|
||
<h2>🛡️ Vulnerability Scanner</h2>
|
||
<p class="security-subtitle">Monitor and track security vulnerabilities across all container images</p>
|
||
</div>
|
||
<div class="security-actions">
|
||
<button id="scanAllImagesBtn" class="btn btn-primary">
|
||
🔄 Scan All Images
|
||
</button>
|
||
<button id="updateTrivyDBBtn" class="btn btn-secondary">
|
||
📥 Update Database
|
||
</button>
|
||
<button id="vulnerabilitySettingsBtn" class="btn btn-secondary">
|
||
⚙️ Settings
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="security-summary-grid">
|
||
<div class="security-summary-card-modern">
|
||
<div class="card-header">
|
||
<span class="card-icon">📊</span>
|
||
<span class="card-label">Total Scanned</span>
|
||
</div>
|
||
<div class="card-value" id="totalScannedImages">-</div>
|
||
<div class="card-footer">images analyzed</div>
|
||
</div>
|
||
<div class="security-summary-card-modern critical-card">
|
||
<div class="card-header">
|
||
<span class="card-icon">🚨</span>
|
||
<span class="card-label">Critical</span>
|
||
</div>
|
||
<div class="card-value" id="totalCriticalVulns">-</div>
|
||
<div class="card-footer">vulnerabilities</div>
|
||
</div>
|
||
<div class="security-summary-card-modern high-card">
|
||
<div class="card-header">
|
||
<span class="card-icon">⚠️</span>
|
||
<span class="card-label">High</span>
|
||
</div>
|
||
<div class="card-value" id="totalHighVulns">-</div>
|
||
<div class="card-footer">vulnerabilities</div>
|
||
</div>
|
||
<div class="security-summary-card-modern">
|
||
<div class="card-header">
|
||
<span class="card-icon">🛡️</span>
|
||
<span class="card-label">At Risk</span>
|
||
</div>
|
||
<div class="card-value" id="atRiskImages">-</div>
|
||
<div class="card-footer">images affected</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="security-queue-status" id="securityQueueStatus" style="display: none;">
|
||
<div class="queue-status-icon">⏳</div>
|
||
<div class="queue-status-content">
|
||
<strong>Scanning in progress:</strong>
|
||
<span id="queueStatusText">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="security-charts-grid">
|
||
<div class="security-chart-card">
|
||
<div class="chart-card-header">
|
||
<h3>Severity Distribution</h3>
|
||
<p class="chart-subtitle">Current vulnerability breakdown</p>
|
||
</div>
|
||
<div class="security-chart-container">
|
||
<canvas id="vulnerabilitySeverityChart"></canvas>
|
||
</div>
|
||
</div>
|
||
<div class="security-chart-card">
|
||
<div class="chart-card-header">
|
||
<h3>Vulnerability Trends</h3>
|
||
<p class="chart-subtitle">Changes over time (last 30 days)</p>
|
||
</div>
|
||
<div class="security-chart-container">
|
||
<canvas id="vulnerabilityTrendsChart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="security-table-card">
|
||
<div class="security-table-header-modern">
|
||
<div class="table-title-group">
|
||
<h3>Vulnerability Scans</h3>
|
||
<span class="scan-count" id="scanCountBadge">0 scans</span>
|
||
</div>
|
||
<div class="security-filters-modern">
|
||
<select id="securitySeverityFilter" class="filter-select">
|
||
<option value="">All Severities</option>
|
||
<option value="critical">Critical</option>
|
||
<option value="high">High</option>
|
||
<option value="medium">Medium</option>
|
||
<option value="low">Low</option>
|
||
<option value="clean">Clean</option>
|
||
</select>
|
||
<select id="securityStatusFilter" class="filter-select">
|
||
<option value="">All Status</option>
|
||
<option value="scanned">Scanned Only</option>
|
||
<option value="remote">Remote Only</option>
|
||
<option value="failed">Failed Only</option>
|
||
</select>
|
||
<input type="text" id="securitySearchInput" class="search-input" placeholder="🔍 Search images...">
|
||
</div>
|
||
</div>
|
||
<div class="table-container">
|
||
<table class="security-table-modern">
|
||
<thead>
|
||
<tr>
|
||
<th>Image Name</th>
|
||
<th>Status</th>
|
||
<th>Total</th>
|
||
<th>Critical</th>
|
||
<th>High</th>
|
||
<th>Medium</th>
|
||
<th>Low</th>
|
||
<th>Scanned</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="securityScansBody">
|
||
<tr>
|
||
<td colspan="8" class="loading">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="graphTab" class="tab-content">
|
||
<div class="graph-section">
|
||
<div class="graph-header">
|
||
<h2>Container Connection Graph</h2>
|
||
<div class="graph-toolbar">
|
||
<div class="graph-search">
|
||
<input type="text" id="graphSearch" class="search-input" placeholder="Search containers...">
|
||
</div>
|
||
<div class="graph-selectors">
|
||
<select id="composeProjectSelect" class="filter-select">
|
||
<option value="">Compose: All Projects</option>
|
||
</select>
|
||
<select id="networkSelect" class="filter-select">
|
||
<option value="">Networks: Show All</option>
|
||
</select>
|
||
<select id="layoutSelect" class="filter-select">
|
||
<option value="cose">Layout: Force-Directed</option>
|
||
<option value="dagre">Layout: Hierarchical</option>
|
||
<option value="circle">Layout: Circular</option>
|
||
<option value="grid">Layout: Grid</option>
|
||
<option value="concentric">Layout: Concentric</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="graph-controls-row">
|
||
<div class="graph-filters">
|
||
<label class="filter-checkbox">
|
||
<input type="checkbox" id="showNetworks" checked>
|
||
<span class="legend-color" style="background: #3498db;"></span>
|
||
Networks <span class="edge-count" id="networkCount">(0)</span>
|
||
</label>
|
||
<label class="filter-checkbox">
|
||
<input type="checkbox" id="showVolumes" checked>
|
||
<span class="legend-color" style="background: #e74c3c;"></span>
|
||
Volumes <span class="edge-count" id="volumeCount">(0)</span>
|
||
</label>
|
||
<label class="filter-checkbox">
|
||
<input type="checkbox" id="showDepends" checked>
|
||
<span class="legend-color legend-arrow" style="background: #16a085;">→</span>
|
||
Dependencies <span class="edge-count" id="dependsCount">(0)</span>
|
||
</label>
|
||
<label class="filter-checkbox">
|
||
<input type="checkbox" id="showLinks" checked>
|
||
<span class="legend-color" style="background: #9b59b6;"></span>
|
||
Links <span class="edge-count" id="linksCount">(0)</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="graph-display-options">
|
||
<label class="filter-checkbox">
|
||
<input type="checkbox" id="colorByProject">
|
||
🎨 Color by Project
|
||
</label>
|
||
<label class="filter-checkbox">
|
||
<input type="checkbox" id="hideEdgeLabels">
|
||
🏷️ Hide Edge Labels
|
||
</label>
|
||
</div>
|
||
|
||
<div class="graph-zoom-controls">
|
||
<button id="zoomOutBtn" class="btn btn-sm btn-secondary" title="Zoom Out">−</button>
|
||
<button id="zoomResetBtn" class="btn btn-sm btn-secondary" title="Reset Zoom">⊙</button>
|
||
<button id="zoomInBtn" class="btn btn-sm btn-secondary" title="Zoom In">+</button>
|
||
<button id="fitGraphBtn" class="btn btn-sm btn-secondary" title="Fit to Screen">⛶</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="graphContainer" class="graph-container">
|
||
<div class="graph-loading">Loading graph...</div>
|
||
</div>
|
||
<div class="graph-info" id="graphInfo">
|
||
<p>Click on containers or connections to see details</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="hostsTab" class="tab-content">
|
||
<div class="hosts-section">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||
<h2 style="margin: 0;">Configured Hosts</h2>
|
||
<button id="addAgentBtn" class="btn btn-success">+ Add Agent Host</button>
|
||
</div>
|
||
<div id="hostsTable" class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Name</th>
|
||
<th>Type</th>
|
||
<th>Address</th>
|
||
<th>Status</th>
|
||
<th>Stats Collection</th>
|
||
<th>Description</th>
|
||
<th>Last Seen</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="hostsBody">
|
||
<tr>
|
||
<td colspan="8" class="loading">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="historyTab" class="tab-content">
|
||
<div class="history-section">
|
||
<h2 style="margin-bottom: 20px;">📜 Container Lifecycle History</h2>
|
||
<div class="history-stats" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-bottom: 20px;">
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="historyTotalContainers">-</div>
|
||
<div class="stat-label">Total Tracked</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="historyActiveContainers">-</div>
|
||
<div class="stat-label">Currently Active</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="historyInactiveContainers">-</div>
|
||
<div class="stat-label">Inactive</div>
|
||
</div>
|
||
</div>
|
||
<div id="historyBody" class="history-cards-container">
|
||
<div class="loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="activityTab" class="tab-content">
|
||
<div class="activity-log-section">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||
<h2 style="margin: 0;">Activity Log</h2>
|
||
<select id="activityTypeFilter" class="filter-select">
|
||
<option value="all">All Activities</option>
|
||
<option value="scan">Scans Only</option>
|
||
<option value="telemetry">Telemetry Only</option>
|
||
</select>
|
||
</div>
|
||
<div id="activityLogTable" class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Type</th>
|
||
<th>Target</th>
|
||
<th>Time</th>
|
||
<th>Duration</th>
|
||
<th>Status</th>
|
||
<th>Details</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="activityLogBody">
|
||
<tr>
|
||
<td colspan="6" class="loading">Loading...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="reportsTab" class="tab-content">
|
||
<div class="reports-section">
|
||
<h2>📈 Environment Changes Report</h2>
|
||
|
||
<!-- Report Filters -->
|
||
<div class="report-filters">
|
||
<div class="filter-group">
|
||
<label for="reportStartDate">Start Date:</label>
|
||
<input type="datetime-local" id="reportStartDate" class="filter-input">
|
||
</div>
|
||
<div class="filter-group">
|
||
<label for="reportEndDate">End Date:</label>
|
||
<input type="datetime-local" id="reportEndDate" class="filter-input">
|
||
</div>
|
||
<div class="filter-group">
|
||
<label for="reportHostFilter">Host:</label>
|
||
<select id="reportHostFilter" class="filter-select">
|
||
<option value="">All Hosts</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label> </label>
|
||
<div style="display: flex; gap: 10px;">
|
||
<button id="report7d" class="btn btn-sm btn-secondary">Last 7 Days</button>
|
||
<button id="report30d" class="btn btn-sm btn-secondary">Last 30 Days</button>
|
||
<button id="report90d" class="btn btn-sm btn-secondary">Last 90 Days</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label> </label>
|
||
<button id="generateReportBtn" class="btn btn-primary">Generate Report</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Report Loading -->
|
||
<div id="reportLoading" class="loading" style="display: none;">Generating report...</div>
|
||
|
||
<!-- Report Results -->
|
||
<div id="reportResults" style="display: none;">
|
||
<!-- Summary Cards -->
|
||
<div class="stats-grid" id="reportSummaryCards">
|
||
<!-- Cards will be injected by JavaScript -->
|
||
</div>
|
||
|
||
<!-- Timeline Chart -->
|
||
<div class="card" style="margin-top: 20px;">
|
||
<h3>Changes Timeline</h3>
|
||
<canvas id="changesTimelineChart" style="max-height: 300px;"></canvas>
|
||
</div>
|
||
|
||
<!-- Changes Details -->
|
||
<div class="report-details">
|
||
<!-- New Containers -->
|
||
<div class="card collapsible" style="margin-top: 20px;">
|
||
<div class="card-header" onclick="toggleReportSection('newContainers')">
|
||
<h3>🆕 New Containers (<span id="newContainersCount">0</span>)</h3>
|
||
<span class="collapse-icon">▼</span>
|
||
</div>
|
||
<div id="newContainersSection" class="card-body" style="display: none;">
|
||
<div id="newContainersTable"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Removed Containers -->
|
||
<div class="card collapsible" style="margin-top: 20px;">
|
||
<div class="card-header" onclick="toggleReportSection('removedContainers')">
|
||
<h3>❌ Removed Containers (<span id="removedContainersCount">0</span>)</h3>
|
||
<span class="collapse-icon">▼</span>
|
||
</div>
|
||
<div id="removedContainersSection" class="card-body" style="display: none;">
|
||
<div id="removedContainersTable"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Image Updates -->
|
||
<div class="card collapsible" style="margin-top: 20px;">
|
||
<div class="card-header" onclick="toggleReportSection('imageUpdates')">
|
||
<h3>🔄 Image Updates (<span id="imageUpdatesCount">0</span>)</h3>
|
||
<span class="collapse-icon">▼</span>
|
||
</div>
|
||
<div id="imageUpdatesSection" class="card-body" style="display: none;">
|
||
<div id="imageUpdatesTable"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- State Changes -->
|
||
<div class="card collapsible" style="margin-top: 20px;">
|
||
<div class="card-header" onclick="toggleReportSection('stateChanges')">
|
||
<h3>🔀 State Changes (<span id="stateChangesCount">0</span>)</h3>
|
||
<span class="collapse-icon">▼</span>
|
||
</div>
|
||
<div id="stateChangesSection" class="card-body" style="display: none;">
|
||
<div id="stateChangesTable"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Top Restarted -->
|
||
<div class="card collapsible" style="margin-top: 20px;">
|
||
<div class="card-header" onclick="toggleReportSection('topRestarted')">
|
||
<h3>🔁 Most Active Containers (<span id="topRestartedCount">0</span>)</h3>
|
||
<span class="collapse-icon">▼</span>
|
||
</div>
|
||
<div id="topRestartedSection" class="card-body" style="display: none;">
|
||
<div id="topRestartedTable"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Export Button -->
|
||
<div style="margin-top: 20px; text-align: right;">
|
||
<button id="exportReportBtn" class="btn btn-secondary">📥 Export Report (JSON)</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Empty State -->
|
||
<div id="reportEmptyState" class="empty-state">
|
||
<p>Select a time range and click "Generate Report" to see environment changes.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="notificationsTab" class="tab-content">
|
||
<div class="notifications-section">
|
||
<h2>📬 Notification Center</h2>
|
||
|
||
<!-- Notification Tabs -->
|
||
<div class="notification-tabs">
|
||
<button class="notification-tab-btn active" data-notif-tab="inbox">Inbox</button>
|
||
<button class="notification-tab-btn" data-notif-tab="channels">Channels</button>
|
||
<button class="notification-tab-btn" data-notif-tab="rules">Rules</button>
|
||
<button class="notification-tab-btn" data-notif-tab="silences">Silences</button>
|
||
</div>
|
||
|
||
<!-- Inbox Tab -->
|
||
<div id="inboxNotifTab" class="notif-tab-content active">
|
||
<div class="notification-inbox-header">
|
||
<div class="notification-filters">
|
||
<button id="filterAll" class="notif-filter-btn active">All</button>
|
||
<button id="filterUnread" class="notif-filter-btn">Unread</button>
|
||
</div>
|
||
<div class="notification-actions">
|
||
<button id="markAllReadBtn" class="btn btn-sm btn-secondary">Mark All Read</button>
|
||
<button id="clearAllNotificationsBtn" class="btn btn-sm btn-warning">Clear All</button>
|
||
</div>
|
||
</div>
|
||
<div id="notificationInboxList" class="notification-inbox-list">
|
||
<div class="loading">Loading notifications...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Channels Tab -->
|
||
<div id="channelsNotifTab" class="notif-tab-content">
|
||
<div class="channels-header">
|
||
<button id="addChannelBtn" class="btn btn-primary">+ Add Channel</button>
|
||
</div>
|
||
<div id="channelsList" class="channels-list">
|
||
<div class="loading">Loading channels...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rules Tab -->
|
||
<div id="rulesNotifTab" class="notif-tab-content">
|
||
<div class="rules-header">
|
||
<button id="addRuleBtn" class="btn btn-primary">+ Add Rule</button>
|
||
</div>
|
||
<div id="rulesList" class="rules-list">
|
||
<div class="loading">Loading rules...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Silences Tab -->
|
||
<div id="silencesNotifTab" class="notif-tab-content">
|
||
<div class="silences-header">
|
||
<button id="addSilenceBtn" class="btn btn-primary">+ Add Silence</button>
|
||
</div>
|
||
<div id="silencesList" class="silences-list">
|
||
<div class="loading">Loading silences...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settingsTab" class="tab-content">
|
||
<div class="settings-section">
|
||
<h2>Settings</h2>
|
||
|
||
<div class="settings-card">
|
||
<h3>🔍 Scanner Configuration</h3>
|
||
<p class="settings-description">
|
||
Configure how frequently Container Census scans your hosts for container information and resource usage.
|
||
</p>
|
||
<div class="alert alert-info" style="margin-bottom: 15px; padding: 12px; background: #e3f2fd; border: 1px solid #90caf9; border-radius: 4px; font-size: 14px;">
|
||
<strong>ℹ️ Note:</strong> Lower intervals provide more real-time data but increase CPU/disk usage. Higher intervals reduce overhead but provide less granular data.
|
||
</div>
|
||
|
||
<div class="frequency-group" style="margin-bottom: 20px;">
|
||
<label for="scanInterval" class="frequency-label">Scan Interval:</label>
|
||
<select id="scanInterval" class="frequency-select">
|
||
<option value="60">Every 1 minute</option>
|
||
<option value="120">Every 2 minutes</option>
|
||
<option value="300">Every 5 minutes (recommended)</option>
|
||
<option value="600">Every 10 minutes</option>
|
||
<option value="900">Every 15 minutes</option>
|
||
</select>
|
||
<button onclick="saveScanInterval()" class="btn btn-primary" style="margin-left: 10px;">Save Interval</button>
|
||
<span id="scanIntervalSaveStatus" class="save-status-inline"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-card">
|
||
<h3>📊 Telemetry Collectors</h3>
|
||
<p class="settings-description">
|
||
Configure telemetry endpoints to track anonymous container usage statistics.
|
||
You can enable the community collector to help improve Container Census, or add your own private collectors.
|
||
</p>
|
||
<div class="alert alert-info" style="margin-bottom: 15px; padding: 12px; background: #e3f2fd; border: 1px solid #90caf9; border-radius: 4px; font-size: 14px;">
|
||
<strong>ℹ️ Note:</strong> Ensure <code>./config</code> volume is mounted in docker-compose.yml for settings to persist across restarts.
|
||
</div>
|
||
|
||
<div class="frequency-group" style="margin-bottom: 20px;">
|
||
<label for="telemetryFrequency" class="frequency-label">Submission Frequency (applies to all collectors):</label>
|
||
<select id="telemetryFrequency" class="frequency-select">
|
||
<option value="24">Daily</option>
|
||
<option value="168">Weekly (recommended)</option>
|
||
<option value="336">Every 2 weeks</option>
|
||
<option value="720">Monthly</option>
|
||
</select>
|
||
<button onclick="saveTelemetryFrequency()" class="btn btn-primary" style="margin-left: 10px;">Save Frequency</button>
|
||
<span id="frequencySaveStatus" class="save-status-inline"></span>
|
||
</div>
|
||
|
||
<div class="custom-collectors">
|
||
<div id="collectorsList"></div>
|
||
|
||
<div class="add-collector-form" style="margin-top: 20px;">
|
||
<h4>Add New Collector</h4>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="collectorName">Name:</label>
|
||
<input type="text" id="collectorName" placeholder="e.g., My Private Collector" class="form-input">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="collectorURL">URL:</label>
|
||
<input type="url" id="collectorURL" placeholder="https://telemetry.example.com/api/ingest" class="form-input">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="collectorAPIKey">API Key (optional):</label>
|
||
<input type="text" id="collectorAPIKey" placeholder="Optional authentication key" class="form-input">
|
||
</div>
|
||
<div class="form-group checkbox-group" style="margin-top: 28px;">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="collectorEnabled" class="checkbox-input" checked>
|
||
<span class="checkbox-text">Enabled</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<button onclick="testCollector()" class="btn btn-secondary" style="margin-right: 10px;">Test Connection</button>
|
||
<button onclick="addCollector()" class="btn btn-primary">Add Collector</button>
|
||
<span id="collectorSaveStatus" class="save-status-inline"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Log Viewer Modal -->
|
||
<div id="logModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Container Logs: <span id="logContainerName"></span></h3>
|
||
<button class="close-btn" onclick="closeLogModal()">×</button>
|
||
</div>
|
||
<div class="log-search-bar">
|
||
<input type="text" id="logSearchInput" placeholder="Search logs..." class="log-search-input">
|
||
<button class="btn btn-sm" onclick="searchLogs('prev')" title="Previous match">↑</button>
|
||
<button class="btn btn-sm" onclick="searchLogs('next')" title="Next match">↓</button>
|
||
<span id="logSearchStatus" class="log-search-status"></span>
|
||
<button class="btn btn-sm" onclick="clearLogSearch()" title="Clear search">✕</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<pre id="logContent" class="log-content">Loading...</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Confirmation Modal -->
|
||
<div id="confirmModal" class="modal">
|
||
<div class="modal-content modal-small">
|
||
<div class="modal-header">
|
||
<h3 id="confirmTitle">Confirm Action</h3>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="confirmMessage"></p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="confirmCancelBtn" class="btn btn-secondary">Cancel</button>
|
||
<button id="confirmOkBtn" class="btn btn-danger">Confirm</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Agent Host Modal -->
|
||
<div id="addAgentModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>Add Agent Host</h2>
|
||
<span class="modal-close" id="closeAddAgent">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addAgentForm">
|
||
<div class="form-group">
|
||
<label for="agentName">Host Name *</label>
|
||
<input type="text" id="agentName" required placeholder="e.g., production-server">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="agentAddress">Agent URL *</label>
|
||
<input type="text" id="agentAddress" required
|
||
pattern="^(https?|agent)://[a-zA-Z0-9\.\-]+(:[0-9]+)?$"
|
||
placeholder="e.g., http://192.168.1.100:9876 or agent://host:9876"
|
||
title="Must be in format: http://host:port, https://host:port, or agent://host:port">
|
||
<small>The URL where the agent is running (with port). Formats: http://, https://, or agent://</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="agentToken">Agent Token *</label>
|
||
<input type="text" id="agentToken" required placeholder="Agent API token">
|
||
<small>The API token generated when starting the agent</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="agentDescription">Description</label>
|
||
<input type="text" id="agentDescription" placeholder="Optional description">
|
||
</div>
|
||
<div class="form-group">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="checkbox" id="agentCollectStats" style="margin-right: 8px; cursor: pointer;">
|
||
<span>Enable CPU/Memory Stats Collection</span>
|
||
</label>
|
||
<small>Collect resource usage statistics during scans (requires Docker API access)</small>
|
||
</div>
|
||
<div id="agentTestResult" class="alert" style="display: none;"></div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" id="testAgentBtn" class="btn btn-secondary">Test Connection</button>
|
||
<button type="button" id="cancelAgentBtn" class="btn btn-secondary">Cancel</button>
|
||
<button type="submit" form="addAgentForm" id="saveAgentBtn" class="btn btn-primary">Add Agent</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Container Timeline Modal -->
|
||
<div id="timelineModal" class="modal">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>📅 Lifecycle Timeline: <span id="timelineContainerName"></span></h3>
|
||
<button class="close-btn" onclick="closeTimelineModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="timelineContent" class="timeline-content">
|
||
<div class="loading">Loading timeline...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats Modal -->
|
||
<div id="statsModal" class="modal">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>📊 Resource Stats: <span id="statsContainerName"></span></h3>
|
||
<button class="close-btn" onclick="closeStatsModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="stats-time-range">
|
||
<button class="stats-range-btn active" data-range="1h">1 Hour</button>
|
||
<button class="stats-range-btn" data-range="24h">24 Hours</button>
|
||
<button class="stats-range-btn" data-range="7d">7 Days</button>
|
||
<button class="stats-range-btn" data-range="all">All Time</button>
|
||
</div>
|
||
<div id="statsContent" class="stats-content">
|
||
<div id="statsMessage" class="loading" style="display: none;"></div>
|
||
<div id="statsChartArea" style="display: none;">
|
||
<div class="stats-summary">
|
||
<div class="stat-box">
|
||
<div class="stat-label">Avg CPU</div>
|
||
<div class="stat-value" id="avgCpu">-</div>
|
||
</div>
|
||
<div class="stat-box">
|
||
<div class="stat-label">Max CPU</div>
|
||
<div class="stat-value" id="maxCpu">-</div>
|
||
</div>
|
||
<div class="stat-box">
|
||
<div class="stat-label">Avg Memory</div>
|
||
<div class="stat-value" id="avgMemory">-</div>
|
||
</div>
|
||
<div class="stat-box">
|
||
<div class="stat-label">Max Memory</div>
|
||
<div class="stat-value" id="maxMemory">-</div>
|
||
</div>
|
||
</div>
|
||
<div class="stats-charts">
|
||
<div class="chart-container">
|
||
<canvas id="cpuChart"></canvas>
|
||
</div>
|
||
<div class="chart-container">
|
||
<canvas id="memoryChart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Channel Modal -->
|
||
<div id="addChannelModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Add Notification Channel</h3>
|
||
<button class="close-btn" onclick="closeAddChannelModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addChannelForm">
|
||
<div class="form-group">
|
||
<label for="channelName">Channel Name *</label>
|
||
<input type="text" id="channelName" required placeholder="e.g., Production Webhook">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="channelType">Channel Type *</label>
|
||
<select id="channelType" required>
|
||
<option value="">Select type...</option>
|
||
<option value="webhook">Webhook</option>
|
||
<option value="ntfy">Ntfy</option>
|
||
<option value="in_app">In-App (Default)</option>
|
||
</select>
|
||
</div>
|
||
<div id="webhookConfig" class="channel-config" style="display: none;">
|
||
<div class="form-group">
|
||
<label for="webhookURL">Webhook URL *</label>
|
||
<input type="url" id="webhookURL" placeholder="https://example.com/webhook">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="webhookHeaders">Custom Headers (JSON)</label>
|
||
<textarea id="webhookHeaders" placeholder='{"Authorization": "Bearer token"}'></textarea>
|
||
</div>
|
||
</div>
|
||
<div id="ntfyConfig" class="channel-config" style="display: none;">
|
||
<div class="form-group">
|
||
<label for="ntfyServerURL">Ntfy Server URL</label>
|
||
<input type="url" id="ntfyServerURL" placeholder="https://ntfy.sh (default)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="ntfyTopic">Topic *</label>
|
||
<input type="text" id="ntfyTopic" placeholder="my-topic">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="ntfyToken">Auth Token (optional)</label>
|
||
<input type="text" id="ntfyToken" placeholder="Bearer token">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<div class="toggle-switch-container">
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="channelEnabled" checked>
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
<span class="toggle-label" id="channelEnabledLabel">Enabled</span>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" id="testChannelBtn" class="btn btn-secondary">Test Channel</button>
|
||
<button type="button" onclick="closeAddChannelModal()" class="btn btn-secondary">Cancel</button>
|
||
<button type="submit" form="addChannelForm" class="btn btn-primary">Add Channel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Rule Modal -->
|
||
<div id="addRuleModal" class="modal">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>Add Notification Rule</h3>
|
||
<button class="close-btn" onclick="closeAddRuleModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addRuleForm">
|
||
<div class="form-group">
|
||
<label for="ruleName">Rule Name *</label>
|
||
<input type="text" id="ruleName" required placeholder="e.g., High CPU Alert">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Event Types *</label>
|
||
<div class="checkbox-group-vertical">
|
||
<label><input type="checkbox" name="eventTypes" value="new_image"><span>🖼️ New Image</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="state_change"><span>🔄 State Change</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="container_started"><span>▶️ Started</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="container_stopped"><span>⏹️ Stopped</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="container_paused"><span>⏸️ Paused</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="container_resumed"><span>▶️ Resumed</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="high_cpu"><span>📈 High CPU</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="high_memory"><span>💾 High Memory</span></label>
|
||
<label><input type="checkbox" name="eventTypes" value="anomalous_behavior"><span>⚠️ Anomaly</span></label>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="ruleHost">Host (optional)</label>
|
||
<select id="ruleHost">
|
||
<option value="">All Hosts</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="ruleContainerPattern">Container Pattern (optional)</label>
|
||
<input type="text" id="ruleContainerPattern" placeholder="e.g., prod-* or *-db">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="ruleImagePattern">Image Pattern (optional)</label>
|
||
<input type="text" id="ruleImagePattern" placeholder="e.g., nginx:* or postgres:*">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="ruleCPUThreshold">CPU Threshold (%)</label>
|
||
<input type="number" id="ruleCPUThreshold" min="0" max="100" step="0.1" placeholder="e.g., 80">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="ruleMemoryThreshold">Memory Threshold (%)</label>
|
||
<input type="number" id="ruleMemoryThreshold" min="0" max="100" step="0.1" placeholder="e.g., 90">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="ruleThresholdDuration">Threshold Duration (seconds)</label>
|
||
<input type="number" id="ruleThresholdDuration" min="1" value="120">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="ruleCooldown">Cooldown (seconds)</label>
|
||
<input type="number" id="ruleCooldown" min="1" value="300">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="ruleChannels">Channels *</label>
|
||
<select id="ruleChannels" multiple size="5" required>
|
||
<option value="">Loading channels...</option>
|
||
</select>
|
||
<small>Hold Ctrl/Cmd to select multiple channels</small>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<div class="toggle-switch-container">
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="ruleEnabled" checked>
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
<span class="toggle-label" id="ruleEnabledLabel">Enabled</span>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="closeAddRuleModal()" class="btn btn-secondary">Cancel</button>
|
||
<button type="submit" form="addRuleForm" class="btn btn-primary">Add Rule</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Silence Modal -->
|
||
<div id="addSilenceModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Add Notification Silence</h3>
|
||
<button class="close-btn" onclick="closeAddSilenceModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addSilenceForm">
|
||
<div class="form-group">
|
||
<label for="silenceReason">Reason (optional)</label>
|
||
<input type="text" id="silenceReason" placeholder="e.g., Maintenance window">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="silenceHost">Host (optional)</label>
|
||
<select id="silenceHost">
|
||
<option value="">All Hosts</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="silenceContainer">Container ID (optional)</label>
|
||
<input type="text" id="silenceContainer" placeholder="Container ID or leave empty">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="silenceHostPattern">Host Pattern (optional)</label>
|
||
<input type="text" id="silenceHostPattern" placeholder="e.g., prod-*">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="silenceContainerPattern">Container Pattern (optional)</label>
|
||
<input type="text" id="silenceContainerPattern" placeholder="e.g., db-*">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="silenceEndsAt">Expires At</label>
|
||
<input type="datetime-local" id="silenceEndsAt">
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="closeAddSilenceModal()" class="btn btn-secondary">Cancel</button>
|
||
<button type="submit" form="addSilenceForm" class="btn btn-primary">Add Silence</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Vulnerability Settings Modal -->
|
||
<div id="vulnerabilitySettingsModal" class="modal">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>⚙️ Vulnerability Scanner Settings</h3>
|
||
<button class="close-btn" onclick="closeVulnerabilitySettingsModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="vulnerabilitySettingsForm">
|
||
<div class="settings-section">
|
||
<h4>General Settings</h4>
|
||
<div class="form-group">
|
||
<label class="toggle-label">
|
||
<input type="checkbox" id="vulnEnabled">
|
||
<span>Enable Vulnerability Scanning</span>
|
||
</label>
|
||
<small>When enabled, images will be scanned for vulnerabilities using Trivy</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="toggle-label">
|
||
<input type="checkbox" id="vulnAutoScan">
|
||
<span>Auto-scan New Images</span>
|
||
</label>
|
||
<small>Automatically queue new images for scanning when discovered</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h4>Performance Settings</h4>
|
||
<div class="form-group">
|
||
<label for="vulnWorkerPoolSize">Worker Pool Size (1-10)</label>
|
||
<input type="number" id="vulnWorkerPoolSize" min="1" max="10" required>
|
||
<small>Number of concurrent scan workers (more = faster but more CPU)</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="vulnScanTimeout">Scan Timeout (minutes, 1-60)</label>
|
||
<input type="number" id="vulnScanTimeout" min="1" max="60" required>
|
||
<small>Maximum time allowed per scan before timeout</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="vulnMaxQueueSize">Max Queue Size (10-1000)</label>
|
||
<input type="number" id="vulnMaxQueueSize" min="10" max="1000" required>
|
||
<small>Maximum number of images that can be queued for scanning</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h4>Cache & Rescan Settings</h4>
|
||
<div class="form-group">
|
||
<label for="vulnCacheTTL">Cache TTL (hours, 1-168)</label>
|
||
<input type="number" id="vulnCacheTTL" min="1" max="168" required>
|
||
<small>How long to cache scan results before considering them stale</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="vulnRescanInterval">Rescan Interval (hours, 24-720)</label>
|
||
<input type="number" id="vulnRescanInterval" min="24" max="720" required>
|
||
<small>How often to automatically rescan images (168 = weekly)</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="vulnDBUpdateInterval">Database Update Interval (hours, 1-168)</label>
|
||
<input type="number" id="vulnDBUpdateInterval" min="1" max="168" required>
|
||
<small>How often to update the Trivy vulnerability database</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h4>Data Retention</h4>
|
||
<div class="form-group">
|
||
<label for="vulnRetentionDays">Scan Retention (days, 1-365)</label>
|
||
<input type="number" id="vulnRetentionDays" min="1" max="365" required>
|
||
<small>How long to keep scan metadata before cleanup</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="vulnDetailedRetentionDays">Detailed Retention (days, 1-90)</label>
|
||
<input type="number" id="vulnDetailedRetentionDays" min="1" max="90" required>
|
||
<small>How long to keep detailed vulnerability data before cleanup</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h4>Notification Alerts</h4>
|
||
<div class="form-group">
|
||
<label class="toggle-label">
|
||
<input type="checkbox" id="vulnAlertCritical">
|
||
<span>Alert on Critical Vulnerabilities</span>
|
||
</label>
|
||
<small>Send notifications when critical vulnerabilities are found</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="toggle-label">
|
||
<input type="checkbox" id="vulnAlertHigh">
|
||
<span>Alert on High Vulnerabilities</span>
|
||
</label>
|
||
<small>Send notifications when high-severity vulnerabilities are found</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h4>Storage</h4>
|
||
<div class="form-group">
|
||
<label for="vulnCacheDir">Cache Directory</label>
|
||
<input type="text" id="vulnCacheDir" required readonly>
|
||
<small>Directory where Trivy stores cache data (read-only)</small>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="closeVulnerabilitySettingsModal()" class="btn btn-secondary">Cancel</button>
|
||
<button type="submit" form="vulnerabilitySettingsForm" class="btn btn-primary">Save Settings</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Vulnerability Details Modal -->
|
||
<div id="vulnerabilityDetailsModal" class="modal">
|
||
<div class="modal-content large-modal">
|
||
<div class="modal-header">
|
||
<h2>🔍 Vulnerability Details: <span id="vulnDetailsImageName"></span></h2>
|
||
<button class="close-btn" onclick="closeVulnerabilityDetailsModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="vulnDetailsContent" class="loading">Loading vulnerabilities...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Changelog Modal -->
|
||
<div id="changelogModal" class="modal">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>What's New</h3>
|
||
<button class="close-btn" onclick="closeChangelogModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="changelogContent" class="changelog-content loading">Loading changelog...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cytoscape.js for graph visualization -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.29.2/cytoscape.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre@2.5.0/cytoscape-dagre.min.js"></script>
|
||
|
||
<!-- Shepherd.js for onboarding tour -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/css/shepherd.css"/>
|
||
<script src="https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js"></script>
|
||
|
||
<script src="notifications.js?v=5"></script>
|
||
<script src="onboarding.js?v=1"></script>
|
||
<script src="app.js?v=11"></script>
|
||
</body>
|
||
</html>
|