@kyro-cms/admin 0.3.2 → 0.3.4

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 (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,97 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { getAuthAdapter } from "../../../lib/db";
3
-
4
- export const GET: APIRoute = async ({ url }) => {
5
- const page = parseInt(url.searchParams.get("page") || "1");
6
- const limit = parseInt(url.searchParams.get("limit") || "25");
7
- const search = url.searchParams.get("search") || "";
8
-
9
- try {
10
- const adapter = await getAuthAdapter();
11
- const allUsers = await adapter.findAllUsers();
12
-
13
- const users = allUsers.map((u) => ({
14
- id: u.id,
15
- email: u.email,
16
- name: u.name,
17
- role: u.role,
18
- tenantId: u.tenantId,
19
- emailVerified: u.emailVerified,
20
- locked: u.locked,
21
- lastLogin: u.lastLogin,
22
- createdAt: u.createdAt,
23
- }));
24
-
25
- return new Response(
26
- JSON.stringify({
27
- docs: users,
28
- totalDocs: users.length,
29
- page,
30
- limit,
31
- totalPages: Math.ceil(users.length / limit),
32
- }),
33
- {
34
- status: 200,
35
- headers: { "Content-Type": "application/json" },
36
- },
37
- );
38
- } catch (error) {
39
- console.error("Error fetching users:", error);
40
- return new Response(
41
- JSON.stringify({
42
- error: "Failed to fetch users",
43
- docs: [],
44
- totalDocs: 0,
45
- }),
46
- {
47
- status: 200,
48
- headers: { "Content-Type": "application/json" },
49
- },
50
- );
51
- }
52
- };
53
-
54
- export const POST: APIRoute = async ({ request }) => {
55
- try {
56
- const body = await request.json();
57
- const { email, password, name, role } = body;
58
-
59
- if (!email || !password) {
60
- return new Response(
61
- JSON.stringify({ error: "Email and password are required" }),
62
- {
63
- status: 400,
64
- headers: { "Content-Type": "application/json" },
65
- },
66
- );
67
- }
68
-
69
- const adapter = await getAuthAdapter();
70
-
71
- const existing = await adapter.findUserByEmail(email);
72
- if (existing) {
73
- return new Response(JSON.stringify({ error: "Email already exists" }), {
74
- status: 400,
75
- headers: { "Content-Type": "application/json" },
76
- });
77
- }
78
-
79
- const user = await adapter.createUser({
80
- email,
81
- password,
82
- name,
83
- role: role || "customer",
84
- });
85
-
86
- return new Response(JSON.stringify({ data: user }), {
87
- status: 201,
88
- headers: { "Content-Type": "application/json" },
89
- });
90
- } catch (error) {
91
- console.error("Error creating user:", error);
92
- return new Response(JSON.stringify({ error: "Failed to create user" }), {
93
- status: 500,
94
- headers: { "Content-Type": "application/json" },
95
- });
96
- }
97
- };
@@ -1,59 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { collections } from "../../lib/config";
3
-
4
- export const GET: APIRoute = async () => {
5
- try {
6
- const collectionList = Object.entries(collections).map(
7
- ([slug, config]) => ({
8
- name: config.label || slug,
9
- slug,
10
- description:
11
- config.admin?.description ||
12
- `Manage ${config.label || slug} documents`,
13
- fields: Object.keys(config.fields || {}).length,
14
- endpoints: {
15
- list: `/api/${slug}`,
16
- create: `POST /api/${slug}`,
17
- read: `GET /api/${slug}/:id`,
18
- update: `PATCH /api/${slug}/:id`,
19
- delete: `DELETE /api/${slug}/:id`,
20
- },
21
- }),
22
- );
23
-
24
- return new Response(
25
- JSON.stringify({
26
- name: "Kyro CMS API",
27
- version: "1.0.0",
28
- description: "Headless CMS REST API",
29
- timestamp: new Date().toISOString(),
30
- endpoints: {
31
- collections: "/api/collections",
32
- health: "/api/health",
33
- upload: "/api/upload",
34
- graphql: "/api/graphql",
35
- },
36
- collections: collectionList,
37
- globals: [],
38
- }),
39
- {
40
- status: 200,
41
- headers: {
42
- "Content-Type": "application/json",
43
- "Access-Control-Allow-Origin": "*",
44
- },
45
- },
46
- );
47
- } catch (error) {
48
- return new Response(
49
- JSON.stringify({
50
- error: error instanceof Error ? error.message : "Unknown error",
51
- timestamp: new Date().toISOString(),
52
- }),
53
- {
54
- status: 500,
55
- headers: { "Content-Type": "application/json" },
56
- },
57
- );
58
- }
59
- };
@@ -1,42 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { dataStore } from "../../../lib/dataStore";
3
- import { globals } from "../../../lib/config";
4
-
5
- export const GET: APIRoute = async ({ params }) => {
6
- const slug = params.slug as string;
7
-
8
- if (!globals[slug]) {
9
- return new Response(JSON.stringify({ error: "Global not found" }), {
10
- status: 404,
11
- });
12
- }
13
-
14
- const data = await dataStore.findGlobal(slug);
15
- return new Response(JSON.stringify({ data }), {
16
- status: 200,
17
- headers: { "Content-Type": "application/json" },
18
- });
19
- };
20
-
21
- export const PATCH: APIRoute = async ({ params, request }) => {
22
- const slug = params.slug as string;
23
-
24
- if (!globals[slug]) {
25
- return new Response(JSON.stringify({ error: "Global not found" }), {
26
- status: 404,
27
- });
28
- }
29
-
30
- try {
31
- const body = await request.json();
32
- const data = await dataStore.updateGlobal(slug, body);
33
- return new Response(JSON.stringify({ data }), {
34
- status: 200,
35
- headers: { "Content-Type": "application/json" },
36
- });
37
- } catch (error) {
38
- return new Response(JSON.stringify({ error: "Failed to update global" }), {
39
- status: 500,
40
- });
41
- }
42
- };
@@ -1,90 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { executeGraphQL } from "../../lib/graphql/schema";
3
- import { dataStore } from "../../lib/dataStore";
4
- import { collections } from "../../lib/config";
5
-
6
- dataStore.initialize(collections);
7
-
8
- export const ALL: APIRoute = async ({ request }) => {
9
- const url = new URL(request.url);
10
- const method = request.method.toUpperCase();
11
-
12
- if (method === "GET") {
13
- return new Response(
14
- JSON.stringify({
15
- message: "Kyro CMS GraphQL API",
16
- version: "1.0",
17
- endpoints: {
18
- playground: "/admin/graphql",
19
- graphql: "/graphql",
20
- introspection: "POST with { __schema { types { name } } }",
21
- },
22
- }),
23
- {
24
- status: 200,
25
- headers: { "Content-Type": "application/json" },
26
- },
27
- );
28
- }
29
-
30
- if (method === "POST") {
31
- try {
32
- const contentType = request.headers.get("content-type") || "";
33
- let query: string;
34
- let variables: Record<string, any> | undefined;
35
-
36
- if (contentType.includes("application/json")) {
37
- const body = await request.json();
38
- query = body.query;
39
- variables = body.variables;
40
- } else {
41
- const formData = await request.formData();
42
- query = formData.get("query") as string;
43
- const variablesStr = formData.get("variables") as string;
44
- if (variablesStr) {
45
- try {
46
- variables = JSON.parse(variablesStr);
47
- } catch {}
48
- }
49
- }
50
-
51
- if (!query) {
52
- return new Response(JSON.stringify({ error: "No query provided" }), {
53
- status: 400,
54
- headers: { "Content-Type": "application/json" },
55
- });
56
- }
57
-
58
- const authHeader = request.headers.get("authorization");
59
- const token = authHeader?.startsWith("Bearer ")
60
- ? authHeader.slice(7)
61
- : undefined;
62
- const apiKey = authHeader?.startsWith("ApiKey ")
63
- ? authHeader.slice(7).trim()
64
- : request.headers.get("x-api-key")?.trim() || undefined;
65
-
66
- const result = await executeGraphQL(query, variables, token, apiKey);
67
-
68
- return new Response(JSON.stringify(result), {
69
- status: 200,
70
- headers: { "Content-Type": "application/json" },
71
- });
72
- } catch (error) {
73
- console.error("GraphQL Error:", error);
74
- return new Response(
75
- JSON.stringify({
76
- errors: [{ message: "Internal server error" }],
77
- }),
78
- {
79
- status: 500,
80
- headers: { "Content-Type": "application/json" },
81
- },
82
- );
83
- }
84
- }
85
-
86
- return new Response(JSON.stringify({ error: "Method not allowed" }), {
87
- status: 405,
88
- headers: { "Content-Type": "application/json" },
89
- });
90
- };
@@ -1,426 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { dataStore } from "../../lib/dataStore";
3
- import { collections as adminCollections } from "../../lib/config";
4
-
5
- export const GET: APIRoute = async () => {
6
- try {
7
- const startTime = Date.now();
8
-
9
- const collectionNames = Object.keys(adminCollections);
10
-
11
- let documentCount = 0;
12
- const collectionCounts: Record<string, number> = {};
13
-
14
- for (const slug of collectionNames) {
15
- try {
16
- const result = await dataStore.find(slug, { limit: 1 });
17
- const count = result.totalDocs || 0;
18
- collectionCounts[slug] = count;
19
- documentCount += count;
20
- } catch (e) {
21
- collectionCounts[slug] = 0;
22
- }
23
- }
24
-
25
- const responseTime = Date.now() - startTime;
26
- const uptime = Math.floor(process.uptime());
27
- const uptimeDays = Math.floor(uptime / 86400);
28
- const uptimeHours = Math.floor((uptime % 86400) / 3600);
29
- const uptimeMinutes = Math.floor((uptime % 3600) / 60);
30
-
31
- const html = `<!DOCTYPE html>
32
- <html lang="en">
33
- <head>
34
- <meta charset="UTF-8">
35
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
36
- <title>System Health - Kyro CMS</title>
37
- <link rel="preconnect" href="https://fonts.googleapis.com">
38
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
39
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
40
- <style>
41
- :root {
42
- --bg: #eaeff2;
43
- --surface: #ffffff;
44
- --surface-accent: #f9fafb;
45
- --text-primary: #0b1222;
46
- --text-secondary: #64748b;
47
- --border: #e5e7eb;
48
- --primary: #0b1222;
49
- --accent: #0b1222;
50
- --accent-text: #ffffff;
51
- --success: #10b981;
52
- }
53
- @media (prefers-color-scheme: dark) {
54
- :root {
55
- --bg: #030712;
56
- --surface: #0b1222;
57
- --surface-accent: #111a2e;
58
- --text-primary: #f8fafc;
59
- --text-secondary: #94a3b8;
60
- --border: #1e293b;
61
- --primary: #ffffff;
62
- --accent: #ffffff;
63
- --accent-text: #0b1222;
64
- }
65
- }
66
- * { margin: 0; padding: 0; box-sizing: border-box; }
67
- body {
68
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
69
- background: var(--bg);
70
- color: var(--text-primary);
71
- min-height: 100vh;
72
- padding: 2rem;
73
- }
74
- .container {
75
- max-width: 720px;
76
- margin: 0 auto;
77
- }
78
- .header {
79
- text-align: center;
80
- margin-bottom: 2.5rem;
81
- }
82
- .logo {
83
- display: inline-flex;
84
- align-items: center;
85
- justify-content: center;
86
- width: 72px;
87
- height: 72px;
88
- background: var(--accent);
89
- color: var(--accent-text);
90
- border-radius: 20px;
91
- font-size: 28px;
92
- font-weight: 900;
93
- margin-bottom: 1.5rem;
94
- box-shadow: 0 8px 32px rgba(0,0,0,0.15);
95
- }
96
- h1 {
97
- font-size: 2rem;
98
- font-weight: 800;
99
- letter-spacing: -0.03em;
100
- margin-bottom: 0.5rem;
101
- }
102
- .subtitle {
103
- color: var(--text-secondary);
104
- font-size: 0.9375rem;
105
- font-weight: 500;
106
- }
107
- .status-badge {
108
- display: inline-flex;
109
- align-items: center;
110
- gap: 0.5rem;
111
- background: var(--success);
112
- color: white;
113
- padding: 0.625rem 1.25rem;
114
- border-radius: 9999px;
115
- font-size: 0.875rem;
116
- font-weight: 700;
117
- margin-top: 1.25rem;
118
- box-shadow: 0 4px 16px rgba(16, 185, 129, 0.3);
119
- }
120
- .status-badge::before {
121
- content: '';
122
- width: 8px;
123
- height: 8px;
124
- background: white;
125
- border-radius: 50%;
126
- animation: pulse 2s infinite;
127
- }
128
- @keyframes pulse {
129
- 0%, 100% { opacity: 1; transform: scale(1); }
130
- 50% { opacity: 0.6; transform: scale(0.9); }
131
- }
132
- .stats-grid {
133
- display: grid;
134
- grid-template-columns: repeat(2, 1fr);
135
- gap: 1rem;
136
- }
137
- .stat-card {
138
- background: var(--surface);
139
- border: 1px solid var(--border);
140
- border-radius: 16px;
141
- padding: 1.5rem;
142
- transition: all 0.2s ease;
143
- }
144
- .stat-card:hover {
145
- border-color: var(--text-secondary);
146
- transform: translateY(-2px);
147
- box-shadow: 0 8px 24px rgba(0,0,0,0.08);
148
- }
149
- .stat-card.wide {
150
- grid-column: span 2;
151
- }
152
- .stat-label {
153
- font-size: 0.75rem;
154
- font-weight: 600;
155
- color: var(--text-secondary);
156
- text-transform: uppercase;
157
- letter-spacing: 0.05em;
158
- margin-bottom: 0.75rem;
159
- }
160
- .stat-value {
161
- font-size: 2rem;
162
- font-weight: 800;
163
- letter-spacing: -0.03em;
164
- line-height: 1;
165
- }
166
- .stat-meta {
167
- font-size: 0.8125rem;
168
- color: var(--text-secondary);
169
- margin-top: 0.5rem;
170
- font-weight: 500;
171
- }
172
- .collection-list {
173
- display: flex;
174
- flex-wrap: wrap;
175
- gap: 0.5rem;
176
- margin-top: 0.5rem;
177
- }
178
- .collection-item {
179
- display: inline-flex;
180
- align-items: center;
181
- gap: 0.375rem;
182
- padding: 0.375rem 0.75rem;
183
- background: var(--surface-accent);
184
- border-radius: 8px;
185
- font-size: 0.8125rem;
186
- font-weight: 600;
187
- color: var(--text-secondary);
188
- }
189
- .collection-count {
190
- background: var(--accent);
191
- color: var(--accent-text);
192
- padding: 0.125rem 0.5rem;
193
- border-radius: 6px;
194
- font-size: 0.75rem;
195
- font-weight: 700;
196
- }
197
- .nav {
198
- display: flex;
199
- justify-content: center;
200
- gap: 0.75rem;
201
- margin-top: 2rem;
202
- flex-wrap: wrap;
203
- }
204
- .nav-link {
205
- color: var(--text-secondary);
206
- text-decoration: none;
207
- font-size: 0.875rem;
208
- font-weight: 600;
209
- padding: 0.75rem 1.25rem;
210
- border-radius: 10px;
211
- background: var(--surface);
212
- border: 1px solid var(--border);
213
- transition: all 0.2s ease;
214
- }
215
- .nav-link:hover {
216
- background: var(--surface-accent);
217
- color: var(--text-primary);
218
- border-color: var(--text-secondary);
219
- }
220
- .timestamp {
221
- text-align: center;
222
- margin-top: 2rem;
223
- font-size: 0.75rem;
224
- color: var(--text-secondary);
225
- font-weight: 500;
226
- }
227
- @media (max-width: 640px) {
228
- .stats-grid {
229
- grid-template-columns: 1fr;
230
- }
231
- .stat-card.wide {
232
- grid-column: span 1;
233
- }
234
- }
235
- </style>
236
- </head>
237
- <body>
238
- <div class="container">
239
- <div class="header">
240
- <div class="logo">K</div>
241
- <h1>System Health</h1>
242
- <p class="subtitle">Kyro CMS is running optimally</p>
243
- <div class="status-badge">All Systems Operational</div>
244
- </div>
245
-
246
- <div class="stats-grid">
247
- <div class="stat-card">
248
- <div class="stat-label">Response Time</div>
249
- <div class="stat-value">${responseTime}<span style="font-size: 1rem; font-weight: 500; color: var(--text-secondary);">ms</span></div>
250
- <div class="stat-meta">Lightning fast</div>
251
- </div>
252
-
253
- <div class="stat-card">
254
- <div class="stat-label">Server Uptime</div>
255
- <div class="stat-value">
256
- ${uptimeDays > 0 ? `${uptimeDays}d ` : ""}${uptimeHours}h <span style="font-size: 0.875rem; font-weight: 500; color: var(--text-secondary);">${uptimeMinutes}m</span>
257
- </div>
258
- <div class="stat-meta">Since last restart</div>
259
- </div>
260
-
261
- <div class="stat-card">
262
- <div class="stat-label">Collections</div>
263
- <div class="stat-value">${collectionNames.length}</div>
264
- <div class="stat-meta">Active content types</div>
265
- </div>
266
-
267
- <div class="stat-card">
268
- <div class="stat-label">Total Documents</div>
269
- <div class="stat-value">${documentCount.toLocaleString()}</div>
270
- <div class="stat-meta">Across all collections</div>
271
- </div>
272
-
273
- <div class="stat-card wide">
274
- <div class="stat-label">Document Distribution</div>
275
- <div class="collection-list">
276
- ${collectionNames
277
- .map(
278
- (slug: string) => `
279
- <div class="collection-item">
280
- ${slug}
281
- <span class="collection-count">${collectionCounts[slug] || 0}</span>
282
- </div>
283
- `,
284
- )
285
- .join("")}
286
- </div>
287
- </div>
288
- </div>
289
-
290
- <div class="nav">
291
- <a href="/admin" class="nav-link">Dashboard</a>
292
- <a href="/admin/api-explorer" class="nav-link">API Explorer</a>
293
- <a href="/admin/graphql" class="nav-link">GraphQL</a>
294
- <a href="/admin/keys" class="nav-link">API Keys</a>
295
- <a href="/admin/webhooks" class="nav-link">Webhooks</a>
296
- </div>
297
-
298
- <div class="timestamp">
299
- Last checked: ${new Date().toLocaleString()} • Kyro CMS v1.0
300
- </div>
301
- </div>
302
- </body>
303
- </html>`;
304
-
305
- return new Response(html, {
306
- status: 200,
307
- headers: {
308
- "Content-Type": "text/html",
309
- },
310
- });
311
- } catch (error) {
312
- const errorMessage =
313
- error instanceof Error ? error.message : "Unknown error";
314
-
315
- const html = `<!DOCTYPE html>
316
- <html lang="en">
317
- <head>
318
- <meta charset="UTF-8">
319
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
320
- <title>System Health - Kyro CMS</title>
321
- <link rel="preconnect" href="https://fonts.googleapis.com">
322
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
323
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
324
- <style>
325
- :root {
326
- --bg: #eaeff2;
327
- --surface: #ffffff;
328
- --text-primary: #0b1222;
329
- --text-secondary: #64748b;
330
- --border: #e5e7eb;
331
- --error: #ef4444;
332
- }
333
- @media (prefers-color-scheme: dark) {
334
- :root {
335
- --bg: #030712;
336
- --surface: #0b1222;
337
- --text-primary: #f8fafc;
338
- --text-secondary: #94a3b8;
339
- --border: #1e293b;
340
- }
341
- }
342
- * { margin: 0; padding: 0; box-sizing: border-box; }
343
- body {
344
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
345
- background: var(--bg);
346
- color: var(--text-primary);
347
- min-height: 100vh;
348
- display: flex;
349
- align-items: center;
350
- justify-content: center;
351
- padding: 2rem;
352
- }
353
- .container {
354
- max-width: 480px;
355
- width: 100%;
356
- text-align: center;
357
- }
358
- .logo {
359
- display: inline-flex;
360
- align-items: center;
361
- justify-content: center;
362
- width: 72px;
363
- height: 72px;
364
- background: var(--error);
365
- color: white;
366
- border-radius: 20px;
367
- font-size: 28px;
368
- font-weight: 900;
369
- margin-bottom: 1.5rem;
370
- }
371
- h1 {
372
- font-size: 2rem;
373
- font-weight: 800;
374
- letter-spacing: -0.03em;
375
- margin-bottom: 0.5rem;
376
- }
377
- .error-badge {
378
- display: inline-flex;
379
- align-items: center;
380
- gap: 0.5rem;
381
- background: var(--error);
382
- color: white;
383
- padding: 0.625rem 1.25rem;
384
- border-radius: 9999px;
385
- font-size: 0.875rem;
386
- font-weight: 700;
387
- margin-top: 1.25rem;
388
- }
389
- .error-card {
390
- background: var(--surface);
391
- border: 1px solid var(--border);
392
- border-radius: 16px;
393
- padding: 1.5rem;
394
- text-align: left;
395
- margin-top: 1.5rem;
396
- }
397
- .error-code {
398
- font-family: 'SF Mono', 'Monaco', monospace;
399
- font-size: 0.8125rem;
400
- color: var(--error);
401
- word-break: break-all;
402
- }
403
- </style>
404
- </head>
405
- <body>
406
- <div class="container">
407
- <div class="header">
408
- <div class="logo">!</div>
409
- <h1>System Health</h1>
410
- <div class="error-badge">System Error</div>
411
- </div>
412
- <div class="error-card">
413
- <div class="error-code">${errorMessage}</div>
414
- </div>
415
- </div>
416
- </body>
417
- </html>`;
418
-
419
- return new Response(html, {
420
- status: 503,
421
- headers: {
422
- "Content-Type": "text/html",
423
- },
424
- });
425
- }
426
- };