Total Hits
{stats.totalHits.toLocaleString()}
Page hits, visitor geography, and data management.
-Loading analytics…
} - {statsStatus === 'error' &&Failed to load analytics.
} - {statsStatus === 'ready' && stats && ( - <> -{stats.totalHits.toLocaleString()}
{stats.last30DaysTotal.toLocaleString()}
{formatDate(stats.firstHitAt)}
{formatDate(stats.lastHitAt)}
No hits tracked yet.
: ( -IP, geography, and returning visitor behavior.
-Privacy: visitor analytics only run after cookie consent. IPs below are masked.
-{stats.visitors.totalVisits.toLocaleString()}
{stats.visitors.uniqueVisitors.toLocaleString()}
{stats.visitors.returningVisits.toLocaleString()}
{stats.visitors.totalVisits > 0 ? `${Math.round((stats.visitors.returningVisits / stats.visitors.totalVisits) * 100)}%` : '0%'}
No visitor records yet.
: ( -| Time | IP | Country | State | County | City | Path | Returning | Visit # |
|---|---|---|---|---|---|---|---|---|
| {formatDate(row.at)} | {maskIp(row.ip)} | {row.country} | {row.state} | {row.county} | {row.city} | {row.path} | {row.returningVisitor ? 'Yes' : 'No'} | {row.visitCount} | -
Submission totals from the contact form.
-{stats.contactTotals.totalSubmissions.toLocaleString()}
{stats.contactTotals.totalQuestions.toLocaleString()}
Export, backup, or retain only recent analytics data.
-{stats.writeStatus.hitStats.ok ? 'Healthy' : 'Error'}
{formatDate(stats.writeStatus.hitStats.at)}
{stats.writeStatus.visitorStats.ok ? 'Healthy' : 'Error'}
{formatDate(stats.writeStatus.visitorStats.at)}
{stats.writeStatus.backups.ok ? 'Healthy' : 'Error'}
{formatDate(stats.writeStatus.backups.at)}
{stats.writeStatus.backups.file ?? 'Not available yet'}
Current deployment metadata and cache purge health for the live app.
-{opsStatus?.buildCommit ?? 'Not available'}
{opsStatus?.buildNumber ?? 'Not available'}
{formatDate(opsStatus?.deployedAt ?? null)}
{opsStatus?.cachePurge.ok ? 'Healthy' : 'Needs setup'}
{formatDate(opsStatus?.cachePurge.at ?? null)}
Backup: {selectedBackupPreview.filename}
-Created: {formatDate(selectedBackupPreview.createdAt)}
-Reason: {selectedBackupPreview.reason}
-Size: {(selectedBackupPreview.sizeBytes / 1024).toFixed(1)} KB
-Content Updated At: {formatDate(selectedBackupPreview.adminUpdatedAt)}
-Total Hits: {selectedBackupPreview.totalHits.toLocaleString()}
-Total Visits: {selectedBackupPreview.totalVisits.toLocaleString()}
-{maintenanceMsg}
} - > - )} -Loading analytics…
+ if (statsStatus === 'error') returnFailed to load analytics.
+ if (!stats) return null + + // Chart data - Daily trend + const dailyChartData = { + labels: stats.last7Days.map((d: { day: string; hits: number }) => d.day), + datasets: [ + { + label: 'Real Visitors', + data: stats.last7DaysReal.map((d: { day: string; hits: number }) => d.hits), + borderColor: '#c9a84c', + backgroundColor: 'rgba(201, 168, 76, 0.1)', + tension: 0.4, + fill: true, + }, + { + label: 'Bot Traffic', + data: stats.last7DaysBot.map((d: { day: string; hits: number }) => d.hits), + borderColor: '#666', + backgroundColor: 'rgba(102, 102, 102, 0.1)', + tension: 0.4, + fill: true, + }, + ], + } + + // Chart data - Bot breakdown (pie) + const botPieData = { + labels: stats.botReasons.map((b: { reason: string; count: number }) => b.reason.replace(/-/g, ' ')), + datasets: [{ + data: stats.botReasons.map((b: { reason: string; count: number }) => b.count), + backgroundColor: [ + '#c9a84c', + '#a0853d', + '#6d5a2e', + '#8b7635', + '#9e8843', + '#bbb477', + '#7a6930', + '#5a4820', + '#c0a060', + '#746c36', + ], + borderColor: '#1a1a15', + borderWidth: 2, + }], + } + + // Traffic ratio (doughnut) + const trafficRatioData = { + labels: ['Real Visitors', 'Bot Traffic'], + datasets: [{ + data: [stats.realHits, stats.botHits], + backgroundColor: ['#c9a84c', '#999'], + borderColor: '#1a1a15', + borderWidth: 2, + }], + } + + // Top paths comparison + const topPathsLabels = Array.from({ length: Math.max(stats.topPathsReal.length, stats.topPathsBot.length) }, (_, i: number) => { + const realPath = stats.topPathsReal[i]?.path || '' + const botPath = stats.topPathsBot[i]?.path || '' + return realPath || botPath || `Path ${i + 1}` + }).slice(0, 8) + + const topPathsChartData = { + labels: topPathsLabels, + datasets: [ + { + label: 'Real Hits', + data: topPathsLabels.map((_label, i) => stats.topPathsReal[i]?.hits || 0), + backgroundColor: '#c9a84c', + }, + { + label: 'Bot Hits', + data: topPathsLabels.map((_label, i) => stats.topPathsBot[i]?.hits || 0), + backgroundColor: '#999', + }, + ], + } + + const chartOptions = { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + labels: { + color: '#f0ead8', + font: { size: 12 }, + }, + }, + tooltip: { + backgroundColor: '#2a2518', + titleColor: '#f0ead8', + bodyColor: '#f0ead8', + borderColor: '#c9a84c', + borderWidth: 1, + }, + }, + scales: { + y: { + beginAtZero: true, + ticks: { color: '#7a7060' }, + grid: { color: '#2a2518' }, + }, + x: { + ticks: { color: '#7a7060' }, + grid: { color: '#2a2518' }, + }, + }, + } + + return ( +Real-time insights into site traffic, bot detection, visitor behavior, and data management.
+{stats.totalHits.toLocaleString()}
+ All page requests +{stats.realHits.toLocaleString()}
+ {((stats.realHits / stats.totalHits) * 100).toFixed(1)}% of total +{stats.botHits.toLocaleString()}
+ {((stats.botHits / stats.totalHits) * 100).toFixed(1)}% of total +{stats.last30DaysRealTotal.toLocaleString()}
+ Real hits • {stats.last30DaysBotTotal.toLocaleString()} bots +{stats.visitors.uniqueVisitors.toLocaleString()}
+ Tracked with consent +{stats.visitors.totalVisits > 0 ? `${Math.round((stats.visitors.returningVisits / stats.visitors.totalVisits) * 100)}%` : '0%'}
+ {stats.visitors.returningVisits.toLocaleString()} return visits +No bot traffic detected.
+ ) : ( +No bots detected yet.
+ ) : ( +No visitor data yet.
+ ) : ( +Real-time tracking of visitor activity, geography, and returning behavior.
+Privacy: visitor analytics only run after cookie consent. IPs below are masked.
+ +{stats.visitors.totalVisits.toLocaleString()}
{stats.visitors.uniqueVisitors.toLocaleString()}
{stats.visitors.returningVisits.toLocaleString()}
{formatDate(stats.visitors.firstVisitAt)}
No visitor records yet.
+ ) : ( +| Time | IP | Country | Path | Returning | Visit # |
|---|---|---|---|---|---|
| {formatDate(row.at)} | {maskIp(row.ip)} | {row.country} | {row.path} | {row.returningVisitor ? 'Yes' : 'No'} | {row.visitCount} | +
Submission totals from the contact form.
+{stats.contactTotals.totalSubmissions.toLocaleString()}
{stats.contactTotals.totalQuestions.toLocaleString()}
Current deployment metadata and cache purge health for the live app.
+{opsStatus?.buildCommit ?? 'Not available'}
{opsStatus?.buildNumber ?? 'Not available'}
{formatDate(opsStatus?.deployedAt ?? null)}
{opsStatus?.cachePurge.ok ? 'Healthy' : 'Needs setup'}
{formatDate(opsStatus?.cachePurge.at ?? null)}
Export, backup, or retain only recent analytics data.
+{stats.writeStatus.hitStats.ok ? 'Healthy' : 'Error'}
{formatDate(stats.writeStatus.hitStats.at)}
{stats.writeStatus.visitorStats.ok ? 'Healthy' : 'Error'}
{formatDate(stats.writeStatus.visitorStats.at)}
{stats.writeStatus.backups.ok ? 'Healthy' : 'Error'}
{formatDate(stats.writeStatus.backups.at)}
Backup: {selectedBackupPreview.filename}
+Created: {formatDate(selectedBackupPreview.createdAt)}
+Size: {(selectedBackupPreview.sizeBytes / 1024).toFixed(1)} KB
+Total Hits: {selectedBackupPreview.totalHits.toLocaleString()}
+Total Visits: {selectedBackupPreview.totalVisits.toLocaleString()}
+{maintenanceMsg}
} +