forked from github-starred/komodo
fix permissioning endpoint
This commit is contained in:
@@ -20,8 +20,8 @@ use super::{update::ResourceTarget, MongoId};
|
|||||||
#[unique_doc_index(doc! {
|
#[unique_doc_index(doc! {
|
||||||
"user_target.type": 1,
|
"user_target.type": 1,
|
||||||
"user_target.id": 1,
|
"user_target.id": 1,
|
||||||
"target.type": 1,
|
"resource_target.type": 1,
|
||||||
"target.id": 1
|
"resource_target.id": 1
|
||||||
})]
|
})]
|
||||||
pub struct Permission {
|
pub struct Permission {
|
||||||
/// The id of the permission document
|
/// The id of the permission document
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
|
||||||
import { useRead } from "@lib/hooks";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Key } from "lucide-react";
|
|
||||||
|
|
||||||
export const ApiKeysSummary = () => {
|
|
||||||
const keys_count = useRead("ListApiKeys", {}).data?.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link to="/keys" className="w-full">
|
|
||||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer">
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle>Api Keys</CardTitle>
|
|
||||||
<CardDescription>{keys_count} Total</CardDescription>
|
|
||||||
</div>
|
|
||||||
<Key className="w-4 h-4" />
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
|
||||||
import { useRead } from "@lib/hooks";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Tag } from "lucide-react";
|
|
||||||
|
|
||||||
export const TagsSummary = () => {
|
|
||||||
const tags_count = useRead("ListTags", {}).data?.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link to="/tags" className="w-full">
|
|
||||||
<Card className="hover:bg-accent/50 transition-colors cursor-pointer">
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle>Tags</CardTitle>
|
|
||||||
<CardDescription>{tags_count} Total</CardDescription>
|
|
||||||
</div>
|
|
||||||
<Tag className="w-4 h-4" />
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useRead } from "@lib/hooks";
|
import { useRead, useUser } from "@lib/hooks";
|
||||||
import { Button } from "@ui/button";
|
import { Button } from "@ui/button";
|
||||||
import {
|
import {
|
||||||
CommandDialog,
|
CommandDialog,
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
} from "@ui/command";
|
} from "@ui/command";
|
||||||
import { Home, Search } from "lucide-react";
|
import { Home, Search, UserCircle2 } from "lucide-react";
|
||||||
import { useState, useEffect, Fragment } from "react";
|
import { useState, useEffect, Fragment } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { ResourceComponents } from "./resources";
|
import { ResourceComponents } from "./resources";
|
||||||
@@ -21,6 +21,8 @@ import { ServerComponents } from "./resources/server";
|
|||||||
import { ProcedureComponents } from "./resources/procedure";
|
import { ProcedureComponents } from "./resources/procedure";
|
||||||
|
|
||||||
export const Omnibar = () => {
|
export const Omnibar = () => {
|
||||||
|
const user = useUser().data;
|
||||||
|
|
||||||
const [open, set] = useState(false);
|
const [open, set] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const nav = (value: string) => {
|
const nav = (value: string) => {
|
||||||
@@ -68,6 +70,13 @@ export const Omnibar = () => {
|
|||||||
<Home className="w-4 h-4" />
|
<Home className="w-4 h-4" />
|
||||||
Home
|
Home
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
<CommandItem
|
||||||
|
className="flex items-center gap-2 cursor-pointer"
|
||||||
|
onSelect={() => nav("/servers")}
|
||||||
|
>
|
||||||
|
<ServerComponents.Icon />
|
||||||
|
Servers
|
||||||
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
className="flex items-center gap-2 cursor-pointer"
|
className="flex items-center gap-2 cursor-pointer"
|
||||||
onSelect={() => nav("/deployments")}
|
onSelect={() => nav("/deployments")}
|
||||||
@@ -82,13 +91,6 @@ export const Omnibar = () => {
|
|||||||
<BuildComponents.Icon />
|
<BuildComponents.Icon />
|
||||||
Builds
|
Builds
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
|
||||||
className="flex items-center gap-2 cursor-pointer"
|
|
||||||
onSelect={() => nav("/servers")}
|
|
||||||
>
|
|
||||||
<ServerComponents.Icon />
|
|
||||||
Servers
|
|
||||||
</CommandItem>
|
|
||||||
<CommandItem
|
<CommandItem
|
||||||
className="flex items-center gap-2 cursor-pointer"
|
className="flex items-center gap-2 cursor-pointer"
|
||||||
onSelect={() => nav("/procedures")}
|
onSelect={() => nav("/procedures")}
|
||||||
@@ -96,6 +98,15 @@ export const Omnibar = () => {
|
|||||||
<ProcedureComponents.Icon />
|
<ProcedureComponents.Icon />
|
||||||
Procedures
|
Procedures
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
{user?.admin && (
|
||||||
|
<CommandItem
|
||||||
|
className="flex items-center gap-2 cursor-pointer"
|
||||||
|
onSelect={() => nav("/users")}
|
||||||
|
>
|
||||||
|
<UserCircle2 className="w-4 h-4" />
|
||||||
|
Users
|
||||||
|
</CommandItem>
|
||||||
|
)}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
|
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export const AlerterComponents: RequiredResourceComponents = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Name: ({ id }: { id: string }) => <>{useAlerter(id)?.name}</>,
|
Name: ({ id }: { id: string }) => <>{useAlerter(id)?.name}</>,
|
||||||
|
name: (id) => useAlerter(id)?.name,
|
||||||
|
|
||||||
Icon: () => <AlarmClock className="w-4 h-4" />,
|
Icon: () => <AlarmClock className="w-4 h-4" />,
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const BuildComponents: RequiredResourceComponents = {
|
|||||||
Table: BuildTable,
|
Table: BuildTable,
|
||||||
|
|
||||||
Name: ({ id }) => <>{useBuild(id)?.name}</>,
|
Name: ({ id }) => <>{useBuild(id)?.name}</>,
|
||||||
|
name: (id) => useBuild(id)?.name,
|
||||||
|
|
||||||
Icon: ({ id }) => {
|
Icon: ({ id }) => {
|
||||||
if (id) return <IconStrictId id={id} />;
|
if (id) return <IconStrictId id={id} />;
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export const BuilderComponents: RequiredResourceComponents = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Name: ({ id }: { id: string }) => <>{useBuilder(id)?.name}</>,
|
Name: ({ id }: { id: string }) => <>{useBuilder(id)?.name}</>,
|
||||||
|
name: (id) => useBuilder(id)?.name,
|
||||||
|
|
||||||
Icon: () => <Factory className="w-4 h-4" />,
|
Icon: () => <Factory className="w-4 h-4" />,
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const DeploymentComponents: RequiredResourceComponents = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Name: ({ id }) => <>{useDeployment(id)?.name}</>,
|
Name: ({ id }) => <>{useDeployment(id)?.name}</>,
|
||||||
|
name: (id) => useDeployment(id)?.name,
|
||||||
|
|
||||||
Icon: ({ id }) => {
|
Icon: ({ id }) => {
|
||||||
const state = useDeployment(id)?.info.state;
|
const state = useDeployment(id)?.info.state;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
|||||||
Table: ProcedureTable,
|
Table: ProcedureTable,
|
||||||
|
|
||||||
Name: ({ id }) => <>{useProcedure(id)?.name}</>,
|
Name: ({ id }) => <>{useProcedure(id)?.name}</>,
|
||||||
|
name: (id) => useProcedure(id)?.name,
|
||||||
|
|
||||||
Icon: () => <Route className="w-4" />,
|
Icon: () => <Route className="w-4" />,
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export const RepoComponents: RequiredResourceComponents = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Name: ({ id }: { id: string }) => <>{useRepo(id)?.name}</>,
|
Name: ({ id }: { id: string }) => <>{useRepo(id)?.name}</>,
|
||||||
|
name: (id) => useRepo(id)?.name,
|
||||||
|
|
||||||
Icon: () => <GitBranch className="w-4 h-4" />,
|
Icon: () => <GitBranch className="w-4 h-4" />,
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const ServerComponents: RequiredResourceComponents = {
|
|||||||
Table: ServerTable,
|
Table: ServerTable,
|
||||||
|
|
||||||
Name: ({ id }: { id: string }) => <>{useServer(id)?.name}</>,
|
Name: ({ id }: { id: string }) => <>{useServer(id)?.name}</>,
|
||||||
|
name: (id) => useServer(id)?.name,
|
||||||
|
|
||||||
Icon: ({ id }) => {
|
Icon: ({ id }) => {
|
||||||
const status = useServer(id)?.info.status;
|
const status = useServer(id)?.info.status;
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export const Topbar = () => {
|
|||||||
const PrimaryDropdown = () => {
|
const PrimaryDropdown = () => {
|
||||||
const type = useResourceParamType();
|
const type = useResourceParamType();
|
||||||
const Components = type && ResourceComponents[type];
|
const Components = type && ResourceComponents[type];
|
||||||
|
console.log(location.pathname.split("/"));
|
||||||
|
|
||||||
const [icon, title] = Components
|
const [icon, title] = Components
|
||||||
? [<Components.Icon />, type + "s"]
|
? [<Components.Icon />, type + "s"]
|
||||||
@@ -85,7 +86,8 @@ const PrimaryDropdown = () => {
|
|||||||
? [<Tag className="w-4 h-4" />, "Tags"]
|
? [<Tag className="w-4 h-4" />, "Tags"]
|
||||||
: location.pathname === "/alerts"
|
: location.pathname === "/alerts"
|
||||||
? [<AlertTriangle className="w-4 h-4" />, "Alerts"]
|
? [<AlertTriangle className="w-4 h-4" />, "Alerts"]
|
||||||
: location.pathname === "/users"
|
: location.pathname === "/users" ||
|
||||||
|
location.pathname.split("/")[1] === "users"
|
||||||
? [<UserCircle2 className="w-4 h-4" />, "Users"]
|
? [<UserCircle2 className="w-4 h-4" />, "Users"]
|
||||||
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
|
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
|
||||||
// : [<Box className="w-4 h-4" />, "Dashboard"];
|
// : [<Box className="w-4 h-4" />, "Dashboard"];
|
||||||
@@ -177,36 +179,43 @@ const SecondaryDropdown = () => {
|
|||||||
|
|
||||||
const type = useResourceParamType();
|
const type = useResourceParamType();
|
||||||
if (type) return <ResourcesDropdown type={type} />;
|
if (type) return <ResourcesDropdown type={type} />;
|
||||||
if (location.pathname !== "/") return;
|
|
||||||
|
|
||||||
const Icon = ICONS[view];
|
if (location.pathname === "/") {
|
||||||
|
const Icon = ICONS[view];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="w-48 justify-between px-3">
|
<Button variant="ghost" className="w-48 justify-between px-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Icon />
|
|
||||||
{view}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-48" side="bottom">
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
{Object.entries(ICONS).map(([view, Icon]) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={view}
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
onClick={() => setView(view as HomeView)}
|
|
||||||
>
|
|
||||||
<Icon />
|
<Icon />
|
||||||
{view}
|
{view}
|
||||||
</DropdownMenuItem>
|
</div>
|
||||||
))}
|
</Button>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuTrigger>
|
||||||
</DropdownMenuContent>
|
<DropdownMenuContent className="w-48" side="bottom">
|
||||||
</DropdownMenu>
|
<DropdownMenuGroup>
|
||||||
);
|
{Object.entries(ICONS).map(([view, Icon]) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={view}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
onClick={() => setView(view as HomeView)}
|
||||||
|
>
|
||||||
|
<Icon />
|
||||||
|
{view}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [_, base, user_id] = location.pathname.split("/");
|
||||||
|
|
||||||
|
if (base === "users") {
|
||||||
|
return <UsersDropdown user_id={user_id} />;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ResourcesDropdown = ({ type }: { type: UsableResource }) => {
|
const ResourcesDropdown = ({ type }: { type: UsableResource }) => {
|
||||||
@@ -274,3 +283,83 @@ const ResourcesDropdown = ({ type }: { type: UsableResource }) => {
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const UsersDropdown = ({ user_id }: { user_id: string | undefined }) => {
|
||||||
|
const nav = useNavigate();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [input, setInput] = useState("");
|
||||||
|
|
||||||
|
const users = useRead("GetUsers", {}).data;
|
||||||
|
|
||||||
|
const selected = user_id
|
||||||
|
? users?.find((user) => user._id?.$oid === user_id)
|
||||||
|
: undefined;
|
||||||
|
const avatar = (selected?.config.data as { avatar?: string })?.avatar;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="ghost" className="justify-between w-[300px] px-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<UserAvatar avatar={avatar} />
|
||||||
|
{selected ? selected.username : "All Users"}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[300px] max-h-[400px] p-0" sideOffset={12}>
|
||||||
|
<Command>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Search Users"
|
||||||
|
className="h-9"
|
||||||
|
value={input}
|
||||||
|
onValueChange={setInput}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty className="flex justify-evenly items-center">
|
||||||
|
No Users Found
|
||||||
|
<SearchX className="w-3 h-3" />
|
||||||
|
</CommandEmpty>
|
||||||
|
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => {
|
||||||
|
setOpen(false);
|
||||||
|
nav(`/users`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button variant="link" className="flex gap-2 items-center p-0">
|
||||||
|
<UserCircle2 className="w-4" />
|
||||||
|
All Users
|
||||||
|
</Button>
|
||||||
|
</CommandItem>
|
||||||
|
{users?.map((user) => (
|
||||||
|
<CommandItem
|
||||||
|
key={user.username}
|
||||||
|
onSelect={() => {
|
||||||
|
setOpen(false);
|
||||||
|
nav(`/users/${user._id?.$oid}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
className="flex gap-2 items-center p-0"
|
||||||
|
>
|
||||||
|
<UserAvatar avatar={(user.config.data as any).avatar} />
|
||||||
|
{user.username}
|
||||||
|
</Button>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserAvatar = ({ avatar }: { avatar: string | undefined }) =>
|
||||||
|
avatar ? (
|
||||||
|
<img src={avatar} alt="Avatar" className="w-4" />
|
||||||
|
) : (
|
||||||
|
<UserCircle2 className="w-4" />
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ResourceComponents } from "@components/resources";
|
||||||
import { Types } from "@monitor/client";
|
import { Types } from "@monitor/client";
|
||||||
import { UsableResource } from "@types";
|
import { UsableResource } from "@types";
|
||||||
import { type ClassValue, clsx } from "clsx";
|
import { type ClassValue, clsx } from "clsx";
|
||||||
@@ -56,3 +57,8 @@ function keep_line(line: string) {
|
|||||||
export function version_is_none({ major, minor, patch }: Types.Version) {
|
export function version_is_none({ major, minor, patch }: Types.Version) {
|
||||||
return major === 0 && minor === 0 && patch === 0;
|
return major === 0 && minor === 0 && patch === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resource_name(type: UsableResource, id: string) {
|
||||||
|
const Components = ResourceComponents[type];
|
||||||
|
return Components.name(id)
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Page, Section } from "@components/layouts";
|
import { Page, Section } from "@components/layouts";
|
||||||
import { Box, History } from "lucide-react";
|
import { Box, History, Key, Tag } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { Card, CardContent } from "@ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||||
import { TagsSummary } from "@components/dashboard/tags";
|
|
||||||
import { ApiKeysSummary } from "@components/dashboard/api-keys";
|
|
||||||
import { ResourceComponents } from "@components/resources";
|
import { ResourceComponents } from "@components/resources";
|
||||||
import { OpenAlerts } from "@components/alert";
|
import { OpenAlerts } from "@components/alert";
|
||||||
import { useUser } from "@lib/hooks";
|
import { useRead, useUser } from "@lib/hooks";
|
||||||
import { ResourceLink } from "@components/resources/common";
|
import { ResourceLink } from "@components/resources/common";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
@@ -79,3 +77,43 @@ const RecentlyViewed = () => {
|
|||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TagsSummary = () => {
|
||||||
|
const tags_count = useRead("ListTags", {}).data?.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link to="/tags" className="w-full">
|
||||||
|
<Card className="hover:bg-accent/50 transition-colors cursor-pointer">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle>Tags</CardTitle>
|
||||||
|
<CardDescription>{tags_count} Total</CardDescription>
|
||||||
|
</div>
|
||||||
|
<Tag className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ApiKeysSummary = () => {
|
||||||
|
const keys_count = useRead("ListApiKeys", {}).data?.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link to="/keys" className="w-full">
|
||||||
|
<Card className="hover:bg-accent/50 transition-colors cursor-pointer">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle>Api Keys</CardTitle>
|
||||||
|
<CardDescription>{keys_count} Total</CardDescription>
|
||||||
|
</div>
|
||||||
|
<Key className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Page, Section } from "@components/layouts";
|
import { Page, Section } from "@components/layouts";
|
||||||
|
import { ResourceComponents } from "@components/resources";
|
||||||
import { ResourceLink } from "@components/resources/common";
|
import { ResourceLink } from "@components/resources/common";
|
||||||
import { ConfirmButton } from "@components/util";
|
import { ConfirmButton } from "@components/util";
|
||||||
import { text_color_class_by_intention } from "@lib/color";
|
import { text_color_class_by_intention } from "@lib/color";
|
||||||
import { useInvalidate, useRead, useSetTitle, useWrite } from "@lib/hooks";
|
import { useInvalidate, useRead, useSetTitle, useWrite } from "@lib/hooks";
|
||||||
|
import { resource_name } from "@lib/utils";
|
||||||
import { Types } from "@monitor/client";
|
import { Types } from "@monitor/client";
|
||||||
import { UsableResource } from "@types";
|
import { UsableResource } from "@types";
|
||||||
import { DataTable } from "@ui/data-table";
|
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||||
import { Input } from "@ui/input";
|
import { Input } from "@ui/input";
|
||||||
import { Label } from "@ui/label";
|
import { Label } from "@ui/label";
|
||||||
import {
|
import {
|
||||||
@@ -282,6 +284,12 @@ const PermissionsTable = () => {
|
|||||||
title="Permissions"
|
title="Permissions"
|
||||||
actions={
|
actions={
|
||||||
<div className="flex gap-6 items-center">
|
<div className="flex gap-6 items-center">
|
||||||
|
<Input
|
||||||
|
placeholder="search"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
className="w-[300px]"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className="flex gap-3 items-center"
|
className="flex gap-3 items-center"
|
||||||
onClick={() => setShowNone(!showNone)}
|
onClick={() => setShowNone(!showNone)}
|
||||||
@@ -289,37 +297,66 @@ const PermissionsTable = () => {
|
|||||||
<Label htmlFor="show-none">Show All Resources</Label>
|
<Label htmlFor="show-none">Show All Resources</Label>
|
||||||
<Switch id="show-none" checked={showNone} />
|
<Switch id="show-none" checked={showNone} />
|
||||||
</div>
|
</div>
|
||||||
<Input
|
|
||||||
placeholder="search"
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
|
||||||
className="w-48"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DataTable
|
<DataTable
|
||||||
tableKey="permissions"
|
tableKey="permissions"
|
||||||
data={
|
data={
|
||||||
permissions
|
permissions?.filter(
|
||||||
?.filter((permission) =>
|
(permission) =>
|
||||||
showNone ? true : permission.level !== Types.PermissionLevel.None
|
(showNone
|
||||||
)
|
? true
|
||||||
.filter((permission) =>
|
: permission.level !== Types.PermissionLevel.None) &&
|
||||||
searchSplit.every(
|
searchSplit.every(
|
||||||
(search) =>
|
(search) =>
|
||||||
permission.name.toLowerCase().includes(search) ||
|
permission.name.toLowerCase().includes(search) ||
|
||||||
permission.resource_target.type.toLowerCase().includes(search)
|
permission.resource_target.type.toLowerCase().includes(search)
|
||||||
)
|
)
|
||||||
) ?? []
|
) ?? []
|
||||||
}
|
}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: "Resource",
|
|
||||||
accessorKey: "resource_target.type",
|
accessorKey: "resource_target.type",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SortableHeader column={column} title="Resource" />
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const Components =
|
||||||
|
ResourceComponents[
|
||||||
|
row.original.resource_target.type as UsableResource
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<Components.Icon />
|
||||||
|
{row.original.resource_target.type}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Target",
|
accessorKey: "resource_target",
|
||||||
|
sortingFn: (a, b) => {
|
||||||
|
const ra = resource_name(
|
||||||
|
a.original.resource_target.type as UsableResource,
|
||||||
|
a.original.resource_target.id
|
||||||
|
);
|
||||||
|
const rb = resource_name(
|
||||||
|
b.original.resource_target.type as UsableResource,
|
||||||
|
b.original.resource_target.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ra && !rb) return 0;
|
||||||
|
if (!ra) return -1;
|
||||||
|
if (!rb) return 1;
|
||||||
|
|
||||||
|
if (ra > rb) return 1;
|
||||||
|
else if (ra < rb) return -1;
|
||||||
|
else return 0;
|
||||||
|
},
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SortableHeader column={column} title="Target" />
|
||||||
|
),
|
||||||
cell: ({
|
cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: { resource_target },
|
original: { resource_target },
|
||||||
@@ -334,7 +371,10 @@ const PermissionsTable = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Level",
|
accessorKey: "level",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SortableHeader column={column} title="Level" />
|
||||||
|
),
|
||||||
cell: ({ row: { original: permission } }) => (
|
cell: ({ row: { original: permission } }) => (
|
||||||
<Select
|
<Select
|
||||||
value={permission.level}
|
value={permission.level}
|
||||||
|
|||||||
1
frontend/src/types.d.ts
vendored
1
frontend/src/types.d.ts
vendored
@@ -17,6 +17,7 @@ export interface RequiredResourceComponents {
|
|||||||
|
|
||||||
/** Name of the resource */
|
/** Name of the resource */
|
||||||
Name: IdComponent;
|
Name: IdComponent;
|
||||||
|
name: (id: string) => string | undefined;
|
||||||
|
|
||||||
/** Icon for the component */
|
/** Icon for the component */
|
||||||
Icon: OptionalIdComponent;
|
Icon: OptionalIdComponent;
|
||||||
|
|||||||
Reference in New Issue
Block a user