From 8f5570128d32ac02f28f0b7a53e004fe0527ac27 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Mon, 1 Apr 2024 02:28:51 -0700 Subject: [PATCH] improve topbar navigation --- .../components/resources/procedure/config.tsx | 56 +++--- .../src/components/resources/server/index.tsx | 5 +- frontend/src/components/tags/index.tsx | 2 +- frontend/src/components/topbar.tsx | 186 +++++++++++++----- frontend/src/lib/hooks.ts | 5 +- frontend/src/lib/utils.ts | 2 +- .../src/pages/{ => home}/all_resources.tsx | 2 +- frontend/src/pages/{ => home}/dashboard.tsx | 0 frontend/src/pages/home/index.tsx | 17 ++ frontend/src/pages/{ => home}/tree.tsx | 0 frontend/src/pages/resource.tsx | 12 +- frontend/src/router.tsx | 8 +- 12 files changed, 199 insertions(+), 96 deletions(-) rename frontend/src/pages/{ => home}/all_resources.tsx (94%) rename frontend/src/pages/{ => home}/dashboard.tsx (100%) create mode 100644 frontend/src/pages/home/index.tsx rename frontend/src/pages/{ => home}/tree.tsx (100%) diff --git a/frontend/src/components/resources/procedure/config.tsx b/frontend/src/components/resources/procedure/config.tsx index 1c1c276b0..6bd41baf7 100644 --- a/frontend/src/components/resources/procedure/config.tsx +++ b/frontend/src/components/resources/procedure/config.tsx @@ -30,6 +30,7 @@ import { SelectValue, } from "@ui/select"; import { Switch } from "@ui/switch"; +import { CommandList } from "cmdk"; import { ArrowDown, ArrowUp, @@ -291,9 +292,8 @@ const ProcedureConfigInner = ({ header: "Delete", cell: ({ row: { index } }) => ( } - variant="destructive" onClick={() => setConfig({ ...config, @@ -343,31 +343,33 @@ const ExecutionTypeSelector = ({ value={search} onValueChange={setSearch} /> - - Empty. - - - - {[ - "RunProcedure", - "RunBuild", - "Deploy", - "StartContainer", - "StopContainer", - "StopAllContainers", - "RemoveContainer", - "CloneRepo", - "PullRepo", - ].map((type) => ( - onSelect(type as Types.Execution["type"])} - className="flex items-center justify-between" - > -
{type}
-
- ))} -
+ + + Empty. + + + + {[ + "RunProcedure", + "RunBuild", + "Deploy", + "StartContainer", + "StopContainer", + "StopAllContainers", + "RemoveContainer", + "CloneRepo", + "PullRepo", + ].map((type) => ( + onSelect(type as Types.Execution["type"])} + className="flex items-center justify-between" + > +
{type}
+
+ ))} +
+
diff --git a/frontend/src/components/resources/server/index.tsx b/frontend/src/components/resources/server/index.tsx index 521f0430f..c49831911 100644 --- a/frontend/src/components/resources/server/index.tsx +++ b/frontend/src/components/resources/server/index.tsx @@ -52,7 +52,10 @@ export const ServerComponents: RequiredResourceComponents = { }, Actions: SERVER_ACTIONS, Page: { - Stats: ({ id }) => , + Stats: ({ id }) => { + const status = useServer(id)?.info.status; + return status === "Ok" && ; + }, Deployments: ({ id }) => { const deployments = useRead("ListDeployments", {}).data?.filter( (deployment) => deployment.info.server_id === id diff --git a/frontend/src/components/tags/index.tsx b/frontend/src/components/tags/index.tsx index 1bc7a3f00..2b2773e5d 100644 --- a/frontend/src/components/tags/index.tsx +++ b/frontend/src/components/tags/index.tsx @@ -54,8 +54,8 @@ export const TagsFilter = () => { ?.filter((tag) => !tags.includes(tag._id!.$oid)) .map((tag) => ( setTags([...tags, tag._id!.$oid])} > {tag.name} diff --git a/frontend/src/components/topbar.tsx b/frontend/src/components/topbar.tsx index 62fd6511d..0adac773a 100644 --- a/frontend/src/components/topbar.tsx +++ b/frontend/src/components/topbar.tsx @@ -1,6 +1,16 @@ import { useRead, useResourceParamType } from "@lib/hooks"; import { ResourceComponents } from "./resources"; -import { Box, Boxes, FolderTree, Key, Tag, UserCircle2 } from "lucide-react"; +import { + Box, + Boxes, + FileQuestion, + FolderTree, + Home, + Key, + SearchX, + Tag, + UserCircle2, +} from "lucide-react"; import { DropdownMenu, DropdownMenuContent, @@ -10,16 +20,28 @@ import { DropdownMenuTrigger, } from "@ui/dropdown-menu"; import { Button } from "@ui/button"; -import { Link, useParams } from "react-router-dom"; +import { Link, useNavigate, useParams } from "react-router-dom"; import { RESOURCE_TARGETS } from "@lib/utils"; import { Omnibar } from "./omnibar"; import { WsStatusIndicator } from "@lib/socket"; import { HeaderUpdates } from "./updates/header"; import { Logout } from "./util"; import { ThemeToggle } from "@ui/theme"; +import { UsableResource } from "@types"; +import { atomWithStorage } from "jotai/utils"; +import { useAtom } from "jotai"; +import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; +import { useState } from "react"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@ui/command"; export const Topbar = () => { - const type = useResourceParamType(); return (
@@ -28,8 +50,8 @@ export const Topbar = () => { MONITOR
- - {type && } + +
@@ -47,21 +69,22 @@ export const Topbar = () => { ); }; -const ResourceTypeDropdown = () => { +const PrimaryDropdown = () => { const type = useResourceParamType(); - const Components = ResourceComponents[type]; + const Components = type && ResourceComponents[type]; - const [icon, title] = type + const [icon, title] = Components ? [, type + "s"] - : location.pathname === "/tree" - ? [, "Tree"] + : location.pathname === "/" + ? [, "Home"] : location.pathname === "/keys" ? [, "Api Keys"] : location.pathname === "/tags" ? [, "Tags"] : location.pathname === "/users" ? [, "Users"] - : [, "Dashboard"]; + : [, "Unknown"]; + // : [, "Dashboard"]; return ( @@ -77,23 +100,8 @@ const ResourceTypeDropdown = () => { - - Dashboard - - - - - - - - - Resources - - - - - - Tree + + Home @@ -140,48 +148,118 @@ const ResourceTypeDropdown = () => { ); }; -const ResourcesDropdown = () => { - const type = useResourceParamType(); - const id = useParams().id as string; - const list = useRead(`List${type}s`, {}).data; +export type HomeView = "Dashboard" | "Tree" | "Resources"; - const selected = list?.find((i) => i.id === id); - const Components = ResourceComponents[type]; +export const homeViewAtom = atomWithStorage( + "home-view-v1", + "Dashboard" +); + +const ICONS = { + Dashboard: () => , + Tree: () => , + Resources: () => , +}; + +const SecondaryDropdown = () => { + const [view, setView] = useAtom(homeViewAtom); + + const type = useResourceParamType(); + if (type) return ; + if (location.pathname !== "/") return; + + const Icon = ICONS[view]; return ( - - - - All {type}s + {Object.entries(ICONS).map(([view, Icon]) => ( + setView(view as HomeView)} + > + + {view} - - - - {!list?.length && ( - No {type}s Found. - )} - - {list?.map(({ id, name }) => ( - - - - {name} - - ))} ); }; + +const ResourcesDropdown = ({ type }: { type: UsableResource }) => { + const nav = useNavigate(); + const id = useParams().id as string; + const list = useRead(`List${type}s`, {}).data; + + const [open, setOpen] = useState(false); + const [input, setInput] = useState(""); + + const selected = list?.find((i) => i.id === id); + const Components = ResourceComponents[type]; + + return ( + + + + + + + + + + {`No ${type}s Found`} + + + + + { + setOpen(false); + nav(`/${type.toLowerCase()}s`); + }} + > + + + {list?.map((resource) => ( + { + setOpen(false); + nav(`/${type.toLowerCase()}s/${resource.id}`); + }} + > + + + ))} + + + + + + ); +}; diff --git a/frontend/src/lib/hooks.ts b/frontend/src/lib/hooks.ts index 4b016f511..476bff568 100644 --- a/frontend/src/lib/hooks.ts +++ b/frontend/src/lib/hooks.ts @@ -143,9 +143,12 @@ export const useAuth = < // ============== UTILITY ============== +/** + * Actually returns UsableResoure | undefined + */ export const useResourceParamType = () => { const type = useParams().type; - if (!type) return undefined as unknown as UsableResource; + if (!type) return undefined; return (type[0].toUpperCase() + type.slice(1, -1)) as UsableResource; }; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index c4250e4ba..52e262d19 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -11,10 +11,10 @@ export const object_keys = (o: T): (keyof T)[] => Object.keys(o) as (keyof T)[]; export const RESOURCE_TARGETS: UsableResource[] = [ - "Procedure", "Deployment", "Server", "Build", + "Procedure", "Repo", "Builder", "Alerter", diff --git a/frontend/src/pages/all_resources.tsx b/frontend/src/pages/home/all_resources.tsx similarity index 94% rename from frontend/src/pages/all_resources.tsx rename to frontend/src/pages/home/all_resources.tsx index 092be1905..494da8bc7 100644 --- a/frontend/src/pages/all_resources.tsx +++ b/frontend/src/pages/home/all_resources.tsx @@ -37,7 +37,7 @@ export const AllResources = () => { if (!count) return; return ( -
}> +
}>
); diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/home/dashboard.tsx similarity index 100% rename from frontend/src/pages/dashboard.tsx rename to frontend/src/pages/home/dashboard.tsx diff --git a/frontend/src/pages/home/index.tsx b/frontend/src/pages/home/index.tsx new file mode 100644 index 000000000..9aeee8fb1 --- /dev/null +++ b/frontend/src/pages/home/index.tsx @@ -0,0 +1,17 @@ +import { homeViewAtom } from "@components/topbar"; +import { useAtom } from "jotai"; +import { Dashboard } from "./dashboard"; +import { AllResources } from "./all_resources"; +import { Tree } from "./tree"; + +export const Home = () => { + const [view, _] = useAtom(homeViewAtom); + switch (view) { + case "Dashboard": + return ; + case "Resources": + return ; + case "Tree": + return ; + } +}; diff --git a/frontend/src/pages/tree.tsx b/frontend/src/pages/home/tree.tsx similarity index 100% rename from frontend/src/pages/tree.tsx rename to frontend/src/pages/home/tree.tsx diff --git a/frontend/src/pages/resource.tsx b/frontend/src/pages/resource.tsx index 63b6061d2..25ba3bdaa 100644 --- a/frontend/src/pages/resource.tsx +++ b/frontend/src/pages/resource.tsx @@ -6,7 +6,7 @@ import { usePushRecentlyViewed, useResourceParamType } from "@lib/hooks"; import { useParams } from "react-router-dom"; export const Resource = () => { - const type = useResourceParamType(); + const type = useResourceParamType()!; const id = useParams().id as string; usePushRecentlyViewed({ type, id }); @@ -30,9 +30,9 @@ export const Resource = () => {
- {Components.Info.map((Info) => ( + {Components.Info.map((Info, i) => ( <> - | + | ))}
@@ -40,8 +40,8 @@ export const Resource = () => { } actions={
- {Components.Actions.map((Action) => ( - + {Components.Actions.map((Action, i) => ( + ))}
} @@ -49,7 +49,7 @@ export const Resource = () => { {/* */} {Object.entries(Components.Page).map(([section, Component]) => ( - + ))} ); diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 442fe50aa..75ee44e65 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -1,24 +1,24 @@ import { Layout } from "@components/layouts"; import { useUser } from "@lib/hooks"; -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"; -import { Tree } from "@pages/tree"; +import { Tree } from "@pages/home/tree"; import { Tags } from "@pages/tags"; import { ResourceUpdates } from "@pages/resource_update"; import { UserPage, UsersPage } from "@pages/users"; -import { AllResources } from "@pages/all_resources"; +import { AllResources } from "@pages/home/all_resources"; import { UserDisabled } from "@pages/user_disabled"; +import { Home } from "@pages/home"; const router = createBrowserRouter([ { path: "/", element: , children: [ - { path: "", element: }, + { path: "", element: }, { path: "keys", element: }, { path: "tags", element: }, { path: "tree", element: },