@alliance-droid/status-feedback-system 0.1.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 (62) hide show
  1. package/README.md +95 -0
  2. package/dist/components/FeedbackButton.svelte +72 -0
  3. package/dist/components/FeedbackButton.svelte.d.ts +10 -0
  4. package/dist/components/FeedbackForm.svelte +151 -0
  5. package/dist/components/FeedbackForm.svelte.d.ts +11 -0
  6. package/dist/components/StatusBanner.svelte +41 -0
  7. package/dist/components/StatusBanner.svelte.d.ts +8 -0
  8. package/dist/components/StatusPage.svelte +156 -0
  9. package/dist/components/StatusPage.svelte.d.ts +12 -0
  10. package/dist/components/admin/AdminDashboard.svelte +274 -0
  11. package/dist/components/admin/AdminDashboard.svelte.d.ts +9 -0
  12. package/dist/components/admin/ApiKeyManager.svelte +68 -0
  13. package/dist/components/admin/ApiKeyManager.svelte.d.ts +7 -0
  14. package/dist/components/admin/FeedbackList.svelte +160 -0
  15. package/dist/components/admin/FeedbackList.svelte.d.ts +17 -0
  16. package/dist/components/admin/IncidentForm.svelte +135 -0
  17. package/dist/components/admin/IncidentForm.svelte.d.ts +14 -0
  18. package/dist/components/admin/IncidentManager.svelte +153 -0
  19. package/dist/components/admin/IncidentManager.svelte.d.ts +13 -0
  20. package/dist/components/admin/ProjectForm.svelte +106 -0
  21. package/dist/components/admin/ProjectForm.svelte.d.ts +13 -0
  22. package/dist/components/admin/ProjectList.svelte +57 -0
  23. package/dist/components/admin/ProjectList.svelte.d.ts +10 -0
  24. package/dist/components/admin/StatusControl.svelte +62 -0
  25. package/dist/components/admin/StatusControl.svelte.d.ts +8 -0
  26. package/dist/components/admin/SystemManager.svelte +119 -0
  27. package/dist/components/admin/SystemManager.svelte.d.ts +13 -0
  28. package/dist/components/admin/index.d.ts +9 -0
  29. package/dist/components/admin/index.js +9 -0
  30. package/dist/components/shared/IncidentTimeline.svelte +55 -0
  31. package/dist/components/shared/IncidentTimeline.svelte.d.ts +7 -0
  32. package/dist/components/shared/StatusIndicator.svelte +50 -0
  33. package/dist/components/shared/StatusIndicator.svelte.d.ts +9 -0
  34. package/dist/components/shared/SystemCard.svelte +30 -0
  35. package/dist/components/shared/SystemCard.svelte.d.ts +9 -0
  36. package/dist/components/shared/UptimeBar.svelte +85 -0
  37. package/dist/components/shared/UptimeBar.svelte.d.ts +8 -0
  38. package/dist/components/shared/index.d.ts +4 -0
  39. package/dist/components/shared/index.js +4 -0
  40. package/dist/index.d.ts +23 -0
  41. package/dist/index.js +24 -0
  42. package/dist/server/db.d.ts +17 -0
  43. package/dist/server/db.js +68 -0
  44. package/dist/server/feedback.d.ts +27 -0
  45. package/dist/server/feedback.js +124 -0
  46. package/dist/server/handlers.d.ts +41 -0
  47. package/dist/server/handlers.js +251 -0
  48. package/dist/server/incidents.d.ts +27 -0
  49. package/dist/server/incidents.js +180 -0
  50. package/dist/server/index.d.ts +101 -0
  51. package/dist/server/index.js +71 -0
  52. package/dist/server/projects.d.ts +45 -0
  53. package/dist/server/projects.js +156 -0
  54. package/dist/server/schema.d.ts +9 -0
  55. package/dist/server/schema.js +94 -0
  56. package/dist/server/systems.d.ts +20 -0
  57. package/dist/server/systems.js +146 -0
  58. package/dist/server/types.d.ts +4 -0
  59. package/dist/server/types.js +4 -0
  60. package/dist/types.d.ts +73 -0
  61. package/dist/types.js +4 -0
  62. package/package.json +71 -0
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @alliance-droid/status-feedback-system
2
+
3
+ Drop-in system status pages and user feedback forms for SvelteKit apps.
4
+
5
+ ## Features
6
+
7
+ - **Status Page** — Public-facing system health display with uptime bars
8
+ - **Feedback Form** — Users submit bugs, feature requests, general feedback
9
+ - **Admin Dashboard** — Manage projects, systems, incidents, and feedback
10
+ - **API Key Auth** — Multi-project support with scoped API keys
11
+ - **MSSQL** — Auto-migration, all tables prefixed `sf_`
12
+
13
+ ## Quick Start
14
+
15
+ ### Server Setup
16
+
17
+ ```typescript
18
+ // src/lib/server/status.ts
19
+ import { createStatusServer } from '@alliance-droid/status-feedback-system/server';
20
+
21
+ export const statusServer = createStatusServer({
22
+ database: { connectionString: env.MSSQL_CONNECTION_STRING }
23
+ });
24
+
25
+ await statusServer.init();
26
+ ```
27
+
28
+ ### API Routes
29
+
30
+ ```typescript
31
+ // src/routes/api/status/[...path]/+server.ts
32
+ import { statusServer } from '$lib/server/status';
33
+
34
+ const handlers = statusServer.createHandlers();
35
+ export const GET = handlers.GET;
36
+ export const POST = handlers.POST;
37
+ export const PATCH = handlers.PATCH;
38
+ export const DELETE = handlers.DELETE;
39
+ ```
40
+
41
+ ### Status Page
42
+
43
+ ```svelte
44
+ <script>
45
+ import { StatusPage } from '@alliance-droid/status-feedback-system';
46
+ let { data } = $props();
47
+ </script>
48
+
49
+ <StatusPage
50
+ projectName="My App"
51
+ systems={data.systems}
52
+ incidents={data.incidents}
53
+ statusHistory={data.statusHistory}
54
+ />
55
+ ```
56
+
57
+ ### Feedback Button
58
+
59
+ ```svelte
60
+ <script>
61
+ import { FeedbackButton } from '@alliance-droid/status-feedback-system';
62
+ </script>
63
+
64
+ <FeedbackButton
65
+ apiKey="sf_abc123..."
66
+ endpoint="/api/status"
67
+ position="bottom-right"
68
+ />
69
+ ```
70
+
71
+ ### Admin Dashboard
72
+
73
+ ```svelte
74
+ <script>
75
+ import { AdminDashboard } from '@alliance-droid/status-feedback-system/admin';
76
+ let { data } = $props();
77
+ </script>
78
+
79
+ <AdminDashboard
80
+ projects={data.projects}
81
+ apiEndpoint="/api/status"
82
+ />
83
+ ```
84
+
85
+ ## Exports
86
+
87
+ | Path | Description |
88
+ |------|-------------|
89
+ | `.` | Client components (StatusPage, FeedbackForm, etc.) |
90
+ | `./server` | Server module (createStatusServer, types) |
91
+ | `./admin` | Admin components (AdminDashboard, etc.) |
92
+
93
+ ## License
94
+
95
+ UNLICENSED — Private package.
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import FeedbackForm from './FeedbackForm.svelte';
3
+
4
+ interface Props {
5
+ apiKey: string;
6
+ endpoint: string;
7
+ categories?: string[];
8
+ userEmail?: string;
9
+ position?: 'bottom-right' | 'bottom-left';
10
+ }
11
+
12
+ let {
13
+ apiKey,
14
+ endpoint,
15
+ categories,
16
+ userEmail,
17
+ position = 'bottom-right',
18
+ }: Props = $props();
19
+
20
+ let open = $state(false);
21
+
22
+ let positionStyles = $derived(
23
+ position === 'bottom-right'
24
+ ? 'right: 1rem; bottom: 1rem;'
25
+ : 'left: 1rem; bottom: 1rem;'
26
+ );
27
+
28
+ let panelStyles = $derived(
29
+ position === 'bottom-right'
30
+ ? 'right: 0; bottom: 3.5rem;'
31
+ : 'left: 0; bottom: 3.5rem;'
32
+ );
33
+ </script>
34
+
35
+ <div class="fixed z-50" style={positionStyles}>
36
+ {#if open}
37
+ <div class="absolute w-80 rounded-lg border border-border bg-background shadow-lg" style={panelStyles}>
38
+ <div class="flex items-center justify-between border-b border-border px-4 py-3">
39
+ <h3 class="text-sm font-semibold text-foreground">
40
+ <i class="fa-solid fa-comment mr-1.5 text-primary"></i>
41
+ Send Feedback
42
+ </h3>
43
+ <button
44
+ onclick={() => (open = false)}
45
+ class="text-foreground-tertiary transition-colors hover:text-foreground"
46
+ aria-label="Close feedback"
47
+ >
48
+ <i class="fa-solid fa-xmark"></i>
49
+ </button>
50
+ </div>
51
+ <div class="p-4">
52
+ <FeedbackForm
53
+ {apiKey}
54
+ {endpoint}
55
+ {categories}
56
+ {userEmail}
57
+ onSuccess={() => {
58
+ setTimeout(() => (open = false), 2000);
59
+ }}
60
+ />
61
+ </div>
62
+ </div>
63
+ {/if}
64
+
65
+ <button
66
+ onclick={() => (open = !open)}
67
+ class="flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-transform hover:scale-105"
68
+ aria-label="Send feedback"
69
+ >
70
+ <i class="fa-solid {open ? 'fa-xmark' : 'fa-comment'} text-lg"></i>
71
+ </button>
72
+ </div>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ apiKey: string;
3
+ endpoint: string;
4
+ categories?: string[];
5
+ userEmail?: string;
6
+ position?: 'bottom-right' | 'bottom-left';
7
+ }
8
+ declare const FeedbackButton: import("svelte").Component<Props, {}, "">;
9
+ type FeedbackButton = ReturnType<typeof FeedbackButton>;
10
+ export default FeedbackButton;
@@ -0,0 +1,151 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ apiKey: string;
4
+ endpoint: string;
5
+ categories?: string[];
6
+ userEmail?: string;
7
+ onSuccess?: () => void;
8
+ onError?: (error: string) => void;
9
+ }
10
+
11
+ let {
12
+ apiKey,
13
+ endpoint,
14
+ categories = ['Bug', 'Feature Request', 'General'],
15
+ userEmail,
16
+ onSuccess,
17
+ onError,
18
+ }: Props = $props();
19
+
20
+ let category = $state(categories[0] || '');
21
+ let message = $state('');
22
+ let email = $state(userEmail ?? '');
23
+ let submitting = $state(false);
24
+ let submitted = $state(false);
25
+ let errorMsg = $state('');
26
+
27
+ async function handleSubmit(e: Event) {
28
+ e.preventDefault();
29
+ if (!message.trim()) return;
30
+
31
+ submitting = true;
32
+ errorMsg = '';
33
+
34
+ try {
35
+ const res = await fetch(`${endpoint}/feedback`, {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'x-api-key': apiKey,
40
+ },
41
+ body: JSON.stringify({
42
+ category,
43
+ message: message.trim(),
44
+ email: email.trim() || undefined,
45
+ pageUrl: window.location.href,
46
+ }),
47
+ });
48
+
49
+ if (!res.ok) {
50
+ const data = await res.json();
51
+ throw new Error(data.error || 'Failed to submit feedback');
52
+ }
53
+
54
+ submitted = true;
55
+ onSuccess?.();
56
+ } catch (err) {
57
+ errorMsg = err instanceof Error ? err.message : 'Something went wrong';
58
+ onError?.(errorMsg);
59
+ } finally {
60
+ submitting = false;
61
+ }
62
+ }
63
+
64
+ function reset() {
65
+ message = '';
66
+ submitted = false;
67
+ errorMsg = '';
68
+ }
69
+ </script>
70
+
71
+ {#if submitted}
72
+ <div class="rounded-lg border border-green-500/30 bg-green-500/10 p-6 text-center">
73
+ <i class="fa-solid fa-circle-check mb-2 text-2xl text-green-500"></i>
74
+ <h3 class="font-semibold text-foreground">Thanks for your feedback!</h3>
75
+ <p class="mt-1 text-sm text-foreground-secondary">We'll review it shortly.</p>
76
+ <button
77
+ onclick={reset}
78
+ class="mt-4 rounded-md bg-surface-hover px-4 py-2 text-sm text-foreground-secondary transition-colors hover:bg-border"
79
+ >
80
+ Send more feedback
81
+ </button>
82
+ </div>
83
+ {:else}
84
+ <form onsubmit={handleSubmit} class="space-y-4">
85
+ {#if categories.length > 1}
86
+ <div>
87
+ <label for="sf-category" class="mb-1 block text-sm font-semibold text-foreground">
88
+ Category
89
+ </label>
90
+ <select
91
+ id="sf-category"
92
+ bind:value={category}
93
+ class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground"
94
+ >
95
+ {#each categories as cat}
96
+ <option value={cat}>{cat}</option>
97
+ {/each}
98
+ </select>
99
+ </div>
100
+ {/if}
101
+
102
+ <div>
103
+ <label for="sf-message" class="mb-1 block text-sm font-semibold text-foreground">
104
+ Message <span class="text-red-500">*</span>
105
+ </label>
106
+ <textarea
107
+ id="sf-message"
108
+ bind:value={message}
109
+ placeholder="Tell us what's on your mind..."
110
+ rows="4"
111
+ maxlength="5000"
112
+ required
113
+ class="w-full resize-y rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground placeholder:text-foreground-tertiary"
114
+ ></textarea>
115
+ </div>
116
+
117
+ <div>
118
+ <label for="sf-email" class="mb-1 block text-sm font-semibold text-foreground">
119
+ Email <span class="text-foreground-tertiary">(optional)</span>
120
+ </label>
121
+ <input
122
+ id="sf-email"
123
+ type="email"
124
+ bind:value={email}
125
+ placeholder="your@email.com"
126
+ class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground placeholder:text-foreground-tertiary"
127
+ />
128
+ </div>
129
+
130
+ {#if errorMsg}
131
+ <div class="rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-500">
132
+ <i class="fa-solid fa-circle-exclamation mr-1"></i>
133
+ {errorMsg}
134
+ </div>
135
+ {/if}
136
+
137
+ <button
138
+ type="submit"
139
+ disabled={submitting || !message.trim()}
140
+ class="w-full rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary-hover disabled:cursor-not-allowed disabled:opacity-50"
141
+ >
142
+ {#if submitting}
143
+ <i class="fa-solid fa-spinner fa-spin mr-1"></i>
144
+ Submitting...
145
+ {:else}
146
+ <i class="fa-solid fa-paper-plane mr-1"></i>
147
+ Submit Feedback
148
+ {/if}
149
+ </button>
150
+ </form>
151
+ {/if}
@@ -0,0 +1,11 @@
1
+ interface Props {
2
+ apiKey: string;
3
+ endpoint: string;
4
+ categories?: string[];
5
+ userEmail?: string;
6
+ onSuccess?: () => void;
7
+ onError?: (error: string) => void;
8
+ }
9
+ declare const FeedbackForm: import("svelte").Component<Props, {}, "">;
10
+ type FeedbackForm = ReturnType<typeof FeedbackForm>;
11
+ export default FeedbackForm;
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import type { System, SystemStatus } from '../types.js';
3
+ import StatusIndicator from './shared/StatusIndicator.svelte';
4
+
5
+ interface Props {
6
+ systems: System[];
7
+ statusUrl?: string;
8
+ }
9
+
10
+ let { systems, statusUrl }: Props = $props();
11
+
12
+ let overallStatus = $derived.by((): SystemStatus => {
13
+ if (systems.length === 0) return 'operational';
14
+ if (systems.some((s) => s.status === 'outage')) return 'outage';
15
+ if (systems.some((s) => s.status === 'degraded')) return 'degraded';
16
+ if (systems.some((s) => s.status === 'maintenance')) return 'maintenance';
17
+ return 'operational';
18
+ });
19
+
20
+ const messages: Record<SystemStatus, string> = {
21
+ operational: 'All systems operational',
22
+ degraded: 'Some systems degraded',
23
+ outage: 'System outage detected',
24
+ maintenance: 'Scheduled maintenance',
25
+ };
26
+ </script>
27
+
28
+ {#if statusUrl}
29
+ <a
30
+ href={statusUrl}
31
+ class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-secondary transition-colors hover:bg-surface-hover"
32
+ >
33
+ <StatusIndicator status={overallStatus} size="sm" />
34
+ <span>{messages[overallStatus]}</span>
35
+ </a>
36
+ {:else}
37
+ <div class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-secondary">
38
+ <StatusIndicator status={overallStatus} size="sm" />
39
+ <span>{messages[overallStatus]}</span>
40
+ </div>
41
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { System } from '../types.js';
2
+ interface Props {
3
+ systems: System[];
4
+ statusUrl?: string;
5
+ }
6
+ declare const StatusBanner: import("svelte").Component<Props, {}, "">;
7
+ type StatusBanner = ReturnType<typeof StatusBanner>;
8
+ export default StatusBanner;
@@ -0,0 +1,156 @@
1
+ <script lang="ts">
2
+ import type { System, Incident, StatusHistoryEntry, SystemStatus } from '../types.js';
3
+ import StatusIndicator from './shared/StatusIndicator.svelte';
4
+ import SystemCard from './shared/SystemCard.svelte';
5
+ import IncidentTimeline from './shared/IncidentTimeline.svelte';
6
+
7
+ interface Props {
8
+ projectName: string;
9
+ systems: System[];
10
+ incidents: Incident[];
11
+ /** Map of systemId → StatusHistoryEntry[] for uptime bars */
12
+ statusHistory?: Record<string, StatusHistoryEntry[]>;
13
+ showUptime?: boolean;
14
+ }
15
+
16
+ let {
17
+ projectName,
18
+ systems,
19
+ incidents,
20
+ statusHistory = {},
21
+ showUptime = true,
22
+ }: Props = $props();
23
+
24
+ // ── Derived state ──
25
+
26
+ let overallStatus = $derived.by((): SystemStatus => {
27
+ if (systems.length === 0) return 'operational';
28
+ if (systems.some((s) => s.status === 'outage')) return 'outage';
29
+ if (systems.some((s) => s.status === 'degraded')) return 'degraded';
30
+ if (systems.some((s) => s.status === 'maintenance')) return 'maintenance';
31
+ return 'operational';
32
+ });
33
+
34
+ const overallMessages: Record<SystemStatus, string> = {
35
+ operational: 'All Systems Operational',
36
+ degraded: 'Partial System Degradation',
37
+ outage: 'Partial System Outage',
38
+ maintenance: 'Scheduled Maintenance',
39
+ };
40
+
41
+ const overallBgClasses: Record<SystemStatus, string> = {
42
+ operational: 'bg-green-500/10 border-green-500/30',
43
+ degraded: 'bg-yellow-500/10 border-yellow-500/30',
44
+ outage: 'bg-red-500/10 border-red-500/30',
45
+ maintenance: 'bg-blue-500/10 border-blue-500/30',
46
+ };
47
+
48
+ let activeIncidents = $derived(incidents.filter((i) => i.status !== 'resolved'));
49
+ let recentResolved = $derived(
50
+ incidents.filter((i) => i.status === 'resolved').slice(0, 10)
51
+ );
52
+
53
+ function formatDate(timestamp: number): string {
54
+ return new Date(timestamp).toLocaleDateString(undefined, {
55
+ year: 'numeric',
56
+ month: 'long',
57
+ day: 'numeric',
58
+ });
59
+ }
60
+ </script>
61
+
62
+ <div class="mx-auto max-w-3xl space-y-8">
63
+ <!-- Header -->
64
+ <div class="text-center">
65
+ <h1 class="text-2xl font-semibold text-foreground">{projectName} Status</h1>
66
+ </div>
67
+
68
+ <!-- Overall Status Banner -->
69
+ <div class="rounded-lg border p-4 text-center {overallBgClasses[overallStatus]}">
70
+ <div class="flex items-center justify-center gap-2">
71
+ <StatusIndicator status={overallStatus} size="lg" />
72
+ <span class="text-lg font-semibold text-foreground">{overallMessages[overallStatus]}</span>
73
+ </div>
74
+ </div>
75
+
76
+ <!-- Systems -->
77
+ {#if systems.length > 0}
78
+ <section>
79
+ <h2 class="mb-4 text-lg font-semibold text-foreground">Systems</h2>
80
+ <div class="space-y-3">
81
+ {#each systems as system}
82
+ <SystemCard
83
+ {system}
84
+ history={statusHistory[system.id] || []}
85
+ {showUptime}
86
+ />
87
+ {/each}
88
+ </div>
89
+ </section>
90
+ {/if}
91
+
92
+ <!-- Active Incidents -->
93
+ {#if activeIncidents.length > 0}
94
+ <section>
95
+ <h2 class="mb-4 text-lg font-semibold text-foreground">Active Incidents</h2>
96
+ <div class="space-y-4">
97
+ {#each activeIncidents as incident}
98
+ <div class="rounded-lg border border-border bg-surface p-4">
99
+ <div class="flex items-start justify-between">
100
+ <div>
101
+ <h3 class="font-semibold text-foreground">{incident.title}</h3>
102
+ <div class="mt-1 flex items-center gap-2 text-xs text-foreground-tertiary">
103
+ <span class="rounded bg-red-500/10 px-1.5 py-0.5 text-red-500">
104
+ {incident.severity}
105
+ </span>
106
+ <span>{formatDate(incident.createdAt)}</span>
107
+ </div>
108
+ </div>
109
+ <StatusIndicator status={incident.status === 'resolved' ? 'operational' : 'outage'} size="sm" />
110
+ </div>
111
+ {#if incident.updates && incident.updates.length > 0}
112
+ <div class="mt-4 border-t border-border pt-4">
113
+ <IncidentTimeline updates={incident.updates} />
114
+ </div>
115
+ {/if}
116
+ </div>
117
+ {/each}
118
+ </div>
119
+ </section>
120
+ {/if}
121
+
122
+ <!-- Resolved Incidents -->
123
+ {#if recentResolved.length > 0}
124
+ <section>
125
+ <h2 class="mb-4 text-lg font-semibold text-foreground">Past Incidents</h2>
126
+ <div class="space-y-4">
127
+ {#each recentResolved as incident}
128
+ <div class="rounded-lg border border-border bg-surface p-4">
129
+ <div class="flex items-start justify-between">
130
+ <div>
131
+ <h3 class="font-semibold text-foreground">{incident.title}</h3>
132
+ <p class="mt-1 text-xs text-foreground-tertiary">
133
+ Resolved {incident.resolvedAt ? formatDate(incident.resolvedAt) : ''}
134
+ </p>
135
+ </div>
136
+ <StatusIndicator status="operational" size="sm" />
137
+ </div>
138
+ {#if incident.updates && incident.updates.length > 0}
139
+ <div class="mt-4 border-t border-border pt-4">
140
+ <IncidentTimeline updates={incident.updates} />
141
+ </div>
142
+ {/if}
143
+ </div>
144
+ {/each}
145
+ </div>
146
+ </section>
147
+ {/if}
148
+
149
+ <!-- No Incidents -->
150
+ {#if activeIncidents.length === 0 && recentResolved.length === 0}
151
+ <div class="rounded-lg border border-border bg-surface p-8 text-center">
152
+ <i class="fa-solid fa-circle-check mb-2 text-2xl text-green-500"></i>
153
+ <p class="text-sm text-foreground-secondary">No incidents reported</p>
154
+ </div>
155
+ {/if}
156
+ </div>
@@ -0,0 +1,12 @@
1
+ import type { System, Incident, StatusHistoryEntry } from '../types.js';
2
+ interface Props {
3
+ projectName: string;
4
+ systems: System[];
5
+ incidents: Incident[];
6
+ /** Map of systemId → StatusHistoryEntry[] for uptime bars */
7
+ statusHistory?: Record<string, StatusHistoryEntry[]>;
8
+ showUptime?: boolean;
9
+ }
10
+ declare const StatusPage: import("svelte").Component<Props, {}, "">;
11
+ type StatusPage = ReturnType<typeof StatusPage>;
12
+ export default StatusPage;