Files
container-census/web/index.html
T
Self Hosters 8ac9ca8947 Add plugin architecture and NPM integration (WIP)
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>
2025-12-02 16:02:53 -05:00

1772 lines
97 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
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="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>&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>🎨 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()">&times;</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">&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-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()">&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>
<!-- 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()">&times;</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>&times;</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()">&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="plugins.js?v=1"></script>
<script src="app.js?v=17"></script>
</body>
</html>