@checkstack/about-frontend 0.2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # @checkstack/about-frontend
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3589199: Add About page with platform information, license, contact details, and version information
8
+
9
+ - New `about-common` package with plugin metadata
10
+ - New `about-frontend` package with the About page and user menu item
11
+ - New `/api/about` backend endpoint exposing core version and loaded plugin versions
12
+ - Accessible via "About Checkstack" in the user menu dropdown
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [3589199]
17
+ - @checkstack/about-common@0.2.0
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@checkstack/about-frontend",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "main": "src/index.tsx",
6
+ "checkstack": {
7
+ "type": "frontend"
8
+ },
9
+ "scripts": {
10
+ "typecheck": "tsc --noEmit",
11
+ "lint": "bun run lint:code",
12
+ "lint:code": "eslint . --max-warnings 0"
13
+ },
14
+ "dependencies": {
15
+ "@checkstack/about-common": "0.1.0",
16
+ "@checkstack/frontend-api": "0.3.8",
17
+ "@checkstack/common": "0.6.4",
18
+ "@checkstack/ui": "1.2.0",
19
+ "react": "^18.2.0",
20
+ "react-router-dom": "^6.22.0",
21
+ "lucide-react": "^0.344.0"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.0.0",
25
+ "@types/react": "^18.2.0",
26
+ "@checkstack/tsconfig": "0.0.4",
27
+ "@checkstack/scripts": "0.1.2"
28
+ }
29
+ }
@@ -0,0 +1,18 @@
1
+ import { useNavigate } from "react-router-dom";
2
+ import { Info } from "lucide-react";
3
+ import { DropdownMenuItem } from "@checkstack/ui";
4
+ import { resolveRoute } from "@checkstack/common";
5
+ import { aboutRoutes } from "./index";
6
+
7
+ export function AboutMenuItem() {
8
+ const navigate = useNavigate();
9
+
10
+ return (
11
+ <DropdownMenuItem
12
+ onClick={() => navigate(resolveRoute(aboutRoutes.routes.page))}
13
+ icon={<Info className="h-4 w-4" />}
14
+ >
15
+ About Checkstack
16
+ </DropdownMenuItem>
17
+ );
18
+ }
@@ -0,0 +1,289 @@
1
+ import { useState, useEffect } from "react";
2
+ import { Info, Github, Mail, Scale, ExternalLink, Package } from "lucide-react";
3
+ import {
4
+ PageLayout,
5
+ Card,
6
+ CardHeader,
7
+ CardTitle,
8
+ CardDescription,
9
+ CardContent,
10
+ Badge,
11
+ LoadingSpinner,
12
+ } from "@checkstack/ui";
13
+
14
+ interface PluginInfo {
15
+ name: string;
16
+ version: string;
17
+ type: "backend" | "frontend" | "common";
18
+ }
19
+
20
+ interface AboutInfo {
21
+ coreVersion: string;
22
+ plugins: PluginInfo[];
23
+ }
24
+
25
+ /**
26
+ * Formats a raw package name for display.
27
+ * Strips `@checkstack/` prefix and converts to title case.
28
+ * e.g., "@checkstack/healthcheck-http-backend" → "Healthcheck HTTP"
29
+ */
30
+ function formatPluginName(name: string): string {
31
+ const stripped = name.replace("@checkstack/", "");
32
+ // Remove type suffixes (-backend, -frontend, -common)
33
+ const withoutSuffix = stripped
34
+ .replace(/-backend$/, "")
35
+ .replace(/-frontend$/, "")
36
+ .replace(/-common$/, "");
37
+ // Title case with dashes as spaces
38
+ return withoutSuffix
39
+ .split("-")
40
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
41
+ .join(" ");
42
+ }
43
+
44
+ /**
45
+ * Returns a badge variant for a plugin type.
46
+ */
47
+ function typeBadgeVariant(type: string): "default" | "secondary" | "info" {
48
+ switch (type) {
49
+ case "backend": {
50
+ return "default";
51
+ }
52
+ case "frontend": {
53
+ return "info";
54
+ }
55
+ default: {
56
+ return "secondary";
57
+ }
58
+ }
59
+ }
60
+
61
+ export function AboutPage() {
62
+ const [aboutInfo, setAboutInfo] = useState<AboutInfo | undefined>();
63
+ const [loading, setLoading] = useState(true);
64
+ const [error, setError] = useState<string | undefined>();
65
+
66
+ useEffect(() => {
67
+ const fetchAboutInfo = async () => {
68
+ try {
69
+ const response = await fetch("/api/about");
70
+ if (!response.ok) {
71
+ throw new Error(`Failed to fetch about info: ${response.status}`);
72
+ }
73
+ const data = (await response.json()) as AboutInfo;
74
+ setAboutInfo(data);
75
+ } catch (error) {
76
+ setError(
77
+ error instanceof Error ? error.message : "Failed to load about info",
78
+ );
79
+ } finally {
80
+ setLoading(false);
81
+ }
82
+ };
83
+
84
+ void fetchAboutInfo();
85
+ }, []);
86
+
87
+ return (
88
+ <PageLayout
89
+ title="About Checkstack"
90
+ icon={Info}
91
+ subtitle="Platform information, license, and version details"
92
+ >
93
+ {/* Hero Section */}
94
+ <Card>
95
+ <CardHeader>
96
+ <div className="flex items-start gap-4">
97
+ <div className="rounded-xl bg-primary/10 p-3">
98
+ <Package className="h-8 w-8 text-primary" />
99
+ </div>
100
+ <div className="space-y-1">
101
+ <CardTitle className="text-xl">Checkstack</CardTitle>
102
+ <CardDescription className="text-base">
103
+ The Modern Status Page &amp; Monitoring Platform
104
+ </CardDescription>
105
+ </div>
106
+ </div>
107
+ </CardHeader>
108
+ <CardContent>
109
+ <p className="text-sm text-muted-foreground leading-relaxed">
110
+ Monitor your systems, keep users informed, and maintain trust.
111
+ Checkstack combines the power of a status page, uptime monitoring,
112
+ and incident management into a single, extensible platform.
113
+ </p>
114
+ <div className="flex flex-wrap gap-3 mt-4">
115
+ <a
116
+ href="https://github.com/enyineer/checkstack"
117
+ target="_blank"
118
+ rel="noopener noreferrer"
119
+ className="inline-flex items-center gap-2 rounded-lg border border-border bg-secondary/50 px-4 py-2 text-sm font-medium text-foreground hover:bg-secondary transition-colors"
120
+ >
121
+ <Github className="h-4 w-4" />
122
+ GitHub Repository
123
+ <ExternalLink className="h-3 w-3 text-muted-foreground" />
124
+ </a>
125
+ <a
126
+ href="mailto:hi@enking.dev"
127
+ className="inline-flex items-center gap-2 rounded-lg border border-border bg-secondary/50 px-4 py-2 text-sm font-medium text-foreground hover:bg-secondary transition-colors"
128
+ >
129
+ <Mail className="h-4 w-4" />
130
+ hi@enking.dev
131
+ </a>
132
+ </div>
133
+ </CardContent>
134
+ </Card>
135
+
136
+ {/* License Section */}
137
+ <Card>
138
+ <CardHeader>
139
+ <div className="flex items-center gap-3">
140
+ <Scale className="h-5 w-5 text-muted-foreground" />
141
+ <CardTitle className="text-lg">License</CardTitle>
142
+ </div>
143
+ <CardDescription>
144
+ Checkstack is licensed under the{" "}
145
+ <strong className="text-foreground">
146
+ Elastic License 2.0 (ELv2)
147
+ </strong>
148
+ </CardDescription>
149
+ </CardHeader>
150
+ <CardContent>
151
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
152
+ <div className="rounded-lg bg-success/5 border border-success/20 p-4">
153
+ <h4 className="text-sm font-semibold text-success mb-2">
154
+ ✅ Allowed
155
+ </h4>
156
+ <ul className="space-y-1 text-sm text-muted-foreground">
157
+ <li>Internal company use</li>
158
+ <li>Personal projects</li>
159
+ <li>Research &amp; education</li>
160
+ <li>Modification &amp; redistribution</li>
161
+ <li>Building applications on top</li>
162
+ </ul>
163
+ </div>
164
+ <div className="rounded-lg bg-destructive/5 border border-destructive/20 p-4">
165
+ <h4 className="text-sm font-semibold text-destructive mb-2">
166
+ ❌ Not Allowed
167
+ </h4>
168
+ <ul className="space-y-1 text-sm text-muted-foreground">
169
+ <li>Selling as a managed SaaS</li>
170
+ <li>Removing license protections</li>
171
+ </ul>
172
+ </div>
173
+ </div>
174
+ <p className="text-xs text-muted-foreground mt-4">
175
+ Need a commercial license to provide Checkstack as a managed / SaaS
176
+ service?{" "}
177
+ <a
178
+ href="mailto:hi@enking.dev"
179
+ className="text-primary hover:underline"
180
+ >
181
+ Contact us
182
+ </a>
183
+ </p>
184
+ </CardContent>
185
+ </Card>
186
+
187
+ {/* Version Information Section */}
188
+ <Card>
189
+ <CardHeader>
190
+ <div className="flex items-center gap-3">
191
+ <Info className="h-5 w-5 text-muted-foreground" />
192
+ <CardTitle className="text-lg">Version Information</CardTitle>
193
+ </div>
194
+ </CardHeader>
195
+ <CardContent>
196
+ {loading && (
197
+ <div className="flex justify-center py-6">
198
+ <LoadingSpinner />
199
+ </div>
200
+ )}
201
+
202
+ {error && (
203
+ <div className="rounded-lg bg-destructive/10 border border-destructive/20 p-4 text-sm text-destructive">
204
+ {error}
205
+ </div>
206
+ )}
207
+
208
+ {aboutInfo && (
209
+ <div className="space-y-6">
210
+ {/* Core Version */}
211
+ <div className="flex items-center justify-between rounded-lg bg-secondary/50 border border-border p-4">
212
+ <div>
213
+ <p className="text-sm font-medium text-foreground">
214
+ Checkstack Core
215
+ </p>
216
+ <p className="text-xs text-muted-foreground">
217
+ Backend platform engine
218
+ </p>
219
+ </div>
220
+ <Badge variant="default" className="font-mono text-xs">
221
+ v{aboutInfo.coreVersion}
222
+ </Badge>
223
+ </div>
224
+
225
+ {/* Plugin Versions Table */}
226
+ {aboutInfo.plugins.length > 0 && (
227
+ <div>
228
+ <h4 className="text-sm font-medium text-foreground mb-3">
229
+ Loaded Plugins ({aboutInfo.plugins.length})
230
+ </h4>
231
+ <div className="rounded-lg border border-border overflow-hidden">
232
+ <table className="w-full text-sm">
233
+ <thead>
234
+ <tr className="border-b border-border bg-muted/50">
235
+ <th className="text-left py-2.5 px-4 font-medium text-muted-foreground">
236
+ Plugin
237
+ </th>
238
+ <th className="text-left py-2.5 px-4 font-medium text-muted-foreground">
239
+ Type
240
+ </th>
241
+ <th className="text-right py-2.5 px-4 font-medium text-muted-foreground">
242
+ Version
243
+ </th>
244
+ </tr>
245
+ </thead>
246
+ <tbody>
247
+ {aboutInfo.plugins.map((plugin) => (
248
+ <tr
249
+ key={plugin.name}
250
+ className="border-b border-border last:border-0 hover:bg-muted/30 transition-colors"
251
+ >
252
+ <td className="py-2.5 px-4">
253
+ <span className="font-medium text-foreground">
254
+ {formatPluginName(plugin.name)}
255
+ </span>
256
+ <span className="block text-xs text-muted-foreground font-mono">
257
+ {plugin.name}
258
+ </span>
259
+ </td>
260
+ <td className="py-2.5 px-4">
261
+ <Badge
262
+ variant={typeBadgeVariant(plugin.type)}
263
+ className="text-[10px]"
264
+ >
265
+ {plugin.type}
266
+ </Badge>
267
+ </td>
268
+ <td className="py-2.5 px-4 text-right font-mono text-muted-foreground">
269
+ {plugin.version}
270
+ </td>
271
+ </tr>
272
+ ))}
273
+ </tbody>
274
+ </table>
275
+ </div>
276
+ </div>
277
+ )}
278
+ </div>
279
+ )}
280
+ </CardContent>
281
+ </Card>
282
+
283
+ {/* Footer Attribution */}
284
+ <p className="text-center text-xs text-muted-foreground py-4">
285
+ Built with ❤️ for reliability engineers everywhere
286
+ </p>
287
+ </PageLayout>
288
+ );
289
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,31 @@
1
+ import {
2
+ createFrontendPlugin,
3
+ UserMenuItemsBottomSlot,
4
+ } from "@checkstack/frontend-api";
5
+ import { createRoutes } from "@checkstack/common";
6
+ import { pluginMetadata } from "@checkstack/about-common";
7
+ import { AboutPage } from "./AboutPage";
8
+ import { AboutMenuItem } from "./AboutMenuItem";
9
+
10
+ export const aboutRoutes = createRoutes(pluginMetadata.pluginId, {
11
+ page: "/",
12
+ });
13
+
14
+ export const aboutPlugin = createFrontendPlugin({
15
+ metadata: pluginMetadata,
16
+ routes: [
17
+ {
18
+ route: aboutRoutes.routes.page,
19
+ element: <AboutPage />,
20
+ },
21
+ ],
22
+ extensions: [
23
+ {
24
+ id: "about.user-menu.link",
25
+ slot: UserMenuItemsBottomSlot,
26
+ component: AboutMenuItem,
27
+ },
28
+ ],
29
+ });
30
+
31
+ export default aboutPlugin;
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkstack/tsconfig/frontend.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }