Canvas: Rework Deployment/Gateways tab
Change-Id: I938262b9a6ba2af060531e7dcdf91ddd66721385
diff --git a/apps/canvas/front/package.json b/apps/canvas/front/package.json
index 55116cb..b25d17b 100644
--- a/apps/canvas/front/package.json
+++ b/apps/canvas/front/package.json
@@ -6,7 +6,7 @@
"scripts": {
"dev": "vite --port=$DODO_PORT_WEB --host",
"build": "vite build",
- "format": "prettier --write src/**/*.{js,ts,jsx,tsx}",
+ "format": "prettier --write src/**/*.{js,ts,jsx,tsx} --list-different",
"format-check": "prettier --check src/**/*.{js,ts,jsx,tsx}",
"lint": "eslint .",
"preview": "vite preview --port=$DODO_PORT_WEB --host",
diff --git a/apps/canvas/front/src/Canvas.tsx b/apps/canvas/front/src/Canvas.tsx
index 3bdf00b..ae7e137 100644
--- a/apps/canvas/front/src/Canvas.tsx
+++ b/apps/canvas/front/src/Canvas.tsx
@@ -2,7 +2,7 @@
import { Canvas } from "@/components/canvas";
import { Details } from "@/components/details";
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./components/ui/resizable";
-import { Tools } from "./Tootls";
+import { Tools } from "./Tools";
import { useStateStore } from "./lib/state";
export function CanvasBuilder() {
diff --git a/apps/canvas/front/src/Deployment.tsx b/apps/canvas/front/src/Deployment.tsx
deleted file mode 100644
index 62eccf6..0000000
--- a/apps/canvas/front/src/Deployment.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useNodes } from "@xyflow/react";
-import { AppNode, nodeLabel, ServiceNode } from "./lib/state";
-import { useMemo } from "react";
-import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "./components/ui/table";
-
-function ingress(nodes: AppNode[]) {
- const nm = new Map(nodes.map((n) => [n.id, n]));
- return nodes
- .filter((n) => n.type === "gateway-https")
- .map((i) => {
- console.log(i.data);
- if (!i.data || !i.data.network || !i.data.subdomain) {
- return null;
- }
- if (!i.data.https || !i.data.https.serviceId || !i.data.https.portId) {
- return null;
- }
- console.log("1231");
- const svc = nm.get(i.data.https.serviceId)! as ServiceNode;
- const port = svc.data.ports.find((p) => p.id === i.data.https!.portId)!;
- console.log({ svc, port });
- return {
- id: `${i.id} - ${port.id}`,
- service: svc,
- port: port,
- endpoint: `https://${i.data.subdomain}.${i.data.network}`,
- };
- })
- .filter((i) => i != null);
-}
-
-export function Deployment() {
- const nodes = useNodes<AppNode>();
- const ing = useMemo(() => ingress(nodes), [nodes]);
- return (
- <>
- <Table>
- <TableCaption>HTTPS Gateways</TableCaption>
- <TableHeader>
- <TableRow>
- <TableHead>Service</TableHead>
- <TableHead>Port</TableHead>
- <TableHead>Endpoint</TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {ing.map((i) => (
- <TableRow>
- <TableCell>{nodeLabel(i.service)}</TableCell>
- <TableCell>{i.port.name}</TableCell>
- <TableCell>
- <a href={i.endpoint} target="_blank">
- {i.endpoint}
- </a>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </>
- );
-}
diff --git a/apps/canvas/front/src/Gateways.tsx b/apps/canvas/front/src/Gateways.tsx
new file mode 100644
index 0000000..a1fce28
--- /dev/null
+++ b/apps/canvas/front/src/Gateways.tsx
@@ -0,0 +1,93 @@
+import { z } from "zod";
+import { accessSchema, useEnv } from "./lib/state";
+import { Copy, Globe, Terminal, Network, Database, Check } from "lucide-react";
+import { Button } from "./components/ui/button";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/ui/tooltip";
+import { useCallback, useState } from "react";
+
+export function Gateways() {
+ const env = useEnv();
+ const groupedAccess = env.access.reduce((acc, curr) => {
+ if (!acc.has(curr.name)) {
+ acc.set(curr.name, []);
+ }
+ acc.get(curr.name)!.push(curr);
+ return acc;
+ }, new Map<string, typeof env.access>());
+ return (
+ <ul>
+ {Array.from(groupedAccess.entries()).map(([name, access]) => (
+ <li key={name}>
+ {access.map((a) => (
+ <Gateway g={a} />
+ ))}
+ </li>
+ ))}
+ </ul>
+ );
+}
+
+function Gateway({ g }: { g: z.infer<typeof accessSchema> }) {
+ const [hidden, content] = (() => {
+ switch (g.type) {
+ case "https":
+ return [g.address, g.address];
+ case "ssh":
+ case "tcp":
+ case "udp":
+ return [`${g.host}:${g.port}`, `${g.host}:${g.port}`];
+ case "postgresql":
+ return [
+ `postgresql://${g.username}:*****@${g.host}:${g.port}/${g.database}`,
+ `postgresql://${g.username}:${g.password}@${g.host}:${g.port}/${g.database}`,
+ ];
+ case "mongodb":
+ return [
+ `mongodb://${g.username}:*****@${g.host}:${g.port}/${g.database}`,
+ `mongodb://${g.username}:${g.password}@${g.host}:${g.port}/${g.database}`,
+ ];
+ }
+ })();
+ const [clicked, setClicked] = useState(false);
+ const [open, setOpen] = useState(false);
+ const copy = useCallback(() => {
+ navigator.clipboard.writeText(content);
+ setClicked(true);
+ setOpen(true);
+ setTimeout(() => {
+ setClicked(false);
+ setOpen(false);
+ }, 1000);
+ }, [content, setClicked, setOpen]);
+ return (
+ <TooltipProvider>
+ <Tooltip delayDuration={100} open={open} onOpenChange={setOpen}>
+ <TooltipTrigger asChild>
+ <Button variant="ghost" onClick={copy}>
+ <AccessType type={g.type} className="w-4 h-4" />
+ <div className="hover:bg-gray-200 p-x-1">{hidden}</div>
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent side="right" className="!bg-transparent cursor-pointer !p-0" sideOffset={1}>
+ {!clicked && <Copy className="w-4 h-4 !bg-transparent" color="black" />}
+ {clicked && <Check className="w-4 h-4 !bg-transparent" color="black" />}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ );
+}
+
+function AccessType({ type, className }: { type: z.infer<typeof accessSchema>["type"]; className?: string }) {
+ switch (type) {
+ case "https":
+ return <Globe className={className} />;
+ case "ssh":
+ return <Terminal className={className} />;
+ case "tcp":
+ case "udp":
+ return <Network className={className} />;
+ case "postgresql":
+ case "mongodb":
+ return <Database className={className} />;
+ }
+}
diff --git a/apps/canvas/front/src/Tootls.tsx b/apps/canvas/front/src/Tools.tsx
similarity index 84%
rename from apps/canvas/front/src/Tootls.tsx
rename to apps/canvas/front/src/Tools.tsx
index b60e55c..c185d45 100644
--- a/apps/canvas/front/src/Tootls.tsx
+++ b/apps/canvas/front/src/Tools.tsx
@@ -1,6 +1,6 @@
import { Badge } from "./components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs";
-import { Deployment } from "./Deployment";
+import { Gateways } from "./Gateways";
import { useEnv, useMessages } from "./lib/state";
import { Messages } from "./Messages";
@@ -14,15 +14,15 @@
<div>Messages</div>
<Badge>{messages.length}</Badge>
</TabsTrigger>
- <TabsTrigger value="deployment">Deployment</TabsTrigger>
+ <TabsTrigger value="gateways">Gateways</TabsTrigger>
<TabsTrigger value="deployKeys">Deploy keys</TabsTrigger>
</TabsList>
<div className="!overflow-y-auto p-1">
<TabsContent value="messages">
<Messages />
</TabsContent>
- <TabsContent value="deployment">
- <Deployment />
+ <TabsContent value="gateways">
+ <Gateways />
</TabsContent>
<TabsContent value="deployKeys">{env && <>{env.deployKey}</>}</TabsContent>
</div>
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index aae7600..2f714f7 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -346,6 +346,50 @@
onClick?: (state: AppState) => void;
};
+export const accessSchema = z.discriminatedUnion("type", [
+ z.object({
+ type: z.literal("https"),
+ name: z.string(),
+ address: z.string(),
+ }),
+ z.object({
+ type: z.literal("ssh"),
+ name: z.string(),
+ host: z.string(),
+ port: z.number(),
+ }),
+ z.object({
+ type: z.literal("tcp"),
+ name: z.string(),
+ host: z.string(),
+ port: z.number(),
+ }),
+ z.object({
+ type: z.literal("udp"),
+ name: z.string(),
+ host: z.string(),
+ port: z.number(),
+ }),
+ z.object({
+ type: z.literal("postgresql"),
+ name: z.string(),
+ host: z.string(),
+ port: z.number(),
+ database: z.string(),
+ username: z.string(),
+ password: z.string(),
+ }),
+ z.object({
+ type: z.literal("mongodb"),
+ name: z.string(),
+ host: z.string(),
+ port: z.number(),
+ database: z.string(),
+ username: z.string(),
+ password: z.string(),
+ }),
+]);
+
export const envSchema = z.object({
managerAddr: z.optional(z.string().min(1)),
deployKey: z.optional(z.nullable(z.string().min(1))),
@@ -365,6 +409,7 @@
id: z.string(),
username: z.string(),
}),
+ access: z.array(accessSchema),
});
export type Env = z.infer<typeof envSchema>;
@@ -381,6 +426,7 @@
id: "",
username: "",
},
+ access: [],
};
export type Project = {