elysia-rack 0.2.0 → 0.3.0

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.
@@ -1,10 +1,12 @@
1
1
  import type { FieldDescriptor } from "../../rack/adapters/index";
2
2
  export interface PanelProps {
3
+ donate?: boolean;
3
4
  resource?: string;
4
5
  metadata?: {
5
6
  label?: string;
6
7
  pluralLabel?: string;
7
8
  group?: string;
9
+ icon?: string;
8
10
  };
9
11
  operations?: {
10
12
  list?: boolean;
@@ -0,0 +1,10 @@
1
+ import { type LucideProps } from "lucide-react";
2
+ export interface RackIconProps extends Omit<LucideProps, "name"> {
3
+ /** Lucide icon name (kebab-case or PascalCase). Falls back to the raw value. */
4
+ name?: string;
5
+ }
6
+ /**
7
+ * Render a Lucide icon from metadata.
8
+ * Unknown names fall back to the raw string so existing emoji icons keep working.
9
+ */
10
+ export declare function RackIcon({ name, ...props }: RackIconProps): import("react").JSX.Element | null;
@@ -1,7 +1,8 @@
1
+ import type { ReactNode } from "react";
1
2
  export interface MastheadProps {
2
3
  title: string;
3
- group?: string;
4
- resource?: string;
5
- total?: number;
4
+ icon?: string;
5
+ detail?: ReactNode;
6
+ actions?: ReactNode;
6
7
  }
7
- export declare function Masthead({ title, group, resource, total }: MastheadProps): import("react").JSX.Element;
8
+ export declare function Masthead({ title, icon, detail, actions }: MastheadProps): import("react").JSX.Element;
@@ -26,5 +26,10 @@ export declare namespace ReactRack {
26
26
  */
27
27
  css?: PanelCssOverride;
28
28
  panelCss?: PanelCssOverride;
29
+ /**
30
+ * Donate badge/link — shown in Dashboard & Panel footer.
31
+ * @default true (show Ko-fi)
32
+ */
33
+ donate?: boolean;
29
34
  }
30
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elysia-rack",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Model-driven CRUD rack for Elysia: REST + QUERY routes, validation, authorization, OpenAPI metadata, and React panel rendering.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,8 +54,9 @@
54
54
  "dev": "bun run src/playground.ts",
55
55
  "test": "bun test --coverage",
56
56
  "typecheck": "tsc --noEmit",
57
- "build": "bun run build:js && bun run build:dts && bun run build:client",
58
- "build:js": "bun build ./src/rack/index.ts ./src/react/index.ts --outdir ./dist --target node --format esm --splitting --external elysia --external react --external react-dom",
57
+ "build": "bun run build:css && bun run build:js && bun run build:dts && bun run build:client",
58
+ "build:css": "tailwindcss -i src/react/page/panel.css -o dist/panel.css --minify",
59
+ "build:js": "bun build ./src/rack/index.ts ./src/react/index.ts --outdir ./dist --target node --format esm --splitting --external elysia --external react --external react-dom --external lucide-react",
59
60
  "build:client": "bun build src/react/page/client/panel.ts --outfile dist/panel-app.js --target browser --format esm && bun build src/react/page/client/theme.ts --outfile dist/panel-theme.js --target browser --format esm && mkdir -p dist && cp src/react/app.html dist/app.html 2>/dev/null || true",
60
61
  "build:dts": "tsc -p tsconfig.build.json",
61
62
  "prepublishOnly": "bun run typecheck && bun run build"
@@ -73,6 +74,7 @@
73
74
  },
74
75
  "devDependencies": {
75
76
  "@electric-sql/pglite": "^0.5.8",
77
+ "@tailwindcss/cli": "4.3.3",
76
78
  "@types/bun": "latest",
77
79
  "@types/react": "^19.2.18",
78
80
  "@types/react-dom": "^19.2.5",
@@ -81,5 +83,12 @@
81
83
  "react": "^19.2.8",
82
84
  "react-dom": "^19.2.8",
83
85
  "typescript": "^5"
86
+ },
87
+ "funding": {
88
+ "type": "ko-fi",
89
+ "url": "https://ko-fi.com/notoofly_manu"
90
+ },
91
+ "dependencies": {
92
+ "lucide-react": "^1.72.0"
84
93
  }
85
94
  }
