elysia-rack 0.2.2 → 0.4.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.
- package/README.md +37 -20
- package/dist/Dashboard-4wv82gwx.js +212 -0
- package/dist/Dashboard-jkvfzyk2.js +210 -0
- package/dist/Dashboard-zhg2jtq5.js +211 -0
- package/dist/{Panel-fbp3wrsw.js → Panel-1qkzh8t7.js} +355 -311
- package/dist/Panel-7zpqz8gy.js +782 -0
- package/dist/Panel-9mmh3tbh.js +790 -0
- package/dist/Panel-ba0qzknj.js +789 -0
- package/dist/Panel-e6jyf4rc.js +789 -0
- package/dist/index-0dwhzk3j.js +145 -0
- package/dist/{index-6v0ns1xn.js → index-b8sq8rbd.js} +1 -1
- package/dist/index-rg87f3v1.js +174 -0
- package/dist/panel-app.js +27 -11
- package/dist/panel.css +2 -1
- package/dist/rack/index.js +5 -5
- package/dist/rack/types/metadata.d.ts +2 -1
- package/dist/react/donate.d.ts +1 -1
- package/dist/react/index.js +40 -15
- package/dist/react/page/Panel.d.ts +1 -0
- package/dist/react/page/components/Icon.d.ts +10 -0
- package/dist/react/page/components/Masthead.d.ts +5 -4
- package/dist/react/render.d.ts +1 -1
- package/dist/react/types.d.ts +18 -2
- package/package.json +8 -3
- package/dist/Dashboard-0da8dvbv.js +0 -225
- package/dist/Dashboard-2j561p7c.js +0 -225
- package/dist/Dashboard-5mt9hnbw.js +0 -252
- package/dist/Dashboard-7gmx8c9y.js +0 -248
- package/dist/Dashboard-s8nfyf7s.js +0 -39
- package/dist/Dashboard-xkh2g9f7.js +0 -248
- package/dist/Dashboard-xpstr7fr.js +0 -172
- package/dist/Panel-3yrk3krj.js +0 -914
- package/dist/Panel-b7jhgvnm.js +0 -916
- package/dist/Panel-n0kstkyw.js +0 -735
- package/dist/Panel-pkvqf5v8.js +0 -912
- package/dist/Panel-wjxjtbmr.js +0 -753
- package/dist/Panel-za4av616.js +0 -753
- package/dist/index-h3zbq796.js +0 -39
- package/dist/index-tgk4jt4d.js +0 -63
- package/dist/index-xsfqad54.js +0 -187
- package/dist/index-zc7rgvmm.js +0 -13
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
setGlobalDonate
|
|
3
|
-
} from "../index-
|
|
3
|
+
} from "../index-b8sq8rbd.js";
|
|
4
4
|
import {
|
|
5
5
|
__require
|
|
6
6
|
} from "../index-37x76zdn.js";
|
|
@@ -18,6 +18,24 @@ import { readFile } from "node:fs/promises";
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { renderToReadableStream } from "react-dom/server";
|
|
21
|
+
|
|
22
|
+
// src/react/types.ts
|
|
23
|
+
var ReactRack;
|
|
24
|
+
((ReactRack) => {
|
|
25
|
+
ReactRack.PAGE = Symbol.for("@elysia-panel/react");
|
|
26
|
+
const lazyPages = new WeakSet;
|
|
27
|
+
function lazyPage(loader) {
|
|
28
|
+
lazyPages.add(loader);
|
|
29
|
+
return loader;
|
|
30
|
+
}
|
|
31
|
+
ReactRack.lazyPage = lazyPage;
|
|
32
|
+
function isLazyPage(entry) {
|
|
33
|
+
return typeof entry === "function" && lazyPages.has(entry);
|
|
34
|
+
}
|
|
35
|
+
ReactRack.isLazyPage = isLazyPage;
|
|
36
|
+
})(ReactRack ||= {});
|
|
37
|
+
|
|
38
|
+
// src/react/render.tsx
|
|
21
39
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
22
40
|
function pageTitle(path, props) {
|
|
23
41
|
const metadata = props?.metadata;
|
|
@@ -67,16 +85,18 @@ function buildShell(template, title) {
|
|
|
67
85
|
return { head: html.slice(0, bodyClose), tail: html.slice(bodyClose) };
|
|
68
86
|
return { head: html, tail: "" };
|
|
69
87
|
}
|
|
88
|
+
function isLazyPage(entry) {
|
|
89
|
+
return ReactRack.isLazyPage(entry);
|
|
90
|
+
}
|
|
70
91
|
function createRenderer(pages) {
|
|
71
92
|
return async function render(path, props) {
|
|
72
|
-
const
|
|
73
|
-
if (!
|
|
93
|
+
const entry = pages[path];
|
|
94
|
+
if (!entry) {
|
|
74
95
|
return new Response("Page Not Found", {
|
|
75
96
|
status: 404
|
|
76
97
|
});
|
|
77
98
|
}
|
|
78
|
-
const
|
|
79
|
-
const Component = module.default;
|
|
99
|
+
const Component = isLazyPage(entry) ? (await entry()).default : entry;
|
|
80
100
|
const stream = await renderToReadableStream(/* @__PURE__ */ jsxDEV(Component, {
|
|
81
101
|
...props
|
|
82
102
|
}, undefined, false, undefined, this));
|
|
@@ -109,12 +129,6 @@ function createRenderer(pages) {
|
|
|
109
129
|
};
|
|
110
130
|
}
|
|
111
131
|
|
|
112
|
-
// src/react/types.ts
|
|
113
|
-
var ReactRack;
|
|
114
|
-
((ReactRack) => {
|
|
115
|
-
ReactRack.PAGE = Symbol.for("@elysia-panel/react");
|
|
116
|
-
})(ReactRack ||= {});
|
|
117
|
-
|
|
118
132
|
// src/react/index.ts
|
|
119
133
|
var PANEL_ASSETS = {
|
|
120
134
|
"panel.css": "text/css; charset=utf-8",
|
|
@@ -155,8 +169,8 @@ function page(path, props) {
|
|
|
155
169
|
};
|
|
156
170
|
}
|
|
157
171
|
var pages = {
|
|
158
|
-
"/dashboard": () => import("../Dashboard-
|
|
159
|
-
"/panel": () => import("../Panel-
|
|
172
|
+
"/dashboard": ReactRack.lazyPage(() => import("../Dashboard-4wv82gwx.js")),
|
|
173
|
+
"/panel": ReactRack.lazyPage(() => import("../Panel-9mmh3tbh.js"))
|
|
160
174
|
};
|
|
161
175
|
var defaultPages = pages;
|
|
162
176
|
function mergeRegistries(base, extra) {
|
|
@@ -167,7 +181,14 @@ function mergeRegistries(base, extra) {
|
|
|
167
181
|
}
|
|
168
182
|
function reactPlugin(options = {}) {
|
|
169
183
|
setGlobalDonate(options.donate);
|
|
170
|
-
const
|
|
184
|
+
const views = options.views ?? [];
|
|
185
|
+
const viewPages = {};
|
|
186
|
+
for (const view of views)
|
|
187
|
+
viewPages[view.path] = view.component;
|
|
188
|
+
const registry = mergeRegistries(defaultPages, [
|
|
189
|
+
...options.pages ? Array.isArray(options.pages) ? options.pages : [options.pages] : [],
|
|
190
|
+
viewPages
|
|
191
|
+
]);
|
|
171
192
|
const render = createRenderer(registry);
|
|
172
193
|
const panelCssOverride = pickPanelCssOption(options);
|
|
173
194
|
let cachedCustomCss = null;
|
|
@@ -214,7 +235,7 @@ function reactPlugin(options = {}) {
|
|
|
214
235
|
cachedCustomCssLoaded = true;
|
|
215
236
|
return cachedCustomCss = null;
|
|
216
237
|
}
|
|
217
|
-
|
|
238
|
+
let app = new Elysia({ name: "@elysia-rack/react" }).decorate("react", { render }).get("/__rack/:name", async ({ params, status }) => {
|
|
218
239
|
const type = PANEL_ASSETS[params.name];
|
|
219
240
|
if (!type)
|
|
220
241
|
return status(404, "Panel asset not found");
|
|
@@ -250,6 +271,10 @@ function reactPlugin(options = {}) {
|
|
|
250
271
|
const descriptor = responseValue;
|
|
251
272
|
return render(descriptor.path, descriptor.props);
|
|
252
273
|
});
|
|
274
|
+
for (const view of views) {
|
|
275
|
+
app = app.get(view.path, () => render(view.path, view.props));
|
|
276
|
+
}
|
|
277
|
+
return app;
|
|
253
278
|
}
|
|
254
279
|
export {
|
|
255
280
|
reactPlugin,
|
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
icon?: string;
|
|
5
|
+
detail?: ReactNode;
|
|
6
|
+
actions?: ReactNode;
|
|
6
7
|
}
|
|
7
|
-
export declare function Masthead({ title,
|
|
8
|
+
export declare function Masthead({ title, icon, detail, actions }: MastheadProps): import("react").JSX.Element;
|
package/dist/react/render.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactRack } from "./types";
|
|
2
2
|
export declare function createRenderer(pages: ReactRack.PageRegistry): (path: string, props: unknown) => Promise<Response>;
|
package/dist/react/types.d.ts
CHANGED
|
@@ -7,9 +7,23 @@ export declare namespace ReactRack {
|
|
|
7
7
|
props?: Props;
|
|
8
8
|
}
|
|
9
9
|
type PageComponent<Props = any> = ComponentType<Props>;
|
|
10
|
-
type
|
|
10
|
+
type PageLoader = () => Promise<{
|
|
11
11
|
default: PageComponent;
|
|
12
|
-
}
|
|
12
|
+
}>;
|
|
13
|
+
/** A page is either a component or a loader wrapped with `lazyPage()`. */
|
|
14
|
+
type PageEntry = PageComponent | PageLoader;
|
|
15
|
+
type PageRegistry = Record<string, PageEntry>;
|
|
16
|
+
/** Mark a `() => import(...)` loader so it is not mistaken for a component. */
|
|
17
|
+
function lazyPage(loader: PageLoader): PageLoader;
|
|
18
|
+
/** @internal */
|
|
19
|
+
function isLazyPage(entry: PageEntry): entry is PageLoader;
|
|
20
|
+
/** A page registered directly with its URL — no `page()` call needed. */
|
|
21
|
+
interface ReactView<Props = any> {
|
|
22
|
+
/** URL path the route is mounted at. */
|
|
23
|
+
path: string;
|
|
24
|
+
component: PageEntry;
|
|
25
|
+
props?: Props;
|
|
26
|
+
}
|
|
13
27
|
/** Override source for panel.css */
|
|
14
28
|
type PanelCssOverride = string | {
|
|
15
29
|
path?: string;
|
|
@@ -17,6 +31,8 @@ export declare namespace ReactRack {
|
|
|
17
31
|
};
|
|
18
32
|
interface ReactPluginOptions {
|
|
19
33
|
pages?: PageRegistry | PageRegistry[];
|
|
34
|
+
/** Pages auto-mounted at their `path`; `page()` is not needed for these. */
|
|
35
|
+
views?: ReactView[];
|
|
20
36
|
/**
|
|
21
37
|
* Override `panel.css` served at `/__rack/panel.css`.
|
|
22
38
|
* - `string`: file path if the file exists, otherwise raw CSS string.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elysia-rack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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:
|
|
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",
|
|
@@ -85,5 +87,8 @@
|
|
|
85
87
|
"funding": {
|
|
86
88
|
"type": "ko-fi",
|
|
87
89
|
"url": "https://ko-fi.com/notoofly_manu"
|
|
90
|
+
},
|
|
91
|
+
"dependencies": {
|
|
92
|
+
"lucide-react": "^1.72.0"
|
|
88
93
|
}
|
|
89
94
|
}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Breadcrumb,
|
|
3
|
-
Masthead,
|
|
4
|
-
Sidebar
|
|
5
|
-
} from "./index-xsfqad54.js";
|
|
6
|
-
import {
|
|
7
|
-
getRackTree
|
|
8
|
-
} from "./index-4wxvmd8a.js";
|
|
9
|
-
import"./index-37x76zdn.js";
|
|
10
|
-
|
|
11
|
-
// src/react/page/Dashboard.tsx
|
|
12
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
13
|
-
function itemLabel(r) {
|
|
14
|
-
return r.metadata.pluralLabel ?? r.metadata.label ?? r.metadata.id;
|
|
15
|
-
}
|
|
16
|
-
function toSidebarItem(node, activeId) {
|
|
17
|
-
return {
|
|
18
|
-
id: node.metadata.id,
|
|
19
|
-
label: itemLabel(node),
|
|
20
|
-
icon: node.metadata.icon,
|
|
21
|
-
href: node.path,
|
|
22
|
-
active: node.metadata.id === activeId,
|
|
23
|
-
children: node.children.filter((c) => !c.metadata.hidden).map((c) => toSidebarItem(c, activeId))
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function buildGroupsFromTree(tree, activeId) {
|
|
27
|
-
const byGroup = new Map;
|
|
28
|
-
for (const root of tree) {
|
|
29
|
-
if (root.metadata.hidden)
|
|
30
|
-
continue;
|
|
31
|
-
const name = root.metadata.group ?? "General";
|
|
32
|
-
let group = byGroup.get(name);
|
|
33
|
-
if (!group) {
|
|
34
|
-
group = { name, items: [] };
|
|
35
|
-
byGroup.set(name, group);
|
|
36
|
-
}
|
|
37
|
-
group.items.push(toSidebarItem(root, activeId));
|
|
38
|
-
}
|
|
39
|
-
return [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
-
}
|
|
41
|
-
async function Dashboard(props) {
|
|
42
|
-
const tree = getRackTree().filter((r) => !r.metadata.hidden);
|
|
43
|
-
const groups = buildGroupsFromTree(tree, props.resource);
|
|
44
|
-
const totalResources = tree.length + tree.reduce((a, n) => a + n.children.length, 0);
|
|
45
|
-
const totalGroups = groups.length;
|
|
46
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
47
|
-
className: "koran-paper font-koran-body text-foreground flex min-h-screen flex-col",
|
|
48
|
-
children: /* @__PURE__ */ jsxDEV("div", {
|
|
49
|
-
className: "flex flex-1 gap-6 p-4 sm:p-6",
|
|
50
|
-
children: [
|
|
51
|
-
/* @__PURE__ */ jsxDEV(Sidebar, {
|
|
52
|
-
title: props.name ?? "Panel",
|
|
53
|
-
groups
|
|
54
|
-
}, undefined, false, undefined, this),
|
|
55
|
-
/* @__PURE__ */ jsxDEV("main", {
|
|
56
|
-
className: "flex min-w-0 flex-1 flex-col",
|
|
57
|
-
children: [
|
|
58
|
-
/* @__PURE__ */ jsxDEV(Breadcrumb, {
|
|
59
|
-
trail: [{ label: "Dashboard" }]
|
|
60
|
-
}, undefined, false, undefined, this),
|
|
61
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
62
|
-
className: "mx-auto w-full max-w-5xl px-4 pb-10 sm:px-6",
|
|
63
|
-
children: [
|
|
64
|
-
/* @__PURE__ */ jsxDEV(Masthead, {
|
|
65
|
-
title: props.name ?? "ERP Demo",
|
|
66
|
-
group: "Dashboard",
|
|
67
|
-
resource: "dashboard",
|
|
68
|
-
total: totalResources
|
|
69
|
-
}, undefined, false, undefined, this),
|
|
70
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
71
|
-
className: "grid grid-cols-2 gap-3 pb-6 lg:grid-cols-4",
|
|
72
|
-
children: [
|
|
73
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
74
|
-
className: "bg-card border-border rounded-lg border px-4 py-3",
|
|
75
|
-
children: [
|
|
76
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
77
|
-
className: "text-text-muted text-xs font-semibold tracking-widest uppercase",
|
|
78
|
-
children: "Resources"
|
|
79
|
-
}, undefined, false, undefined, this),
|
|
80
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
81
|
-
className: "font-koran text-2xl font-black",
|
|
82
|
-
children: totalResources
|
|
83
|
-
}, undefined, false, undefined, this),
|
|
84
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
85
|
-
className: "text-text-muted text-xs",
|
|
86
|
-
children: [
|
|
87
|
-
totalGroups,
|
|
88
|
-
" groups"
|
|
89
|
-
]
|
|
90
|
-
}, undefined, true, undefined, this)
|
|
91
|
-
]
|
|
92
|
-
}, undefined, true, undefined, this),
|
|
93
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
94
|
-
className: "bg-card border-border rounded-lg border px-4 py-3",
|
|
95
|
-
children: [
|
|
96
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
97
|
-
className: "text-text-muted text-xs font-semibold tracking-widest uppercase",
|
|
98
|
-
children: "Modules"
|
|
99
|
-
}, undefined, false, undefined, this),
|
|
100
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
101
|
-
className: "font-koran text-2xl font-black",
|
|
102
|
-
children: totalGroups
|
|
103
|
-
}, undefined, false, undefined, this),
|
|
104
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
105
|
-
className: "text-text-muted text-xs",
|
|
106
|
-
children: "Core → Governance"
|
|
107
|
-
}, undefined, false, undefined, this)
|
|
108
|
-
]
|
|
109
|
-
}, undefined, true, undefined, this),
|
|
110
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
111
|
-
className: "bg-card border-border rounded-lg border px-4 py-3",
|
|
112
|
-
children: [
|
|
113
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
114
|
-
className: "text-text-muted text-xs font-semibold tracking-widest uppercase",
|
|
115
|
-
children: "Status"
|
|
116
|
-
}, undefined, false, undefined, this),
|
|
117
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
118
|
-
className: "font-koran text-2xl font-black",
|
|
119
|
-
children: "Ready"
|
|
120
|
-
}, undefined, false, undefined, this),
|
|
121
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
122
|
-
className: "text-text-muted text-xs",
|
|
123
|
-
children: "PGlite in-memory"
|
|
124
|
-
}, undefined, false, undefined, this)
|
|
125
|
-
]
|
|
126
|
-
}, undefined, true, undefined, this),
|
|
127
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
128
|
-
className: "bg-card border-border rounded-lg border px-4 py-3",
|
|
129
|
-
children: [
|
|
130
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
131
|
-
className: "text-text-muted text-xs font-semibold tracking-widest uppercase",
|
|
132
|
-
children: "Tree"
|
|
133
|
-
}, undefined, false, undefined, this),
|
|
134
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
135
|
-
className: "font-koran text-2xl font-black",
|
|
136
|
-
children: tree.length
|
|
137
|
-
}, undefined, false, undefined, this),
|
|
138
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
139
|
-
className: "text-text-muted text-xs",
|
|
140
|
-
children: "root nodes"
|
|
141
|
-
}, undefined, false, undefined, this)
|
|
142
|
-
]
|
|
143
|
-
}, undefined, true, undefined, this)
|
|
144
|
-
]
|
|
145
|
-
}, undefined, true, undefined, this),
|
|
146
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
147
|
-
className: "border-border bg-card rounded-lg border",
|
|
148
|
-
children: [
|
|
149
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
150
|
-
className: "border-border flex items-center justify-between border-b px-4 py-3",
|
|
151
|
-
children: [
|
|
152
|
-
/* @__PURE__ */ jsxDEV("h2", {
|
|
153
|
-
className: "text-sm font-semibold tracking-widest uppercase",
|
|
154
|
-
children: "Resource Tree"
|
|
155
|
-
}, undefined, false, undefined, this),
|
|
156
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
157
|
-
className: "text-text-muted text-xs",
|
|
158
|
-
children: [
|
|
159
|
-
totalResources,
|
|
160
|
-
" resources"
|
|
161
|
-
]
|
|
162
|
-
}, undefined, true, undefined, this)
|
|
163
|
-
]
|
|
164
|
-
}, undefined, true, undefined, this),
|
|
165
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
166
|
-
className: "divide-border divide-y",
|
|
167
|
-
children: [
|
|
168
|
-
groups.map((g) => /* @__PURE__ */ jsxDEV("div", {
|
|
169
|
-
className: "px-4 py-3",
|
|
170
|
-
children: [
|
|
171
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
172
|
-
className: "text-xs font-semibold tracking-widest uppercase text-form-label",
|
|
173
|
-
children: g.name
|
|
174
|
-
}, undefined, false, undefined, this),
|
|
175
|
-
/* @__PURE__ */ jsxDEV("ul", {
|
|
176
|
-
className: "mt-2 flex flex-wrap gap-2",
|
|
177
|
-
children: g.items.map((it) => /* @__PURE__ */ jsxDEV("li", {
|
|
178
|
-
children: [
|
|
179
|
-
/* @__PURE__ */ jsxDEV("a", {
|
|
180
|
-
href: it.href,
|
|
181
|
-
className: "bg-secondary text-secondary-foreground inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium hover:underline",
|
|
182
|
-
children: [
|
|
183
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
184
|
-
children: it.icon ?? "•"
|
|
185
|
-
}, undefined, false, undefined, this),
|
|
186
|
-
" ",
|
|
187
|
-
it.label
|
|
188
|
-
]
|
|
189
|
-
}, undefined, true, undefined, this),
|
|
190
|
-
(it.children?.length ?? 0) > 0 && /* @__PURE__ */ jsxDEV("span", {
|
|
191
|
-
className: "text-text-muted ml-1 text-xs",
|
|
192
|
-
children: [
|
|
193
|
-
"+",
|
|
194
|
-
it.children?.length ?? 0,
|
|
195
|
-
" child"
|
|
196
|
-
]
|
|
197
|
-
}, undefined, true, undefined, this)
|
|
198
|
-
]
|
|
199
|
-
}, it.id, true, undefined, this))
|
|
200
|
-
}, undefined, false, undefined, this)
|
|
201
|
-
]
|
|
202
|
-
}, g.name, true, undefined, this)),
|
|
203
|
-
groups.length === 0 && /* @__PURE__ */ jsxDEV("p", {
|
|
204
|
-
className: "text-text-muted px-4 py-6 text-center text-sm",
|
|
205
|
-
children: "Belum ada resource terdaftar — pastikan rack() sudah dipanggil sebelum dashboard render."
|
|
206
|
-
}, undefined, false, undefined, this)
|
|
207
|
-
]
|
|
208
|
-
}, undefined, true, undefined, this)
|
|
209
|
-
]
|
|
210
|
-
}, undefined, true, undefined, this),
|
|
211
|
-
/* @__PURE__ */ jsxDEV("footer", {
|
|
212
|
-
className: "border-border text-text-muted mt-6 border-t pt-3 text-center text-xs tracking-widest uppercase",
|
|
213
|
-
children: "Printed by elysia-rack — ERP blueprint"
|
|
214
|
-
}, undefined, false, undefined, this)
|
|
215
|
-
]
|
|
216
|
-
}, undefined, true, 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,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
|
-
};
|