mirror of
https://github.com/selfhosters-cc/container-census.git
synced 2026-05-18 05:08:59 -05:00
8ac9ca8947
Plugin system infrastructure: - Plugin interface with lifecycle management (Init, Start, Stop) - Plugin manager for registration and route mounting - Scoped database access for plugin data/settings - Event bus for plugin communication - Badge providers and container enrichers NPM plugin (Nginx Proxy Manager): - API client with JWT authentication - Instance management (add/edit/delete/test/sync) - Proxy host fetching and container matching - Badge provider for exposed containers - Tab UI with external JS loading Container model updates: - Added NetworkDetails (IP, aliases) for plugin matching - Added StartedAt timestamp for uptime display - Added PluginData map for plugin enrichment Frontend plugin system: - Plugin manager JS for loading tabs and badges - Integrations dropdown in navigation - External script loading with init function callbacks - Container uptime display on cards Note: Plugin tab JS execution has issues - Next.js migration planned. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1772 lines
97 KiB
HTML
1772 lines
97 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="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<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><a href="#" onclick="switchTab('dashboard', true); return false;" style="text-decoration: none; color: inherit;">Census</a></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>
|
||
<button id="logoutBtn" class="top-navbar-icon-btn" onclick="logout()" title="Logout" aria-label="Logout" style="display: none;">
|
||
<span>🚪</span>
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="app-layout">
|
||
<!-- Sidebar -->
|
||
<aside class="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="sidebar-logo" onclick="showTab('dashboard')" style="cursor: pointer;">
|
||
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="dashboard" data-shortcut="1">
|
||
<span class="nav-icon">📊</span>
|
||
<span class="nav-label">Dashboard</span>
|
||
<span class="nav-shortcut">1</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="containers" data-shortcut="2">
|
||
<span class="nav-icon">📦</span>
|
||
<span class="nav-label">Containers</span>
|
||
<span class="nav-badge" id="containersBadge"></span>
|
||
<span class="nav-shortcut">2</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="monitoring" data-shortcut="3">
|
||
<span class="nav-icon">📈</span>
|
||
<span class="nav-label">Monitoring</span>
|
||
<span class="nav-badge" id="monitoringBadge"></span>
|
||
<span class="nav-shortcut">3</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="images" data-shortcut="4">
|
||
<span class="nav-icon">🖼️</span>
|
||
<span class="nav-label">Images</span>
|
||
<span class="nav-badge" id="imagesBadge"></span>
|
||
<span class="nav-shortcut">4</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="security" data-shortcut="5">
|
||
<span class="nav-icon">🛡️</span>
|
||
<span class="nav-label">Security</span>
|
||
<span class="nav-badge" id="securityBadge"></span>
|
||
<span class="nav-shortcut">5</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="graph" data-shortcut="6">
|
||
<span class="nav-icon">🕸️</span>
|
||
<span class="nav-label">Graph</span>
|
||
<span class="nav-shortcut">6</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="hosts" data-shortcut="7">
|
||
<span class="nav-icon">🖥️</span>
|
||
<span class="nav-label">Hosts</span>
|
||
<span class="nav-badge" id="hostsBadge"></span>
|
||
<span class="nav-shortcut">7</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="history" data-shortcut="8">
|
||
<span class="nav-icon">📜</span>
|
||
<span class="nav-label">History</span>
|
||
<span class="nav-shortcut">8</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="activity" data-shortcut="9">
|
||
<span class="nav-icon">📋</span>
|
||
<span class="nav-label">Activity Log</span>
|
||
<span class="nav-badge" id="activityBadge"></span>
|
||
<span class="nav-shortcut">9</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="reports" data-shortcut="0">
|
||
<span class="nav-icon">📊</span>
|
||
<span class="nav-label">Reports</span>
|
||
<span class="nav-shortcut">0</span>
|
||
</button>
|
||
<button class="nav-item" data-tab="notifications" data-shortcut="N">
|
||
<span class="nav-icon">🔔</span>
|
||
<span class="nav-label">Notifications</span>
|
||
<span class="nav-badge" id="notificationsSidebarBadge"></span>
|
||
<span class="nav-shortcut">N</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>
|
||
|
||
<!-- Dashboard Tab (New Default Landing Page) -->
|
||
<div id="dashboardTab" class="tab-content active">
|
||
<div class="dashboard-hero">
|
||
<div class="dashboard-hero-content">
|
||
<h1 class="dashboard-title">
|
||
Container Census Dashboard
|
||
</h1>
|
||
<p class="dashboard-subtitle">Monitor and manage your multi-host Docker infrastructure</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- System Health & Quick Actions (combined, side-by-side on desktop) -->
|
||
<div class="dashboard-section dashboard-top-row" style="margin-top: 0;">
|
||
<div class="dashboard-top-left">
|
||
<h2 class="dashboard-section-title">Quick Actions</h2>
|
||
<div class="dashboard-quick-actions-compact">
|
||
<button id="dashboardScanBtn" class="dashboard-action-card-compact">
|
||
<span class="action-icon">🔄</span>
|
||
<span class="action-label">Scan All Hosts</span>
|
||
</button>
|
||
<button onclick="checkAllUpdates()" class="dashboard-action-card-compact">
|
||
<span class="action-icon">⬆️</span>
|
||
<span class="action-label">Check Updates</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dashboard-top-right">
|
||
<h2 class="dashboard-section-title">System Health</h2>
|
||
<div class="dashboard-metrics-grid">
|
||
<div class="dashboard-metric-card">
|
||
<div class="metric-icon" style="background: linear-gradient(135deg, #667eea, #764ba2);">
|
||
🖥️
|
||
</div>
|
||
<div class="metric-content">
|
||
<div class="metric-value" id="dashTotalHosts">-</div>
|
||
<div class="metric-label">Total Hosts</div>
|
||
<div class="metric-change" id="dashHostsChange"></div>
|
||
</div>
|
||
</div>
|
||
<div class="dashboard-metric-card">
|
||
<div class="metric-icon" style="background: linear-gradient(135deg, #10b981, #34d399);">
|
||
✅
|
||
</div>
|
||
<div class="metric-content">
|
||
<div class="metric-value" id="dashRunningContainers">-</div>
|
||
<div class="metric-label">Running Containers</div>
|
||
<div class="metric-change" id="dashRunningChange"></div>
|
||
</div>
|
||
</div>
|
||
<div class="dashboard-metric-card">
|
||
<div class="metric-icon" style="background: linear-gradient(135deg, #f59e0b, #fbbf24);">
|
||
📦
|
||
</div>
|
||
<div class="metric-content">
|
||
<div class="metric-value" id="dashTotalContainers">-</div>
|
||
<div class="metric-label">Total Containers</div>
|
||
<div class="metric-change" id="dashContainersChange"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Navigation Links -->
|
||
<div class="dashboard-section">
|
||
<h2 class="dashboard-section-title">Navigation</h2>
|
||
<div class="dashboard-quick-actions">
|
||
<button onclick="switchTab('hosts', true)" class="dashboard-action-card">
|
||
<span class="action-icon">🖥️</span>
|
||
<span class="action-label">Manage Hosts</span>
|
||
</button>
|
||
<button onclick="switchTab('containers', true)" class="dashboard-action-card">
|
||
<span class="action-icon">📦</span>
|
||
<span class="action-label">View Containers</span>
|
||
</button>
|
||
<button onclick="switchTab('security', true)" class="dashboard-action-card">
|
||
<span class="action-icon">🛡️</span>
|
||
<span class="action-label">Scan Vulnerabilities</span>
|
||
</button>
|
||
<button onclick="switchTab('graph', true)" class="dashboard-action-card">
|
||
<span class="action-icon">🕸️</span>
|
||
<span class="action-label">Network Graph</span>
|
||
</button>
|
||
<button onclick="switchTab('reports', true)" class="dashboard-action-card">
|
||
<span class="action-icon">📈</span>
|
||
<span class="action-label">Generate Reports</span>
|
||
</button>
|
||
<button onclick="switchTab('notifications', true)" class="dashboard-action-card">
|
||
<span class="action-icon">🔔</span>
|
||
<span class="action-label">Notifications</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Health Status Grid -->
|
||
<div class="dashboard-grid">
|
||
<div class="dashboard-card">
|
||
<h3 class="dashboard-card-title">🖥️ Host Status</h3>
|
||
<div id="dashHostStatus" class="dashboard-card-content">
|
||
<div class="loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dashboard-card">
|
||
<h3 class="dashboard-card-title">📊 Resource Usage</h3>
|
||
<div id="dashResourceStatus" class="dashboard-card-content">
|
||
<div class="loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Activity Timeline -->
|
||
<div class="dashboard-section">
|
||
<h2 class="dashboard-section-title">Recent Activity</h2>
|
||
<div class="dashboard-card">
|
||
<div id="dashRecentActivity" class="dashboard-timeline">
|
||
<div class="loading">Loading recent events...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Telemetry Settings -->
|
||
<div class="dashboard-section">
|
||
<h2 class="dashboard-section-title">📡 Telemetry & Analytics</h2>
|
||
<div class="dashboard-card dashboard-telemetry-card">
|
||
<div class="telemetry-header">
|
||
<div>
|
||
<h3 class="dashboard-card-title-inline">Community Analytics</h3>
|
||
<p class="telemetry-description">Help improve Container Census by sharing anonymous usage statistics</p>
|
||
</div>
|
||
<div class="telemetry-toggle-container">
|
||
<div class="toggle-label-inline">Share Data</div>
|
||
<label class="toggle-switch-modern toggle-switch-large">
|
||
<input type="checkbox" id="dashTelemetryEnabled">
|
||
<span class="toggle-slider-modern"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="telemetry-content" id="dashTelemetryContent">
|
||
<div class="loading">Loading telemetry status...</div>
|
||
</div>
|
||
<div class="telemetry-footer">
|
||
<a href="https://selfhosters.cc/stats" target="_blank" class="btn btn-secondary">
|
||
View Community Dashboard
|
||
</a>
|
||
<button onclick="switchTab('settings', true)" class="btn btn-info">
|
||
Configure Telemetry
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Security Scanning -->
|
||
<div class="dashboard-section">
|
||
<h2 class="dashboard-section-title">🛡️ Security Scanning</h2>
|
||
<div class="dashboard-card dashboard-telemetry-card">
|
||
<div class="telemetry-header">
|
||
<div>
|
||
<h3 class="dashboard-card-title-inline">Vulnerability Scanning</h3>
|
||
<p class="telemetry-description">Scan container images for security vulnerabilities using Trivy</p>
|
||
</div>
|
||
<div class="telemetry-toggle-container">
|
||
<div class="toggle-label-inline">Enable Scanning</div>
|
||
<label class="toggle-switch-modern toggle-switch-large">
|
||
<input type="checkbox" id="dashSecurityEnabled">
|
||
<span class="toggle-slider-modern"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="telemetry-content" id="dashSecurityContent">
|
||
<div class="loading">Loading security status...</div>
|
||
</div>
|
||
<div class="telemetry-footer" id="dashSecurityFooter">
|
||
<button onclick="switchTab('security', true)" class="btn btn-secondary">
|
||
View Security Details
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="containersTab" class="tab-content">
|
||
<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>🎨 User Interface</h3>
|
||
<p class="settings-description">
|
||
Customize the appearance and layout of the container cards to match your preferences.
|
||
</p>
|
||
|
||
<div class="frequency-group" style="margin-bottom: 20px;">
|
||
<label for="cardDesignTheme" class="frequency-label">Container Card Design:</label>
|
||
<select id="cardDesignTheme" class="frequency-select">
|
||
<option value="compact">Compact Metro - Information dense, ideal for many containers</option>
|
||
<option value="material" selected>Spacious Material - Clean & readable (recommended)</option>
|
||
<option value="dashboard">Modern Dashboard - Metrics focused with data visualization</option>
|
||
</select>
|
||
<button onclick="saveCardDesign()" class="btn btn-primary" style="margin-left: 10px;">Save Design</button>
|
||
<span id="cardDesignSaveStatus" class="save-status-inline"></span>
|
||
</div>
|
||
|
||
<div class="alert alert-info" style="padding: 12px; background: #e3f2fd; border: 1px solid #90caf9; border-radius: 4px; font-size: 14px;">
|
||
<strong>ℹ️ Preview:</strong> Changes will apply immediately to the Containers tab after saving.
|
||
</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="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 class="settings-card">
|
||
<h3>⬆️ Image Update Management</h3>
|
||
<p class="settings-description">
|
||
Configure automatic checking for container image updates. Container Census can monitor your :latest tagged containers and notify you when new images are available.
|
||
</p>
|
||
|
||
<div class="alert alert-info" style="margin-bottom: 20px; padding: 12px; background: #e3f2fd; border: 1px solid #90caf9; border-radius: 4px; font-size: 14px;">
|
||
<strong>ℹ️ Note:</strong> Only containers using :latest tags are supported. Updates preserve all container configuration (volumes, environment variables, networks, ports).
|
||
</div>
|
||
|
||
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px; padding: 12px; background: #f8f9fa; border-radius: 4px;">
|
||
<label class="checkbox-label" style="margin: 0;">
|
||
<input type="checkbox" id="autoCheckEnabled" class="checkbox-input">
|
||
<span class="checkbox-text" style="font-weight: 500;">Enable Automatic Update Checking</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="frequency-group" style="margin-bottom: 20px;">
|
||
<label for="checkIntervalHours" class="frequency-label">Check Interval:</label>
|
||
<select id="checkIntervalHours" class="frequency-select">
|
||
<option value="1">Every hour</option>
|
||
<option value="6">Every 6 hours</option>
|
||
<option value="12">Every 12 hours</option>
|
||
<option value="24">Every 24 hours (recommended)</option>
|
||
<option value="48">Every 48 hours</option>
|
||
<option value="168">Weekly</option>
|
||
</select>
|
||
<button onclick="saveImageUpdateSettings()" class="btn btn-primary" style="margin-left: 10px;">Save Settings</button>
|
||
<span id="imageUpdateSaveStatus" class="save-status-inline"></span>
|
||
</div>
|
||
|
||
<div style="display: flex; align-items: center; gap: 10px; padding: 12px; background: #f8f9fa; border-radius: 4px;">
|
||
<label class="checkbox-label" style="margin: 0;">
|
||
<input type="checkbox" id="onlyCheckLatestTags" class="checkbox-input" checked disabled>
|
||
<span class="checkbox-text" style="font-size: 13px; color: var(--text-secondary);">Only check :latest tagged images (required)</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-card">
|
||
<h3>💾 Configuration Backup & Migration</h3>
|
||
<p class="settings-description">
|
||
Export your current settings to a YAML file for backup, or import settings from a YAML file to migrate configurations.
|
||
</p>
|
||
|
||
<div style="display: flex; gap: 15px; margin-top: 20px;">
|
||
<div style="flex: 1;">
|
||
<h4 style="font-size: 14px; margin-bottom: 8px;">📤 Export Settings</h4>
|
||
<p style="font-size: 13px; color: var(--text-secondary); margin-bottom: 10px;">
|
||
Download all current settings as a YAML file for backup or migration to another instance.
|
||
</p>
|
||
<button onclick="exportSettings()" class="btn btn-secondary">
|
||
📥 Download Settings YAML
|
||
</button>
|
||
</div>
|
||
|
||
<div style="flex: 1;">
|
||
<h4 style="font-size: 14px; margin-bottom: 8px;">📥 Import Settings</h4>
|
||
<p style="font-size: 13px; color: var(--text-secondary); margin-bottom: 10px;">
|
||
Upload a YAML configuration file to import settings. This will override current settings.
|
||
</p>
|
||
<input type="file" id="importFileInput" accept=".yaml,.yml" style="display: none;" onchange="handleImportFile(event)">
|
||
<button onclick="document.getElementById('importFileInput').click()" class="btn btn-primary">
|
||
📤 Upload Settings YAML
|
||
</button>
|
||
<span id="importStatus" class="save-status-inline"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-card" style="border: 2px solid #dc3545;">
|
||
<h3 style="color: #dc3545;">⚠️ Danger Zone</h3>
|
||
<p class="settings-description">
|
||
These actions are irreversible and will permanently delete data from the database.
|
||
Use with caution!
|
||
</p>
|
||
|
||
<div class="danger-zone-actions" style="display: grid; gap: 15px; margin-top: 20px;">
|
||
<div class="danger-action" style="padding: 15px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px;">
|
||
<h4 style="margin: 0 0 8px 0; font-size: 14px;">🔄 Reset All Settings</h4>
|
||
<p style="margin: 0 0 10px 0; font-size: 13px; color: #856404;">
|
||
Deletes all system settings from the database and reverts to defaults.
|
||
Settings will be automatically re-imported from config.yaml on next page load if the file exists.
|
||
</p>
|
||
<button onclick="resetAllSettings()" class="btn btn-warning">Reset Settings to Defaults</button>
|
||
</div>
|
||
|
||
<div class="danger-action" style="padding: 15px; background: #f8d7da; border: 1px solid #dc3545; border-radius: 4px;">
|
||
<h4 style="margin: 0 0 8px 0; font-size: 14px;">🗑️ Clear Container History</h4>
|
||
<p style="margin: 0 0 10px 0; font-size: 13px; color: #721c24;">
|
||
Deletes all historical container scan data and statistics (keeps current snapshot).
|
||
This will free up database space but you'll lose historical trends and charts.
|
||
</p>
|
||
<button onclick="clearContainerHistory()" class="btn btn-danger">Clear Container History</button>
|
||
</div>
|
||
|
||
<div class="danger-action" style="padding: 15px; background: #f8d7da; border: 1px solid #dc3545; border-radius: 4px;">
|
||
<h4 style="margin: 0 0 8px 0; font-size: 14px;">🛡️ Clear Vulnerability Scans</h4>
|
||
<p style="margin: 0 0 10px 0; font-size: 13px; color: #721c24;">
|
||
Deletes all vulnerability scan results and detailed CVE data.
|
||
Images will be automatically rescanned on next scheduled scan.
|
||
</p>
|
||
<button onclick="clearVulnerabilities()" class="btn btn-danger">Clear All Vulnerability Data</button>
|
||
</div>
|
||
|
||
<div class="danger-action" style="padding: 15px; background: #f8d7da; border: 1px solid #dc3545; border-radius: 4px;">
|
||
<h4 style="margin: 0 0 8px 0; font-size: 14px;">📋 Clear Activity Log</h4>
|
||
<p style="margin: 0 0 10px 0; font-size: 13px; color: #721c24;">
|
||
Deletes all lifecycle events, container state changes, and activity history.
|
||
New events will be logged as they occur after clearing.
|
||
</p>
|
||
<button onclick="clearActivityLog()" class="btn btn-danger">Clear Activity Log</button>
|
||
</div>
|
||
|
||
<div class="danger-action" style="padding: 15px; background: #f8d7da; border: 1px solid #dc3545; border-radius: 4px;">
|
||
<h4 style="margin: 0 0 8px 0; font-size: 14px;">💀 Nuclear Option: Clear Everything</h4>
|
||
<p style="margin: 0 0 10px 0; font-size: 13px; color: #721c24;">
|
||
<strong>Deletes ALL data:</strong> settings, container history, vulnerabilities, activity log, hosts, and notifications.
|
||
This is like starting fresh with a brand new installation.
|
||
</p>
|
||
<button onclick="nuclearReset()" class="btn btn-danger" style="background-color: #8b0000;">⚠️ RESET EVERYTHING ⚠️</button>
|
||
</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">
|
||
<button class="btn btn-sm btn-primary" onclick="refreshLogs()" title="Refresh logs">🔄 Refresh</button>
|
||
<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-row">
|
||
<div class="form-group" style="flex: 1;">
|
||
<label for="ruleName">Rule Name *</label>
|
||
<input type="text" id="ruleName" required placeholder="e.g., High CPU Alert">
|
||
</div>
|
||
<div class="form-group" style="flex: 0 0 auto; min-width: 140px;">
|
||
<label style="display: block; margin-bottom: 8px;">Status</label>
|
||
<div style="display: inline-flex; align-items: center; gap: 12px;">
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="ruleEnabled" checked onchange="document.getElementById('ruleEnabledText').textContent = this.checked ? 'Enabled' : 'Disabled'">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
<span class="toggle-text-label" id="ruleEnabledText">Enabled</span>
|
||
</div>
|
||
</div>
|
||
</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="image_update_available"><span>🔄 Update Available</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>
|
||
</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>
|
||
|
||
<!-- Update Results Modal -->
|
||
<div id="updateResultsModal" class="modal">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>Container Updates Available</h3>
|
||
<button class="close-btn" onclick="closeUpdateResultsModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Info banner explaining filtering -->
|
||
<div class="alert alert-info" style="margin-bottom: 20px; display: none;" id="updateCheckInfo">
|
||
<strong>ℹ️ Update Check:</strong> Checked <strong><span id="checkedCount">0</span></strong> of <strong><span id="totalCount">0</span></strong> total containers.
|
||
Only containers using <code>:latest</code> tags (or no tag) are checked for updates.
|
||
Containers with specific version tags (e.g., <code>:1.2.3</code>) are skipped.
|
||
</div>
|
||
|
||
<div id="updateResultsContent">
|
||
<!-- Empty state -->
|
||
<div id="noUpdatesFound" class="empty-state" style="display: none;">
|
||
<div style="font-size: 48px; margin-bottom: 10px;">✅</div>
|
||
<h3>All containers are up to date!</h3>
|
||
<p>All <span id="noUpdatesCheckedCount">0</span> checked container(s) with <code>:latest</code> tags are up to date.</p>
|
||
</div>
|
||
|
||
<!-- Updates found -->
|
||
<div id="updatesFound" style="display: none;">
|
||
<div class="updates-header" style="margin-bottom: 15px;">
|
||
<p><strong><span id="updateCount">0</span> container(s)</strong> have updates available</p>
|
||
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
||
<button onclick="selectAllUpdates()" class="btn btn-sm btn-secondary">Select All</button>
|
||
<button onclick="deselectAllUpdates()" class="btn btn-sm btn-secondary">Deselect All</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<table class="updates-table updates-table-card-layout">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 40px;">
|
||
<input type="checkbox" id="selectAllCheckbox" onchange="toggleAllUpdates(this.checked)">
|
||
</th>
|
||
<th>Update Details</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="updatesTableBody">
|
||
<!-- Populated by JavaScript -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="alert alert-info" style="margin-top: 20px;">
|
||
<strong>💡 Tip:</strong> You can also check and update individual containers from the <strong>Containers</strong> tab. Each container card has dedicated buttons for checking and applying updates.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Loading state -->
|
||
<div id="updateResultsLoading" class="loading">
|
||
Checking for updates...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="closeUpdateResultsModal()" class="btn btn-secondary">Close</button>
|
||
<button type="button" id="updateSelectedBtn" onclick="startBatchUpdate()" class="btn btn-primary" disabled>Update Selected</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Update Progress Modal -->
|
||
<div id="updateProgressModal" class="modal">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>Updating Containers</h3>
|
||
<button class="close-btn" onclick="closeUpdateProgressModal()" id="updateProgressCloseBtn" disabled>×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="update-progress-summary" style="margin-bottom: 20px;">
|
||
<div class="progress-bar-container" style="background: #e9ecef; border-radius: 4px; height: 24px; position: relative; overflow: hidden;">
|
||
<div id="updateProgressBar" class="progress-bar-fill" style="background: linear-gradient(90deg, #4CAF50, #45a049); height: 100%; width: 0%; transition: width 0.3s ease;"></div>
|
||
<div id="updateProgressText" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; font-weight: 600; color: #333;">0 / 0</div>
|
||
</div>
|
||
<div style="margin-top: 10px; text-align: center; font-size: 14px; color: var(--text-secondary);">
|
||
<span id="updateStatusText">Preparing...</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="update-containers-list" style="max-height: 400px; overflow-y: auto;">
|
||
<div id="updateContainersList">
|
||
<!-- Populated by JavaScript -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="update-logs-section" style="margin-top: 20px;">
|
||
<h4 style="margin-bottom: 10px;">Real-time Logs</h4>
|
||
<div id="updateLogs" class="log-content" style="max-height: 300px; overflow-y: auto; background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 12px;">
|
||
<div style="color: #4CAF50;">▶ Starting batch update...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" id="updateProgressDoneBtn" onclick="finishBatchUpdate()" class="btn btn-primary" style="display: none;">Done</button>
|
||
</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="plugins.js?v=1"></script>
|
||
<script src="app.js?v=17"></script>
|
||
</body>
|
||
</html>
|