diff --git a/apps/dokploy/components/dashboard/home/show-home.tsx b/apps/dokploy/components/dashboard/home/show-home.tsx index f77b71f71..47d7bf31e 100644 --- a/apps/dokploy/components/dashboard/home/show-home.tsx +++ b/apps/dokploy/components/dashboard/home/show-home.tsx @@ -223,7 +223,7 @@ export const ShowHome = () => { {canReadDeployments && ( view all → diff --git a/apps/dokploy/components/dashboard/overview/show-overview-deployments.tsx b/apps/dokploy/components/dashboard/overview/show-overview-deployments.tsx new file mode 100644 index 000000000..be12da353 --- /dev/null +++ b/apps/dokploy/components/dashboard/overview/show-overview-deployments.tsx @@ -0,0 +1,77 @@ +import { Rocket } from "lucide-react"; +import { useRouter } from "next/router"; +import { ShowDeploymentsTable } from "@/components/dashboard/deployments/show-deployments-table"; +import { ShowQueueTable } from "@/components/dashboard/deployments/show-queue-table"; +import { + Card, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +const SUBTAB_VALUES = ["deployments", "queue"] as const; +type SubtabValue = (typeof SUBTAB_VALUES)[number]; +const DEFAULT_SUBTAB: SubtabValue = "deployments"; + +function isValidSubtab(t: string): t is SubtabValue { + return SUBTAB_VALUES.includes(t as SubtabValue); +} + +export const ShowOverviewDeployments = () => { + const router = useRouter(); + const subtab = + typeof router.query.subtab === "string" && + isValidSubtab(router.query.subtab) + ? router.query.subtab + : DEFAULT_SUBTAB; + + const setSubtab = (value: string) => { + if (!isValidSubtab(value)) return; + const { subtab: _current, ...query } = router.query; + router.replace( + { + pathname: router.pathname, + query: value === DEFAULT_SUBTAB ? query : { ...query, subtab: value }, + }, + undefined, + { shallow: true }, + ); + }; + + return ( + +
+ +
+
+ + + Deployments + + + All application and compose deployments in one place. + +
+
+ + + Deployments + Queue + + + + + + + + +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/overview/show-overview-domains.tsx b/apps/dokploy/components/dashboard/overview/show-overview-domains.tsx index 23348e0ff..e70045087 100644 --- a/apps/dokploy/components/dashboard/overview/show-overview-domains.tsx +++ b/apps/dokploy/components/dashboard/overview/show-overview-domains.tsx @@ -4,6 +4,7 @@ import { ExternalLink, Loader2 } from "lucide-react"; import Link from "next/link"; import { useMemo, useState } from "react"; import { toast } from "sonner"; +import { COMPOSE_REDEPLOY_TOAST } from "@/components/dashboard/application/domains/redeploy-hint"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -71,7 +72,12 @@ export const ShowOverviewDomains = () => { try { const result = await toggleEnable({ domainId: domain.domainId }); utils.overview.domains.invalidate(); - toast.success(result.enabled ? "Domain enabled" : "Domain disabled"); + toast.success( + result.enabled ? "Domain enabled" : "Domain disabled", + result.requiresRedeploy + ? { description: COMPOSE_REDEPLOY_TOAST } + : undefined, + ); } catch { toast.error(`Error updating "${domain.host}"`); } diff --git a/apps/dokploy/components/dashboard/search-command.tsx b/apps/dokploy/components/dashboard/search-command.tsx index d20fcd698..2d066992a 100644 --- a/apps/dokploy/components/dashboard/search-command.tsx +++ b/apps/dokploy/components/dashboard/search-command.tsx @@ -175,7 +175,7 @@ export const SearchCommand = () => { { - router.push("/dashboard/deployments"); + router.push("/dashboard/overview?tab=deployments"); setOpen(false); }} > diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx index 34f4f73a3..66504d7e2 100644 --- a/apps/dokploy/components/layouts/side.tsx +++ b/apps/dokploy/components/layouts/side.tsx @@ -29,7 +29,6 @@ import { type LucideIcon, Package, Palette, - Rocket, Server, ShieldCheck, Smartphone, @@ -179,10 +178,11 @@ const MENU: Menu = { }, { isSingle: true, - title: "Deployments", - url: "/dashboard/deployments", - icon: Rocket, - isEnabled: ({ permissions }) => !!permissions?.deployment.read, + title: "Overview", + url: "/dashboard/overview", + icon: LayoutGrid, + // Only enabled for users with access to services + isEnabled: ({ permissions }) => !!permissions?.service.read, }, { isSingle: true, @@ -216,14 +216,6 @@ const MENU: Menu = { // Only enabled for users with access to Docker isEnabled: ({ permissions }) => !!permissions?.docker.read, }, - { - isSingle: true, - title: "Overview", - url: "/dashboard/overview", - icon: LayoutGrid, - // Only enabled for users with access to services - isEnabled: ({ permissions }) => !!permissions?.service.read, - }, { isSingle: true, title: "Requests", diff --git a/apps/dokploy/pages/dashboard/deployments.tsx b/apps/dokploy/pages/dashboard/deployments.tsx index 835b25db3..e264e6d4c 100644 --- a/apps/dokploy/pages/dashboard/deployments.tsx +++ b/apps/dokploy/pages/dashboard/deployments.tsx @@ -1,113 +1,19 @@ -import { validateRequest } from "@dokploy/server/lib/auth"; -import { hasPermission } from "@dokploy/server/services/permission"; -import { Rocket } from "lucide-react"; import type { GetServerSidePropsContext } from "next"; -import { useRouter } from "next/router"; -import type { ReactElement } from "react"; -import { ShowDeploymentsTable } from "@/components/dashboard/deployments/show-deployments-table"; -import { ShowQueueTable } from "@/components/dashboard/deployments/show-queue-table"; -import { DashboardLayout } from "@/components/layouts/dashboard-layout"; -import { - Card, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -const TAB_VALUES = ["deployments", "queue"] as const; -type TabValue = (typeof TAB_VALUES)[number]; - -function isValidTab(t: string): t is TabValue { - return TAB_VALUES.includes(t as TabValue); +export default function DeploymentsRedirect() { + return null; } -function DeploymentsPage() { - const router = useRouter(); - const tab = - router.query.tab && isValidTab(router.query.tab as string) - ? (router.query.tab as TabValue) - : "deployments"; - - const setTab = (value: string) => { - if (!isValidTab(value)) return; - router.replace( - { pathname: "/dashboard/deployments", query: { tab: value } }, - undefined, - { shallow: true }, - ); - }; - - return ( -
- -
- -
-
- - - Deployments - - - All application and compose deployments in one place. - -
-
- - - Deployments - Queue - - - - - - - - -
-
-
-
- ); -} - -export default DeploymentsPage; - -DeploymentsPage.getLayout = (page: ReactElement) => { - return {page}; -}; - export async function getServerSideProps(ctx: GetServerSidePropsContext) { - const { user, session } = await validateRequest(ctx.req); - if (!user) { - return { - redirect: { - permanent: false, - destination: "/", - }, - }; - } - - const canView = await hasPermission( - { - user: { id: user.id }, - session: { activeOrganizationId: session?.activeOrganizationId || "" }, - }, - { deployment: ["read"] }, - ); - - if (!canView) { - return { - redirect: { - permanent: false, - destination: "/dashboard/home", - }, - }; - } + const destination = + ctx.query.tab === "queue" + ? "/dashboard/overview?tab=deployments&subtab=queue" + : "/dashboard/overview?tab=deployments"; return { - props: {}, + redirect: { + permanent: false, + destination, + }, }; } diff --git a/apps/dokploy/pages/dashboard/overview.tsx b/apps/dokploy/pages/dashboard/overview.tsx index 153c73a56..2094ece2d 100644 --- a/apps/dokploy/pages/dashboard/overview.tsx +++ b/apps/dokploy/pages/dashboard/overview.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/router"; import type { ReactElement } from "react"; import superjson from "superjson"; import { ShowOverviewBackups } from "@/components/dashboard/overview/show-overview-backups"; +import { ShowOverviewDeployments } from "@/components/dashboard/overview/show-overview-deployments"; import { ShowOverviewDomains } from "@/components/dashboard/overview/show-overview-domains"; import { ShowOverviewServices } from "@/components/dashboard/overview/show-overview-services"; import { DashboardLayout } from "@/components/layouts/dashboard-layout"; @@ -20,17 +21,19 @@ const Overview = () => { const canSeeBackups = !!permissions?.backup.read && !!permissions?.volumeBackup.read; const canSeeDomains = !!permissions?.domain.read; + const canSeeDeployments = !!permissions?.deployment.read; const queryTab = typeof router.query.tab === "string" ? router.query.tab : DEFAULT_TAB; const activeTab = (queryTab === "backups" && !canSeeBackups) || - (queryTab === "domains" && !canSeeDomains) + (queryTab === "domains" && !canSeeDomains) || + (queryTab === "deployments" && !canSeeDeployments) ? DEFAULT_TAB : queryTab; const setTab = (value: string) => { - const { tab: _current, ...query } = router.query; + const { tab: _current, subtab: _subtab, ...query } = router.query; router.replace( { pathname: router.pathname, @@ -47,6 +50,9 @@ const Overview = () => { Services {canSeeBackups && Backups} {canSeeDomains && Domains} + {canSeeDeployments && ( + Deployments + )} @@ -61,6 +67,11 @@ const Overview = () => { )} + {canSeeDeployments && ( + + + + )} ); };