Skill: Status Page

## Overview

7 stars

Best use case

Skill: Status Page is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

## Overview

Teams using Skill: Status Page should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/status-page/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/web-applications/status-page/skills/status-page/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/status-page/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Skill: Status Page Compares

Feature / AgentSkill: Status PageStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

## Overview

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Skill: Status Page

## Overview

Core domain skill for the self-hosted status page. Covers the public status API, overall status computation, uptime bar generation, incident lifecycle, and the admin dashboard data model.

## Overall Status Computation

```ts
type ServiceStatus = 'up' | 'down' | 'timeout' | 'error';

type OverallStatus = 'operational' | 'degraded' | 'outage' | 'maintenance';

function computeOverallStatus(services: Array<{ status: ServiceStatus }>): OverallStatus {
  if (services.length === 0) return 'operational';
  const statuses = services.map(s => s.status);
  if (statuses.every(s => s === 'up')) return 'operational';
  if (statuses.every(s => s !== 'up')) return 'outage';
  return 'degraded';
}
```

## Uptime Calculation

```ts
// api/src/lib/uptime.ts
import type { Database } from 'better-sqlite3';

export function calculateUptime(db: Database, serviceId: number, days: number): number {
  const row = db.prepare(`
    SELECT
      COUNT(*) AS total,
      SUM(CASE WHEN status = 'up' THEN 1 ELSE 0 END) AS up_count
    FROM checks
    WHERE service_id = ?
      AND checked_at >= datetime('now', '-' || ? || ' days')
  `).get(serviceId, days) as { total: number; up_count: number } | undefined;

  if (!row || row.total === 0) return 100; // no data = assume operational
  return Math.round((row.up_count / row.total) * 1000) / 10; // one decimal place
}
```

## Uptime Bar Generation

```ts
export function buildUptimeBar(
  db: Database,
  serviceId: number,
  days: number,
): string[] {
  const rows = db.prepare(`
    SELECT
      date(checked_at) AS day,
      SUM(CASE WHEN status = 'up'   THEN 1 ELSE 0 END) AS up_count,
      SUM(CASE WHEN status = 'down' THEN 1 ELSE 0 END) AS down_count,
      SUM(CASE WHEN status IN ('timeout','error') THEN 1 ELSE 0 END) AS warn_count,
      COUNT(*) AS total
    FROM checks
    WHERE service_id = ?
      AND checked_at >= datetime('now', '-' || ? || ' days')
    GROUP BY date(checked_at)
    ORDER BY day ASC
  `).all(serviceId, days) as Array<{
    day: string; up_count: number; down_count: number; warn_count: number; total: number;
  }>;

  const byDay = new Map(rows.map(r => [r.day, r]));

  const result: string[] = [];
  for (let i = days - 1; i >= 0; i--) {
    const d = new Date();
    d.setDate(d.getDate() - i);
    const key = d.toISOString().slice(0, 10);
    const row = byDay.get(key);
    if (!row) {
      result.push('no-data');
    } else if (row.down_count > 0) {
      result.push('down');
    } else if (row.warn_count > row.up_count) {
      result.push('timeout');
    } else {
      result.push('up');
    }
  }
  return result;
}
```

## Public Status Endpoint

```ts
// api/src/routes/public.ts
router.get('/status', (req, res) => {
  const services = db.prepare(`
    SELECT s.*,
      (SELECT status FROM checks WHERE service_id = s.id ORDER BY checked_at DESC LIMIT 1) AS current_status,
      (SELECT response_ms FROM checks WHERE service_id = s.id ORDER BY checked_at DESC LIMIT 1) AS last_response_ms,
      (SELECT checked_at FROM checks WHERE service_id = s.id ORDER BY checked_at DESC LIMIT 1) AS last_checked_at
    FROM services s
    WHERE s.is_active = 1
    ORDER BY s.display_order ASC, s.id ASC
  `).all() as ServiceRow[];

  const siteName = db.prepare(`SELECT value FROM site_settings WHERE key = 'site_name'`).get() as { value: string } | undefined;

  const result = services.map(s => ({
    id: s.id,
    name: s.name,
    description: s.description,
    status: s.current_status ?? 'no-data',
    uptime30d: calculateUptime(db, s.id, 30),
    uptimeBar: buildUptimeBar(db, s.id, 90),
    lastCheck: s.last_checked_at ? {
      status: s.current_status,
      responseMs: s.last_response_ms,
      checkedAt: s.last_checked_at,
    } : null,
  }));

  res.json({
    siteName: siteName?.value ?? 'Status',
    overallStatus: computeOverallStatus(result),
    services: result,
    activeIncidents: getActiveIncidents(db),
  });
});
```

## Auto-Incident Logic

