forked from github-starred/komodo
improve topbar navigation
This commit is contained in:
@@ -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 } }) => (
|
||||
<ConfirmButton
|
||||
title="Delete"
|
||||
title="Delete Row"
|
||||
icon={<Trash2 className="w-4 h-4" />}
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
@@ -343,6 +343,7 @@ const ExecutionTypeSelector = ({
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty className="flex justify-evenly items-center">
|
||||
Empty.
|
||||
<SearchX className="w-3 h-3" />
|
||||
@@ -368,6 +369,7 @@ const ExecutionTypeSelector = ({
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -52,7 +52,10 @@ export const ServerComponents: RequiredResourceComponents = {
|
||||
},
|
||||
Actions: SERVER_ACTIONS,
|
||||
Page: {
|
||||
Stats: ({ id }) => <ServerStats server_id={id} />,
|
||||
Stats: ({ id }) => {
|
||||
const status = useServer(id)?.info.status;
|
||||
return status === "Ok" && <ServerStats server_id={id} />;
|
||||
},
|
||||
Deployments: ({ id }) => {
|
||||
const deployments = useRead("ListDeployments", {}).data?.filter(
|
||||
(deployment) => deployment.info.server_id === id
|
||||
|
||||
@@ -54,8 +54,8 @@ export const TagsFilter = () => {
|
||||
?.filter((tag) => !tags.includes(tag._id!.$oid))
|
||||
.map((tag) => (
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
key={tag.name}
|
||||
className="cursor-pointer"
|
||||
onClick={() => setTags([...tags, tag._id!.$oid])}
|
||||
>
|
||||
{tag.name}
|
||||
|
||||
@@ -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 (
|
||||
<div className="sticky top-0 border-b bg-background z-50 w-full">
|
||||
<div className="container flex items-center justify-between py-4 gap-8">
|
||||
@@ -28,8 +50,8 @@ export const Topbar = () => {
|
||||
MONITOR
|
||||
</Link>
|
||||
<div className="flex gap-2">
|
||||
<ResourceTypeDropdown />
|
||||
{type && <ResourcesDropdown />}
|
||||
<PrimaryDropdown />
|
||||
<SecondaryDropdown />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex md:gap-4">
|
||||
@@ -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
|
||||
? [<Components.Icon />, type + "s"]
|
||||
: location.pathname === "/tree"
|
||||
? [<FolderTree className="w-4 h-4" />, "Tree"]
|
||||
: location.pathname === "/"
|
||||
? [<Home className="w-4 h-4" />, "Home"]
|
||||
: location.pathname === "/keys"
|
||||
? [<Key className="w-4 h-4" />, "Api Keys"]
|
||||
: location.pathname === "/tags"
|
||||
? [<Tag className="w-4 h-4" />, "Tags"]
|
||||
: location.pathname === "/users"
|
||||
? [<UserCircle2 className="w-4 h-4" />, "Users"]
|
||||
: [<Box className="w-4 h-4" />, "Dashboard"];
|
||||
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
|
||||
// : [<Box className="w-4 h-4" />, "Dashboard"];
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -77,23 +100,8 @@ const ResourceTypeDropdown = () => {
|
||||
<DropdownMenuGroup>
|
||||
<Link to="/">
|
||||
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
|
||||
<Box className="w-4 h-4" />
|
||||
Dashboard
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<Link to="/resources">
|
||||
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
|
||||
<Boxes className="w-4 h-4" />
|
||||
Resources
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link to="/tree">
|
||||
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
|
||||
<FolderTree className="w-4 h-4" />
|
||||
Tree
|
||||
<Home className="w-4 h-4" />
|
||||
Home
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
@@ -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<HomeView>(
|
||||
"home-view-v1",
|
||||
"Dashboard"
|
||||
);
|
||||
|
||||
const ICONS = {
|
||||
Dashboard: () => <Box className="w-4 h-4" />,
|
||||
Tree: () => <FolderTree className="w-4 h-4" />,
|
||||
Resources: () => <Boxes className="w-4 h-4" />,
|
||||
};
|
||||
|
||||
const SecondaryDropdown = () => {
|
||||
const [view, setView] = useAtom(homeViewAtom);
|
||||
|
||||
const type = useResourceParamType();
|
||||
if (type) return <ResourcesDropdown type={type} />;
|
||||
if (location.pathname !== "/") return;
|
||||
|
||||
const Icon = ICONS[view];
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="w-48 justify-between px-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Components.Icon id={selected?.id} />
|
||||
{selected ? selected.name : `All ${type}s`}
|
||||
<Icon />
|
||||
{view}
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-48" side="bottom">
|
||||
<DropdownMenuGroup>
|
||||
<Link to={`/${type.toLowerCase()}s`}>
|
||||
<DropdownMenuItem className="flex items-center gap-2">
|
||||
<Components.Icon />
|
||||
All {type}s
|
||||
{Object.entries(ICONS).map(([view, Icon]) => (
|
||||
<DropdownMenuItem
|
||||
key={view}
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => setView(view as HomeView)}
|
||||
>
|
||||
<Icon />
|
||||
{view}
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuGroup>
|
||||
{!list?.length && (
|
||||
<DropdownMenuItem disabled>No {type}s Found.</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{list?.map(({ id, name }) => (
|
||||
<Link key={id} to={`/${type.toLowerCase()}s/${id}`}>
|
||||
<DropdownMenuItem className="flex items-center gap-2">
|
||||
<Components.Icon id={id} />
|
||||
{name}
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" className="justify-between w-[300px] px-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Components.Icon id={selected?.id} />
|
||||
{selected ? selected.name : `All ${type}s`}
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] max-h-[400px] p-0" sideOffset={12}>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder={`Search ${type}s`}
|
||||
className="h-9"
|
||||
value={input}
|
||||
onValueChange={setInput}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty className="flex justify-evenly items-center">
|
||||
{`No ${type}s Found`}
|
||||
<SearchX className="w-3 h-3" />
|
||||
</CommandEmpty>
|
||||
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
nav(`/${type.toLowerCase()}s`);
|
||||
}}
|
||||
>
|
||||
<Button variant="link" className="flex gap-2 items-center p-0">
|
||||
<Components.Icon />
|
||||
All {type}s
|
||||
</Button>
|
||||
</CommandItem>
|
||||
{list?.map((resource) => (
|
||||
<CommandItem
|
||||
key={resource.id}
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
nav(`/${type.toLowerCase()}s/${resource.id}`);
|
||||
}}
|
||||
>
|
||||
<Components.Link id={resource.id} />
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ export const object_keys = <T extends object>(o: T): (keyof T)[] =>
|
||||
Object.keys(o) as (keyof T)[];
|
||||
|
||||
export const RESOURCE_TARGETS: UsableResource[] = [
|
||||
"Procedure",
|
||||
"Deployment",
|
||||
"Server",
|
||||
"Build",
|
||||
"Procedure",
|
||||
"Repo",
|
||||
"Builder",
|
||||
"Alerter",
|
||||
|
||||
@@ -37,7 +37,7 @@ export const AllResources = () => {
|
||||
if (!count) return;
|
||||
|
||||
return (
|
||||
<Section title={type + "s"} actions={<Components.New />}>
|
||||
<Section key={type} title={type + "s"} actions={<Components.New />}>
|
||||
<Components.Table />
|
||||
</Section>
|
||||
);
|
||||
17
frontend/src/pages/home/index.tsx
Normal file
17
frontend/src/pages/home/index.tsx
Normal file
@@ -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 <Dashboard />;
|
||||
case "Resources":
|
||||
return <AllResources />;
|
||||
case "Tree":
|
||||
return <Tree />;
|
||||
}
|
||||
};
|
||||
@@ -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.Icon id={id} />
|
||||
<Components.Status id={id} />
|
||||
</div>
|
||||
{Components.Info.map((Info) => (
|
||||
{Components.Info.map((Info, i) => (
|
||||
<>
|
||||
| <Info id={id} />
|
||||
| <Info key={i} id={id} />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
@@ -40,8 +40,8 @@ export const Resource = () => {
|
||||
}
|
||||
actions={
|
||||
<div className="flex gap-4 items-center">
|
||||
{Components.Actions.map((Action) => (
|
||||
<Action id={id} />
|
||||
{Components.Actions.map((Action, i) => (
|
||||
<Action key={i} id={id} />
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export const Resource = () => {
|
||||
<ResourceUpdates type={type} id={id} />
|
||||
{/* <ResourcePermissions type={type} id={id} /> */}
|
||||
{Object.entries(Components.Page).map(([section, Component]) => (
|
||||
<Component id={id} key={section} />
|
||||
<Component key={section} id={id} />
|
||||
))}
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -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: <Layout />,
|
||||
children: [
|
||||
{ path: "", element: <Dashboard /> },
|
||||
{ path: "", element: <Home /> },
|
||||
{ path: "keys", element: <Keys /> },
|
||||
{ path: "tags", element: <Tags /> },
|
||||
{ path: "tree", element: <Tree /> },
|
||||
|
||||
Reference in New Issue
Block a user