Files
container-census/web/index.html
2025-11-02 18:03:51 -05:00

1325 lines
69 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>&nbsp;</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>&nbsp;</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()">&times;</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">&times;</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()">&times;</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()">&times;</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()">&times;</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()">&times;</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()">&times;</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()">&times;</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()">&times;</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()">&times;</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>