From 45e9b62d833ad152af904d7b7650e0db15fe4209 Mon Sep 17 00:00:00 2001 From: VrianCao <45995071+VrianCao@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:41:34 +0800 Subject: [PATCH] Use true 60-day availability bars --- Develop/LOCAL-TESTING.md | 6 ++--- Develop/REVIEW.md | 2 +- apps/web/src/components/MonitorCard.tsx | 4 ++-- apps/web/src/i18n/messages.ts | 24 +++++++++---------- apps/worker/scripts/seed-local.sql | 6 ++--- apps/worker/src/public/data.ts | 2 +- apps/worker/src/public/homepage.ts | 4 ++-- apps/worker/src/public/status-refresh.ts | 2 +- apps/worker/src/schemas/public-status.ts | 4 ++-- apps/worker/src/snapshots/public-homepage.ts | 2 +- .../internal-sharded-public-snapshot.test.ts | 2 +- .../test/snapshots-public-homepage.test.ts | 2 +- 12 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Develop/LOCAL-TESTING.md b/Develop/LOCAL-TESTING.md index 603451bf..b5f267b7 100644 --- a/Develop/LOCAL-TESTING.md +++ b/Develop/LOCAL-TESTING.md @@ -611,9 +611,9 @@ pnpm format --- -### Public Status (30-day uptime bars) +### Public Status (60-day uptime bars) -The public status page now uses the daily rollup table (`monitor_daily_rollups`) to render a 30-day uptime bar per monitor. +The public status page now uses the daily rollup table (`monitor_daily_rollups`) to render a 60-day uptime bar per monitor. To generate rollup data locally (yesterday's rollup): @@ -621,7 +621,7 @@ To generate rollup data locally (yesterday's rollup): curl "http://localhost:8787/__scheduled?cron=0+0+*+*+*" ``` -The UI will show up to 30 bars when data exists. If there is no rollup data yet, uptime fields may be empty. +The UI will show up to 60 bars when data exists. If there is no rollup data yet, uptime fields may be empty. ## 常见问题 diff --git a/Develop/REVIEW.md b/Develop/REVIEW.md index bd78460a..0677b797 100644 --- a/Develop/REVIEW.md +++ b/Develop/REVIEW.md @@ -8,7 +8,7 @@ The following are implemented and deployed: - Worker: Hono + Zod API (`/api/v1/public/*`, `/api/v1/admin/*`), scheduled monitor engine, retention, daily rollups - Storage: D1 schema + migrations (monitors/state/results/outages/incidents/maintenance/notifications/settings/snapshots) -- Public: status snapshot, status page payload (monitors + 30d uptime bars + incidents + maintenance), latency/uptime/outages endpoints +- Public: status snapshot, status page payload (monitors + 60d uptime bars + incidents + maintenance), latency/uptime/outages endpoints - Admin: monitor CRUD + test, notification channel CRUD + test, incidents CRUD + updates + resolve, maintenance windows CRUD, analytics + CSV exports, settings - CI/CD: GitHub Actions (lint + typecheck + test + auto-deploy) diff --git a/apps/web/src/components/MonitorCard.tsx b/apps/web/src/components/MonitorCard.tsx index facaa05c..30902208 100644 --- a/apps/web/src/components/MonitorCard.tsx +++ b/apps/web/src/components/MonitorCard.tsx @@ -21,7 +21,7 @@ import { } from '../utils/uptime'; const HEARTBEAT_BARS = 60; -const AVAILABILITY_BARS = 30; +const AVAILABILITY_BARS = 60; type PublicMonitorLike = Pick< PublicMonitor, @@ -189,7 +189,7 @@ export function MonitorCard({ - {/* Availability (30d) */} + {/* Availability (60d) */}
{t('monitor_card.availability_30d')} diff --git a/apps/web/src/i18n/messages.ts b/apps/web/src/i18n/messages.ts index 505942db..ea2e2ef2 100644 --- a/apps/web/src/i18n/messages.ts +++ b/apps/web/src/i18n/messages.ts @@ -116,12 +116,12 @@ const en = { 'monitor_card.never_checked': 'Never checked', 'monitor_card.stale': 'stale', - 'monitor_card.availability_30d': '30-day availability', + 'monitor_card.availability_30d': '60-day availability', 'monitor_card.last_checks': 'Last {count} checks', 'monitor_card.fast': 'fast', 'monitor_card.avg': 'avg', 'monitor_card.slow': 'slow', - 'monitor_card.uptime_title': '30-day availability', + 'monitor_card.uptime_title': '60-day availability', 'heartbeat.to': 'to', 'heartbeat.sample_checks': '{count} checks', @@ -168,7 +168,7 @@ const en = { 'Current API does not support saving locale yet. This browser will remember your choice locally.', 'admin_settings.uptime_rating.title': 'Uptime Color Rating', 'admin_settings.uptime_rating.help': - 'Controls the color thresholds for daily bars and 30d uptime.', + 'Controls the color thresholds for daily bars and 60d uptime.', 'admin_settings.uptime_rating.level_1': 'Level 1 - Personal / Hobby', 'admin_settings.uptime_rating.level_2': 'Level 2 - Basic Business / Content', 'admin_settings.uptime_rating.level_3': 'Level 3 - Production / SaaS', @@ -569,12 +569,12 @@ const zhCn: LocaleMessages = { 'monitor_card.never_checked': '尚未探测', 'monitor_card.stale': '数据过期', - 'monitor_card.availability_30d': '近 30 天可用性', + 'monitor_card.availability_30d': '近 60 天可用性', 'monitor_card.last_checks': '最近 {count} 次探测', 'monitor_card.fast': '最快', 'monitor_card.avg': '平均', 'monitor_card.slow': '最慢', - 'monitor_card.uptime_title': '近 30 天可用性', + 'monitor_card.uptime_title': '近 60 天可用性', 'heartbeat.to': '至', 'heartbeat.sample_checks': '{count} 次探测', @@ -619,7 +619,7 @@ const zhCn: LocaleMessages = { 'admin_settings.locale.local_only': '当前 API 暂不支持保存语言设置,本浏览器会在本地记住你的选择。', 'admin_settings.uptime_rating.title': '可用率颜色评级', - 'admin_settings.uptime_rating.help': '用于控制每日柱状图与 30 天可用率的颜色阈值。', + 'admin_settings.uptime_rating.help': '用于控制每日柱状图与 60 天可用率的颜色阈值。', 'admin_settings.uptime_rating.level_1': '等级 1 - 个人 / 爱好项目', 'admin_settings.uptime_rating.level_2': '等级 2 - 基础业务 / 内容站', 'admin_settings.uptime_rating.level_3': '等级 3 - 生产环境 / SaaS', @@ -980,12 +980,12 @@ const zhTw: LocaleMessages = { 'monitor_card.never_checked': '尚未探測', 'monitor_card.stale': '資料過期', - 'monitor_card.availability_30d': '近 30 天可用性', + 'monitor_card.availability_30d': '近 60 天可用性', 'monitor_card.last_checks': '最近 {count} 次探測', 'monitor_card.fast': '最快', 'monitor_card.avg': '平均', 'monitor_card.slow': '最慢', - 'monitor_card.uptime_title': '近 30 天可用性', + 'monitor_card.uptime_title': '近 60 天可用性', 'heartbeat.to': '至', 'heartbeat.sample_checks': '{count} 次探測', @@ -1162,12 +1162,12 @@ const ja: LocaleMessages = { 'monitor_card.never_checked': '未チェック', 'monitor_card.stale': '古いデータ', - 'monitor_card.availability_30d': '過去30日の可用性', + 'monitor_card.availability_30d': '過去60日の可用性', 'monitor_card.last_checks': '直近 {count} 回のチェック', 'monitor_card.fast': '最速', 'monitor_card.avg': '平均', 'monitor_card.slow': '最遅', - 'monitor_card.uptime_title': '過去30日の可用性', + 'monitor_card.uptime_title': '過去60日の可用性', 'heartbeat.to': '〜', 'heartbeat.sample_checks': '{count} 回のチェック', @@ -1344,12 +1344,12 @@ const es: LocaleMessages = { 'monitor_card.never_checked': 'Nunca verificado', 'monitor_card.stale': 'desactualizado', - 'monitor_card.availability_30d': 'Disponibilidad de 30 dias', + 'monitor_card.availability_30d': 'Disponibilidad de 60 dias', 'monitor_card.last_checks': 'Ultimos {count} chequeos', 'monitor_card.fast': 'rapido', 'monitor_card.avg': 'prom', 'monitor_card.slow': 'lento', - 'monitor_card.uptime_title': 'Disponibilidad de 30 dias', + 'monitor_card.uptime_title': 'Disponibilidad de 60 dias', 'heartbeat.to': 'a', 'heartbeat.sample_checks': '{count} chequeos', diff --git a/apps/worker/scripts/seed-local.sql b/apps/worker/scripts/seed-local.sql index c248e1b9..9234b538 100644 --- a/apps/worker/scripts/seed-local.sql +++ b/apps/worker/scripts/seed-local.sql @@ -417,7 +417,7 @@ VALUES CAST(strftime('%s','now') AS INTEGER) - 2400 ); --- 7) Settings + 30-day rollup samples (status page/admin analytics warm start). +-- 7) Settings + 60-day rollup samples (status page/admin analytics warm start). INSERT OR REPLACE INTO settings (key, value) VALUES ('site_title', 'Uptimer Local Demo'); INSERT OR REPLACE INTO settings (key, value) VALUES ('site_description', ''); INSERT OR REPLACE INTO settings (key, value) VALUES ('site_timezone', 'UTC'); @@ -437,7 +437,7 @@ WITH RECURSIVE day_seq(i, day_start) AS ( i + 1, day_start - 86400 FROM day_seq - WHERE i < 29 + WHERE i < 59 ) INSERT INTO monitor_daily_rollups ( monitor_id, @@ -487,7 +487,7 @@ WITH RECURSIVE day_seq(i, day_start) AS ( i + 1, day_start - 86400 FROM day_seq - WHERE i < 29 + WHERE i < 59 ) INSERT INTO monitor_daily_rollups ( monitor_id, diff --git a/apps/worker/src/public/data.ts b/apps/worker/src/public/data.ts index e840e514..610eee01 100644 --- a/apps/worker/src/public/data.ts +++ b/apps/worker/src/public/data.ts @@ -125,7 +125,7 @@ export const STATUS_ACTIVE_INCIDENT_LIMIT = 5; export const STATUS_ACTIVE_MAINTENANCE_LIMIT = 3; export const STATUS_UPCOMING_MAINTENANCE_LIMIT = 5; -const UPTIME_DAYS = 30; +const UPTIME_DAYS = 60; const HEARTBEAT_POINTS = 60; const D1_MAX_SQL_VARIABLES = 100; const TODAY_PARTIAL_UPTIME_FIXED_BINDINGS = 2; diff --git a/apps/worker/src/public/homepage.ts b/apps/worker/src/public/homepage.ts index 8fdf822f..86db53cb 100644 --- a/apps/worker/src/public/homepage.ts +++ b/apps/worker/src/public/homepage.ts @@ -52,7 +52,7 @@ import { } from './visibility'; const PREVIEW_BATCH_LIMIT = 50; -const UPTIME_DAYS = 30; +const UPTIME_DAYS = 60; const HEARTBEAT_POINTS = 60; const HOMEPAGE_FAST_PATCH_BASE_MAX_AGE_SECONDS = 75; const HOMEPAGE_FAST_PATCH_UPDATE_GRACE_SECONDS = 15; @@ -923,7 +923,7 @@ async function buildHomepageMonitorCardsFromRows( const placeholders = buildNumberedPlaceholders(selectedIds.length); const todayStartAt = utcDayStart(now); // Always compute a partial "today" bucket whenever we're inside the current UTC day. - // This avoids missing uptime strips / 30d uptime immediately after a fresh deployment. + // This avoids missing uptime strips / 60d uptime immediately after a fresh deployment. const needsToday = rangeEnd > rangeEndFullDays; const monitors = rows.map((row) => toHomepageMonitorCard(row, now, maintenanceMonitorIds)); const baseMonitorsById = baseSnapshot ? getHomepageSnapshotMonitorById(baseSnapshot) : null; diff --git a/apps/worker/src/public/status-refresh.ts b/apps/worker/src/public/status-refresh.ts index 50ec78d7..843c5540 100644 --- a/apps/worker/src/public/status-refresh.ts +++ b/apps/worker/src/public/status-refresh.ts @@ -29,7 +29,7 @@ import { const STATUS_FAST_PATCH_BASE_MAX_AGE_SECONDS = 75; const STATUS_FAST_PATCH_UPDATE_GRACE_SECONDS = 15; const STATUS_FAST_PATCH_MAX_STALE_SECONDS = 10 * 60; -const UPTIME_DAYS = 30; +const UPTIME_DAYS = 60; type StatusMonitor = PublicStatusResponse['monitors'][number]; type StatusPublicSettings = SettingsResponse['settings']; diff --git a/apps/worker/src/schemas/public-status.ts b/apps/worker/src/schemas/public-status.ts index 159eaf41..871ce1b9 100644 --- a/apps/worker/src/schemas/public-status.ts +++ b/apps/worker/src/schemas/public-status.ts @@ -100,10 +100,10 @@ const publicMonitorSchema = z.object({ // Last N checks (bounded in payload) for heartbeat bar. heartbeats: z.array(heartbeatSchema).optional().default([]), - // 30-day availability computed from daily rollups (UTC full days). + // 60-day availability computed from daily rollups (UTC full days). uptime_30d: uptimeSummarySchema.nullable(), - // 30 daily points (oldest -> newest). Each entry is the day's total uptime. + // 60 daily points (oldest -> newest). Each entry is the day's total uptime. uptime_days: z.array(uptimeDaySchema), }); diff --git a/apps/worker/src/snapshots/public-homepage.ts b/apps/worker/src/snapshots/public-homepage.ts index d30a3fc7..b2c98676 100644 --- a/apps/worker/src/snapshots/public-homepage.ts +++ b/apps/worker/src/snapshots/public-homepage.ts @@ -306,7 +306,7 @@ function renderHomepageMonitorPreloadCard( ? `Last checked: ${formatTimestamp(monitor.last_checked_at)}` : 'Never checked'; - return `
${escapeHtml(monitor.name)}
${escapeHtml(monitor.type)}
${escapeHtml(uptimePct)}${statusLabel}
Availability (30d)
${buildUptimeStripSvg(monitor.uptime_day_strip)}
Recent checks
${buildHeartbeatStripSvg(monitor.heartbeat_strip)}
${lastCheckedLabel}
`; + return `
${escapeHtml(monitor.name)}
${escapeHtml(monitor.type)}
${escapeHtml(uptimePct)}${statusLabel}
Availability (60d)
${buildUptimeStripSvg(monitor.uptime_day_strip)}
Recent checks
${buildHeartbeatStripSvg(monitor.heartbeat_strip)}
${lastCheckedLabel}
`; } export function renderHomepageMonitorPreloadCardFragment( diff --git a/apps/worker/test/internal-sharded-public-snapshot.test.ts b/apps/worker/test/internal-sharded-public-snapshot.test.ts index 6ba6c18f..b50d1454 100644 --- a/apps/worker/test/internal-sharded-public-snapshot.test.ts +++ b/apps/worker/test/internal-sharded-public-snapshot.test.ts @@ -1112,7 +1112,7 @@ describe('internal sharded public snapshot fragment seed route', () => { [HOMEPAGE_ARTIFACT_MONITOR_FRAGMENTS_KEY, 'monitor:1'], ]); const artifactBody = JSON.parse(writes[1]![3] as string) as { card_html?: string }; - expect(artifactBody.card_html).toContain('Availability (30d)'); + expect(artifactBody.card_html).toContain('Availability (60d)'); }); it('seeds bounded status fragments from the current static snapshot', async () => { diff --git a/apps/worker/test/snapshots-public-homepage.test.ts b/apps/worker/test/snapshots-public-homepage.test.ts index bb7bf1a4..078e3fea 100644 --- a/apps/worker/test/snapshots-public-homepage.test.ts +++ b/apps/worker/test/snapshots-public-homepage.test.ts @@ -156,7 +156,7 @@ describe('snapshots/public-homepage', () => { expect(body.name).toBe(''); expect(body.group_name).toBe('Core'); expect(body.card_html).toContain('<API & edge>'); - expect(body.card_html).toContain('Availability (30d)'); + expect(body.card_html).toContain('Availability (60d)'); expect(body.card_html).toContain('