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 />
<HeaderUpdates />
<ThemeToggle />
{/* <UserSettings /> */}
<Logout />
</div>
</div>

View File

@@ -10,12 +10,9 @@ import { Button } from "@ui/button";
import { Calendar, User } from "lucide-react";
import { UpdateDetails, UpdateUser } from "./details";
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";
const fmt_date = (d: Date) =>
`${d.getDate()}/${d.getMonth() + 1} @ ${d.getHours()}:${d.getMinutes()}`;
export const SingleUpdate = ({ update }: { update: Types.UpdateListItem }) => {
const Components =
update.target.type !== "System"

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ export const RESOURCE_TARGETS: UsableResource[] = [
"Procedure",
];
export const fmt_update_date = (d: Date) => {
export const fmt_date = (d: Date) => {
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 { Resource } from "@pages/resource";
import { Resources } from "@pages/resources";
import { Keys } from "@pages/keys";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
const router = createBrowserRouter([
@@ -12,6 +13,7 @@ const router = createBrowserRouter([
element: <Layout />,
children: [
{ path: "", element: <Dashboard /> },
{ path: "keys", element: <Keys /> },
{
path: ":type",
children: [