@astrale-os/adapter-cloudflare 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +202 -0
  2. package/package.json +4 -4
  3. package/template/.agents/skills/astrale-cli/SKILL.md +633 -0
  4. package/template/.domain-studio/comments.json +4 -0
  5. package/template/.env.example +3 -6
  6. package/template/CLAUDE.md +8 -6
  7. package/template/README.md +28 -20
  8. package/template/client/__tests__/app.test.tsx +39 -248
  9. package/template/client/__tests__/harness.ts +0 -47
  10. package/template/client/__tests__/shell.test.ts +46 -0
  11. package/template/client/package.json +1 -1
  12. package/template/client/src/app.tsx +6 -8
  13. package/template/client/src/shell/index.ts +1 -0
  14. package/template/client/src/shell/transformers.ts +36 -33
  15. package/template/client/src/shell/use-node.ts +21 -13
  16. package/template/client/src/ui/index.ts +4 -7
  17. package/template/core/README.md +23 -0
  18. package/template/deps.ts +10 -9
  19. package/template/domain.ts +11 -9
  20. package/template/env.ts +3 -5
  21. package/template/functions/index.ts +14 -37
  22. package/template/icons.ts +25 -0
  23. package/template/integrations/README.md +22 -0
  24. package/template/package.json +3 -3
  25. package/template/runtime/index.ts +25 -72
  26. package/template/schema/index.ts +10 -10
  27. package/template/tsconfig.json +1 -0
  28. package/template/views/index.ts +15 -12
  29. package/template/client/__tests__/kernel.test.ts +0 -77
  30. package/template/client/__tests__/seam.test.tsx +0 -115
  31. package/template/client/src/status/components/StatusCard.tsx +0 -160
  32. package/template/client/src/status/components/index.ts +0 -1
  33. package/template/client/src/status/hooks/index.ts +0 -3
  34. package/template/client/src/status/hooks/useCheck.mutation.ts +0 -16
  35. package/template/client/src/status/hooks/useCheckable.query.ts +0 -72
  36. package/template/client/src/status/index.ts +0 -7
  37. package/template/client/src/status/status.api.ts +0 -29
  38. package/template/client/src/status/status.mappers.ts +0 -102
  39. package/template/client/src/status/status.types.ts +0 -26
  40. package/template/client/src/ui/StatusBadge.tsx +0 -31
  41. package/template/client/src/views/status.tsx +0 -28
  42. package/template/core/monitor/health.ts +0 -34
  43. package/template/core/monitor/index.ts +0 -9
  44. package/template/core/monitor/keys.ts +0 -41
  45. package/template/core/monitor/node.ts +0 -63
  46. package/template/integrations/prober/http.ts +0 -32
  47. package/template/integrations/prober/mock.ts +0 -18
  48. package/template/integrations/prober/port.ts +0 -26
  49. package/template/integrations/prober/registry.ts +0 -65
  50. package/template/pnpm-lock.yaml +0 -2780
  51. package/template/runtime/monitoring/index.ts +0 -8
  52. package/template/runtime/monitoring/monitor/check.ts +0 -29
  53. package/template/runtime/monitoring/monitor/index.ts +0 -10
  54. package/template/runtime/monitoring/monitor/seed.ts +0 -104
  55. package/template/runtime/monitoring/monitor/watch.ts +0 -31
  56. package/template/runtime/monitoring/page/add.ts +0 -21
  57. package/template/runtime/monitoring/page/check.ts +0 -50
  58. package/template/runtime/monitoring/page/create.ts +0 -24
  59. package/template/runtime/monitoring/page/index.ts +0 -9
  60. package/template/runtime/shared.ts +0 -21
  61. package/template/schema/monitor.ts +0 -92
  62. package/template/views/status-page.ts +0 -16
  63. package/template/views/welcome.ts +0 -35
