mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
feat(ui): add Vercel-style breadcrumb navigation with project/service
switchers - Create AdvanceBreadcrumb component with searchable dropdowns - Add project selector with environment expansion support - Add service selector for quick switching between services - Add environment selector badge for multi-environment projects - Replace BreadcrumbSidebar with new component across all service pages - Update projects page, environment page, and all service detail pages (application, compose, postgres, mysql, mariadb, redis, mongo)
This commit is contained in:
@@ -13,7 +13,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
@@ -169,9 +169,7 @@ export const ShowProjects = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<BreadcrumbSidebar
|
||||
list={[{ name: "Projects", href: "/dashboard/projects" }]}
|
||||
/>
|
||||
<AdvanceBreadcrumb />
|
||||
{!isCloud && (
|
||||
<div className="absolute top-4 right-4">
|
||||
<TimeBadge />
|
||||
@@ -564,7 +562,7 @@ export const ShowProjects = () => {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardFooter className="pt-4">
|
||||
<div className="space-y-1 text-sm flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
||||
<div className="space-y-1 text-xs flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
||||
<DateTooltip date={project.createdAt}>
|
||||
Created
|
||||
</DateTooltip>
|
||||
|
||||
564
apps/dokploy/components/shared/advance-breadcrumb.tsx
Normal file
564
apps/dokploy/components/shared/advance-breadcrumb.tsx
Normal file
@@ -0,0 +1,564 @@
|
||||
import type { ServiceType } from "@dokploy/server/db/schema";
|
||||
import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
CircuitBoard,
|
||||
FolderInput,
|
||||
GlobeIcon,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
MariadbIcon,
|
||||
MongodbIcon,
|
||||
MysqlIcon,
|
||||
PostgresqlIcon,
|
||||
RedisIcon,
|
||||
} from "@/components/icons/data-tools-icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
interface AdvanceBreadcrumbProps {
|
||||
projectId?: string;
|
||||
environmentId?: string;
|
||||
serviceId?: string;
|
||||
serviceType?: ServiceType;
|
||||
}
|
||||
|
||||
const getServiceIcon = (type: ServiceType, className = "size-4") => {
|
||||
const icons: Record<ServiceType, React.ReactNode> = {
|
||||
application: <GlobeIcon className={className} />,
|
||||
compose: <CircuitBoard className={className} />,
|
||||
postgres: <PostgresqlIcon className={className} />,
|
||||
mysql: <MysqlIcon className={className} />,
|
||||
mariadb: <MariadbIcon className={className} />,
|
||||
redis: <RedisIcon className={className} />,
|
||||
mongo: <MongodbIcon className={className} />,
|
||||
};
|
||||
|
||||
return icons[type];
|
||||
};
|
||||
|
||||
interface ServiceItem {
|
||||
id: string;
|
||||
name: string;
|
||||
type: ServiceType;
|
||||
appName?: string;
|
||||
}
|
||||
|
||||
export const AdvanceBreadcrumb = ({
|
||||
projectId,
|
||||
environmentId,
|
||||
serviceId,
|
||||
}: AdvanceBreadcrumbProps) => {
|
||||
const router = useRouter();
|
||||
const [projectOpen, setProjectOpen] = useState(false);
|
||||
const [serviceOpen, setServiceOpen] = useState(false);
|
||||
const [environmentOpen, setEnvironmentOpen] = useState(false);
|
||||
const [projectSearch, setProjectSearch] = useState("");
|
||||
const [serviceSearch, setServiceSearch] = useState("");
|
||||
const [expandedProjectId, setExpandedProjectId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
// Fetch all projects
|
||||
const { data: allProjects } = api.project.all.useQuery();
|
||||
|
||||
// Fetch current project data
|
||||
const { data: currentProject } = api.project.one.useQuery(
|
||||
{ projectId: projectId || "" },
|
||||
{ enabled: !!projectId },
|
||||
);
|
||||
|
||||
// Fetch current environment
|
||||
const { data: currentEnvironment } = api.environment.one.useQuery(
|
||||
{ environmentId: environmentId || "" },
|
||||
{ enabled: !!environmentId },
|
||||
);
|
||||
|
||||
// Fetch environments for current project
|
||||
const { data: projectEnvironments } = api.environment.byProjectId.useQuery(
|
||||
{ projectId: projectId || "" },
|
||||
{ enabled: !!projectId },
|
||||
);
|
||||
|
||||
// Close dropdowns on escape key
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
setProjectOpen(false);
|
||||
setServiceOpen(false);
|
||||
setEnvironmentOpen(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
// Extract services from current environment
|
||||
const services: ServiceItem[] = [];
|
||||
if (currentEnvironment) {
|
||||
currentEnvironment.applications?.forEach(
|
||||
(app: { applicationId: string; name: string; appName: string }) => {
|
||||
services.push({
|
||||
id: app.applicationId,
|
||||
name: app.name,
|
||||
type: "application",
|
||||
appName: app.appName,
|
||||
});
|
||||
},
|
||||
);
|
||||
currentEnvironment.compose?.forEach(
|
||||
(comp: { composeId: string; name: string; appName: string }) => {
|
||||
services.push({
|
||||
id: comp.composeId,
|
||||
name: comp.name,
|
||||
type: "compose",
|
||||
appName: comp.appName,
|
||||
});
|
||||
},
|
||||
);
|
||||
currentEnvironment.postgres?.forEach(
|
||||
(pg: { postgresId: string; name: string; appName: string }) => {
|
||||
services.push({
|
||||
id: pg.postgresId,
|
||||
name: pg.name,
|
||||
type: "postgres",
|
||||
appName: pg.appName,
|
||||
});
|
||||
},
|
||||
);
|
||||
currentEnvironment.mysql?.forEach(
|
||||
(my: { mysqlId: string; name: string; appName: string }) => {
|
||||
services.push({
|
||||
id: my.mysqlId,
|
||||
name: my.name,
|
||||
type: "mysql",
|
||||
appName: my.appName,
|
||||
});
|
||||
},
|
||||
);
|
||||
currentEnvironment.mariadb?.forEach(
|
||||
(maria: { mariadbId: string; name: string; appName: string }) => {
|
||||
services.push({
|
||||
id: maria.mariadbId,
|
||||
name: maria.name,
|
||||
type: "mariadb",
|
||||
appName: maria.appName,
|
||||
});
|
||||
},
|
||||
);
|
||||
currentEnvironment.redis?.forEach(
|
||||
(red: { redisId: string; name: string; appName: string }) => {
|
||||
services.push({
|
||||
id: red.redisId,
|
||||
name: red.name,
|
||||
type: "redis",
|
||||
appName: red.appName,
|
||||
});
|
||||
},
|
||||
);
|
||||
currentEnvironment.mongo?.forEach(
|
||||
(mon: { mongoId: string; name: string; appName: string }) => {
|
||||
services.push({
|
||||
id: mon.mongoId,
|
||||
name: mon.name,
|
||||
type: "mongo",
|
||||
appName: mon.appName,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Get current service
|
||||
const currentService = services.find((s) => s.id === serviceId);
|
||||
|
||||
// Navigate to project's default environment
|
||||
const handleProjectSelect = (
|
||||
selectedProjectId: string,
|
||||
selectedEnvironmentId?: string,
|
||||
) => {
|
||||
const project = allProjects?.find((p) => p.projectId === selectedProjectId);
|
||||
if (project && project.environments.length > 0) {
|
||||
// Use provided environment or find production environment or use the first one
|
||||
const targetEnvId =
|
||||
selectedEnvironmentId ||
|
||||
project.environments.find((e) => e.name === "production")
|
||||
?.environmentId ||
|
||||
project.environments[0]?.environmentId;
|
||||
|
||||
router.push(
|
||||
`/dashboard/project/${selectedProjectId}/environment/${targetEnvId}`,
|
||||
);
|
||||
}
|
||||
setProjectOpen(false);
|
||||
setExpandedProjectId(null);
|
||||
};
|
||||
|
||||
// Navigate to environment
|
||||
const handleEnvironmentSelect = (envId: string) => {
|
||||
router.push(`/dashboard/project/${projectId}/environment/${envId}`);
|
||||
setEnvironmentOpen(false);
|
||||
};
|
||||
|
||||
// Navigate to service
|
||||
const handleServiceSelect = (service: ServiceItem) => {
|
||||
const serviceTypePath =
|
||||
service.type === "application" ? "application" : service.type;
|
||||
router.push(
|
||||
`/dashboard/project/${projectId}/environment/${environmentId}/services/${serviceTypePath}/${service.id}`,
|
||||
);
|
||||
setServiceOpen(false);
|
||||
};
|
||||
|
||||
// Filter projects based on search
|
||||
const filteredProjects =
|
||||
allProjects?.filter(
|
||||
(p) =>
|
||||
p.name.toLowerCase().includes(projectSearch.toLowerCase()) ||
|
||||
p.description?.toLowerCase().includes(projectSearch.toLowerCase()),
|
||||
) || [];
|
||||
|
||||
// Filter services based on search
|
||||
const filteredServices = services.filter(
|
||||
(s) =>
|
||||
s.name.toLowerCase().includes(serviceSearch.toLowerCase()) ||
|
||||
s.appName?.toLowerCase().includes(serviceSearch.toLowerCase()),
|
||||
);
|
||||
|
||||
// If we're just on the projects page, show simple breadcrumb
|
||||
if (!projectId) {
|
||||
return (
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderInput className="size-4 text-muted-foreground" />
|
||||
<span className="font-medium">Projects</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
|
||||
<div className="flex items-center">
|
||||
{/* Project Selector */}
|
||||
<Popover open={projectOpen} onOpenChange={setProjectOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-expanded={projectOpen}
|
||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||
>
|
||||
<FolderInput className="size-4 text-muted-foreground" />
|
||||
<span className="font-medium max-w-[150px] truncate">
|
||||
{currentProject?.name || "Select Project"}
|
||||
</span>
|
||||
<ChevronDown className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-[380px] p-0"
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
>
|
||||
<Command shouldFilter={false}>
|
||||
<div className="relative">
|
||||
<CommandInput
|
||||
placeholder="Find Project..."
|
||||
value={projectSearch}
|
||||
onValueChange={setProjectSearch}
|
||||
className="w-full focus:ring-0"
|
||||
/>
|
||||
<kbd className="pointer-events-none h-5 absolute right-2 top-1/2 -translate-y-1/2 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 flex">
|
||||
Esc
|
||||
</kbd>
|
||||
</div>
|
||||
<CommandList>
|
||||
<CommandEmpty>No projects found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<ScrollArea className="h-[300px]">
|
||||
{filteredProjects.map((project) => {
|
||||
const totalServices = project.environments.reduce(
|
||||
(total, env) =>
|
||||
total +
|
||||
(env.applications?.length || 0) +
|
||||
(env.compose?.length || 0) +
|
||||
(env.postgres?.length || 0) +
|
||||
(env.mysql?.length || 0) +
|
||||
(env.mariadb?.length || 0) +
|
||||
(env.redis?.length || 0) +
|
||||
(env.mongo?.length || 0),
|
||||
0,
|
||||
);
|
||||
const isSelected = project.projectId === projectId;
|
||||
const isExpanded =
|
||||
expandedProjectId === project.projectId;
|
||||
|
||||
return (
|
||||
<div key={project.projectId}>
|
||||
<CommandItem
|
||||
value={project.projectId}
|
||||
onSelect={() => {
|
||||
if (project.environments.length > 1) {
|
||||
setExpandedProjectId(
|
||||
isExpanded ? null : project.projectId,
|
||||
);
|
||||
} else {
|
||||
handleProjectSelect(project.projectId);
|
||||
}
|
||||
}}
|
||||
className="flex items-center justify-between py-3 px-2 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center justify-center size-8 rounded-md bg-muted text-xs font-semibold uppercase">
|
||||
{project.name.slice(0, 2)}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
{project.name}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{project.environments.length} env
|
||||
{project.environments.length !== 1
|
||||
? "s"
|
||||
: ""}{" "}
|
||||
· {totalServices} service
|
||||
{totalServices !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isSelected && (
|
||||
<Check className="size-4 text-primary" />
|
||||
)}
|
||||
{project.environments.length > 1 && (
|
||||
<ChevronRight
|
||||
className={`size-4 text-muted-foreground transition-transform ${isExpanded ? "rotate-90" : ""}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
|
||||
{/* Expanded environments */}
|
||||
{isExpanded && (
|
||||
<div className="ml-11 border-l pl-3 py-1 space-y-1">
|
||||
{project.environments.map((env) => {
|
||||
const envServices =
|
||||
(env.applications?.length || 0) +
|
||||
(env.compose?.length || 0) +
|
||||
(env.postgres?.length || 0) +
|
||||
(env.mysql?.length || 0) +
|
||||
(env.mariadb?.length || 0) +
|
||||
(env.redis?.length || 0) +
|
||||
(env.mongo?.length || 0);
|
||||
const isEnvSelected =
|
||||
env.environmentId === environmentId;
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
key={env.environmentId}
|
||||
value={env.environmentId}
|
||||
onSelect={() =>
|
||||
handleProjectSelect(
|
||||
project.projectId,
|
||||
env.environmentId,
|
||||
)
|
||||
}
|
||||
className="flex items-center justify-between py-2 px-2 cursor-pointer text-sm"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-xs">{env.name}</p>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{envServices} service
|
||||
{envServices !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
{isEnvSelected && (
|
||||
<Check className="size-3 text-primary" />
|
||||
)}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</ScrollArea>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* Environment Selector */}
|
||||
{projectEnvironments && projectEnvironments.length > 1 && (
|
||||
<Popover open={environmentOpen} onOpenChange={setEnvironmentOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-expanded={environmentOpen}
|
||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-1"
|
||||
>
|
||||
<p className="text-xs font-normal">
|
||||
{currentEnvironment?.name || "production"}
|
||||
</p>
|
||||
<ChevronDown className="size-3 text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-[200px] p-1"
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{projectEnvironments.map((env) => {
|
||||
const isSelected = env.environmentId === environmentId;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={env.environmentId}
|
||||
onClick={() =>
|
||||
handleEnvironmentSelect(env.environmentId)
|
||||
}
|
||||
className="flex items-center justify-between w-full px-2 py-1.5 text-sm rounded-md hover:bg-accent cursor-pointer"
|
||||
>
|
||||
<p className="text-xs">{env.name}</p>
|
||||
{isSelected && (
|
||||
<Check className="size-3 text-primary" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
{projectEnvironments && projectEnvironments.length === 1 && (
|
||||
<p className="text-xs font-normal ml-1">
|
||||
{currentEnvironment?.name || "production"}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Service Selector - only show when viewing a service */}
|
||||
{serviceId && currentService && (
|
||||
<>
|
||||
<Separator orientation="vertical" className="mx-2 h-6" />
|
||||
|
||||
<Popover open={serviceOpen} onOpenChange={setServiceOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-expanded={serviceOpen}
|
||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||
>
|
||||
{getServiceIcon(currentService.type)}
|
||||
<span className="font-medium max-w-[150px] truncate">
|
||||
{currentService.name}
|
||||
</span>
|
||||
<ChevronDown className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-[350px] p-0"
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
>
|
||||
<Command shouldFilter={false}>
|
||||
<div className="relative">
|
||||
<CommandInput
|
||||
placeholder="Find Service..."
|
||||
value={serviceSearch}
|
||||
onValueChange={setServiceSearch}
|
||||
className="w-full focus:ring-0"
|
||||
/>
|
||||
<kbd className="pointer-events-none h-5 select-none absolute right-2 top-1/2 -translate-y-1/2 items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 flex">
|
||||
Esc
|
||||
</kbd>
|
||||
</div>
|
||||
<CommandList>
|
||||
<CommandEmpty>No services found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<ScrollArea className="h-[300px]">
|
||||
{filteredServices.map((service) => {
|
||||
const isSelected = service.id === serviceId;
|
||||
return (
|
||||
<CommandItem
|
||||
key={service.id}
|
||||
value={service.id}
|
||||
onSelect={() => handleServiceSelect(service)}
|
||||
className="flex items-center justify-between py-3 px-2 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center justify-center size-8 rounded-md bg-muted">
|
||||
{getServiceIcon(service.type)}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
{service.name}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground capitalize">
|
||||
{service.type}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{isSelected && (
|
||||
<Check className="size-4 text-primary" />
|
||||
)}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</ScrollArea>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* Close button to go back to environment */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-7 ml-1"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<X className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -44,8 +44,8 @@ import {
|
||||
RedisIcon,
|
||||
} from "@/components/icons/data-tools-icons";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
|
||||
@@ -865,18 +865,7 @@ const EnvironmentPage = (
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: projectData?.name || "",
|
||||
},
|
||||
{
|
||||
name: currentEnvironment.name,
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<AdvanceBreadcrumb projectId={projectId} environmentId={environmentId} />
|
||||
<Head>
|
||||
<title>
|
||||
Environment: {currentEnvironment.name} | {projectData?.name} | Dokploy
|
||||
|
||||
@@ -34,7 +34,7 @@ import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -103,21 +103,11 @@ const Service = (
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<UseKeyboardNav forPage="application" />
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: data?.environment?.project?.name || "",
|
||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
},
|
||||
{
|
||||
name: data?.environment?.name || "",
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
{
|
||||
name: data?.name || "",
|
||||
},
|
||||
]}
|
||||
<AdvanceBreadcrumb
|
||||
projectId={projectId as string}
|
||||
environmentId={environmentId as string}
|
||||
serviceId={applicationId}
|
||||
serviceType="application"
|
||||
/>
|
||||
<Head>
|
||||
<title>
|
||||
|
||||
@@ -30,7 +30,7 @@ import { ShowBackups } from "@/components/dashboard/database/backups/show-backup
|
||||
import { ComposeFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-compose-monitoring";
|
||||
import { ComposePaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -92,21 +92,11 @@ const Service = (
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<UseKeyboardNav forPage="compose" />
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: data?.environment?.project?.name || "",
|
||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
},
|
||||
{
|
||||
name: data?.environment?.name || "",
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
{
|
||||
name: data?.name || "",
|
||||
},
|
||||
]}
|
||||
<AdvanceBreadcrumb
|
||||
projectId={projectId as string}
|
||||
environmentId={environmentId as string}
|
||||
serviceId={composeId}
|
||||
serviceType="compose"
|
||||
/>
|
||||
<Head>
|
||||
<title>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/
|
||||
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
|
||||
import { MariadbIcon } from "@/components/icons/data-tools-icons";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -74,21 +74,11 @@ const Mariadb = (
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<UseKeyboardNav forPage="mariadb" />
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: data?.environment?.project?.name || "",
|
||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
},
|
||||
{
|
||||
name: data?.environment?.name || "",
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
{
|
||||
name: data?.name || "",
|
||||
},
|
||||
]}
|
||||
<AdvanceBreadcrumb
|
||||
projectId={projectId as string}
|
||||
environmentId={environmentId as string}
|
||||
serviceId={mariadbId}
|
||||
serviceType="mariadb"
|
||||
/>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Head>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/
|
||||
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
|
||||
import { MongodbIcon } from "@/components/icons/data-tools-icons";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -73,21 +73,11 @@ const Mongo = (
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<UseKeyboardNav forPage="mongodb" />
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: data?.environment?.project?.name || "",
|
||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
},
|
||||
{
|
||||
name: data?.environment?.name || "",
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
{
|
||||
name: data?.name || "",
|
||||
},
|
||||
]}
|
||||
<AdvanceBreadcrumb
|
||||
projectId={projectId as string}
|
||||
environmentId={environmentId as string}
|
||||
serviceId={mongoId}
|
||||
serviceType="mongo"
|
||||
/>
|
||||
<Head>
|
||||
<title>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { UpdateMysql } from "@/components/dashboard/mysql/update-mysql";
|
||||
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
|
||||
import { MysqlIcon } from "@/components/icons/data-tools-icons";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -72,21 +72,11 @@ const MySql = (
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<UseKeyboardNav forPage="mysql" />
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: data?.environment?.project?.name || "",
|
||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
},
|
||||
{
|
||||
name: data?.environment?.name || "",
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
{
|
||||
name: data?.name || "",
|
||||
},
|
||||
]}
|
||||
<AdvanceBreadcrumb
|
||||
projectId={projectId as string}
|
||||
environmentId={environmentId as string}
|
||||
serviceId={mysqlId}
|
||||
serviceType="mysql"
|
||||
/>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Head>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { UpdatePostgres } from "@/components/dashboard/postgres/update-postgres"
|
||||
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
|
||||
import { PostgresqlIcon } from "@/components/icons/data-tools-icons";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -72,21 +72,11 @@ const Postgresql = (
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<UseKeyboardNav forPage="postgres" />
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: data?.environment?.project?.name || "",
|
||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
},
|
||||
{
|
||||
name: data?.environment?.name || "",
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
{
|
||||
name: data?.name || "",
|
||||
},
|
||||
]}
|
||||
<AdvanceBreadcrumb
|
||||
projectId={projectId as string}
|
||||
environmentId={environmentId as string}
|
||||
serviceId={postgresId}
|
||||
serviceType="postgres"
|
||||
/>
|
||||
<Head>
|
||||
<title>
|
||||
|
||||
@@ -22,7 +22,7 @@ import { UpdateRedis } from "@/components/dashboard/redis/update-redis";
|
||||
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
|
||||
import { RedisIcon } from "@/components/icons/data-tools-icons";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -72,21 +72,11 @@ const Redis = (
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<UseKeyboardNav forPage="redis" />
|
||||
<BreadcrumbSidebar
|
||||
list={[
|
||||
{ name: "Projects", href: "/dashboard/projects" },
|
||||
{
|
||||
name: data?.environment?.project?.name || "",
|
||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
},
|
||||
{
|
||||
name: data?.environment?.name || "",
|
||||
dropdownItems: environmentDropdownItems,
|
||||
},
|
||||
{
|
||||
name: data?.name || "",
|
||||
},
|
||||
]}
|
||||
<AdvanceBreadcrumb
|
||||
projectId={projectId as string}
|
||||
environmentId={environmentId as string}
|
||||
serviceId={redisId}
|
||||
serviceType="redis"
|
||||
/>
|
||||
<Head>
|
||||
<title>
|
||||
|
||||
Reference in New Issue
Block a user