mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-02 18:22:16 +00:00
feat: move deployments into an overview page tab
This commit is contained in:
@@ -223,7 +223,7 @@ export const ShowHome = () => {
|
|||||||
</div>
|
</div>
|
||||||
{canReadDeployments && (
|
{canReadDeployments && (
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/deployments"
|
href="/dashboard/overview?tab=deployments"
|
||||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
view all →
|
view all →
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<Card className="h-full bg-sidebar p-2.5 rounded-xl min-h-[45vh]">
|
||||||
|
<div className="rounded-xl bg-background shadow-md h-full">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
||||||
|
<Rocket className="size-5" />
|
||||||
|
Deployments
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
All application and compose deployments in one place.
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tabs
|
||||||
|
value={subtab}
|
||||||
|
onValueChange={setSubtab}
|
||||||
|
className="w-full min-w-0"
|
||||||
|
>
|
||||||
|
<TabsList className="mt-2">
|
||||||
|
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
||||||
|
<TabsTrigger value="queue">Queue</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="deployments" className="mt-0 min-w-0 pt-4">
|
||||||
|
<ShowDeploymentsTable />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="queue" className="mt-0 pt-4">
|
||||||
|
<ShowQueueTable />
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</CardHeader>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { ExternalLink, Loader2 } from "lucide-react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { COMPOSE_REDEPLOY_TOAST } from "@/components/dashboard/application/domains/redeploy-hint";
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -71,7 +72,12 @@ export const ShowOverviewDomains = () => {
|
|||||||
try {
|
try {
|
||||||
const result = await toggleEnable({ domainId: domain.domainId });
|
const result = await toggleEnable({ domainId: domain.domainId });
|
||||||
utils.overview.domains.invalidate();
|
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 {
|
} catch {
|
||||||
toast.error(`Error updating "${domain.host}"`);
|
toast.error(`Error updating "${domain.host}"`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export const SearchCommand = () => {
|
|||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/deployments");
|
router.push("/dashboard/overview?tab=deployments");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
Package,
|
Package,
|
||||||
Palette,
|
Palette,
|
||||||
Rocket,
|
|
||||||
Server,
|
Server,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Smartphone,
|
Smartphone,
|
||||||
@@ -179,10 +178,11 @@ const MENU: Menu = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
isSingle: true,
|
isSingle: true,
|
||||||
title: "Deployments",
|
title: "Overview",
|
||||||
url: "/dashboard/deployments",
|
url: "/dashboard/overview",
|
||||||
icon: Rocket,
|
icon: LayoutGrid,
|
||||||
isEnabled: ({ permissions }) => !!permissions?.deployment.read,
|
// Only enabled for users with access to services
|
||||||
|
isEnabled: ({ permissions }) => !!permissions?.service.read,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isSingle: true,
|
isSingle: true,
|
||||||
@@ -216,14 +216,6 @@ const MENU: Menu = {
|
|||||||
// Only enabled for users with access to Docker
|
// Only enabled for users with access to Docker
|
||||||
isEnabled: ({ permissions }) => !!permissions?.docker.read,
|
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,
|
isSingle: true,
|
||||||
title: "Requests",
|
title: "Requests",
|
||||||
|
|||||||
@@ -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 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;
|
export default function DeploymentsRedirect() {
|
||||||
type TabValue = (typeof TAB_VALUES)[number];
|
return null;
|
||||||
|
|
||||||
function isValidTab(t: string): t is TabValue {
|
|
||||||
return TAB_VALUES.includes(t as TabValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="w-full">
|
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl min-h-[45vh]">
|
|
||||||
<div className="rounded-xl bg-background shadow-md h-full">
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
|
||||||
<Rocket className="size-5" />
|
|
||||||
Deployments
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
All application and compose deployments in one place.
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Tabs value={tab} onValueChange={setTab} className="w-full min-w-0">
|
|
||||||
<TabsList className="mt-2">
|
|
||||||
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
|
||||||
<TabsTrigger value="queue">Queue</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="deployments" className="mt-0 min-w-0 pt-4">
|
|
||||||
<ShowDeploymentsTable />
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="queue" className="mt-0 pt-4">
|
|
||||||
<ShowQueueTable />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</CardHeader>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DeploymentsPage;
|
|
||||||
|
|
||||||
DeploymentsPage.getLayout = (page: ReactElement) => {
|
|
||||||
return <DashboardLayout>{page}</DashboardLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||||
const { user, session } = await validateRequest(ctx.req);
|
const destination =
|
||||||
if (!user) {
|
ctx.query.tab === "queue"
|
||||||
return {
|
? "/dashboard/overview?tab=deployments&subtab=queue"
|
||||||
redirect: {
|
: "/dashboard/overview?tab=deployments";
|
||||||
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",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {},
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import type { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ShowOverviewBackups } from "@/components/dashboard/overview/show-overview-backups";
|
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 { ShowOverviewDomains } from "@/components/dashboard/overview/show-overview-domains";
|
||||||
import { ShowOverviewServices } from "@/components/dashboard/overview/show-overview-services";
|
import { ShowOverviewServices } from "@/components/dashboard/overview/show-overview-services";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
@@ -20,17 +21,19 @@ const Overview = () => {
|
|||||||
const canSeeBackups =
|
const canSeeBackups =
|
||||||
!!permissions?.backup.read && !!permissions?.volumeBackup.read;
|
!!permissions?.backup.read && !!permissions?.volumeBackup.read;
|
||||||
const canSeeDomains = !!permissions?.domain.read;
|
const canSeeDomains = !!permissions?.domain.read;
|
||||||
|
const canSeeDeployments = !!permissions?.deployment.read;
|
||||||
|
|
||||||
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 =
|
const activeTab =
|
||||||
(queryTab === "backups" && !canSeeBackups) ||
|
(queryTab === "backups" && !canSeeBackups) ||
|
||||||
(queryTab === "domains" && !canSeeDomains)
|
(queryTab === "domains" && !canSeeDomains) ||
|
||||||
|
(queryTab === "deployments" && !canSeeDeployments)
|
||||||
? DEFAULT_TAB
|
? DEFAULT_TAB
|
||||||
: queryTab;
|
: queryTab;
|
||||||
|
|
||||||
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,
|
||||||
@@ -47,6 +50,9 @@ const Overview = () => {
|
|||||||
<TabsTrigger value="services">Services</TabsTrigger>
|
<TabsTrigger value="services">Services</TabsTrigger>
|
||||||
{canSeeBackups && <TabsTrigger value="backups">Backups</TabsTrigger>}
|
{canSeeBackups && <TabsTrigger value="backups">Backups</TabsTrigger>}
|
||||||
{canSeeDomains && <TabsTrigger value="domains">Domains</TabsTrigger>}
|
{canSeeDomains && <TabsTrigger value="domains">Domains</TabsTrigger>}
|
||||||
|
{canSeeDeployments && (
|
||||||
|
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
||||||
|
)}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="services">
|
<TabsContent value="services">
|
||||||
<ShowOverviewServices />
|
<ShowOverviewServices />
|
||||||
@@ -61,6 +67,11 @@ const Overview = () => {
|
|||||||
<ShowOverviewDomains />
|
<ShowOverviewDomains />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
)}
|
)}
|
||||||
|
{canSeeDeployments && (
|
||||||
|
<TabsContent value="deployments">
|
||||||
|
<ShowOverviewDeployments />
|
||||||
|
</TabsContent>
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user