@kyro-cms/admin 0.10.10 → 0.10.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyro-cms/admin",
3
- "version": "0.10.10",
3
+ "version": "0.10.11",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -165,7 +165,7 @@ export function ActionBar({
165
165
  <button key={v} type="button" onClick={() => { setView(v); setMoreOpen(false); }}
166
166
  className={`px-2.5 py-1 text-[11px] font-semibold rounded-md transition-all flex-1 ${
167
167
  view === v
168
- ? "bg-[var(--kyro-primary)] text-white"
168
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]"
169
169
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)]"
170
170
  }`}
171
171
  >
@@ -698,10 +698,10 @@ export function GraphQLPlayground({
698
698
  </div>
699
699
  )}
700
700
  <div className="ml-auto flex items-center gap-1 flex-wrap justify-end">
701
- <button onClick={() => { setShowDocs(!showDocs); setRightTab("docs"); }} className={`p-1.5 rounded-lg transition-all ${rightTab === "docs" && showDocs ? "bg-[var(--kyro-primary)] text-white" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title="Schema docs">
701
+ <button onClick={() => { setShowDocs(!showDocs); setRightTab("docs"); }} className={`p-1.5 rounded-lg transition-all ${rightTab === "docs" && showDocs ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title="Schema docs">
702
702
  <Book className="w-3.5 h-3.5" />
703
703
  </button>
704
- <button onClick={() => setRightTab("history")} className={`p-1.5 rounded-lg transition-all ${rightTab === "history" ? "bg-[var(--kyro-primary)] text-white" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title="History">
704
+ <button onClick={() => setRightTab("history")} className={`p-1.5 rounded-lg transition-all ${rightTab === "history" ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title="History">
705
705
  <Clock className="w-3.5 h-3.5" />
706
706
  </button>
707
707
  <button onClick={handlePrettify} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]" title="Prettify (Cmd+Shift+P)">
@@ -720,7 +720,7 @@ export function GraphQLPlayground({
720
720
  <button
721
721
  onClick={handleRun}
722
722
  disabled={isLoading}
723
- className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-[var(--kyro-primary)] text-white text-xs font-semibold hover:opacity-90 disabled:opacity-50 transition-all shadow-sm"
723
+ className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] text-xs font-semibold hover:opacity-90 disabled:opacity-50 transition-all shadow-sm"
724
724
  >
725
725
  <Play className="w-3 h-3" />
726
726
  {isLoading ? "..." : "Run"}
@@ -734,13 +734,13 @@ export function GraphQLPlayground({
734
734
  <div className="flex md:hidden gap-1 px-3 py-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] shrink-0">
735
735
  <button
736
736
  onClick={() => setMobilePanel("editor")}
737
- className={`flex-1 px-3 py-1.5 text-[10px] font-semibold rounded-md transition-all ${mobilePanel === "editor" ? "bg-[var(--kyro-primary)] text-white" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`}
737
+ className={`flex-1 px-3 py-1.5 text-[10px] font-semibold rounded-md transition-all ${mobilePanel === "editor" ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`}
738
738
  >
739
739
  Editor
740
740
  </button>
741
741
  <button
742
742
  onClick={() => setMobilePanel("response")}
743
- className={`flex-1 px-3 py-1.5 text-[10px] font-semibold rounded-md transition-all ${mobilePanel === "response" ? "bg-[var(--kyro-primary)] text-white" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`}
743
+ className={`flex-1 px-3 py-1.5 text-[10px] font-semibold rounded-md transition-all ${mobilePanel === "response" ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`}
744
744
  >
745
745
  Output
746
746
  </button>
@@ -755,7 +755,7 @@ export function GraphQLPlayground({
755
755
  key={p.key}
756
756
  onClick={() => setActiveEditorTab(p.key)}
757
757
  className={`px-2.5 py-1 text-[10px] font-semibold rounded-md transition-all ${activeEditorTab === p.key
758
- ? "bg-[var(--kyro-primary)] text-white"
758
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]"
759
759
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
760
760
  }`}
761
761
  >
@@ -810,7 +810,7 @@ export function GraphQLPlayground({
810
810
  key={p.key}
811
811
  onClick={() => { setRightTab(p.key); if (p.key === "docs") setShowDocs(true); }}
812
812
  className={`px-2.5 py-1 text-[10px] font-semibold rounded-md transition-all ${rightTab === p.key
813
- ? "bg-[var(--kyro-primary)] text-white"
813
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]"
814
814
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
815
815
  }`}
816
816
  >
@@ -682,7 +682,7 @@ export function MediaGallery({
682
682
  </div>
683
683
 
684
684
  {selectedIds.has(item.id) && (
685
- <div className="absolute top-3 left-3 w-6 h-6 rounded-lg bg-[var(--kyro-primary)] text-white flex items-center justify-center shadow-lg border-2 border-white/20 animate-in zoom-in duration-300">
685
+ <div className="absolute top-3 left-3 w-6 h-6 rounded-lg bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center shadow-lg border-2 border-white/20 animate-in zoom-in duration-300">
686
686
  <Check className="w-4 h-4" />
687
687
  </div>
688
688
  )}
@@ -378,7 +378,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
378
378
  <button
379
379
  onClick={handleSend}
380
380
  disabled={loading || !currentRequest.url}
381
- className="flex items-center gap-1 px-3 py-1.5 rounded-md bg-[var(--kyro-primary)] text-white text-xs font-semibold hover:opacity-90 disabled:opacity-50 transition-all shadow-sm"
381
+ className="flex items-center gap-1 px-3 py-1.5 rounded-md bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] text-xs font-semibold hover:opacity-90 disabled:opacity-50 transition-all shadow-sm"
382
382
  >
383
383
  <Play className="w-3 h-3" />
384
384
  {loading ? "..." : "Send"}
@@ -479,7 +479,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
479
479
  key={req.id}
480
480
  className={`flex items-center gap-2 px-2 py-1 rounded cursor-pointer transition-colors ${
481
481
  selectedRequest?.id === req.id
482
- ? "bg-[var(--kyro-primary)] text-white"
482
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]"
483
483
  : "hover:bg-[var(--kyro-surface-accent)]"
484
484
  }`}
485
485
  onClick={() => loadRequest(req)}
@@ -573,7 +573,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
573
573
  onClick={() => setMobilePanel("editor")}
574
574
  className={`flex-1 px-3 py-1 text-[10px] font-semibold rounded-md transition-all ${
575
575
  mobilePanel === "editor"
576
- ? "bg-[var(--kyro-primary)] text-white"
576
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]"
577
577
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
578
578
  }`}
579
579
  >
@@ -583,7 +583,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
583
583
  onClick={() => setMobilePanel("response")}
584
584
  className={`flex-1 px-3 py-1 text-[10px] font-semibold rounded-md transition-all ${
585
585
  mobilePanel === "response"
586
- ? "bg-[var(--kyro-primary)] text-white"
586
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]"
587
587
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
588
588
  }`}
589
589
  >
@@ -604,7 +604,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
604
604
  onClick={() => setActiveEditorTab(p.key)}
605
605
  className={`px-2.5 py-1 text-[10px] font-semibold rounded-md transition-all ${
606
606
  activeEditorTab === p.key
607
- ? "bg-[var(--kyro-primary)] text-white"
607
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]"
608
608
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
609
609
  }`}
610
610
  >
@@ -11,6 +11,7 @@ interface UserMenuProps {
11
11
  export function UserMenu({ adminPath }: UserMenuProps) {
12
12
  const currentUser = useAuthStore((s) => s.user);
13
13
  const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
14
+ const [apiAccess, setApiAccess] = useState<{ graphqlEnabled?: boolean } | null>(null);
14
15
 
15
16
  useEffect(() => {
16
17
  const avatar = currentUser?.avatar;
@@ -25,6 +26,15 @@ export function UserMenu({ adminPath }: UserMenuProps) {
25
26
  }
26
27
  }, [currentUser?.avatar]);
27
28
 
29
+ useEffect(() => {
30
+ apiGet<any>("/api/globals/access-settings")
31
+ .then((res: any) => {
32
+ const data = res?.data || res;
33
+ setApiAccess(data?.apiAccess || {});
34
+ })
35
+ .catch(() => setApiAccess({}));
36
+ }, []);
37
+
28
38
  return (
29
39
  <Dropdown
30
40
  align="right"
@@ -109,12 +119,14 @@ export function UserMenu({ adminPath }: UserMenuProps) {
109
119
  API Explorer
110
120
  </DropdownItem>
111
121
 
112
- <DropdownItem
113
- icon={<Zap className="w-4 h-4" />}
114
- onClick={() => (window.location.href = `${adminPath}/graphql`)}
115
- >
116
- GraphQL Playground
117
- </DropdownItem>
122
+ {(apiAccess === null || apiAccess?.graphqlEnabled) && (
123
+ <DropdownItem
124
+ icon={<Zap className="w-4 h-4" />}
125
+ onClick={() => (window.location.href = `${adminPath}/graphql`)}
126
+ >
127
+ GraphQL Playground
128
+ </DropdownItem>
129
+ )}
118
130
 
119
131
  <DropdownSeparator />
120
132
 
@@ -47,6 +47,15 @@ export function ArrayLayout({
47
47
  const val = item[f.name!];
48
48
  if (val && typeof val === "string") return val;
49
49
  }
50
+ if (f.type === "group" && (f as any).fields) {
51
+ for (const inner of (f as any).fields) {
52
+ if (inner.type === "text" || inner.type === "textarea") {
53
+ const group = item[f.name!] as Record<string, unknown> | undefined;
54
+ const val = group?.[inner.name!];
55
+ if (val && typeof val === "string") return val;
56
+ }
57
+ }
58
+ }
50
59
  }
51
60
  return "";
52
61
  }
@@ -166,7 +166,7 @@ const MenuBar = ({
166
166
  disabled={disabled}
167
167
  title={title}
168
168
  className={`p-1 rounded flex items-center justify-center transition-all duration-150 relative
169
- ${isActive ? "bg-[var(--kyro-primary)] text-white shadow-xs scale-95" : "text-[var(--kyro-text)] hover:bg-[var(--kyro-bg-hover)]"}
169
+ ${isActive ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] shadow-xs scale-95" : "text-[var(--kyro-text)] hover:bg-[var(--kyro-bg-hover)]"}
170
170
  ${disabled ? "opacity-35 cursor-not-allowed" : "cursor-pointer active:scale-90"}`}
171
171
  >
172
172
  {children}
@@ -656,7 +656,7 @@ function RichTextEditor({
656
656
  <button
657
657
  type="button"
658
658
  onClick={() => setIsExpanded(false)}
659
- className="px-2.5 py-1 bg-[var(--kyro-primary)] text-white text-xs rounded-md shadow-sm font-medium hover:scale-102 active:scale-98 transition-transform cursor-pointer"
659
+ className="px-2.5 py-1 bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] text-xs rounded-md shadow-sm font-medium hover:scale-102 active:scale-98 transition-transform cursor-pointer"
660
660
  >
661
661
  Minimize Workspace
662
662
  </button>
@@ -298,7 +298,7 @@ if (includeSiteName) {
298
298
  id="kyro-fab"
299
299
  type="button"
300
300
  onclick="window.openCommandPalette?.()"
301
- class="fixed bottom-6 right-6 z-50 w-12 h-12 flex md:hidden items-center justify-center rounded-2xl bg-[var(--kyro-primary)] text-white shadow-lg shadow-[var(--kyro-primary)]/30 hover:shadow-xl hover:shadow-[var(--kyro-primary)]/40 hover:scale-110 active:scale-95 transition-all duration-200"
301
+ class="fixed bottom-6 right-6 z-50 w-12 h-12 flex md:hidden items-center justify-center rounded-2xl bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] shadow-lg shadow-[var(--kyro-primary)]/30 hover:shadow-xl hover:shadow-[var(--kyro-primary)]/40 hover:scale-110 active:scale-95 transition-all duration-200"
302
302
  title="Open command palette (Cmd+K)"
303
303
  >
304
304
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
@@ -2,14 +2,33 @@
2
2
  import AdminLayout from "../layouts/AdminLayout.astro";
3
3
  import { GraphQLPlayground } from "../components/GraphQLPlayground";
4
4
  import { apiPath } from "../lib/paths";
5
+ import { getGlobal } from "../lib/globals";
6
+
7
+ const accessSettings = await getGlobal("access-settings");
8
+ const apiAccess = accessSettings?.apiAccess || {};
9
+ const graphqlEnabled = apiAccess.graphqlEnabled !== false;
5
10
  ---
6
11
 
7
12
  <AdminLayout title="GraphQL Explorer">
8
13
  <div class="flex-1 overflow-hidden h-[calc(100vh-64px)] md:h-[calc(100vh-80px)]">
9
- <GraphQLPlayground
10
- client:load
11
- endpoint={`${apiPath}/graphql`}
12
- initialShowDocs={true}
13
- />
14
+ {graphqlEnabled ? (
15
+ <GraphQLPlayground
16
+ client:load
17
+ endpoint={`${apiPath}/graphql`}
18
+ initialShowDocs={true}
19
+ />
20
+ ) : (
21
+ <div class="flex items-center justify-center h-full">
22
+ <div class="text-center p-8 max-w-md">
23
+ <div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center">
24
+ <svg class="w-8 h-8 text-[var(--kyro-text-muted)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
25
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
26
+ </svg>
27
+ </div>
28
+ <h2 class="text-xl font-bold text-[var(--kyro-text-primary)] mb-2">GraphQL Disabled</h2>
29
+ <p class="text-sm text-[var(--kyro-text-secondary)]">The GraphQL API is currently disabled. Enable it in Access Control settings to use the GraphQL Explorer.</p>
30
+ </div>
31
+ </div>
32
+ )}
14
33
  </div>
15
34
  </AdminLayout>
@@ -2,6 +2,11 @@
2
2
  import AdminLayout from "../layouts/AdminLayout.astro";
3
3
  import { GraphQLPlayground } from "../components/GraphQLPlayground";
4
4
  import { apiPath } from "../lib/paths";
5
+ import { getGlobal } from "../lib/globals";
6
+
7
+ const accessSettings = await getGlobal("access-settings");
8
+ const apiAccess = accessSettings?.apiAccess || {};
9
+ const graphqlEnabled = apiAccess.graphqlEnabled !== false;
5
10
 
6
11
  const url = Astro.request.url;
7
12
  const params = new URL(url).searchParams;
@@ -10,10 +15,24 @@ const initialQuery = params.get("query") || "";
10
15
 
11
16
  <AdminLayout title="GraphQL Playground">
12
17
  <div class="flex-1 overflow-hidden h-[calc(100vh-64px)] md:h-[calc(100vh-80px)]">
13
- <GraphQLPlayground
14
- client:load
15
- endpoint={`${apiPath}/graphql`}
16
- initialQuery={initialQuery}
17
- />
18
+ {graphqlEnabled ? (
19
+ <GraphQLPlayground
20
+ client:load
21
+ endpoint={`${apiPath}/graphql`}
22
+ initialQuery={initialQuery}
23
+ />
24
+ ) : (
25
+ <div class="flex items-center justify-center h-full">
26
+ <div class="text-center p-8 max-w-md">
27
+ <div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center">
28
+ <svg class="w-8 h-8 text-[var(--kyro-text-muted)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
29
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
30
+ </svg>
31
+ </div>
32
+ <h2 class="text-xl font-bold text-[var(--kyro-text-primary)] mb-2">GraphQL Disabled</h2>
33
+ <p class="text-sm text-[var(--kyro-text-secondary)]">The GraphQL API is currently disabled. Enable it in Access Control settings to use the GraphQL playground.</p>
34
+ </div>
35
+ </div>
36
+ )}
18
37
  </div>
19
38
  </AdminLayout>
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  import AdminLayout from "../layouts/AdminLayout.astro";
3
3
  import { collections } from "../lib/config";
4
-
5
4
  const authCollections = ["users", "audit_logs"];
6
5
  import { adminPath, apiPath } from "../lib/paths";
7
6
 
@@ -238,196 +237,6 @@ const authItems = authCollections.map((slug) => ({
238
237
  </div>
239
238
  </div>
240
239
 
241
- <!-- Developer Tools Section -->
242
- <div class="surface-tile overflow-hidden">
243
- <div class="p-6 border-b border-[var(--kyro-border)]">
244
- <h2
245
- class="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]"
246
- >
247
- Developer Tools
248
- </h2>
249
- <p class="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">
250
- REST API & GraphQL testing tools
251
- </p>
252
- </div>
253
-
254
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 p-8">
255
- <!-- REST API -->
256
- <div class="bg-[var(--kyro-surface-accent)] rounded-2xl p-6">
257
- <div class="flex items-center gap-3 mb-6">
258
- <div
259
- class="w-10 h-10 rounded-xl bg-pink-500/10 flex items-center justify-center text-pink-500"
260
- >
261
- <svg
262
- class="w-5 h-5"
263
- fill="none"
264
- stroke="currentColor"
265
- viewBox="0 0 24 24"
266
- >
267
- <path
268
- stroke-linecap="round"
269
- stroke-linejoin="round"
270
- stroke-width="2"
271
- d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
272
- ></path>
273
- </svg>
274
- </div>
275
- <div>
276
- <h3 class="text-xl font-bold text-[var(--kyro-text-primary)]">
277
- REST API
278
- </h3>
279
- <p class="text-xs text-[var(--kyro-text-secondary)]">
280
- HTTP endpoints
281
- </p>
282
- </div>
283
- </div>
284
- <div class="grid grid-cols-1 gap-4">
285
- <a
286
- href={`${adminPath}/rest-playground`}
287
- class="group p-4 bg-[var(--kyro-surface)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)]"
288
- >
289
- <div class="flex items-center gap-3 mb-2">
290
- <div
291
- class="w-8 h-8 rounded-lg bg-blue-500/10 flex items-center justify-center text-blue-500 group-hover:scale-110 transition-transform"
292
- >
293
- <svg
294
- class="w-4 h-4"
295
- fill="none"
296
- stroke="currentColor"
297
- viewBox="0 0 24 24"
298
- >
299
- <path
300
- stroke-linecap="round"
301
- stroke-linejoin="round"
302
- stroke-width="2"
303
- 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"
304
- ></path>
305
- </svg>
306
- </div>
307
- <h4 class="font-medium text-[var(--kyro-text-primary)]">
308
- Playground
309
- </h4>
310
- </div>
311
- <p class="text-xs text-[var(--kyro-text-secondary)]">
312
- Saved requests & history
313
- </p>
314
- </a>
315
- </div>
316
- <div class="mt-4 pt-4 border-t border-[var(--kyro-border)]">
317
- <a
318
- href={`${apiPath}/collections`}
319
- target="_blank"
320
- class="flex items-center gap-2 text-xs text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-secondary)] transition-colors"
321
- >
322
- <svg
323
- class="w-3 h-3"
324
- fill="none"
325
- stroke="currentColor"
326
- viewBox="0 0 24 24"
327
- >
328
- <path
329
- stroke-linecap="round"
330
- stroke-linejoin="round"
331
- stroke-width="2"
332
- d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
333
- ></path>
334
- </svg>
335
- View Collections JSON
336
- </a>
337
- </div>
338
- </div>
339
-
340
- <!-- GraphQL -->
341
- <div class="bg-[var(--kyro-surface-accent)] rounded-2xl p-6">
342
- <div class="flex items-center gap-3 mb-6">
343
- <div
344
- class="w-10 h-10 rounded-xl bg-pink-500/10 flex items-center justify-center text-pink-500"
345
- >
346
- <svg
347
- class="w-5 h-5"
348
- fill="none"
349
- stroke="currentColor"
350
- viewBox="0 0 24 24"
351
- >
352
- <path
353
- stroke-linecap="round"
354
- stroke-linejoin="round"
355
- stroke-width="2"
356
- d="M13 10V3L4 14h7v7l9-11h-7z"></path>
357
- </svg>
358
- </div>
359
- <div>
360
- <h3 class="text-xl font-bold text-[var(--kyro-text-primary)]">
361
- GraphQL
362
- </h3>
363
- <p class="text-xs text-[var(--kyro-text-secondary)]">
364
- Query language API
365
- </p>
366
- </div>
367
- </div>
368
- <div class="grid grid-cols-1 gap-4">
369
- <a
370
- href={`${adminPath}/graphql`}
371
- class="group p-4 bg-[var(--kyro-surface)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)]"
372
- >
373
- <div class="flex items-center gap-3 mb-2">
374
- <div
375
- class="w-8 h-8 rounded-lg bg-purple-500/10 flex items-center justify-center text-purple-500 group-hover:scale-110 transition-transform"
376
- >
377
- <svg
378
- class="w-4 h-4"
379
- fill="none"
380
- stroke="currentColor"
381
- viewBox="0 0 24 24"
382
- >
383
- <path
384
- stroke-linecap="round"
385
- stroke-linejoin="round"
386
- stroke-width="2"
387
- d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
388
- ></path>
389
- <path
390
- stroke-linecap="round"
391
- stroke-linejoin="round"
392
- stroke-width="2"
393
- d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
394
- </svg>
395
- </div>
396
- <h4 class="font-medium text-[var(--kyro-text-primary)]">
397
- Explorer
398
- </h4>
399
- </div>
400
- <p class="text-xs text-[var(--kyro-text-secondary)]">
401
- Write & test queries
402
- </p>
403
- </a>
404
- </div>
405
- <div class="mt-4 pt-4 border-t border-[var(--kyro-border)]">
406
- <a
407
- href={`${apiPath}/graphql`}
408
- target="_blank"
409
- class="flex items-center gap-2 text-xs text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-secondary)] transition-colors"
410
- >
411
- <svg
412
- class="w-3 h-3"
413
- fill="none"
414
- stroke="currentColor"
415
- viewBox="0 0 24 24"
416
- >
417
- <path
418
- stroke-linecap="round"
419
- stroke-linejoin="round"
420
- stroke-width="2"
421
- d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
422
- ></path>
423
- </svg>
424
- View GraphQL Endpoint
425
- </a>
426
- </div>
427
- </div>
428
- </div>
429
- </div>
430
-
431
240
  <script is:inline define:vars={{ apiPath, adminPath }}>
432
241
  (function () {
433
242
  var searchInput = document.getElementById("header-search-input");