@@ -1,115 +0,0 @@
1
- /**
2
- * What the presentation/logic seam unlocks:
3
- *
4
- * 1. PRESENTATION components (`@/ui`) test with ZERO infrastructure — no fake
5
- * shell, no fake kernel, no session. Just props in, DOM out. (Contrast
6
- * app.test.tsx, which stands up a handshake + kernel stub to reach the same
7
- * markup.)
8
- * 2. FEATURE hooks (`@/status`) test against a bare `KernelClient` (the fake
9
- * kernel fetch stub) with no handshake — the write lifecycle and the
10
- * reload signal are verified once, in isolation.
11
- */
12
- import { act, cleanup, render, renderHook, screen, waitFor } from '@testing-library/react'
13
- import { afterEach, describe, expect, it } from 'vitest'
14
-
15
- import { useCheck, useCheckable } from '@/status'
16
- import { StatusBadge } from '@/ui'
17
-
18
- import { checkableNode, fakeKernelSession, installFakeKernel, ok } from './harness'
19
-
20
- afterEach(cleanup)
21
-
22
- describe('presentation is pure (no kernel, no session, no shell)', () => {
23
- it('StatusBadge renders the label and tone class for each status', () => {
24
- const { rerender } = render(<StatusBadge status="up" />)
25
- expect(screen.getByText('UP').className).toContain('status-up')
26
-
27
- rerender(<StatusBadge status="degraded" />)
28
- expect(screen.getByText('DEGRADED').className).toContain('status-degraded')
29
-
30
- rerender(<StatusBadge status="down" />)
31
- expect(screen.getByText('DOWN').className).toContain('status-down')
32
-
33
- rerender(<StatusBadge status="weird" />)
34
- expect(screen.getByText('UNKNOWN').className).toContain('status-unknown')
35
- })
36
- })
37
-
38
- describe('useCheckable — query + reload signal', () => {
39
- it('loads the record, then exposes reloading across a reload()', async () => {
40
- const session = fakeKernelSession()
41
- let status = 'down'
42
- const kernel = installFakeKernel(() => ok(checkableNode({ id: 'page-1', name: 'API', status })))
43
-
44
- try {
45
- const { result } = renderHook(() => useCheckable(session, 'page-1'))
46
- await waitFor(() => expect(result.current.state).toBe('ok'))
47
- expect(result.current.record?.status).toBe('down')
48
- expect(result.current.reloading).toBe(false)
49
-
50
- // Server-side status flips; a reload picks it up and flags `reloading`.
51
- status = 'up'
52
- act(() => {
53
- result.current.reload()
54
- })
55
- expect(result.current.reloading).toBe(true)
56
-
57
- await waitFor(() => expect(result.current.reloading).toBe(false))
58
- expect(result.current.record?.status).toBe('up')
59
- } finally {
60
- kernel.restore()
61
- }
62
- })
63
-
64
- it('surfaces a node-load error', async () => {
65
- const session = fakeKernelSession()
66
- const kernel = installFakeKernel(() => ({ error: { code: 3002, message: 'Path not found' } }))
67
- try {
68
- const { result } = renderHook(() => useCheckable(session, 'missing'))
69
- await waitFor(() => expect(result.current.state).toBe('error'))
70
- expect(result.current.message).toMatch(/Path not found/)
71
- } finally {
72
- kernel.restore()
73
- }
74
- })
75
- })
76
-
77
- describe('useCheck — the shared write-lifecycle', () => {
78
- it('idle → running → done, then resolves true', async () => {
79
- const session = fakeKernelSession()
80
- const kernel = installFakeKernel(() => ok(null))
81
- try {
82
- const { result } = renderHook(() => useCheck(session, 'page-1'))
83
- expect(result.current.phase).toBe('idle')
84
-
85
- let returned: boolean | undefined
86
- await act(async () => {
87
- returned = await result.current.run()
88
- })
89
- expect(returned).toBe(true)
90
- expect(result.current.phase).toBe('done')
91
- expect(result.current.error).toBeNull()
92
- } finally {
93
- kernel.restore()
94
- }
95
- })
96
-
97
- it('captures a failure: phase failed, error set, resolves false', async () => {
98
- const session = fakeKernelSession()
99
- const kernel = installFakeKernel(() => ({
100
- error: { code: 2004, message: 'Permission denied' },
101
- }))
102
- try {
103
- const { result } = renderHook(() => useCheck(session, 'page-1'))
104
- let returned: boolean | undefined
105
- await act(async () => {
106
- returned = await result.current.run()
107
- })
108
- expect(returned).toBe(false)
109
- expect(result.current.phase).toBe('failed')
110
- expect(result.current.error).toMatch(/Permission denied/)
111
- } finally {
112
- kernel.restore()
113
- }
114
- })
115
- })
@@ -1,160 +0,0 @@
1
- /**
2
- * StatusCard — the status feature's one container. It owns the data/logic: loads
3
- * the node (`useCheckable`), wires the `check` write (`useCheck`), and composes
4
- * the presentation. Handles the query's loading/error/ok states; on `ok` it
5
- * renders a `Panel` with the check-error banner (if any) + the StatusBadge.
6
- * "Check now" runs the check then reloads the node so the fresh status renders.
7
- *
8
- * The generic surfaces come from the `@/ui` design system; the only domain-shaped
9
- * primitive is the shared `StatusBadge`.
10
- */
11
- import type { KernelClient } from '@/shell'
12
-
13
- import {
14
- EmptyState,
15
- ErrorBanner,
16
- ExternalLink,
17
- Mono,
18
- Panel,
19
- relativeTime,
20
- Spinner,
21
- StatusBadge,
22
- } from '@/ui'
23
-
24
- import type { WatchedMonitorRecord } from '../status.types'
25
-
26
- import { useCheck, useCheckable } from '../hooks'
27
-
28
- function latestCheckedAt(monitors: WatchedMonitorRecord[]): string | undefined {
29
- let latest = 0
30
- let latestIso: string | undefined
31
- for (const monitor of monitors) {
32
- if (!monitor.lastCheckedAt) continue
33
- const time = Date.parse(monitor.lastCheckedAt)
34
- if (!Number.isNaN(time) && time > latest) {
35
- latest = time
36
- latestIso = monitor.lastCheckedAt
37
- }
38
- }
39
- return latestIso
40
- }
41
-
42
- export function StatusCard({ session, nodeId }: { session: KernelClient; nodeId: string }) {
43
- const checkable = useCheckable(session, nodeId)
44
- const check = useCheck(session, nodeId)
45
-
46
- async function checkNow() {
47
- if (await check.run()) checkable.reload()
48
- }
49
-
50
- if (checkable.state === 'idle' || checkable.state === 'loading') {
51
- return <Spinner label="Loading…" />
52
- }
53
- if (checkable.state === 'error') {
54
- return <ErrorBanner>Failed to load: {checkable.message}</ErrorBanner>
55
- }
56
-
57
- const record = checkable.record!
58
- const checking = check.phase === 'running'
59
- const busy = checking || checkable.reloading
60
- const monitors = record.monitors
61
- const criticalCount = monitors.filter((monitor) => monitor.critical).length
62
- const downCount = monitors.filter((monitor) => monitor.status === 'down').length
63
- const lastChecked = latestCheckedAt(monitors)
64
- const checkButton = (
65
- <button type="button" className="check-btn" onClick={checkNow} disabled={busy}>
66
- {checking ? 'Checking...' : checkable.reloading ? 'Refreshing...' : 'Check now'}
67
- </button>
68
- )
69
-
70
- return (
71
- <Panel title={record.name} actions={checkButton}>
72
- {check.phase === 'failed' && check.error && (
73
- <ErrorBanner>Check failed: {check.error}</ErrorBanner>
74
- )}
75
- <div className="status-dashboard">
76
- <div className="status-overview">
77
- <div>
78
- <p className="status-label">Overall health</p>
79
- <StatusBadge status={record.status} />
80
- </div>
81
- <div className="summary-grid" aria-label="Monitoring summary">
82
- <div className="summary-cell">
83
- <span className="summary-value">{monitors.length}</span>
84
- <span className="summary-label">monitors</span>
85
- </div>
86
- <div className="summary-cell">
87
- <span className="summary-value">{criticalCount}</span>
88
- <span className="summary-label">critical</span>
89
- </div>
90
- <div className="summary-cell">
91
- <span className="summary-value">{downCount}</span>
92
- <span className="summary-label">down</span>
93
- </div>
94
- </div>
95
- </div>
96
-
97
- <div className="status-meta">
98
- <span>Page path</span>
99
- <Mono value={record.path} />
100
- <span>Last checked</span>
101
- <span className="mono" title={lastChecked}>
102
- {relativeTime(lastChecked)}
103
- </span>
104
- </div>
105
-
106
- <section className="monitor-section" aria-label="Watched monitors">
107
- <div className="monitor-section-head">
108
- <h3>Watched monitors</h3>
109
- {checkable.reloading && <span className="refreshing">Refreshing</span>}
110
- </div>
111
-
112
- {monitors.length === 0 ? (
113
- <EmptyState hint="StatusPage.add({ monitor, critical })">
114
- This page is not watching any monitors yet.
115
- </EmptyState>
116
- ) : (
117
- <div className="monitor-list">
118
- {monitors.map((monitor) => (
119
- <MonitorRow key={`${monitor.path}:${monitor.critical}`} monitor={monitor} />
120
- ))}
121
- </div>
122
- )}
123
- </section>
124
- </div>
125
- </Panel>
126
- )
127
- }
128
-
129
- function MonitorRow({ monitor }: { monitor: WatchedMonitorRecord }) {
130
- return (
131
- <article className="monitor-row">
132
- <div className="monitor-status">
133
- <StatusBadge status={monitor.status} />
134
- <span className={monitor.critical ? 'weight weight-critical' : 'weight'}>
135
- {monitor.critical ? 'Critical' : 'Standard'}
136
- </span>
137
- </div>
138
-
139
- <div className="monitor-main">
140
- <h4>{monitor.name}</h4>
141
- <ExternalLink url={monitor.url} />
142
- </div>
143
-
144
- <dl className="monitor-metrics">
145
- <div>
146
- <dt>HTTP</dt>
147
- <dd>{monitor.statusCode ?? '-'}</dd>
148
- </div>
149
- <div>
150
- <dt>Latency</dt>
151
- <dd>{monitor.latencyMs === undefined ? '-' : `${monitor.latencyMs}ms`}</dd>
152
- </div>
153
- <div>
154
- <dt>Checked</dt>
155
- <dd title={monitor.lastCheckedAt}>{relativeTime(monitor.lastCheckedAt)}</dd>
156
- </div>
157
- </dl>
158
- </article>
159
- )
160
- }
@@ -1 +0,0 @@
1
- export { StatusCard } from './StatusCard'
@@ -1,3 +0,0 @@
1
- export { useCheckable } from './useCheckable.query'
2
- export type { CheckableQuery } from './useCheckable.query'
3
- export { useCheck } from './useCheck.mutation'
@@ -1,16 +0,0 @@
1
- /** Run a checkable node's `check` — the feature's one write capability. */
2
- import { type Capability, type KernelClient, useCapability } from '@/shell'
3
-
4
- import { check } from '../status.api'
5
-
6
- /**
7
- * Wrap `@<id>::check` in the shared `useCapability` lifecycle (idle → running →
8
- * done/failed). The view runs it and, on success, reloads the node so the fresh
9
- * status renders:
10
- *
11
- * const probe = useCheck(session, nodeId)
12
- * if (await probe.run()) reload()
13
- */
14
- export function useCheck(session: KernelClient, nodeId: string): Capability {
15
- return useCapability(() => check(session, nodeId))
16
- }
@@ -1,72 +0,0 @@
1
- /** Load a status page with its watched monitors, with reload for post-check refresh. */
2
-
3
- import { type KernelClient, type KernelNode, useAsync } from '@/shell'
4
-
5
- import type { StatusPanelRecord, WatchedMonitorRecord } from '../status.types'
6
-
7
- import { getCheckable, getLinks, getNodeRef } from '../status.api'
8
- import { checkableFromNode, monitorFromRef, watchedMonitorRefs } from '../status.mappers'
9
-
10
- export type CheckableQuery = {
11
- /** Lifecycle of the underlying page + monitor graph read. */
12
- state: 'idle' | 'loading' | 'error' | 'ok'
13
- /** The projected record once `ok`. */
14
- record?: StatusPanelRecord
15
- /** Failure message once `error`. */
16
- message?: string
17
- /** Re-fetch the page and watched monitors after `check` mutates them. */
18
- reload(): void
19
- /** True while a `reload()`-triggered re-fetch is in flight. */
20
- reloading: boolean
21
- }
22
-
23
- async function loadMonitor(
24
- session: KernelClient,
25
- ref: ReturnType<typeof watchedMonitorRefs>[number],
26
- ): Promise<WatchedMonitorRecord> {
27
- try {
28
- const node = (await getNodeRef(session, ref.target)) as KernelNode
29
- return { ...checkableFromNode(node), critical: ref.critical }
30
- } catch {
31
- return monitorFromRef(ref)
32
- }
33
- }
34
-
35
- async function loadStatusPanel(session: KernelClient, nodeId: string): Promise<StatusPanelRecord> {
36
- const page = checkableFromNode((await getCheckable(session, nodeId)) as KernelNode)
37
- if (page.className !== 'StatusPage') return { ...page, monitors: [] }
38
-
39
- const refs = watchedMonitorRefs(await getLinks(session, nodeId))
40
- const monitors = await Promise.all(refs.map((ref) => loadMonitor(session, ref)))
41
- return { ...page, monitors }
42
- }
43
-
44
- /**
45
- * Load the status page `nodeId`, then its outgoing `watches` targets. The view
46
- * calls `reload()` after `::check`, so the rolled-up page status and all monitor
47
- * rows refresh together.
48
- */
49
- export function useCheckable(session: KernelClient, nodeId: string): CheckableQuery {
50
- const resource = useAsync(() => loadStatusPanel(session, nodeId), [session, nodeId])
51
-
52
- switch (resource.state.status) {
53
- case 'ok':
54
- return {
55
- state: 'ok',
56
- record: resource.state.data,
57
- reload: resource.reload,
58
- reloading: resource.reloading,
59
- }
60
- case 'error':
61
- return {
62
- state: 'error',
63
- message: resource.state.message,
64
- reload: resource.reload,
65
- reloading: resource.reloading,
66
- }
67
- case 'loading':
68
- return { state: 'loading', reload: resource.reload, reloading: resource.reloading }
69
- default:
70
- return { state: 'idle', reload: resource.reload, reloading: resource.reloading }
71
- }
72
- }
@@ -1,7 +0,0 @@
1
- /** The status feature — its api, types, mappers, hooks and components. One card,
2
- * polymorphic over any `Checkable` node (Monitor or StatusPage). */
3
- export * from './components'
4
- export * from './hooks'
5
- export { check } from './status.api'
6
- export { checkableFromNode, watchedMonitorRefs } from './status.mappers'
7
- export type { CheckableRecord, StatusPanelRecord, WatchedMonitorRecord } from './status.types'
@@ -1,29 +0,0 @@
1
- /** Raw kernel calls for the status feature — node instance methods. */
2
- import { callMethod, invokeNode, type KernelClient } from '@/shell'
3
-
4
- /**
5
- * Run a checkable node's `check` instance method (`@<id>::check`). The single
6
- * write both classes share: a Monitor probes its target URL, a StatusPage rolls
7
- * its watched monitors up — each updates its own `status` prop server-side; the
8
- * caller reloads the node to render the fresh value.
9
- */
10
- export function check(session: KernelClient, nodeId: string): Promise<unknown> {
11
- return invokeNode(session, nodeId, 'check', {})
12
- }
13
-
14
- export function getCheckable(session: KernelClient, nodeId: string): Promise<unknown> {
15
- return invokeNode(session, nodeId, 'get', {})
16
- }
17
-
18
- export function getLinks(session: KernelClient, nodeId: string): Promise<unknown> {
19
- return invokeNode(session, nodeId, 'getLinks', { direction: 'out' })
20
- }
21
-
22
- function getRefMethod(ref: string): string {
23
- if (ref.startsWith('/') || ref.startsWith('@')) return `${ref}::get`
24
- return `@${ref}::get`
25
- }
26
-
27
- export function getNodeRef(session: KernelClient, ref: string): Promise<unknown> {
28
- return callMethod(session, getRefMethod(ref), {})
29
- }
@@ -1,102 +0,0 @@
1
- /** Kernel graph payloads -> typed records for the status feature. */
2
- import {
3
- classShortName,
4
- type KernelNode,
5
- PROP,
6
- qualifiedProp,
7
- qualifiedString,
8
- readProp,
9
- } from '@/shell'
10
-
11
- import type { CheckableRecord, WatchedMonitorRecord } from './status.types'
12
-
13
- const lastSegment = (path: string): string => path.split('/').filter(Boolean).pop() ?? path
14
-
15
- function numericProp(props: Record<string, unknown>, name: string): number | undefined {
16
- const value = qualifiedProp(props, name)
17
- if (typeof value === 'number' && Number.isFinite(value)) return value
18
- if (typeof value === 'string' && value.trim() !== '') {
19
- const parsed = Number(value)
20
- if (Number.isFinite(parsed)) return parsed
21
- }
22
- return undefined
23
- }
24
-
25
- /**
26
- * Project a `Checkable` `KernelNode` into a `CheckableRecord`: the name from the
27
- * kernel `Named.name` key, plus any monitor details the node carries.
28
- */
29
- export function checkableFromNode(node: KernelNode): CheckableRecord {
30
- const p = node.props ?? {}
31
- return {
32
- id: node.id,
33
- path: node.path ?? `@${node.id}`,
34
- className: classShortName(node),
35
- name: readProp(p, PROP.named.name) ?? lastSegment(node.path ?? '') ?? node.id,
36
- status: qualifiedString(p, 'status') ?? 'unknown',
37
- url: qualifiedString(p, 'url'),
38
- statusCode: numericProp(p, 'statusCode'),
39
- latencyMs: numericProp(p, 'latencyMs'),
40
- lastCheckedAt: qualifiedString(p, 'lastCheckedAt'),
41
- }
42
- }
43
-
44
- type RawLink = {
45
- class?: unknown
46
- edgeClass?: unknown
47
- target?: unknown
48
- to?: unknown
49
- props?: Record<string, unknown>
50
- }
51
-
52
- function rawString(value: unknown): string | undefined {
53
- if (typeof value === 'string' && value !== '') return value
54
- if (value && typeof value === 'object') {
55
- const raw = (value as { raw?: unknown }).raw
56
- if (typeof raw === 'string' && raw !== '') return raw
57
- }
58
- return undefined
59
- }
60
-
61
- function rawLinks(raw: unknown): RawLink[] {
62
- if (Array.isArray(raw)) return raw as RawLink[]
63
- const obj = raw as { links?: RawLink[]; edges?: RawLink[] } | null
64
- return obj?.links ?? obj?.edges ?? []
65
- }
66
-
67
- function booleanProp(props: Record<string, unknown> | undefined, name: string): boolean {
68
- if (!props) return false
69
- const direct = props[name]
70
- if (typeof direct === 'boolean') return direct
71
- return qualifiedProp(props, name) === true
72
- }
73
-
74
- export type WatchedMonitorRef = {
75
- target: string
76
- critical: boolean
77
- }
78
-
79
- /** Parse outgoing StatusPage `watches` edges into target refs and roll-up weight. */
80
- export function watchedMonitorRefs(raw: unknown): WatchedMonitorRef[] {
81
- const out: WatchedMonitorRef[] = []
82
- for (const link of rawLinks(raw)) {
83
- const cls = rawString(link.class) ?? rawString(link.edgeClass) ?? ''
84
- if (cls && !cls.endsWith('class.watches') && !cls.endsWith('.watches')) continue
85
- const target = rawString(link.target) ?? rawString(link.to)
86
- if (!target) continue
87
- out.push({ target, critical: booleanProp(link.props, 'critical') })
88
- }
89
- return out
90
- }
91
-
92
- /** Fallback row when a linked monitor ref exists but the target cannot be loaded. */
93
- export function monitorFromRef(ref: WatchedMonitorRef): WatchedMonitorRecord {
94
- return {
95
- id: ref.target,
96
- path: ref.target,
97
- className: 'Monitor',
98
- name: lastSegment(ref.target.replace(/^@/, '')),
99
- status: 'unknown',
100
- critical: ref.critical,
101
- }
102
- }
@@ -1,26 +0,0 @@
1
- /** The status feature's client types — what graph nodes project to. */
2
-
3
- /**
4
- * Client-side projection of the `Checkable` node the view renders (a StatusPage).
5
- * `status` stays a plain string — the subject's verdict
6
- * (up/degraded/down/unknown), which `StatusBadge` renders.
7
- */
8
- export type CheckableRecord = {
9
- id: string
10
- path: string
11
- className: string
12
- name: string
13
- status: string
14
- url?: string
15
- statusCode?: number
16
- latencyMs?: number
17
- lastCheckedAt?: string
18
- }
19
-
20
- export type WatchedMonitorRecord = CheckableRecord & {
21
- critical: boolean
22
- }
23
-
24
- export type StatusPanelRecord = CheckableRecord & {
25
- monitors: WatchedMonitorRecord[]
26
- }
@@ -1,31 +0,0 @@
1
- /** Health status chip — pure presentational; styling lives in `styles.css`. */
2
-
3
- /** The health vocabulary the badge styles, shared across checkable nodes. */
4
- export type HealthStatus = 'up' | 'down' | 'degraded' | 'unknown'
5
-
6
- /** Map any raw status string to the four styled states (unrecognized → unknown). */
7
- function normalize(status: string): HealthStatus {
8
- return status === 'up' || status === 'down' || status === 'degraded' ? status : 'unknown'
9
- }
10
-
11
- /**
12
- * Color-coded status pill: up = green, degraded = amber, down = red, unknown =
13
- * muted. The label is the uppercased status (`UP` / `DEGRADED` / `DOWN` /
14
- * `UNKNOWN`). Takes any raw status string — the vocabulary depends on the node
15
- * type (up/down for a Monitor, up/degraded/down for a StatusPage) — and styles
16
- * the four known states, falling back to `unknown` for anything else. Both
17
- * checkable node types render through it, so it lives in the feature-agnostic
18
- * `@/ui`, not a feature's own folder.
19
- */
20
- export function StatusBadge({ status }: { status: string }) {
21
- const known = normalize(status)
22
- const label =
23
- known === 'up'
24
- ? 'UP'
25
- : known === 'degraded'
26
- ? 'DEGRADED'
27
- : known === 'down'
28
- ? 'DOWN'
29
- : 'UNKNOWN'
30
- return <span className={`status-badge status-${known}`}>{label}</span>
31
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * The status view — the SPA's one view: the StatusPage panel at `/ui/status-page`.
3
- * Mounted by the Astrale shell as a sandboxed iframe; `@/shell` (built on the real
4
- * `@astrale-os/shell`) completes the handshake and hands over the kernel session +
5
- * the target node id. `ViewFrame` gates the handshake (loading / standalone /
6
- * ready); the `ready` body delegates to `StatusCard`, the feature container that
7
- * loads the node, renders its rolled-up status, and exposes a "Check now" action.
8
- *
9
- * Pure composition — no data/transport logic lives here (that's `@/status` +
10
- * `@/shell`).
11
- */
12
- import { type ShellState, ViewFrame } from '@/shell'
13
- import { StatusCard } from '@/status'
14
-
15
- export function StatusView(shell: ShellState) {
16
- return (
17
- <ViewFrame shell={shell} title="Status page" subline="monitoring view">
18
- {(session, nodeId) =>
19
- nodeId ? (
20
- // Keyed by node id so a target hot-swap remounts with fresh state.
21
- <StatusCard key={nodeId} session={session} nodeId={nodeId} />
22
- ) : (
23
- <div className="banner">No target node — open this view from a StatusPage.</div>
24
- )
25
- }
26
- </ViewFrame>
27
- )
28
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * Health logic — PURE status rules, no I/O. `classify` maps one probe's HTTP code
3
- * to a monitor verdict; `rollup` aggregates a status page's watched monitors into
4
- * a page verdict. (Node identity/layout/seed data lives in `./node`; the kernel
5
- * reads/writes live in `runtime/`; the network probe behind the `Prober` port.)
6
- */
7
-
8
- /** A monitor's health verdict. `unknown` is the pre-first-check state. */
9
- export type HealthStatus = 'up' | 'down' | 'unknown'
10
-
11
- /**
12
- * Map an observed HTTP status code to a verdict. `0` means the host was
13
- * unreachable (DNS/timeout/refused) — that's `down`. 2xx/3xx is `up`; anything
14
- * else (4xx/5xx) is `down`. Pure.
15
- */
16
- export function classify(statusCode: number): HealthStatus {
17
- if (statusCode >= 200 && statusCode < 400) return 'up'
18
- return 'down'
19
- }
20
-
21
- /** A status page's rolled-up verdict — `degraded` = only non-critical checks down. */
22
- export type PageStatus = 'up' | 'degraded' | 'down' | 'unknown'
23
-
24
- /**
25
- * Roll up a page's watched monitors into a page verdict: a CRITICAL monitor down
26
- * ⇒ `down`; any other monitor down ⇒ `degraded`; all up ⇒ `up`; none ⇒ `unknown`.
27
- * Pure.
28
- */
29
- export function rollup(members: readonly { status: string; critical: boolean }[]): PageStatus {
30
- if (members.length === 0) return 'unknown'
31
- const down = members.filter((m) => m.status === 'down')
32
- if (down.some((m) => m.critical)) return 'down'
33
- return down.length > 0 ? 'degraded' : 'up'
34
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * The monitoring bounded context's PURE core, in one place: schema accessors
3
- * (`keys`), the health status rule (`health`), and node identity/layout/seed
4
- * data (`node`). No I/O, no clock/RNG — all deterministic + testable.
5
- * `runtime/monitoring/` imports the operations' logic from here.
6
- */
7
- export * from './keys'
8
- export * from './health'
9
- export * from './node'