@kyro-cms/admin 0.12.19 → 0.12.21
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/index.cjs +24 -24
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +19 -19
- package/package.json +6 -6
- package/src/components/ApiKeysManager.tsx +177 -227
- package/src/components/AuditLogsPage.tsx +7 -7
- package/src/components/AuthorDashboard.tsx +235 -0
- package/src/components/AutoForm.tsx +15 -4
- package/src/components/CustomerDashboard.tsx +213 -0
- package/src/components/Dashboard.tsx +1 -1
- package/src/components/DashboardMetrics.tsx +74 -9
- package/src/components/DashboardQuickSections.tsx +237 -0
- package/src/components/ListView.tsx +3 -3
- package/src/components/MediaGallery.tsx +2 -2
- package/src/components/PluginsManager.tsx +1 -1
- package/src/components/SessionsManager.tsx +15 -15
- package/src/components/Sidebar.astro +564 -116
- package/src/components/UserManagement.tsx +43 -9
- package/src/components/UserMenu.tsx +123 -61
- package/src/components/autoform/AutoFormEditView.tsx +4 -4
- package/src/components/autoform/AutoFormHeader.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +8 -8
- package/src/components/fields/CodeField.tsx +0 -1
- package/src/components/fields/TabsLayout.tsx +5 -2
- package/src/components/ui/CommandPalette.tsx +1 -1
- package/src/components/ui/Dropdown.tsx +71 -16
- package/src/components/ui/PageHeader.tsx +1 -1
- package/src/components/ui/Pagination.tsx +1 -1
- package/src/components/ui/SeoPreview.tsx +25 -21
- package/src/components/ui/Toast.tsx +3 -2
- package/src/components/ui/Toaster.tsx +1 -1
- package/src/components/users/UserDetail.tsx +74 -29
- package/src/components/users/UserForm.tsx +1 -1
- package/src/components/users/UsersList.tsx +2 -2
- package/src/integration.ts +4 -0
- package/src/layouts/AdminLayout.astro +26 -34
- package/src/layouts/AuthLayout.astro +18 -9
- package/src/lib/auth-server.ts +99 -0
- package/src/pages/403.astro +54 -35
- package/src/pages/[collection]/[id].astro +8 -0
- package/src/pages/[collection]/index.astro +7 -0
- package/src/pages/audit/index.astro +4 -1
- package/src/pages/auth/check-email.astro +146 -0
- package/src/pages/auth/forgot-password.astro +95 -0
- package/src/pages/auth/login.astro +29 -12
- package/src/pages/auth/register.astro +38 -12
- package/src/pages/auth/reset-password.astro +125 -0
- package/src/pages/auth/verify-email.astro +87 -0
- package/src/pages/index.astro +220 -121
- package/src/pages/keys.astro +3 -1
- package/src/pages/media.astro +3 -1
- package/src/pages/preview/[collection]/[id].astro +23 -73
- package/src/pages/roles/index.astro +134 -101
- package/src/pages/settings/[slug].astro +43 -3
- package/src/pages/users/[id].astro +5 -1
- package/src/pages/users/index.astro +4 -0
- package/src/pages/users/new.astro +4 -0
- package/src/pages/webhooks.astro +3 -1
- package/src/routes.ts +16 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import { apiPost } from "../lib/api";
|
|
3
2
|
import { useResourceManager } from "../lib/useResourceManager";
|
|
4
|
-
import { useUIStore, toast } from "../lib/stores";
|
|
5
3
|
import {
|
|
6
4
|
Key, Plus, Trash2, Clock, CheckCircle2,
|
|
7
|
-
Shield,
|
|
8
|
-
Code,
|
|
5
|
+
Shield, Info, Terminal, Code, AlertTriangle,
|
|
9
6
|
} from "./ui/icons";
|
|
10
7
|
import { PageHeader } from "./ui/PageHeader";
|
|
11
8
|
import { Badge } from "./ui/Badge";
|
|
@@ -23,11 +20,9 @@ interface ApiKeyItem {
|
|
|
23
20
|
expiresAt?: string;
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
23
|
export function ApiKeysManager() {
|
|
29
|
-
|
|
30
|
-
const { items: keys, loading, create,
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
const { items: keys, loading, create, remove, isCreateModalOpen, setIsCreateModalOpen } =
|
|
31
26
|
useResourceManager<ApiKeyItem>(
|
|
32
27
|
React.useMemo(() => ({
|
|
33
28
|
endpoint: "/api/keys",
|
|
@@ -44,7 +39,6 @@ export function ApiKeysManager() {
|
|
|
44
39
|
const [newKeyPermissions, setNewKeyPermissions] = useState<string[]>(["*"]);
|
|
45
40
|
const [newKeyExpires, setNewKeyExpires] = useState("");
|
|
46
41
|
const [createError, setCreateError] = useState("");
|
|
47
|
-
const { confirm: kyroConfirm } = useUIStore();
|
|
48
42
|
|
|
49
43
|
const handleCreateKey = async () => {
|
|
50
44
|
if (!newKeyName.trim()) { setCreateError("Name is required"); return; }
|
|
@@ -61,18 +55,18 @@ export function ApiKeysManager() {
|
|
|
61
55
|
};
|
|
62
56
|
|
|
63
57
|
return (
|
|
64
|
-
<div className="w-full space-y-
|
|
65
|
-
|
|
58
|
+
<div className="w-full space-y-6 pb-24">
|
|
59
|
+
{/* Header */}
|
|
66
60
|
<PageHeader
|
|
67
61
|
title={t("tooltips.apiKeys", { defaultValue: "API Keys" })}
|
|
68
|
-
description="Programmatic tokens for secure
|
|
62
|
+
description="Programmatic tokens for secure system integration and REST API access."
|
|
69
63
|
icon={Key}
|
|
70
64
|
actions={
|
|
71
65
|
<div className="flex items-center gap-3">
|
|
72
66
|
<button
|
|
73
67
|
type="button"
|
|
74
68
|
onClick={() => setShowHelpModal(true)}
|
|
75
|
-
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-
|
|
69
|
+
className="px-4 py-2.5 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)] hover:text-[var(--kyro-text-primary)] transition-colors flex items-center gap-2"
|
|
76
70
|
>
|
|
77
71
|
<Info className="w-4 h-4" />
|
|
78
72
|
<span>Integration Guide</span>
|
|
@@ -80,7 +74,7 @@ export function ApiKeysManager() {
|
|
|
80
74
|
<button
|
|
81
75
|
type="button"
|
|
82
76
|
onClick={() => { setNewKeyName(""); setCreateError(""); setIsCreateModalOpen(true); }}
|
|
83
|
-
className="px-
|
|
77
|
+
className="px-5 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-bold text-xs hover:opacity-95 transition-opacity flex items-center gap-2"
|
|
84
78
|
>
|
|
85
79
|
<Plus className="w-4 h-4" />
|
|
86
80
|
<span>Create Access Key</span>
|
|
@@ -89,275 +83,242 @@ export function ApiKeysManager() {
|
|
|
89
83
|
}
|
|
90
84
|
/>
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</div>
|
|
101
|
-
<h3 className="text-xs font-bold uppercase tracking-widest opacity-60">Shell / CURL</h3>
|
|
102
|
-
</div>
|
|
103
|
-
<div className="flex gap-1.5">
|
|
104
|
-
<div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
|
|
105
|
-
<div className="w-2.5 h-2.5 rounded-full bg-amber-500/20" />
|
|
106
|
-
<div className="w-2.5 h-2.5 rounded-full bg-green-500/20" />
|
|
86
|
+
{/* Code Snippets */}
|
|
87
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
88
|
+
{/* cURL Example */}
|
|
89
|
+
<div className="surface-tile p-5 rounded-2xl border border-[var(--kyro-border)]">
|
|
90
|
+
<div className="flex items-center justify-between mb-4">
|
|
91
|
+
<div className="flex items-center gap-2.5">
|
|
92
|
+
<div className="p-1.5 bg-blue-500/10 rounded-lg">
|
|
93
|
+
<Terminal className="w-4 h-4 text-blue-500" />
|
|
107
94
|
</div>
|
|
95
|
+
<span className="text-xs font-bold text-[var(--kyro-text-primary)] tracking-wide">Shell / cURL</span>
|
|
108
96
|
</div>
|
|
97
|
+
<span className="text-[10px] font-mono text-[var(--kyro-text-secondary)] opacity-60">HTTP REST</span>
|
|
98
|
+
</div>
|
|
109
99
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<span><span className="text-[var(--kyro-primary)]">curl</span> -X GET \</span>
|
|
115
|
-
</div>
|
|
116
|
-
<div className="flex gap-4">
|
|
117
|
-
<span className="opacity-20 select-none w-4">2</span>
|
|
118
|
-
<span> https://api.yoursite.com/v1/posts \</span>
|
|
119
|
-
</div>
|
|
120
|
-
<div className="flex gap-4">
|
|
121
|
-
<span className="opacity-20 select-none w-4">3</span>
|
|
122
|
-
<span> -H <span className="text-green-500">"Authorization: ApiKey kyro_xxx"</span></span>
|
|
123
|
-
</div>
|
|
124
|
-
</pre>
|
|
100
|
+
<pre className="p-4 bg-[var(--kyro-bg)] rounded-lg border border-[var(--kyro-border)] font-mono text-xs leading-relaxed overflow-x-auto text-[var(--kyro-text-primary)]">
|
|
101
|
+
<div className="flex gap-3">
|
|
102
|
+
<span className="opacity-30 select-none">1</span>
|
|
103
|
+
<span><span className="text-blue-500 font-bold">curl</span> -X GET \</span>
|
|
125
104
|
</div>
|
|
126
|
-
|
|
105
|
+
<div className="flex gap-3">
|
|
106
|
+
<span className="opacity-30 select-none">2</span>
|
|
107
|
+
<span> https://api.yoursite.com/v1/posts \</span>
|
|
108
|
+
</div>
|
|
109
|
+
<div className="flex gap-3">
|
|
110
|
+
<span className="opacity-30 select-none">3</span>
|
|
111
|
+
<span> -H <span className="text-emerald-500 font-medium">"Authorization: ApiKey kyro_xxx"</span></span>
|
|
112
|
+
</div>
|
|
113
|
+
</pre>
|
|
127
114
|
</div>
|
|
128
115
|
|
|
129
|
-
{/*
|
|
130
|
-
<div className="
|
|
131
|
-
<div className="
|
|
132
|
-
<div className="flex items-center
|
|
133
|
-
<div className="
|
|
134
|
-
<
|
|
135
|
-
<Code className="w-4 h-4 text-indigo-500" />
|
|
136
|
-
</div>
|
|
137
|
-
<h3 className="text-xs font-bold uppercase tracking-widest opacity-60">JavaScript SDK</h3>
|
|
138
|
-
</div>
|
|
139
|
-
<div className="flex gap-1.5">
|
|
140
|
-
<div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
|
|
141
|
-
<div className="w-2.5 h-2.5 rounded-full bg-amber-500/20" />
|
|
142
|
-
<div className="w-2.5 h-2.5 rounded-full bg-green-500/20" />
|
|
116
|
+
{/* JS SDK Example */}
|
|
117
|
+
<div className="surface-tile p-5 rounded-2xl border border-[var(--kyro-border)]">
|
|
118
|
+
<div className="flex items-center justify-between mb-4">
|
|
119
|
+
<div className="flex items-center gap-2.5">
|
|
120
|
+
<div className="p-1.5 bg-indigo-500/10 rounded-lg">
|
|
121
|
+
<Code className="w-4 h-4 text-indigo-500" />
|
|
143
122
|
</div>
|
|
123
|
+
<span className="text-xs font-bold text-[var(--kyro-text-primary)] tracking-wide">JavaScript / Node</span>
|
|
144
124
|
</div>
|
|
125
|
+
<span className="text-[10px] font-mono text-[var(--kyro-text-secondary)] opacity-60">Fetch API</span>
|
|
126
|
+
</div>
|
|
145
127
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<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>
|
|
151
|
-
</div>
|
|
152
|
-
<div className="flex gap-4">
|
|
153
|
-
<span className="opacity-20 select-none w-4">2</span>
|
|
154
|
-
<span> headers: {"{"} </span>
|
|
155
|
-
</div>
|
|
156
|
-
<div className="flex gap-4">
|
|
157
|
-
<span className="opacity-20 select-none w-4">3</span>
|
|
158
|
-
<span> <span className="text-green-500">'Authorization'</span>: <span className="text-green-500">'ApiKey xxx'</span></span>
|
|
159
|
-
</div>
|
|
160
|
-
<div className="flex gap-4">
|
|
161
|
-
<span className="opacity-20 select-none w-4">4</span>
|
|
162
|
-
<span> {"}"}</span>
|
|
163
|
-
</div>
|
|
164
|
-
<div className="flex gap-4">
|
|
165
|
-
<span className="opacity-20 select-none w-4">5</span>
|
|
166
|
-
<span>{"}"});</span>
|
|
167
|
-
</div>
|
|
168
|
-
</pre>
|
|
128
|
+
<pre className="p-4 bg-[var(--kyro-bg)] rounded-lg border border-[var(--kyro-border)] font-mono text-xs leading-relaxed overflow-x-auto text-[var(--kyro-text-primary)]">
|
|
129
|
+
<div className="flex gap-3">
|
|
130
|
+
<span className="opacity-30 select-none">1</span>
|
|
131
|
+
<span><span className="text-indigo-400 font-medium">const</span> res = <span className="text-indigo-400 font-medium">await</span> <span className="text-blue-400">fetch</span>(url, {"{"}</span>
|
|
169
132
|
</div>
|
|
170
|
-
|
|
133
|
+
<div className="flex gap-3">
|
|
134
|
+
<span className="opacity-30 select-none">2</span>
|
|
135
|
+
<span> headers: {"{"} <span className="text-emerald-500 font-medium">'Authorization'</span>: <span className="text-emerald-500 font-medium">'ApiKey xxx'</span> {"}"}</span>
|
|
136
|
+
</div>
|
|
137
|
+
<div className="flex gap-3">
|
|
138
|
+
<span className="opacity-30 select-none">3</span>
|
|
139
|
+
<span>{"}"});</span>
|
|
140
|
+
</div>
|
|
141
|
+
</pre>
|
|
171
142
|
</div>
|
|
172
143
|
</div>
|
|
173
144
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<div className="flex items-start gap-4 relative z-10">
|
|
182
|
-
<div className="p-3 bg-amber-500/10 rounded-2xl">
|
|
183
|
-
<Shield className="w-6 h-6 text-amber-500" />
|
|
145
|
+
{/* Main Container */}
|
|
146
|
+
<div className="space-y-6">
|
|
147
|
+
{/* Security Notice */}
|
|
148
|
+
<div className="surface-tile p-5 rounded-2xl border border-[var(--kyro-border)]">
|
|
149
|
+
<div className="flex items-start gap-4">
|
|
150
|
+
<div className="p-2.5 bg-amber-500/10 rounded-xl shrink-0">
|
|
151
|
+
<Shield className="w-5 h-5 text-amber-500" />
|
|
184
152
|
</div>
|
|
185
|
-
<div>
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
"
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
153
|
+
<div className="space-y-3">
|
|
154
|
+
<div>
|
|
155
|
+
<h4 className="text-sm font-bold text-[var(--kyro-text-primary)]">Security Best Practices</h4>
|
|
156
|
+
<p className="text-xs text-[var(--kyro-text-secondary)] mt-0.5">
|
|
157
|
+
API keys grant direct access to your CMS data. Treat them securely as secrets.
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
<div className="grid sm:grid-cols-2 gap-2 text-xs text-[var(--kyro-text-secondary)] font-medium">
|
|
161
|
+
<div className="flex items-center gap-2">
|
|
162
|
+
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
|
|
163
|
+
<span>Never commit keys to version control repositories</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="flex items-center gap-2">
|
|
166
|
+
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
|
|
167
|
+
<span>Rotate key credentials every 90 days</span>
|
|
168
|
+
</div>
|
|
169
|
+
<div className="flex items-center gap-2">
|
|
170
|
+
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
|
|
171
|
+
<span>Use environment variables for production keys</span>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="flex items-center gap-2">
|
|
174
|
+
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
|
|
175
|
+
<span>Revoke unused or compromised keys immediately</span>
|
|
176
|
+
</div>
|
|
202
177
|
</div>
|
|
203
178
|
</div>
|
|
204
179
|
</div>
|
|
205
180
|
</div>
|
|
206
181
|
|
|
207
|
-
{/*
|
|
182
|
+
{/* Newly Created Key Alert */}
|
|
208
183
|
{newKey && (
|
|
209
|
-
<div className="
|
|
210
|
-
<div className="flex items-start gap-
|
|
211
|
-
<div className="p-
|
|
212
|
-
<CheckCircle2 className="w-
|
|
184
|
+
<div className="p-6 rounded-2xl border border-emerald-500/30 bg-emerald-500/10 text-[var(--kyro-text-primary)]">
|
|
185
|
+
<div className="flex items-start gap-4">
|
|
186
|
+
<div className="p-2.5 bg-emerald-500/20 rounded-xl shrink-0">
|
|
187
|
+
<CheckCircle2 className="w-6 h-6 text-emerald-500" />
|
|
213
188
|
</div>
|
|
214
|
-
<div className="flex-1">
|
|
215
|
-
<div className="flex items-center justify-between
|
|
216
|
-
<h3 className="text-
|
|
189
|
+
<div className="flex-1 space-y-3">
|
|
190
|
+
<div className="flex items-center justify-between">
|
|
191
|
+
<h3 className="text-base font-bold text-emerald-500">API Key Generated Successfully</h3>
|
|
217
192
|
<Badge variant="success" className="text-[10px] font-bold">New</Badge>
|
|
218
193
|
</div>
|
|
219
|
-
<p className="text-
|
|
220
|
-
|
|
194
|
+
<p className="text-xs text-[var(--kyro-text-secondary)]">
|
|
195
|
+
Copy this key now. It will <strong className="text-emerald-500">never be displayed again</strong>.
|
|
221
196
|
</p>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
<span className="opacity-80 select-all">{newKey.key}</span>
|
|
197
|
+
<div className="p-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl font-mono text-xs break-all select-all font-semibold">
|
|
198
|
+
{newKey.key}
|
|
225
199
|
</div>
|
|
226
|
-
|
|
227
200
|
<button
|
|
228
201
|
type="button"
|
|
229
202
|
onClick={() => setNewKey(null)}
|
|
230
|
-
className="
|
|
203
|
+
className="text-xs font-bold text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-colors"
|
|
231
204
|
>
|
|
232
|
-
Dismiss
|
|
205
|
+
Dismiss
|
|
233
206
|
</button>
|
|
234
207
|
</div>
|
|
235
208
|
</div>
|
|
236
209
|
</div>
|
|
237
210
|
)}
|
|
238
211
|
|
|
239
|
-
|
|
240
|
-
|
|
212
|
+
{/* Active Keys Section */}
|
|
213
|
+
<div className="surface-tile p-6 rounded-2xl border border-[var(--kyro-border)] space-y-4">
|
|
214
|
+
<div className="flex items-center justify-between">
|
|
241
215
|
<div className="flex items-center gap-2">
|
|
242
|
-
<
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
{keys.length} KEY{keys.length !== 1 && "S"}
|
|
216
|
+
<h2 className="text-sm font-bold text-[var(--kyro-text-primary)] tracking-wide">Active Credentials</h2>
|
|
217
|
+
<span className="px-2 py-0.5 text-[10px] font-bold rounded-md bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)]">
|
|
218
|
+
{keys.length}
|
|
219
|
+
</span>
|
|
247
220
|
</div>
|
|
248
221
|
</div>
|
|
249
222
|
|
|
250
223
|
{loading ? (
|
|
251
|
-
<div className="
|
|
252
|
-
|
|
224
|
+
<div className="p-8 text-center text-xs font-medium text-[var(--kyro-text-secondary)]">
|
|
225
|
+
Loading API keys...
|
|
253
226
|
</div>
|
|
254
227
|
) : keys.length === 0 ? (
|
|
255
|
-
<div className="p-
|
|
256
|
-
<
|
|
257
|
-
|
|
228
|
+
<div className="p-10 text-center rounded-xl border border-dashed border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/30 space-y-3">
|
|
229
|
+
<Key className="w-8 h-8 text-[var(--kyro-text-secondary)] mx-auto opacity-50" />
|
|
230
|
+
<div>
|
|
231
|
+
<h3 className="text-sm font-bold text-[var(--kyro-text-primary)]">No API Keys Generated</h3>
|
|
232
|
+
<p className="text-xs text-[var(--kyro-text-secondary)] mt-1 max-w-sm mx-auto">
|
|
233
|
+
Create an API key to allow external services to connect to your Kyro CMS instance safely.
|
|
234
|
+
</p>
|
|
258
235
|
</div>
|
|
259
|
-
<h3 className="text-2xl font-bold mb-3">Initialize Your Access</h3>
|
|
260
|
-
<p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-8 max-w-sm mx-auto">
|
|
261
|
-
No API credentials found. Generate a key to begin interacting with the Kyro CMS programmatic interface.
|
|
262
|
-
</p>
|
|
263
236
|
<button
|
|
264
237
|
type="button"
|
|
265
238
|
onClick={() => setIsCreateModalOpen(true)}
|
|
266
|
-
className="
|
|
239
|
+
className="mt-2 inline-flex items-center gap-2 px-4 py-2 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] text-xs font-bold rounded-xl hover:opacity-95 transition-opacity"
|
|
267
240
|
>
|
|
268
|
-
<Plus className="w-
|
|
269
|
-
|
|
241
|
+
<Plus className="w-4 h-4" />
|
|
242
|
+
<span>Create API Key</span>
|
|
270
243
|
</button>
|
|
271
244
|
</div>
|
|
272
245
|
) : (
|
|
273
|
-
<div className="
|
|
246
|
+
<div className="divide-y divide-[var(--kyro-border)]">
|
|
274
247
|
{keys.map((key) => (
|
|
275
|
-
<div
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
<div className="
|
|
281
|
-
<div className="
|
|
282
|
-
<
|
|
248
|
+
<div key={key.id} className="py-3.5 flex items-center justify-between gap-4 first:pt-0 last:pb-0">
|
|
249
|
+
<div className="flex items-center gap-3 min-w-0">
|
|
250
|
+
<div className="p-2 bg-[var(--kyro-surface-accent)] rounded-lg shrink-0">
|
|
251
|
+
<Key className="w-4 h-4 text-[var(--kyro-text-secondary)]" />
|
|
252
|
+
</div>
|
|
253
|
+
<div className="min-w-0">
|
|
254
|
+
<div className="flex items-center gap-2">
|
|
255
|
+
<span className="text-xs font-bold text-[var(--kyro-text-primary)] truncate">{key.name}</span>
|
|
256
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 shrink-0" />
|
|
283
257
|
</div>
|
|
284
|
-
<div className="
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
) : (
|
|
296
|
-
key.permissions?.slice(0, 2).join(", ")
|
|
297
|
-
)}
|
|
298
|
-
{key.permissions && key.permissions.length > 2 && <span className="opacity-40">+{key.permissions.length - 2}</span>}
|
|
299
|
-
</span>
|
|
300
|
-
<span className="opacity-30">·</span>
|
|
301
|
-
<span className="flex items-center gap-1">
|
|
302
|
-
<Clock className="w-3 h-3" />
|
|
303
|
-
{key.lastUsed ? new Date(key.lastUsed).toLocaleDateString() : "Never"}
|
|
304
|
-
</span>
|
|
305
|
-
</div>
|
|
258
|
+
<div className="flex items-center gap-3 text-[11px] text-[var(--kyro-text-secondary)] mt-0.5">
|
|
259
|
+
<span className="font-mono opacity-70">{key.keyPrefix}••••••••••••••••</span>
|
|
260
|
+
<span>·</span>
|
|
261
|
+
<span>
|
|
262
|
+
{key.permissions?.includes("*") ? "Full Access" : key.permissions?.slice(0, 2).join(", ")}
|
|
263
|
+
</span>
|
|
264
|
+
<span>·</span>
|
|
265
|
+
<span className="flex items-center gap-1">
|
|
266
|
+
<Clock className="w-3 h-3" />
|
|
267
|
+
{key.lastUsed ? new Date(key.lastUsed).toLocaleDateString() : "Never used"}
|
|
268
|
+
</span>
|
|
306
269
|
</div>
|
|
307
270
|
</div>
|
|
308
|
-
|
|
309
|
-
<button
|
|
310
|
-
type="button"
|
|
311
|
-
onClick={() => remove(key.id, "API Key")}
|
|
312
|
-
className="p-2 bg-red-500/5 border border-red-500/10 rounded-lg hover:bg-red-500/10 hover:border-red-500/30 transition-all opacity-0 group-hover:opacity-100 shrink-0"
|
|
313
|
-
title={t("tooltips.revokeAccess", { defaultValue: "Revoke Access" })}
|
|
314
|
-
>
|
|
315
|
-
<Trash2 className="w-3.5 h-3.5 text-red-500/50 group-hover/delete:text-red-500" />
|
|
316
|
-
</button>
|
|
317
271
|
</div>
|
|
272
|
+
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
onClick={() => remove(key.id, "API Key")}
|
|
276
|
+
className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-colors shrink-0"
|
|
277
|
+
title={t("tooltips.revokeAccess", { defaultValue: "Revoke Access" })}
|
|
278
|
+
>
|
|
279
|
+
<Trash2 className="w-4 h-4" />
|
|
280
|
+
</button>
|
|
318
281
|
</div>
|
|
319
282
|
))}
|
|
320
283
|
</div>
|
|
321
284
|
)}
|
|
322
|
-
</
|
|
285
|
+
</div>
|
|
323
286
|
</div>
|
|
324
287
|
|
|
325
|
-
{/* Create Modal */}
|
|
288
|
+
{/* Create Key Modal */}
|
|
326
289
|
<Modal size="lg" open={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} title={t("tooltips.createNewApiKey", { defaultValue: "Create New API Key" })}>
|
|
327
290
|
<ModalContent>
|
|
328
|
-
<div className="space-y-
|
|
291
|
+
<div className="space-y-4">
|
|
329
292
|
<div>
|
|
330
|
-
<label className="block text-xs font-bold mb-1
|
|
293
|
+
<label className="block text-xs font-bold mb-1 text-[var(--kyro-text-secondary)]">Key Name</label>
|
|
331
294
|
<input
|
|
332
295
|
type="text"
|
|
333
296
|
value={newKeyName}
|
|
334
297
|
onChange={(e) => { setNewKeyName(e.target.value); setCreateError(""); }}
|
|
335
|
-
placeholder="e.g., Production
|
|
336
|
-
className="w-full px-
|
|
298
|
+
placeholder="e.g., Production Frontend, Mobile App, Webhook Client"
|
|
299
|
+
className="w-full px-3.5 py-2.5 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl text-xs font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-sidebar-active)]"
|
|
337
300
|
onKeyDown={(e) => e.key === "Enter" && handleCreateKey()}
|
|
338
301
|
/>
|
|
339
|
-
{createError && <p className="mt-1
|
|
302
|
+
{createError && <p className="mt-1 text-xs text-red-500">{createError}</p>}
|
|
340
303
|
</div>
|
|
341
304
|
|
|
342
|
-
|
|
343
|
-
|
|
344
305
|
<div>
|
|
345
|
-
<label className="block text-xs font-bold mb-1
|
|
306
|
+
<label className="block text-xs font-bold mb-1 text-[var(--kyro-text-secondary)]">Expires (Optional)</label>
|
|
346
307
|
<input
|
|
347
308
|
type="datetime-local"
|
|
348
309
|
value={newKeyExpires}
|
|
349
310
|
onChange={(e) => setNewKeyExpires(e.target.value)}
|
|
350
|
-
className="w-full px-
|
|
311
|
+
className="w-full px-3.5 py-2.5 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl text-xs font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-sidebar-active)]"
|
|
351
312
|
/>
|
|
352
|
-
<p className="mt-1 text-[10px] text-[var(--kyro-text-
|
|
353
|
-
Leave
|
|
313
|
+
<p className="mt-1 text-[10px] text-[var(--kyro-text-secondary)]">
|
|
314
|
+
Leave blank for non-expiring credentials.
|
|
354
315
|
</p>
|
|
355
316
|
</div>
|
|
356
317
|
|
|
357
318
|
<div className="p-3 bg-amber-500/10 border border-amber-500/20 rounded-xl flex items-start gap-2.5">
|
|
358
|
-
<AlertTriangle className="w-4 h-4 text-amber-500
|
|
319
|
+
<AlertTriangle className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
|
|
359
320
|
<p className="text-[11px] text-amber-600 font-medium">
|
|
360
|
-
The key will be
|
|
321
|
+
The generated key string will only be displayed once upon creation.
|
|
361
322
|
</p>
|
|
362
323
|
</div>
|
|
363
324
|
</div>
|
|
@@ -366,50 +327,39 @@ export function ApiKeysManager() {
|
|
|
366
327
|
<button
|
|
367
328
|
type="button"
|
|
368
329
|
onClick={() => setIsCreateModalOpen(false)}
|
|
369
|
-
className="px-4 py-2 rounded-
|
|
330
|
+
className="px-4 py-2 rounded-xl text-xs font-bold border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
|
|
370
331
|
>
|
|
371
332
|
Cancel
|
|
372
333
|
</button>
|
|
373
334
|
<button
|
|
374
335
|
type="button"
|
|
375
336
|
onClick={handleCreateKey}
|
|
376
|
-
className="px-4 py-2 rounded-
|
|
337
|
+
className="px-4 py-2 rounded-xl text-xs font-bold bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
|
|
377
338
|
>
|
|
378
339
|
Generate Key
|
|
379
340
|
</button>
|
|
380
341
|
</ModalActions>
|
|
381
342
|
</Modal>
|
|
382
343
|
|
|
383
|
-
{/*
|
|
344
|
+
{/* Integration Guide Modal */}
|
|
384
345
|
<Modal size="lg" open={showHelpModal} onClose={() => setShowHelpModal(false)} title={t("tooltips.howApiKeysWork", { defaultValue: "How API Keys Work" })}>
|
|
385
346
|
<ModalContent>
|
|
386
|
-
<div className="space-y-
|
|
387
|
-
<div>
|
|
388
|
-
<h4 className="font-bold mb-2">What is an API key?</h4>
|
|
389
|
-
<p className="text-sm text-[var(--kyro-text-secondary)]">
|
|
390
|
-
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.
|
|
391
|
-
</p>
|
|
392
|
-
</div>
|
|
347
|
+
<div className="space-y-4">
|
|
393
348
|
<div>
|
|
394
|
-
<h4 className="font-bold mb-
|
|
395
|
-
<p className="text-
|
|
396
|
-
|
|
349
|
+
<h4 className="text-xs font-bold text-[var(--kyro-text-primary)] mb-1">Authorization Header Format</h4>
|
|
350
|
+
<p className="text-xs text-[var(--kyro-text-secondary)] mb-2">
|
|
351
|
+
Pass your API key in the standard Authorization header with the <code className="font-mono bg-[var(--kyro-surface-accent)] px-1 py-0.5 rounded text-[11px]">ApiKey</code> prefix:
|
|
397
352
|
</p>
|
|
398
|
-
<div className="bg-[var(--kyro-bg)] rounded-
|
|
399
|
-
|
|
400
|
-
<span className="text-[var(--kyro-text-secondary)]">Authorization:</span>{" "}
|
|
401
|
-
<span className="text-[var(--kyro-primary)]">ApiKey </span>
|
|
402
|
-
<span className="text-green-500">kyro_xxxxxxxxxxxx</span>
|
|
403
|
-
</div>
|
|
353
|
+
<div className="bg-[var(--kyro-bg)] rounded-xl p-3 border border-[var(--kyro-border)] font-mono text-xs text-[var(--kyro-text-primary)]">
|
|
354
|
+
Authorization: ApiKey kyro_xxxxxxxxxxxxxxxx
|
|
404
355
|
</div>
|
|
405
356
|
</div>
|
|
406
357
|
<div>
|
|
407
|
-
<h4 className="font-bold mb-
|
|
408
|
-
<ul className="text-
|
|
409
|
-
<li>
|
|
410
|
-
<li>
|
|
411
|
-
<li>
|
|
412
|
-
<li>Revoke keys that are no longer in use</li>
|
|
358
|
+
<h4 className="text-xs font-bold text-[var(--kyro-text-primary)] mb-1">Key Security Principles</h4>
|
|
359
|
+
<ul className="text-xs text-[var(--kyro-text-secondary)] space-y-1.5 list-disc list-inside">
|
|
360
|
+
<li>Keep keys private and store them in server environment variables</li>
|
|
361
|
+
<li>Never expose keys in client-side front-end code bases</li>
|
|
362
|
+
<li>Use separate keys for distinct microservices or environments</li>
|
|
413
363
|
</ul>
|
|
414
364
|
</div>
|
|
415
365
|
</div>
|
|
@@ -418,9 +368,9 @@ export function ApiKeysManager() {
|
|
|
418
368
|
<button
|
|
419
369
|
type="button"
|
|
420
370
|
onClick={() => setShowHelpModal(false)}
|
|
421
|
-
className="px-4 py-2 rounded-
|
|
371
|
+
className="px-4 py-2 rounded-xl text-xs font-bold bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
|
|
422
372
|
>
|
|
423
|
-
Got
|
|
373
|
+
Got It
|
|
424
374
|
</button>
|
|
425
375
|
</ModalActions>
|
|
426
376
|
</Modal>
|