mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-18 03:02:56 +00:00
feat(networks): enhance network management and UI components
- Added functionality for assigning Docker networks to services, allowing users to manage network associations directly from the UI. - Introduced new components for assigning networks to both individual services and compose files, improving user experience and flexibility. - Updated existing components to integrate network assignment features, ensuring seamless interaction within the dashboard. - Enhanced the database schema to support new network-related fields, including `networkIds` and `detachDokployNetwork`, for better service configuration. - Improved the layout and responsiveness of various UI elements to accommodate new features and enhance usability.
This commit is contained in:
@@ -46,3 +46,4 @@ yarn-error.log*
|
|||||||
.db
|
.db
|
||||||
|
|
||||||
.playwright-*
|
.playwright-*
|
||||||
|
.credentials
|
||||||
@@ -32,6 +32,8 @@ const baseApp: ApplicationNested = {
|
|||||||
railpackVersion: "0.15.4",
|
railpackVersion: "0.15.4",
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
previewLabels: [],
|
previewLabels: [],
|
||||||
|
networkIds: [],
|
||||||
|
detachDokployNetwork: false,
|
||||||
createEnvFile: true,
|
createEnvFile: true,
|
||||||
bitbucketRepositorySlug: "",
|
bitbucketRepositorySlug: "",
|
||||||
herokuVersion: "",
|
herokuVersion: "",
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ const baseApp: ApplicationNested = {
|
|||||||
rollbackActive: false,
|
rollbackActive: false,
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
previewLabels: [],
|
previewLabels: [],
|
||||||
|
networkIds: [],
|
||||||
|
detachDokployNetwork: false,
|
||||||
createEnvFile: true,
|
createEnvFile: true,
|
||||||
bitbucketRepositorySlug: "",
|
bitbucketRepositorySlug: "",
|
||||||
herokuVersion: "",
|
herokuVersion: "",
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export const ShowImport = ({ composeId }: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Dialog open={showModal} onOpenChange={setShowModal}>
|
<Dialog open={showModal} onOpenChange={setShowModal}>
|
||||||
<DialogContent className="max-w-[50vw]">
|
<DialogContent className="sm:max-w-3xl max-h-[85vh] flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-2xl font-bold">
|
<DialogTitle className="text-2xl font-bold">
|
||||||
Template Information
|
Template Information
|
||||||
@@ -199,7 +199,7 @@ export const ShowImport = ({ composeId }: Props) => {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6 flex-1 min-h-0 overflow-y-auto pr-1">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Code2 className="h-5 w-5 text-primary" />
|
<Code2 className="h-5 w-5 text-primary" />
|
||||||
@@ -207,12 +207,14 @@ export const ShowImport = ({ composeId }: Props) => {
|
|||||||
Docker Compose
|
Docker Compose
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<CodeEditor
|
<div className="max-h-[45vh] overflow-auto rounded-md border">
|
||||||
language="yaml"
|
<CodeEditor
|
||||||
value={templateInfo?.compose || ""}
|
language="yaml"
|
||||||
className="font-mono"
|
value={templateInfo?.compose || ""}
|
||||||
readOnly
|
className="font-mono"
|
||||||
/>
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { toast } from "sonner";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -111,7 +112,10 @@ export const IsolatedDeploymentTab = ({ composeId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">Enable Isolated Deployment</CardTitle>
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
|
Enable Isolated Deployment
|
||||||
|
<Badge variant="yellow">Deprecated</Badge>
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Configure isolated deployment to the compose file.
|
Configure isolated deployment to the compose file.
|
||||||
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
||||||
@@ -138,6 +142,11 @@ export const IsolatedDeploymentTab = ({ composeId }: Props) => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
Isolated deployment is deprecated. Use the Networks section above to
|
||||||
|
attach networks per service and detach them from dokploy-network —
|
||||||
|
it is declarative and does not break on restarts.
|
||||||
|
</AlertBlock>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
Preview Compose
|
Preview Compose
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-6xl max-h-200">
|
<DialogContent className="sm:max-w-6xl max-h-[85vh] flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Converted Compose</DialogTitle>
|
<DialogTitle>Converted Compose</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -62,10 +62,6 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
|
||||||
<AlertBlock type="info" className="mb-4">
|
|
||||||
Preview your docker-compose file with added domains. Note: At least
|
|
||||||
one domain must be specified for this conversion to take effect.
|
|
||||||
</AlertBlock>
|
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<div className="flex flex-row items-center justify-center min-h-100 border p-4 rounded-md">
|
<div className="flex flex-row items-center justify-center min-h-100 border p-4 rounded-md">
|
||||||
<Loader2 className="h-8 w-8 text-muted-foreground mb-2 animate-spin" />
|
<Loader2 className="h-8 w-8 text-muted-foreground mb-2 animate-spin" />
|
||||||
@@ -79,7 +75,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row gap-2 justify-end my-4">
|
<div className="flex flex-row gap-2 justify-end">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
isLoading={isPending}
|
isLoading={isPending}
|
||||||
@@ -100,14 +96,15 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<pre>
|
<div className="flex-1 min-h-0 overflow-auto rounded-md border">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={compose || ""}
|
value={compose || ""}
|
||||||
language="yaml"
|
language="yaml"
|
||||||
readOnly
|
readOnly
|
||||||
height="50rem"
|
height="100%"
|
||||||
|
wrapperClassName="h-full"
|
||||||
/>
|
/>
|
||||||
</pre>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -0,0 +1,347 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Check, ChevronsUpDown, Loader2, RefreshCw } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useFieldArray, useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
composeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceSchema = z.object({
|
||||||
|
serviceName: z.string(),
|
||||||
|
networkIds: z.array(z.string()),
|
||||||
|
detachDokployNetwork: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
services: z.array(serviceSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const AssignComposeNetworks = ({ composeId }: Props) => {
|
||||||
|
const [cacheType, setCacheType] = useState<"cache" | "fetch">("cache");
|
||||||
|
|
||||||
|
const { data: compose } = api.compose.one.useQuery({ composeId });
|
||||||
|
const {
|
||||||
|
data: services,
|
||||||
|
isLoading: isLoadingServices,
|
||||||
|
error: servicesError,
|
||||||
|
refetch: refetchServices,
|
||||||
|
isRefetching: isRefetchingServices,
|
||||||
|
} = api.compose.loadServices.useQuery(
|
||||||
|
{ composeId, type: cacheType },
|
||||||
|
{ retry: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const onRetry = () => {
|
||||||
|
setCacheType("fetch");
|
||||||
|
setTimeout(() => refetchServices(), 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: networks } = api.network.all.useQuery(
|
||||||
|
{ serverId: compose?.serverId ?? undefined },
|
||||||
|
{ enabled: compose !== undefined },
|
||||||
|
);
|
||||||
|
const { mutateAsync, isPending } = api.compose.update.useMutation();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: { services: [] },
|
||||||
|
});
|
||||||
|
const { fields } = useFieldArray({
|
||||||
|
control: form.control,
|
||||||
|
name: "services",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!services) return;
|
||||||
|
const serviceNetworks = compose?.serviceNetworks ?? [];
|
||||||
|
form.reset({
|
||||||
|
services: services.map((serviceName) => {
|
||||||
|
const config = serviceNetworks.find(
|
||||||
|
(s) => s.serviceName === serviceName,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
serviceName,
|
||||||
|
networkIds: config?.networkIds ?? [],
|
||||||
|
detachDokployNetwork: config?.detachDokployNetwork ?? false,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}, [services, compose?.serviceNetworks, form]);
|
||||||
|
|
||||||
|
const allowsBridge = compose?.composeType === "docker-compose";
|
||||||
|
const availableNetworks = (networks ?? []).filter(
|
||||||
|
(n) => allowsBridge || n.driver === "overlay",
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSubmit = async (values: FormValues) => {
|
||||||
|
try {
|
||||||
|
const serviceNetworks = values.services.filter(
|
||||||
|
(s) => s.networkIds.length > 0 || s.detachDokployNetwork,
|
||||||
|
);
|
||||||
|
await mutateAsync({
|
||||||
|
composeId,
|
||||||
|
serviceNetworks,
|
||||||
|
});
|
||||||
|
toast.success("Networks updated. Redeploy the compose to apply them.");
|
||||||
|
await utils.compose.one.invalidate({ composeId });
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Error updating networks", {
|
||||||
|
description: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-background">
|
||||||
|
<CardHeader className="flex flex-row items-start justify-between flex-wrap gap-2">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<CardTitle className="text-xl">Networks</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Attach Docker networks per service and detach it from
|
||||||
|
dokploy-network. Takes effect on the next deploy.
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
isLoading={isRefetchingServices}
|
||||||
|
onClick={onRetry}
|
||||||
|
>
|
||||||
|
<RefreshCw className="size-4" />
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
{servicesError ? (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<AlertBlock type="error">
|
||||||
|
Could not load the compose services. If this compose was just
|
||||||
|
created from a template, it hasn't been cloned yet — click Reload
|
||||||
|
to clone the repository and read its services.
|
||||||
|
</AlertBlock>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
isLoading={isRefetchingServices}
|
||||||
|
onClick={onRetry}
|
||||||
|
>
|
||||||
|
<RefreshCw className="size-4" />
|
||||||
|
Reload services
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : isLoadingServices ? (
|
||||||
|
<div className="flex flex-row gap-2 items-center justify-center py-10 text-sm text-muted-foreground">
|
||||||
|
<span>Loading services...</span>
|
||||||
|
<Loader2 className="animate-spin size-4" />
|
||||||
|
</div>
|
||||||
|
) : !services?.length ? (
|
||||||
|
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||||
|
No services found in this compose.
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="flex flex-col gap-4"
|
||||||
|
>
|
||||||
|
{fields.map((fieldItem, index) => (
|
||||||
|
<ServiceRow
|
||||||
|
key={fieldItem.id}
|
||||||
|
control={form.control}
|
||||||
|
index={index}
|
||||||
|
service={fieldItem.serviceName}
|
||||||
|
availableNetworks={availableNetworks}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!form.formState.isDirty}
|
||||||
|
isLoading={isPending}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type NetworkOption = { networkId: string; name: string; driver: string };
|
||||||
|
|
||||||
|
const ServiceRow = ({
|
||||||
|
control,
|
||||||
|
index,
|
||||||
|
service,
|
||||||
|
availableNetworks,
|
||||||
|
}: {
|
||||||
|
control: ReturnType<typeof useForm<FormValues>>["control"];
|
||||||
|
index: number;
|
||||||
|
service: string;
|
||||||
|
availableNetworks: NetworkOption[];
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3 rounded-lg border p-4">
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={`services.${index}.detachDokployNetwork`}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between gap-3 space-y-0">
|
||||||
|
<span className="text-sm font-medium">{service}</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Detach dokploy-network
|
||||||
|
</span>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={`services.${index}.networkIds`}
|
||||||
|
render={({ field }) => {
|
||||||
|
const selectedNetworks = availableNetworks.filter((n) =>
|
||||||
|
field.value.includes(n.networkId),
|
||||||
|
);
|
||||||
|
const toggle = (networkId: string) => {
|
||||||
|
field.onChange(
|
||||||
|
field.value.includes(networkId)
|
||||||
|
? field.value.filter((id) => id !== networkId)
|
||||||
|
: [...field.value, networkId],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<FormItem className="space-y-3">
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{selectedNetworks.length > 0
|
||||||
|
? `${selectedNetworks.length} network${
|
||||||
|
selectedNetworks.length > 1 ? "s" : ""
|
||||||
|
} selected`
|
||||||
|
: "Select networks..."}
|
||||||
|
</span>
|
||||||
|
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-[var(--radix-popover-trigger-width)] p-0"
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Search networks..."
|
||||||
|
className="h-9"
|
||||||
|
/>
|
||||||
|
<CommandList className="max-h-60">
|
||||||
|
{availableNetworks.length === 0 ? (
|
||||||
|
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||||
|
No networks available on this server.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CommandGroup>
|
||||||
|
{availableNetworks.map((n) => {
|
||||||
|
const isSelected = field.value.includes(
|
||||||
|
n.networkId,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={n.networkId}
|
||||||
|
value={n.name}
|
||||||
|
onSelect={() => toggle(n.networkId)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
className="mr-2"
|
||||||
|
onCheckedChange={() => toggle(n.networkId)}
|
||||||
|
/>
|
||||||
|
<span>{n.name}</span>
|
||||||
|
<Badge variant="outline" className="ml-2">
|
||||||
|
{n.driver}
|
||||||
|
</Badge>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto size-4",
|
||||||
|
isSelected ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
{selectedNetworks.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{selectedNetworks.map((n) => (
|
||||||
|
<Badge key={n.networkId} variant="secondary">
|
||||||
|
{n.name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
import { Check, ChevronsUpDown, X } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
type ServiceType =
|
||||||
|
| "application"
|
||||||
|
| "postgres"
|
||||||
|
| "mysql"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "redis"
|
||||||
|
| "libsql";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
type: ServiceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AssignNetworks = ({ id, type }: Props) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [selected, setSelected] = useState<string[]>([]);
|
||||||
|
const [detached, setDetached] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
service,
|
||||||
|
serverId,
|
||||||
|
networkIds,
|
||||||
|
detachDokployNetwork,
|
||||||
|
updateAsync,
|
||||||
|
isUpdating,
|
||||||
|
refetch,
|
||||||
|
} = useServiceNetworks(id, type);
|
||||||
|
|
||||||
|
const { data: networks } = api.network.all.useQuery(
|
||||||
|
{ serverId: serverId ?? undefined },
|
||||||
|
{ enabled: service !== undefined },
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: applicationDomains } = api.domain.byApplicationId.useQuery(
|
||||||
|
{ applicationId: id },
|
||||||
|
{ enabled: type === "application" },
|
||||||
|
);
|
||||||
|
const hasDomains = (applicationDomains?.length ?? 0) > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelected(networkIds ?? []);
|
||||||
|
setDetached(detachDokployNetwork ?? false);
|
||||||
|
}, [networkIds, detachDokployNetwork]);
|
||||||
|
|
||||||
|
const availableNetworks = (networks ?? []).filter(
|
||||||
|
(n) => n.driver === "overlay",
|
||||||
|
);
|
||||||
|
const selectedNetworks = availableNetworks.filter((n) =>
|
||||||
|
selected.includes(n.networkId),
|
||||||
|
);
|
||||||
|
const isDirty =
|
||||||
|
selected.length !== (networkIds?.length ?? 0) ||
|
||||||
|
selected.some((networkId) => !networkIds?.includes(networkId)) ||
|
||||||
|
detached !== detachDokployNetwork;
|
||||||
|
|
||||||
|
const toggle = (networkId: string) => {
|
||||||
|
setSelected((prev) =>
|
||||||
|
prev.includes(networkId)
|
||||||
|
? prev.filter((id) => id !== networkId)
|
||||||
|
: [...prev, networkId],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
try {
|
||||||
|
await updateAsync({
|
||||||
|
networkIds: selected,
|
||||||
|
detachDokployNetwork: detached,
|
||||||
|
});
|
||||||
|
toast.success("Networks updated. Redeploy the service to apply them.");
|
||||||
|
await refetch();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Error updating networks", {
|
||||||
|
description: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-background">
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<CardTitle className="text-xl">Networks</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Attach additional Docker networks to this service so it can reach
|
||||||
|
services on those networks. Takes effect on the next deploy.
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<div className="flex flex-row items-start justify-between gap-3 rounded-lg border p-4">
|
||||||
|
<div className="space-y-1 pr-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
Detach from dokploy-network
|
||||||
|
</span>
|
||||||
|
<Badge variant="secondary">dokploy-network</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
By default the service joins the shared dokploy-network. Detach it
|
||||||
|
to keep it reachable only through the networks you attach below.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch checked={detached} onCheckedChange={setDetached} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{detached && hasDomains && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
Warning: this service has domains. Detaching it from dokploy-network
|
||||||
|
will break Traefik routing, and its domains will stop working.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{detached && !hasDomains && selected.length === 0 && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
This service is detached from dokploy-network but has no other
|
||||||
|
network attached. It would be unreachable, so dokploy-network will
|
||||||
|
be kept until you attach a network below.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{selectedNetworks.length > 0
|
||||||
|
? `${selectedNetworks.length} network${
|
||||||
|
selectedNetworks.length > 1 ? "s" : ""
|
||||||
|
} selected`
|
||||||
|
: "Select networks..."}
|
||||||
|
</span>
|
||||||
|
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-[var(--radix-popover-trigger-width)] p-0"
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search networks..." className="h-9" />
|
||||||
|
<CommandList className="max-h-60">
|
||||||
|
{availableNetworks.length === 0 ? (
|
||||||
|
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||||
|
No overlay networks on this server.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CommandEmpty className="py-6 text-center text-sm text-muted-foreground">
|
||||||
|
No networks found.
|
||||||
|
</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{availableNetworks.map((n) => {
|
||||||
|
const isSelected = selected.includes(n.networkId);
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={n.networkId}
|
||||||
|
value={n.name}
|
||||||
|
onSelect={() => toggle(n.networkId)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
className="mr-2"
|
||||||
|
onCheckedChange={() => toggle(n.networkId)}
|
||||||
|
/>
|
||||||
|
<span>{n.name}</span>
|
||||||
|
<Badge variant="outline" className="ml-2">
|
||||||
|
{n.driver}
|
||||||
|
</Badge>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto size-4",
|
||||||
|
isSelected ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
{selectedNetworks.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{selectedNetworks.map((n) => (
|
||||||
|
<Badge
|
||||||
|
key={n.networkId}
|
||||||
|
variant="secondary"
|
||||||
|
className="flex items-center gap-1 pr-1"
|
||||||
|
>
|
||||||
|
{n.name}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggle(n.networkId)}
|
||||||
|
className="ml-0.5 rounded-full outline-hidden hover:opacity-70"
|
||||||
|
>
|
||||||
|
<X className="size-3" />
|
||||||
|
<span className="sr-only">Remove {n.name}</span>
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button disabled={!isDirty} isLoading={isUpdating} onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maps a service type to its one-query and update-mutation, normalizing the
|
||||||
|
// per-type id field name and the networkIds field.
|
||||||
|
const useServiceNetworks = (id: string, type: ServiceType) => {
|
||||||
|
const application = api.application.one.useQuery(
|
||||||
|
{ applicationId: id },
|
||||||
|
{ enabled: type === "application" },
|
||||||
|
);
|
||||||
|
const postgres = api.postgres.one.useQuery(
|
||||||
|
{ postgresId: id },
|
||||||
|
{ enabled: type === "postgres" },
|
||||||
|
);
|
||||||
|
const mysql = api.mysql.one.useQuery(
|
||||||
|
{ mysqlId: id },
|
||||||
|
{ enabled: type === "mysql" },
|
||||||
|
);
|
||||||
|
const mariadb = api.mariadb.one.useQuery(
|
||||||
|
{ mariadbId: id },
|
||||||
|
{ enabled: type === "mariadb" },
|
||||||
|
);
|
||||||
|
const mongo = api.mongo.one.useQuery(
|
||||||
|
{ mongoId: id },
|
||||||
|
{ enabled: type === "mongo" },
|
||||||
|
);
|
||||||
|
const redis = api.redis.one.useQuery(
|
||||||
|
{ redisId: id },
|
||||||
|
{ enabled: type === "redis" },
|
||||||
|
);
|
||||||
|
const libsql = api.libsql.one.useQuery(
|
||||||
|
{ libsqlId: id },
|
||||||
|
{ enabled: type === "libsql" },
|
||||||
|
);
|
||||||
|
const applicationUpdate = api.application.update.useMutation();
|
||||||
|
const postgresUpdate = api.postgres.update.useMutation();
|
||||||
|
const mysqlUpdate = api.mysql.update.useMutation();
|
||||||
|
const mariadbUpdate = api.mariadb.update.useMutation();
|
||||||
|
const mongoUpdate = api.mongo.update.useMutation();
|
||||||
|
const redisUpdate = api.redis.update.useMutation();
|
||||||
|
const libsqlUpdate = api.libsql.update.useMutation();
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
application: {
|
||||||
|
query: application,
|
||||||
|
mutation: applicationUpdate,
|
||||||
|
save: (payload: SavePayload) =>
|
||||||
|
applicationUpdate.mutateAsync({ applicationId: id, ...payload }),
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
query: postgres,
|
||||||
|
mutation: postgresUpdate,
|
||||||
|
save: (payload: SavePayload) =>
|
||||||
|
postgresUpdate.mutateAsync({ postgresId: id, ...payload }),
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
query: mysql,
|
||||||
|
mutation: mysqlUpdate,
|
||||||
|
save: (payload: SavePayload) =>
|
||||||
|
mysqlUpdate.mutateAsync({ mysqlId: id, ...payload }),
|
||||||
|
},
|
||||||
|
mariadb: {
|
||||||
|
query: mariadb,
|
||||||
|
mutation: mariadbUpdate,
|
||||||
|
save: (payload: SavePayload) =>
|
||||||
|
mariadbUpdate.mutateAsync({ mariadbId: id, ...payload }),
|
||||||
|
},
|
||||||
|
mongo: {
|
||||||
|
query: mongo,
|
||||||
|
mutation: mongoUpdate,
|
||||||
|
save: (payload: SavePayload) =>
|
||||||
|
mongoUpdate.mutateAsync({ mongoId: id, ...payload }),
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
query: redis,
|
||||||
|
mutation: redisUpdate,
|
||||||
|
save: (payload: SavePayload) =>
|
||||||
|
redisUpdate.mutateAsync({ redisId: id, ...payload }),
|
||||||
|
},
|
||||||
|
libsql: {
|
||||||
|
query: libsql,
|
||||||
|
mutation: libsqlUpdate,
|
||||||
|
save: (payload: SavePayload) =>
|
||||||
|
libsqlUpdate.mutateAsync({ libsqlId: id, ...payload }),
|
||||||
|
},
|
||||||
|
}[type];
|
||||||
|
|
||||||
|
const service = map.query.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
service,
|
||||||
|
serverId: service?.serverId ?? null,
|
||||||
|
networkIds: service?.networkIds ?? [],
|
||||||
|
detachDokployNetwork: service?.detachDokployNetwork ?? false,
|
||||||
|
updateAsync: map.save,
|
||||||
|
isUpdating: map.mutation.isPending,
|
||||||
|
refetch: map.query.refetch,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type SavePayload = {
|
||||||
|
networkIds: string[];
|
||||||
|
detachDokployNetwork: boolean;
|
||||||
|
};
|
||||||
@@ -328,7 +328,7 @@ export const RequestsTable = ({ dateRange }: RequestsTableProps) => {
|
|||||||
open={!!selectedRow}
|
open={!!selectedRow}
|
||||||
onOpenChange={(_open) => setSelectedRow(undefined)}
|
onOpenChange={(_open) => setSelectedRow(undefined)}
|
||||||
>
|
>
|
||||||
<SheetContent className="sm:max-w-[740px] flex flex-col">
|
<SheetContent className="w-full sm:max-w-[740px]! flex flex-col">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Request log</SheetTitle>
|
<SheetTitle>Request log</SheetTitle>
|
||||||
<SheetDescription>
|
<SheetDescription>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
|
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
|
||||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||||
|
import { AssignNetworks } from "@/components/dashboard/networks/assign-networks";
|
||||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||||
import { ShowClusterSettings } from "../application/advanced/cluster/show-cluster-settings";
|
import { ShowClusterSettings } from "../application/advanced/cluster/show-cluster-settings";
|
||||||
import { RebuildDatabase } from "./rebuild-database";
|
import { RebuildDatabase } from "./rebuild-database";
|
||||||
@@ -21,6 +22,7 @@ export const ShowDatabaseAdvancedSettings = ({ id, type }: Props) => {
|
|||||||
<ShowClusterSettings id={id} type={type} />
|
<ShowClusterSettings id={id} type={type} />
|
||||||
) : null}
|
) : null}
|
||||||
<ShowVolumes id={id} type={type} />
|
<ShowVolumes id={id} type={type} />
|
||||||
|
<AssignNetworks id={id} type={type} />
|
||||||
<ShowResources id={id} type={type} />
|
<ShowResources id={id} type={type} />
|
||||||
<RebuildDatabase id={id} type={type} />
|
<RebuildDatabase id={id} type={type} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const DrawerLogs = ({ isOpen, onClose, filteredLogs }: Props) => {
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SheetContent className="sm:max-w-[740px] flex flex-col">
|
<SheetContent className="w-full sm:max-w-[740px]! flex flex-col">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Deployment Logs</SheetTitle>
|
<SheetTitle>Deployment Logs</SheetTitle>
|
||||||
<SheetDescription>Details of the request log entry.</SheetDescription>
|
<SheetDescription>Details of the request log entry.</SheetDescription>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||||||
<textarea
|
<textarea
|
||||||
data-slot="textarea"
|
data-slot="textarea"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex field-sizing-content min-h-[80px] w-full rounded-lg border border-input bg-transparent px-3 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
"flex field-sizing-content min-h-[80px] w-full [overflow-wrap:anywhere] rounded-lg border border-input bg-transparent px-3 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
+9
-1
@@ -14,11 +14,19 @@ CREATE TABLE "network" (
|
|||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
ALTER TABLE "application" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
ALTER TABLE "application" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
||||||
ALTER TABLE "compose" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
ALTER TABLE "application" ADD COLUMN "detachDokployNetwork" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "compose" ADD COLUMN "serviceNetworks" jsonb DEFAULT '[]'::jsonb;--> statement-breakpoint
|
||||||
|
ALTER TABLE "libsql" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
||||||
|
ALTER TABLE "libsql" ADD COLUMN "detachDokployNetwork" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE "mariadb" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
ALTER TABLE "mariadb" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "detachDokployNetwork" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE "mongo" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
ALTER TABLE "mongo" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "detachDokployNetwork" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE "mysql" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
ALTER TABLE "mysql" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "detachDokployNetwork" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE "postgres" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
ALTER TABLE "postgres" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "detachDokployNetwork" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE "redis" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
ALTER TABLE "redis" ADD COLUMN "networkIds" text[] DEFAULT '{}';--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "detachDokployNetwork" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE "network" ADD CONSTRAINT "network_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
ALTER TABLE "network" ADD CONSTRAINT "network_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
ALTER TABLE "network" ADD CONSTRAINT "network_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
|
ALTER TABLE "network" ADD CONSTRAINT "network_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"id": "957b942e-9aeb-4551-8d5d-1174eec70fd6",
|
"id": "7a9f40d5-02c9-4ac5-8b88-08bdbdd02aac",
|
||||||
"prevId": "daaa9837-09f3-452c-b5ba-232c3d6eeca2",
|
"prevId": "daaa9837-09f3-452c-b5ba-232c3d6eeca2",
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
@@ -1590,6 +1590,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"default": "'{}'"
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"detachDokployNetwork": {
|
||||||
|
"name": "detachDokployNetwork",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
@@ -2610,12 +2617,12 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
"networkIds": {
|
"serviceNetworks": {
|
||||||
"name": "networkIds",
|
"name": "serviceNetworks",
|
||||||
"type": "text[]",
|
"type": "jsonb",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"default": "'{}'"
|
"default": "'[]'::jsonb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
@@ -3977,6 +3984,20 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": 1
|
"default": 1
|
||||||
},
|
},
|
||||||
|
"networkIds": {
|
||||||
|
"name": "networkIds",
|
||||||
|
"type": "text[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"detachDokployNetwork": {
|
||||||
|
"name": "detachDokployNetwork",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"name": "createdAt",
|
"name": "createdAt",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -4250,6 +4271,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"default": "'{}'"
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"detachDokployNetwork": {
|
||||||
|
"name": "detachDokployNetwork",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
@@ -4502,6 +4530,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"default": "'{}'"
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"detachDokployNetwork": {
|
||||||
|
"name": "detachDokployNetwork",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
@@ -4764,119 +4799,6 @@
|
|||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"public.network": {
|
|
||||||
"name": "network",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"networkId": {
|
|
||||||
"name": "networkId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"driver": {
|
|
||||||
"name": "driver",
|
|
||||||
"type": "networkDriver",
|
|
||||||
"typeSchema": "public",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "'bridge'"
|
|
||||||
},
|
|
||||||
"internal": {
|
|
||||||
"name": "internal",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"attachable": {
|
|
||||||
"name": "attachable",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"enableIPv4": {
|
|
||||||
"name": "enableIPv4",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"enableIPv6": {
|
|
||||||
"name": "enableIPv6",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"ipam": {
|
|
||||||
"name": "ipam",
|
|
||||||
"type": "jsonb",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"default": "'{}'::jsonb"
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"name": "createdAt",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"organizationId": {
|
|
||||||
"name": "organizationId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"serverId": {
|
|
||||||
"name": "serverId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"network_organizationId_organization_id_fk": {
|
|
||||||
"name": "network_organizationId_organization_id_fk",
|
|
||||||
"tableFrom": "network",
|
|
||||||
"tableTo": "organization",
|
|
||||||
"columnsFrom": [
|
|
||||||
"organizationId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"network_serverId_server_serverId_fk": {
|
|
||||||
"name": "network_serverId_server_serverId_fk",
|
|
||||||
"tableFrom": "network",
|
|
||||||
"tableTo": "server",
|
|
||||||
"columnsFrom": [
|
|
||||||
"serverId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"serverId"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"policies": {},
|
|
||||||
"checkConstraints": {},
|
|
||||||
"isRLSEnabled": false
|
|
||||||
},
|
|
||||||
"public.mysql": {
|
"public.mysql": {
|
||||||
"name": "mysql",
|
"name": "mysql",
|
||||||
"schema": "",
|
"schema": "",
|
||||||
@@ -5088,6 +5010,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"default": "'{}'"
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"detachDokployNetwork": {
|
||||||
|
"name": "detachDokployNetwork",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
@@ -5133,6 +5062,119 @@
|
|||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
|
"public.network": {
|
||||||
|
"name": "network",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"networkId": {
|
||||||
|
"name": "networkId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"name": "driver",
|
||||||
|
"type": "networkDriver",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'bridge'"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"name": "internal",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"attachable": {
|
||||||
|
"name": "attachable",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"enableIPv4": {
|
||||||
|
"name": "enableIPv4",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"enableIPv6": {
|
||||||
|
"name": "enableIPv6",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"ipam": {
|
||||||
|
"name": "ipam",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "'{}'::jsonb"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"organizationId": {
|
||||||
|
"name": "organizationId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"serverId": {
|
||||||
|
"name": "serverId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"network_organizationId_organization_id_fk": {
|
||||||
|
"name": "network_organizationId_organization_id_fk",
|
||||||
|
"tableFrom": "network",
|
||||||
|
"tableTo": "organization",
|
||||||
|
"columnsFrom": [
|
||||||
|
"organizationId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"network_serverId_server_serverId_fk": {
|
||||||
|
"name": "network_serverId_server_serverId_fk",
|
||||||
|
"tableFrom": "network",
|
||||||
|
"tableTo": "server",
|
||||||
|
"columnsFrom": [
|
||||||
|
"serverId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"serverId"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
"public.custom": {
|
"public.custom": {
|
||||||
"name": "custom",
|
"name": "custom",
|
||||||
"schema": "",
|
"schema": "",
|
||||||
@@ -6307,6 +6349,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"default": "'{}'"
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"detachDokployNetwork": {
|
||||||
|
"name": "detachDokployNetwork",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
@@ -6807,6 +6856,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"default": "'{}'"
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"detachDokployNetwork": {
|
||||||
|
"name": "detachDokployNetwork",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
|
|||||||
@@ -1230,8 +1230,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 175,
|
"idx": 175,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1784692797270,
|
"when": 1784965709630,
|
||||||
"tag": "0175_gray_dreaming_celestial",
|
"tag": "0175_fantastic_peter_quill",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+2
@@ -35,6 +35,7 @@ import { ShowVolumeBackups } from "@/components/dashboard/application/volume-bac
|
|||||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
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 { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||||
|
import { AssignNetworks } from "@/components/dashboard/networks/assign-networks";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
@@ -418,6 +419,7 @@ const Service = (
|
|||||||
<ShowBuildServer applicationId={applicationId} />
|
<ShowBuildServer applicationId={applicationId} />
|
||||||
<ShowResources id={applicationId} type="application" />
|
<ShowResources id={applicationId} type="application" />
|
||||||
<ShowVolumes id={applicationId} type="application" />
|
<ShowVolumes id={applicationId} type="application" />
|
||||||
|
<AssignNetworks id={applicationId} type="application" />
|
||||||
<ShowRedirects applicationId={applicationId} />
|
<ShowRedirects applicationId={applicationId} />
|
||||||
<ShowSecurity applicationId={applicationId} />
|
<ShowSecurity applicationId={applicationId} />
|
||||||
<ShowPorts applicationId={applicationId} />
|
<ShowPorts applicationId={applicationId} />
|
||||||
|
|||||||
+2
@@ -31,6 +31,7 @@ import { UpdateCompose } from "@/components/dashboard/compose/update-compose";
|
|||||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||||
import { ComposeFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-compose-monitoring";
|
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 { ComposePaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring";
|
||||||
|
import { AssignComposeNetworks } from "@/components/dashboard/networks/assign-compose-networks";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
@@ -427,6 +428,7 @@ const Service = (
|
|||||||
<AddCommandCompose composeId={composeId} />
|
<AddCommandCompose composeId={composeId} />
|
||||||
<ShowVolumes id={composeId} type="compose" />
|
<ShowVolumes id={composeId} type="compose" />
|
||||||
<ShowImport composeId={composeId} />
|
<ShowImport composeId={composeId} />
|
||||||
|
<AssignComposeNetworks composeId={composeId} />
|
||||||
<IsolatedDeploymentTab composeId={composeId} />
|
<IsolatedDeploymentTab composeId={composeId} />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
+1
-1
@@ -185,7 +185,7 @@ const Libsql = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-auto">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
+1
-1
@@ -199,7 +199,7 @@ const Mariadb = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-auto">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
+1
-1
@@ -199,7 +199,7 @@ const Mongo = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-auto">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
+1
-1
@@ -199,7 +199,7 @@ const MySql = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-auto">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start ",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start ",
|
||||||
|
|||||||
+1
-1
@@ -200,7 +200,7 @@ const Postgresql = (
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-auto">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
+1
-1
@@ -198,7 +198,7 @@ const Redis = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-auto">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
@@ -362,7 +362,6 @@ ${input.logs}`,
|
|||||||
name: input.name,
|
name: input.name,
|
||||||
sourceType: "raw",
|
sourceType: "raw",
|
||||||
appName: `${projectName}-${generatePassword(6)}`,
|
appName: `${projectName}-${generatePassword(6)}`,
|
||||||
isolatedDeployment: true,
|
|
||||||
environmentId: input.environmentId,
|
environmentId: input.environmentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -639,7 +639,6 @@ export const composeRouter = createTRPCRouter({
|
|||||||
name: input.id,
|
name: input.id,
|
||||||
sourceType: "raw",
|
sourceType: "raw",
|
||||||
appName: appName,
|
appName: appName,
|
||||||
isolatedDeployment: template.config.config?.isolated !== false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await addNewService(ctx, compose.composeId);
|
await addNewService(ctx, compose.composeId);
|
||||||
@@ -997,7 +996,6 @@ export const composeRouter = createTRPCRouter({
|
|||||||
composeFile: templateData.compose,
|
composeFile: templateData.compose,
|
||||||
sourceType: "raw",
|
sourceType: "raw",
|
||||||
env: processedTemplate.envs?.join("\n"),
|
env: processedTemplate.envs?.join("\n"),
|
||||||
isolatedDeployment: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (processedTemplate.mounts && processedTemplate.mounts.length > 0) {
|
if (processedTemplate.mounts && processedTemplate.mounts.length > 0) {
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return environments;
|
return environments;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: `Error fetching environments: ${error instanceof Error ? error.message : error}`,
|
message: `Error fetching environments: ${error instanceof Error ? error.message : error}`,
|
||||||
|
|||||||
@@ -259,9 +259,13 @@ export const scheduleRouter = createTRPCRouter({
|
|||||||
where: where[input.scheduleType],
|
where: where[input.scheduleType],
|
||||||
orderBy: [asc(schedules.createdAt)],
|
orderBy: [asc(schedules.createdAt)],
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
|
columns: { applicationId: true, appName: true, name: true, serverId: true },
|
||||||
|
},
|
||||||
server: true,
|
server: true,
|
||||||
compose: true,
|
compose: {
|
||||||
|
columns: { composeId: true, appName: true, name: true, serverId: true },
|
||||||
|
},
|
||||||
deployments: {
|
deployments: {
|
||||||
orderBy: [desc(deployments.createdAt)],
|
orderBy: [desc(deployments.createdAt)],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,14 +57,16 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
return await db.query.volumeBackups.findMany({
|
return await db.query.volumeBackups.findMany({
|
||||||
where: eq(volumeBackups[`${input.volumeBackupType}Id`], input.id),
|
where: eq(volumeBackups[`${input.volumeBackupType}Id`], input.id),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
postgres: true,
|
columns: { applicationId: true, appName: true, serverId: true },
|
||||||
mysql: true,
|
},
|
||||||
mariadb: true,
|
postgres: { columns: { postgresId: true, appName: true, serverId: true } },
|
||||||
mongo: true,
|
mysql: { columns: { mysqlId: true, appName: true, serverId: true } },
|
||||||
redis: true,
|
mariadb: { columns: { mariadbId: true, appName: true, serverId: true } },
|
||||||
compose: true,
|
mongo: { columns: { mongoId: true, appName: true, serverId: true } },
|
||||||
libsql: true,
|
redis: { columns: { redisId: true, appName: true, serverId: true } },
|
||||||
|
compose: { columns: { composeId: true, appName: true, serverId: true } },
|
||||||
|
libsql: { columns: { libsqlId: true, appName: true, serverId: true } },
|
||||||
},
|
},
|
||||||
orderBy: [desc(volumeBackups.createdAt)],
|
orderBy: [desc(volumeBackups.createdAt)],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -234,6 +234,9 @@ export const applications = pgTable("application", {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
networkIds: text("networkIds").array().default([]),
|
networkIds: text("networkIds").array().default([]),
|
||||||
|
detachDokployNetwork: boolean("detachDokployNetwork")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const applicationsRelations = relations(
|
export const applicationsRelations = relations(
|
||||||
@@ -376,6 +379,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
watchPaths: z.array(z.string()).optional().optional(),
|
watchPaths: z.array(z.string()).optional().optional(),
|
||||||
previewLabels: z.array(z.string()).optional(),
|
previewLabels: z.array(z.string()).optional(),
|
||||||
networkIds: z.array(z.string()).optional(),
|
networkIds: z.array(z.string()).optional(),
|
||||||
|
detachDokployNetwork: z.boolean().optional(),
|
||||||
cleanCache: z.boolean().optional(),
|
cleanCache: z.boolean().optional(),
|
||||||
stopGracePeriodSwarm: z.number().nullable(),
|
stopGracePeriodSwarm: z.number().nullable(),
|
||||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
import {
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
jsonb,
|
||||||
|
pgEnum,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -113,7 +120,15 @@ export const compose = pgTable("compose", {
|
|||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
networkIds: text("networkIds").array().default([]),
|
serviceNetworks: jsonb("serviceNetworks")
|
||||||
|
.$type<
|
||||||
|
Array<{
|
||||||
|
serviceName: string;
|
||||||
|
networkIds: string[];
|
||||||
|
detachDokployNetwork: boolean;
|
||||||
|
}>
|
||||||
|
>()
|
||||||
|
.default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const composeRelations = relations(compose, ({ one, many }) => ({
|
export const composeRelations = relations(compose, ({ one, many }) => ({
|
||||||
@@ -175,6 +190,15 @@ const createSchema = createInsertSchema(compose, {
|
|||||||
.optional(),
|
.optional(),
|
||||||
triggerType: z.enum(["push", "tag"]).optional(),
|
triggerType: z.enum(["push", "tag"]).optional(),
|
||||||
composeStatus: z.enum(["idle", "running", "done", "error"]).optional(),
|
composeStatus: z.enum(["idle", "running", "done", "error"]).optional(),
|
||||||
|
serviceNetworks: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
serviceName: z.string(),
|
||||||
|
networkIds: z.array(z.string()),
|
||||||
|
detachDokployNetwork: z.boolean(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateCompose = createSchema.pick({
|
export const apiCreateCompose = createSchema.pick({
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export * from "./libsql";
|
|||||||
export * from "./mariadb";
|
export * from "./mariadb";
|
||||||
export * from "./mongo";
|
export * from "./mongo";
|
||||||
export * from "./mount";
|
export * from "./mount";
|
||||||
export * from "./network";
|
|
||||||
export * from "./mysql";
|
export * from "./mysql";
|
||||||
|
export * from "./network";
|
||||||
export * from "./notification";
|
export * from "./notification";
|
||||||
export * from "./patch";
|
export * from "./patch";
|
||||||
export * from "./port";
|
export * from "./port";
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ export const libsql = pgTable("libsql", {
|
|||||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "number" }),
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "number" }),
|
||||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
|
networkIds: text("networkIds").array().default([]),
|
||||||
|
detachDokployNetwork: boolean("detachDokployNetwork")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -146,6 +150,8 @@ const createSchema = createInsertSchema(libsql, {
|
|||||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
stopGracePeriodSwarm: z.number().nullable(),
|
stopGracePeriodSwarm: z.number().nullable(),
|
||||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
|
networkIds: z.array(z.string()).optional(),
|
||||||
|
detachDokployNetwork: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateLibsql = createSchema
|
export const apiCreateLibsql = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import {
|
||||||
|
bigint,
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
json,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -89,6 +96,9 @@ export const mariadb = pgTable("mariadb", {
|
|||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
networkIds: text("networkIds").array().default([]),
|
networkIds: text("networkIds").array().default([]),
|
||||||
|
detachDokployNetwork: boolean("detachDokployNetwork")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
|
export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
|
||||||
@@ -149,6 +159,8 @@ const createSchema = createInsertSchema(mariadb, {
|
|||||||
stopGracePeriodSwarm: z.number().nullable(),
|
stopGracePeriodSwarm: z.number().nullable(),
|
||||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||||
|
networkIds: z.array(z.string()).optional(),
|
||||||
|
detachDokployNetwork: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMariaDB = createSchema.pick({
|
export const apiCreateMariaDB = createSchema.pick({
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ export const mongo = pgTable("mongo", {
|
|||||||
}),
|
}),
|
||||||
replicaSets: boolean("replicaSets").default(false),
|
replicaSets: boolean("replicaSets").default(false),
|
||||||
networkIds: text("networkIds").array().default([]),
|
networkIds: text("networkIds").array().default([]),
|
||||||
|
detachDokployNetwork: boolean("detachDokployNetwork")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mongoRelations = relations(mongo, ({ one, many }) => ({
|
export const mongoRelations = relations(mongo, ({ one, many }) => ({
|
||||||
@@ -147,6 +150,8 @@ const createSchema = createInsertSchema(mongo, {
|
|||||||
stopGracePeriodSwarm: z.number().nullable(),
|
stopGracePeriodSwarm: z.number().nullable(),
|
||||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||||
|
networkIds: z.array(z.string()).optional(),
|
||||||
|
detachDokployNetwork: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMongo = createSchema.pick({
|
export const apiCreateMongo = createSchema.pick({
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import {
|
||||||
|
bigint,
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
json,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -87,6 +94,9 @@ export const mysql = pgTable("mysql", {
|
|||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
networkIds: text("networkIds").array().default([]),
|
networkIds: text("networkIds").array().default([]),
|
||||||
|
detachDokployNetwork: boolean("detachDokployNetwork")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mysqlRelations = relations(mysql, ({ one, many }) => ({
|
export const mysqlRelations = relations(mysql, ({ one, many }) => ({
|
||||||
@@ -146,6 +156,8 @@ const createSchema = createInsertSchema(mysql, {
|
|||||||
stopGracePeriodSwarm: z.number().nullable(),
|
stopGracePeriodSwarm: z.number().nullable(),
|
||||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||||
|
networkIds: z.array(z.string()).optional(),
|
||||||
|
detachDokployNetwork: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMySql = createSchema.pick({
|
export const apiCreateMySql = createSchema.pick({
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import {
|
||||||
|
bigint,
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
json,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -87,6 +94,9 @@ export const postgres = pgTable("postgres", {
|
|||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
networkIds: text("networkIds").array().default([]),
|
networkIds: text("networkIds").array().default([]),
|
||||||
|
detachDokployNetwork: boolean("detachDokployNetwork")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const postgresRelations = relations(postgres, ({ one, many }) => ({
|
export const postgresRelations = relations(postgres, ({ one, many }) => ({
|
||||||
@@ -141,6 +151,8 @@ const createSchema = createInsertSchema(postgres, {
|
|||||||
stopGracePeriodSwarm: z.number().nullable(),
|
stopGracePeriodSwarm: z.number().nullable(),
|
||||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||||
|
networkIds: z.array(z.string()).optional(),
|
||||||
|
detachDokployNetwork: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreatePostgres = createSchema.pick({
|
export const apiCreatePostgres = createSchema.pick({
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import {
|
||||||
|
bigint,
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
json,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -81,6 +88,9 @@ export const redis = pgTable("redis", {
|
|||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
networkIds: text("networkIds").array().default([]),
|
networkIds: text("networkIds").array().default([]),
|
||||||
|
detachDokployNetwork: boolean("detachDokployNetwork")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const redisRelations = relations(redis, ({ one, many }) => ({
|
export const redisRelations = relations(redis, ({ one, many }) => ({
|
||||||
@@ -130,6 +140,8 @@ const createSchema = createInsertSchema(redis, {
|
|||||||
stopGracePeriodSwarm: z.number().nullable(),
|
stopGracePeriodSwarm: z.number().nullable(),
|
||||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||||
|
networkIds: z.array(z.string()).optional(),
|
||||||
|
detachDokployNetwork: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateRedis = createSchema.pick({
|
export const apiCreateRedis = createSchema.pick({
|
||||||
|
|||||||
@@ -83,8 +83,17 @@ export const findDeploymentById = async (deploymentId: string) => {
|
|||||||
const deployment = await db.query.deployments.findFirst({
|
const deployment = await db.query.deployments.findFirst({
|
||||||
where: eq(deployments.deploymentId, deploymentId),
|
where: eq(deployments.deploymentId, deploymentId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
compose: true,
|
columns: {
|
||||||
|
applicationId: true,
|
||||||
|
appName: true,
|
||||||
|
name: true,
|
||||||
|
serverId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compose: {
|
||||||
|
columns: { composeId: true, appName: true, name: true, serverId: true },
|
||||||
|
},
|
||||||
schedule: true,
|
schedule: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ export const findDomainById = async (domainId: string) => {
|
|||||||
const domain = await db.query.domains.findFirst({
|
const domain = await db.query.domains.findFirst({
|
||||||
where: eq(domains.domainId, domainId),
|
where: eq(domains.domainId, domainId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
|
columns: { applicationId: true, appName: true, name: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
@@ -96,7 +98,9 @@ export const findDomainsByApplicationId = async (applicationId: string) => {
|
|||||||
const domainsArray = await db.query.domains.findMany({
|
const domainsArray = await db.query.domains.findMany({
|
||||||
where: eq(domains.applicationId, applicationId),
|
where: eq(domains.applicationId, applicationId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
|
columns: { applicationId: true, appName: true, name: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,7 +111,9 @@ export const findDomainsByComposeId = async (composeId: string) => {
|
|||||||
const domainsArray = await db.query.domains.findMany({
|
const domainsArray = await db.query.domains.findMany({
|
||||||
where: eq(domains.composeId, composeId),
|
where: eq(domains.composeId, composeId),
|
||||||
with: {
|
with: {
|
||||||
compose: true,
|
compose: {
|
||||||
|
columns: { composeId: true, appName: true, name: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -201,18 +201,51 @@ export const findEnvironmentById = async (environmentId: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const findEnvironmentsByProjectId = async (projectId: string) => {
|
export const findEnvironmentsByProjectId = async (projectId: string) => {
|
||||||
|
const serviceColumns = {
|
||||||
|
name: true,
|
||||||
|
description: true,
|
||||||
|
appName: true,
|
||||||
|
createdAt: true,
|
||||||
|
serverId: true,
|
||||||
|
applicationStatus: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
const projectEnvironments = await db.query.environments.findMany({
|
const projectEnvironments = await db.query.environments.findMany({
|
||||||
where: eq(environments.projectId, projectId),
|
where: eq(environments.projectId, projectId),
|
||||||
orderBy: asc(environments.createdAt),
|
orderBy: asc(environments.createdAt),
|
||||||
with: {
|
with: {
|
||||||
applications: true,
|
applications: {
|
||||||
mariadb: true,
|
columns: { ...serviceColumns, applicationId: true, icon: true },
|
||||||
mongo: true,
|
with: { server: { columns: { name: true } } },
|
||||||
mysql: true,
|
},
|
||||||
postgres: true,
|
mariadb: {
|
||||||
redis: true,
|
columns: { ...serviceColumns, mariadbId: true },
|
||||||
compose: true,
|
with: { server: { columns: { name: true } } },
|
||||||
libsql: true,
|
},
|
||||||
|
mongo: {
|
||||||
|
columns: { ...serviceColumns, mongoId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
columns: { ...serviceColumns, mysqlId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
columns: { ...serviceColumns, postgresId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
columns: { ...serviceColumns, redisId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
compose: {
|
||||||
|
columns: { ...serviceColumns, composeId: true, composeStatus: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
libsql: {
|
||||||
|
columns: { ...serviceColumns, libsqlId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
project: true,
|
project: true,
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
|||||||
@@ -107,81 +107,29 @@ export const createFileMount = async (mountId: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const findMountById = async (mountId: string) => {
|
export const findMountById = async (mountId: string) => {
|
||||||
|
const serviceWith = {
|
||||||
|
columns: { serverId: true, appName: true },
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
columns: {},
|
||||||
|
with: {
|
||||||
|
project: { columns: { organizationId: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
const mount = await db.query.mounts.findFirst({
|
const mount = await db.query.mounts.findFirst({
|
||||||
where: eq(mounts.mountId, mountId),
|
where: eq(mounts.mountId, mountId),
|
||||||
with: {
|
with: {
|
||||||
application: {
|
application: serviceWith,
|
||||||
with: {
|
compose: serviceWith,
|
||||||
environment: {
|
libsql: serviceWith,
|
||||||
with: {
|
mariadb: serviceWith,
|
||||||
project: true,
|
mongo: serviceWith,
|
||||||
},
|
mysql: serviceWith,
|
||||||
},
|
postgres: serviceWith,
|
||||||
},
|
redis: serviceWith,
|
||||||
},
|
|
||||||
compose: {
|
|
||||||
with: {
|
|
||||||
environment: {
|
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
libsql: {
|
|
||||||
with: {
|
|
||||||
environment: {
|
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mariadb: {
|
|
||||||
with: {
|
|
||||||
environment: {
|
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mongo: {
|
|
||||||
with: {
|
|
||||||
environment: {
|
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mysql: {
|
|
||||||
with: {
|
|
||||||
environment: {
|
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
postgres: {
|
|
||||||
with: {
|
|
||||||
environment: {
|
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
redis: {
|
|
||||||
with: {
|
|
||||||
environment: {
|
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!mount) {
|
if (!mount) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { type apiCreateNetwork, network } from "@dokploy/server/db/schema";
|
import { type apiCreateNetwork, network } from "@dokploy/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, inArray, isNull } from "drizzle-orm";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
import { IS_CLOUD } from "../constants";
|
import { IS_CLOUD } from "../constants";
|
||||||
|
import type { ApplicationNested } from "../utils/builders";
|
||||||
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
||||||
|
|
||||||
// Networks managed by Docker/Dokploy itself that must never be imported
|
// Networks managed by Docker/Dokploy itself that must never be imported
|
||||||
@@ -273,14 +274,39 @@ const createDockerNetworkFromRow = async (row: typeof network.$inferSelect) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Re-creates the Docker network from the stored record, for records whose
|
|
||||||
// network was removed from Docker outside of Dokploy
|
|
||||||
export const recreateNetwork = async (networkId: string) => {
|
export const recreateNetwork = async (networkId: string) => {
|
||||||
const row = await findNetworkById(networkId);
|
const row = await findNetworkById(networkId);
|
||||||
await createDockerNetworkFromRow(row);
|
await createDockerNetworkFromRow(row);
|
||||||
return row;
|
return row;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const resolveServiceNetworks = async (
|
||||||
|
application: Partial<ApplicationNested>,
|
||||||
|
) => {
|
||||||
|
if (application.networkSwarm) {
|
||||||
|
return application.networkSwarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { networkIds, detachDokployNetwork } = application;
|
||||||
|
const rows =
|
||||||
|
networkIds && networkIds.length > 0
|
||||||
|
? await db.query.network.findMany({
|
||||||
|
where: and(
|
||||||
|
inArray(network.networkId, networkIds),
|
||||||
|
eq(network.driver, "overlay"),
|
||||||
|
),
|
||||||
|
columns: { name: true },
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const networks = detachDokployNetwork ? [] : ["dokploy-network"];
|
||||||
|
for (const row of rows) {
|
||||||
|
networks.push(row.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return networks.map((name) => ({ Target: name }));
|
||||||
|
};
|
||||||
|
|
||||||
export const inspectNetwork = async (networkId: string) => {
|
export const inspectNetwork = async (networkId: string) => {
|
||||||
const row = await findNetworkById(networkId);
|
const row = await findNetworkById(networkId);
|
||||||
|
|
||||||
|
|||||||
@@ -48,19 +48,60 @@ export const createProject = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const findProjectById = async (projectId: string) => {
|
export const findProjectById = async (projectId: string) => {
|
||||||
|
const serviceColumns = {
|
||||||
|
name: true,
|
||||||
|
description: true,
|
||||||
|
appName: true,
|
||||||
|
createdAt: true,
|
||||||
|
serverId: true,
|
||||||
|
applicationStatus: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
const project = await db.query.projects.findFirst({
|
const project = await db.query.projects.findFirst({
|
||||||
where: eq(projects.projectId, projectId),
|
where: eq(projects.projectId, projectId),
|
||||||
with: {
|
with: {
|
||||||
environments: {
|
environments: {
|
||||||
with: {
|
with: {
|
||||||
applications: true,
|
applications: {
|
||||||
compose: true,
|
columns: {
|
||||||
libsql: true,
|
...serviceColumns,
|
||||||
mariadb: true,
|
applicationId: true,
|
||||||
mongo: true,
|
icon: true,
|
||||||
mysql: true,
|
},
|
||||||
postgres: true,
|
with: { server: { columns: { name: true } } },
|
||||||
redis: true,
|
},
|
||||||
|
compose: {
|
||||||
|
columns: {
|
||||||
|
...serviceColumns,
|
||||||
|
composeId: true,
|
||||||
|
composeStatus: true,
|
||||||
|
},
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
libsql: {
|
||||||
|
columns: { ...serviceColumns, libsqlId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
mariadb: {
|
||||||
|
columns: { ...serviceColumns, mariadbId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
mongo: {
|
||||||
|
columns: { ...serviceColumns, mongoId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
columns: { ...serviceColumns, mysqlId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
columns: { ...serviceColumns, postgresId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
columns: { ...serviceColumns, redisId: true },
|
||||||
|
with: { server: { columns: { name: true } } },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
projectTags: {
|
projectTags: {
|
||||||
@@ -107,27 +148,49 @@ export const updateProjectById = async (
|
|||||||
|
|
||||||
export const validUniqueServerAppName = async (appName: string) => {
|
export const validUniqueServerAppName = async (appName: string) => {
|
||||||
const query = await db.query.environments.findMany({
|
const query = await db.query.environments.findMany({
|
||||||
|
columns: { environmentId: true },
|
||||||
with: {
|
with: {
|
||||||
applications: {
|
applications: {
|
||||||
where: eq(applications.appName, appName),
|
where: eq(applications.appName, appName),
|
||||||
|
columns: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
libsql: {
|
libsql: {
|
||||||
where: eq(libsql.appName, appName),
|
where: eq(libsql.appName, appName),
|
||||||
|
columns: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mariadb: {
|
mariadb: {
|
||||||
where: eq(mariadb.appName, appName),
|
where: eq(mariadb.appName, appName),
|
||||||
|
columns: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mongo: {
|
mongo: {
|
||||||
where: eq(mongo.appName, appName),
|
where: eq(mongo.appName, appName),
|
||||||
|
columns: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mysql: {
|
mysql: {
|
||||||
where: eq(mysql.appName, appName),
|
where: eq(mysql.appName, appName),
|
||||||
|
columns: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
where: eq(postgres.appName, appName),
|
where: eq(postgres.appName, appName),
|
||||||
|
columns: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
redis: {
|
redis: {
|
||||||
where: eq(redis.appName, appName),
|
where: eq(redis.appName, appName),
|
||||||
|
columns: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { getRemoteDocker } from "../utils/servers/remote-docker";
|
|||||||
import { type Application, findApplicationById } from "./application";
|
import { type Application, findApplicationById } from "./application";
|
||||||
import { findDeploymentById } from "./deployment";
|
import { findDeploymentById } from "./deployment";
|
||||||
import type { Mount } from "./mount";
|
import type { Mount } from "./mount";
|
||||||
|
import { resolveServiceNetworks } from "./network";
|
||||||
import type { Port } from "./port";
|
import type { Port } from "./port";
|
||||||
import type { Project } from "./project";
|
import type { Project } from "./project";
|
||||||
import {
|
import {
|
||||||
@@ -257,6 +258,10 @@ const rollbackApplication = async (
|
|||||||
|
|
||||||
const volumesMount = generateVolumeMounts(mounts);
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(
|
||||||
|
fullContext as Parameters<typeof resolveServiceNetworks>[0],
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -265,7 +270,6 @@ const rollbackApplication = async (
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
Ulimits,
|
Ulimits,
|
||||||
} = generateConfigContainer(
|
} = generateConfigContainer(
|
||||||
fullContext as Parameters<typeof generateConfigContainer>[0],
|
fullContext as Parameters<typeof generateConfigContainer>[0],
|
||||||
@@ -304,7 +308,7 @@ const rollbackApplication = async (
|
|||||||
...(Ulimits && { Ulimits }),
|
...(Ulimits && { Ulimits }),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -100,15 +100,16 @@ export const deleteServer = async (serverId: string) => {
|
|||||||
export const haveActiveServices = async (serverId: string) => {
|
export const haveActiveServices = async (serverId: string) => {
|
||||||
const currentServer = await db.query.server.findFirst({
|
const currentServer = await db.query.server.findFirst({
|
||||||
where: eq(server.serverId, serverId),
|
where: eq(server.serverId, serverId),
|
||||||
|
columns: { serverId: true },
|
||||||
with: {
|
with: {
|
||||||
applications: true,
|
applications: { columns: { applicationId: true } },
|
||||||
compose: true,
|
compose: { columns: { composeId: true } },
|
||||||
libsql: true,
|
libsql: { columns: { libsqlId: true } },
|
||||||
mariadb: true,
|
mariadb: { columns: { mariadbId: true } },
|
||||||
mongo: true,
|
mongo: { columns: { mongoId: true } },
|
||||||
mysql: true,
|
mysql: { columns: { mysqlId: true } },
|
||||||
postgres: true,
|
postgres: { columns: { postgresId: true } },
|
||||||
redis: true,
|
redis: { columns: { redisId: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ export const generateRandomDomain = ({
|
|||||||
projectName,
|
projectName,
|
||||||
}: Schema): string => {
|
}: Schema): string => {
|
||||||
const hash = randomBytes(3).toString("hex");
|
const hash = randomBytes(3).toString("hex");
|
||||||
const slugIp = serverIp.replaceAll(".", "-").replaceAll(":", "-");
|
const effectiveIp = serverIp || "127.0.0.1";
|
||||||
|
const slugIp = effectiveIp.replaceAll(".", "-").replaceAll(":", "-");
|
||||||
|
|
||||||
// Domain labels have a max length of 63 characters
|
// Domain labels have a max length of 63 characters
|
||||||
// Reserve space for: hash (6) + separators (1-2) + ip section + dot + sslip.io (8)
|
// Reserve space for: hash (6) + separators (1-2) + ip section + dot + sslip.io (8)
|
||||||
@@ -46,7 +47,7 @@ export const generateRandomDomain = ({
|
|||||||
? projectName.substring(0, maxProjectNameLength)
|
? projectName.substring(0, maxProjectNameLength)
|
||||||
: projectName;
|
: projectName;
|
||||||
|
|
||||||
return `${truncatedProjectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.sslip.io`;
|
return `${truncatedProjectName}-${hash}-${slugIp}.sslip.io`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateHash = (length = 8): string => {
|
export const generateHash = (length = 8): string => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { resolveServiceNetworks } from "@dokploy/server/services/network";
|
||||||
import { findRegistryByIdWithCredentials } from "@dokploy/server/services/registry";
|
import { findRegistryByIdWithCredentials } from "@dokploy/server/services/registry";
|
||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
@@ -100,6 +101,8 @@ export const mechanizeDockerContainer = async (
|
|||||||
|
|
||||||
const volumesMount = generateVolumeMounts(mounts);
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(application);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -108,7 +111,6 @@ export const mechanizeDockerContainer = async (
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
StopGracePeriod,
|
StopGracePeriod,
|
||||||
EndpointSpec,
|
EndpointSpec,
|
||||||
Ulimits,
|
Ulimits,
|
||||||
@@ -147,7 +149,7 @@ export const mechanizeDockerContainer = async (
|
|||||||
...(Ulimits && { Ulimits }),
|
...(Ulimits && { Ulimits }),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions, PortConfig } from "dockerode";
|
import type { CreateServiceOptions, PortConfig } from "dockerode";
|
||||||
|
import { resolveServiceNetworks } from "../../services/network";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -46,6 +47,8 @@ export const buildLibsql = async (libsql: LibsqlNested) => {
|
|||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}${sqldNode === "replica" ? `\nSQLD_PRIMARY_URL="${sqldPrimaryUrl}"` : ""}`;
|
}${sqldNode === "replica" ? `\nSQLD_PRIMARY_URL="${sqldPrimaryUrl}"` : ""}`;
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(libsql);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -54,7 +57,6 @@ export const buildLibsql = async (libsql: LibsqlNested) => {
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
} = generateConfigContainer(libsql);
|
} = generateConfigContainer(libsql);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
@@ -96,7 +98,7 @@ export const buildLibsql = async (libsql: LibsqlNested) => {
|
|||||||
: {}),
|
: {}),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
|
import { resolveServiceNetworks } from "../../services/network";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -37,6 +38,8 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(mariadb);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -45,7 +48,6 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
StopGracePeriod,
|
StopGracePeriod,
|
||||||
EndpointSpec,
|
EndpointSpec,
|
||||||
Ulimits,
|
Ulimits,
|
||||||
@@ -87,7 +89,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
...(Ulimits && { Ulimits }),
|
...(Ulimits && { Ulimits }),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
|
import { resolveServiceNetworks } from "../../services/network";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -83,6 +84,8 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(mongo);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -91,7 +94,6 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
StopGracePeriod,
|
StopGracePeriod,
|
||||||
EndpointSpec,
|
EndpointSpec,
|
||||||
Ulimits,
|
Ulimits,
|
||||||
@@ -143,7 +145,7 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
...(Ulimits && { Ulimits }),
|
...(Ulimits && { Ulimits }),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
|
import { resolveServiceNetworks } from "../../services/network";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -43,6 +44,8 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(mysql);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -51,7 +54,6 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
StopGracePeriod,
|
StopGracePeriod,
|
||||||
EndpointSpec,
|
EndpointSpec,
|
||||||
Ulimits,
|
Ulimits,
|
||||||
@@ -93,7 +95,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
...(Ulimits && { Ulimits }),
|
...(Ulimits && { Ulimits }),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
|
import { resolveServiceNetworks } from "../../services/network";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -36,6 +37,8 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(postgres);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -44,7 +47,6 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
StopGracePeriod,
|
StopGracePeriod,
|
||||||
EndpointSpec,
|
EndpointSpec,
|
||||||
Ulimits,
|
Ulimits,
|
||||||
@@ -85,7 +87,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
...(Ulimits && { Ulimits }),
|
...(Ulimits && { Ulimits }),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
|
import { resolveServiceNetworks } from "../../services/network";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -34,6 +35,8 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const resolvedNetworks = await resolveServiceNetworks(redis);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
@@ -42,7 +45,6 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
Mode,
|
Mode,
|
||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
|
||||||
StopGracePeriod,
|
StopGracePeriod,
|
||||||
EndpointSpec,
|
EndpointSpec,
|
||||||
Ulimits,
|
Ulimits,
|
||||||
@@ -91,7 +93,7 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
...(Ulimits && { Ulimits }),
|
...(Ulimits && { Ulimits }),
|
||||||
Labels,
|
Labels,
|
||||||
},
|
},
|
||||||
Networks,
|
Networks: resolvedNetworks,
|
||||||
RestartPolicy,
|
RestartPolicy,
|
||||||
Placement,
|
Placement,
|
||||||
Resources: {
|
Resources: {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import fs, { existsSync, readFileSync } from "node:fs";
|
import fs, { existsSync, readFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { paths } from "@dokploy/server/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
|
import { db } from "@dokploy/server/db";
|
||||||
|
import { network } from "@dokploy/server/db/schema";
|
||||||
import type { Compose } from "@dokploy/server/services/compose";
|
import type { Compose } from "@dokploy/server/services/compose";
|
||||||
import type { Domain } from "@dokploy/server/services/domain";
|
import type { Domain } from "@dokploy/server/services/domain";
|
||||||
|
import { inArray } from "drizzle-orm";
|
||||||
import { parse, stringify } from "yaml";
|
import { parse, stringify } from "yaml";
|
||||||
import { execAsyncRemote } from "../process/execAsync";
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
import { cloneBitbucketRepository } from "../providers/bitbucket";
|
import { cloneBitbucketRepository } from "../providers/bitbucket";
|
||||||
@@ -228,14 +231,79 @@ export const addDomainToCompose = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add dokploy-network to the root of the compose file
|
const injectedNetworkNames = await applyServiceNetworks(result, compose);
|
||||||
|
|
||||||
if (!compose.isolatedDeployment) {
|
if (!compose.isolatedDeployment) {
|
||||||
result.networks = addDokployNetworkToRoot(result.networks);
|
declareUsedNetworksInRoot(result, injectedNetworkNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const applyServiceNetworks = async (
|
||||||
|
result: ComposeSpecification,
|
||||||
|
compose: Compose,
|
||||||
|
) => {
|
||||||
|
const injectedNetworkNames = new Set<string>();
|
||||||
|
const serviceNetworks = compose.serviceNetworks ?? [];
|
||||||
|
if (serviceNetworks.length === 0) return injectedNetworkNames;
|
||||||
|
|
||||||
|
const allNetworkIds = [
|
||||||
|
...new Set(serviceNetworks.flatMap((s) => s.networkIds)),
|
||||||
|
];
|
||||||
|
const networks =
|
||||||
|
allNetworkIds.length > 0
|
||||||
|
? await db.query.network.findMany({
|
||||||
|
where: inArray(network.networkId, allNetworkIds),
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
for (const config of serviceNetworks) {
|
||||||
|
const service = result.services?.[config.serviceName];
|
||||||
|
if (!service) continue;
|
||||||
|
|
||||||
|
for (const networkId of config.networkIds) {
|
||||||
|
const match = networks.find((n) => n.networkId === networkId);
|
||||||
|
if (!match) continue;
|
||||||
|
service.networks = addDokployNetworkToService(
|
||||||
|
service.networks,
|
||||||
|
match.name,
|
||||||
|
);
|
||||||
|
injectedNetworkNames.add(match.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.detachDokployNetwork) {
|
||||||
|
removeNetworkFromService(service, "dokploy-network");
|
||||||
|
removeNetworkFromService(service, "default");
|
||||||
|
removeDokployNetworkLabel(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return injectedNetworkNames;
|
||||||
|
};
|
||||||
|
|
||||||
|
const declareUsedNetworksInRoot = (
|
||||||
|
result: ComposeSpecification,
|
||||||
|
injectedNetworkNames: Set<string>,
|
||||||
|
) => {
|
||||||
|
const isUsed = (name: string) =>
|
||||||
|
Object.values(result.services ?? {}).some((service) => {
|
||||||
|
const nets = service?.networks;
|
||||||
|
if (Array.isArray(nets)) return nets.includes(name);
|
||||||
|
if (nets && typeof nets === "object") return name in nets;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isUsed("dokploy-network")) {
|
||||||
|
result.networks = addDokployNetworkToRoot(result.networks);
|
||||||
|
}
|
||||||
|
for (const name of injectedNetworkNames) {
|
||||||
|
if (isUsed(name)) {
|
||||||
|
result.networks = addDokployNetworkToRoot(result.networks, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const writeComposeFile = async (
|
export const writeComposeFile = async (
|
||||||
compose: Compose,
|
compose: Compose,
|
||||||
composeSpec: ComposeSpecification,
|
composeSpec: ComposeSpecification,
|
||||||
@@ -349,9 +417,10 @@ export const createDomainLabels = (
|
|||||||
|
|
||||||
export const addDokployNetworkToService = (
|
export const addDokployNetworkToService = (
|
||||||
networkService: DefinitionsService["networks"],
|
networkService: DefinitionsService["networks"],
|
||||||
|
networkName = "dokploy-network",
|
||||||
) => {
|
) => {
|
||||||
let networks = networkService;
|
let networks = networkService;
|
||||||
const network = "dokploy-network";
|
const network = networkName;
|
||||||
const defaultNetwork = "default";
|
const defaultNetwork = "default";
|
||||||
if (!networks) {
|
if (!networks) {
|
||||||
networks = [];
|
networks = [];
|
||||||
@@ -376,11 +445,40 @@ export const addDokployNetworkToService = (
|
|||||||
return networks;
|
return networks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeNetworkFromService = (
|
||||||
|
service: DefinitionsService,
|
||||||
|
networkName: string,
|
||||||
|
) => {
|
||||||
|
const networks = service.networks;
|
||||||
|
if (Array.isArray(networks)) {
|
||||||
|
service.networks = networks.filter((n) => n !== networkName);
|
||||||
|
} else if (networks && typeof networks === "object") {
|
||||||
|
delete networks[networkName];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeDokployNetworkLabel = (service: DefinitionsService) => {
|
||||||
|
const stripped = (labels: DefinitionsService["labels"]) => {
|
||||||
|
if (Array.isArray(labels)) {
|
||||||
|
return labels.filter(
|
||||||
|
(l) =>
|
||||||
|
l !== "traefik.docker.network=dokploy-network" &&
|
||||||
|
l !== "traefik.swarm.network=dokploy-network",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
};
|
||||||
|
if (service.labels) service.labels = stripped(service.labels);
|
||||||
|
if (service.deploy?.labels)
|
||||||
|
service.deploy.labels = stripped(service.deploy.labels);
|
||||||
|
};
|
||||||
|
|
||||||
export const addDokployNetworkToRoot = (
|
export const addDokployNetworkToRoot = (
|
||||||
networkRoot: PropertiesNetworks | undefined,
|
networkRoot: PropertiesNetworks | undefined,
|
||||||
|
networkName = "dokploy-network",
|
||||||
) => {
|
) => {
|
||||||
let networks = networkRoot;
|
let networks = networkRoot;
|
||||||
const network = "dokploy-network";
|
const network = networkName;
|
||||||
|
|
||||||
if (!networks) {
|
if (!networks) {
|
||||||
networks = {};
|
networks = {};
|
||||||
|
|||||||
@@ -548,7 +548,6 @@ export const generateConfigContainer = (
|
|||||||
labelsSwarm,
|
labelsSwarm,
|
||||||
replicas,
|
replicas,
|
||||||
mounts,
|
mounts,
|
||||||
networkSwarm,
|
|
||||||
stopGracePeriodSwarm,
|
stopGracePeriodSwarm,
|
||||||
endpointSpecSwarm,
|
endpointSpecSwarm,
|
||||||
ulimitsSwarm,
|
ulimitsSwarm,
|
||||||
@@ -611,13 +610,6 @@ export const generateConfigContainer = (
|
|||||||
stopGracePeriodSwarm !== undefined && {
|
stopGracePeriodSwarm !== undefined && {
|
||||||
StopGracePeriod: stopGracePeriodSwarm,
|
StopGracePeriod: stopGracePeriodSwarm,
|
||||||
}),
|
}),
|
||||||
...(networkSwarm
|
|
||||||
? {
|
|
||||||
Networks: networkSwarm,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
|
||||||
}),
|
|
||||||
...(endpointSpecSwarm && {
|
...(endpointSpecSwarm && {
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
...(endpointSpecSwarm.Mode && { Mode: endpointSpecSwarm.Mode }),
|
...(endpointSpecSwarm.Mode && { Mode: endpointSpecSwarm.Mode }),
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ export const initSchedules = async () => {
|
|||||||
where: eq(schedules.enabled, true),
|
where: eq(schedules.enabled, true),
|
||||||
with: {
|
with: {
|
||||||
server: true,
|
server: true,
|
||||||
application: true,
|
application: {
|
||||||
compose: true,
|
columns: { applicationId: true, appName: true, serverId: true },
|
||||||
|
},
|
||||||
|
compose: {
|
||||||
|
columns: { composeId: true, appName: true, serverId: true },
|
||||||
|
},
|
||||||
organization: true,
|
organization: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ export const initVolumeBackupsCronJobs = async () => {
|
|||||||
const volumeBackupsResult = await db.query.volumeBackups.findMany({
|
const volumeBackupsResult = await db.query.volumeBackups.findMany({
|
||||||
where: eq(volumeBackups.enabled, true),
|
where: eq(volumeBackups.enabled, true),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: { columns: { applicationId: true } },
|
||||||
compose: true,
|
compose: { columns: { composeId: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user