From f9ce0a4f3b7245e7e103a9afc521f5638504b467 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Mon, 24 Aug 2026 21:13:20 +0000 Subject: [PATCH] Smarter initial analytics page when there is no visitor data yet. --- frontend/src/AnalyticsView.vue | 1 - frontend/src/VisitorCharts.vue | 5 ++--- frontend/src/analytics/time.js | 37 ++++++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/frontend/src/AnalyticsView.vue b/frontend/src/AnalyticsView.vue index 4a11576..6423387 100644 --- a/frontend/src/AnalyticsView.vue +++ b/frontend/src/AnalyticsView.vue @@ -233,7 +233,6 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c -

no crawler hits recorded yet

diff --git a/frontend/src/VisitorCharts.vue b/frontend/src/VisitorCharts.vue index f9ce7a9..8d43f1e 100644 --- a/frontend/src/VisitorCharts.vue +++ b/frontend/src/VisitorCharts.vue @@ -68,8 +68,8 @@ const viewChart = computed(() => buildChart(viewSeries.value, now.value)) diff --git a/frontend/src/analytics/time.js b/frontend/src/analytics/time.js index 6e478d6..343b458 100644 --- a/frontend/src/analytics/time.js +++ b/frontend/src/analytics/time.js @@ -63,9 +63,24 @@ export function sumRange(raw, t0, t1) { export function weeklySeries(buckets) { const raw = rawTimes(buckets) const times = Object.keys(raw).map(Number) - if (!times.length) return null const now = Date.now() const thisMonday = mondayUTC(now) + if (!times.length) { + const points = [] + const end = Math.min(thisMonday + WEEK, Math.floor(now / MIN5) * MIN5 + MIN5) + for (let t = thisMonday; t < end; t += MIN5) { + points.push({ t, count: 0 }) + } + return { + series: [{ points, label: `Week ${isoWeek(thisMonday)}`, opacity: 1, area: true }], + t0: thisMonday, + t1: thisMonday + WEEK, + rate: HOUR / MIN5, + binMinutes: 5, + unitMinutes: 60, + unit: 'hour', + } + } const oldest = Math.min(...times) // Weeks back as far as the data reaches: difference in Monday indices. const available = (thisMonday - mondayUTC(oldest)) / WEEK + 1 @@ -113,13 +128,27 @@ export function weeklySeries(buckets) { export function rollingSeries(buckets, rangeKey) { const raw = rawTimes(buckets) const times = Object.keys(raw).map(Number) - if (!times.length) return null const { span, bucket, minSpan = 0 } = RANGES[rangeKey] const t1 = Date.now() - const earliest = Math.min(...times) const t0 = Math.floor((span != null ? t1 - span - : Math.min(earliest, t1 - minSpan)) / DAY) * DAY + : Math.min(times.length ? Math.min(...times) : Infinity, t1 - minSpan)) / DAY) * DAY + if (!times.length) { + const bucketMs = t1 - t0 <= 31 * DAY ? Math.min(bucket, 6 * HOUR) : bucket + const points = [] + for (let t = t0; t < t1; t += bucketMs) { + points.push({ t, count: 0 }) + } + return { + series: [{ points, label: '', opacity: 1, area: true }], + t0, + t1, + rate: DAY / bucketMs, + binMinutes: bucketMs / 60e3, + unitMinutes: 24 * 60, + unit: 'day', + } + } // The bucket follows the actual window length, not the range key: when // "all" is capped to its 30-day minimum it covers the very window "month" // does, and daily bins would draw a different curve over the same data