@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
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
import React, { useState
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { apiPost } from "../lib/api";
|
|
3
|
+
import { useResourceManager } from "../lib/useResourceManager";
|
|
4
|
+
import { useUIStore } from "../lib/stores";
|
|
3
5
|
import {
|
|
4
|
-
Key,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Zap,
|
|
12
|
-
AlertTriangle,
|
|
13
|
-
X,
|
|
14
|
-
Info,
|
|
15
|
-
Terminal,
|
|
16
|
-
ExternalLink,
|
|
17
|
-
Code,
|
|
18
|
-
} from "lucide-react";
|
|
19
|
-
import { ConfirmModal, Modal, ModalContent, ModalActions } from "./ui/Modal";
|
|
6
|
+
Key, Plus, Trash2, Copy, CheckCircle2, Clock,
|
|
7
|
+
Shield, Zap, AlertTriangle, Info, Terminal,
|
|
8
|
+
Code, RefreshCw,
|
|
9
|
+
} from "./ui/icons";
|
|
10
|
+
import { PageHeader } from "./ui/PageHeader";
|
|
11
|
+
import { Badge } from "./ui/Badge";
|
|
12
|
+
import { Modal, ModalContent, ModalActions } from "./ui/Modal";
|
|
20
13
|
|
|
21
14
|
interface ApiKeyItem {
|
|
22
15
|
id: string;
|
|
@@ -26,81 +19,85 @@ interface ApiKeyItem {
|
|
|
26
19
|
permissions?: string[];
|
|
27
20
|
lastUsed?: string;
|
|
28
21
|
createdAt: string;
|
|
22
|
+
expiresAt?: string;
|
|
29
23
|
}
|
|
30
24
|
|
|
25
|
+
interface PermissionsOption {
|
|
26
|
+
label: string;
|
|
27
|
+
value: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ALL_PERMISSIONS: PermissionsOption[] = [
|
|
31
|
+
{ label: "All (*)", value: "*" },
|
|
32
|
+
{ label: "Posts — Read", value: "posts:read" },
|
|
33
|
+
{ label: "Posts — Create", value: "posts:create" },
|
|
34
|
+
{ label: "Posts — Update", value: "posts:update" },
|
|
35
|
+
{ label: "Posts — Delete", value: "posts:delete" },
|
|
36
|
+
{ label: "Pages — Read", value: "pages:read" },
|
|
37
|
+
{ label: "Pages — Create", value: "pages:create" },
|
|
38
|
+
{ label: "Pages — Update", value: "pages:update" },
|
|
39
|
+
{ label: "Pages — Delete", value: "pages:delete" },
|
|
40
|
+
{ label: "Users — Read", value: "users:read" },
|
|
41
|
+
{ label: "Users — Admin", value: "users:admin" },
|
|
42
|
+
{ label: "Media — Read", value: "media:read" },
|
|
43
|
+
{ label: "Media — Upload", value: "media:upload" },
|
|
44
|
+
{ label: "API Keys — Read", value: "apikeys:read" },
|
|
45
|
+
{ label: "API Keys — Admin", value: "apikeys:admin" },
|
|
46
|
+
];
|
|
47
|
+
|
|
31
48
|
export function ApiKeysManager() {
|
|
32
|
-
const
|
|
33
|
-
|
|
49
|
+
const { items: keys, loading, create, update, remove, isCreateModalOpen, setIsCreateModalOpen } =
|
|
50
|
+
useResourceManager<ApiKeyItem>(
|
|
51
|
+
React.useMemo(() => ({
|
|
52
|
+
endpoint: "/api/keys",
|
|
53
|
+
transformLoad: (data) => (data as ApiKeyItem[]).map((k) => ({
|
|
54
|
+
...k,
|
|
55
|
+
keyPrefix: k.keyPrefix || k.key?.substring(0, 8) || "",
|
|
56
|
+
})),
|
|
57
|
+
}), [])
|
|
58
|
+
);
|
|
59
|
+
|
|
34
60
|
const [newKey, setNewKey] = useState<ApiKeyItem | null>(null);
|
|
35
|
-
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
36
|
-
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
|
37
61
|
const [showHelpModal, setShowHelpModal] = useState(false);
|
|
38
|
-
const [showAlertModal, setShowAlertModal] = useState(false);
|
|
39
|
-
const [alertMessage, setAlertMessage] = useState("");
|
|
40
|
-
const [deleteKeyId, setDeleteKeyId] = useState<string | null>(null);
|
|
41
62
|
const [newKeyName, setNewKeyName] = useState("");
|
|
63
|
+
const [newKeyPermissions, setNewKeyPermissions] = useState<string[]>(["*"]);
|
|
64
|
+
const [newKeyExpires, setNewKeyExpires] = useState("");
|
|
42
65
|
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
43
66
|
const [createError, setCreateError] = useState("");
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
setLoading(true);
|
|
47
|
-
try {
|
|
48
|
-
const data = await apiGet("/api/keys");
|
|
49
|
-
setKeys(
|
|
50
|
-
data.map((k: any) => ({
|
|
51
|
-
...k,
|
|
52
|
-
key: k.key,
|
|
53
|
-
keyPrefix: k.keyPrefix || k.key?.substring(0, 8) || "",
|
|
54
|
-
})),
|
|
55
|
-
);
|
|
56
|
-
} catch (e) {
|
|
57
|
-
console.error(e);
|
|
58
|
-
} finally {
|
|
59
|
-
setLoading(false);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
loadKeys();
|
|
65
|
-
}, []);
|
|
67
|
+
const [rotatingId, setRotatingId] = useState<string | null>(null);
|
|
68
|
+
const { alert, confirm: kyroConfirm } = useUIStore();
|
|
66
69
|
|
|
67
70
|
const handleCreateKey = async () => {
|
|
68
|
-
if (!newKeyName.trim()) {
|
|
69
|
-
setCreateError("Please enter a name for the API key");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
71
|
+
if (!newKeyName.trim()) { setCreateError("Name is required"); return; }
|
|
73
72
|
try {
|
|
74
|
-
const
|
|
73
|
+
const data: Record<string, unknown> = { name: newKeyName, permissions: newKeyPermissions };
|
|
74
|
+
if (newKeyExpires) data.expiresAt = new Date(newKeyExpires).toISOString();
|
|
75
|
+
const created = await create(data);
|
|
75
76
|
setNewKey(created);
|
|
76
|
-
setShowCreateModal(false);
|
|
77
77
|
setNewKeyName("");
|
|
78
|
+
setNewKeyPermissions(["*"]);
|
|
79
|
+
setNewKeyExpires("");
|
|
78
80
|
setCreateError("");
|
|
79
|
-
|
|
80
|
-
} catch (e) {
|
|
81
|
-
console.error(e);
|
|
82
|
-
setCreateError("Failed to create API key");
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const handleDeleteKey = async (id: string) => {
|
|
87
|
-
setDeleteKeyId(id);
|
|
88
|
-
setShowDeleteModal(true);
|
|
81
|
+
} catch { setCreateError("Failed to create API key"); }
|
|
89
82
|
};
|
|
90
83
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
84
|
+
const handleRotateKey = (key: ApiKeyItem) => {
|
|
85
|
+
kyroConfirm({
|
|
86
|
+
title: "Rotate API Key",
|
|
87
|
+
message: `Are you sure you want to regenerate the key "${key.name}"? The current key will stop working immediately and this action cannot be undone.`,
|
|
88
|
+
variant: "danger",
|
|
89
|
+
onConfirm: async () => {
|
|
90
|
+
setRotatingId(key.id);
|
|
91
|
+
try {
|
|
92
|
+
const rotated = await apiPost<ApiKeyItem>(`/api/keys/${key.id}/rotate`);
|
|
93
|
+
setNewKey(rotated);
|
|
94
|
+
} catch {
|
|
95
|
+
alert({ title: "Error", message: "Failed to rotate key. Please try again." });
|
|
96
|
+
} finally {
|
|
97
|
+
setRotatingId(null);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
});
|
|
104
101
|
};
|
|
105
102
|
|
|
106
103
|
const copyToClipboard = (key: string, id: string) => {
|
|
@@ -110,314 +107,396 @@ export function ApiKeysManager() {
|
|
|
110
107
|
};
|
|
111
108
|
|
|
112
109
|
return (
|
|
113
|
-
<div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
110
|
+
<div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
|
|
111
|
+
|
|
112
|
+
<PageHeader
|
|
113
|
+
title="API Keys"
|
|
114
|
+
description="Programmatic tokens for secure infrastructure integration."
|
|
115
|
+
icon={Key}
|
|
116
|
+
actions={
|
|
117
117
|
<div className="flex items-center gap-3">
|
|
118
|
-
<h1 className="text-4xl font-black tracking-tighter text-[var(--kyro-text-primary)]">
|
|
119
|
-
API <span className="text-[var(--kyro-primary)]">Keys</span>
|
|
120
|
-
</h1>
|
|
121
118
|
<button
|
|
122
119
|
type="button"
|
|
123
120
|
onClick={() => setShowHelpModal(true)}
|
|
124
|
-
className="
|
|
125
|
-
title="Learn how API keys work"
|
|
121
|
+
className="px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] rounded-xl font-bold text-xs border border-[var(--kyro-border)] hover:bg-[var(--kyro-surface)] transition-all flex items-center gap-2"
|
|
126
122
|
>
|
|
127
|
-
<Info className="w-
|
|
123
|
+
<Info className="w-4 h-4" />
|
|
124
|
+
<span>Integration Guide</span>
|
|
125
|
+
</button>
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onClick={() => { setNewKeyName(""); setCreateError(""); setIsCreateModalOpen(true); }}
|
|
129
|
+
className="px-6 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-bold text-sm shadow-xl shadow-[var(--kyro-primary)]/10 hover:scale-[1.02] active:scale-[0.98] transition-all flex items-center gap-2"
|
|
130
|
+
>
|
|
131
|
+
<Plus className="w-4 h-4" />
|
|
132
|
+
<span>Create Access Key</span>
|
|
128
133
|
</button>
|
|
129
134
|
</div>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
</p>
|
|
133
|
-
</div>
|
|
134
|
-
<button
|
|
135
|
-
type="button"
|
|
136
|
-
onClick={() => {
|
|
137
|
-
setNewKeyName("");
|
|
138
|
-
setCreateError("");
|
|
139
|
-
setShowCreateModal(true);
|
|
140
|
-
}}
|
|
141
|
-
className="flex items-center gap-2 px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-full font-black text-sm shadow-xl hover:opacity-90 active:scale-95 transition-all"
|
|
142
|
-
>
|
|
143
|
-
<Plus className="w-4 h-4" />
|
|
144
|
-
Create API Key
|
|
145
|
-
</button>
|
|
146
|
-
</div>
|
|
135
|
+
}
|
|
136
|
+
/>
|
|
147
137
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
</p>
|
|
159
|
-
<div className="space-y-2">
|
|
160
|
-
<div className="bg-[var(--kyro-bg-secondary)] rounded-lg p-3 font-mono text-xs border border-[var(--kyro-border)]">
|
|
161
|
-
<div className="text-[var(--kyro-text-muted)] mb-2">
|
|
162
|
-
Example request:
|
|
163
|
-
</div>
|
|
164
|
-
<div className="text-[var(--kyro-primary)]">curl -X GET \</div>
|
|
165
|
-
<div className="text-[var(--kyro-text-secondary)]">
|
|
166
|
-
{" "}
|
|
167
|
-
https://yoursite.com/api/posts \
|
|
138
|
+
<div className="grid md:grid-cols-2 gap-6">
|
|
139
|
+
{/* Terminal Card 1 */}
|
|
140
|
+
<div className="group relative overflow-hidden bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-[2rem] p-1 hover:border-[var(--kyro-primary)]/30 transition-all duration-500">
|
|
141
|
+
<div className="p-6 bg-[var(--kyro-surface)] rounded-[1.8rem] h-full">
|
|
142
|
+
<div className="flex items-center justify-between mb-6">
|
|
143
|
+
<div className="flex items-center gap-3">
|
|
144
|
+
<div className="p-2 bg-blue-500/10 rounded-lg">
|
|
145
|
+
<Terminal className="w-4 h-4 text-blue-500" />
|
|
146
|
+
</div>
|
|
147
|
+
<h3 className="text-xs font-bold uppercase tracking-widest opacity-60">Shell / CURL</h3>
|
|
168
148
|
</div>
|
|
169
|
-
<div className="
|
|
170
|
-
|
|
171
|
-
|
|
149
|
+
<div className="flex gap-1.5">
|
|
150
|
+
<div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
|
|
151
|
+
<div className="w-2.5 h-2.5 rounded-full bg-amber-500/20" />
|
|
152
|
+
<div className="w-2.5 h-2.5 rounded-full bg-green-500/20" />
|
|
172
153
|
</div>
|
|
173
154
|
</div>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
155
|
+
|
|
156
|
+
<div className="relative group/code">
|
|
157
|
+
<pre className="p-5 bg-[var(--kyro-bg)] rounded-2xl border border-[var(--kyro-border)] font-mono text-[10px] leading-relaxed overflow-x-auto">
|
|
158
|
+
<div className="flex gap-4">
|
|
159
|
+
<span className="opacity-20 select-none w-4">1</span>
|
|
160
|
+
<span><span className="text-[var(--kyro-primary)]">curl</span> -X GET \</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div className="flex gap-4">
|
|
163
|
+
<span className="opacity-20 select-none w-4">2</span>
|
|
164
|
+
<span> https://api.yoursite.com/v1/posts \</span>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="flex gap-4">
|
|
167
|
+
<span className="opacity-20 select-none w-4">3</span>
|
|
168
|
+
<span> -H <span className="text-green-500">"Authorization: ApiKey kyro_xxx"</span></span>
|
|
169
|
+
</div>
|
|
170
|
+
</pre>
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
onClick={() => navigator.clipboard.writeText('curl -X GET https://api.yoursite.com/v1/posts -H "Authorization: ApiKey YOUR_KEY"')}
|
|
174
|
+
className="absolute top-3 right-3 p-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg opacity-0 group-hover/code:opacity-100 transition-opacity hover:text-[var(--kyro-primary)] shadow-sm"
|
|
175
|
+
>
|
|
176
|
+
<Copy className="w-3.5 h-3.5" />
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
185
179
|
</div>
|
|
186
180
|
</div>
|
|
187
181
|
|
|
188
|
-
{/*
|
|
189
|
-
<div className="
|
|
190
|
-
<div className="
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
182
|
+
{/* Terminal Card 2 */}
|
|
183
|
+
<div className="group relative overflow-hidden bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-[2rem] p-1 hover:border-[var(--kyro-primary)]/30 transition-all duration-500">
|
|
184
|
+
<div className="p-6 bg-[var(--kyro-surface)] rounded-[1.8rem] h-full">
|
|
185
|
+
<div className="flex items-center justify-between mb-6">
|
|
186
|
+
<div className="flex items-center gap-3">
|
|
187
|
+
<div className="p-2 bg-indigo-500/10 rounded-lg">
|
|
188
|
+
<Code className="w-4 h-4 text-indigo-500" />
|
|
189
|
+
</div>
|
|
190
|
+
<h3 className="text-xs font-bold uppercase tracking-widest opacity-60">JavaScript SDK</h3>
|
|
191
|
+
</div>
|
|
192
|
+
<div className="flex gap-1.5">
|
|
193
|
+
<div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
|
|
194
|
+
<div className="w-2.5 h-2.5 rounded-full bg-amber-500/20" />
|
|
195
|
+
<div className="w-2.5 h-2.5 rounded-full bg-green-500/20" />
|
|
196
|
+
</div>
|
|
201
197
|
</div>
|
|
202
|
-
|
|
203
|
-
<div>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
198
|
+
|
|
199
|
+
<div className="relative group/code">
|
|
200
|
+
<pre className="p-5 bg-[var(--kyro-bg)] rounded-2xl border border-[var(--kyro-border)] font-mono text-[10px] leading-relaxed overflow-x-auto">
|
|
201
|
+
<div className="flex gap-4">
|
|
202
|
+
<span className="opacity-20 select-none w-4">1</span>
|
|
203
|
+
<span><span className="text-indigo-400">const</span> res = <span className="text-indigo-400">await</span> <span className="text-blue-400">fetch</span>(url, {"{"}</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div className="flex gap-4">
|
|
206
|
+
<span className="opacity-20 select-none w-4">2</span>
|
|
207
|
+
<span> headers: {"{"} </span>
|
|
208
|
+
</div>
|
|
209
|
+
<div className="flex gap-4">
|
|
210
|
+
<span className="opacity-20 select-none w-4">3</span>
|
|
211
|
+
<span> <span className="text-green-500">'Authorization'</span>: <span className="text-green-500">'ApiKey xxx'</span></span>
|
|
212
|
+
</div>
|
|
213
|
+
<div className="flex gap-4">
|
|
214
|
+
<span className="opacity-20 select-none w-4">4</span>
|
|
215
|
+
<span> {"}"}</span>
|
|
216
|
+
</div>
|
|
217
|
+
<div className="flex gap-4">
|
|
218
|
+
<span className="opacity-20 select-none w-4">5</span>
|
|
219
|
+
<span>{"}"});</span>
|
|
220
|
+
</div>
|
|
221
|
+
</pre>
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
onClick={() => navigator.clipboard.writeText('await fetch(url, { headers: { "Authorization": "ApiKey YOUR_KEY" } })')}
|
|
225
|
+
className="absolute top-3 right-3 p-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg opacity-0 group-hover/code:opacity-100 transition-opacity hover:text-[var(--kyro-primary)] shadow-sm"
|
|
226
|
+
>
|
|
227
|
+
<Copy className="w-3.5 h-3.5" />
|
|
228
|
+
</button>
|
|
207
229
|
</div>
|
|
208
|
-
<div> {"}"}</div>
|
|
209
|
-
<div>{"}"})</div>
|
|
210
230
|
</div>
|
|
211
231
|
</div>
|
|
212
232
|
</div>
|
|
213
233
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
<div className="
|
|
217
|
-
<
|
|
218
|
-
|
|
219
|
-
<h4 className="font-bold text-[var(--kyro-text-primary)] mb-2">
|
|
220
|
-
Best Practices for API Keys
|
|
221
|
-
</h4>
|
|
222
|
-
<ul className="text-sm text-[var(--kyro-text-secondary)] grid md:grid-cols-2 gap-2">
|
|
223
|
-
<li className="flex items-center gap-2">
|
|
224
|
-
<span className="text-amber-600">•</span> Never commit to
|
|
225
|
-
version control
|
|
226
|
-
</li>
|
|
227
|
-
<li className="flex items-center gap-2">
|
|
228
|
-
<span className="text-amber-600">•</span> Store in environment
|
|
229
|
-
variables
|
|
230
|
-
</li>
|
|
231
|
-
<li className="flex items-center gap-2">
|
|
232
|
-
<span className="text-amber-600">•</span> Use separate keys per
|
|
233
|
-
app
|
|
234
|
-
</li>
|
|
235
|
-
<li className="flex items-center gap-2">
|
|
236
|
-
<span className="text-amber-600">•</span> Revoke unused keys
|
|
237
|
-
</li>
|
|
238
|
-
</ul>
|
|
234
|
+
<div className="flex surface-tile flex-col gap-8">
|
|
235
|
+
{/* Warning/Best Practices Banner */}
|
|
236
|
+
<div className="relative overflow-hidden p-6 rounded-3xl border border-[var(--kyro-border)] bg-gradient-to-br from-[var(--kyro-surface-accent)] to-transparent group">
|
|
237
|
+
<div className="absolute top-0 right-0 p-8 opacity-5 group-hover:opacity-10 transition-opacity">
|
|
238
|
+
<Shield className="w-32 h-32 rotate-12" />
|
|
239
239
|
</div>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
240
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
<div className="flex items-start gap-4">
|
|
247
|
-
<div className="p-3 bg-green-500/20 rounded-2xl">
|
|
248
|
-
<CheckCircle2 className="w-6 h-6 text-green-500" />
|
|
241
|
+
<div className="flex items-start gap-4 relative z-10">
|
|
242
|
+
<div className="p-3 bg-amber-500/10 rounded-2xl">
|
|
243
|
+
<Shield className="w-6 h-6 text-amber-500" />
|
|
249
244
|
</div>
|
|
250
|
-
<div
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<p className="text-sm text-[var(--kyro-text-secondary)] mb-4">
|
|
255
|
-
Copy this key now.{" "}
|
|
256
|
-
<span className="text-red-500 font-bold">
|
|
257
|
-
This is the only time it will be shown.
|
|
258
|
-
</span>
|
|
245
|
+
<div>
|
|
246
|
+
<h4 className="text-lg font-bold text-[var(--kyro-text-primary)] mb-1">Security Best Practices</h4>
|
|
247
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-4 max-w-2xl">
|
|
248
|
+
API keys grant full access to your resources. Treat them with the same level of security as your passwords.
|
|
259
249
|
</p>
|
|
260
|
-
<div className="
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
250
|
+
<div className="grid sm:grid-cols-2 gap-x-8 gap-y-2">
|
|
251
|
+
{[
|
|
252
|
+
"Never commit keys to version control",
|
|
253
|
+
"Rotate keys every 90 days",
|
|
254
|
+
"Use specific permissions (least privilege)",
|
|
255
|
+
"Store keys in secure environment variables"
|
|
256
|
+
].map((text, i) => (
|
|
257
|
+
<div key={i} className="flex items-center gap-2 text-xs font-medium text-[var(--kyro-text-secondary)]">
|
|
258
|
+
<div className="w-1.5 h-1.5 rounded-full bg-amber-500/50" />
|
|
259
|
+
{text}
|
|
260
|
+
</div>
|
|
261
|
+
))}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* Success Banner for New Keys */}
|
|
268
|
+
{newKey && (
|
|
269
|
+
<div className="relative overflow-hidden p-8 rounded-3xl border border-green-500/30 bg-green-500/5 backdrop-blur-xl animate-in fade-in slide-in-from-top-4 duration-500">
|
|
270
|
+
<div className="flex items-start gap-6">
|
|
271
|
+
<div className="p-4 bg-green-500/10 rounded-2xl">
|
|
272
|
+
<CheckCircle2 className="w-8 h-8 text-green-500" />
|
|
273
|
+
</div>
|
|
274
|
+
<div className="flex-1">
|
|
275
|
+
<div className="flex items-center justify-between mb-2">
|
|
276
|
+
<h3 className="text-xl font-bold text-green-500">API Key Generated</h3>
|
|
277
|
+
<Badge variant="success" className="text-[10px] font-bold">New</Badge>
|
|
278
|
+
</div>
|
|
279
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] mb-6">
|
|
280
|
+
This is the <span className="text-green-500 font-bold uppercase tracking-tight">only time</span> the full key will be shown. Please store it securely.
|
|
281
|
+
</p>
|
|
282
|
+
|
|
283
|
+
<div className="flex flex-col sm:flex-row gap-3">
|
|
284
|
+
<div className="flex-1 flex items-center gap-3 p-4 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-2xl font-mono text-sm break-all group relative">
|
|
285
|
+
<span className="opacity-80 select-all">{newKey.key}</span>
|
|
286
|
+
</div>
|
|
287
|
+
<button
|
|
288
|
+
type="button"
|
|
289
|
+
onClick={() => copyToClipboard(newKey.key!, newKey.id)}
|
|
290
|
+
className="flex items-center justify-center gap-2 px-8 py-4 bg-[var(--kyro-primary)] text-white rounded-2xl font-bold hover:scale-[1.02] active:scale-[0.98] transition-all shadow-lg shadow-[var(--kyro-primary)]/20"
|
|
291
|
+
>
|
|
292
|
+
{copiedId === newKey.id ? <CheckCircle2 className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
|
|
293
|
+
<span>{copiedId === newKey.id ? "Copied" : "Copy Key"}</span>
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
264
297
|
<button
|
|
265
298
|
type="button"
|
|
266
|
-
onClick={() =>
|
|
267
|
-
className="
|
|
268
|
-
title="Copy to clipboard"
|
|
299
|
+
onClick={() => setNewKey(null)}
|
|
300
|
+
className="mt-6 text-xs font-bold uppercase tracking-widest text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-colors"
|
|
269
301
|
>
|
|
270
|
-
|
|
271
|
-
<CheckCircle2 className="w-5 h-5" />
|
|
272
|
-
) : (
|
|
273
|
-
<Copy className="w-5 h-5" />
|
|
274
|
-
)}
|
|
302
|
+
Dismiss Message
|
|
275
303
|
</button>
|
|
276
304
|
</div>
|
|
277
305
|
</div>
|
|
278
306
|
</div>
|
|
279
|
-
|
|
280
|
-
type="button"
|
|
281
|
-
onClick={() => setNewKey(null)}
|
|
282
|
-
className="mt-6 text-sm font-bold text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
|
|
283
|
-
>
|
|
284
|
-
I've copied my key - dismiss this message
|
|
285
|
-
</button>
|
|
286
|
-
</div>
|
|
287
|
-
)}
|
|
288
|
-
|
|
289
|
-
{/* Keys List */}
|
|
290
|
-
<section className="space-y-6">
|
|
291
|
-
<div className="flex items-center gap-2 px-2">
|
|
292
|
-
<Key className="w-4 h-4 text-[var(--kyro-primary)] opacity-40" />
|
|
293
|
-
<span className="text-[10px] font-black uppercase tracking-widest opacity-40">
|
|
294
|
-
Your API Keys
|
|
295
|
-
</span>
|
|
296
|
-
</div>
|
|
307
|
+
)}
|
|
297
308
|
|
|
298
|
-
|
|
299
|
-
<div className="
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
<section className="space-y-6">
|
|
310
|
+
<div className="flex items-center justify-between px-2">
|
|
311
|
+
<div className="flex items-center gap-2">
|
|
312
|
+
<div className="w-1 h-4 bg-[var(--kyro-primary)] rounded-full" />
|
|
313
|
+
<h2 className="text-sm font-medium tracking-[0.2em] opacity-40">Active Credentials</h2>
|
|
314
|
+
</div>
|
|
315
|
+
<div className="text-[10px] font-bold opacity-40">
|
|
316
|
+
{keys.length} KEY{keys.length !== 1 && "S"}
|
|
304
317
|
</div>
|
|
305
|
-
<h3 className="text-lg font-black mb-2">No API Keys Yet</h3>
|
|
306
|
-
<p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-6">
|
|
307
|
-
Create your first API key to authenticate with the API.
|
|
308
|
-
</p>
|
|
309
|
-
<button
|
|
310
|
-
type="button"
|
|
311
|
-
onClick={() => setShowCreateModal(true)}
|
|
312
|
-
className="px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-full font-black text-sm hover:opacity-90 transition-all"
|
|
313
|
-
>
|
|
314
|
-
<Plus className="w-4 h-4 inline mr-2" />
|
|
315
|
-
Create Your First Key
|
|
316
|
-
</button>
|
|
317
318
|
</div>
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
319
|
+
|
|
320
|
+
{loading ? (
|
|
321
|
+
<div className="flex items-center justify-center p-20 surface-tile rounded-3xl opacity-50 italic">
|
|
322
|
+
Synchronizing with vault...
|
|
323
|
+
</div>
|
|
324
|
+
) : keys.length === 0 ? (
|
|
325
|
+
<div className="p-16 text-center rounded-[3rem] border-2 border-dashed border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/30">
|
|
326
|
+
<div className="w-20 h-20 mx-auto mb-6 bg-gradient-to-tr from-[var(--kyro-surface)] to-[var(--kyro-surface-accent)] rounded-3xl flex items-center justify-center shadow-xl border border-[var(--kyro-border)]">
|
|
327
|
+
<Key className="w-10 h-10 text-[var(--kyro-primary)]" />
|
|
328
|
+
</div>
|
|
329
|
+
<h3 className="text-2xl font-bold mb-3">Initialize Your Access</h3>
|
|
330
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-8 max-w-sm mx-auto">
|
|
331
|
+
No API credentials found. Generate a key to begin interacting with the Kyro CMS programmatic interface.
|
|
332
|
+
</p>
|
|
333
|
+
<button
|
|
334
|
+
type="button"
|
|
335
|
+
onClick={() => setIsCreateModalOpen(true)}
|
|
336
|
+
className="inline-flex items-center gap-3 px-8 py-4 bg-[var(--kyro-primary)] text-white rounded-2xl font-bold hover:scale-[1.05] transition-all shadow-xl shadow-[var(--kyro-primary)]/10"
|
|
324
337
|
>
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
338
|
+
<Plus className="w-5 h-5" />
|
|
339
|
+
Generate API Key
|
|
340
|
+
</button>
|
|
341
|
+
</div>
|
|
342
|
+
) : (
|
|
343
|
+
<div className="grid gap-4">
|
|
344
|
+
{keys.map((key) => (
|
|
345
|
+
<div
|
|
346
|
+
key={key.id}
|
|
347
|
+
className="group relative overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-3xl p-6 hover:border-[var(--kyro-primary)]/50 transition-all duration-300"
|
|
348
|
+
>
|
|
349
|
+
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 relative z-10">
|
|
350
|
+
<div className="flex-1">
|
|
351
|
+
<div className="flex items-center gap-3 mb-4">
|
|
352
|
+
<div className="p-2.5 bg-[var(--kyro-surface-accent)] rounded-xl group-hover:bg-[var(--kyro-primary)]/10 transition-colors">
|
|
353
|
+
<Key className="w-5 h-5 text-[var(--kyro-text-secondary)] group-hover:text-[var(--kyro-primary)] transition-colors" />
|
|
354
|
+
</div>
|
|
355
|
+
<div>
|
|
356
|
+
<h3 className="text-lg font-bold group-hover:text-[var(--kyro-primary)] transition-colors">{key.name}</h3>
|
|
357
|
+
<div className="flex items-center gap-2 mt-0.5">
|
|
358
|
+
<span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
|
359
|
+
<span className="text-[10px] font-bold uppercase tracking-wider text-green-500">Live</span>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
346
362
|
</div>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
363
|
+
|
|
364
|
+
<div className="grid sm:grid-cols-3 gap-6 pt-2">
|
|
365
|
+
<div className="space-y-1">
|
|
366
|
+
<span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Key Snippet</span>
|
|
367
|
+
<div className="font-mono text-sm opacity-60 tracking-tighter">
|
|
368
|
+
{key.keyPrefix}<span className="opacity-20">••••••••••••••••</span>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
<div className="space-y-1 border-l border-[var(--kyro-border)] pl-6">
|
|
372
|
+
<span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Usage Context</span>
|
|
373
|
+
<div className="flex flex-wrap gap-1">
|
|
374
|
+
{key.permissions?.includes("*") ? (
|
|
375
|
+
<Badge variant="warning" className="text-[8px] font-bold uppercase px-1.5">Full Access</Badge>
|
|
376
|
+
) : (
|
|
377
|
+
key.permissions?.slice(0, 2).map((p) => (
|
|
378
|
+
<Badge key={p} variant="outline" className="text-[8px] font-bold px-1.5 opacity-60">{p}</Badge>
|
|
379
|
+
))
|
|
380
|
+
)}
|
|
381
|
+
{key.permissions && key.permissions.length > 2 && (
|
|
382
|
+
<span className="text-[8px] font-bold opacity-30">+{key.permissions.length - 2} more</span>
|
|
383
|
+
)}
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<div className="space-y-1 border-l border-[var(--kyro-border)] pl-6">
|
|
387
|
+
<span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Last Activity</span>
|
|
388
|
+
<div className="text-[10px] font-bold opacity-60 flex items-center gap-1.5">
|
|
389
|
+
<Clock className="w-3 h-3" />
|
|
390
|
+
{key.lastUsed ? new Date(key.lastUsed).toLocaleDateString() : "Never used"}
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
350
393
|
</div>
|
|
351
394
|
</div>
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
395
|
+
|
|
396
|
+
<div className="flex items-center gap-2 lg:bg-[var(--kyro-surface-accent)]/50 lg:p-2 lg:rounded-2xl lg:opacity-0 group-hover:opacity-100 transition-all duration-300 lg:translate-x-4 group-hover:translate-x-0">
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
onClick={() => copyToClipboard(key.key || `${key.keyPrefix}...`, key.id)}
|
|
400
|
+
className="flex-1 lg:flex-none p-3 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl hover:border-[var(--kyro-primary)] transition-all flex items-center justify-center gap-2"
|
|
401
|
+
title="Copy key"
|
|
402
|
+
>
|
|
403
|
+
{copiedId === key.id ? <CheckCircle2 className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
|
|
404
|
+
<span className="text-xs font-bold">{copiedId === key.id ? "Copied" : "Copy"}</span>
|
|
405
|
+
</button>
|
|
406
|
+
|
|
407
|
+
<div className="w-px h-6 bg-[var(--kyro-border)] mx-1 hidden lg:block" />
|
|
408
|
+
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
onClick={() => handleRotateKey(key)}
|
|
412
|
+
disabled={rotatingId === key.id}
|
|
413
|
+
className="p-3 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl hover:border-[var(--kyro-primary)] transition-all group/rotate"
|
|
414
|
+
title="Rotate Key"
|
|
415
|
+
>
|
|
416
|
+
<RefreshCw className={`w-4 h-4 text-[var(--kyro-text-secondary)] group-hover/rotate:text-[var(--kyro-primary)] ${rotatingId === key.id ? "animate-spin" : ""}`} />
|
|
417
|
+
</button>
|
|
418
|
+
|
|
419
|
+
<button
|
|
420
|
+
type="button"
|
|
421
|
+
onClick={() => remove(key.id, "API Key")}
|
|
422
|
+
className="p-3 bg-red-500/5 border border-red-500/10 rounded-xl hover:bg-red-500/10 hover:border-red-500/30 transition-all group/delete"
|
|
423
|
+
title="Revoke Access"
|
|
424
|
+
>
|
|
425
|
+
<Trash2 className="w-4 h-4 text-red-500/50 group-hover/delete:text-red-500" />
|
|
426
|
+
</button>
|
|
427
|
+
</div>
|
|
381
428
|
</div>
|
|
382
429
|
</div>
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
</
|
|
430
|
+
))}
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
433
|
+
</section>
|
|
434
|
+
</div>
|
|
388
435
|
|
|
389
436
|
{/* Create Modal */}
|
|
390
|
-
<Modal
|
|
391
|
-
open={showCreateModal}
|
|
392
|
-
onClose={() => setShowCreateModal(false)}
|
|
393
|
-
title="Create New API Key"
|
|
394
|
-
>
|
|
437
|
+
<Modal size="lg" open={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} title="Create New API Key">
|
|
395
438
|
<ModalContent>
|
|
396
|
-
<
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
/>
|
|
411
|
-
{createError && (
|
|
412
|
-
<p className="mt-2 text-sm text-red-500">{createError}</p>
|
|
413
|
-
)}
|
|
414
|
-
<div className="mt-4 p-4 bg-amber-500/10 border border-amber-500/20 rounded-xl flex items-start gap-3">
|
|
415
|
-
<AlertTriangle className="w-5 h-5 text-amber-500 flex-shrink-0 mt-0.5" />
|
|
439
|
+
<div className="space-y-5">
|
|
440
|
+
<div>
|
|
441
|
+
<label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)]">Name</label>
|
|
442
|
+
<input
|
|
443
|
+
type="text"
|
|
444
|
+
value={newKeyName}
|
|
445
|
+
onChange={(e) => { setNewKeyName(e.target.value); setCreateError(""); }}
|
|
446
|
+
placeholder="e.g., Production App, Staging, Mobile App"
|
|
447
|
+
className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
|
|
448
|
+
onKeyDown={(e) => e.key === "Enter" && handleCreateKey()}
|
|
449
|
+
/>
|
|
450
|
+
{createError && <p className="mt-1.5 text-xs text-red-500">{createError}</p>}
|
|
451
|
+
</div>
|
|
452
|
+
|
|
416
453
|
<div>
|
|
417
|
-
<
|
|
418
|
-
<
|
|
419
|
-
|
|
420
|
-
|
|
454
|
+
<label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)]">Permissions</label>
|
|
455
|
+
<div className="space-y-1 max-h-48 overflow-y-auto">
|
|
456
|
+
{ALL_PERMISSIONS.map((opt) => (
|
|
457
|
+
<label key={opt.value} className="flex items-center gap-3 p-2 rounded-lg hover:bg-[var(--kyro-surface-accent)] cursor-pointer">
|
|
458
|
+
<input
|
|
459
|
+
type="checkbox"
|
|
460
|
+
checked={newKeyPermissions.includes(opt.value)}
|
|
461
|
+
onChange={(e) => {
|
|
462
|
+
if (opt.value === "*") {
|
|
463
|
+
setNewKeyPermissions(e.target.checked ? ["*"] : []);
|
|
464
|
+
} else {
|
|
465
|
+
setNewKeyPermissions(
|
|
466
|
+
e.target.checked
|
|
467
|
+
? [...newKeyPermissions.filter((p) => p !== "*"), opt.value]
|
|
468
|
+
: newKeyPermissions.filter((p) => p !== opt.value)
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}}
|
|
472
|
+
className="accent-[var(--kyro-primary)]"
|
|
473
|
+
/>
|
|
474
|
+
<span className="text-sm font-mono">{opt.label}</span>
|
|
475
|
+
</label>
|
|
476
|
+
))}
|
|
477
|
+
</div>
|
|
478
|
+
<p className="mt-1 text-[10px] text-[var(--kyro-text-muted)]">
|
|
479
|
+
Select specific permissions or choose "*" for full access.
|
|
480
|
+
</p>
|
|
481
|
+
</div>
|
|
482
|
+
|
|
483
|
+
<div>
|
|
484
|
+
<label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)]">Expires (optional)</label>
|
|
485
|
+
<input
|
|
486
|
+
type="datetime-local"
|
|
487
|
+
value={newKeyExpires}
|
|
488
|
+
onChange={(e) => setNewKeyExpires(e.target.value)}
|
|
489
|
+
className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
|
|
490
|
+
/>
|
|
491
|
+
<p className="mt-1 text-[10px] text-[var(--kyro-text-muted)]">
|
|
492
|
+
Leave empty for no expiration. Time is in UTC.
|
|
493
|
+
</p>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<div className="p-3 bg-amber-500/10 border border-amber-500/20 rounded-xl flex items-start gap-2.5">
|
|
497
|
+
<AlertTriangle className="w-4 h-4 text-amber-500 flex-shrink-0 mt-0.5" />
|
|
498
|
+
<p className="text-[11px] text-amber-600 font-medium">
|
|
499
|
+
The key will be shown only once after creation — copy it immediately.
|
|
421
500
|
</p>
|
|
422
501
|
</div>
|
|
423
502
|
</div>
|
|
@@ -425,8 +504,8 @@ export function ApiKeysManager() {
|
|
|
425
504
|
<ModalActions>
|
|
426
505
|
<button
|
|
427
506
|
type="button"
|
|
428
|
-
onClick={() =>
|
|
429
|
-
className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]
|
|
507
|
+
onClick={() => setIsCreateModalOpen(false)}
|
|
508
|
+
className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
|
|
430
509
|
>
|
|
431
510
|
Cancel
|
|
432
511
|
</button>
|
|
@@ -440,70 +519,24 @@ export function ApiKeysManager() {
|
|
|
440
519
|
</ModalActions>
|
|
441
520
|
</Modal>
|
|
442
521
|
|
|
443
|
-
{/* Delete Confirmation Modal */}
|
|
444
|
-
<Modal
|
|
445
|
-
open={showDeleteModal}
|
|
446
|
-
onClose={() => setShowDeleteModal(false)}
|
|
447
|
-
title="Delete API Key"
|
|
448
|
-
variant="danger"
|
|
449
|
-
>
|
|
450
|
-
<ModalContent>
|
|
451
|
-
<p className="text-sm text-[var(--kyro-text-secondary)] mb-4">
|
|
452
|
-
Are you sure you want to delete this API key? This action cannot be
|
|
453
|
-
undone.
|
|
454
|
-
</p>
|
|
455
|
-
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl">
|
|
456
|
-
<p className="text-sm font-medium text-red-500">
|
|
457
|
-
Any applications or integrations using this key will immediately
|
|
458
|
-
lose access.
|
|
459
|
-
</p>
|
|
460
|
-
</div>
|
|
461
|
-
</ModalContent>
|
|
462
|
-
<ModalActions>
|
|
463
|
-
<button
|
|
464
|
-
type="button"
|
|
465
|
-
onClick={() => setShowDeleteModal(false)}
|
|
466
|
-
className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
|
|
467
|
-
>
|
|
468
|
-
Keep Key
|
|
469
|
-
</button>
|
|
470
|
-
<button
|
|
471
|
-
type="button"
|
|
472
|
-
onClick={confirmDeleteKey}
|
|
473
|
-
className="px-4 py-2 rounded-lg font-medium text-sm bg-red-500 text-white hover:bg-red-600 transition-colors"
|
|
474
|
-
>
|
|
475
|
-
Delete Permanently
|
|
476
|
-
</button>
|
|
477
|
-
</ModalActions>
|
|
478
|
-
</Modal>
|
|
479
|
-
|
|
480
522
|
{/* Help Modal */}
|
|
481
|
-
<Modal
|
|
482
|
-
open={showHelpModal}
|
|
483
|
-
onClose={() => setShowHelpModal(false)}
|
|
484
|
-
title="How API Keys Work"
|
|
485
|
-
>
|
|
523
|
+
<Modal size="lg" open={showHelpModal} onClose={() => setShowHelpModal(false)} title="How API Keys Work">
|
|
486
524
|
<ModalContent>
|
|
487
525
|
<div className="space-y-6">
|
|
488
526
|
<div>
|
|
489
527
|
<h4 className="font-bold mb-2">What is an API key?</h4>
|
|
490
528
|
<p className="text-sm text-[var(--kyro-text-secondary)]">
|
|
491
|
-
An API key is a unique token that authenticates your requests to
|
|
492
|
-
the API. Think of it as a password that's specifically for
|
|
493
|
-
programmatic access.
|
|
529
|
+
An API key is a unique token that authenticates your requests to the API. Think of it as a password that's specifically for programmatic access.
|
|
494
530
|
</p>
|
|
495
531
|
</div>
|
|
496
532
|
<div>
|
|
497
533
|
<h4 className="font-bold mb-2">How to use it</h4>
|
|
498
534
|
<p className="text-sm text-[var(--kyro-text-secondary)] mb-3">
|
|
499
|
-
Add your API key to the Authorization header of your HTTP
|
|
500
|
-
requests:
|
|
535
|
+
Add your API key to the Authorization header of your HTTP requests:
|
|
501
536
|
</p>
|
|
502
537
|
<div className="bg-[var(--kyro-bg)] rounded-lg p-4 font-mono text-sm space-y-2">
|
|
503
538
|
<div>
|
|
504
|
-
<span className="text-[var(--kyro-text-secondary)]">
|
|
505
|
-
Authorization:
|
|
506
|
-
</span>{" "}
|
|
539
|
+
<span className="text-[var(--kyro-text-secondary)]">Authorization:</span>{" "}
|
|
507
540
|
<span className="text-[var(--kyro-primary)]">ApiKey </span>
|
|
508
541
|
<span className="text-green-500">kyro_xxxxxxxxxxxx</span>
|
|
509
542
|
</div>
|
|
@@ -513,9 +546,7 @@ export function ApiKeysManager() {
|
|
|
513
546
|
<h4 className="font-bold mb-2">Best practices</h4>
|
|
514
547
|
<ul className="text-sm text-[var(--kyro-text-secondary)] space-y-2 list-disc list-inside">
|
|
515
548
|
<li>Never share your API key publicly</li>
|
|
516
|
-
<li>
|
|
517
|
-
Store it securely (environment variables, secrets manager)
|
|
518
|
-
</li>
|
|
549
|
+
<li>Store it securely (environment variables, secrets manager)</li>
|
|
519
550
|
<li>Create separate keys for different applications</li>
|
|
520
551
|
<li>Revoke keys that are no longer in use</li>
|
|
521
552
|
</ul>
|
|
@@ -532,28 +563,6 @@ export function ApiKeysManager() {
|
|
|
532
563
|
</button>
|
|
533
564
|
</ModalActions>
|
|
534
565
|
</Modal>
|
|
535
|
-
|
|
536
|
-
{/* Alert Modal */}
|
|
537
|
-
<Modal
|
|
538
|
-
open={showAlertModal}
|
|
539
|
-
onClose={() => setShowAlertModal(false)}
|
|
540
|
-
title="Error"
|
|
541
|
-
>
|
|
542
|
-
<ModalContent>
|
|
543
|
-
<p className="text-sm text-[var(--kyro-text-secondary)]">
|
|
544
|
-
{alertMessage}
|
|
545
|
-
</p>
|
|
546
|
-
</ModalContent>
|
|
547
|
-
<ModalActions>
|
|
548
|
-
<button
|
|
549
|
-
type="button"
|
|
550
|
-
onClick={() => setShowAlertModal(false)}
|
|
551
|
-
className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
|
|
552
|
-
>
|
|
553
|
-
OK
|
|
554
|
-
</button>
|
|
555
|
-
</ModalActions>
|
|
556
|
-
</Modal>
|
|
557
566
|
</div>
|
|
558
567
|
);
|
|
559
|
-
}
|
|
568
|
+
}
|