···
-
<meta charset="UTF-8" />
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
-
<title>Cachet Analytics Dashboard</title>
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
-
box-sizing: border-box;
-
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-
justify-content: space-between;
-
.header-links a:hover {
-
text-decoration: underline;
-
border: 1px solid #ddd;
-
.controls button:hover {
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-
justify-content: center;
-
.auto-refresh input[type="checkbox"] {
-
@media (max-width: 768px) {
-
grid-template-columns: 1fr;
-
flex-direction: column;
-
<h1>📊 Cachet Analytics Dashboard</h1>
-
<div class="header-links">
-
<a href="/swagger">API Docs</a>
-
<a href="/stats">Raw Stats</a>
-
<div class="dashboard">
-
<select id="daysSelect">
-
<option value="1">Last 24 hours</option>
-
<option value="7" selected>Last 7 days</option>
-
<option value="30">Last 30 days</option>
-
<button onclick="loadData()">Refresh</button>
-
<div class="auto-refresh">
-
<input type="checkbox" id="autoRefresh" />
-
<label for="autoRefresh">Auto-refresh (30s)</label>
-
<div id="loading" class="loading">Loading analytics data...</div>
-
<div id="error" class="error" style="display: none"></div>
-
<div id="content" style="display: none">
-
class="chart-container"
-
style="margin-bottom: 2rem; height: 450px"
-
<div class="chart-title">
-
Traffic Overview - All Routes Over Time
-
id="trafficOverviewChart"
-
style="padding-bottom: 2rem"
-
<div class="stats-grid">
-
<div class="stat-card">
-
<div class="stat-number" id="totalRequests">-</div>
-
<div class="stat-label">Total Requests</div>
-
<div class="stat-card">
-
<div class="stat-number" id="avgResponseTime">-</div>
-
<div class="stat-label">Avg Response Time (ms)</div>
-
<div class="stat-card">
-
<div class="stat-number" id="p95ResponseTime">-</div>
-
<div class="stat-label">P95 Response Time (ms)</div>
-
<div class="stat-card">
-
<div class="stat-number" id="uniqueEndpoints">-</div>
-
<div class="stat-label">Unique Endpoints</div>
-
<div class="stat-card">
-
<div class="stat-number" id="errorRate">-</div>
-
<div class="stat-label">Error Rate (%)</div>
-
<div class="stat-card">
-
<div class="stat-number" id="fastRequests">-</div>
-
<div class="stat-label">Fast Requests (<100ms)</div>
-
<div class="stat-card">
-
<div class="stat-number" id="uptime">-</div>
-
<div class="stat-label">Uptime (%)</div>
-
<div class="stat-card">
-
<div class="stat-number" id="throughput">-</div>
-
<div class="stat-label">Throughput (req/hr)</div>
-
<div class="stat-card">
-
<div class="stat-number" id="apdex">-</div>
-
<div class="stat-label">APDEX Score</div>
-
<div class="stat-card">
-
<div class="stat-number" id="cacheHitRate">-</div>
-
<div class="stat-label">Cache Hit Rate (%)</div>
-
<div class="stats-grid">
-
<div class="stat-card">
-
<div class="stat-number" id="peakHour">-</div>
-
<div class="stat-label">Peak Hour</div>
-
<div class="stat-card">
-
<div class="stat-number" id="peakHourRequests">-</div>
-
<div class="stat-label">Peak Hour Requests</div>
-
<div class="stat-card">
-
<div class="stat-number" id="peakDay">-</div>
-
<div class="stat-label">Peak Day</div>
-
<div class="stat-card">
-
<div class="stat-number" id="peakDayRequests">-</div>
-
<div class="stat-label">Peak Day Requests</div>
-
<div class="stat-card">
-
<div class="stat-number" id="dashboardRequests">-</div>
-
<div class="stat-label">Dashboard Requests</div>
-
<div class="charts-grid">
-
<div class="chart-container">
-
<div class="chart-title">Requests Over Time</div>
-
<canvas id="timeChart"></canvas>
-
<div class="chart-container">
-
<div class="chart-title">
-
Latency Over Time (Hourly)
-
<canvas id="latencyTimeChart"></canvas>
-
<div class="chart-container">
-
<div class="chart-title">
-
Response Time Distribution
-
<canvas id="latencyDistributionChart"></canvas>
-
<div class="chart-container">
-
<div class="chart-title">Latency Percentiles</div>
-
<canvas id="percentilesChart"></canvas>
-
<div class="chart-container">
-
<div class="chart-title">Top Endpoints</div>
-
<canvas id="endpointChart"></canvas>
-
<div class="chart-container">
-
<div class="chart-title">Slowest Endpoints</div>
-
<canvas id="slowestEndpointsChart"></canvas>
-
<div class="chart-container">
-
<div class="chart-title">Status Codes</div>
-
<canvas id="statusChart"></canvas>
-
<div class="chart-container">
-
<div class="chart-title">Top User Agents</div>
-
<canvas id="userAgentChart"></canvas>
-
let autoRefreshInterval;
-
async function loadData() {
-
const days = document.getElementById("daysSelect").value;
-
const loading = document.getElementById("loading");
-
const error = document.getElementById("error");
-
const content = document.getElementById("content");
-
loading.style.display = "block";
-
error.style.display = "none";
-
content.style.display = "none";
-
const response = await fetch(`/stats?days=${days}`);
-
throw new Error(`HTTP ${response.status}`);
-
const data = await response.json();
-
loading.style.display = "none";
-
content.style.display = "block";
-
loading.style.display = "none";
-
error.style.display = "block";
-
error.textContent = `Failed to load data: ${err.message}`;
-
function updateDashboard(data) {
-
document.getElementById("totalRequests").textContent =
-
data.totalRequests.toLocaleString();
-
document.getElementById("avgResponseTime").textContent =
-
data.averageResponseTime
-
? Math.round(data.averageResponseTime)
-
document.getElementById("p95ResponseTime").textContent = data
-
.latencyAnalytics.percentiles.p95
-
? Math.round(data.latencyAnalytics.percentiles.p95)
-
document.getElementById("uniqueEndpoints").textContent =
-
data.requestsByEndpoint.length;
-
const errorRequests = data.requestsByStatus
-
.filter((s) => s.status >= 400)
-
.reduce((sum, s) => sum + s.count, 0);
-
? ((errorRequests / data.totalRequests) * 100).toFixed(
-
document.getElementById("errorRate").textContent = errorRate;
-
// Calculate fast requests percentage
-
const fastRequestsData = data.latencyAnalytics.distribution
-
(d) => d.range === "0-50ms" || d.range === "50-100ms",
-
.reduce((sum, d) => sum + d.percentage, 0);
-
document.getElementById("fastRequests").textContent =
-
fastRequestsData.toFixed(1) + "%";
-
document.getElementById("uptime").textContent =
-
data.performanceMetrics.uptime.toFixed(1);
-
document.getElementById("throughput").textContent = Math.round(
-
data.performanceMetrics.throughput,
-
document.getElementById("apdex").textContent =
-
data.performanceMetrics.apdex.toFixed(2);
-
document.getElementById("cacheHitRate").textContent =
-
data.performanceMetrics.cachehitRate.toFixed(1);
-
document.getElementById("peakHour").textContent =
-
data.peakTraffic.peakHour;
-
document.getElementById("peakHourRequests").textContent =
-
data.peakTraffic.peakRequests.toLocaleString();
-
document.getElementById("peakDay").textContent =
-
data.peakTraffic.peakDay;
-
document.getElementById("peakDayRequests").textContent =
-
data.peakTraffic.peakDayRequests.toLocaleString();
-
document.getElementById("dashboardRequests").textContent =
-
data.dashboardMetrics.statsRequests.toLocaleString();
-
// Determine if we're showing hourly or daily data
-
document.getElementById("daysSelect").value,
-
const isHourly = days === 1;
-
updateTrafficOverviewChart(data.trafficOverview, days);
-
updateTimeChart(data.requestsByDay, isHourly);
-
updateLatencyTimeChart(
-
data.latencyAnalytics.latencyOverTime,
-
updateLatencyDistributionChart(
-
data.latencyAnalytics.distribution,
-
updatePercentilesChart(data.latencyAnalytics.percentiles);
-
updateEndpointChart(data.requestsByEndpoint.slice(0, 10));
-
updateSlowestEndpointsChart(
-
data.latencyAnalytics.slowestEndpoints,
-
updateStatusChart(data.requestsByStatus);
-
updateUserAgentChart(data.topUserAgents.slice(0, 5));
-
function updateTrafficOverviewChart(data, days) {
-
.getElementById("trafficOverviewChart")
-
if (charts.trafficOverview) charts.trafficOverview.destroy();
-
// Update chart title based on granularity
-
const chartTitle = document
-
.querySelector("#trafficOverviewChart")
-
.parentElement.querySelector(".chart-title");
-
let titleText = "Traffic Overview - All Routes Over Time";
-
titleText += " (Hourly)";
-
} else if (days <= 7) {
-
titleText += " (4-Hour Intervals)";
-
titleText += " (Daily)";
-
chartTitle.textContent = titleText;
-
// Get all unique routes across all time periods
-
const allRoutes = new Set();
-
data.forEach((timePoint) => {
-
Object.keys(timePoint.routes).forEach((route) =>
-
// Define colors for different route types
-
"User Data": "#2ecc71",
-
"User Redirects": "#27ae60",
-
"Emoji Data": "#e74c3c",
-
"Emoji Redirects": "#c0392b",
-
"Emoji List": "#e67e22",
-
"Health Check": "#f39c12",
-
"API Documentation": "#9b59b6",
-
"Cache Management": "#34495e",
-
// Create datasets for each route
-
const datasets = Array.from(allRoutes).map((route) => {
-
const color = routeColors[route] || "#95a5a6";
-
(timePoint) => timePoint.routes[route] || 0,
-
backgroundColor: color + "20", // Add transparency
-
// Format labels based on time granularity
-
const labels = data.map((timePoint) => {
-
// Show just hour for 24h view
-
return timePoint.time.split(" ")[1] || timePoint.time;
-
} else if (days <= 7) {
-
// Show day and hour for 7-day view
-
const parts = timePoint.time.split(" ");
-
const date = parts[0].split("-")[2]; // Get day
-
const hour = parts[1] || "00:00";
-
return `${date} ${hour}`;
-
// Show full date for longer periods
-
charts.trafficOverview = new Chart(ctx, {
-
maintainAspectRatio: false,
-
afterLabel: function (context) {
-
data[context.dataIndex];
-
return `Total: ${timePoint.total} requests`;
-
function updateTimeChart(data, isHourly) {
-
.getElementById("timeChart")
-
if (charts.time) charts.time.destroy();
-
const chartTitle = document
-
.querySelector("#timeChart")
-
.parentElement.querySelector(".chart-title");
-
chartTitle.textContent = isHourly
-
? "Requests Over Time (Hourly)"
-
: "Requests Over Time (Daily)";
-
charts.time = new Chart(ctx, {
-
labels: data.map((d) =>
-
isHourly ? d.date.split(" ")[1] : d.date,
-
data: data.map((d) => d.count),
-
borderColor: "#3498db",
-
backgroundColor: "rgba(52, 152, 219, 0.1)",
-
function updateEndpointChart(data) {
-
.getElementById("endpointChart")
-
if (charts.endpoint) charts.endpoint.destroy();
-
charts.endpoint = new Chart(ctx, {
-
labels: data.map((d) => d.endpoint),
-
data: data.map((d) => d.count),
-
backgroundColor: "#2ecc71",
-
function updateStatusChart(data) {
-
.getElementById("statusChart")
-
if (charts.status) charts.status.destroy();
-
const colors = data.map((d) => {
-
if (d.status >= 200 && d.status < 300) return "#2ecc71";
-
if (d.status >= 300 && d.status < 400) return "#f39c12";
-
if (d.status >= 400 && d.status < 500) return "#e74c3c";
-
charts.status = new Chart(ctx, {
-
labels: data.map((d) => `${d.status}`),
-
data: data.map((d) => d.count),
-
backgroundColor: colors,
-
function updateUserAgentChart(data) {
-
.getElementById("userAgentChart")
-
if (charts.userAgent) charts.userAgent.destroy();
-
charts.userAgent = new Chart(ctx, {
-
labels: data.map((d) => d.userAgent),
-
data: data.map((d) => d.count),
-
function updateLatencyTimeChart(data, isHourly) {
-
.getElementById("latencyTimeChart")
-
if (charts.latencyTime) charts.latencyTime.destroy();
-
const chartTitle = document
-
.querySelector("#latencyTimeChart")
-
.parentElement.querySelector(".chart-title");
-
chartTitle.textContent = isHourly
-
? "Latency Over Time (Hourly)"
-
: "Latency Over Time (Daily)";
-
charts.latencyTime = new Chart(ctx, {
-
labels: data.map((d) =>
-
isHourly ? d.time.split(" ")[1] : d.time,
-
label: "Average Response Time",
-
data: data.map((d) => d.averageResponseTime),
-
borderColor: "#3498db",
-
backgroundColor: "rgba(52, 152, 219, 0.1)",
-
label: "P95 Response Time",
-
data: data.map((d) => d.p95),
-
borderColor: "#e74c3c",
-
backgroundColor: "rgba(231, 76, 60, 0.1)",
-
text: "Response Time (ms)",
-
function updateLatencyDistributionChart(data) {
-
.getElementById("latencyDistributionChart")
-
if (charts.latencyDistribution)
-
charts.latencyDistribution.destroy();
-
charts.latencyDistribution = new Chart(ctx, {
-
labels: data.map((d) => d.range),
-
data: data.map((d) => d.count),
-
backgroundColor: "#2ecc71",
-
function updatePercentilesChart(percentiles) {
-
.getElementById("percentilesChart")
-
if (charts.percentiles) charts.percentiles.destroy();
-
{ label: "P50", value: percentiles.p50 },
-
{ label: "P75", value: percentiles.p75 },
-
{ label: "P90", value: percentiles.p90 },
-
{ label: "P95", value: percentiles.p95 },
-
{ label: "P99", value: percentiles.p99 },
-
].filter((d) => d.value !== null);
-
charts.percentiles = new Chart(ctx, {
-
labels: data.map((d) => d.label),
-
label: "Response Time (ms)",
-
data: data.map((d) => d.value),
-
function updateSlowestEndpointsChart(data) {
-
.getElementById("slowestEndpointsChart")
-
if (charts.slowestEndpoints) charts.slowestEndpoints.destroy();
-
charts.slowestEndpoints = new Chart(ctx, {
-
labels: data.map((d) => d.endpoint),
-
label: "Avg Response Time (ms)",
-
data: data.map((d) => d.averageResponseTime),
-
backgroundColor: "#e74c3c",
-
.getElementById("autoRefresh")
-
.addEventListener("change", function () {
-
autoRefreshInterval = setInterval(loadData, 30000);
-
clearInterval(autoRefreshInterval);
-
.getElementById("daysSelect")
-
.addEventListener("change", loadData);
···
+
<meta charset="UTF-8" />
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
<title>Cachet Analytics Dashboard</title>
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+
box-sizing: border-box;
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
justify-content: space-between;
+
.header-links a:hover {
+
text-decoration: underline;
+
border: 1px solid #ddd;
+
.controls button:hover {
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
justify-content: center;
+
.auto-refresh input[type="checkbox"] {
+
@media (max-width: 768px) {
+
grid-template-columns: 1fr;
+
flex-direction: column;
+
<h1>📊 Cachet Analytics Dashboard</h1>
+
<div class="header-links">
+
<a href="https://github.com/taciturnaxolotl/cachet">Github</a>
+
<a href="/swagger">API Docs</a>
+
<a href="/stats">Raw Stats</a>
+
<div class="dashboard">
+
<select id="daysSelect">
+
<option value="1">Last 24 hours</option>
+
<option value="7" selected>Last 7 days</option>
+
<option value="30">Last 30 days</option>
+
<button onclick="loadData()">Refresh</button>
+
<div class="auto-refresh">
+
<input type="checkbox" id="autoRefresh" />
+
<label for="autoRefresh">Auto-refresh (30s)</label>
+
<div id="loading" class="loading">Loading analytics data...</div>
+
<div id="error" class="error" style="display: none"></div>
+
<div id="content" style="display: none">
+
<div class="chart-container" style="margin-bottom: 2rem; height: 450px">
+
<div class="chart-title">Traffic Overview - All Routes Over Time</div>
+
id="trafficOverviewChart"
+
style="padding-bottom: 2rem"
+
<div class="stats-grid">
+
<div class="stat-card">
+
<div class="stat-number" id="totalRequests">-</div>
+
<div class="stat-label">Total Requests</div>
+
<div class="stat-card">
+
<div class="stat-number" id="avgResponseTime">-</div>
+
<div class="stat-label">Avg Response Time (ms)</div>
+
<div class="stat-card">
+
<div class="stat-number" id="p95ResponseTime">-</div>
+
<div class="stat-label">P95 Response Time (ms)</div>
+
<div class="stat-card">
+
<div class="stat-number" id="uniqueEndpoints">-</div>
+
<div class="stat-label">Unique Endpoints</div>
+
<div class="stat-card">
+
<div class="stat-number" id="errorRate">-</div>
+
<div class="stat-label">Error Rate (%)</div>
+
<div class="stat-card">
+
<div class="stat-number" id="fastRequests">-</div>
+
<div class="stat-label">Fast Requests (<100ms)</div>
+
<div class="stat-card">
+
<div class="stat-number" id="uptime">-</div>
+
<div class="stat-label">Uptime (%)</div>
+
<div class="stat-card">
+
<div class="stat-number" id="throughput">-</div>
+
<div class="stat-label">Throughput (req/hr)</div>
+
<div class="stat-card">
+
<div class="stat-number" id="apdex">-</div>
+
<div class="stat-label">APDEX Score</div>
+
<div class="stat-card">
+
<div class="stat-number" id="cacheHitRate">-</div>
+
<div class="stat-label">Cache Hit Rate (%)</div>
+
<div class="stats-grid">
+
<div class="stat-card">
+
<div class="stat-number" id="peakHour">-</div>
+
<div class="stat-label">Peak Hour</div>
+
<div class="stat-card">
+
<div class="stat-number" id="peakHourRequests">-</div>
+
<div class="stat-label">Peak Hour Requests</div>
+
<div class="stat-card">
+
<div class="stat-number" id="peakDay">-</div>
+
<div class="stat-label">Peak Day</div>
+
<div class="stat-card">
+
<div class="stat-number" id="peakDayRequests">-</div>
+
<div class="stat-label">Peak Day Requests</div>
+
<div class="stat-card">
+
<div class="stat-number" id="dashboardRequests">-</div>
+
<div class="stat-label">Dashboard Requests</div>
+
<div class="charts-grid">
+
<div class="chart-container">
+
<div class="chart-title">Requests Over Time</div>
+
<canvas id="timeChart"></canvas>
+
<div class="chart-container">
+
<div class="chart-title">Latency Over Time (Hourly)</div>
+
<canvas id="latencyTimeChart"></canvas>
+
<div class="chart-container">
+
<div class="chart-title">Response Time Distribution</div>
+
<canvas id="latencyDistributionChart"></canvas>
+
<div class="chart-container">
+
<div class="chart-title">Latency Percentiles</div>
+
<canvas id="percentilesChart"></canvas>
+
<div class="chart-container">
+
<div class="chart-title">Top Endpoints</div>
+
<canvas id="endpointChart"></canvas>
+
<div class="chart-container">
+
<div class="chart-title">Slowest Endpoints</div>
+
<canvas id="slowestEndpointsChart"></canvas>
+
<div class="chart-container">
+
<div class="chart-title">Status Codes</div>
+
<canvas id="statusChart"></canvas>
+
<div class="chart-container">
+
<div class="chart-title">Top User Agents</div>
+
<canvas id="userAgentChart"></canvas>
+
let autoRefreshInterval;
+
async function loadData() {
+
const days = document.getElementById("daysSelect").value;
+
const loading = document.getElementById("loading");
+
const error = document.getElementById("error");
+
const content = document.getElementById("content");
+
loading.style.display = "block";
+
error.style.display = "none";
+
content.style.display = "none";
+
const response = await fetch(`/stats?days=${days}`);
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
+
const data = await response.json();
+
loading.style.display = "none";
+
content.style.display = "block";
+
loading.style.display = "none";
+
error.style.display = "block";
+
error.textContent = `Failed to load data: ${err.message}`;
+
function updateDashboard(data) {
+
document.getElementById("totalRequests").textContent =
+
data.totalRequests.toLocaleString();
+
document.getElementById("avgResponseTime").textContent =
+
data.averageResponseTime
+
? Math.round(data.averageResponseTime)
+
document.getElementById("p95ResponseTime").textContent = data
+
.latencyAnalytics.percentiles.p95
+
? Math.round(data.latencyAnalytics.percentiles.p95)
+
document.getElementById("uniqueEndpoints").textContent =
+
data.requestsByEndpoint.length;
+
const errorRequests = data.requestsByStatus
+
.filter((s) => s.status >= 400)
+
.reduce((sum, s) => sum + s.count, 0);
+
? ((errorRequests / data.totalRequests) * 100).toFixed(1)
+
document.getElementById("errorRate").textContent = errorRate;
+
// Calculate fast requests percentage
+
const fastRequestsData = data.latencyAnalytics.distribution
+
.filter((d) => d.range === "0-50ms" || d.range === "50-100ms")
+
.reduce((sum, d) => sum + d.percentage, 0);
+
document.getElementById("fastRequests").textContent =
+
fastRequestsData.toFixed(1) + "%";
+
document.getElementById("uptime").textContent =
+
data.performanceMetrics.uptime.toFixed(1);
+
document.getElementById("throughput").textContent = Math.round(
+
data.performanceMetrics.throughput,
+
document.getElementById("apdex").textContent =
+
data.performanceMetrics.apdex.toFixed(2);
+
document.getElementById("cacheHitRate").textContent =
+
data.performanceMetrics.cachehitRate.toFixed(1);
+
document.getElementById("peakHour").textContent =
+
data.peakTraffic.peakHour;
+
document.getElementById("peakHourRequests").textContent =
+
data.peakTraffic.peakRequests.toLocaleString();
+
document.getElementById("peakDay").textContent =
+
data.peakTraffic.peakDay;
+
document.getElementById("peakDayRequests").textContent =
+
data.peakTraffic.peakDayRequests.toLocaleString();
+
document.getElementById("dashboardRequests").textContent =
+
data.dashboardMetrics.statsRequests.toLocaleString();
+
// Determine if we're showing hourly or daily data
+
const days = parseInt(document.getElementById("daysSelect").value);
+
const isHourly = days === 1;
+
updateTrafficOverviewChart(data.trafficOverview, days);
+
updateTimeChart(data.requestsByDay, isHourly);
+
updateLatencyTimeChart(data.latencyAnalytics.latencyOverTime, isHourly);
+
updateLatencyDistributionChart(data.latencyAnalytics.distribution);
+
updatePercentilesChart(data.latencyAnalytics.percentiles);
+
updateEndpointChart(data.requestsByEndpoint.slice(0, 10));
+
updateSlowestEndpointsChart(data.latencyAnalytics.slowestEndpoints);
+
updateStatusChart(data.requestsByStatus);
+
updateUserAgentChart(data.topUserAgents.slice(0, 5));
+
function updateTrafficOverviewChart(data, days) {
+
.getElementById("trafficOverviewChart")
+
if (charts.trafficOverview) charts.trafficOverview.destroy();
+
// Update chart title based on granularity
+
const chartTitle = document
+
.querySelector("#trafficOverviewChart")
+
.parentElement.querySelector(".chart-title");
+
let titleText = "Traffic Overview - All Routes Over Time";
+
titleText += " (Hourly)";
+
} else if (days <= 7) {
+
titleText += " (4-Hour Intervals)";
+
titleText += " (Daily)";
+
chartTitle.textContent = titleText;
+
// Get all unique routes across all time periods
+
const allRoutes = new Set();
+
data.forEach((timePoint) => {
+
Object.keys(timePoint.routes).forEach((route) =>
+
// Define colors for different route types
+
"User Data": "#2ecc71",
+
"User Redirects": "#27ae60",
+
"Emoji Data": "#e74c3c",
+
"Emoji Redirects": "#c0392b",
+
"Emoji List": "#e67e22",
+
"Health Check": "#f39c12",
+
"API Documentation": "#9b59b6",
+
"Cache Management": "#34495e",
+
// Create datasets for each route
+
const datasets = Array.from(allRoutes).map((route) => {
+
const color = routeColors[route] || "#95a5a6";
+
data: data.map((timePoint) => timePoint.routes[route] || 0),
+
backgroundColor: color + "20", // Add transparency
+
// Format labels based on time granularity
+
const labels = data.map((timePoint) => {
+
// Show just hour for 24h view
+
return timePoint.time.split(" ")[1] || timePoint.time;
+
} else if (days <= 7) {
+
// Show day and hour for 7-day view
+
const parts = timePoint.time.split(" ");
+
const date = parts[0].split("-")[2]; // Get day
+
const hour = parts[1] || "00:00";
+
return `${date} ${hour}`;
+
// Show full date for longer periods
+
charts.trafficOverview = new Chart(ctx, {
+
maintainAspectRatio: false,
+
afterLabel: function (context) {
+
const timePoint = data[context.dataIndex];
+
return `Total: ${timePoint.total} requests`;
+
text: days === 1 ? "Hour" : days <= 7 ? "Day & Hour" : "Date",
+
function updateTimeChart(data, isHourly) {
+
const ctx = document.getElementById("timeChart").getContext("2d");
+
if (charts.time) charts.time.destroy();
+
const chartTitle = document
+
.querySelector("#timeChart")
+
.parentElement.querySelector(".chart-title");
+
chartTitle.textContent = isHourly
+
? "Requests Over Time (Hourly)"
+
: "Requests Over Time (Daily)";
+
charts.time = new Chart(ctx, {
+
labels: data.map((d) => (isHourly ? d.date.split(" ")[1] : d.date)),
+
data: data.map((d) => d.count),
+
borderColor: "#3498db",
+
backgroundColor: "rgba(52, 152, 219, 0.1)",
+
function updateEndpointChart(data) {
+
const ctx = document.getElementById("endpointChart").getContext("2d");
+
if (charts.endpoint) charts.endpoint.destroy();
+
charts.endpoint = new Chart(ctx, {
+
labels: data.map((d) => d.endpoint),
+
data: data.map((d) => d.count),
+
backgroundColor: "#2ecc71",
+
function updateStatusChart(data) {
+
const ctx = document.getElementById("statusChart").getContext("2d");
+
if (charts.status) charts.status.destroy();
+
const colors = data.map((d) => {
+
if (d.status >= 200 && d.status < 300) return "#2ecc71";
+
if (d.status >= 300 && d.status < 400) return "#f39c12";
+
if (d.status >= 400 && d.status < 500) return "#e74c3c";
+
charts.status = new Chart(ctx, {
+
labels: data.map((d) => `${d.status}`),
+
data: data.map((d) => d.count),
+
backgroundColor: colors,
+
function updateUserAgentChart(data) {
+
const ctx = document.getElementById("userAgentChart").getContext("2d");
+
if (charts.userAgent) charts.userAgent.destroy();
+
charts.userAgent = new Chart(ctx, {
+
labels: data.map((d) => d.userAgent),
+
data: data.map((d) => d.count),
+
function updateLatencyTimeChart(data, isHourly) {
+
.getElementById("latencyTimeChart")
+
if (charts.latencyTime) charts.latencyTime.destroy();
+
const chartTitle = document
+
.querySelector("#latencyTimeChart")
+
.parentElement.querySelector(".chart-title");
+
chartTitle.textContent = isHourly
+
? "Latency Over Time (Hourly)"
+
: "Latency Over Time (Daily)";
+
charts.latencyTime = new Chart(ctx, {
+
labels: data.map((d) => (isHourly ? d.time.split(" ")[1] : d.time)),
+
label: "Average Response Time",
+
data: data.map((d) => d.averageResponseTime),
+
borderColor: "#3498db",
+
backgroundColor: "rgba(52, 152, 219, 0.1)",
+
label: "P95 Response Time",
+
data: data.map((d) => d.p95),
+
borderColor: "#e74c3c",
+
backgroundColor: "rgba(231, 76, 60, 0.1)",
+
text: "Response Time (ms)",
+
function updateLatencyDistributionChart(data) {
+
.getElementById("latencyDistributionChart")
+
if (charts.latencyDistribution) charts.latencyDistribution.destroy();
+
charts.latencyDistribution = new Chart(ctx, {
+
labels: data.map((d) => d.range),
+
data: data.map((d) => d.count),
+
backgroundColor: "#2ecc71",
+
function updatePercentilesChart(percentiles) {
+
.getElementById("percentilesChart")
+
if (charts.percentiles) charts.percentiles.destroy();
+
{ label: "P50", value: percentiles.p50 },
+
{ label: "P75", value: percentiles.p75 },
+
{ label: "P90", value: percentiles.p90 },
+
{ label: "P95", value: percentiles.p95 },
+
{ label: "P99", value: percentiles.p99 },
+
].filter((d) => d.value !== null);
+
charts.percentiles = new Chart(ctx, {
+
labels: data.map((d) => d.label),
+
label: "Response Time (ms)",
+
data: data.map((d) => d.value),
+
function updateSlowestEndpointsChart(data) {
+
.getElementById("slowestEndpointsChart")
+
if (charts.slowestEndpoints) charts.slowestEndpoints.destroy();
+
charts.slowestEndpoints = new Chart(ctx, {
+
labels: data.map((d) => d.endpoint),
+
label: "Avg Response Time (ms)",
+
data: data.map((d) => d.averageResponseTime),
+
backgroundColor: "#e74c3c",
+
.getElementById("autoRefresh")
+
.addEventListener("change", function () {
+
autoRefreshInterval = setInterval(loadData, 30000);
+
clearInterval(autoRefreshInterval);
+
.getElementById("daysSelect")
+
.addEventListener("change", loadData);