feat: add cluster & bucket management

This commit is contained in:
2024-08-16 01:23:55 +07:00
parent b0e5d53ee0
commit dfb4e30e23
41 changed files with 1394 additions and 67 deletions
+40
View File
@@ -0,0 +1,40 @@
import { Cylinder, HardDrive, LayoutDashboard } from "lucide-react";
import { Menu } from "react-daisyui";
import { Link } from "react-router-dom";
const Sidebar = () => {
return (
<aside className="bg-base-100 border-r border-base-300/30 w-[220px] overflow-y-auto">
<div className="p-4">
<img
src="https://garagehq.deuxfleurs.fr/images/garage-logo.svg"
alt="logo"
className="w-full max-w-[100px] mx-auto"
/>
<p className="text-sm font-medium text-center">WebUI</p>
</div>
<Menu className="gap-y-1">
<Menu.Item>
<Link to="/">
<LayoutDashboard />
<p>Dashboard</p>
</Link>
</Menu.Item>
<Menu.Item>
<Link to="/cluster">
<HardDrive />
<p>Cluster</p>
</Link>
</Menu.Item>
<Menu.Item>
<Link to="/buckets">
<Cylinder />
<p>Buckets</p>
</Link>
</Menu.Item>
</Menu>
</aside>
);
};
export default Sidebar;
+64
View File
@@ -0,0 +1,64 @@
import { cn } from "@/lib/utils";
import { LucideIcon } from "lucide-react";
import { useMemo } from "react";
import { Tabs } from "react-daisyui";
import { useSearchParams } from "react-router-dom";
export type Tab = {
name: string;
title?: string;
icon?: LucideIcon;
Component?: () => JSX.Element;
};
type Props = {
tabs: Tab[];
name?: string;
className?: string;
contentClassName?: string;
};
const TabView = ({
tabs,
name = "tab",
className,
contentClassName,
}: Props) => {
const [searchParams, setSearchParams] = useSearchParams();
const curTab = searchParams.get(name) || tabs[0].name;
const content = useMemo(() => {
const Comp = tabs.find((tab) => tab.name === curTab)?.Component;
return Comp ? <Comp /> : null;
}, [curTab, tabs]);
return (
<>
<Tabs
variant="boxed"
className={cn("w-auto inline-flex flex-row items-stretch", className)}
>
{tabs.map(({ icon: Icon, ...tab }) => (
<Tabs.Tab
key={tab.name}
active={curTab === tab.name}
className="flex flex-row items-center gap-x-2 h-auto"
onClick={() => {
setSearchParams((params) => {
params.set(name, tab.name);
return params;
});
}}
>
{Icon ? <Icon size={20} /> : null}
<span>{tab.title || tab.name}</span>
</Tabs.Tab>
))}
</Tabs>
<div className={cn("mt-4", contentClassName)}>{content}</div>
</>
);
};
export default TabView;
+21 -37
View File
@@ -1,8 +1,9 @@
import { PageContext } from "@/context/page-context";
import { useContext } from "react";
import { Link, Outlet } from "react-router-dom";
import { Menu } from "react-daisyui";
import { HardDrive, LayoutDashboard } from "lucide-react";
import { Suspense, useContext } from "react";
import { Outlet, useNavigate } from "react-router-dom";
import Sidebar from "../containers/sidebar";
import { ArrowLeft } from "lucide-react";
import Button from "../ui/button";
const MainLayout = () => {
return (
@@ -13,50 +14,33 @@ const MainLayout = () => {
<Header />
<main className="flex-1 overflow-y-auto p-4 md:p-8">
<div className="container max-w-5xl">
<Suspense>
<Outlet />
</div>
</Suspense>
</main>
</div>
</div>
);
};
const Sidebar = () => {
return (
<aside className="bg-base-100 border-r border-base-300/30 w-[220px] overflow-y-auto">
<div className="p-4">
<img
src="https://garagehq.deuxfleurs.fr/images/garage-logo.svg"
alt="logo"
className="w-full max-w-[100px] mx-auto"
/>
<p className="text-sm font-medium text-center">WebUI</p>
</div>
<Menu className="gap-y-1">
<Menu.Item>
<Link to="/">
<LayoutDashboard />
<p>Dashboard</p>
</Link>
</Menu.Item>
<Menu.Item>
<Link to="/cluster">
<HardDrive />
<p>Cluster</p>
</Link>
</Menu.Item>
</Menu>
</aside>
);
};
const Header = () => {
const page = useContext(PageContext);
const navigate = useNavigate();
return (
<header className="bg-base-100 p-4 md:p-8 md:py-6 flex flex-row items-center gap-4">
<h1 className="text-2xl font-medium">{page?.title || "Dashboard"}</h1>
<header className="bg-base-100 px-4 h-16 md:px-8 md:h-20 flex flex-row items-center gap-4">
{page?.prev ? (
<Button
href={page.prev}
onClick={() => navigate(page.prev!, { replace: true })}
color="ghost"
shape="circle"
className="-ml-4"
>
<ArrowLeft />
</Button>
) : null}
<h1 className="text-xl">{page?.title || "Dashboard"}</h1>
</header>
);
};
+23
View File
@@ -0,0 +1,23 @@
import { ComponentPropsWithoutRef, forwardRef } from "react";
import { Button as BaseButton } from "react-daisyui";
import { Link } from "react-router-dom";
type ButtonProps = ComponentPropsWithoutRef<typeof BaseButton> & {
href?: string;
target?: "_blank" | "_self" | "_parent" | "_top";
};
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ href, ...props }, ref) => {
return (
<BaseButton
ref={ref}
tag={href ? Link : undefined}
{...props}
{...(href ? { to: href } : {})}
/>
);
}
);
export default Button;
+41
View File
@@ -0,0 +1,41 @@
import { cn } from "@/lib/utils";
import { X } from "lucide-react";
import React, { forwardRef } from "react";
import { Button } from "react-daisyui";
type Props = React.ComponentPropsWithoutRef<"div"> & {
onClick?: () => void;
onRemove?: () => void;
};
const Chips = forwardRef<HTMLDivElement, Props>(
({ className, children, onRemove, ...props }, ref) => {
const Comp = props.onClick ? "button" : "div";
return (
<Comp
ref={ref as never}
className={cn(
"inline-flex flex-row items-center h-8 px-4 rounded-full text-sm border border-primary/80 text-base-content cursor-default",
className
)}
{...(props as any)}
>
{children}
{onRemove ? (
<Button
color="ghost"
shape="circle"
size="sm"
className="-mr-3"
onClick={onRemove}
>
<X size={16} />
</Button>
) : null}
</Comp>
);
}
);
export default Chips;