Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Develop/LOCAL-TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -611,17 +611,17 @@ 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):

```bash
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.

## 常见问题

Expand Down
2 changes: 1 addition & 1 deletion Develop/REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/MonitorCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../utils/uptime';

const HEARTBEAT_BARS = 60;
const AVAILABILITY_BARS = 30;
const AVAILABILITY_BARS = 60;

type PublicMonitorLike = Pick<
PublicMonitor,
Expand Down Expand Up @@ -189,7 +189,7 @@ export function MonitorCard({
</div>
</div>

{/* Availability (30d) */}
{/* Availability (60d) */}
<div>
<div className="mb-2 text-[11px] text-slate-400 dark:text-slate-500">
{t('monitor_card.availability_30d')}
Expand Down
24 changes: 12 additions & 12 deletions apps/web/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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} 次探测',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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} 次探測',
Expand Down Expand Up @@ -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} 回のチェック',
Expand Down Expand Up @@ -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',
Expand Down
6 changes: 3 additions & 3 deletions apps/worker/scripts/seed-local.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion apps/worker/src/public/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions apps/worker/src/public/homepage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion apps/worker/src/public/status-refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down
4 changes: 2 additions & 2 deletions apps/worker/src/schemas/public-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
});

Expand Down
2 changes: 1 addition & 1 deletion apps/worker/src/snapshots/public-homepage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ function renderHomepageMonitorPreloadCard(
? `Last checked: ${formatTimestamp(monitor.last_checked_at)}`
: 'Never checked';

return `<article class="card"><div class="row"><div class="lhs"><span class="dot dot-${status}"></span><div class="ut"><div class="mn">${escapeHtml(monitor.name)}</div><div class="mt">${escapeHtml(monitor.type)}</div></div></div><div class="rhs"><span class="up">${escapeHtml(uptimePct)}</span><span class="sb sb-${status}">${statusLabel}</span></div></div><div><div class="lbl">Availability (30d)</div><div class="strip">${buildUptimeStripSvg(monitor.uptime_day_strip)}</div></div><div><div class="lbl">Recent checks</div><div class="strip">${buildHeartbeatStripSvg(monitor.heartbeat_strip)}</div></div><div class="ft">${lastCheckedLabel}</div></article>`;
return `<article class="card"><div class="row"><div class="lhs"><span class="dot dot-${status}"></span><div class="ut"><div class="mn">${escapeHtml(monitor.name)}</div><div class="mt">${escapeHtml(monitor.type)}</div></div></div><div class="rhs"><span class="up">${escapeHtml(uptimePct)}</span><span class="sb sb-${status}">${statusLabel}</span></div></div><div><div class="lbl">Availability (60d)</div><div class="strip">${buildUptimeStripSvg(monitor.uptime_day_strip)}</div></div><div><div class="lbl">Recent checks</div><div class="strip">${buildHeartbeatStripSvg(monitor.heartbeat_strip)}</div></div><div class="ft">${lastCheckedLabel}</div></article>`;
}

export function renderHomepageMonitorPreloadCardFragment(
Expand Down
2 changes: 1 addition & 1 deletion apps/worker/test/internal-sharded-public-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/worker/test/snapshots-public-homepage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ describe('snapshots/public-homepage', () => {
expect(body.name).toBe('<API & edge>');
expect(body.group_name).toBe('Core');
expect(body.card_html).toContain('&lt;API &amp; edge&gt;');
expect(body.card_html).toContain('Availability (30d)');
expect(body.card_html).toContain('Availability (60d)');
expect(body.card_html).toContain('<path d="M');
expect(body.card_html).not.toContain('<rect ');
});
Expand Down
Loading