@@ -1,225 +0,0 @@
1
- import {
2
- buildRackTree,
3
- flatRackTree,
4
- getRackTree
5
- } from "./index-4wxvmd8a.js";
6
- import"./index-37x76zdn.js";
7
-
8
- // src/react/page/components/Breadcrumb.tsx
9
- import { jsxDEV } from "react/jsx-dev-runtime";
10
- function Breadcrumb({ trail }) {
11
- return /* @__PURE__ */ jsxDEV("nav", {
12
- "aria-label": "Breadcrumb",
13
- className: "pb-3 text-sm",
14
- children: /* @__PURE__ */ jsxDEV("ol", {
15
- className: "flex flex-wrap items-center gap-1.5",
16
- children: trail.map((item, i) => {
17
- const last = i === trail.length - 1;
18
- return /* @__PURE__ */ jsxDEV("li", {
19
- className: "flex items-center gap-1.5",
20
- children: [
21
- i > 0 ? /* @__PURE__ */ jsxDEV("span", {
22
- "aria-hidden": "true",
23
- className: "text-text-muted",
24
- children: "/"
25
- }, undefined, false, undefined, this) : null,
26
- item.href && !last ? /* @__PURE__ */ jsxDEV("a", {
27
- href: item.href,
28
- className: "text-text-muted hover:text-foreground",
29
- children: item.label
30
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
31
- "aria-current": last ? "page" : undefined,
32
- className: last ? "text-foreground font-semibold" : "text-text-muted",
33
- children: item.label
34
- }, undefined, false, undefined, this)
35
- ]
36
- }, `${item.label}-${i}`, true, undefined, this);
37
- })
38
- }, undefined, false, undefined, this)
39
- }, undefined, false, undefined, this);
40
- }
41
-
42
- // src/react/page/components/Sidebar.tsx
43
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
44
- function SidebarItemView({ item, depth = 0 }) {
45
- const hasChildren = item.children && item.children.length > 0;
46
- return /* @__PURE__ */ jsxDEV2("li", {
47
- children: [
48
- /* @__PURE__ */ jsxDEV2("a", {
49
- href: item.href,
50
- "aria-current": item.active ? "page" : undefined,
51
- className: item.active ? "bg-sidebar-primary text-sidebar-primary-foreground flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-semibold" : "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex items-center gap-2 rounded-md px-2 py-1.5 text-sm",
52
- style: depth > 0 ? { paddingLeft: `${8 + depth * 12}px` } : undefined,
53
- children: [
54
- item.icon ? /* @__PURE__ */ jsxDEV2("span", {
55
- "aria-hidden": "true",
56
- children: item.icon
57
- }, undefined, false, undefined, this) : null,
58
- /* @__PURE__ */ jsxDEV2("span", {
59
- children: item.label
60
- }, undefined, false, undefined, this)
61
- ]
62
- }, undefined, true, undefined, this),
63
- hasChildren ? /* @__PURE__ */ jsxDEV2("ul", {
64
- className: "border-sidebar-border mt-0.5 ml-2 flex flex-col gap-0.5 border-l py-1 pl-2",
65
- children: item.children.map((child) => /* @__PURE__ */ jsxDEV2(SidebarItemView, {
66
- item: child,
67
- depth: depth + 1
68
- }, child.id, false, undefined, this))
69
- }, undefined, false, undefined, this) : null
70
- ]
71
- }, undefined, true, undefined, this);
72
- }
73
- function hasActive(items) {
74
- for (const it of items) {
75
- if (it.active)
76
- return true;
77
- if (it.children && hasActive(it.children))
78
- return true;
79
- }
80
- return false;
81
- }
82
- function Sidebar({ title, groups }) {
83
- return /* @__PURE__ */ jsxDEV2("aside", {
84
- className: "bg-sidebar text-sidebar-foreground border-sidebar-border min-h-0 w-64 shrink-0 overflow-y-auto rounded-lg border",
85
- children: [
86
- /* @__PURE__ */ jsxDEV2("div", {
87
- className: "border-sidebar-border border-b px-4 py-3",
88
- children: /* @__PURE__ */ jsxDEV2("p", {
89
- className: "font-koran text-xl font-black tracking-tight",
90
- children: title
91
- }, undefined, false, undefined, this)
92
- }, undefined, false, undefined, this),
93
- /* @__PURE__ */ jsxDEV2("nav", {
94
- className: "flex flex-col gap-4 p-3",
95
- children: groups.map((group) => /* @__PURE__ */ jsxDEV2("section", {
96
- children: /* @__PURE__ */ jsxDEV2("details", {
97
- className: "group",
98
- open: hasActive(group.items) || undefined,
99
- children: [
100
- /* @__PURE__ */ jsxDEV2("summary", {
101
- className: "font-koran border-sidebar-border flex cursor-pointer list-none items-center justify-between border-b px-2 pb-1 text-base font-bold tracking-tight [&::-webkit-details-marker]:hidden",
102
- children: [
103
- /* @__PURE__ */ jsxDEV2("span", {
104
- children: group.name
105
- }, undefined, false, undefined, this),
106
- /* @__PURE__ */ jsxDEV2("span", {
107
- "aria-hidden": "true",
108
- className: "text-text-muted text-xs transition group-open:rotate-180",
109
- children: "▾"
110
- }, undefined, false, undefined, this)
111
- ]
112
- }, undefined, true, undefined, this),
113
- /* @__PURE__ */ jsxDEV2("ul", {
114
- className: "border-sidebar-border mt-1 ml-2 flex flex-col gap-0.5 border-l py-1 pl-2",
115
- children: group.items.map((item) => /* @__PURE__ */ jsxDEV2(SidebarItemView, {
116
- item
117
- }, item.id, false, undefined, this))
118
- }, undefined, false, undefined, this)
119
- ]
120
- }, undefined, true, undefined, this)
121
- }, group.name, false, undefined, this))
122
- }, undefined, false, undefined, this)
123
- ]
124
- }, undefined, true, undefined, this);
125
- }
126
-
127
- // src/react/page/Dashboard.tsx
128
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
129
- function itemLabel(r) {
130
- return r.metadata.pluralLabel ?? r.metadata.label ?? r.metadata.id;
131
- }
132
- function buildGroups(racks, selectedId) {
133
- const byGroup = new Map;
134
- for (const r of racks) {
135
- if (r.metadata.hidden)
136
- continue;
137
- const name = r.metadata.group ?? "General";
138
- let group = byGroup.get(name);
139
- if (!group) {
140
- group = { name, items: [] };
141
- byGroup.set(name, group);
142
- }
143
- group.items.push({
144
- id: r.metadata.id,
145
- label: itemLabel(r),
146
- icon: r.metadata.icon,
147
- href: `?resource=${encodeURIComponent(r.metadata.id)}`,
148
- active: r.metadata.id === selectedId
149
- });
150
- }
151
- const groups = [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
152
- for (const group of groups) {
153
- const orderOf = (id) => racks.find((r) => r.metadata.id === id)?.metadata.order ?? 0;
154
- group.items.sort((a, b) => orderOf(a.id) - orderOf(b.id) || a.label.localeCompare(b.label));
155
- }
156
- return groups;
157
- }
158
- function toSidebarItem(node, selectedId) {
159
- return {
160
- id: node.metadata.id,
161
- label: itemLabel(node),
162
- icon: node.metadata.icon,
163
- href: `?resource=${encodeURIComponent(node.metadata.id)}`,
164
- active: node.metadata.id === selectedId,
165
- children: node.children.filter((c) => !c.metadata.hidden).map((c) => toSidebarItem(c, selectedId))
166
- };
167
- }
168
- function buildGroupsFromTree(tree, selectedId) {
169
- const byGroup = new Map;
170
- for (const root of tree) {
171
- if (root.metadata.hidden)
172
- continue;
173
- const name = root.metadata.group ?? "General";
174
- let group = byGroup.get(name);
175
- if (!group) {
176
- group = { name, items: [] };
177
- byGroup.set(name, group);
178
- }
179
- group.items.push(toSidebarItem(root, selectedId));
180
- }
181
- return [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
182
- }
183
- async function Dashboard(props) {
184
- const tree = props.racks !== undefined ? buildRackTree(props.racks.filter((r) => !r.metadata.hidden)) : getRackTree().filter((r) => !r.metadata.hidden);
185
- const flat = props.racks !== undefined ? props.racks.filter((r) => !r.metadata.hidden) : flatRackTree(tree).filter((r) => !r.metadata.hidden);
186
- const selected = flat.find((r) => r.metadata.id === props.resource) ?? flat[0];
187
- const groups = tree.length ? buildGroupsFromTree(tree, selected?.metadata.id) : buildGroups(flat, selected?.metadata.id);
188
- const trail = [
189
- { label: "Dashboard", href: "?" },
190
- ...selected?.metadata.group ? [{ label: selected.metadata.group }] : [],
191
- ...selected ? [{ label: itemLabel(selected) }] : []
192
- ];
193
- return /* @__PURE__ */ jsxDEV3("div", {
194
- className: "koran-paper font-koran-body text-foreground flex flex-col min-h-screen",
195
- children: /* @__PURE__ */ jsxDEV3("div", {
196
- className: "flex min-h-0 flex-1 gap-6 p-4 sm:p-6 h-full",
197
- children: [
198
- /* @__PURE__ */ jsxDEV3(Sidebar, {
199
- title: props.name ?? "Panel",
200
- groups
201
- }, undefined, false, undefined, this),
202
- /* @__PURE__ */ jsxDEV3("main", {
203
- className: "flex min-w-0 flex-1 flex-col",
204
- children: [
205
- /* @__PURE__ */ jsxDEV3(Breadcrumb, {
206
- trail
207
- }, undefined, false, undefined, this),
208
- selected ? /* @__PURE__ */ jsxDEV3("iframe", {
209
- src: selected.path,
210
- title: itemLabel(selected),
211
- sandbox: "allow-scripts allow-same-origin allow-forms",
212
- className: "bg-card min-h-0 w-full flex-1 rounded-lg"
213
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("p", {
214
- className: "text-text-muted border-border rounded-lg border px-4 py-10 text-center",
215
- children: "No resources registered. Mount a resource with rack() first."
216
- }, undefined, false, undefined, this)
217
- ]
218
- }, undefined, true, undefined, this)
219
- ]
220
- }, undefined, true, undefined, this)
221
- }, undefined, false, undefined, this);
222
- }
223
- export {
224
- Dashboard as default
225
- };
@@ -1,252 +0,0 @@
1
- import {
2
- flatRackTree,
3
- getRackTree
4
- } from "./index-tgk4jt4d.js";
5
- import"./index-37x76zdn.js";
6
-
7
- // src/react/page/components/Breadcrumb.tsx
8
- import { jsxDEV } from "react/jsx-dev-runtime";
9
- function Breadcrumb({ trail }) {
10
- return /* @__PURE__ */ jsxDEV("nav", {
11
- "aria-label": "Breadcrumb",
12
- className: "pb-3 text-sm",
13
- children: /* @__PURE__ */ jsxDEV("ol", {
14
- className: "flex flex-wrap items-center gap-1.5",
15
- children: trail.map((item, i) => {
16
- const last = i === trail.length - 1;
17
- return /* @__PURE__ */ jsxDEV("li", {
18
- className: "flex items-center gap-1.5",
19
- children: [
20
- i > 0 ? /* @__PURE__ */ jsxDEV("span", {
21
- "aria-hidden": "true",
22
- className: "text-text-muted",
23
- children: "/"
24
- }, undefined, false, undefined, this) : null,
25
- item.href && !last ? /* @__PURE__ */ jsxDEV("a", {
26
- href: item.href,
27
- className: "text-text-muted hover:text-foreground",
28
- children: item.label
29
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
30
- "aria-current": last ? "page" : undefined,
31
- className: last ? "text-foreground font-semibold" : "text-text-muted",
32
- children: item.label
33
- }, undefined, false, undefined, this)
34
- ]
35
- }, `${item.label}-${i}`, true, undefined, this);
36
- })
37
- }, undefined, false, undefined, this)
38
- }, undefined, false, undefined, this);
39
- }
40
-
41
- // src/react/page/components/Sidebar.tsx
42
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
43
- function SidebarItemView({ item, depth = 0 }) {
44
- const hasChildren = item.children && item.children.length > 0;
45
- return /* @__PURE__ */ jsxDEV2("li", {
46
- children: [
47
- /* @__PURE__ */ jsxDEV2("a", {
48
- href: item.href,
49
- "aria-current": item.active ? "page" : undefined,
50
- className: item.active ? "bg-sidebar-primary text-sidebar-primary-foreground flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-semibold" : "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex items-center gap-2 rounded-md px-2 py-1.5 text-sm",
51
- style: depth > 0 ? { paddingLeft: `${8 + depth * 12}px` } : undefined,
52
- children: [
53
- item.icon ? /* @__PURE__ */ jsxDEV2("span", {
54
- "aria-hidden": "true",
55
- children: item.icon
56
- }, undefined, false, undefined, this) : null,
57
- /* @__PURE__ */ jsxDEV2("span", {
58
- children: item.label
59
- }, undefined, false, undefined, this)
60
- ]
61
- }, undefined, true, undefined, this),
62
- hasChildren ? /* @__PURE__ */ jsxDEV2("ul", {
63
- className: "border-sidebar-border mt-0.5 ml-2 flex flex-col gap-0.5 border-l py-1 pl-2",
64
- children: item.children.map((child) => /* @__PURE__ */ jsxDEV2(SidebarItemView, {
65
- item: child,
66
- depth: depth + 1
67
- }, child.id, false, undefined, this))
68
- }, undefined, false, undefined, this) : null
69
- ]
70
- }, undefined, true, undefined, this);
71
- }
72
- function hasActive(items) {
73
- for (const it of items) {
74
- if (it.active)
75
- return true;
76
- if (it.children && hasActive(it.children))
77
- return true;
78
- }
79
- return false;
80
- }
81
- function Sidebar({ title, groups }) {
82
- return /* @__PURE__ */ jsxDEV2("aside", {
83
- className: "bg-sidebar text-sidebar-foreground border-sidebar-border min-h-0 w-64 shrink-0 overflow-y-auto rounded-lg border",
84
- children: [
85
- /* @__PURE__ */ jsxDEV2("div", {
86
- className: "border-sidebar-border border-b px-4 py-3",
87
- children: /* @__PURE__ */ jsxDEV2("p", {
88
- className: "font-koran text-xl font-black tracking-tight",
89
- children: title
90
- }, undefined, false, undefined, this)
91
- }, undefined, false, undefined, this),
92
- /* @__PURE__ */ jsxDEV2("nav", {
93
- className: "flex flex-col gap-4 p-3",
94
- children: groups.map((group) => /* @__PURE__ */ jsxDEV2("section", {
95
- children: /* @__PURE__ */ jsxDEV2("details", {
96
- className: "group",
97
- open: hasActive(group.items) || undefined,
98
- children: [
99
- /* @__PURE__ */ jsxDEV2("summary", {
100
- className: "font-koran border-sidebar-border flex cursor-pointer list-none items-center justify-between border-b px-2 pb-1 text-base font-bold tracking-tight [&::-webkit-details-marker]:hidden",
101
- children: [
102
- /* @__PURE__ */ jsxDEV2("span", {
103
- children: group.name
104
- }, undefined, false, undefined, this),
105
- /* @__PURE__ */ jsxDEV2("span", {
106
- "aria-hidden": "true",
107
- className: "text-text-muted text-xs transition group-open:rotate-180",
108
- children: "▾"
109
- }, undefined, false, undefined, this)
110
- ]
111
- }, undefined, true, undefined, this),
112
- /* @__PURE__ */ jsxDEV2("ul", {
113
- className: "border-sidebar-border mt-1 ml-2 flex flex-col gap-0.5 border-l py-1 pl-2",
114
- children: group.items.map((item) => /* @__PURE__ */ jsxDEV2(SidebarItemView, {
115
- item
116
- }, item.id, false, undefined, this))
117
- }, undefined, false, undefined, this)
118
- ]
119
- }, undefined, true, undefined, this)
120
- }, group.name, false, undefined, this))
121
- }, undefined, false, undefined, this)
122
- ]
123
- }, undefined, true, undefined, this);
124
- }
125
-
126
- // src/react/page/Dashboard.tsx
127
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
128
- function itemLabel(r) {
129
- return r.metadata.pluralLabel ?? r.metadata.label ?? r.metadata.id;
130
- }
131
- function buildGroups(racks, selectedId) {
132
- const byGroup = new Map;
133
- for (const r of racks) {
134
- if (r.metadata.hidden)
135
- continue;
136
- const name = r.metadata.group ?? "General";
137
- let group = byGroup.get(name);
138
- if (!group) {
139
- group = { name, items: [] };
140
- byGroup.set(name, group);
141
- }
142
- group.items.push({
143
- id: r.metadata.id,
144
- label: itemLabel(r),
145
- icon: r.metadata.icon,
146
- href: `?resource=${encodeURIComponent(r.metadata.id)}`,
147
- active: r.metadata.id === selectedId
148
- });
149
- }
150
- const groups = [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
151
- for (const group of groups) {
152
- const orderOf = (id) => racks.find((r) => r.metadata.id === id)?.metadata.order ?? 0;
153
- group.items.sort((a, b) => orderOf(a.id) - orderOf(b.id) || a.label.localeCompare(b.label));
154
- }
155
- return groups;
156
- }
157
- function toSidebarItem(node, selectedId) {
158
- return {
159
- id: node.metadata.id,
160
- label: itemLabel(node),
161
- icon: node.metadata.icon,
162
- href: `?resource=${encodeURIComponent(node.metadata.id)}`,
163
- active: node.metadata.id === selectedId,
164
- children: node.children.filter((c) => !c.metadata.hidden).map((c) => toSidebarItem(c, selectedId))
165
- };
166
- }
167
- function buildGroupsFromTree(tree, selectedId) {
168
- const byGroup = new Map;
169
- for (const root of tree) {
170
- if (root.metadata.hidden)
171
- continue;
172
- const name = root.metadata.group ?? "General";
173
- let group = byGroup.get(name);
174
- if (!group) {
175
- group = { name, items: [] };
176
- byGroup.set(name, group);
177
- }
178
- group.items.push(toSidebarItem(root, selectedId));
179
- }
180
- return [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
181
- }
182
- function treeFromRacks(racks) {
183
- const nodes = new Map;
184
- for (const r of racks)
185
- nodes.set(r.metadata.id, { ...r, children: [] });
186
- const roots = [];
187
- for (const node of nodes.values()) {
188
- const parentId = node.metadata.parent;
189
- if (parentId && nodes.has(parentId) && parentId !== node.metadata.id) {
190
- nodes.get(parentId).children.push(node);
191
- } else {
192
- roots.push(node);
193
- }
194
- }
195
- const sort = (list) => {
196
- list.sort((a, b) => {
197
- const oa = a.metadata.order ?? 0;
198
- const ob = b.metadata.order ?? 0;
199
- if (oa !== ob)
200
- return oa - ob;
201
- return itemLabel(a).localeCompare(itemLabel(b));
202
- });
203
- for (const n of list)
204
- if (n.children.length)
205
- sort(n.children);
206
- };
207
- sort(roots);
208
- return roots;
209
- }
210
- async function Dashboard(props) {
211
- const tree = props.racks !== undefined ? treeFromRacks(props.racks.filter((r) => !r.metadata.hidden)) : getRackTree().filter((r) => !r.metadata.hidden);
212
- const flat = props.racks !== undefined ? props.racks.filter((r) => !r.metadata.hidden) : flatRackTree(tree).filter((r) => !r.metadata.hidden);
213
- const selected = flat.find((r) => r.metadata.id === props.resource) ?? flat[0];
214
- const groups = tree.length ? buildGroupsFromTree(tree, selected?.metadata.id) : buildGroups(flat, selected?.metadata.id);
215
- const trail = [
216
- { label: "Dashboard", href: "?" },
217
- ...selected?.metadata.group ? [{ label: selected.metadata.group }] : [],
218
- ...selected ? [{ label: itemLabel(selected) }] : []
219
- ];
220
- return /* @__PURE__ */ jsxDEV3("div", {
221
- className: "koran-paper font-koran-body text-foreground flex flex-col min-h-screen",
222
- children: /* @__PURE__ */ jsxDEV3("div", {
223
- className: "flex min-h-0 flex-1 gap-6 p-4 sm:p-6 h-full",
224
- children: [
225
- /* @__PURE__ */ jsxDEV3(Sidebar, {
226
- title: props.name ?? "Panel",
227
- groups
228
- }, undefined, false, undefined, this),
229
- /* @__PURE__ */ jsxDEV3("main", {
230
- className: "flex min-w-0 flex-1 flex-col",
231
- children: [
232
- /* @__PURE__ */ jsxDEV3(Breadcrumb, {
233
- trail
234
- }, undefined, false, undefined, this),
235
- selected ? /* @__PURE__ */ jsxDEV3("iframe", {
236
- src: selected.path,
237
- title: itemLabel(selected),
238
- sandbox: "allow-scripts allow-same-origin allow-forms",
239
- className: "bg-card min-h-0 w-full flex-1 rounded-lg"
240
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("p", {
241
- className: "text-text-muted border-border rounded-lg border px-4 py-10 text-center",
242
- children: "No resources registered. Mount a resource with rack() first."
243
- }, undefined, false, undefined, this)
244
- ]
245
- }, undefined, true, undefined, this)
246
- ]
247
- }, undefined, true, undefined, this)
248
- }, undefined, false, undefined, this);
249
- }
250
- export {
251
- Dashboard as default
252
- };
@@ -1,39 +0,0 @@
1
- import"./index-37x76zdn.js";
2
-
3
- // src/react/page/Dashboard.tsx
4
- import { jsxDEV } from "react/jsx-dev-runtime";
5
- async function Dashboard(props) {
6
- return /* @__PURE__ */ jsxDEV("div", {
7
- className: "koran-paper font-koran-body text-foreground flex min-h-screen items-center justify-center p-6",
8
- children: /* @__PURE__ */ jsxDEV("div", {
9
- className: "bg-card border-border max-w-lg rounded-xl border px-8 py-12 text-center shadow-sm",
10
- children: [
11
- /* @__PURE__ */ jsxDEV("p", {
12
- className: "text-text-muted text-xs font-semibold tracking-widest uppercase",
13
- children: "Dashboard"
14
- }, undefined, false, undefined, this),
15
- /* @__PURE__ */ jsxDEV("h1", {
16
- className: "font-koran mt-2 text-4xl font-black tracking-tight",
17
- children: "Coming Soon"
18
- }, undefined, false, undefined, this),
19
- /* @__PURE__ */ jsxDEV("p", {
20
- className: "text-text-muted mt-3 text-sm leading-relaxed",
21
- children: [
22
- props.name ? `${props.name} — ` : "",
23
- "This dashboard is coming soon. Your panels are available via the sidebar in each resource view."
24
- ]
25
- }, undefined, true, undefined, this),
26
- /* @__PURE__ */ jsxDEV("div", {
27
- className: "mt-6 flex justify-center",
28
- children: /* @__PURE__ */ jsxDEV("span", {
29
- className: "bg-primary text-primary-foreground rounded-md px-4 py-1.5 text-sm font-semibold",
30
- children: "Stay tuned"
31
- }, undefined, false, undefined, this)
32
- }, undefined, false, undefined, this)
33
- ]
34
- }, undefined, true, undefined, this)
35
- }, undefined, false, undefined, this);
36
- }
37
- export {
38
- Dashboard as default
39
- };