Files
container-census/web/analytics/index.html
Self Hosters 8f960fbf68 Fix sidebar navigation and plugin routing issues
- Always show "Manage Plugins" link in sidebar even when all plugins disabled
- Restore NPM plugin static page to avoid bundle.js 404 errors
- Remove npm from dynamic route generateStaticParams (uses static route)
- NPM plugin now properly uses its dedicated React component
- Graph and security plugins continue to use dynamic [pluginId] route

This fixes the issue where disabling all plugins made it impossible to
re-enable them, and resolves bundle.js loading errors for NPM plugin.
2025-12-07 20:22:52 -05:00

313 lines
14 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 Analytics</title>
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
</head>
<body>
<div class="container">
<header>
<div class="header-content">
<div>
<h1>📊 Container Census Analytics</h1>
<p class="subtitle">Global container usage statistics (anonymous data)</p>
</div>
<div class="header-right">
<div id="liveIndicator" class="live-indicator">
<div class="pulse-dot"></div>
<span id="liveStatus">Waiting...</span>
<span id="eventCounter" class="event-counter" style="display: none;">0</span>
</div>
<span id="versionBadge" class="version-badge">v0.0.0</span>
</div>
</div>
</header>
<div class="summary-cards">
<div class="card">
<div class="card-value" id="totalInstallations">-</div>
<div class="card-label">Active Installations (30d)</div>
</div>
<div class="card">
<div class="card-value" id="totalSubmissions">-</div>
<div class="card-label">Total Submissions</div>
</div>
<div class="card">
<div class="card-value" id="totalContainers">-</div>
<div class="card-label">Total Containers</div>
</div>
<div class="card">
<div class="card-value" id="avgContainers">-</div>
<div class="card-label">Avg Containers/Install</div>
</div>
<div class="card">
<div class="card-value" id="totalHosts">-</div>
<div class="card-label">Total Hosts</div>
</div>
<div class="card">
<div class="card-value" id="totalAgents">-</div>
<div class="card-label">Total Agents</div>
</div>
<div class="card">
<div class="card-value" id="uniqueImages">-</div>
<div class="card-label">Unique Images</div>
</div>
</div>
<div class="filters">
<label>
Time Range:
<select id="timeRange">
<option value="7">Last 7 days</option>
<option value="30" selected>Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="365">Last year</option>
</select>
</label>
<button onclick="refreshData()">🔄 Refresh</button>
</div>
<div class="tabs">
<button class="tab-button active" onclick="showTab('charts', this)">Charts</button>
<button class="tab-button" onclick="showTab('trends', this)">Trends</button>
<button class="tab-button" onclick="showTab('images', this)">Container Images</button>
<button class="tab-button" onclick="showTab('database', this)">Database</button>
</div>
<div id="chartsTab" class="tab-content active">
<div class="chart-container">
<h2>Top 20 Most Popular Container Images</h2>
<canvas id="topImagesChart"></canvas>
</div>
<div class="chart-container">
<h2>Installation Growth Over Time</h2>
<canvas id="growthChart"></canvas>
</div>
<div class="chart-container">
<h2>Active Installations (Daily)</h2>
<p class="chart-subtitle">Unique installations checking for updates each day</p>
<canvas id="activeInstallsChart"></canvas>
</div>
<div class="chart-row">
<div class="chart-container half-width">
<h2>Registry Distribution</h2>
<canvas id="registriesChart"></canvas>
</div>
<div class="chart-container half-width">
<h2>Version Adoption</h2>
<canvas id="versionsChart"></canvas>
</div>
</div>
<div class="chart-row">
<div class="chart-container half-width">
<h2>Scan Interval Configuration</h2>
<canvas id="scanIntervalsChart"></canvas>
</div>
<div class="chart-container half-width activity-heatmap-container">
<h2>Activity Pattern (UTC)</h2>
<canvas id="activityHeatmapChart"></canvas>
</div>
</div>
<div class="chart-row">
<div class="chart-container half-width">
<h2>🐳 Docker Compose Adoption</h2>
<canvas id="composeAdoptionChart"></canvas>
</div>
<div class="chart-container half-width">
<h2>🔗 Container Connectivity</h2>
<canvas id="connectivityChart"></canvas>
</div>
</div>
<div class="chart-row">
<div class="chart-container half-width">
<h2>📦 Shared Volumes Usage</h2>
<canvas id="sharedVolumesChart"></canvas>
</div>
<div class="chart-container half-width">
<h2>🕸️ Custom Networks</h2>
<canvas id="customNetworksChart"></canvas>
</div>
</div>
<div class="chart-container">
<h2>🌍 Geographic Distribution (by Timezone)</h2>
<canvas id="geographyChart"></canvas>
</div>
</div>
<div id="trendsTab" class="tab-content">
<!-- Hottest Images Section -->
<div class="chart-container">
<div class="trends-header">
<h2>Hottest Container Images</h2>
<div class="trends-toggle">
<button id="hottestByContainersBtn" class="toggle-btn active" onclick="switchHottestView('containers')">By Containers</button>
<button id="hottestByAdoptionBtn" class="toggle-btn" onclick="switchHottestView('adoption')">By Adoption</button>
</div>
</div>
<p class="chart-subtitle" id="hottestSubtitle">Loading...</p>
<div id="hottestContainersChart" class="hottest-chart">
<canvas id="hottestByContainersCanvas"></canvas>
</div>
<div id="hottestAdoptionChart" class="hottest-chart" style="display: none;">
<canvas id="hottestByAdoptionCanvas"></canvas>
</div>
</div>
<!-- Movers Section -->
<div class="chart-container">
<div class="trends-header">
<h2>Biggest Movers This Week</h2>
<span class="period-badge" id="moversPeriodBadge">Week over Week</span>
</div>
<p class="chart-subtitle" id="moversSubtitle">Loading...</p>
<div class="movers-grid">
<div class="movers-column risers">
<div class="movers-column-header">
<span class="movers-icon">📈</span>
<h3>Risers</h3>
<span class="movers-count" id="risersCount">(0)</span>
</div>
<div id="risersList" class="movers-list">
<p class="loading-message">Loading...</p>
</div>
</div>
<div class="movers-column fallers">
<div class="movers-column-header">
<span class="movers-icon">📉</span>
<h3>Fallers</h3>
<span class="movers-count" id="fallersCount">(0)</span>
</div>
<div id="fallersList" class="movers-list">
<p class="loading-message">Loading...</p>
</div>
</div>
</div>
</div>
<!-- New Entries Section -->
<div class="chart-container new-entries-section">
<div class="trends-header">
<h2>✨ New Entries</h2>
<span class="new-entries-badge" id="newEntriesBadge">0 new</span>
</div>
<p class="chart-subtitle" id="newEntriesSubtitle">Loading...</p>
<div id="newEntriesGrid" class="new-entries-grid">
<p class="loading-message">Loading...</p>
</div>
</div>
</div>
<div id="imagesTab" class="tab-content">
<div class="table-container">
<div class="table-controls">
<input type="text" id="imageSearch" placeholder="Search images..." onkeyup="filterImages()">
<div class="results-info">
<span id="resultsCount">Showing 0 images</span>
</div>
</div>
<div class="table-wrapper">
<table id="imagesTable" class="data-table">
<thead>
<tr>
<th onclick="sortTable('name', this)" class="sortable">
Image Name <span class="sort-indicator"></span>
</th>
<th onclick="sortTable('count', this)" class="sortable text-right">
Container Count <span class="sort-indicator"></span>
</th>
<th onclick="sortTable('registry', this)" class="sortable">
Registry <span class="sort-indicator"></span>
</th>
<th onclick="sortTable('installations', this)" class="sortable text-right">
Installations <span class="sort-indicator"></span>
</th>
<th onclick="sortTable('percentage', this)" class="sortable text-right">
% of Total <span class="sort-indicator"></span>
</th>
</tr>
</thead>
<tbody id="imagesTableBody">
<tr>
<td colspan="5" class="loading-cell">Loading...</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<button id="prevPage" onclick="changePage(-1)" disabled>← Previous</button>
<span id="pageInfo">Page 1</span>
<button id="nextPage" onclick="changePage(1)">Next →</button>
</div>
</div>
</div>
<div id="databaseTab" class="tab-content">
<div class="database-viewer">
<div class="db-controls">
<div class="control-group">
<label>
Table:
<select id="dbTableSelect" onchange="loadDatabaseView()">
<option value="telemetry_reports">Telemetry Reports</option>
<option value="submission_events">Submission Events</option>
<option value="image_stats">Image Stats</option>
</select>
</label>
<label>
Installation ID:
<input type="text" id="dbInstallationFilter" placeholder="Filter by ID..." onchange="loadDatabaseView()">
</label>
<button onclick="loadDatabaseView()">🔄 Refresh</button>
<button onclick="exportDatabaseView()">💾 Export JSON</button>
</div>
<div class="auto-refresh-control">
<label>
<input type="checkbox" id="dbAutoRefresh" onchange="toggleAutoRefresh()">
Auto-refresh (5s)
</label>
</div>
</div>
<div class="db-info">
<span id="dbRecordCount">0 records</span>
</div>
<div class="db-table-wrapper">
<div id="databaseContent" class="database-content">
<p class="loading-message">Select a table and click refresh to view data</p>
</div>
</div>
<div class="pagination">
<button id="dbPrevPage" onclick="changeDbPage(-1)" disabled>← Previous</button>
<span id="dbPageInfo">Page 1</span>
<button id="dbNextPage" onclick="changeDbPage(1)" disabled>Next →</button>
</div>
</div>
</div>
<footer>
<p>Data is collected anonymously from installations that opt-in to telemetry.</p>
<p>No personally identifiable information is collected.</p>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>