mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-07 20:45:17 +00:00
refactor: move cluster nodes into docker dashboard swarm tab
- Add Nodes sub-tab under Docker > Swarm (visible with organization.update permission) - Remove Cluster item from settings sidebar and delete /dashboard/settings/cluster page - Point internal cluster links to /dashboard/docker?tab=swarm&subtab=nodes - Make ShowNodes card full-width to match the other docker tabs
This commit is contained in:
+1
-1
@@ -187,7 +187,7 @@ export const ShowClusterSettings = ({ id, type }: Props) => {
|
|||||||
To use a cluster feature, you need to configure at least
|
To use a cluster feature, you need to configure at least
|
||||||
a registry first. Please, go to{" "}
|
a registry first. Please, go to{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings/cluster"
|
href="/dashboard/docker?tab=swarm&subtab=nodes"
|
||||||
className="text-foreground"
|
className="text-foreground"
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const ShowNodes = ({ serverId }: Props) => {
|
|||||||
const haveAtLeastOneRegistry = !!(registry && registry?.length > 0);
|
const haveAtLeastOneRegistry = !!(registry && registry?.length > 0);
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
|
<Card className="h-full bg-sidebar p-2.5 rounded-xl mx-auto w-full">
|
||||||
<div className="rounded-xl bg-background shadow-md ">
|
<div className="rounded-xl bg-background shadow-md ">
|
||||||
<CardHeader className="flex flex-row gap-2 justify-between w-full items-center flex-wrap">
|
<CardHeader className="flex flex-row gap-2 justify-between w-full items-center flex-wrap">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const DocLinks = () => (
|
|||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="h-3 w-3" />
|
||||||
</a>
|
</a>
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings/cluster"
|
href="/dashboard/docker?tab=swarm&subtab=nodes"
|
||||||
className="text-xs text-primary underline underline-offset-4 inline-flex items-center gap-1"
|
className="text-xs text-primary underline underline-offset-4 inline-flex items-center gap-1"
|
||||||
>
|
>
|
||||||
Cluster Settings
|
Cluster Settings
|
||||||
@@ -85,7 +85,7 @@ export const SwarmNotAvailable = ({
|
|||||||
<li>
|
<li>
|
||||||
Check the{" "}
|
Check the{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings/cluster"
|
href="/dashboard/docker?tab=swarm&subtab=nodes"
|
||||||
className="text-primary underline underline-offset-4"
|
className="text-primary underline underline-offset-4"
|
||||||
>
|
>
|
||||||
Cluster Settings
|
Cluster Settings
|
||||||
@@ -130,7 +130,7 @@ export const ServicesError = ({
|
|||||||
<li>
|
<li>
|
||||||
Network connectivity issues to a remote server — check{" "}
|
Network connectivity issues to a remote server — check{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings/cluster"
|
href="/dashboard/docker?tab=swarm&subtab=nodes"
|
||||||
className="text-primary underline underline-offset-4"
|
className="text-primary underline underline-offset-4"
|
||||||
>
|
>
|
||||||
Cluster Settings
|
Cluster Settings
|
||||||
@@ -183,7 +183,7 @@ export const NoServices = ({ nodeCount, onRefresh }: NoServicesProps) => (
|
|||||||
Worker nodes need to pull images from a shared registry. Configure one
|
Worker nodes need to pull images from a shared registry. Configure one
|
||||||
in{" "}
|
in{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings/cluster"
|
href="/dashboard/docker?tab=swarm&subtab=nodes"
|
||||||
className="text-primary underline underline-offset-4"
|
className="text-primary underline underline-offset-4"
|
||||||
>
|
>
|
||||||
Cluster Settings
|
Cluster Settings
|
||||||
@@ -255,7 +255,7 @@ export const NoRunningContainers = ({
|
|||||||
<li>
|
<li>
|
||||||
Images can't be pulled on worker nodes — verify your{" "}
|
Images can't be pulled on worker nodes — verify your{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings/cluster"
|
href="/dashboard/docker?tab=swarm&subtab=nodes"
|
||||||
className="text-primary underline underline-offset-4"
|
className="text-primary underline underline-offset-4"
|
||||||
>
|
>
|
||||||
registry configuration
|
registry configuration
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ export const ShowSwarmContainers = ({ serverId }: Props) => {
|
|||||||
<p className="mt-2 text-xs">
|
<p className="mt-2 text-xs">
|
||||||
Manage nodes in{" "}
|
Manage nodes in{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings/cluster"
|
href="/dashboard/docker?tab=swarm&subtab=nodes"
|
||||||
className="underline underline-offset-4"
|
className="underline underline-offset-4"
|
||||||
>
|
>
|
||||||
Cluster Settings
|
Cluster Settings
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ export default function SwarmMonitorCard({ serverId }: Props) {
|
|||||||
{!serverId && (
|
{!serverId && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
window.location.replace("/dashboard/settings/cluster")
|
window.location.replace(
|
||||||
|
"/dashboard/docker?tab=swarm&subtab=nodes",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Settings className="mr-2 h-4 w-4" />
|
<Settings className="mr-2 h-4 w-4" />
|
||||||
|
|||||||
@@ -373,14 +373,6 @@ const MENU: Menu = {
|
|||||||
icon: ShieldCheck,
|
icon: ShieldCheck,
|
||||||
isEnabled: ({ permissions }) => !!permissions?.certificate.read,
|
isEnabled: ({ permissions }) => !!permissions?.certificate.read,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
isSingle: true,
|
|
||||||
title: "Cluster",
|
|
||||||
url: "/dashboard/settings/cluster",
|
|
||||||
icon: Boxes,
|
|
||||||
// Only enabled for admins
|
|
||||||
isEnabled: ({ permissions }) => !!permissions?.organization.update,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
isSingle: true,
|
isSingle: true,
|
||||||
title: "Notifications",
|
title: "Notifications",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import superjson from "superjson";
|
|||||||
import { ShowContainers } from "@/components/dashboard/docker/show/show-containers";
|
import { ShowContainers } from "@/components/dashboard/docker/show/show-containers";
|
||||||
import { ShowVolumes } from "@/components/dashboard/docker/volumes/show-volumes";
|
import { ShowVolumes } from "@/components/dashboard/docker/volumes/show-volumes";
|
||||||
import { ShowNetworks } from "@/components/dashboard/networks/show-networks";
|
import { ShowNetworks } from "@/components/dashboard/networks/show-networks";
|
||||||
|
import { ShowNodes } from "@/components/dashboard/settings/cluster/nodes/show-nodes";
|
||||||
import { ShowSwarmContainers } from "@/components/dashboard/swarm/containers/show-swarm-containers";
|
import { ShowSwarmContainers } from "@/components/dashboard/swarm/containers/show-swarm-containers";
|
||||||
import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card";
|
import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
@@ -17,17 +18,30 @@ import { appRouter } from "@/server/api/root";
|
|||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
const DEFAULT_TAB = "containers";
|
const DEFAULT_TAB = "containers";
|
||||||
|
const DEFAULT_SWARM_TAB = "overview";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
|
|
||||||
|
const canManageCluster = !!permissions?.organization.update;
|
||||||
|
|
||||||
const queryTab =
|
const queryTab =
|
||||||
typeof router.query.tab === "string" ? router.query.tab : DEFAULT_TAB;
|
typeof router.query.tab === "string" ? router.query.tab : DEFAULT_TAB;
|
||||||
const activeTab = isCloud && queryTab === "networks" ? DEFAULT_TAB : queryTab;
|
const activeTab = isCloud && queryTab === "networks" ? DEFAULT_TAB : queryTab;
|
||||||
|
|
||||||
|
const querySwarmTab =
|
||||||
|
typeof router.query.subtab === "string"
|
||||||
|
? router.query.subtab
|
||||||
|
: DEFAULT_SWARM_TAB;
|
||||||
|
const activeSwarmTab =
|
||||||
|
!canManageCluster && querySwarmTab === "nodes"
|
||||||
|
? DEFAULT_SWARM_TAB
|
||||||
|
: querySwarmTab;
|
||||||
|
|
||||||
const setTab = (value: string) => {
|
const setTab = (value: string) => {
|
||||||
const { tab: _current, ...query } = router.query;
|
const { tab: _current, subtab: _subtab, ...query } = router.query;
|
||||||
router.replace(
|
router.replace(
|
||||||
{
|
{
|
||||||
pathname: router.pathname,
|
pathname: router.pathname,
|
||||||
@@ -38,6 +52,19 @@ const Dashboard = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setSwarmTab = (value: string) => {
|
||||||
|
const { subtab: _current, ...query } = router.query;
|
||||||
|
router.replace(
|
||||||
|
{
|
||||||
|
pathname: router.pathname,
|
||||||
|
query:
|
||||||
|
value === DEFAULT_SWARM_TAB ? query : { ...query, subtab: value },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shallow: true },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerFilter>
|
<ServerFilter>
|
||||||
{(serverId) => (
|
{(serverId) => (
|
||||||
@@ -52,10 +79,13 @@ const Dashboard = () => {
|
|||||||
<ShowContainers serverId={serverId} />
|
<ShowContainers serverId={serverId} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="swarm">
|
<TabsContent value="swarm">
|
||||||
<Tabs defaultValue="overview">
|
<Tabs value={activeSwarmTab} onValueChange={setSwarmTab}>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||||
<TabsTrigger value="containers">Containers</TabsTrigger>
|
<TabsTrigger value="containers">Containers</TabsTrigger>
|
||||||
|
{canManageCluster && (
|
||||||
|
<TabsTrigger value="nodes">Nodes</TabsTrigger>
|
||||||
|
)}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="overview">
|
<TabsContent value="overview">
|
||||||
<SwarmMonitorCard serverId={serverId} />
|
<SwarmMonitorCard serverId={serverId} />
|
||||||
@@ -67,6 +97,11 @@ const Dashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
{canManageCluster && (
|
||||||
|
<TabsContent value="nodes">
|
||||||
|
<ShowNodes serverId={serverId} />
|
||||||
|
</TabsContent>
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="volumes">
|
<TabsContent value="volumes">
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import { validateRequest } from "@dokploy/server";
|
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
import type { ReactElement } from "react";
|
|
||||||
import superjson from "superjson";
|
|
||||||
import { ShowNodes } from "@/components/dashboard/settings/cluster/nodes/show-nodes";
|
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
|
||||||
import { ServerFilter } from "@/components/shared/server-filter";
|
|
||||||
import { appRouter } from "@/server/api/root";
|
|
||||||
|
|
||||||
const Page = () => {
|
|
||||||
return (
|
|
||||||
<ServerFilter>
|
|
||||||
{(serverId) => (
|
|
||||||
<div className="flex flex-col gap-4 w-full">
|
|
||||||
<ShowNodes serverId={serverId} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ServerFilter>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
|
||||||
|
|
||||||
Page.getLayout = (page: ReactElement) => {
|
|
||||||
return <DashboardLayout metaName="Nodes">{page}</DashboardLayout>;
|
|
||||||
};
|
|
||||||
export async function getServerSideProps(
|
|
||||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
|
||||||
) {
|
|
||||||
const { req, res } = ctx;
|
|
||||||
const { user, session } = await validateRequest(ctx.req);
|
|
||||||
if (!user || user.role === "member") {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
permanent: false,
|
|
||||||
destination: "/",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const helpers = createServerSideHelpers({
|
|
||||||
router: appRouter,
|
|
||||||
ctx: {
|
|
||||||
req: req as any,
|
|
||||||
res: res as any,
|
|
||||||
db: null as any,
|
|
||||||
session: session as any,
|
|
||||||
user: user as any,
|
|
||||||
},
|
|
||||||
transformer: superjson,
|
|
||||||
});
|
|
||||||
await helpers.user.get.prefetch();
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
trpcState: helpers.dehydrate(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user