@kyro-cms/admin 0.3.2 → 0.3.5
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/dist/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminLayout from "../layouts/AdminLayout.astro";
|
|
3
|
+
import { adminPath } from "../lib/paths";
|
|
4
|
+
|
|
5
|
+
// Ensure this page looks premium
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<AdminLayout title="Access Denied">
|
|
9
|
+
<div class="flex-1 flex items-center justify-center p-8">
|
|
10
|
+
<div class="max-w-md w-full text-center space-y-8">
|
|
11
|
+
<div class="relative">
|
|
12
|
+
<div class="absolute inset-0 bg-red-500/20 blur-3xl rounded-full"></div>
|
|
13
|
+
<div
|
|
14
|
+
class="relative w-24 h-24 mx-auto bg-red-500/10 border border-red-500/20 rounded-3xl flex items-center justify-center text-red-500"
|
|
15
|
+
>
|
|
16
|
+
<svg
|
|
17
|
+
class="w-12 h-12"
|
|
18
|
+
fill="none"
|
|
19
|
+
stroke="currentColor"
|
|
20
|
+
viewBox="0 0 24 24"
|
|
21
|
+
>
|
|
22
|
+
<path
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
stroke-linejoin="round"
|
|
25
|
+
stroke-width="2"
|
|
26
|
+
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
27
|
+
></path>
|
|
28
|
+
</svg>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="space-y-2">
|
|
33
|
+
<h1
|
|
34
|
+
class="text-4xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
|
|
35
|
+
>
|
|
36
|
+
Access Denied
|
|
37
|
+
</h1>
|
|
38
|
+
<p class="text-[var(--kyro-text-secondary)] font-medium">
|
|
39
|
+
You don't have the required permissions to access this page. Please
|
|
40
|
+
contact your administrator if you believe this is an error.
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="pt-4">
|
|
45
|
+
<a
|
|
46
|
+
href={adminPath}
|
|
47
|
+
class="inline-flex items-center gap-2 px-8 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-bold transition-all hover:opacity-90 active:scale-95 shadow-lg"
|
|
48
|
+
>
|
|
49
|
+
<svg
|
|
50
|
+
class="w-4 h-4"
|
|
51
|
+
fill="none"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
>
|
|
55
|
+
<path
|
|
56
|
+
stroke-linecap="round"
|
|
57
|
+
stroke-linejoin="round"
|
|
58
|
+
stroke-width="2.5"
|
|
59
|
+
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
|
60
|
+
></path>
|
|
61
|
+
</svg>
|
|
62
|
+
Back to Dashboard
|
|
63
|
+
</a>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</AdminLayout>
|
|
@@ -1,51 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
import AdminLayout from "../../layouts/AdminLayout.astro";
|
|
3
|
-
import { collections } from "
|
|
4
|
-
import { AutoForm } from "
|
|
3
|
+
import { collections } from "../../lib/config";
|
|
4
|
+
import { AutoForm } from "../../components/AutoForm";
|
|
5
5
|
|
|
6
6
|
const { collection, id } = Astro.params;
|
|
7
7
|
|
|
8
|
+
import { adminPath, apiPath } from "../../lib/paths";
|
|
9
|
+
|
|
8
10
|
// Validate collection exists
|
|
9
11
|
if (!collection || !collections[collection]) {
|
|
10
|
-
return Astro.redirect(
|
|
12
|
+
return Astro.redirect(adminPath);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
const config = collections[collection];
|
|
14
16
|
|
|
15
|
-
// Handle legacy integer IDs (e.g., "team-1" -> find by slug instead)
|
|
16
|
-
let lookupId = id;
|
|
17
|
-
if (id && id.includes("-")) {
|
|
18
|
-
const parts = id.split("-");
|
|
19
|
-
const potentialNum = parts[parts.length - 1];
|
|
20
|
-
if (/^\d+$/.test(potentialNum)) {
|
|
21
|
-
// Legacy integer ID - try to find document by slug from the remaining parts
|
|
22
|
-
const slugPart = parts.slice(0, -1).join("-");
|
|
23
|
-
try {
|
|
24
|
-
const slugResponse = await fetch(
|
|
25
|
-
`${Astro.url.origin}/api/${collection}?limit=100`,
|
|
26
|
-
{
|
|
27
|
-
headers: { "Content-Type": "application/json" },
|
|
28
|
-
},
|
|
29
|
-
);
|
|
30
|
-
if (slugResponse.ok) {
|
|
31
|
-
const slugResult = await slugResponse.json();
|
|
32
|
-
const found = (slugResult.docs || []).find(
|
|
33
|
-
(d: any) => d.slug === slugPart,
|
|
34
|
-
);
|
|
35
|
-
if (found) {
|
|
36
|
-
lookupId = found.id;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
} catch (e) {}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
17
|
// Fetch document if editing
|
|
44
18
|
let doc: any = null;
|
|
45
|
-
|
|
19
|
+
let fetchError: string | null = null;
|
|
20
|
+
if (id && id !== "new") {
|
|
46
21
|
try {
|
|
47
22
|
const response = await fetch(
|
|
48
|
-
`${Astro.url.origin}
|
|
23
|
+
`${Astro.url.origin}${apiPath}/${collection}/${id}`,
|
|
49
24
|
{
|
|
50
25
|
headers: Astro.request.headers,
|
|
51
26
|
credentials: "include",
|
|
@@ -54,17 +29,16 @@ if (lookupId && lookupId !== "new") {
|
|
|
54
29
|
if (response.ok) {
|
|
55
30
|
const result = await response.json();
|
|
56
31
|
doc = result.data || null;
|
|
32
|
+
} else {
|
|
33
|
+
const errorData = await response.json().catch(() => ({}));
|
|
34
|
+
fetchError = errorData.error || `Failed to load document (${response.status})`;
|
|
57
35
|
}
|
|
58
36
|
} catch (error) {
|
|
37
|
+
fetchError = "Failed to fetch document. Check server connection.";
|
|
59
38
|
console.error("Failed to fetch document:", error);
|
|
60
39
|
}
|
|
61
40
|
}
|
|
62
41
|
|
|
63
|
-
// Redirect to UUID URL if using legacy ID
|
|
64
|
-
if (id && lookupId && id !== lookupId && doc) {
|
|
65
|
-
return Astro.redirect(`/${collection}/${lookupId}`, 301);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
42
|
const isNew = !doc;
|
|
69
43
|
const docStatus = doc?.status || "draft";
|
|
70
44
|
const title = isNew
|
|
@@ -76,157 +50,17 @@ const description =
|
|
|
76
50
|
---
|
|
77
51
|
|
|
78
52
|
<AdminLayout title={title}>
|
|
79
|
-
<div class="flex-1 overflow-y-auto
|
|
53
|
+
<div class="flex-1 overflow-y-auto space-y-6">
|
|
80
54
|
<form id="doc-form">
|
|
81
55
|
<AutoForm
|
|
82
56
|
client:only="react"
|
|
83
57
|
config={config}
|
|
84
58
|
data={doc || {}}
|
|
85
59
|
collectionSlug={collection}
|
|
60
|
+
documentId={id || undefined}
|
|
86
61
|
documentName={doc?.title || doc?.name || doc?.slug || "new-document"}
|
|
87
62
|
/>
|
|
88
63
|
<input type="hidden" id="form-data" name="form-data" value="{}" />
|
|
89
64
|
</form>
|
|
90
65
|
</div>
|
|
91
|
-
|
|
92
|
-
<script define:vars={{ collection, id, isNew }}>
|
|
93
|
-
function showToast(message, isError = false) {
|
|
94
|
-
const container = document.getElementById("toast-container");
|
|
95
|
-
const msg = document.getElementById("toast-message");
|
|
96
|
-
if (container && msg) {
|
|
97
|
-
msg.textContent = message;
|
|
98
|
-
container.classList.remove("hidden");
|
|
99
|
-
if (!isError) {
|
|
100
|
-
setTimeout(() => container.classList.add("hidden"), 3000);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Wait for DOM to be ready
|
|
106
|
-
function setupFormHandler() {
|
|
107
|
-
const form = document.getElementById("doc-form");
|
|
108
|
-
const btn = document.getElementById("btn-save");
|
|
109
|
-
|
|
110
|
-
if (!form || !btn) {
|
|
111
|
-
setTimeout(setupFormHandler, 100);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Button click handler
|
|
116
|
-
btn.addEventListener("click", async () => {
|
|
117
|
-
// Check form validity
|
|
118
|
-
if (!form.checkValidity()) {
|
|
119
|
-
form.reportValidity();
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const originalText = btn.textContent || "";
|
|
124
|
-
btn.textContent = "Saving…";
|
|
125
|
-
btn.setAttribute("disabled", "true");
|
|
126
|
-
|
|
127
|
-
// Get data from hidden input (populated by React AutoForm onChange)
|
|
128
|
-
const hiddenInput = document.getElementById("form-data");
|
|
129
|
-
let data = {};
|
|
130
|
-
|
|
131
|
-
if (hiddenInput && hiddenInput.value) {
|
|
132
|
-
try {
|
|
133
|
-
const val = hiddenInput.value;
|
|
134
|
-
if (val) data = JSON.parse(val);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error("Failed to parse form data:", err);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const url = isNew ? `/api/${collection}` : `/api/${collection}/${id}`;
|
|
141
|
-
const method = isNew ? "POST" : "PATCH";
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const response = await fetch(url, {
|
|
145
|
-
method,
|
|
146
|
-
credentials: "include",
|
|
147
|
-
headers: { "Content-Type": "application/json" },
|
|
148
|
-
body: JSON.stringify(data),
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (response.ok) {
|
|
152
|
-
showToast(
|
|
153
|
-
isNew ? "Document created successfully" : "Changes saved",
|
|
154
|
-
);
|
|
155
|
-
setTimeout(() => {
|
|
156
|
-
window.location.href = `/${collection}`;
|
|
157
|
-
}, 800);
|
|
158
|
-
} else {
|
|
159
|
-
const error = await response.json();
|
|
160
|
-
showToast(error.error || "An error occurred", true);
|
|
161
|
-
}
|
|
162
|
-
} catch (error) {
|
|
163
|
-
showToast("Failed to save document", true);
|
|
164
|
-
} finally {
|
|
165
|
-
btn.textContent = originalText;
|
|
166
|
-
btn.removeAttribute("disabled");
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
setupFormHandler();
|
|
172
|
-
|
|
173
|
-
// Setup publish/unpublish handlers
|
|
174
|
-
const btnPublish = document.getElementById("btn-publish");
|
|
175
|
-
const btnUnpublish = document.getElementById("btn-unpublish");
|
|
176
|
-
|
|
177
|
-
async function handlePublish() {
|
|
178
|
-
if (!btnPublish) return;
|
|
179
|
-
btnPublish.textContent = "Publishing...";
|
|
180
|
-
btnPublish.setAttribute("disabled", "true");
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const response = await fetch(`/api/${collection}/${id}/publish`, {
|
|
184
|
-
method: "POST",
|
|
185
|
-
credentials: "include",
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (response.ok) {
|
|
189
|
-
showToast("Published successfully");
|
|
190
|
-
location.reload();
|
|
191
|
-
} else {
|
|
192
|
-
const error = await response.json();
|
|
193
|
-
showToast(error.error || "Failed to publish", true);
|
|
194
|
-
}
|
|
195
|
-
} catch (err) {
|
|
196
|
-
showToast("Failed to publish", true);
|
|
197
|
-
} finally {
|
|
198
|
-
btnPublish.textContent = "Publish";
|
|
199
|
-
btnPublish.removeAttribute("disabled");
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function handleUnpublish() {
|
|
204
|
-
if (!btnUnpublish) return;
|
|
205
|
-
btnUnpublish.textContent = "Unpublishing...";
|
|
206
|
-
btnUnpublish.setAttribute("disabled", "true");
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const response = await fetch(`/api/${collection}/${id}/unpublish`, {
|
|
210
|
-
method: "POST",
|
|
211
|
-
credentials: "include",
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
if (response.ok) {
|
|
215
|
-
showToast("Unpublished successfully");
|
|
216
|
-
location.reload();
|
|
217
|
-
} else {
|
|
218
|
-
const error = await response.json();
|
|
219
|
-
showToast(error.error || "Failed to unpublish", true);
|
|
220
|
-
}
|
|
221
|
-
} catch (err) {
|
|
222
|
-
showToast("Failed to unpublish", true);
|
|
223
|
-
} finally {
|
|
224
|
-
btnUnpublish.textContent = "Unpublish";
|
|
225
|
-
btnUnpublish.removeAttribute("disabled");
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (btnPublish) btnPublish.addEventListener("click", handlePublish);
|
|
230
|
-
if (btnUnpublish) btnUnpublish.addEventListener("click", handleUnpublish);
|
|
231
|
-
</script>
|
|
232
66
|
</AdminLayout>
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
import AdminLayout from "../../layouts/AdminLayout.astro";
|
|
3
|
-
import { collections } from "
|
|
4
|
-
import {
|
|
3
|
+
import { collections } from "../../lib/config";
|
|
4
|
+
import { ListView } from "../../components/ListView";
|
|
5
|
+
import { adminPath, apiPath } from "../../lib/paths";
|
|
5
6
|
|
|
6
7
|
const { collection } = Astro.params;
|
|
7
8
|
|
|
8
9
|
if (!collection || !collections[collection]) {
|
|
9
|
-
return Astro.redirect(
|
|
10
|
+
return Astro.redirect(adminPath);
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
// Redirect system collections to dedicated pages
|
|
14
|
+
if (collection === "users") return Astro.redirect(`${adminPath}/users`);
|
|
15
|
+
if (collection === "audit_logs") return Astro.redirect(`${adminPath}/audit`);
|
|
16
|
+
|
|
12
17
|
const config = collections[collection];
|
|
13
18
|
|
|
14
19
|
const page = parseInt(Astro.url.searchParams.get("page") || "1");
|
|
@@ -19,7 +24,7 @@ let totalDocs = 0;
|
|
|
19
24
|
|
|
20
25
|
try {
|
|
21
26
|
const response = await fetch(
|
|
22
|
-
`${Astro.url.origin}
|
|
27
|
+
`${Astro.url.origin}${apiPath}/${collection}?page=${page}&limit=${limit}&t=${Date.now()}`,
|
|
23
28
|
{
|
|
24
29
|
headers: Astro.request.headers,
|
|
25
30
|
credentials: "include",
|
|
@@ -36,8 +41,8 @@ try {
|
|
|
36
41
|
---
|
|
37
42
|
|
|
38
43
|
<AdminLayout title={config.label || collection || "Collection"}>
|
|
39
|
-
<div class="flex-1 overflow-y-auto
|
|
40
|
-
<
|
|
44
|
+
<div class="flex-1 overflow-y-auto">
|
|
45
|
+
<ListView
|
|
41
46
|
client:load
|
|
42
47
|
collection={config}
|
|
43
48
|
collectionSlug={collection}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminLayout from "../layouts/AdminLayout.astro";
|
|
3
|
+
import { ApiExplorer } from "../components/ApiExplorer";
|
|
4
|
+
|
|
5
|
+
import { adminPath, apiPath } from "../lib/paths";
|
|
6
|
+
|
|
7
|
+
const collectionsResponse = await fetch(
|
|
8
|
+
`${Astro.url.origin}${apiPath}/collections`,
|
|
9
|
+
);
|
|
10
|
+
const collectionsData = await collectionsResponse.json();
|
|
11
|
+
const collections = collectionsData.collections || [];
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<AdminLayout title="API Explorer">
|
|
15
|
+
<div class="flex-1 overflow-hidden">
|
|
16
|
+
<!-- Header -->
|
|
17
|
+
<div class="mb-6 surface-tile">
|
|
18
|
+
<div class="flex items-center justify-between mb-4">
|
|
19
|
+
<div>
|
|
20
|
+
<h1
|
|
21
|
+
class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
|
|
22
|
+
>
|
|
23
|
+
API Explorer
|
|
24
|
+
</h1>
|
|
25
|
+
<p
|
|
26
|
+
class="text-[var(--kyro-text-secondary)] font-bold mt-2 text-sm tracking-wider"
|
|
27
|
+
>
|
|
28
|
+
Test and explore REST API endpoints interactively
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex items-center gap-3">
|
|
32
|
+
<a
|
|
33
|
+
href={`${adminPath}/rest-playground`}
|
|
34
|
+
class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
|
|
35
|
+
>
|
|
36
|
+
<svg
|
|
37
|
+
class="w-4 h-4"
|
|
38
|
+
fill="none"
|
|
39
|
+
stroke="currentColor"
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
>
|
|
42
|
+
<path
|
|
43
|
+
stroke-linecap="round"
|
|
44
|
+
stroke-linejoin="round"
|
|
45
|
+
stroke-width="2"
|
|
46
|
+
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
|
47
|
+
></path>
|
|
48
|
+
</svg>
|
|
49
|
+
Playground
|
|
50
|
+
</a>
|
|
51
|
+
<a
|
|
52
|
+
href={`${apiPath}/collections`}
|
|
53
|
+
target="_blank"
|
|
54
|
+
class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
|
|
55
|
+
>
|
|
56
|
+
<svg
|
|
57
|
+
class="w-4 h-4"
|
|
58
|
+
fill="none"
|
|
59
|
+
stroke="currentColor"
|
|
60
|
+
viewBox="0 0 24 24"
|
|
61
|
+
>
|
|
62
|
+
<path
|
|
63
|
+
stroke-linecap="round"
|
|
64
|
+
stroke-linejoin="round"
|
|
65
|
+
stroke-width="2"
|
|
66
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
67
|
+
></path>
|
|
68
|
+
</svg>
|
|
69
|
+
Collections JSON
|
|
70
|
+
</a>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<!-- HTTP Methods Legend -->
|
|
75
|
+
<div class="flex items-center gap-4 text-xs">
|
|
76
|
+
<span class="text-[var(--kyro-text-muted)]">Methods:</span>
|
|
77
|
+
<span class="px-2 py-1 bg-green-500/10 text-green-600 rounded font-bold"
|
|
78
|
+
>GET</span
|
|
79
|
+
>
|
|
80
|
+
<span class="px-2 py-1 bg-blue-500/10 text-blue-600 rounded font-bold"
|
|
81
|
+
>POST</span
|
|
82
|
+
>
|
|
83
|
+
<span
|
|
84
|
+
class="px-2 py-1 bg-yellow-500/10 text-yellow-600 rounded font-bold"
|
|
85
|
+
>PATCH</span
|
|
86
|
+
>
|
|
87
|
+
<span class="px-2 py-1 bg-red-500/10 text-red-600 rounded font-bold"
|
|
88
|
+
>DELETE</span
|
|
89
|
+
>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- Explorer Component -->
|
|
94
|
+
<div class="surface-tile overflow-hidden p-6">
|
|
95
|
+
<ApiExplorer client:load collections={collections} />
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Quick Reference -->
|
|
99
|
+
<div class="mt-6 surface-tile p-6">
|
|
100
|
+
<h2 class="text-xl font-bold text-[var(--kyro-text-primary)] mb-4">
|
|
101
|
+
Quick Reference
|
|
102
|
+
</h2>
|
|
103
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
|
|
104
|
+
<div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
|
|
105
|
+
<h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
|
|
106
|
+
List All
|
|
107
|
+
</h3>
|
|
108
|
+
<code class="text-xs text-[var(--kyro-text-secondary)] block"
|
|
109
|
+
>GET {apiPath}/:collection</code
|
|
110
|
+
>
|
|
111
|
+
<p class="text-xs text-[var(--kyro-text-muted)] mt-2">
|
|
112
|
+
Returns all documents with pagination
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
|
|
116
|
+
<h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
|
|
117
|
+
Get One
|
|
118
|
+
</h3>
|
|
119
|
+
<code class="text-xs text-[var(--kyro-text-secondary)] block"
|
|
120
|
+
>GET {apiPath}/:collection/:id</code
|
|
121
|
+
>
|
|
122
|
+
<p class="text-xs text-[var(--kyro-text-muted)] mt-2">
|
|
123
|
+
Returns a single document by ID
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
|
|
127
|
+
<h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
|
|
128
|
+
Create
|
|
129
|
+
</h3>
|
|
130
|
+
<code class="text-xs text-[var(--kyro-text-secondary)] block"
|
|
131
|
+
>POST {apiPath}/:collection</code
|
|
132
|
+
>
|
|
133
|
+
<p class="text-xs text-[var(--kyro-text-muted)] mt-2">
|
|
134
|
+
Creates a new document
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
|
|
138
|
+
<h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
|
|
139
|
+
Update
|
|
140
|
+
</h3>
|
|
141
|
+
<code class="text-xs text-[var(--kyro-text-secondary)] block"
|
|
142
|
+
>PATCH {apiPath}/:collection/:id</code
|
|
143
|
+
>
|
|
144
|
+
<p class="text-xs text-[var(--kyro-text-muted)] mt-2">
|
|
145
|
+
Updates an existing document
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
|
|
149
|
+
<h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
|
|
150
|
+
Delete
|
|
151
|
+
</h3>
|
|
152
|
+
<code class="text-xs text-[var(--kyro-text-secondary)] block"
|
|
153
|
+
>DELETE {apiPath}/:collection/:id</code
|
|
154
|
+
>
|
|
155
|
+
<p class="text-xs text-[var(--kyro-text-muted)] mt-2">
|
|
156
|
+
Deletes a document
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
|
|
160
|
+
<h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
|
|
161
|
+
Pagination
|
|
162
|
+
</h3>
|
|
163
|
+
<code class="text-xs text-[var(--kyro-text-secondary)] block"
|
|
164
|
+
>?page=1&limit=10</code
|
|
165
|
+
>
|
|
166
|
+
<p class="text-xs text-[var(--kyro-text-muted)] mt-2">
|
|
167
|
+
Add query params for pagination
|
|
168
|
+
</p>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</AdminLayout>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AuthLayout from "../../layouts/AuthLayout.astro";
|
|
3
|
+
import { adminPath, apiPath } from "../../lib/paths";
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<AuthLayout title="Sign In">
|
|
7
|
+
<div class="surface-tile p-8 w-full" style="max-width: 420px;">
|
|
8
|
+
<div class="text-center mb-8">
|
|
9
|
+
<h1
|
|
10
|
+
class="text-2xl font-bold tracking-tight text-[var(--kyro-text-primary)]"
|
|
11
|
+
>
|
|
12
|
+
Welcome back
|
|
13
|
+
</h1>
|
|
14
|
+
<p class="text-sm text-[var(--kyro-text-secondary)] mt-2">
|
|
15
|
+
Sign in to your account
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<form id="login-form" class="space-y-5">
|
|
20
|
+
<div>
|
|
21
|
+
<label
|
|
22
|
+
for="email"
|
|
23
|
+
class="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2 text-left"
|
|
24
|
+
>Email</label
|
|
25
|
+
>
|
|
26
|
+
<input
|
|
27
|
+
type="email"
|
|
28
|
+
id="email"
|
|
29
|
+
name="email"
|
|
30
|
+
required
|
|
31
|
+
class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
|
|
32
|
+
placeholder="admin@example.com"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<label
|
|
38
|
+
for="password"
|
|
39
|
+
class="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2"
|
|
40
|
+
>Password</label
|
|
41
|
+
>
|
|
42
|
+
<input
|
|
43
|
+
type="password"
|
|
44
|
+
id="password"
|
|
45
|
+
name="password"
|
|
46
|
+
required
|
|
47
|
+
class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
|
|
48
|
+
placeholder="••••••••"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div id="form-message" class="hidden p-3 rounded-xl text-sm font-bold">
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<button
|
|
56
|
+
type="submit"
|
|
57
|
+
class="w-full py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl text-sm font-bold hover:opacity-90 transition-colors shadow-lg"
|
|
58
|
+
>
|
|
59
|
+
Sign In
|
|
60
|
+
</button>
|
|
61
|
+
</form>
|
|
62
|
+
|
|
63
|
+
<p class="text-center text-sm text-[var(--kyro-text-secondary)] mt-6">
|
|
64
|
+
Don't have an account? <a
|
|
65
|
+
href={`${adminPath}/register`}
|
|
66
|
+
class="font-medium text-[var(--kyro-text-primary)] hover:underline"
|
|
67
|
+
>Register</a
|
|
68
|
+
>
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<script define:vars={{ apiPath, adminPath }}>
|
|
73
|
+
document
|
|
74
|
+
.getElementById("login-form")
|
|
75
|
+
?.addEventListener("submit", async (e) => {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
const form = e.target;
|
|
78
|
+
const message = document.getElementById("form-message");
|
|
79
|
+
const button = form.querySelector('button[type="submit"]');
|
|
80
|
+
|
|
81
|
+
const email = form.email.value;
|
|
82
|
+
const password = form.password.value;
|
|
83
|
+
|
|
84
|
+
button.disabled = true;
|
|
85
|
+
button.textContent = "Signing in...";
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch(apiPath + "/auth/login", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
body: JSON.stringify({ email, password }),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const data = await res.json();
|
|
95
|
+
|
|
96
|
+
if (res.ok && data.success) {
|
|
97
|
+
// Cookies set server-side via Set-Cookie headers
|
|
98
|
+
// Store user in memory only (not localStorage)
|
|
99
|
+
window.__kyroAuth = { user: data.user, verified: true };
|
|
100
|
+
message.textContent = "Success! Redirecting...";
|
|
101
|
+
message.className =
|
|
102
|
+
"block p-3 rounded-xl text-sm font-regular bg-green-50 text-green-600";
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
window.location.href = adminPath;
|
|
105
|
+
}, 500);
|
|
106
|
+
} else {
|
|
107
|
+
message.textContent = data.error || "Login failed";
|
|
108
|
+
message.className =
|
|
109
|
+
"block p-3 rounded-xl text-sm font-regular bg-red-50 text-red-600";
|
|
110
|
+
button.disabled = false;
|
|
111
|
+
button.textContent = "Sign In";
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
message.textContent = "Connection error";
|
|
115
|
+
message.className =
|
|
116
|
+
"block p-3 rounded-xl text-sm font-regular bg-red-50 text-red-600";
|
|
117
|
+
button.disabled = false;
|
|
118
|
+
button.textContent = "Sign In";
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
</script>
|
|
122
|
+
</AuthLayout>
|