diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c2e0b68f..ed6e0b48 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,8 +14,10 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", "@vercel/analytics": "^1.2.2", @@ -39,7 +41,8 @@ "sharp": "^0.33.5", "simple-icons": "^13.5.0", "sonner": "^1.5.0", - "tailwind-merge": "^2.3.0" + "tailwind-merge": "^2.3.0", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^22", @@ -1028,6 +1031,12 @@ "node": ">=14" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", @@ -1602,6 +1611,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.2.tgz", + "integrity": "sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", @@ -1643,6 +1695,35 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz", + "integrity": "sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tabs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", @@ -7550,6 +7631,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 6bd130d2..56807210 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,8 +24,10 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", "@vercel/analytics": "^1.2.2", @@ -49,7 +51,8 @@ "sharp": "^0.33.5", "simple-icons": "^13.5.0", "sonner": "^1.5.0", - "tailwind-merge": "^2.3.0" + "tailwind-merge": "^2.3.0", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^22", diff --git a/frontend/src/app/json-editor/page.tsx b/frontend/src/app/json-editor/page.tsx new file mode 100644 index 00000000..24b8f452 --- /dev/null +++ b/frontend/src/app/json-editor/page.tsx @@ -0,0 +1,490 @@ +"use client"; + +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { fetchCategories } from "@/lib/data"; +import { Category } from "@/lib/types"; +import { PlusCircle, Trash2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { z } from "zod"; + +const scriptSchema = z.object({ + name: z.string().min(1), + slug: z.string().min(1), + categories: z.array(z.number()), + date_created: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), + type: z.enum(["vm", "ct", "misc"]), + updateable: z.boolean(), + privileged: z.boolean(), + interface_port: z.number().nullable(), + documentation: z.string().nullable(), + website: z.string().url().nullable(), + logo: z.string().url().nullable(), + description: z.string().min(1), + install_methods: z.array( + z.object({ + type: z.enum(["default", "alpine"]), + script: z.string().min(1), + resources: z.object({ + cpu: z.number().nullable(), + ram: z.number().nullable(), + hdd: z.number().nullable(), + os: z.string().nullable(), + version: z.number().nullable(), + }), + }), + ), + default_credentials: z.object({ + username: z.string().nullable(), + password: z.string().nullable(), + }), + notes: z.array( + z.object({ + text: z.string().min(1), + type: z.string().min(1), + }), + ), +}); + +type Script = z.infer; + +export default function JSONGenerator() { + const [script, setScript] = useState