diff --git a/frontend/src/components/util.tsx b/frontend/src/components/util.tsx
index 6786616d4..bd1aa8cc3 100644
--- a/frontend/src/components/util.tsx
+++ b/frontend/src/components/util.tsx
@@ -20,7 +20,7 @@ import {
DialogContent,
DialogFooter,
} from "@ui/dialog";
-import { toast } from "@ui/use-toast";
+import { toast, useToast } from "@ui/use-toast";
import { RESOURCE_TARGETS, cn } from "@lib/utils";
import {
useInvalidate,
@@ -434,3 +434,34 @@ export const UserDropdown = () => {
);
};
+
+export const CopyButton = ({ content }: { content: string | undefined }) => {
+ const { toast } = useToast();
+ const [copied, set] = useState(false);
+
+ useEffect(() => {
+ if (copied) {
+ toast({ title: `Copied "${content}"` });
+ const timeout = setTimeout(() => set(false), 3000);
+ return () => {
+ clearTimeout(timeout);
+ };
+ }
+ }, [content, copied, toast]);
+
+ return (
+
+ );
+};
diff --git a/frontend/src/pages/keys.tsx b/frontend/src/pages/keys.tsx
index 71371f7cd..24c561b77 100644
--- a/frontend/src/pages/keys.tsx
+++ b/frontend/src/pages/keys.tsx
@@ -1,19 +1,32 @@
-import { Page, Section } from "@components/layouts";
-import { ConfirmButton } from "@components/util";
+import { Page } from "@components/layouts";
+import { ConfirmButton, CopyButton } from "@components/util";
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
import { fmt_date } from "@lib/utils";
import {
- Card,
- CardContent,
- CardHeader,
- CardTitle,
-} from "@ui/card";
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@ui/dialog";
+import { Button } from "@ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@ui/card";
import { useToast } from "@ui/use-toast";
-import { Key, Trash } from "lucide-react";
+import { Trash, PlusCircle, Loader2, Check } from "lucide-react";
+import { useState } from "react";
+import { Input } from "@ui/input";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@ui/dropdown-menu";
export const Keys = () => {
return (
-
+ }>
);
@@ -22,29 +35,145 @@ export const Keys = () => {
export const ApiKeysList = () => {
const keys = useRead("ListApiKeys", {}).data;
return (
- }>
-
- {keys?.map((key) => (
-
-
- {key.name}
-
-
-
- created at: {fmt_date(new Date(key.created_at))}
-
- expires:{" "}
- {key.expires === 0 ? "never" : fmt_date(new Date(key.expires))}
+
+ {keys?.map((key) => (
+
+
+ {key.name}
+
+
+
+ created at: {fmt_date(new Date(key.created_at))}
+
+ expires:{" "}
+ {key.expires === 0 ? "never" : fmt_date(new Date(key.expires))}
+
+ {key.key}
+
+
+ ))}
+
+ );
+};
+
+const ONE_DAY_MS = 1000 * 60 * 60 * 24;
+
+type ExpiresOptions = "90 days" | "180 days" | "1 year" | "never";
+
+const CreateKey = () => {
+ const [open, setOpen] = useState(false);
+ const [name, setName] = useState("");
+ const [expires, setExpires] = useState
("never");
+ const [submitted, setSubmitted] = useState<{ key: string; secret: string }>();
+ const invalidate = useInvalidate();
+ const { mutate, isPending } = useWrite("CreateApiKey", {
+ onSuccess: ({ key, secret }) => {
+ invalidate(["ListApiKeys"]);
+ setSubmitted({ key, secret });
+ },
+ });
+ const now = Date.now();
+ const expiresOptions: Record = {
+ "90 days": now + ONE_DAY_MS * 90,
+ "180 days": now + ONE_DAY_MS * 180,
+ "1 year": now + ONE_DAY_MS * 365,
+ never: 0,
+ };
+ const submit = () => mutate({ name, expires: expiresOptions[expires] });
+ const onOpenChange = (open: boolean) => {
+ setOpen(open);
+ if (!open) {
+ setName("");
+ setExpires("never");
+ setSubmitted(undefined);
+ }
+ };
+ return (
+
+
+
+
+ >
+ ) : (
+ <>
+
+ Create Api Key
+
+
+
+ Name
+ setName(e.target.value)}
+ />
+
+
+ Expiry
+
+
+
+
+
+
+ {Object.keys(expiresOptions)
+ .filter((option) => option !== expires)
+ .map((option) => (
+ setExpires(option as any)}
+ >
+ {option}
+
+ ))}
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
);
};
@@ -57,8 +186,8 @@ const DeleteKey = ({ api_key }: { api_key: string }) => {
toast({ title: "Api Key Deleted" });
},
onError: () => {
- toast({ title: "Failed to delte api key", })
- }
+ toast({ title: "Failed to delte api key" });
+ },
});
return (