From b7dc24a235eb04a48a46898c734897a6545329a2 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 29 Dec 2025 20:28:11 +0100 Subject: [PATCH] Deploy application dialog --- package-lock.json | 111 +++++++++++ package.json | 2 + src/app/applications/page.tsx | 11 +- src/components/dialogs/DeployApplication.tsx | 106 +++++++++++ src/components/ui/combobox.tsx | 82 +++++++++ src/components/ui/command.tsx | 184 +++++++++++++++++++ src/components/ui/popover.tsx | 48 +++++ 7 files changed, 540 insertions(+), 4 deletions(-) create mode 100644 src/components/dialogs/DeployApplication.tsx create mode 100644 src/components/ui/combobox.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/popover.tsx diff --git a/package-lock.json b/package-lock.json index a1f74dc..7b5a641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", @@ -20,6 +21,7 @@ "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "dockerode": "^4.0.9", "http-status-codes": "^2.3.0", "iron-session": "^8.0.4", @@ -1322,6 +1324,99 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "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-popover/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "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-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -2548,6 +2643,22 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/package.json b/package.json index 05246f9..bd67553 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", @@ -20,6 +21,7 @@ "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "dockerode": "^4.0.9", "http-status-codes": "^2.3.0", "iron-session": "^8.0.4", diff --git a/src/app/applications/page.tsx b/src/app/applications/page.tsx index 9ee1ebc..1edffb2 100644 --- a/src/app/applications/page.tsx +++ b/src/app/applications/page.tsx @@ -6,6 +6,7 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Play, Square, RotateCcw, MoreVertical, Plus } from "lucide-react"; import { Progress } from "@/components/ui/progress"; +import DeployApplication from "@/components/dialogs/DeployApplication"; const applications = [ { @@ -97,10 +98,12 @@ export default function ApplicationsPage() { - + + +
diff --git a/src/components/dialogs/DeployApplication.tsx b/src/components/dialogs/DeployApplication.tsx new file mode 100644 index 0000000..98ce304 --- /dev/null +++ b/src/components/dialogs/DeployApplication.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { useState, ReactNode, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "../ui/textarea"; +import { + authNode, + getLiveNodes, + getNodes, + setNodes as updateNodes, +} from "@/core/node"; +import { Combobox } from "../ui/combobox"; +import { Node, NodeLiveData } from "@/lib/types"; + +export default function DeployApplication({ + children, +}: { + children: ReactNode; +}) { + const [nodeList, setNodeList] = useState<(NodeLiveData & Node)[]>([]); + const [name, setName] = useState(""); + const [repo, setRepo] = useState(""); + const [node, setNode] = useState(""); + + useEffect(() => { + const fetchNodes = async () => { + setNodeList(await getLiveNodes()); + }; + fetchNodes(); + }, []); + + const deploy = () => {}; + + return ( + + {children} + + + + Deploy Application + + +
+
+ + setName(e.target.value)} + placeholder="My app" + /> +
+ +
+ + setRepo(e.target.value)} + placeholder="https://github.com/me/myrepo.git" + /> +
+ +
+ + node.liveData) + .map((node) => ({ label: node.name, value: node.node_id }))} + placeholder="Select Node..." + listPlaceholder="No nodes found :(" + /> +
+
+ + + + + + + + + +
+
+ ); +} diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx new file mode 100644 index 0000000..6c6c953 --- /dev/null +++ b/src/components/ui/combobox.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { Check, ChevronsUpDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { useState } from "react"; + +export type ComboData = { value: string; label: string }[]; + +export function Combobox({ + data, + value, + setValue, + placeholder, + listPlaceholder, +}: { + data: ComboData; + value: string; + setValue: (v: string) => void; + placeholder?: string; + listPlaceholder?: string; +}) { + const [open, setOpen] = useState(false); + + return ( + + + + + + + + + {listPlaceholder} + + {data.map((itm) => ( + { + setValue(currentValue === value ? "" : currentValue); + setOpen(false); + }} + > + {itm.label} + + + ))} + + + + + + ); +} diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..8cb4ca7 --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,184 @@ +"use client" + +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" +import { SearchIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + className, + showCloseButton = true, + ...props +}: React.ComponentProps & { + title?: string + description?: string + className?: string + showCloseButton?: boolean +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..01e468b --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }