forked from github-starred/komodo
api key page
This commit is contained in:
@@ -50,6 +50,7 @@ export const Layout = () => {
|
|||||||
<WsStatusIndicator />
|
<WsStatusIndicator />
|
||||||
<HeaderUpdates />
|
<HeaderUpdates />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
{/* <UserSettings /> */}
|
||||||
<Logout />
|
<Logout />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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,9 +113,14 @@ export const ActionButton = forwardRef<
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
>(({ title, icon, disabled, className, loading, onClick }, ref) => (
|
>(
|
||||||
|
(
|
||||||
|
{ variant, size, title, icon, disabled, className, loading, onClick },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
size={size}
|
||||||
|
variant={variant || "outline"}
|
||||||
className={cn("flex items-center justify-between w-[150px]", className)}
|
className={cn("flex items-center justify-between w-[150px]", className)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -112,7 +128,8 @@ export const ActionButton = forwardRef<
|
|||||||
>
|
>
|
||||||
{title} {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : icon}
|
{title} {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : icon}
|
||||||
</Button>
|
</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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
71
frontend/src/pages/keys.tsx
Normal file
71
frontend/src/pages/keys.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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: [
|
||||||
|
|||||||
Reference in New Issue
Block a user