forge-admin 0.0.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 +73 -0
- package/app.db +0 -0
- package/components.json +20 -0
- package/dist/assets/index-BPVmexx_.css +1 -0
- package/dist/assets/index-BtNewH3n.js +258 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.html +27 -0
- package/dist/placeholder.svg +1 -0
- package/dist/robots.txt +14 -0
- package/eslint.config.js +26 -0
- package/index.html +26 -0
- package/package.json +107 -0
- package/postcss.config.js +6 -0
- package/public/favicon.ico +0 -0
- package/public/placeholder.svg +1 -0
- package/public/robots.txt +14 -0
- package/src/App.css +42 -0
- package/src/App.tsx +32 -0
- package/src/admin/convertSchema.ts +83 -0
- package/src/admin/factory.ts +12 -0
- package/src/admin/introspecter.ts +6 -0
- package/src/admin/router.ts +38 -0
- package/src/admin/schema.ts +17 -0
- package/src/admin/sqlite.ts +73 -0
- package/src/admin/types.ts +35 -0
- package/src/components/AdminLayout.tsx +19 -0
- package/src/components/AdminSidebar.tsx +102 -0
- package/src/components/DataTable.tsx +166 -0
- package/src/components/ModelForm.tsx +221 -0
- package/src/components/NavLink.tsx +28 -0
- package/src/components/StatCard.tsx +32 -0
- package/src/components/ui/accordion.tsx +52 -0
- package/src/components/ui/alert-dialog.tsx +104 -0
- package/src/components/ui/alert.tsx +43 -0
- package/src/components/ui/aspect-ratio.tsx +5 -0
- package/src/components/ui/avatar.tsx +38 -0
- package/src/components/ui/badge.tsx +29 -0
- package/src/components/ui/breadcrumb.tsx +90 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/calendar.tsx +54 -0
- package/src/components/ui/card.tsx +43 -0
- package/src/components/ui/carousel.tsx +224 -0
- package/src/components/ui/chart.tsx +303 -0
- package/src/components/ui/checkbox.tsx +26 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.tsx +132 -0
- package/src/components/ui/context-menu.tsx +178 -0
- package/src/components/ui/dialog.tsx +95 -0
- package/src/components/ui/drawer.tsx +87 -0
- package/src/components/ui/dropdown-menu.tsx +179 -0
- package/src/components/ui/form.tsx +129 -0
- package/src/components/ui/hover-card.tsx +27 -0
- package/src/components/ui/input-otp.tsx +61 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +17 -0
- package/src/components/ui/menubar.tsx +207 -0
- package/src/components/ui/navigation-menu.tsx +120 -0
- package/src/components/ui/pagination.tsx +81 -0
- package/src/components/ui/popover.tsx +29 -0
- package/src/components/ui/progress.tsx +23 -0
- package/src/components/ui/radio-group.tsx +36 -0
- package/src/components/ui/resizable.tsx +37 -0
- package/src/components/ui/scroll-area.tsx +38 -0
- package/src/components/ui/select.tsx +143 -0
- package/src/components/ui/separator.tsx +20 -0
- package/src/components/ui/sheet.tsx +107 -0
- package/src/components/ui/sidebar.tsx +637 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +23 -0
- package/src/components/ui/sonner.tsx +27 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +72 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.tsx +21 -0
- package/src/components/ui/toast.tsx +111 -0
- package/src/components/ui/toaster.tsx +24 -0
- package/src/components/ui/toggle-group.tsx +49 -0
- package/src/components/ui/toggle.tsx +37 -0
- package/src/components/ui/tooltip.tsx +28 -0
- package/src/components/ui/use-toast.ts +3 -0
- package/src/config/define.ts +6 -0
- package/src/config/index.ts +0 -0
- package/src/config/load.ts +45 -0
- package/src/config/types.ts +5 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-toast.ts +186 -0
- package/src/index.css +142 -0
- package/src/lib/models.ts +138 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +5 -0
- package/src/orm/cli/makemigrations.ts +63 -0
- package/src/orm/cli/migrate.ts +127 -0
- package/src/orm/cli.ts +30 -0
- package/src/orm/core/base-model.ts +6 -0
- package/src/orm/core/manager.ts +27 -0
- package/src/orm/core/query-builder.ts +74 -0
- package/src/orm/db/connection.ts +0 -0
- package/src/orm/db/sql-types.ts +72 -0
- package/src/orm/db/sqlite.ts +4 -0
- package/src/orm/decorators/field.ts +80 -0
- package/src/orm/decorators/model.ts +36 -0
- package/src/orm/decorators/relations.ts +0 -0
- package/src/orm/metadata/field-metadata.ts +0 -0
- package/src/orm/metadata/field-types.ts +12 -0
- package/src/orm/metadata/get-meta.ts +9 -0
- package/src/orm/metadata/index.ts +15 -0
- package/src/orm/metadata/keys.ts +2 -0
- package/src/orm/metadata/model-registry.ts +53 -0
- package/src/orm/metadata/modifiers.ts +26 -0
- package/src/orm/metadata/types.ts +45 -0
- package/src/orm/migration-engine/diff.ts +243 -0
- package/src/orm/migration-engine/operations.ts +186 -0
- package/src/orm/schema/build.ts +138 -0
- package/src/orm/schema/state.ts +23 -0
- package/src/orm/schema/writeMigrations.ts +21 -0
- package/src/orm/syncdb.ts +25 -0
- package/src/pages/Dashboard.tsx +127 -0
- package/src/pages/Index.tsx +18 -0
- package/src/pages/ModelPage.tsx +177 -0
- package/src/pages/NotFound.tsx +24 -0
- package/src/pages/SchemaEditor.tsx +170 -0
- package/src/pages/Settings.tsx +166 -0
- package/src/server.ts +69 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +112 -0
- package/tailwind.config.ts +114 -0
- package/tsconfig.app.json +30 -0
- package/tsconfig.json +16 -0
- package/tsconfig.node.json +22 -0
- package/vite.config.js +23 -0
- package/vite.config.ts +18 -0
package/dist/favicon.ico
ADDED
|
Binary file
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<!-- TODO: Set the document title to the name of your application -->
|
|
7
|
+
<title>Lovable App</title>
|
|
8
|
+
<meta name="description" content="Lovable Generated Project" />
|
|
9
|
+
<meta name="author" content="Lovable" />
|
|
10
|
+
|
|
11
|
+
<!-- TODO: Update og:title to match your application name -->
|
|
12
|
+
<meta property="og:title" content="Lovable App" />
|
|
13
|
+
<meta property="og:description" content="Lovable Generated Project" />
|
|
14
|
+
<meta property="og:type" content="website" />
|
|
15
|
+
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
|
|
16
|
+
|
|
17
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
18
|
+
<meta name="twitter:site" content="@Lovable" />
|
|
19
|
+
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
|
|
20
|
+
<script type="module" crossorigin src="/assets/index-BtNewH3n.js"></script>
|
|
21
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BPVmexx_.css">
|
|
22
|
+
</head>
|
|
23
|
+
|
|
24
|
+
<body>
|
|
25
|
+
<div id="root"></div>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
package/dist/robots.txt
ADDED
package/eslint.config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
4
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{ ignores: ["dist"] },
|
|
9
|
+
{
|
|
10
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
11
|
+
files: ["**/*.{ts,tsx}"],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
ecmaVersion: 2020,
|
|
14
|
+
globals: globals.browser,
|
|
15
|
+
},
|
|
16
|
+
plugins: {
|
|
17
|
+
"react-hooks": reactHooks,
|
|
18
|
+
"react-refresh": reactRefresh,
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
...reactHooks.configs.recommended.rules,
|
|
22
|
+
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
|
23
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
);
|
package/index.html
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<!-- TODO: Set the document title to the name of your application -->
|
|
7
|
+
<title>Lovable App</title>
|
|
8
|
+
<meta name="description" content="Lovable Generated Project" />
|
|
9
|
+
<meta name="author" content="Lovable" />
|
|
10
|
+
|
|
11
|
+
<!-- TODO: Update og:title to match your application name -->
|
|
12
|
+
<meta property="og:title" content="Lovable App" />
|
|
13
|
+
<meta property="og:description" content="Lovable Generated Project" />
|
|
14
|
+
<meta property="og:type" content="website" />
|
|
15
|
+
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
|
|
16
|
+
|
|
17
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
18
|
+
<meta name="twitter:site" content="@Lovable" />
|
|
19
|
+
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
|
|
20
|
+
</head>
|
|
21
|
+
|
|
22
|
+
<body>
|
|
23
|
+
<div id="root"></div>
|
|
24
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "forge-admin",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "**URL**: https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID",
|
|
6
|
+
"homepage": "https://github.com/joeldsouza28/tauri-builder-suite#readme",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/joeldsouza28/tauri-builder-suite/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/joeldsouza28/tauri-builder-suite.git"
|
|
13
|
+
},
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"author": "",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "eslint.config.js",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"backend": "tsx src/main.ts",
|
|
21
|
+
"build": "vite build",
|
|
22
|
+
"build:dev": "vite build --mode development",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"preview": "vite preview",
|
|
25
|
+
"orm": "tsx src/orm/cli.ts",
|
|
26
|
+
"server": "tsx src/server.ts"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@hookform/resolvers": "^3.10.0",
|
|
30
|
+
"@radix-ui/react-accordion": "^1.2.11",
|
|
31
|
+
"@radix-ui/react-alert-dialog": "^1.1.14",
|
|
32
|
+
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
|
33
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
34
|
+
"@radix-ui/react-checkbox": "^1.3.2",
|
|
35
|
+
"@radix-ui/react-collapsible": "^1.1.11",
|
|
36
|
+
"@radix-ui/react-context-menu": "^2.2.15",
|
|
37
|
+
"@radix-ui/react-dialog": "^1.1.14",
|
|
38
|
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
39
|
+
"@radix-ui/react-hover-card": "^1.1.14",
|
|
40
|
+
"@radix-ui/react-label": "^2.1.7",
|
|
41
|
+
"@radix-ui/react-menubar": "^1.1.15",
|
|
42
|
+
"@radix-ui/react-navigation-menu": "^1.2.13",
|
|
43
|
+
"@radix-ui/react-popover": "^1.1.14",
|
|
44
|
+
"@radix-ui/react-progress": "^1.1.7",
|
|
45
|
+
"@radix-ui/react-radio-group": "^1.3.7",
|
|
46
|
+
"@radix-ui/react-scroll-area": "^1.2.9",
|
|
47
|
+
"@radix-ui/react-select": "^2.2.5",
|
|
48
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
49
|
+
"@radix-ui/react-slider": "^1.3.5",
|
|
50
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
51
|
+
"@radix-ui/react-switch": "^1.2.5",
|
|
52
|
+
"@radix-ui/react-tabs": "^1.1.12",
|
|
53
|
+
"@radix-ui/react-toast": "^1.2.14",
|
|
54
|
+
"@radix-ui/react-toggle": "^1.1.9",
|
|
55
|
+
"@radix-ui/react-toggle-group": "^1.1.10",
|
|
56
|
+
"@radix-ui/react-tooltip": "^1.2.7",
|
|
57
|
+
"@tanstack/react-query": "^5.83.0",
|
|
58
|
+
"better-sqlite3": "^12.5.0",
|
|
59
|
+
"class-variance-authority": "^0.7.1",
|
|
60
|
+
"clsx": "^2.1.1",
|
|
61
|
+
"cmdk": "^1.1.1",
|
|
62
|
+
"date-fns": "^3.6.0",
|
|
63
|
+
"embla-carousel-react": "^8.6.0",
|
|
64
|
+
"express": "^5.2.1",
|
|
65
|
+
"input-otp": "^1.4.2",
|
|
66
|
+
"lucide-react": "^0.462.0",
|
|
67
|
+
"next-themes": "^0.3.0",
|
|
68
|
+
"react": "^18.3.1",
|
|
69
|
+
"react-day-picker": "^8.10.1",
|
|
70
|
+
"react-dom": "^18.3.1",
|
|
71
|
+
"react-hook-form": "^7.61.1",
|
|
72
|
+
"react-resizable-panels": "^2.1.9",
|
|
73
|
+
"react-router-dom": "^6.30.1",
|
|
74
|
+
"recharts": "^2.15.4",
|
|
75
|
+
"sonner": "^1.7.4",
|
|
76
|
+
"tailwind-merge": "^2.6.0",
|
|
77
|
+
"tailwindcss-animate": "^1.0.7",
|
|
78
|
+
"tsconfig-paths": "^4.2.0",
|
|
79
|
+
"tsx": "^4.21.0",
|
|
80
|
+
"vaul": "^0.9.9",
|
|
81
|
+
"zod": "^3.25.76"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@eslint/js": "^9.32.0",
|
|
85
|
+
"@tailwindcss/typography": "^0.5.16",
|
|
86
|
+
"@types/node": "^22.16.5",
|
|
87
|
+
"@types/react": "^18.3.23",
|
|
88
|
+
"@types/react-dom": "^18.3.7",
|
|
89
|
+
"@vitejs/plugin-react-swc": "^3.11.0",
|
|
90
|
+
"autoprefixer": "^10.4.21",
|
|
91
|
+
"eslint": "^9.32.0",
|
|
92
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
93
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
94
|
+
"globals": "^15.15.0",
|
|
95
|
+
"lovable-tagger": "^1.1.13",
|
|
96
|
+
"postcss": "^8.5.6",
|
|
97
|
+
"tailwindcss": "^3.4.17",
|
|
98
|
+
"typescript": "^5.8.3",
|
|
99
|
+
"typescript-eslint": "^8.38.0",
|
|
100
|
+
"vite": "^5.4.19"
|
|
101
|
+
},
|
|
102
|
+
"ts-node": {
|
|
103
|
+
"require": [
|
|
104
|
+
"tsconfig-paths/register"
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
package/src/App.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#root {
|
|
2
|
+
max-width: 1280px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo {
|
|
9
|
+
height: 6em;
|
|
10
|
+
padding: 1.5em;
|
|
11
|
+
will-change: filter;
|
|
12
|
+
transition: filter 300ms;
|
|
13
|
+
}
|
|
14
|
+
.logo:hover {
|
|
15
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
16
|
+
}
|
|
17
|
+
.logo.react:hover {
|
|
18
|
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes logo-spin {
|
|
22
|
+
from {
|
|
23
|
+
transform: rotate(0deg);
|
|
24
|
+
}
|
|
25
|
+
to {
|
|
26
|
+
transform: rotate(360deg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
31
|
+
a:nth-of-type(2) .logo {
|
|
32
|
+
animation: logo-spin infinite 20s linear;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.card {
|
|
37
|
+
padding: 2em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.read-the-docs {
|
|
41
|
+
color: #888;
|
|
42
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Toaster } from "@/components/ui/toaster";
|
|
2
|
+
import { Toaster as Sonner } from "@/components/ui/sonner";
|
|
3
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
6
|
+
import Index from "./pages/Index";
|
|
7
|
+
import ModelPage from "./pages/ModelPage";
|
|
8
|
+
import SchemaEditor from "./pages/SchemaEditor";
|
|
9
|
+
import Settings from "./pages/Settings";
|
|
10
|
+
import NotFound from "./pages/NotFound";
|
|
11
|
+
|
|
12
|
+
const queryClient = new QueryClient();
|
|
13
|
+
|
|
14
|
+
const App = () => (
|
|
15
|
+
<QueryClientProvider client={queryClient}>
|
|
16
|
+
<TooltipProvider>
|
|
17
|
+
<Toaster />
|
|
18
|
+
<Sonner />
|
|
19
|
+
<BrowserRouter>
|
|
20
|
+
<Routes>
|
|
21
|
+
<Route path="/" element={<Index />} />
|
|
22
|
+
<Route path="/models/:modelName" element={<ModelPage />} />
|
|
23
|
+
<Route path="/schema" element={<SchemaEditor />} />
|
|
24
|
+
<Route path="/settings" element={<Settings />} />
|
|
25
|
+
<Route path="*" element={<NotFound />} />
|
|
26
|
+
</Routes>
|
|
27
|
+
</BrowserRouter>
|
|
28
|
+
</TooltipProvider>
|
|
29
|
+
</QueryClientProvider>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export default App;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
type OrmField = {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
options?: any;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type OrmModel = {
|
|
8
|
+
tableName: string;
|
|
9
|
+
fields: Record<string, OrmField>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type OrmSchema = Record<string, OrmModel>;
|
|
13
|
+
|
|
14
|
+
export function ormSchemaToAdminConfig(schema: OrmSchema) {
|
|
15
|
+
return Object.values(schema).map(model => {
|
|
16
|
+
const fields = Object.values(model.fields);
|
|
17
|
+
return {
|
|
18
|
+
name: model.tableName,
|
|
19
|
+
displayName: toTitleCase(model.tableName),
|
|
20
|
+
icon: "", // you can plug icons later
|
|
21
|
+
fields: fields.map(f => ormFieldToAdminField(f)),
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ormFieldToAdminField(field: OrmField) {
|
|
27
|
+
const base = {
|
|
28
|
+
name: field.name,
|
|
29
|
+
label: toTitleCase(field.name),
|
|
30
|
+
} as any;
|
|
31
|
+
|
|
32
|
+
switch (field.type) {
|
|
33
|
+
case "primary":
|
|
34
|
+
return {
|
|
35
|
+
...base,
|
|
36
|
+
type: "number",
|
|
37
|
+
required: true,
|
|
38
|
+
primaryKey: true
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
case "string":
|
|
42
|
+
return {
|
|
43
|
+
...base,
|
|
44
|
+
type: "string",
|
|
45
|
+
required: true,
|
|
46
|
+
maxLength: field.options?.maxLength,
|
|
47
|
+
primaryKey: false
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
case "email":
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
type: "email",
|
|
54
|
+
required: true,
|
|
55
|
+
primaryKey: false
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
case "datetime":
|
|
59
|
+
let readonly = false;
|
|
60
|
+
if(field.options?.autoNowAdd){
|
|
61
|
+
readonly = true
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
type: "datetime",
|
|
66
|
+
primaryKey: false,
|
|
67
|
+
readonly
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
type: "string",
|
|
74
|
+
primaryKey: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function toTitleCase(str: string) {
|
|
80
|
+
return str
|
|
81
|
+
.replace(/_/g, " ")
|
|
82
|
+
.replace(/^\w/, c => c.toUpperCase());
|
|
83
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// admin/schema/factory.ts
|
|
2
|
+
import { SQLiteIntrospector } from "./sqlite";
|
|
3
|
+
|
|
4
|
+
export function createSchemaIntrospector() {
|
|
5
|
+
const dbType = process.env.DB_TYPE;
|
|
6
|
+
|
|
7
|
+
switch (dbType) {
|
|
8
|
+
case "sqlite":
|
|
9
|
+
default:
|
|
10
|
+
return new SQLiteIntrospector();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ormSchemaToAdminConfig } from "./convertSchema";
|
|
2
|
+
import { getModelByName } from "@/orm/metadata/model-registry";
|
|
3
|
+
import { createSchemaIntrospector } from "./factory";
|
|
4
|
+
import { loadOrmConfig, loadModels } from "@/config/load";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const config = await loadOrmConfig();
|
|
8
|
+
await loadModels(config.modelsPath);
|
|
9
|
+
|
|
10
|
+
const introspector = createSchemaIntrospector();
|
|
11
|
+
|
|
12
|
+
export const adminRouter = {
|
|
13
|
+
schema() {
|
|
14
|
+
let schema = introspector.getSchema();
|
|
15
|
+
const adminConfig = ormSchemaToAdminConfig(schema);
|
|
16
|
+
return adminConfig
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
list(model: string) {
|
|
20
|
+
let modelClass = getModelByName(model);
|
|
21
|
+
return modelClass.objects.all();
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
create(model: string, data: any) {
|
|
25
|
+
let modelClass = getModelByName(model);
|
|
26
|
+
modelClass.objects.create(data);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
update(model: string, id: number, data: any) {
|
|
30
|
+
let modelClass = getModelByName(model);
|
|
31
|
+
return modelClass.objects.update(data, id);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
delete(model: string, id: number) {
|
|
35
|
+
let modelClass = getModelByName(model);
|
|
36
|
+
return modelClass.objects.delete(id);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export function getAdminSchema() {
|
|
5
|
+
const statePath = path.resolve(
|
|
6
|
+
process.cwd(),
|
|
7
|
+
"src/orm/migrations/_state.json"
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(statePath)) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"No migration state found. Run makemigrations first."
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
17
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// admin/schema/sqlite.ts
|
|
2
|
+
import { db } from "../orm/db/sqlite";
|
|
3
|
+
import { SchemaIntrospector } from "./introspecter";
|
|
4
|
+
|
|
5
|
+
export class SQLiteIntrospector implements SchemaIntrospector {
|
|
6
|
+
getSchema() {
|
|
7
|
+
const tables = db
|
|
8
|
+
.prepare(`
|
|
9
|
+
SELECT name FROM sqlite_master
|
|
10
|
+
WHERE type='table'
|
|
11
|
+
AND name NOT LIKE 'sqlite_%'
|
|
12
|
+
AND name != 'orm_migrations'
|
|
13
|
+
`)
|
|
14
|
+
.all()
|
|
15
|
+
.map((r: any) => r.name);
|
|
16
|
+
|
|
17
|
+
const schema: any = {};
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
for (const table of tables) {
|
|
22
|
+
schema[table] = {
|
|
23
|
+
tableName: table,
|
|
24
|
+
fields: {},
|
|
25
|
+
};
|
|
26
|
+
const columns = db
|
|
27
|
+
.prepare(`PRAGMA table_info("${table}")`)
|
|
28
|
+
.all();
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
schema[table] = {
|
|
32
|
+
tableName: table,
|
|
33
|
+
fields: {},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (const col of columns) {
|
|
37
|
+
schema[table].fields[col.name] = {
|
|
38
|
+
name: col.name,
|
|
39
|
+
type: mapSQLiteType(col),
|
|
40
|
+
...(mapSQLiteOptions(col) && {
|
|
41
|
+
options: mapSQLiteOptions(col),
|
|
42
|
+
readonly: mapSQLiteOptions(col)?.autoNowAdd
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return schema;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function mapSQLiteType(col: any) {
|
|
54
|
+
if (col.pk === 1) return "primary";
|
|
55
|
+
const t = col.type.toLowerCase();
|
|
56
|
+
if (t.includes("date") || t.includes("time")) {
|
|
57
|
+
return "datetime";
|
|
58
|
+
}
|
|
59
|
+
return "string";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
function mapSQLiteOptions(col: any) {
|
|
64
|
+
const t = col.type.toLowerCase();
|
|
65
|
+
|
|
66
|
+
const options: any = {};
|
|
67
|
+
// SQLite can't know this — best guess
|
|
68
|
+
if (t.includes("date") || t.includes("time")) {
|
|
69
|
+
options.autoNowAdd = true; // unknown, default false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.keys(options).length ? options : undefined;
|
|
73
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type ModelType = {
|
|
2
|
+
name: string,
|
|
3
|
+
displayName: string,
|
|
4
|
+
icon: "", // you can plug icons later
|
|
5
|
+
fields: FieldMeta[],
|
|
6
|
+
}
|
|
7
|
+
export type FieldMeta = {
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
label: string;
|
|
11
|
+
required: boolean;
|
|
12
|
+
primaryKey: boolean,
|
|
13
|
+
readonly: boolean,
|
|
14
|
+
default: string;
|
|
15
|
+
options?: any;
|
|
16
|
+
maxLength?: number;
|
|
17
|
+
nullable: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// admin/schema/types.ts
|
|
23
|
+
export type AdminSchema = {
|
|
24
|
+
[tableName: string]: {
|
|
25
|
+
tableName: string;
|
|
26
|
+
fields: {
|
|
27
|
+
[fieldName: string]: {
|
|
28
|
+
name: string;
|
|
29
|
+
type: "string" | "number" | "boolean" | "date";
|
|
30
|
+
primary: boolean;
|
|
31
|
+
nullable: boolean;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { AdminSidebar } from './AdminSidebar';
|
|
3
|
+
import { ModelType } from '@/admin/types';
|
|
4
|
+
|
|
5
|
+
interface AdminLayoutProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
models: ModelType[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function AdminLayout({ children, models }: AdminLayoutProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex min-h-screen w-full bg-background">
|
|
13
|
+
<AdminSidebar models={models}/>
|
|
14
|
+
<main className="flex-1 overflow-auto">
|
|
15
|
+
{children}
|
|
16
|
+
</main>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|