forked from github-starred/komodo
refactor component layout
This commit is contained in:
@@ -5,7 +5,7 @@ import { ChevronRight, Circle, LogOut } from "lucide-react";
|
||||
import { Link, useLocation, useParams } from "react-router-dom";
|
||||
|
||||
import { useUser } from "@hooks";
|
||||
import { ServerName } from "@pages/server";
|
||||
import { ServerName } from "@resources/server/util";
|
||||
import { DeploymentName } from "@resources/deployment/util";
|
||||
|
||||
export const Paths = () => {
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { Types } from "@monitor/client";
|
||||
import { client } from "./main";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { WriteResponses } from "@monitor/client/dist/responses";
|
||||
|
||||
export const useRead = <T extends Types.ReadRequest>(req: T) =>
|
||||
useQuery([req], () => client.read(req));
|
||||
|
||||
export const useWrite = <T extends Types.WriteRequest>() =>
|
||||
useMutation((req: T) => client.write(req));
|
||||
export const useWrite = <
|
||||
T extends Types.WriteRequest["type"],
|
||||
P = Extract<Types.WriteRequest, { type: T }>["params"]
|
||||
>(
|
||||
type: T
|
||||
) =>
|
||||
useMutation(
|
||||
[type],
|
||||
async (params: P) =>
|
||||
(await client.write({ type, params } as any)) as WriteResponses[T]
|
||||
);
|
||||
|
||||
export const useExecute = <T extends Types.ExecuteRequest>() =>
|
||||
useMutation((req: T) => client.execute(req));
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@ui/card";
|
||||
import { ReactNode } from "react";
|
||||
import { useRead } from "@hooks";
|
||||
import { DeploymentCard } from "@resources/deployment/card";
|
||||
import { Button } from "@ui/button";
|
||||
import { Input } from "@ui/input";
|
||||
import { PlusCircle } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export const Deployments = () => {
|
||||
const deployments = useRead({ type: "ListDeployments", params: {} }).data;
|
||||
const [search, set] = useState("");
|
||||
|
||||
export const Resources = ({
|
||||
title,
|
||||
newButton,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
newButton: ReactNode;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row justify-between">
|
||||
<CardTitle className="text-3xl">{title}</CardTitle>
|
||||
{newButton}
|
||||
</CardHeader>
|
||||
<CardContent className="h-fit min-h-[50vh] max-h-[70vh] overflow-auto">
|
||||
<div className="grid gap-4 md:grid-cols-2">{children}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex flex-col gap-12">
|
||||
<div className="flex justify-between">
|
||||
<h1 className="text-3xl">Deployments</h1>
|
||||
<div className="flex gap-4">
|
||||
<Input
|
||||
className="w-[300px]"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(e) => set(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
className="w-[200px] flex items-center gap-2"
|
||||
variant="outline"
|
||||
intent="success"
|
||||
>
|
||||
<PlusCircle className="w-4 h-4 text-green-500" />
|
||||
New Deployment
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-8">
|
||||
{deployments?.map(
|
||||
({ id, name }) =>
|
||||
(search.includes(name) || name.includes(search)) && (
|
||||
<DeploymentCard key={id} id={id} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { Resource } from "@layouts/resource";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useExecute, useRead, useSetRecentlyViewed } from "@hooks";
|
||||
import { ActionButton } from "@components/util";
|
||||
import { Hammer } from "lucide-react";
|
||||
import { version_to_string } from "@util/helpers";
|
||||
|
||||
export const BuildName = ({ id }: { id: string }) => {
|
||||
const builds = useRead({ type: "ListBuilds", params: {} }).data;
|
||||
const build = builds?.find((b) => b.id === id);
|
||||
return <>{build?.name ?? "..."}</>;
|
||||
};
|
||||
|
||||
export const BuildVersion = ({ id }: { id: string }) => {
|
||||
const builds = useRead({ type: "ListBuilds", params: {} }).data;
|
||||
const build = builds?.find((b) => b.id === id);
|
||||
return <>{version_to_string(build?.version) ?? "..."}</>;
|
||||
};
|
||||
|
||||
export const RebuildBuild = ({ buildId }: { buildId: string }) => {
|
||||
const { mutate, isLoading } = useExecute();
|
||||
return (
|
||||
<ActionButton
|
||||
title="Build"
|
||||
intent="success"
|
||||
icon={<Hammer className="h-4 w-4" />}
|
||||
onClick={() =>
|
||||
mutate({ type: "RunBuild", params: { build_id: buildId } })
|
||||
}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Build = () => {
|
||||
const { buildId } = useParams();
|
||||
const push = useSetRecentlyViewed();
|
||||
|
||||
if (!buildId) return null;
|
||||
push("Build", buildId);
|
||||
|
||||
return (
|
||||
<Resource
|
||||
title={<BuildName id={buildId} />}
|
||||
info={<BuildVersion id={buildId} />}
|
||||
actions={<RebuildBuild buildId={buildId} />}
|
||||
tabs={[
|
||||
{
|
||||
title: "Config",
|
||||
component: "config",
|
||||
},
|
||||
{
|
||||
title: "Builder",
|
||||
component: "builder",
|
||||
},
|
||||
{
|
||||
title: "Updates",
|
||||
component: "updates",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useGetRecentlyViewed } from "@hooks";
|
||||
import { BuildCard, DeploymentCard, NewDeployment, ServerCard } from "..";
|
||||
import { BuildCard, ServerCard } from "..";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
} from "@ui/dropdown";
|
||||
import { Button } from "@ui/button";
|
||||
import { ChevronDown, PlusCircle } from "lucide-react";
|
||||
import { DeploymentCard } from "@resources/deployment/card";
|
||||
import { NewDeployment } from "@resources/deployment/new";
|
||||
|
||||
const NewButton = () => {
|
||||
const [open, set] = useState<"deployment" | "build" | "server" | boolean>(
|
||||
@@ -38,13 +40,21 @@ const NewButton = () => {
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => set("deployment")}>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => set("deployment")}
|
||||
>
|
||||
Deployment
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => set("build")}>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => set("build")}
|
||||
>
|
||||
Build
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem> Server </DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer">
|
||||
Server
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -21,52 +21,7 @@ import {
|
||||
} from "@ui/dialog";
|
||||
import { useState } from "react";
|
||||
import { RecentlyViewed } from "./components/recently-viewed";
|
||||
import { ServerStats, ServerStatusIcon } from "@pages/server";
|
||||
|
||||
export const NewDeployment = ({
|
||||
open,
|
||||
set,
|
||||
}: {
|
||||
open: boolean;
|
||||
set: (b: boolean) => void;
|
||||
}) => {
|
||||
const { mutate } = useWrite();
|
||||
const [name, setName] = useState("");
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={set}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Deployment</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>Deployment Name</div>
|
||||
<Input
|
||||
className="max-w-[50%]"
|
||||
placeholder="Deployment Name"
|
||||
name={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
intent="success"
|
||||
onClick={() => {
|
||||
mutate({
|
||||
type: "CreateDeployment",
|
||||
params: { name, config: {} },
|
||||
});
|
||||
set(false);
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
import { ServerStats, ServerStatusIcon } from "@resources/server/util";
|
||||
|
||||
const NewBuild = ({
|
||||
open,
|
||||
@@ -113,24 +68,6 @@ const NewBuild = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const DeploymentCard = ({ id }: { id: string }) => {
|
||||
const deployments = useRead({ type: "ListDeployments", params: {} }).data;
|
||||
const deployment = deployments?.find((d) => d.id === id);
|
||||
if (!deployment) return null;
|
||||
return (
|
||||
<Link to={`/deployments/${deployment.id}`}>
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>{deployment.name}</CardTitle>
|
||||
<CardDescription>
|
||||
{deployment.status ?? "not deployed"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const ServerCard = ({ id }: { id: string }) => {
|
||||
const servers = useRead({ type: "ListServers", params: {} }).data;
|
||||
const server = servers?.find((server) => server.id === id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useRead } from "@hooks";
|
||||
import { DeploymentCard } from "@pages/dashboard";
|
||||
import { DeploymentCard } from "@resources/deployment/card";
|
||||
import { NewDeployment } from "@resources/deployment/new";
|
||||
import { Button } from "@ui/button";
|
||||
import { Input } from "@ui/input";
|
||||
import { PlusCircle } from "lucide-react";
|
||||
@@ -7,7 +8,8 @@ import { useState } from "react";
|
||||
|
||||
export const Deployments = () => {
|
||||
const deployments = useRead({ type: "ListDeployments", params: {} }).data;
|
||||
const [search, set] = useState("");
|
||||
const [search, setSearch] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-12">
|
||||
@@ -18,16 +20,18 @@ export const Deployments = () => {
|
||||
className="w-[300px]"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(e) => set(e.target.value)}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
className="w-[200px] flex items-center gap-2"
|
||||
variant="outline"
|
||||
intent="success"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<PlusCircle className="w-4 h-4 text-green-500" />
|
||||
New Deployment
|
||||
</Button>
|
||||
<NewDeployment open={open} set={setOpen} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-8">
|
||||
|
||||
18
frontend/src/resources/build/components/actions.tsx
Normal file
18
frontend/src/resources/build/components/actions.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ActionButton } from "@components/util";
|
||||
import { useExecute } from "@hooks";
|
||||
import { Hammer } from "lucide-react";
|
||||
|
||||
export const RebuildBuild = ({ buildId }: { buildId: string }) => {
|
||||
const { mutate, isLoading } = useExecute();
|
||||
return (
|
||||
<ActionButton
|
||||
intent="success"
|
||||
title="Build"
|
||||
icon={<Hammer className="h-4 w-4" />}
|
||||
onClick={() =>
|
||||
mutate({ type: "RunBuild", params: { build_id: buildId } })
|
||||
}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
35
frontend/src/resources/build/page.tsx
Normal file
35
frontend/src/resources/build/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useSetRecentlyViewed } from "@hooks";
|
||||
import { Resource } from "@layouts/resource";
|
||||
import { BuildName, BuildVersion } from "./util";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { RebuildBuild } from "./components/actions";
|
||||
|
||||
export const Build = () => {
|
||||
const { buildId } = useParams();
|
||||
const push = useSetRecentlyViewed();
|
||||
|
||||
if (!buildId) return null;
|
||||
push("Build", buildId);
|
||||
|
||||
return (
|
||||
<Resource
|
||||
title={<BuildName id={buildId} />}
|
||||
info={<BuildVersion id={buildId} />}
|
||||
actions={<RebuildBuild buildId={buildId} />}
|
||||
tabs={[
|
||||
{
|
||||
title: "Config",
|
||||
component: "config",
|
||||
},
|
||||
{
|
||||
title: "Builder",
|
||||
component: "builder",
|
||||
},
|
||||
{
|
||||
title: "Updates",
|
||||
component: "updates",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
14
frontend/src/resources/build/util.tsx
Normal file
14
frontend/src/resources/build/util.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useRead } from "@hooks";
|
||||
import { version_to_string } from "@util/helpers";
|
||||
|
||||
export const BuildName = ({ id }: { id: string }) => {
|
||||
const builds = useRead({ type: "ListBuilds", params: {} }).data;
|
||||
const build = builds?.find((b) => b.id === id);
|
||||
return <>{build?.name ?? "..."}</>;
|
||||
};
|
||||
|
||||
export const BuildVersion = ({ id }: { id: string }) => {
|
||||
const builds = useRead({ type: "ListBuilds", params: {} }).data;
|
||||
const build = builds?.find((b) => b.id === id);
|
||||
return <>{version_to_string(build?.version) ?? "..."}</>;
|
||||
};
|
||||
21
frontend/src/resources/deployment/card.tsx
Normal file
21
frontend/src/resources/deployment/card.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useRead } from "@hooks";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export const DeploymentCard = ({ id }: { id: string }) => {
|
||||
const deployments = useRead({ type: "ListDeployments", params: {} }).data;
|
||||
const deployment = deployments?.find((d) => d.id === id);
|
||||
if (!deployment) return null;
|
||||
return (
|
||||
<Link to={`/deployments/${deployment.id}`}>
|
||||
<Card hoverable>
|
||||
<CardHeader>
|
||||
<CardTitle>{deployment.name}</CardTitle>
|
||||
<CardDescription>
|
||||
{deployment.status ?? "not deployed"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
56
frontend/src/resources/deployment/new.tsx
Normal file
56
frontend/src/resources/deployment/new.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@ui/dialog";
|
||||
import { Input } from "@ui/input";
|
||||
import { useWrite } from "@hooks";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const NewDeployment = ({
|
||||
open,
|
||||
set,
|
||||
}: {
|
||||
open: boolean;
|
||||
set: (b: boolean) => void;
|
||||
}) => {
|
||||
const nav = useNavigate();
|
||||
const { mutateAsync } = useWrite("CreateDeployment");
|
||||
const [name, setName] = useState("");
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={set}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Deployment</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>Deployment Name</div>
|
||||
<Input
|
||||
className="max-w-[50%]"
|
||||
placeholder="Deployment Name"
|
||||
name={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
intent="success"
|
||||
onClick={async () => {
|
||||
const { _id } = await mutateAsync({ name, config: {} });
|
||||
nav(`/deployments/${_id?.$oid}`);
|
||||
set(false);
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
40
frontend/src/resources/server/page.tsx
Normal file
40
frontend/src/resources/server/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useSetRecentlyViewed } from "@hooks";
|
||||
import { Resource } from "@layouts/resource";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { ServerInfo, ServerName } from "./util";
|
||||
|
||||
export const Server = () => {
|
||||
const { serverId } = useParams();
|
||||
const push = useSetRecentlyViewed();
|
||||
|
||||
// if (!serverId) return null;
|
||||
// push("Server", serverId!);
|
||||
if (!serverId) return null;
|
||||
push("Server", serverId);
|
||||
|
||||
return (
|
||||
<Resource
|
||||
title={<ServerName serverId={serverId} />}
|
||||
info={<ServerInfo serverId={serverId} />}
|
||||
actions=""
|
||||
tabs={[
|
||||
{
|
||||
title: "Config",
|
||||
component: <>config</>,
|
||||
},
|
||||
{
|
||||
title: "Deployments",
|
||||
component: <>server deployments</>,
|
||||
},
|
||||
{
|
||||
title: "Stats",
|
||||
component: "server stats",
|
||||
},
|
||||
{
|
||||
title: "Updates",
|
||||
component: <>updates</>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useRead, useSetRecentlyViewed } from "@hooks";
|
||||
import { Resource } from "@layouts/resource";
|
||||
import { useRead } from "@hooks";
|
||||
import { ServerStatus } from "@monitor/client/dist/types";
|
||||
import { CardDescription } from "@ui/card";
|
||||
import { cn } from "@util/helpers";
|
||||
import { Circle, Cpu, Database, MemoryStick } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
export const ServerName = ({ serverId }: { serverId: string | undefined }) => {
|
||||
const servers = useRead({ type: "ListServers", params: {} }).data;
|
||||
@@ -80,39 +78,3 @@ export const ServerStatusIcon = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Server = () => {
|
||||
const { serverId } = useParams();
|
||||
const push = useSetRecentlyViewed();
|
||||
|
||||
// if (!serverId) return null;
|
||||
// push("Server", serverId!);
|
||||
if (!serverId) return null;
|
||||
push("Server", serverId);
|
||||
|
||||
return (
|
||||
<Resource
|
||||
title={<ServerName serverId={serverId} />}
|
||||
info={<ServerInfo serverId={serverId} />}
|
||||
actions=""
|
||||
tabs={[
|
||||
{
|
||||
title: "Config",
|
||||
component: <>config</>,
|
||||
},
|
||||
{
|
||||
title: "Deployments",
|
||||
component: <>server deployments</>,
|
||||
},
|
||||
{
|
||||
title: "Stats",
|
||||
component: "server stats",
|
||||
},
|
||||
{
|
||||
title: "Updates",
|
||||
component: <>updates</>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -3,12 +3,12 @@ import { Layout } from "@layouts/layout";
|
||||
import { Login } from "@pages/auth/login";
|
||||
import { Signup } from "@pages/auth/signup";
|
||||
import { Dashboard } from "@pages/dashboard";
|
||||
import { Server } from "@pages/server";
|
||||
import { Servers } from "@pages/servers";
|
||||
import { Deployments } from "@pages/deployments";
|
||||
import { Builds } from "@pages/builds";
|
||||
import { Build } from "@pages/build";
|
||||
import { Deployment } from "@resources/deployment/page";
|
||||
import { Server } from "@resources/server/page";
|
||||
import { Build } from "@resources/build/page";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user