diff --git a/client/core/rs/src/entities/permission.rs b/client/core/rs/src/entities/permission.rs index 0e21f4957..d95100ba1 100644 --- a/client/core/rs/src/entities/permission.rs +++ b/client/core/rs/src/entities/permission.rs @@ -20,8 +20,8 @@ use super::{update::ResourceTarget, MongoId}; #[unique_doc_index(doc! { "user_target.type": 1, "user_target.id": 1, - "target.type": 1, - "target.id": 1 + "resource_target.type": 1, + "resource_target.id": 1 })] pub struct Permission { /// The id of the permission document diff --git a/frontend/src/components/dashboard/api-keys.tsx b/frontend/src/components/dashboard/api-keys.tsx deleted file mode 100644 index b387fdda7..000000000 --- a/frontend/src/components/dashboard/api-keys.tsx +++ /dev/null @@ -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 ( - - - -
-
- Api Keys - {keys_count} Total -
- -
-
-
- - ); -}; diff --git a/frontend/src/components/dashboard/tags.tsx b/frontend/src/components/dashboard/tags.tsx deleted file mode 100644 index 9dcdbabd5..000000000 --- a/frontend/src/components/dashboard/tags.tsx +++ /dev/null @@ -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 ( - - - -
-
- Tags - {tags_count} Total -
- -
-
-
- - ); -}; diff --git a/frontend/src/components/omnibar.tsx b/frontend/src/components/omnibar.tsx index d47751472..c70bd8f13 100644 --- a/frontend/src/components/omnibar.tsx +++ b/frontend/src/components/omnibar.tsx @@ -1,4 +1,4 @@ -import { useRead } from "@lib/hooks"; +import { useRead, useUser } from "@lib/hooks"; import { Button } from "@ui/button"; import { CommandDialog, @@ -9,7 +9,7 @@ import { CommandSeparator, CommandItem, } from "@ui/command"; -import { Home, Search } from "lucide-react"; +import { Home, Search, UserCircle2 } from "lucide-react"; import { useState, useEffect, Fragment } from "react"; import { useNavigate } from "react-router-dom"; import { ResourceComponents } from "./resources"; @@ -21,6 +21,8 @@ import { ServerComponents } from "./resources/server"; import { ProcedureComponents } from "./resources/procedure"; export const Omnibar = () => { + const user = useUser().data; + const [open, set] = useState(false); const navigate = useNavigate(); const nav = (value: string) => { @@ -68,6 +70,13 @@ export const Omnibar = () => { Home + nav("/servers")} + > + + Servers + nav("/deployments")} @@ -82,13 +91,6 @@ export const Omnibar = () => { Builds - nav("/servers")} - > - - Servers - nav("/procedures")} @@ -96,6 +98,15 @@ export const Omnibar = () => { Procedures + {user?.admin && ( + nav("/users")} + > + + Users + + )} diff --git a/frontend/src/components/resources/alerter/index.tsx b/frontend/src/components/resources/alerter/index.tsx index f8e6c2117..018ecb6fc 100644 --- a/frontend/src/components/resources/alerter/index.tsx +++ b/frontend/src/components/resources/alerter/index.tsx @@ -131,6 +131,7 @@ export const AlerterComponents: RequiredResourceComponents = { }, Name: ({ id }: { id: string }) => <>{useAlerter(id)?.name}, + name: (id) => useAlerter(id)?.name, Icon: () => , diff --git a/frontend/src/components/resources/build/index.tsx b/frontend/src/components/resources/build/index.tsx index 71f2ce5ee..797a13c5b 100644 --- a/frontend/src/components/resources/build/index.tsx +++ b/frontend/src/components/resources/build/index.tsx @@ -21,6 +21,7 @@ export const BuildComponents: RequiredResourceComponents = { Table: BuildTable, Name: ({ id }) => <>{useBuild(id)?.name}, + name: (id) => useBuild(id)?.name, Icon: ({ id }) => { if (id) return ; diff --git a/frontend/src/components/resources/builder/index.tsx b/frontend/src/components/resources/builder/index.tsx index 8ea80ce9e..18c12f8ef 100644 --- a/frontend/src/components/resources/builder/index.tsx +++ b/frontend/src/components/resources/builder/index.tsx @@ -125,6 +125,7 @@ export const BuilderComponents: RequiredResourceComponents = { }, Name: ({ id }: { id: string }) => <>{useBuilder(id)?.name}, + name: (id) => useBuilder(id)?.name, Icon: () => , diff --git a/frontend/src/components/resources/deployment/index.tsx b/frontend/src/components/resources/deployment/index.tsx index d136e1d9b..2e69929b4 100644 --- a/frontend/src/components/resources/deployment/index.tsx +++ b/frontend/src/components/resources/deployment/index.tsx @@ -40,6 +40,7 @@ export const DeploymentComponents: RequiredResourceComponents = { }, Name: ({ id }) => <>{useDeployment(id)?.name}, + name: (id) => useDeployment(id)?.name, Icon: ({ id }) => { const state = useDeployment(id)?.info.state; diff --git a/frontend/src/components/resources/procedure/index.tsx b/frontend/src/components/resources/procedure/index.tsx index 3df374f0b..2ceba38f2 100644 --- a/frontend/src/components/resources/procedure/index.tsx +++ b/frontend/src/components/resources/procedure/index.tsx @@ -36,6 +36,7 @@ export const ProcedureComponents: RequiredResourceComponents = { Table: ProcedureTable, Name: ({ id }) => <>{useProcedure(id)?.name}, + name: (id) => useProcedure(id)?.name, Icon: () => , diff --git a/frontend/src/components/resources/repo/index.tsx b/frontend/src/components/resources/repo/index.tsx index a7d7277d0..081fac91f 100644 --- a/frontend/src/components/resources/repo/index.tsx +++ b/frontend/src/components/resources/repo/index.tsx @@ -84,6 +84,7 @@ export const RepoComponents: RequiredResourceComponents = { }, Name: ({ id }: { id: string }) => <>{useRepo(id)?.name}, + name: (id) => useRepo(id)?.name, Icon: () => , diff --git a/frontend/src/components/resources/server/index.tsx b/frontend/src/components/resources/server/index.tsx index 12bde5a9a..2a32f1ee0 100644 --- a/frontend/src/components/resources/server/index.tsx +++ b/frontend/src/components/resources/server/index.tsx @@ -38,6 +38,7 @@ export const ServerComponents: RequiredResourceComponents = { Table: ServerTable, Name: ({ id }: { id: string }) => <>{useServer(id)?.name}, + name: (id) => useServer(id)?.name, Icon: ({ id }) => { const status = useServer(id)?.info.status; diff --git a/frontend/src/components/topbar.tsx b/frontend/src/components/topbar.tsx index 58d90ee53..fc5f724fe 100644 --- a/frontend/src/components/topbar.tsx +++ b/frontend/src/components/topbar.tsx @@ -74,6 +74,7 @@ export const Topbar = () => { const PrimaryDropdown = () => { const type = useResourceParamType(); const Components = type && ResourceComponents[type]; + console.log(location.pathname.split("/")); const [icon, title] = Components ? [, type + "s"] @@ -85,7 +86,8 @@ const PrimaryDropdown = () => { ? [, "Tags"] : location.pathname === "/alerts" ? [, "Alerts"] - : location.pathname === "/users" + : location.pathname === "/users" || + location.pathname.split("/")[1] === "users" ? [, "Users"] : [, "Unknown"]; // : [, "Dashboard"]; @@ -177,36 +179,43 @@ const SecondaryDropdown = () => { const type = useResourceParamType(); if (type) return ; - if (location.pathname !== "/") return; - const Icon = ICONS[view]; + if (location.pathname === "/") { + const Icon = ICONS[view]; - return ( - - - - - - - {Object.entries(ICONS).map(([view, Icon]) => ( - setView(view as HomeView)} - > + return ( + + + + + + + {Object.entries(ICONS).map(([view, Icon]) => ( + setView(view as HomeView)} + > + + {view} + + ))} + + + + ); + } + + const [_, base, user_id] = location.pathname.split("/"); + + if (base === "users") { + return ; + } }; const ResourcesDropdown = ({ type }: { type: UsableResource }) => { @@ -274,3 +283,83 @@ const ResourcesDropdown = ({ type }: { type: UsableResource }) => { ); }; + +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 ( + + + + + + + + + + No Users Found + + + + + { + setOpen(false); + nav(`/users`); + }} + > + + + {users?.map((user) => ( + { + setOpen(false); + nav(`/users/${user._id?.$oid}`); + }} + > + + + ))} + + + + + + ); +}; + +const UserAvatar = ({ avatar }: { avatar: string | undefined }) => + avatar ? ( + Avatar + ) : ( + + ); diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 1242c513d..1cebcb3d7 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,3 +1,4 @@ +import { ResourceComponents } from "@components/resources"; import { Types } from "@monitor/client"; import { UsableResource } from "@types"; 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) { return major === 0 && minor === 0 && patch === 0; } + +export function resource_name(type: UsableResource, id: string) { + const Components = ResourceComponents[type]; + return Components.name(id) +} \ No newline at end of file diff --git a/frontend/src/pages/home/dashboard.tsx b/frontend/src/pages/home/dashboard.tsx index 207e4682e..8534b3749 100644 --- a/frontend/src/pages/home/dashboard.tsx +++ b/frontend/src/pages/home/dashboard.tsx @@ -1,12 +1,10 @@ import { Page, Section } from "@components/layouts"; -import { Box, History } from "lucide-react"; -import { useNavigate } from "react-router-dom"; -import { Card, CardContent } from "@ui/card"; -import { TagsSummary } from "@components/dashboard/tags"; -import { ApiKeysSummary } from "@components/dashboard/api-keys"; +import { Box, History, Key, Tag } from "lucide-react"; +import { Link, useNavigate } from "react-router-dom"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { ResourceComponents } from "@components/resources"; import { OpenAlerts } from "@components/alert"; -import { useUser } from "@lib/hooks"; +import { useRead, useUser } from "@lib/hooks"; import { ResourceLink } from "@components/resources/common"; import { Fragment } from "react"; @@ -79,3 +77,43 @@ const RecentlyViewed = () => { ); }; + +const TagsSummary = () => { + const tags_count = useRead("ListTags", {}).data?.length; + + return ( + + + +
+
+ Tags + {tags_count} Total +
+ +
+
+
+ + ); +}; + +const ApiKeysSummary = () => { + const keys_count = useRead("ListApiKeys", {}).data?.length; + + return ( + + + +
+
+ Api Keys + {keys_count} Total +
+ +
+
+
+ + ); +}; diff --git a/frontend/src/pages/users.tsx b/frontend/src/pages/users.tsx index bc5adf61e..07bb7c53f 100644 --- a/frontend/src/pages/users.tsx +++ b/frontend/src/pages/users.tsx @@ -1,11 +1,13 @@ import { Page, Section } from "@components/layouts"; +import { ResourceComponents } from "@components/resources"; import { ResourceLink } from "@components/resources/common"; import { ConfirmButton } from "@components/util"; import { text_color_class_by_intention } from "@lib/color"; import { useInvalidate, useRead, useSetTitle, useWrite } from "@lib/hooks"; +import { resource_name } from "@lib/utils"; import { Types } from "@monitor/client"; import { UsableResource } from "@types"; -import { DataTable } from "@ui/data-table"; +import { DataTable, SortableHeader } from "@ui/data-table"; import { Input } from "@ui/input"; import { Label } from "@ui/label"; import { @@ -282,6 +284,12 @@ const PermissionsTable = () => { title="Permissions" actions={
+ setSearch(e.target.value)} + className="w-[300px]" + />
setShowNone(!showNone)} @@ -289,37 +297,66 @@ const PermissionsTable = () => {
- setSearch(e.target.value)} - className="w-48" - />
} > - showNone ? true : permission.level !== Types.PermissionLevel.None - ) - .filter((permission) => + permissions?.filter( + (permission) => + (showNone + ? true + : permission.level !== Types.PermissionLevel.None) && searchSplit.every( (search) => permission.name.toLowerCase().includes(search) || permission.resource_target.type.toLowerCase().includes(search) ) - ) ?? [] + ) ?? [] } columns={[ { - header: "Resource", accessorKey: "resource_target.type", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const Components = + ResourceComponents[ + row.original.resource_target.type as UsableResource + ]; + return ( +
+ + {row.original.resource_target.type} +
+ ); + }, }, { - 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 }) => ( + + ), cell: ({ row: { original: { resource_target }, @@ -334,7 +371,10 @@ const PermissionsTable = () => { }, }, { - header: "Level", + accessorKey: "level", + header: ({ column }) => ( + + ), cell: ({ row: { original: permission } }) => (