```ts
// api/src/lib/checker.ts (excerpt)
function maybeOpenAutoIncident(db: Database, serviceId: number): void {
  const recent = db.prepare(`
    SELECT status FROM checks
    WHERE service_id = ?
    ORDER BY checked_at DESC
    LIMIT 3
  `).all(serviceId) as Array<{ status: string }>;

  if (recent.length < 3) return;
  if (recent.some(r => r.status === 'up')) return;

  // Check no open incident already exists
  const open = db.prepare(`
    SELECT id FROM incidents
    WHERE service_id = ? AND resolved_at IS NULL
  `).get(serviceId);
  if (open) return;

  const svc = db.prepare('SELECT name FROM services WHERE id = ?').get(serviceId) as { name: string };

  const incident = db.prepare(`
    INSERT INTO incidents (service_id, title, status, impact, auto_opened)
    VALUES (?, ?, 'investigating', 'minor', 1)
    RETURNING *
  `).get(serviceId, `${svc.name} is down`) as { id: number };

  db.prepare(`
    INSERT INTO incident_updates (incident_id, message, status)
    VALUES (?, ?, 'investigating')
  `).run(incident.id, 'Automatic incident opened: 3 consecutive failed health checks.');
}
```

## Incident Status Transitions

Valid transitions:
- `investigating` -> `identified` | `monitoring` | `resolved`
- `identified` -> `monitoring` | `resolved`
- `monitoring` -> `resolved` | `identified`
- `resolved` is final

On posting an update with `status = 'resolved'`, set `resolved_at = NOW()` on the parent incident:

```ts
router.post('/:id/updates', requireAuth, (req, res) => {
  const { message, status } = req.body as { message: string; status: string };
  db.prepare(`
    INSERT INTO incident_updates (incident_id, message, status)
    VALUES (?, ?, ?)
  `).run(req.params.id, message, status);

  if (status === 'resolved') {
    db.prepare(`
      UPDATE incidents SET status = 'resolved', resolved_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
      WHERE id = ?
    `).run(req.params.id);
  } else {
    db.prepare(`UPDATE incidents SET status = ? WHERE id = ?`).run(status, req.params.id);
  }

  res.json({ ok: true });
});
```

## TypeScript Types

```ts
// api/src/types.ts
export interface Service {
  id: number;
  name: string;
  url: string;
  description: string | null;
  check_interval: number;
  timeout_seconds: number;
  expected_status: number;
  auto_incident: number;
  is_active: number;
  display_order: number;
  created_at: string;
}

export interface Check {
  id: number;
  service_id: number;
  checked_at: string;
  status: 'up' | 'down' | 'timeout' | 'error';
  status_code: number | null;
  response_ms: number | null;
  error_message: string | null;
}

export interface Incident {
  id: number;
  service_id: number | null;
  title: string;
  status: 'investigating' | 'identified' | 'monitoring' | 'resolved';
  impact: 'none' | 'minor' | 'major' | 'critical';
  started_at: string;
  resolved_at: string | null;
  auto_opened: number;
}

export interface IncidentUpdate {
  id: number;
  incident_id: number;
  message: string;
  status: 'investigating' | 'identified' | 'monitoring' | 'resolved';
  posted_at: string;
}
```

## UptimeBar React Component

```tsx
// web/src/components/UptimeBar.tsx
const STATUS_COLORS: Record<string, string> = {
  up:      'var(--uptime-seg-up)',
  down:    'var(--uptime-seg-down)',
  timeout: 'var(--uptime-seg-timeout)',
  error:   'var(--uptime-seg-timeout)',
  'no-data': 'var(--uptime-seg-no-data)',
};

export function UptimeBar({ segments }: { segments: string[] }) {
  return (
    <div style={{ display: 'flex', gap: 2, height: 24 }}>
      {segments.map((s, i) => (
        <div
          key={i}
          style={{
            flex: 1,
            minWidth: 3,
            maxWidth: 6,
            borderRadius: 2,
            background: STATUS_COLORS[s] ?? STATUS_COLORS['no-data'],
          }}
          title={`Day ${segments.length - i}: ${s}`}
          aria-label={`Day ${segments.length - i}: ${s}`}
        />
      ))}
    </div>
  );
}
```

## pnpm Commands

```bash
pnpm install              # install all workspace deps
pnpm --filter api dev     # run API with ts-node-dev (port 3001)
pnpm --filter web dev     # Vite dev server (port 5173)
pnpm --filter api test    # Vitest
pnpm --filter api build   # compile TS
pnpm --filter web build   # Vite build
pnpm typecheck            # tsc --noEmit in all packages
pnpm lint                 # ESLint
```

Related Skills

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".

poll-builder

7
from heldernoid/agentic-build-templates

Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.

Skill: personal-finance

7
from heldernoid/agentic-build-templates

## Overview

Skill: csv-import

7
from heldernoid/agentic-build-templates

## Overview

Skill: Syntax Highlighting

7
from heldernoid/agentic-build-templates

## Purpose

Skill: Pastebin Core

7
from heldernoid/agentic-build-templates

## Purpose

Skill: Cost Reporting

7
from heldernoid/agentic-build-templates

## Overview