api key page

This commit is contained in:
mbecker20
2024-03-24 17:33:09 -07:00
parent 4174eebffe
commit 59b09e580e
7 changed files with 141 additions and 25 deletions

View File

@@ -50,6 +50,7 @@ export const Layout = () => {
<WsStatusIndicator /> <WsStatusIndicator />
<HeaderUpdates /> <HeaderUpdates />
<ThemeToggle /> <ThemeToggle />
{/* <UserSettings /> */}
<Logout /> <Logout />
</div> </div>
</div> </div>

View File

@@ -10,12 +10,9 @@ import { Button } from "@ui/button";
import { Calendar, User } from "lucide-react"; import { Calendar, User } from "lucide-react";
import { UpdateDetails, UpdateUser } from "./details"; import { UpdateDetails, UpdateUser } from "./details";
import { ResourceComponents } from "@components/resources"; import { ResourceComponents } from "@components/resources";
import { cn, fmt_version } from "@lib/utils"; import { cn, fmt_date, fmt_version } from "@lib/utils";
import { Types } from "@monitor/client"; import { Types } from "@monitor/client";
const fmt_date = (d: Date) =>
`${d.getDate()}/${d.getMonth() + 1} @ ${d.getHours()}:${d.getMinutes()}`;
export const SingleUpdate = ({ update }: { update: Types.UpdateListItem }) => { export const SingleUpdate = ({ update }: { update: Types.UpdateListItem }) => {
const Components = const Components =
update.target.type !== "System" update.target.type !== "System"

View File

@@ -13,7 +13,7 @@ import {
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Types } from "@monitor/client"; import { Types } from "@monitor/client";
import { Section } from "@components/layouts"; import { Section } from "@components/layouts";
import { fmt_update_date, fmt_version } from "@lib/utils"; import { fmt_date, fmt_version } from "@lib/utils";
import { UpdateDetails, UpdateUser } from "./details"; import { UpdateDetails, UpdateUser } from "./details";
import { UpdateStatus } from "@monitor/client/dist/types"; import { UpdateStatus } from "@monitor/client/dist/types";
@@ -57,7 +57,7 @@ const UpdateCard = ({ update }: { update: Types.UpdateListItem }) => {
<div> <div>
<div className="flex items-center gap-2 text-muted-foreground"> <div className="flex items-center gap-2 text-muted-foreground">
<Calendar className="w-4" /> <Calendar className="w-4" />
{fmt_update_date(new Date(update.start_ts))} {fmt_date(new Date(update.start_ts))}
</div> </div>
<div className="flex items-center gap-2 text-muted-foreground"> <div className="flex items-center gap-2 text-muted-foreground">
<User className="w-4" /> <User className="w-4" />

View File

@@ -7,7 +7,9 @@ import {
Loader2, Loader2,
LogOut, LogOut,
Moon, Moon,
Settings,
SunMedium, SunMedium,
User,
} from "lucide-react"; } from "lucide-react";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { import {
@@ -35,7 +37,6 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@ui/dropdown-menu"; } from "@ui/dropdown-menu";
import { ResourceComponents } from "./resources"; import { ResourceComponents } from "./resources";
import { WsStatusIndicator } from "@lib/socket";
export const WithLoading = ({ export const WithLoading = ({
children, children,
@@ -95,6 +96,16 @@ export const ThemeToggle = () => {
export const ActionButton = forwardRef< export const ActionButton = forwardRef<
HTMLButtonElement, HTMLButtonElement,
{ {
variant?:
| "link"
| "default"
| "destructive"
| "outline"
| "secondary"
| "ghost"
| null
| undefined;
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
title: string; title: string;
icon: ReactNode; icon: ReactNode;
disabled?: boolean; disabled?: boolean;
@@ -102,17 +113,23 @@ export const ActionButton = forwardRef<
onClick?: () => void; onClick?: () => void;
loading?: boolean; loading?: boolean;
} }
>(({ title, icon, disabled, className, loading, onClick }, ref) => ( >(
<Button (
variant="outline" { variant, size, title, icon, disabled, className, loading, onClick },
className={cn("flex items-center justify-between w-[150px]", className)} ref
onClick={onClick} ) => (
disabled={disabled} <Button
ref={ref} size={size}
> variant={variant || "outline"}
{title} {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : icon} className={cn("flex items-center justify-between w-[150px]", className)}
</Button> onClick={onClick}
)); disabled={disabled}
ref={ref}
>
{title} {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : icon}
</Button>
)
);
export const ActionWithDialog = ({ export const ActionWithDialog = ({
name, name,
@@ -235,12 +252,24 @@ export const CopyResource = ({
}; };
export const ConfirmButton = ({ export const ConfirmButton = ({
variant,
size,
title, title,
icon, icon,
disabled, disabled,
loading, loading,
onClick, onClick,
}: { }: {
variant?:
| "link"
| "default"
| "destructive"
| "outline"
| "secondary"
| "ghost"
| null
| undefined;
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
title: string; title: string;
icon: ReactNode; icon: ReactNode;
onClick: () => void; onClick: () => void;
@@ -252,6 +281,8 @@ export const ConfirmButton = ({
return ( return (
<> <>
<ActionButton <ActionButton
variant={variant}
size={size}
title={confirmed ? "Confirm" : title} title={confirmed ? "Confirm" : title}
icon={confirmed ? <Check className="w-4 h-4" /> : icon} icon={confirmed ? <Check className="w-4 h-4" /> : icon}
disabled={disabled} disabled={disabled}
@@ -309,6 +340,12 @@ export const ResourceTypeDropdown = () => {
</Link> </Link>
); );
})} })}
<Link to="/keys">
<DropdownMenuItem className="flex items-center gap-2">
<Box className="w-4 h-4" />
Api Keys
</DropdownMenuItem>
</Link>
</DropdownMenuGroup> </DropdownMenuGroup>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -374,18 +411,26 @@ export const Logout = () => (
</Button> </Button>
); );
export const UserDropdown = () => { export const UserSettings = () => (
const user = useRead("GetUser", {}).data; <Link to="/settings">
<Button variant="ghost" size="icon">
<Settings className="w-4 h-4" />
</Button>
</Link>
);
export const UserDropdown = () => {
// const user = useRead("GetUser", {}).data;
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button className="gap-2" variant="outline"> <Button variant="ghost" size="icon">
<WsStatusIndicator /> <User className="w-4 h-4" />
{user?.username}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent></DropdownMenuContent> <DropdownMenuContent>
<Logout />
</DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );
}; };

View File

@@ -20,7 +20,7 @@ export const RESOURCE_TARGETS: UsableResource[] = [
"Procedure", "Procedure",
]; ];
export const fmt_update_date = (d: Date) => { export const fmt_date = (d: Date) => {
return `${d.getDate()}/${d.getMonth() + 1} @ ${d.getHours()}:${d.getMinutes()}`; return `${d.getDate()}/${d.getMonth() + 1} @ ${d.getHours()}:${d.getMinutes()}`;
}; };

View File

@@ -0,0 +1,71 @@
import { Page, Section } from "@components/layouts";
import { ConfirmButton } from "@components/util";
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
import { fmt_date } from "@lib/utils";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@ui/card";
import { useToast } from "@ui/use-toast";
import { Key, Trash } from "lucide-react";
export const Keys = () => {
return (
<Page title="">
<ApiKeysList />
</Page>
);
};
export const ApiKeysList = () => {
const keys = useRead("ListApiKeys", {}).data;
return (
<Section title="Api Keys" icon={<Key className="w-4 h-4" />}>
<div className="flex flex-col lg:flex-row gap-4 w-full">
{keys?.map((key) => (
<Card
id={key.key}
className="h-full hover:bg-accent/50 group-focus:bg-accent/50 transition-colors"
>
<CardHeader className="flex-row justify-between items-center">
<CardTitle>{key.name}</CardTitle>
<DeleteKey api_key={key.key} />
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
<div>created at: {fmt_date(new Date(key.created_at))}</div>
<div>
expires:{" "}
{key.expires === 0 ? "never" : fmt_date(new Date(key.expires))}
</div>
<div>{key.key}</div>
</CardContent>
</Card>
))}
</div>
</Section>
);
};
const DeleteKey = ({ api_key }: { api_key: string }) => {
const invalidate = useInvalidate();
const { toast } = useToast();
const { mutate, isPending } = useWrite("DeleteApiKey", {
onSuccess: () => {
invalidate(["ListApiKeys"]);
toast({ title: "Api Key Deleted" });
},
onError: () => {
toast({ title: "Failed to delte api key", })
}
});
return (
<ConfirmButton
title="Delete"
icon={<Trash className="w-4 h-4" />}
onClick={() => mutate({ key: api_key })}
loading={isPending}
/>
);
};

View File

@@ -4,6 +4,7 @@ import { Dashboard } from "@pages/dashboard";
import { Login } from "@pages/login"; import { Login } from "@pages/login";
import { Resource } from "@pages/resource"; import { Resource } from "@pages/resource";
import { Resources } from "@pages/resources"; import { Resources } from "@pages/resources";
import { Keys } from "@pages/keys";
import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { RouterProvider, createBrowserRouter } from "react-router-dom";
const router = createBrowserRouter([ const router = createBrowserRouter([
@@ -12,6 +13,7 @@ const router = createBrowserRouter([
element: <Layout />, element: <Layout />,
children: [ children: [
{ path: "", element: <Dashboard /> }, { path: "", element: <Dashboard /> },
{ path: "keys", element: <Keys /> },
{ {
path: ":type", path: ":type",
children: [ children: [