From 3d4d94fff0a05cf2a69dcc9bb9ee9f404c5dbf8d Mon Sep 17 00:00:00 2001 From: Daryl Lim <5508348+daryllimyt@users.noreply.github.com> Date: Thu, 11 Apr 2024 18:18:33 -0700 Subject: [PATCH] feat(ui): Update settings credentials page --- .../src/app/settings/credentials/page.tsx | 140 +++++++--- frontend/src/app/settings/layout.tsx | 8 +- .../src/components/confirmation-dialog.tsx | 2 +- .../src/components/new-credential-dialog.tsx | 259 +++++++++++++----- 4 files changed, 309 insertions(+), 100 deletions(-) diff --git a/frontend/src/app/settings/credentials/page.tsx b/frontend/src/app/settings/credentials/page.tsx index 46e7336a..9dcdc725 100644 --- a/frontend/src/app/settings/credentials/page.tsx +++ b/frontend/src/app/settings/credentials/page.tsx @@ -1,15 +1,32 @@ "use client" import { useSession } from "@/providers/session" +import { Label } from "@radix-ui/react-label" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import { PlusCircle } from "lucide-react" +import { PlusCircle, Trash2Icon } from "lucide-react" import { Secret } from "@/types/schemas" import { deleteSecret, fetchAllSecrets } from "@/lib/secrets" import { Button } from "@/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" import { toast } from "@/components/ui/use-toast" +import { ConfirmationDialog } from "@/components/confirmation-dialog" import { CenteredSpinner } from "@/components/loading/spinner" import { NewCredentialsDialog, @@ -63,45 +80,106 @@ export default function CredentialsPage() { } return ( -
-
+
+

Credentials

+ + + + +
- - - - - +
- {secrets ? ( + {secrets?.length ? ( secrets?.map((secret, idx) => ( -
- - - -
+ )) ) : ( - + )}
) } + +function SecretsTable({ + secret, + deleteFn, +}: { + secret: Secret + deleteFn: (secret: Secret) => void +}) { + return ( + + +
+
+ {secret.name} + + {secret.description || "No description."} + +
+ deleteFn(secret)} + > + + +
+
+ + + + + Key + Value + + + + {secret.keys.map(({ key }, idx) => ( + + + + {key} + + + + + + + ))} + +
+
+
+ ) +} diff --git a/frontend/src/app/settings/layout.tsx b/frontend/src/app/settings/layout.tsx index 3d3b0084..4853baf0 100644 --- a/frontend/src/app/settings/layout.tsx +++ b/frontend/src/app/settings/layout.tsx @@ -30,9 +30,9 @@ export default async function SettingsLayout({ data: { session }, } = await supabase.auth.getSession() return ( - <> +
-
+

Settings

@@ -44,9 +44,9 @@ export default async function SettingsLayout({

-
{children}
+
{children}
- +
) } diff --git a/frontend/src/components/confirmation-dialog.tsx b/frontend/src/components/confirmation-dialog.tsx index 46a57475..b64c712e 100644 --- a/frontend/src/components/confirmation-dialog.tsx +++ b/frontend/src/components/confirmation-dialog.tsx @@ -35,7 +35,7 @@ export function ConfirmationDialog({ Cancel - Continue + Confirm diff --git a/frontend/src/components/new-credential-dialog.tsx b/frontend/src/components/new-credential-dialog.tsx index c089cccd..429fd5ee 100644 --- a/frontend/src/components/new-credential-dialog.tsx +++ b/frontend/src/components/new-credential-dialog.tsx @@ -5,7 +5,10 @@ import { useSession } from "@/providers/session" import { zodResolver } from "@hookform/resolvers/zod" import { DialogProps } from "@radix-ui/react-dialog" import { useMutation, useQueryClient } from "@tanstack/react-query" -import { useForm } from "react-hook-form" +import { KeyRoundIcon, PlusCircle, Trash2Icon } from "lucide-react" +import { ArrayPath, FieldPath, useFieldArray, useForm } from "react-hook-form" +import SyntaxHighlighter from "react-syntax-highlighter" +import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs" import { Secret, secretSchema } from "@/types/schemas" import { createSecret } from "@/lib/secrets" @@ -14,7 +17,6 @@ import { Dialog, DialogClose, DialogContent, - DialogDescription, DialogFooter, DialogHeader, DialogTitle, @@ -23,8 +25,10 @@ import { import { Form, FormControl, + FormDescription, FormField, FormItem, + FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" @@ -44,8 +48,7 @@ export function NewCredentialsDialog({ const queryClient = useQueryClient() const { mutate } = useMutation({ - mutationFn: (secret: Secret) => - createSecret(session, secret.name, secret.value), + mutationFn: (secret: Secret) => createSecret(session, secret), onSuccess: (data, variables, context) => { toast({ title: "Added new secret", @@ -62,75 +65,203 @@ export function NewCredentialsDialog({ }, }) - const form = useForm({ + const methods = useForm({ resolver: zodResolver(secretSchema), + defaultValues: { + name: "", + type: "custom", + keys: [{ key: "", value: "" }], + }, }) + const { getValues, control, register, watch, trigger } = methods - const onSubmit = (values: Secret) => { - console.log("Submitting new secret", values) + const onSubmit = async () => { + const validated = await trigger() + if (!validated) { + console.error("Form validation failed") + return + } + const values = getValues() mutate(values) } + const inputKey = "keys" + const typedKey = inputKey as FieldPath + const { fields, append, remove } = useFieldArray({ + control, + name: inputKey as ArrayPath, + }) return ( - {children} - - - New credential - - Create a new secret. You can refer to this as{" "} - {"{{ SECRETS.}}"} - - -
- - ( - - - - - - - )} - /> - ( - - - - - - - )} - /> - - - - - - - -
+
+ + {children} + + + Create New Secret +
+ + Create a secret that can have multiple key-value credential + pairs. You can reference these secrets in your workflows + through{" "} + + {"{{ SECRETS..}}"} + + {". "}For example, if I have a secret called with key + 'GH_ACCESS_TOKEN' I can reference this as{" "} + + {"{{ SECRETS.my_github_secret.GH_ACCESS_TOKEN }}"} + + {". "} + +
+
+
+ ( + + Name + + + + + + )} + /> + ( + + Description + + A description for this secret. + + + + + + + )} + /> + ( + + Keys +
+ {fields.map((field, index) => { + return ( +
+ + + + + + + + +
+ ) + })} + +
+ +
+ )} + /> + + + + + +
+
+
+
) } + +function InlineHLCode({ children }: { children: string }) { + return ( + + {children} + + ) +} + export const NewCredentialsDialogTrigger = DialogTrigger