mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
@@ -144,38 +144,6 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
className="grid w-full gap-8 "
|
||||
>
|
||||
<div className="grid w-full md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="memoryReservation"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel>Memory Reservation</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Memory soft limit in bytes. Example: 256MB =
|
||||
268435456 bytes
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="268435456 (256MB in bytes)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="memoryLimit"
|
||||
@@ -209,6 +177,37 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="memoryReservation"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel>Memory Reservation</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Memory soft limit in bytes. Example: 256MB =
|
||||
268435456 bytes
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="268435456 (256MB in bytes)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -45,8 +45,8 @@ export const ShowVolumes = ({ id, type }: Props) => {
|
||||
<div>
|
||||
<CardTitle className="text-xl">Volumes</CardTitle>
|
||||
<CardDescription>
|
||||
If you want to persist data in this postgres database use the
|
||||
following config to setup the volumes
|
||||
If you want to persist data in this service use the following config
|
||||
to setup the volumes
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -98,8 +98,12 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
||||
applicationId={applicationId}
|
||||
domainId={item.domainId}
|
||||
>
|
||||
<Button variant="ghost">
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-blue-500/10 "
|
||||
>
|
||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||
</Button>
|
||||
</AddDomain>
|
||||
<DialogAction
|
||||
|
||||
@@ -235,7 +235,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
|
||||
<CommandGroup>
|
||||
{repositories?.map((repo) => (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
value={repo.name}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
@@ -245,7 +245,12 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{repo.name}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{repo.owner.username}
|
||||
</span>
|
||||
</span>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
|
||||
@@ -226,7 +226,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
||||
<CommandGroup>
|
||||
{repositories?.map((repo) => (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
value={repo.name}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
@@ -236,7 +236,12 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{repo.name}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{repo.owner.login}
|
||||
</span>
|
||||
</span>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
|
||||
@@ -248,7 +248,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
||||
{repositories?.map((repo) => {
|
||||
return (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
value={repo.name}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
@@ -260,7 +260,12 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{repo.name}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{repo.owner.username}
|
||||
</span>
|
||||
</span>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
|
||||
@@ -20,9 +20,10 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import type { ServiceType } from "@dokploy/server/db/schema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { Copy, Trash2 } from "lucide-react";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -39,16 +40,42 @@ const deleteComposeSchema = z.object({
|
||||
type DeleteCompose = z.infer<typeof deleteComposeSchema>;
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
id: string;
|
||||
type: ServiceType | "application";
|
||||
}
|
||||
|
||||
export const DeleteCompose = ({ composeId }: Props) => {
|
||||
export const DeleteService = ({ id, type }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { mutateAsync, isLoading } = api.compose.delete.useMutation();
|
||||
const { data } = api.compose.one.useQuery(
|
||||
{ composeId },
|
||||
{ enabled: !!composeId },
|
||||
);
|
||||
|
||||
const queryMap = {
|
||||
postgres: () =>
|
||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||
mariadb: () =>
|
||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||
application: () =>
|
||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||
compose: () =>
|
||||
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
|
||||
};
|
||||
const { data, refetch } = queryMap[type]
|
||||
? queryMap[type]()
|
||||
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||
|
||||
const mutationMap = {
|
||||
postgres: () => api.postgres.remove.useMutation(),
|
||||
redis: () => api.redis.remove.useMutation(),
|
||||
mysql: () => api.mysql.remove.useMutation(),
|
||||
mariadb: () => api.mariadb.remove.useMutation(),
|
||||
application: () => api.application.delete.useMutation(),
|
||||
mongo: () => api.mongo.remove.useMutation(),
|
||||
compose: () => api.compose.delete.useMutation(),
|
||||
};
|
||||
const { mutateAsync, isLoading } = mutationMap[type]
|
||||
? mutationMap[type]()
|
||||
: api.mongo.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
const form = useForm<DeleteCompose>({
|
||||
defaultValues: {
|
||||
@@ -62,14 +89,23 @@ export const DeleteCompose = ({ composeId }: Props) => {
|
||||
const expectedName = `${data?.name}/${data?.appName}`;
|
||||
if (formData.projectName === expectedName) {
|
||||
const { deleteVolumes } = formData;
|
||||
await mutateAsync({ composeId, deleteVolumes })
|
||||
await mutateAsync({
|
||||
mongoId: id || "",
|
||||
postgresId: id || "",
|
||||
redisId: id || "",
|
||||
mysqlId: id || "",
|
||||
mariadbId: id || "",
|
||||
applicationId: id || "",
|
||||
composeId: id || "",
|
||||
deleteVolumes,
|
||||
})
|
||||
.then((result) => {
|
||||
push(`/dashboard/project/${result?.projectId}`);
|
||||
toast.success("Compose deleted successfully");
|
||||
toast.success("deleted successfully");
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the compose");
|
||||
toast.error("Error deleting the service");
|
||||
});
|
||||
} else {
|
||||
form.setError("projectName", {
|
||||
@@ -95,8 +131,8 @@ export const DeleteCompose = ({ composeId }: Props) => {
|
||||
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
compose. If you are sure please enter the compose name to delete
|
||||
this compose.
|
||||
service. If you are sure please enter the service name to delete
|
||||
this service.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4">
|
||||
@@ -119,9 +155,7 @@ export const DeleteCompose = ({ composeId }: Props) => {
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (data?.name && data?.appName) {
|
||||
navigator.clipboard.writeText(
|
||||
`${data.name}/${data.appName}`,
|
||||
);
|
||||
copy(`${data.name}/${data.appName}`);
|
||||
toast.success("Copied to clipboard. Be careful!");
|
||||
}
|
||||
}}
|
||||
@@ -142,27 +176,29 @@ export const DeleteCompose = ({ composeId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="deleteVolumes"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
{type === "compose" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="deleteVolumes"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormLabel className="ml-2">
|
||||
Delete volumes associated with this compose
|
||||
</FormLabel>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormLabel className="ml-2">
|
||||
Delete volumes associated with this compose
|
||||
</FormLabel>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
@@ -97,8 +97,12 @@ export const ShowDomainsCompose = ({ composeId }: Props) => {
|
||||
composeId={composeId}
|
||||
domainId={item.domainId}
|
||||
>
|
||||
<Button variant="ghost">
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-blue-500/10 "
|
||||
>
|
||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||
</Button>
|
||||
</AddDomainCompose>
|
||||
<DialogAction
|
||||
|
||||
@@ -237,7 +237,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
||||
<CommandGroup>
|
||||
{repositories?.map((repo) => (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
value={repo.name}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
@@ -247,7 +247,12 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{repo.name}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{repo.owner.username}
|
||||
</span>
|
||||
</span>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
|
||||
@@ -228,7 +228,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
<CommandGroup>
|
||||
{repositories?.map((repo) => (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
value={repo.name}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
@@ -238,7 +238,12 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{repo.name}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{repo.owner.login}
|
||||
</span>
|
||||
</span>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
|
||||
@@ -250,7 +250,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
||||
{repositories?.map((repo) => {
|
||||
return (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
value={repo.name}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
@@ -262,7 +262,12 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{repo.name}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{repo.owner.username}
|
||||
</span>
|
||||
</span>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
|
||||
@@ -70,7 +70,7 @@ interface Props {
|
||||
|
||||
export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
@@ -166,7 +166,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
Select a Server (Optional)
|
||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
</TooltipTrigger>
|
||||
@@ -197,7 +197,12 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||
key={server.serverId}
|
||||
value={server.serverId}
|
||||
>
|
||||
{server.name}
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>{server.name}</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
{server.ipAddress}
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||
|
||||
@@ -73,6 +73,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.compose.create.useMutation();
|
||||
@@ -173,7 +174,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
Select a Server (Optional)
|
||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
</TooltipTrigger>
|
||||
@@ -204,7 +205,12 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
key={server.serverId}
|
||||
value={server.serverId}
|
||||
>
|
||||
{server.name}
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>{server.name}</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
{server.ipAddress}
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||
|
||||
@@ -89,7 +89,7 @@ const mySchema = z.discriminatedUnion("type", [
|
||||
z
|
||||
.object({
|
||||
type: z.literal("postgres"),
|
||||
databaseName: z.string().min(1, "Database name required"),
|
||||
databaseName: z.string().default("postgres"),
|
||||
databaseUser: z.string().default("postgres"),
|
||||
})
|
||||
.merge(baseDatabaseSchema),
|
||||
@@ -110,7 +110,7 @@ const mySchema = z.discriminatedUnion("type", [
|
||||
type: z.literal("mysql"),
|
||||
databaseRootPassword: z.string().default(""),
|
||||
databaseUser: z.string().default("mysql"),
|
||||
databaseName: z.string().min(1, "Database name required"),
|
||||
databaseName: z.string().default("mysql"),
|
||||
})
|
||||
.merge(baseDatabaseSchema),
|
||||
z
|
||||
@@ -119,7 +119,7 @@ const mySchema = z.discriminatedUnion("type", [
|
||||
dockerImage: z.string().default("mariadb:4"),
|
||||
databaseRootPassword: z.string().default(""),
|
||||
databaseUser: z.string().default("mariadb"),
|
||||
databaseName: z.string().min(1, "Database name required"),
|
||||
databaseName: z.string().default("mariadb"),
|
||||
})
|
||||
.merge(baseDatabaseSchema),
|
||||
]);
|
||||
@@ -206,7 +206,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
promise = postgresMutation.mutateAsync({
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
databaseName: data.databaseName,
|
||||
databaseName: data.databaseName || "postgres",
|
||||
|
||||
databaseUser:
|
||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||
@@ -233,7 +233,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
databaseRootPassword: data.databaseRootPassword,
|
||||
databaseName: data.databaseName,
|
||||
databaseName: data.databaseName || "mariadb",
|
||||
databaseUser:
|
||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||
serverId: data.serverId,
|
||||
@@ -242,7 +242,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
promise = mysqlMutation.mutateAsync({
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
databaseName: data.databaseName,
|
||||
databaseName: data.databaseName || "mysql",
|
||||
databaseUser:
|
||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||
databaseRootPassword: data.databaseRootPassword,
|
||||
|
||||
@@ -80,6 +80,7 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
const [viewMode, setViewMode] = useState<"detailed" | "icon">("detailed");
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const { data } = api.compose.templates.useQuery();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
const { data: tags, isLoading: isLoadingTags } =
|
||||
api.compose.getTags.useQuery();
|
||||
@@ -308,7 +309,7 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
{/* Create Button */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex-none px-6 pb-6 pt-3 mt-auto",
|
||||
"flex-none px-6 py-3 mt-auto",
|
||||
viewMode === "detailed"
|
||||
? "flex items-center justify-between bg-muted/30 border-t"
|
||||
: "flex justify-center",
|
||||
@@ -372,7 +373,8 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Label className="break-all w-fit flex flex-row gap-1 items-center pb-2 pt-3.5">
|
||||
Select a Server (Optional)
|
||||
Select a Server{" "}
|
||||
{!isCloud ? "(Optional)" : ""}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</Label>
|
||||
</TooltipTrigger>
|
||||
@@ -405,7 +407,12 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
key={server.serverId}
|
||||
value={server.serverId}
|
||||
>
|
||||
{server.name}
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>{server.name}</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
{server.ipAddress}
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>
|
||||
|
||||
@@ -23,8 +23,10 @@ import {
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -149,14 +151,91 @@ export const ShowProjects = () => {
|
||||
href={`/dashboard/project/${project.projectId}`}
|
||||
>
|
||||
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
|
||||
<Button
|
||||
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
||||
size="sm"
|
||||
variant="default"
|
||||
>
|
||||
<ExternalLinkIcon className="size-3.5" />
|
||||
</Button>
|
||||
|
||||
{project.applications.length > 0 ||
|
||||
project.compose.length > 0 ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
||||
size="sm"
|
||||
variant="default"
|
||||
>
|
||||
<ExternalLinkIcon className="size-3.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-[200px] space-y-2 overflow-y-auto max-h-[400px]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{project.applications.length > 0 && (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>
|
||||
Applications
|
||||
</DropdownMenuLabel>
|
||||
{project.applications.map((app) => (
|
||||
<div key={app.applicationId}>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs">
|
||||
{app.name}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{app.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
>
|
||||
<span>{domain.host}</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
{project.compose.length > 0 && (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>
|
||||
Compose
|
||||
</DropdownMenuLabel>
|
||||
{project.compose.map((comp) => (
|
||||
<div key={comp.composeId}>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs">
|
||||
{comp.name}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{comp.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
>
|
||||
<span>{domain.host}</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between gap-2">
|
||||
<span className="flex flex-col gap-1.5">
|
||||
@@ -182,7 +261,10 @@ export const ShowProjects = () => {
|
||||
<MoreHorizontalIcon className="size-5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-[200px] space-y-2">
|
||||
<DropdownMenuContent
|
||||
className="w-[200px] space-y-2 overflow-y-auto max-h-[280px]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
Actions
|
||||
</DropdownMenuLabel>
|
||||
|
||||
@@ -61,6 +61,7 @@ export const AddCertificate = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { mutateAsync, isError, error, isLoading } =
|
||||
api.certificates.create.useMutation();
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
@@ -181,7 +182,7 @@ export const AddCertificate = () => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
Select a Server (Optional)
|
||||
Select a Server {!isCloud && "(Optional)"}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
</TooltipTrigger>
|
||||
@@ -202,7 +203,12 @@ export const AddCertificate = () => {
|
||||
key={server.serverId}
|
||||
value={server.serverId}
|
||||
>
|
||||
{server.name}
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>{server.name}</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
{server.ipAddress}
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { sshKeyCreate, type sshKeyType } from "@/server/db/validations";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
||||
import { DownloadIcon, PenBoxIcon, PlusIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -111,6 +111,26 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
|
||||
toast.error("Error generating the SSH Key");
|
||||
});
|
||||
|
||||
const downloadKey = (
|
||||
content: string,
|
||||
defaultFilename: string,
|
||||
keyType: "private" | "public",
|
||||
) => {
|
||||
const keyName = form.watch("name");
|
||||
const filename = keyName
|
||||
? `${keyName}${sshKeyId ? `_${sshKeyId}` : ""}_${keyType}_${defaultFilename}`
|
||||
: `${keyType}_${defaultFilename}`;
|
||||
const blob = new Blob([content], { type: "text/plain" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger className="" asChild>
|
||||
@@ -245,7 +265,41 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<DialogFooter className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
{form.watch("privateKey") && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="default"
|
||||
onClick={() =>
|
||||
downloadKey(form.watch("privateKey"), "id_rsa", "private")
|
||||
}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
Private Key
|
||||
</Button>
|
||||
)}
|
||||
{form.watch("publicKey") && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="default"
|
||||
onClick={() =>
|
||||
downloadKey(
|
||||
form.watch("publicKey"),
|
||||
"id_rsa.pub",
|
||||
"public",
|
||||
)
|
||||
}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
Public Key
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Button isLoading={isLoading} type="submit">
|
||||
{sshKeyId ? "Update" : "Create"}
|
||||
</Button>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
import { AttachAddon } from "@xterm/addon-attach";
|
||||
import { ClipboardAddon } from "@xterm/addon-clipboard";
|
||||
import { useTheme } from "next-themes";
|
||||
import { getLocalServerData } from "./local-server-config";
|
||||
|
||||
@@ -37,6 +38,7 @@ export const Terminal: React.FC<Props> = ({ id, serverId }) => {
|
||||
foreground: "currentColor",
|
||||
},
|
||||
});
|
||||
|
||||
const addonFit = new FitAddon();
|
||||
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
@@ -54,6 +56,8 @@ export const Terminal: React.FC<Props> = ({ id, serverId }) => {
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
const addonAttach = new AttachAddon(ws);
|
||||
const clipboardAddon = new ClipboardAddon();
|
||||
term.loadAddon(clipboardAddon);
|
||||
|
||||
// @ts-ignore
|
||||
term.open(termRef.current);
|
||||
@@ -68,7 +72,7 @@ export const Terminal: React.FC<Props> = ({ id, serverId }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="w-full h-full bg-transparent border rounded-lg p-2 ">
|
||||
<div className="w-full h-full bg-transparent border rounded-lg p-2">
|
||||
<div id={id} ref={termRef} className="rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -247,7 +247,7 @@ const MENU: Menu = {
|
||||
settings: [
|
||||
{
|
||||
isSingle: true,
|
||||
title: "Server",
|
||||
title: "Web Server",
|
||||
url: "/dashboard/settings/server",
|
||||
icon: Activity,
|
||||
// Only enabled for admins in non-cloud environments
|
||||
@@ -262,7 +262,7 @@ const MENU: Menu = {
|
||||
},
|
||||
{
|
||||
isSingle: true,
|
||||
title: "Servers",
|
||||
title: "Remote Servers",
|
||||
url: "/dashboard/settings/servers",
|
||||
icon: Server,
|
||||
// Only enabled for admins
|
||||
@@ -783,7 +783,7 @@ export default function Page({ children }: Props) {
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
{!isCloud && (
|
||||
{!isCloud && auth?.rol === "admin" && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<UpdateServerButton />
|
||||
|
||||
3
apps/dokploy/drizzle/0060_disable-aggressive-cache.sql
Normal file
3
apps/dokploy/drizzle/0060_disable-aggressive-cache.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- Custom SQL migration file, put you code below!
|
||||
|
||||
UPDATE "admin" SET "cleanupCacheApplications" = false;
|
||||
4341
apps/dokploy/drizzle/meta/0060_snapshot.json
Normal file
4341
apps/dokploy/drizzle/meta/0060_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -421,6 +421,13 @@
|
||||
"when": 1737615160768,
|
||||
"tag": "0059_striped_bill_hollister",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 60,
|
||||
"version": "6",
|
||||
"when": 1737929896838,
|
||||
"tag": "0060_disable-aggressive-cache",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.17.8",
|
||||
"version": "v0.17.9",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -35,6 +35,18 @@
|
||||
"test": "vitest --config __test__/vitest.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"bl": "6.0.11",
|
||||
"rotating-file-stream": "3.2.3",
|
||||
"qrcode": "^1.5.3",
|
||||
"otpauth": "^9.2.3",
|
||||
"hi-base32": "^0.5.1",
|
||||
"boxen": "^7.1.1",
|
||||
"@octokit/auth-app": "^6.0.4",
|
||||
"nodemailer": "6.9.14",
|
||||
"@react-email/components": "^0.0.21",
|
||||
"node-os-utils": "1.3.7",
|
||||
"@lucia-auth/adapter-drizzle": "1.0.7",
|
||||
"dockerode": "4.0.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-yaml": "^6.1.1",
|
||||
"@codemirror/language": "^6.10.1",
|
||||
@@ -75,6 +87,7 @@
|
||||
"@uiw/react-codemirror": "^4.22.1",
|
||||
"@xterm/addon-attach": "0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"@xterm/addon-clipboard": "0.1.0",
|
||||
"adm-zip": "^0.5.14",
|
||||
"bcrypt": "5.1.1",
|
||||
"bullmq": "5.4.2",
|
||||
@@ -127,6 +140,9 @@
|
||||
"@faker-js/faker": "^8.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/nodemailer": "^6.4.15",
|
||||
"@types/node-os-utils": "1.3.4",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
|
||||
@@ -182,8 +182,9 @@ export default async function handler(
|
||||
}
|
||||
} else if (req.headers["x-github-event"] === "pull_request") {
|
||||
const prId = githubBody?.pull_request?.id;
|
||||
const action = githubBody?.action;
|
||||
|
||||
if (githubBody?.action === "closed") {
|
||||
if (action === "closed") {
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentsByPullRequestId(prId);
|
||||
|
||||
@@ -201,79 +202,86 @@ export default async function handler(
|
||||
res.status(200).json({ message: "Preview Deployment Closed" });
|
||||
return;
|
||||
}
|
||||
|
||||
// opened or synchronize or reopened
|
||||
const repository = githubBody?.repository?.name;
|
||||
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
||||
const branch = githubBody?.pull_request?.base?.ref;
|
||||
const owner = githubBody?.repository?.owner?.login;
|
||||
if (
|
||||
action === "opened" ||
|
||||
action === "synchronize" ||
|
||||
action === "reopened"
|
||||
) {
|
||||
const repository = githubBody?.repository?.name;
|
||||
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
||||
const branch = githubBody?.pull_request?.base?.ref;
|
||||
const owner = githubBody?.repository?.owner?.login;
|
||||
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
eq(applications.sourceType, "github"),
|
||||
eq(applications.repository, repository),
|
||||
eq(applications.branch, branch),
|
||||
eq(applications.isPreviewDeploymentsActive, true),
|
||||
eq(applications.owner, owner),
|
||||
),
|
||||
with: {
|
||||
previewDeployments: true,
|
||||
},
|
||||
});
|
||||
|
||||
const prBranch = githubBody?.pull_request?.head?.ref;
|
||||
|
||||
const prNumber = githubBody?.pull_request?.number;
|
||||
const prTitle = githubBody?.pull_request?.title;
|
||||
const prURL = githubBody?.pull_request?.html_url;
|
||||
|
||||
for (const app of apps) {
|
||||
const previewLimit = app?.previewLimit || 0;
|
||||
if (app?.previewDeployments?.length > previewLimit) {
|
||||
continue;
|
||||
}
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentByApplicationId(app.applicationId, prId);
|
||||
|
||||
let previewDeploymentId =
|
||||
previewDeploymentResult?.previewDeploymentId || "";
|
||||
|
||||
if (!previewDeploymentResult) {
|
||||
const previewDeployment = await createPreviewDeployment({
|
||||
applicationId: app.applicationId as string,
|
||||
branch: prBranch,
|
||||
pullRequestId: prId,
|
||||
pullRequestNumber: prNumber,
|
||||
pullRequestTitle: prTitle,
|
||||
pullRequestURL: prURL,
|
||||
});
|
||||
previewDeploymentId = previewDeployment.previewDeploymentId;
|
||||
}
|
||||
|
||||
const jobData: DeploymentJob = {
|
||||
applicationId: app.applicationId as string,
|
||||
titleLog: "Preview Deployment",
|
||||
descriptionLog: `Hash: ${deploymentHash}`,
|
||||
type: "deploy",
|
||||
applicationType: "application-preview",
|
||||
server: !!app.serverId,
|
||||
previewDeploymentId,
|
||||
};
|
||||
|
||||
if (IS_CLOUD && app.serverId) {
|
||||
jobData.serverId = app.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
eq(applications.sourceType, "github"),
|
||||
eq(applications.repository, repository),
|
||||
eq(applications.branch, branch),
|
||||
eq(applications.isPreviewDeploymentsActive, true),
|
||||
eq(applications.owner, owner),
|
||||
),
|
||||
with: {
|
||||
previewDeployments: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const prBranch = githubBody?.pull_request?.head?.ref;
|
||||
|
||||
const prNumber = githubBody?.pull_request?.number;
|
||||
const prTitle = githubBody?.pull_request?.title;
|
||||
const prURL = githubBody?.pull_request?.html_url;
|
||||
|
||||
for (const app of apps) {
|
||||
const previewLimit = app?.previewLimit || 0;
|
||||
if (app?.previewDeployments?.length > previewLimit) {
|
||||
continue;
|
||||
}
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentByApplicationId(app.applicationId, prId);
|
||||
|
||||
let previewDeploymentId =
|
||||
previewDeploymentResult?.previewDeploymentId || "";
|
||||
|
||||
if (!previewDeploymentResult) {
|
||||
const previewDeployment = await createPreviewDeployment({
|
||||
applicationId: app.applicationId as string,
|
||||
branch: prBranch,
|
||||
pullRequestId: prId,
|
||||
pullRequestNumber: prNumber,
|
||||
pullRequestTitle: prTitle,
|
||||
pullRequestURL: prURL,
|
||||
});
|
||||
previewDeploymentId = previewDeployment.previewDeploymentId;
|
||||
}
|
||||
|
||||
const jobData: DeploymentJob = {
|
||||
applicationId: app.applicationId as string,
|
||||
titleLog: "Preview Deployment",
|
||||
descriptionLog: `Hash: ${deploymentHash}`,
|
||||
type: "deploy",
|
||||
applicationType: "application-preview",
|
||||
server: !!app.serverId,
|
||||
previewDeploymentId,
|
||||
};
|
||||
|
||||
if (IS_CLOUD && app.serverId) {
|
||||
jobData.serverId = app.serverId;
|
||||
await deploy(jobData);
|
||||
continue;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
return res.status(200).json({ message: "Apps Deployed" });
|
||||
}
|
||||
return res.status(200).json({ message: "Apps Deployed" });
|
||||
}
|
||||
|
||||
return res.status(400).json({ message: "No Actions matched" });
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ShowGeneralApplication } from "@/components/dashboard/application/gener
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { ShowPreviewDeployments } from "@/components/dashboard/application/preview-deployments/show-preview-deployments";
|
||||
import { UpdateApplication } from "@/components/dashboard/application/update-application";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
@@ -82,8 +83,6 @@ const Service = (
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync, isLoading: isRemoving } =
|
||||
api.application.delete.useMutation();
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
@@ -177,34 +176,7 @@ const Service = (
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateApplication applicationId={applicationId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Delete Application"
|
||||
description="Are you sure you want to delete this application?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
applicationId: applicationId,
|
||||
})
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Application deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting application");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DeleteService id={applicationId} type="application" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command";
|
||||
import { DeleteCompose } from "@/components/dashboard/compose/delete-compose";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowDeploymentsCompose } from "@/components/dashboard/compose/deployments/show-deployments-compose";
|
||||
import { ShowDomainsCompose } from "@/components/dashboard/compose/domains/show-domains";
|
||||
import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show";
|
||||
@@ -168,7 +168,7 @@ const Service = (
|
||||
<UpdateCompose composeId={composeId} />
|
||||
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeleteCompose composeId={composeId} />
|
||||
<DeleteService id={composeId} type="compose" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { ShowExternalMariadbCredentials } from "@/components/dashboard/mariadb/general/show-external-mariadb-credentials";
|
||||
import { ShowGeneralMariadb } from "@/components/dashboard/mariadb/general/show-general-mariadb";
|
||||
@@ -67,8 +68,7 @@ const Mariadb = (
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.mariadb.remove.useMutation();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<BreadcrumbSidebar
|
||||
@@ -148,35 +148,10 @@ const Mariadb = (
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateMariadb mariadbId={mariadbId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Mariadb"
|
||||
description="Are you sure you want to delete this mariadb?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ mariadbId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Mariadb deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the mariadb");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DeleteService id={mariadbId} type="mariadb" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { ShowExternalMongoCredentials } from "@/components/dashboard/mongo/general/show-external-mongo-credentials";
|
||||
import { ShowGeneralMongo } from "@/components/dashboard/mongo/general/show-general-mongo";
|
||||
@@ -69,8 +70,6 @@ const Mongo = (
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.mongo.remove.useMutation();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
@@ -155,32 +154,7 @@ const Mongo = (
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateMongo mongoId={mongoId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove mongo"
|
||||
description="Are you sure you want to delete this mongo?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ mongoId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Mongo deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the mongo");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DeleteService id={mongoId} type="mongo" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ShowExternalMysqlCredentials } from "@/components/dashboard/mysql/general/show-external-mysql-credentials";
|
||||
@@ -68,8 +69,6 @@ const MySql = (
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.mysql.remove.useMutation();
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<BreadcrumbSidebar
|
||||
@@ -154,32 +153,7 @@ const MySql = (
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateMysql mysqlId={mysqlId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Mysql"
|
||||
description="Are you sure you want to delete this mysql?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ mysqlId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Mysql deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the mysql");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DeleteService id={mysqlId} type="mysql" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||
@@ -70,9 +71,6 @@ const Postgresql = (
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.postgres.remove.useMutation();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<BreadcrumbSidebar
|
||||
@@ -156,32 +154,7 @@ const Postgresql = (
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdatePostgres postgresId={postgresId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Postgres"
|
||||
description="Are you sure you want to delete this postgres?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ postgresId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Postgres deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the postgres");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DeleteService id={postgresId} type="postgres" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||
import { ShowExternalRedisCredentials } from "@/components/dashboard/redis/general/show-external-redis-credentials";
|
||||
@@ -68,8 +69,6 @@ const Redis = (
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.redis.remove.useMutation();
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<BreadcrumbSidebar
|
||||
@@ -153,32 +152,7 @@ const Redis = (
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateRedis redisId={redisId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Redis"
|
||||
description="Are you sure you want to delete this redis?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ redisId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Redis deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the redis");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DeleteService id={redisId} type="redis" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"settings.common.save": "Сохранить",
|
||||
"settings.common.enterTerminal": "Открыть терминал",
|
||||
"settings.server.domain.title": "Домен сервера",
|
||||
"settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.",
|
||||
"settings.server.domain.form.domain": "Домен",
|
||||
@@ -7,18 +8,26 @@
|
||||
"settings.server.domain.form.certificate.label": "Сертификат",
|
||||
"settings.server.domain.form.certificate.placeholder": "Выберите сертификат",
|
||||
"settings.server.domain.form.certificateOptions.none": "Нет",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (По умолчанию)",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
|
||||
|
||||
"settings.server.webServer.title": "Веб-сервер",
|
||||
"settings.server.webServer.description": "Перезагрузка или очистка веб-сервера.",
|
||||
"settings.server.webServer.server.label": "Сервер",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.storage.label": "Дисковое пространство",
|
||||
"settings.server.webServer.actions": "Действия",
|
||||
"settings.server.webServer.reload": "Перезагрузить",
|
||||
"settings.server.webServer.watchLogs": "Просмотр логов",
|
||||
"settings.server.webServer.updateServerIp": "Изменить IP адрес",
|
||||
"settings.server.webServer.server.label": "Сервер",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.traefik.modifyEnv": "Изменить переменные окружения",
|
||||
"settings.server.webServer.traefik.managePorts": "Назначение портов",
|
||||
"settings.server.webServer.traefik.managePortsDescription": "Добавить или удалить дополнительные порты для Traefik",
|
||||
"settings.server.webServer.traefik.targetPort": "Внутренний порт",
|
||||
"settings.server.webServer.traefik.publishedPort": "Внешний порт",
|
||||
"settings.server.webServer.traefik.addPort": "Добавить порт",
|
||||
"settings.server.webServer.traefik.portsUpdated": "Порты успешно обновлены",
|
||||
"settings.server.webServer.traefik.portsUpdateError": "Не удалось обновить порты",
|
||||
"settings.server.webServer.traefik.publishMode": "Режим сопоставления",
|
||||
"settings.server.webServer.storage.label": "Дисковое пространство",
|
||||
"settings.server.webServer.storage.cleanUnusedImages": "Очистить неиспользуемые образы",
|
||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Очистить неиспользуемые тома",
|
||||
"settings.server.webServer.storage.cleanStoppedContainers": "Очистить остановленные контейнеры",
|
||||
@@ -40,5 +49,10 @@
|
||||
"settings.appearance.themes.dark": "Темная",
|
||||
"settings.appearance.themes.system": "Системная",
|
||||
"settings.appearance.language": "Язык",
|
||||
"settings.appearance.languageDescription": "Select a language for your dashboard"
|
||||
"settings.appearance.languageDescription": "Выберите язык для панели управления",
|
||||
|
||||
"settings.terminal.connectionSettings": "Настройки подключения",
|
||||
"settings.terminal.ipAddress": "IP адрес",
|
||||
"settings.terminal.port": "Порт",
|
||||
"settings.terminal.username": "Имя пользователя"
|
||||
}
|
||||
|
||||
10
apps/dokploy/public/templates/alist.svg
Normal file
10
apps/dokploy/public/templates/alist.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="1252" height="1252" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<g>
|
||||
<g id="#70c6beff">
|
||||
<path id="svg_2" d="m634.37,138.38c11.88,-1.36 24.25,1.3 34.18,8.09c14.96,9.66 25.55,24.41 34.49,39.51c40.59,68.03 81.45,135.91 122.02,203.96c54.02,90.99 108.06,181.97 161.94,273.06c37.28,63 74.65,125.96 112.18,188.82c24.72,41.99 50.21,83.54 73.84,126.16c10.18,17.84 15.77,38.44 14.93,59.03c-0.59,15.92 -3.48,32.28 -11.84,46.08c-11.73,19.46 -31.39,33.2 -52.71,40.36c-11.37,4.09 -23.3,6.87 -35.43,6.89c-132.32,-0.05 -264.64,0.04 -396.95,0.03c-11.38,-0.29 -22.95,-1.6 -33.63,-5.72c-7.81,-3.33 -15.5,-7.43 -21.61,-13.42c-10.43,-10.32 -17.19,-24.96 -15.38,-39.83c0.94,-10.39 3.48,-20.64 7.76,-30.16c4.15,-9.77 9.99,-18.67 15.06,-27.97c22.13,-39.47 45.31,-78.35 69.42,-116.65c7.72,-12.05 14.44,-25.07 25.12,-34.87c11.35,-10.39 25.6,-18.54 41.21,-19.6c12.55,-0.52 24.89,3.82 35.35,10.55c11.8,6.92 21.09,18.44 24.2,31.88c4.49,17.01 -0.34,34.88 -7.55,50.42c-8.09,17.65 -19.62,33.67 -25.81,52.18c-1.13,4.21 -2.66,9.52 0.48,13.23c3.19,3 7.62,4.18 11.77,5.22c12,2.67 24.38,1.98 36.59,2.06c45,-0.01 90,0 135,0c8.91,-0.15 17.83,0.3 26.74,-0.22c6.43,-0.74 13.44,-1.79 18.44,-6.28c3.3,-2.92 3.71,-7.85 2.46,-11.85c-2.74,-8.86 -7.46,-16.93 -12.12,-24.89c-119.99,-204.91 -239.31,-410.22 -360.56,-614.4c-3.96,-6.56 -7.36,-13.68 -13.03,-18.98c-2.8,-2.69 -6.95,-4.22 -10.77,-3.11c-3.25,1.17 -5.45,4.03 -7.61,6.57c-5.34,6.81 -10.12,14.06 -14.51,21.52c-20.89,33.95 -40.88,68.44 -61.35,102.64c-117.9,198.43 -235.82,396.85 -353.71,595.29c-7.31,13.46 -15.09,26.67 -23.57,39.43c-7.45,10.96 -16.49,21.23 -28.14,27.83c-13.73,7.94 -30.69,11.09 -46.08,6.54c-11.23,-3.47 -22.09,-9.12 -30.13,-17.84c-10.18,-10.08 -14.69,-24.83 -14.17,-38.94c0.52,-14.86 5.49,-29.34 12.98,-42.1c71.58,-121.59 143.62,-242.92 215.93,-364.09c37.2,-62.8 74.23,-125.69 111.64,-188.36c37.84,-63.5 75.77,-126.94 113.44,-190.54c21.02,-35.82 42.19,-71.56 64.28,-106.74c6.79,-11.15 15.58,-21.15 26.16,-28.85c8.68,-5.92 18.42,-11 29.05,-11.94z" fill="#70c6be"/>
|
||||
</g>
|
||||
<g id="#1ba0d8ff">
|
||||
<path id="svg_3" d="m628.35,608.38c17.83,-2.87 36.72,1.39 51.5,11.78c11.22,8.66 19.01,21.64 21.26,35.65c1.53,10.68 0.49,21.75 -3.44,31.84c-3.02,8.73 -7.35,16.94 -12.17,24.81c-68.76,115.58 -137.5,231.17 -206.27,346.75c-8.8,14.47 -16.82,29.47 -26.96,43.07c-7.37,9.11 -16.58,16.85 -27.21,21.89c-22.47,11.97 -51.79,4.67 -68.88,-13.33c-8.66,-8.69 -13.74,-20.63 -14.4,-32.84c-0.98,-12.64 1.81,-25.42 7.53,-36.69c5.03,-10.96 10.98,-21.45 17.19,-31.77c30.22,-50.84 60.17,-101.84 90.3,-152.73c41.24,-69.98 83.16,-139.55 124.66,-209.37c4.41,-7.94 9.91,-15.26 16.09,-21.9c8.33,-8.46 18.9,-15.3 30.8,-17.16z" fill="#1ba0d8"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
BIN
apps/dokploy/public/templates/answer.png
Normal file
BIN
apps/dokploy/public/templates/answer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
5
apps/dokploy/public/templates/erpnext.svg
Normal file
5
apps/dokploy/public/templates/erpnext.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="101" height="100" viewBox="0 0 101 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M69.3721 38.0715H61.6579V30.3573C61.6579 25.4287 57.6579 21.4287 52.7293 21.4287H46.7649C41.8363 21.4287 37.8363 25.4287 37.8363 30.3573V38.0715H22.9792V45.2143H44.9792V28.5716H54.515V45.2143H71.1578V54.75H61.6221H54.515V71.393H44.9792V54.75H22.9792V61.8928H37.8363V69.6072C37.8363 74.5358 41.8363 78.5358 46.7649 78.5358H52.7293C57.6579 78.5358 61.6579 74.5358 61.6579 69.6072V61.8928H69.3721C74.3006 61.8928 78.3006 57.8928 78.3006 52.9643V47C78.3006 42.0715 74.3006 38.0715 69.3721 38.0715Z" fill="white"/>
|
||||
<path d="M72.0506 0H29.1935C13.4139 0 0.62207 12.7919 0.62207 28.5714V71.4286C0.62207 87.2081 13.4139 100 29.1935 100H72.0506C87.8302 100 100.622 87.2081 100.622 71.4286V28.5714C100.622 12.7919 87.8302 0 72.0506 0Z" fill="#0089FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M68.0067 32.945H66.3293H44.5235H33.6205V23.999H68.0067V32.945ZM44.5233 53.9122V66.2129H68.2861V75.1589H33.6204V44.9662H44.5233H66.3291V53.9122H44.5233Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
17
apps/dokploy/public/templates/maybe.svg
Normal file
17
apps/dokploy/public/templates/maybe.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="147" height="24" fill="none">
|
||||
<path
|
||||
d="M30.827 24h5.224a2.24 2.24 0 0 0 2.241-2.238 2.24 2.24 0 0 0-2.24-2.239h-5.225a2.24 2.24 0 0 0-2.241 2.239A2.24 2.24 0 0 0 30.826 24zM7.465 24H2.241A2.24 2.24 0 0 1 0 21.762a2.24 2.24 0 0 1 2.24-2.239h5.225a2.24 2.24 0 1 1 0 4.477zm13.792-.014h-4.253a2.24 2.24 0 1 1 0-4.477h4.253a2.24 2.24 0 1 1 0 4.477z"
|
||||
fill="#F23E94" />
|
||||
<path
|
||||
d="M28.796 17.558h5.03a2.24 2.24 0 1 0 0-4.477h-5.03a2.24 2.24 0 1 0 0 4.477zm-19.387 0H4.38a2.24 2.24 0 1 1 0-4.477h5.03a2.24 2.24 0 1 1 0 4.477zm13.466-.014h-7.486a2.24 2.24 0 0 1-2.24-2.239 2.24 2.24 0 0 1 2.24-2.239h7.486a2.24 2.24 0 0 1 0 4.477z"
|
||||
fill="#6927DA" />
|
||||
<path
|
||||
d="M22.657 10.92h8.94a2.24 2.24 0 1 0 0-4.477h-8.94a2.24 2.24 0 1 0 0 4.477zm-6.497 0H6.608a2.24 2.24 0 1 1 0-4.477h9.552a2.24 2.24 0 1 1 0 4.477z"
|
||||
fill="#1570EF" />
|
||||
<path
|
||||
d="M29.448 4.477h-5.041a2.24 2.24 0 0 1-2.241-2.238A2.24 2.24 0 0 1 24.407 0h5.041a2.24 2.24 0 1 1 0 4.477zm-15.656 0H8.751A2.24 2.24 0 1 1 8.75 0h5.041a2.24 2.24 0 1 1 0 4.477z"
|
||||
fill="#2CE" />
|
||||
<path
|
||||
d="M73.648 10.81v6.455h-4.49v-5.753c0-1.97-.878-2.632-2.694-2.632-1.698 0-3.142.663-3.142 2.632v5.753h-4.49v-5.753c0-1.97-.878-2.632-2.694-2.632-1.698 0-3.142.663-3.142 2.632v5.753h-4.49v-11.7h4.49v2.067c1.249-1.521 2.889-2.457 5.153-2.457 2.069 0 3.943.858 4.743 3.003 1.308-1.833 3.045-3.003 5.583-3.003 2.772 0 5.173 1.54 5.173 5.635zm13.918 2.945v-.897c-1.503-.215-3.163-.39-4.666-.39-1.971 0-2.713.253-2.713.955 0 .663.586.917 2.264.917 1.406 0 3.826-.37 5.115-.585zm4.49-2.477v5.987h-4.49v-1.56c-2.089 1.287-4.666 1.755-6.657 1.755-3.064 0-5.407-.956-5.407-3.725 0-3.198 3.514-3.997 6.735-3.997 1.99 0 3.806.273 5.329.585-.117-1.619-1.582-2.028-3.631-2.028-2.089 0-4.08.292-6.852.936l-.624-3.062c2.537-.565 5.27-.994 8.315-.994 4.9 0 7.242 1.638 7.281 6.103zm13.976-5.713h4.548l-5.505 11.407c-1.639 3.393-3.416 5.363-7.027 5.363-1.288 0-2.928-.254-4.431-.683V18.24c1.737.39 2.967.585 3.767.585 1.503 0 2.147-.117 2.967-1.56h-2.108l-5.602-11.7h4.548l4.412 9.204 4.431-9.204zm17.744 5.85c0-1.716-1.191-2.535-3.572-2.535-2.382 0-3.573.819-3.573 2.535 0 1.716 1.191 2.535 3.573 2.535 2.381 0 3.572-.82 3.572-2.535zm4.685 0c0 4.719-3.377 6.24-6.403 6.24-2.128 0-4.236-.663-5.427-2.477v2.087h-4.489v-15.6h4.489v5.986c1.191-1.813 3.299-2.476 5.427-2.476 3.026 0 6.403 1.52 6.403 6.24zm6.461-1.482h7.32c-.508-1.346-1.757-1.833-3.69-1.833-1.893 0-3.142.487-3.63 1.833zm7.476 3.14h4.392c-.781 2.905-3.143 4.582-8.062 4.582-5.27 0-8.511-2.106-8.511-6.24s3.455-6.24 8.335-6.24c5.017 0 8.336 2.223 8.336 6.98h-12.123c.313 1.697 1.777 2.185 4.139 2.185 2.206 0 3.104-.507 3.494-1.268z"
|
||||
fill="#141414" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
BIN
apps/dokploy/public/templates/spacedrive.png
Normal file
BIN
apps/dokploy/public/templates/spacedrive.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 319 KiB |
@@ -9,6 +9,7 @@ import {
|
||||
apiSaveExternalPortMariaDB,
|
||||
apiUpdateMariaDB,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
IS_CLOUD,
|
||||
addNewService,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
createMariadb,
|
||||
createMount,
|
||||
deployMariadb,
|
||||
findBackupsByDbId,
|
||||
findMariadbById,
|
||||
findProjectById,
|
||||
findServerById,
|
||||
@@ -211,8 +213,10 @@ export const mariadbRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const backups = await findBackupsByDbId(input.mariadbId, "mariadb");
|
||||
const cleanupOperations = [
|
||||
async () => await removeService(mongo?.appName, mongo.serverId),
|
||||
async () => await cancelJobs(backups),
|
||||
async () => await removeMariadbById(input.mariadbId),
|
||||
];
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
apiSaveExternalPortMongo,
|
||||
apiUpdateMongo,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
IS_CLOUD,
|
||||
addNewService,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
createMongo,
|
||||
createMount,
|
||||
deployMongo,
|
||||
findBackupsByDbId,
|
||||
findMongoById,
|
||||
findProjectById,
|
||||
removeMongoById,
|
||||
@@ -252,9 +254,11 @@ export const mongoRouter = createTRPCRouter({
|
||||
message: "You are not authorized to delete this mongo",
|
||||
});
|
||||
}
|
||||
const backups = await findBackupsByDbId(input.mongoId, "mongo");
|
||||
|
||||
const cleanupOperations = [
|
||||
async () => await removeService(mongo?.appName, mongo.serverId),
|
||||
async () => await cancelJobs(backups),
|
||||
async () => await removeMongoById(input.mongoId),
|
||||
];
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
IS_CLOUD,
|
||||
addNewService,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
createMount,
|
||||
createMysql,
|
||||
deployMySql,
|
||||
findBackupsByDbId,
|
||||
findMySqlById,
|
||||
findProjectById,
|
||||
removeMySqlById,
|
||||
@@ -249,8 +251,10 @@ export const mysqlRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const backups = await findBackupsByDbId(input.mysqlId, "mysql");
|
||||
const cleanupOperations = [
|
||||
async () => await removeService(mongo?.appName, mongo.serverId),
|
||||
async () => await cancelJobs(backups),
|
||||
async () => await removeMySqlById(input.mysqlId),
|
||||
];
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
apiSaveExternalPortPostgres,
|
||||
apiUpdatePostgres,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
IS_CLOUD,
|
||||
addNewService,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
createMount,
|
||||
createPostgres,
|
||||
deployPostgres,
|
||||
findBackupsByDbId,
|
||||
findPostgresById,
|
||||
findProjectById,
|
||||
removePostgresById,
|
||||
@@ -231,8 +233,11 @@ export const postgresRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const backups = await findBackupsByDbId(input.postgresId, "postgres");
|
||||
|
||||
const cleanupOperations = [
|
||||
removeService(postgres.appName, postgres.serverId),
|
||||
cancelJobs(backups),
|
||||
removePostgresById(input.postgresId),
|
||||
];
|
||||
|
||||
|
||||
@@ -244,7 +244,6 @@ export const redisRouter = createTRPCRouter({
|
||||
message: "You are not authorized to delete this Redis",
|
||||
});
|
||||
}
|
||||
|
||||
const cleanupOperations = [
|
||||
async () => await removeService(redis?.appName, redis.serverId),
|
||||
async () => await removeRedisById(input.redisId),
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import {
|
||||
type BackupScheduleList,
|
||||
IS_CLOUD,
|
||||
removeScheduleBackup,
|
||||
scheduleBackup,
|
||||
} from "@dokploy/server/index";
|
||||
|
||||
type QueueJob =
|
||||
| {
|
||||
type: "backup";
|
||||
@@ -59,3 +66,19 @@ export const updateJob = async (job: QueueJob) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const cancelJobs = async (backups: BackupScheduleList) => {
|
||||
for (const backup of backups) {
|
||||
if (backup.enabled) {
|
||||
if (IS_CLOUD) {
|
||||
await removeJob({
|
||||
cronSchedule: backup.schedule,
|
||||
backupId: backup.backupId,
|
||||
type: "backup",
|
||||
});
|
||||
} else {
|
||||
removeScheduleBackup(backup.backupId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--terminal-paste: rgba(0, 0, 0, 0.2);
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
|
||||
@@ -51,6 +52,7 @@
|
||||
}
|
||||
|
||||
.dark {
|
||||
--terminal-paste: rgba(255, 255, 255, 0.2);
|
||||
--background: 0 0% 0%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
@@ -235,3 +237,8 @@
|
||||
background-color: hsl(var(--muted-foreground) / 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.xterm-bg-257.xterm-fg-257 {
|
||||
background-color: var(--terminal-paste) !important;
|
||||
color: currentColor !important;
|
||||
}
|
||||
|
||||
14
apps/dokploy/templates/alist/docker-compose.yml
Normal file
14
apps/dokploy/templates/alist/docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
alist:
|
||||
image: xhofe/alist:v3.41.0
|
||||
volumes:
|
||||
- alist-data:/opt/alist/data
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- UMASK=022
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
alist-data:
|
||||
22
apps/dokploy/templates/alist/index.ts
Normal file
22
apps/dokploy/templates/alist/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 5244,
|
||||
serviceName: "alist",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
};
|
||||
}
|
||||
31
apps/dokploy/templates/answer/docker-compose.yml
Normal file
31
apps/dokploy/templates/answer/docker-compose.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
services:
|
||||
answer:
|
||||
image: apache/answer:1.4.1
|
||||
ports:
|
||||
- '80'
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- answer-data:/data
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
db:
|
||||
image: postgres:16
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: answer
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
volumes:
|
||||
answer-data:
|
||||
db-data:
|
||||
33
apps/dokploy/templates/answer/index.ts
Normal file
33
apps/dokploy/templates/answer/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateHash,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainServiceHash = generateHash(schema.projectName);
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 9080,
|
||||
serviceName: "answer",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`ANSWER_HOST=http://${mainDomain}`,
|
||||
`SERVICE_HASH=${mainServiceHash}`,
|
||||
];
|
||||
|
||||
const mounts: Template["mounts"] = [];
|
||||
|
||||
return {
|
||||
envs,
|
||||
mounts,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
346
apps/dokploy/templates/erpnext/docker-compose.yml
Normal file
346
apps/dokploy/templates/erpnext/docker-compose.yml
Normal file
@@ -0,0 +1,346 @@
|
||||
x-custom-image: &custom_image
|
||||
image: ${IMAGE_NAME:-docker.io/frappe/erpnext}:${VERSION:-version-15}
|
||||
pull_policy: ${PULL_POLICY:-always}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
|
||||
services:
|
||||
backend:
|
||||
<<: *custom_image
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wait-for-it
|
||||
- '0.0.0.0:8000'
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
frontend:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_started
|
||||
required: true
|
||||
websocket:
|
||||
condition: service_started
|
||||
required: true
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
|
||||
SOCKETIO: websocket:9000
|
||||
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
|
||||
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
|
||||
UPSTREAM_REAL_IP_RECURSIVE: "off"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
networks:
|
||||
- bench-network
|
||||
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wait-for-it
|
||||
- '0.0.0.0:8080'
|
||||
interval: 2s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
|
||||
queue-default:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- default
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wait-for-it
|
||||
- 'redis-queue:6379'
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
required: true
|
||||
|
||||
queue-long:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- long
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wait-for-it
|
||||
- 'redis-queue:6379'
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
required: true
|
||||
|
||||
queue-short:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- short
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wait-for-it
|
||||
- 'redis-queue:6379'
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
required: true
|
||||
|
||||
scheduler:
|
||||
<<: *custom_image
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wait-for-it
|
||||
- 'redis-queue:6379'
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
command:
|
||||
- bench
|
||||
- schedule
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
required: true
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
|
||||
websocket:
|
||||
<<: *custom_image
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wait-for-it
|
||||
- '0.0.0.0:9000'
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
required: true
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
|
||||
configurator:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${CONFIGURE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
[[ $${REGENERATE_APPS_TXT} == "1" ]] && ls -1 apps > sites/apps.txt;
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && exit 0;
|
||||
bench set-config -g db_host $$DB_HOST;
|
||||
bench set-config -gp db_port $$DB_PORT;
|
||||
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
|
||||
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
|
||||
bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
|
||||
bench set-config -gp socketio_port $$SOCKETIO_PORT;
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_PORT: "3306"
|
||||
REDIS_CACHE: redis-cache:6379
|
||||
REDIS_QUEUE: redis-queue:6379
|
||||
SOCKETIO_PORT: "9000"
|
||||
REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
|
||||
create-site:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${CREATE_SITE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
wait-for-it -t 120 db:3306;
|
||||
wait-for-it -t 120 redis-cache:6379;
|
||||
wait-for-it -t 120 redis-queue:6379;
|
||||
export start=`date +%s`;
|
||||
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
|
||||
do
|
||||
echo "Waiting for sites/common_site_config.json to be created";
|
||||
sleep 5;
|
||||
if (( `date +%s`-start > 120 )); then
|
||||
echo "could not find sites/common_site_config.json with required keys";
|
||||
exit 1
|
||||
fi
|
||||
done;
|
||||
echo "sites/common_site_config.json found";
|
||||
[[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0;
|
||||
bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME};
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
environment:
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
|
||||
SITE_NAME: ${SITE_NAME}
|
||||
networks:
|
||||
- bench-network
|
||||
|
||||
migration:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${MIGRATE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
curl -f http://${SITE_NAME}:8080/api/method/ping || echo "Site busy" && exit 0;
|
||||
bench --site all set-config -p maintenance_mode 1;
|
||||
bench --site all set-config -p pause_scheduler 1;
|
||||
bench --site all migrate;
|
||||
bench --site all set-config -p maintenance_mode 0;
|
||||
bench --site all set-config -p pause_scheduler 0;
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
networks:
|
||||
- bench-network
|
||||
|
||||
db:
|
||||
image: mariadb:10.6
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
healthcheck:
|
||||
test: mysqladmin ping -h localhost --password=${DB_ROOT_PASSWORD}
|
||||
interval: 1s
|
||||
retries: 20
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
- --skip-character-set-client-handshake
|
||||
- --skip-innodb-read-only-compressed
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
||||
- MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
networks:
|
||||
- bench-network
|
||||
|
||||
redis-cache:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-cache-data:/data
|
||||
networks:
|
||||
- bench-network
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- redis-cli
|
||||
- ping
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis-queue:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-queue-data:/data
|
||||
networks:
|
||||
- bench-network
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- redis-cli
|
||||
- ping
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis-socketio:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-socketio-data:/data
|
||||
networks:
|
||||
- bench-network
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- redis-cli
|
||||
- ping
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
redis-cache-data:
|
||||
redis-queue-data:
|
||||
redis-socketio-data:
|
||||
sites:
|
||||
|
||||
networks:
|
||||
bench-network:
|
||||
37
apps/dokploy/templates/erpnext/index.ts
Normal file
37
apps/dokploy/templates/erpnext/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const dbRootPassword = generatePassword(32);
|
||||
const adminPassword = generatePassword(32);
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 8080,
|
||||
serviceName: "frontend",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`SITE_NAME=${mainDomain}`,
|
||||
`ADMIN_PASSWORD=${adminPassword}`,
|
||||
`DB_ROOT_PASSWORD=${dbRootPassword}`,
|
||||
"MIGRATE=1",
|
||||
"CREATE_SITE=1",
|
||||
"CONFIGURE=1",
|
||||
"REGENERATE_APPS_TXT=1",
|
||||
"INSTALL_APP_ARGS=--install-app erpnext",
|
||||
"IMAGE_NAME=docker.io/frappe/erpnext",
|
||||
"VERSION=version-15",
|
||||
"FRAPPE_SITE_NAME_HEADER=",
|
||||
];
|
||||
|
||||
return { envs, domains };
|
||||
}
|
||||
37
apps/dokploy/templates/maybe/docker-compose.yml
Normal file
37
apps/dokploy/templates/maybe/docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
services:
|
||||
app:
|
||||
image: ghcr.io/maybe-finance/maybe:sha-68c570eed8810fd59b5b33cca51bbad5eabb4cb4
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ../files/uploads:/app/uploads
|
||||
environment:
|
||||
DATABASE_URL: postgresql://maybe:maybe@db:5432/maybe
|
||||
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
|
||||
SELF_HOSTED: true
|
||||
SYNTH_API_KEY: ${SYNTH_API_KEY}
|
||||
RAILS_FORCE_SSL: "false"
|
||||
RAILS_ASSUME_SSL: "false"
|
||||
GOOD_JOB_EXECUTION_MODE: async
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: postgres:16
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: maybe
|
||||
POSTGRES_USER: maybe
|
||||
POSTGRES_PASSWORD: maybe
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
43
apps/dokploy/templates/maybe/index.ts
Normal file
43
apps/dokploy/templates/maybe/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateBase64,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const secretKeyBase = generateBase64(64);
|
||||
const synthApiKey = generateBase64(32);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 3000,
|
||||
serviceName: "app",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`SECRET_KEY_BASE=${secretKeyBase}`,
|
||||
"SELF_HOSTED=true",
|
||||
`SYNTH_API_KEY=${synthApiKey}`,
|
||||
"RAILS_FORCE_SSL=false",
|
||||
"RAILS_ASSUME_SSL=false",
|
||||
"GOOD_JOB_EXECUTION_MODE=async",
|
||||
];
|
||||
|
||||
const mounts: Template["mounts"] = [
|
||||
{
|
||||
filePath: "./uploads",
|
||||
content: "This is where user uploads will be stored",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
envs,
|
||||
mounts,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
9
apps/dokploy/templates/spacedrive/docker-compose.yml
Normal file
9
apps/dokploy/templates/spacedrive/docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
server:
|
||||
image: ghcr.io/spacedriveapp/spacedrive/server:latest
|
||||
ports:
|
||||
- 8080
|
||||
environment:
|
||||
- SD_AUTH=${SD_USERNAME}:${SD_PASSWORD}
|
||||
volumes:
|
||||
- /var/spacedrive:/var/spacedrive
|
||||
28
apps/dokploy/templates/spacedrive/index.ts
Normal file
28
apps/dokploy/templates/spacedrive/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const randomDomain = generateRandomDomain(schema);
|
||||
const secretKey = generatePassword();
|
||||
const randomUsername = "admin"; // Default username
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: randomDomain,
|
||||
port: 8080,
|
||||
serviceName: "server",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [`SD_USERNAME=${randomUsername}`, `SD_PASSWORD=${secretKey}`];
|
||||
|
||||
return {
|
||||
envs,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
@@ -13,8 +13,11 @@ services:
|
||||
image: amancevice/superset
|
||||
restart: always
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- superset_postgres
|
||||
- superset_redis
|
||||
volumes:
|
||||
# This superset_config.py can be edited in Dokploy's UI Advanced -> Volume Mount
|
||||
- ../files/superset/superset_config.py:/etc/superset/superset_config.py
|
||||
environment:
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
MAPBOX_API_KEY: ${MAPBOX_API_KEY}
|
||||
@@ -22,11 +25,11 @@ services:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
# Note: superset_config.py can be edited in Dokploy's UI Volume Mount
|
||||
- ../files/superset/superset_config.py:/etc/superset/superset_config.py
|
||||
# Ensure the hosts matches your service names below.
|
||||
POSTGRES_HOST: superset_postgres
|
||||
REDIS_HOST: superset_redis
|
||||
|
||||
db:
|
||||
superset_postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
@@ -34,7 +37,7 @@ services:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
- superset_postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 30s
|
||||
@@ -43,11 +46,11 @@ services:
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
redis:
|
||||
superset_redis:
|
||||
image: redis
|
||||
restart: always
|
||||
volumes:
|
||||
- redis:/data
|
||||
- superset_redis_data:/data
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
@@ -58,5 +61,5 @@ services:
|
||||
- dokploy-network
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
redis:
|
||||
superset_postgres_data:
|
||||
superset_redis_data:
|
||||
|
||||
@@ -47,13 +47,13 @@ CACHE_CONFIG = {
|
||||
"CACHE_REDIS_HOST": "redis",
|
||||
"CACHE_REDIS_PORT": 6379,
|
||||
"CACHE_REDIS_DB": 1,
|
||||
"CACHE_REDIS_URL": f"redis://:{os.getenv('REDIS_PASSWORD')}@redis:6379/1",
|
||||
"CACHE_REDIS_URL": f"redis://:{os.getenv('REDIS_PASSWORD')}@{os.getenv('REDIS_HOST')}:6379/1",
|
||||
}
|
||||
|
||||
FILTER_STATE_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_filter_"}
|
||||
EXPLORE_FORM_DATA_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_explore_form_"}
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@db:5432/{os.getenv('POSTGRES_DB')}"
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}:5432/{os.getenv('POSTGRES_DB')}"
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
||||
`.trim(),
|
||||
},
|
||||
|
||||
@@ -538,7 +538,7 @@ export const templates: TemplateData[] = [
|
||||
website: "https://filebrowser.org/",
|
||||
docs: "https://filebrowser.org/",
|
||||
},
|
||||
tags: ["file", "manager"],
|
||||
tags: ["file-manager", "storage"],
|
||||
load: () => import("./filebrowser/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
@@ -834,7 +834,7 @@ export const templates: TemplateData[] = [
|
||||
website: "https://nextcloud.com/",
|
||||
docs: "https://docs.nextcloud.com/",
|
||||
},
|
||||
tags: ["file", "sync"],
|
||||
tags: ["file-manager", "sync"],
|
||||
load: () => import("./nextcloud-aio/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
@@ -1341,4 +1341,86 @@ export const templates: TemplateData[] = [
|
||||
tags: ["dashboard", "monitoring"],
|
||||
load: () => import("./homarr/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "erpnext",
|
||||
name: "ERPNext",
|
||||
version: "version-15",
|
||||
description: "100% Open Source and highly customizable ERP software.",
|
||||
logo: "erpnext.svg",
|
||||
links: {
|
||||
github: "https://github.com/frappe/erpnext",
|
||||
docs: "https://docs.frappe.io/erpnext",
|
||||
website: "https://erpnext.com",
|
||||
},
|
||||
tags: [
|
||||
"erp",
|
||||
"accounts",
|
||||
"manufacturing",
|
||||
"retail",
|
||||
"sales",
|
||||
"pos",
|
||||
"hrms",
|
||||
],
|
||||
load: () => import("./erpnext/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "maybe",
|
||||
name: "Maybe",
|
||||
version: "latest",
|
||||
description:
|
||||
"Maybe is a self-hosted finance tracking application designed to simplify budgeting and expenses.",
|
||||
logo: "maybe.svg",
|
||||
links: {
|
||||
github: "https://github.com/maybe-finance/maybe",
|
||||
website: "https://maybe.finance/",
|
||||
docs: "https://docs.maybe.finance/",
|
||||
},
|
||||
tags: ["finance", "self-hosted"],
|
||||
load: () => import("./maybe/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "spacedrive",
|
||||
name: "Spacedrive",
|
||||
version: "latest",
|
||||
description:
|
||||
"Spacedrive is a cross-platform file manager. It connects your devices together to help you organize files from anywhere. powered by a virtual distributed filesystem (VDFS) written in Rust. Organize files across many devices in one place.",
|
||||
links: {
|
||||
github: "https://github.com/spacedriveapp/spacedrive",
|
||||
website: "https://spacedrive.com/",
|
||||
docs: "https://www.spacedrive.com/docs/product/getting-started/introduction",
|
||||
},
|
||||
logo: "spacedrive.png",
|
||||
tags: ["file-manager", "vdfs", "storage"],
|
||||
load: () => import("./spacedrive/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "alist",
|
||||
name: "AList",
|
||||
version: "v3.41.0",
|
||||
description:
|
||||
"🗂️A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs.",
|
||||
logo: "alist.svg",
|
||||
links: {
|
||||
github: "https://github.com/AlistGo/alist",
|
||||
website: "https://alist.nn.ci",
|
||||
docs: "https://alist.nn.ci/guide/install/docker.html",
|
||||
},
|
||||
tags: ["file", "webdav", "storage"],
|
||||
load: () => import("./alist/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "answer",
|
||||
name: "Answer",
|
||||
version: "v1.4.1",
|
||||
description:
|
||||
"Answer is an open-source Q&A platform for building a self-hosted question-and-answer service.",
|
||||
logo: "answer.png",
|
||||
links: {
|
||||
github: "https://github.com/apache/answer",
|
||||
website: "https://answer.apache.org/",
|
||||
docs: "https://answer.apache.org/docs",
|
||||
},
|
||||
tags: ["q&a", "self-hosted"],
|
||||
load: () => import("./answer/index").then((m) => m.generate),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -451,7 +451,7 @@ export const deployPreviewApplication = async ({
|
||||
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
|
||||
});
|
||||
application.appName = previewDeployment.appName;
|
||||
application.env = application.previewEnv;
|
||||
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
|
||||
application.buildArgs = application.previewBuildArgs;
|
||||
|
||||
const admin = await findAdminById(application.project.adminId);
|
||||
@@ -564,7 +564,7 @@ export const deployRemotePreviewApplication = async ({
|
||||
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
|
||||
});
|
||||
application.appName = previewDeployment.appName;
|
||||
application.env = application.previewEnv;
|
||||
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
|
||||
application.buildArgs = application.previewBuildArgs;
|
||||
|
||||
if (application.serverId) {
|
||||
|
||||
@@ -2,11 +2,13 @@ import { db } from "@dokploy/server/db";
|
||||
import { type apiCreateBackup, backups } from "@dokploy/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { IS_CLOUD } from "../constants";
|
||||
import { removeScheduleBackup, scheduleBackup } from "../utils/backups/utils";
|
||||
|
||||
export type Backup = typeof backups.$inferSelect;
|
||||
|
||||
export type BackupSchedule = Awaited<ReturnType<typeof findBackupById>>;
|
||||
|
||||
export type BackupScheduleList = Awaited<ReturnType<typeof findBackupsByDbId>>;
|
||||
export const createBackup = async (input: typeof apiCreateBackup._type) => {
|
||||
const newBackup = await db
|
||||
.insert(backups)
|
||||
@@ -69,3 +71,20 @@ export const removeBackupById = async (backupId: string) => {
|
||||
|
||||
return result[0];
|
||||
};
|
||||
|
||||
export const findBackupsByDbId = async (
|
||||
id: string,
|
||||
type: "postgres" | "mysql" | "mariadb" | "mongo",
|
||||
) => {
|
||||
const result = await db.query.backups.findMany({
|
||||
where: eq(backups[`${type}Id`], id),
|
||||
with: {
|
||||
postgres: true,
|
||||
mysql: true,
|
||||
mariadb: true,
|
||||
mongo: true,
|
||||
destination: true,
|
||||
},
|
||||
});
|
||||
return result || [];
|
||||
};
|
||||
|
||||
@@ -216,8 +216,8 @@ echo "$json_output"
|
||||
};
|
||||
|
||||
export const cleanupFullDocker = async (serverId?: string | null) => {
|
||||
const cleanupImages = "docker image prune --all --force";
|
||||
const cleanupVolumes = "docker volume prune --all --force";
|
||||
const cleanupImages = "docker image prune --force";
|
||||
const cleanupVolumes = "docker volume prune --force";
|
||||
const cleanupContainers = "docker container prune --force";
|
||||
const cleanupSystem = "docker system prune --all --force --volumes";
|
||||
const cleanupBuilder = "docker builder prune --all --force";
|
||||
|
||||
@@ -189,10 +189,12 @@ export const getDefaultTraefikConfig = () => {
|
||||
: {
|
||||
swarm: {
|
||||
exposedByDefault: false,
|
||||
watch: false,
|
||||
watch: true,
|
||||
},
|
||||
docker: {
|
||||
exposedByDefault: false,
|
||||
watch: true,
|
||||
network: "dokploy-network",
|
||||
},
|
||||
}),
|
||||
file: {
|
||||
@@ -243,10 +245,12 @@ export const getDefaultServerTraefikConfig = () => {
|
||||
providers: {
|
||||
swarm: {
|
||||
exposedByDefault: false,
|
||||
watch: false,
|
||||
watch: true,
|
||||
},
|
||||
docker: {
|
||||
exposedByDefault: false,
|
||||
watch: true,
|
||||
network: "dokploy-network",
|
||||
},
|
||||
file: {
|
||||
directory: "/etc/dokploy/traefik/dynamic",
|
||||
|
||||
@@ -144,10 +144,11 @@ export const getContainerByName = (name: string): Promise<ContainerInfo> => {
|
||||
};
|
||||
export const cleanUpUnusedImages = async (serverId?: string) => {
|
||||
try {
|
||||
const command = "docker image prune --force";
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, "docker image prune --all --force");
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync("docker image prune --all --force");
|
||||
await execAsync(command);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -157,10 +158,11 @@ export const cleanUpUnusedImages = async (serverId?: string) => {
|
||||
|
||||
export const cleanStoppedContainers = async (serverId?: string) => {
|
||||
try {
|
||||
const command = "docker container prune --force";
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, "docker container prune --force");
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync("docker container prune --force");
|
||||
await execAsync(command);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -170,10 +172,11 @@ export const cleanStoppedContainers = async (serverId?: string) => {
|
||||
|
||||
export const cleanUpUnusedVolumes = async (serverId?: string) => {
|
||||
try {
|
||||
const command = "docker volume prune --force";
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, "docker volume prune --all --force");
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync("docker volume prune --all --force");
|
||||
await execAsync(command);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -199,21 +202,20 @@ export const cleanUpInactiveContainers = async () => {
|
||||
};
|
||||
|
||||
export const cleanUpDockerBuilder = async (serverId?: string) => {
|
||||
const command = "docker builder prune --all --force";
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, "docker builder prune --all --force");
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync("docker builder prune --all --force");
|
||||
await execAsync(command);
|
||||
}
|
||||
};
|
||||
|
||||
export const cleanUpSystemPrune = async (serverId?: string) => {
|
||||
const command = "docker system prune --all --force --volumes";
|
||||
if (serverId) {
|
||||
await execAsyncRemote(
|
||||
serverId,
|
||||
"docker system prune --all --force --volumes",
|
||||
);
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync("docker system prune --all --force --volumes");
|
||||
await execAsync(command);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ export const cloneGitRepository = async (
|
||||
});
|
||||
}
|
||||
|
||||
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
||||
await spawnAsync(
|
||||
"git",
|
||||
[
|
||||
@@ -91,7 +92,7 @@ export const cloneGitRepository = async (
|
||||
env: {
|
||||
...process.env,
|
||||
...(customGitSSHKeyId && {
|
||||
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -168,7 +169,8 @@ export const getCustomGitCloneCommand = async (
|
||||
);
|
||||
if (customGitSSHKeyId) {
|
||||
const sshKey = await findSSHKeyById(customGitSSHKeyId);
|
||||
const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
|
||||
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
||||
const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
|
||||
command.push(
|
||||
`
|
||||
echo "${sshKey.privateKey}" > /tmp/id_rsa
|
||||
@@ -304,6 +306,7 @@ export const cloneGitRawRepository = async (entity: {
|
||||
});
|
||||
}
|
||||
|
||||
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
||||
await spawnAsync(
|
||||
"git",
|
||||
[
|
||||
@@ -322,7 +325,7 @@ export const cloneGitRawRepository = async (entity: {
|
||||
env: {
|
||||
...process.env,
|
||||
...(customGitSSHKeyId && {
|
||||
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -381,7 +384,8 @@ export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
|
||||
command.push(`mkdir -p ${outputPath};`);
|
||||
if (customGitSSHKeyId) {
|
||||
const sshKey = await findSSHKeyById(customGitSSHKeyId);
|
||||
const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
|
||||
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
||||
const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
|
||||
command.push(
|
||||
`
|
||||
echo "${sshKey.privateKey}" > /tmp/id_rsa
|
||||
|
||||
63
pnpm-lock.yaml
generated
63
pnpm-lock.yaml
generated
@@ -124,6 +124,12 @@ importers:
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.3.4
|
||||
version: 3.9.0(react-hook-form@7.52.1(react@18.2.0))
|
||||
'@lucia-auth/adapter-drizzle':
|
||||
specifier: 1.0.7
|
||||
version: 1.0.7(lucia@3.2.0)
|
||||
'@octokit/auth-app':
|
||||
specifier: ^6.0.4
|
||||
version: 6.1.1
|
||||
'@octokit/webhooks':
|
||||
specifier: ^13.2.7
|
||||
version: 13.3.0
|
||||
@@ -184,6 +190,9 @@ importers:
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.0.7
|
||||
version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@react-email/components':
|
||||
specifier: ^0.0.21
|
||||
version: 0.0.21(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@stepperize/react':
|
||||
specifier: 4.0.1
|
||||
version: 4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -217,6 +226,9 @@ importers:
|
||||
'@xterm/addon-attach':
|
||||
specifier: 0.10.0
|
||||
version: 0.10.0(@xterm/xterm@5.5.0)
|
||||
'@xterm/addon-clipboard':
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0(@xterm/xterm@5.5.0)
|
||||
'@xterm/xterm':
|
||||
specifier: ^5.4.0
|
||||
version: 5.5.0
|
||||
@@ -226,6 +238,12 @@ importers:
|
||||
bcrypt:
|
||||
specifier: 5.1.1
|
||||
version: 5.1.1
|
||||
bl:
|
||||
specifier: 6.0.11
|
||||
version: 6.0.11
|
||||
boxen:
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.1
|
||||
bullmq:
|
||||
specifier: 5.4.2
|
||||
version: 5.4.2
|
||||
@@ -247,6 +265,9 @@ importers:
|
||||
date-fns:
|
||||
specifier: 3.6.0
|
||||
version: 3.6.0
|
||||
dockerode:
|
||||
specifier: 4.0.2
|
||||
version: 4.0.2
|
||||
dotenv:
|
||||
specifier: 16.4.5
|
||||
version: 16.4.5
|
||||
@@ -259,6 +280,9 @@ importers:
|
||||
fancy-ansi:
|
||||
specifier: ^0.1.3
|
||||
version: 0.1.3
|
||||
hi-base32:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1
|
||||
i18next:
|
||||
specifier: ^23.16.4
|
||||
version: 23.16.5
|
||||
@@ -292,21 +316,33 @@ importers:
|
||||
next-themes:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
node-os-utils:
|
||||
specifier: 1.3.7
|
||||
version: 1.3.7
|
||||
node-pty:
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0
|
||||
node-schedule:
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1
|
||||
nodemailer:
|
||||
specifier: 6.9.14
|
||||
version: 6.9.14
|
||||
octokit:
|
||||
specifier: 3.1.2
|
||||
version: 3.1.2
|
||||
otpauth:
|
||||
specifier: ^9.2.3
|
||||
version: 9.3.4
|
||||
postgres:
|
||||
specifier: 3.4.4
|
||||
version: 3.4.4
|
||||
public-ip:
|
||||
specifier: 6.0.2
|
||||
version: 6.0.2
|
||||
qrcode:
|
||||
specifier: ^1.5.3
|
||||
version: 1.5.4
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
@@ -325,6 +361,9 @@ importers:
|
||||
recharts:
|
||||
specifier: ^2.12.7
|
||||
version: 2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
rotating-file-stream:
|
||||
specifier: 3.2.3
|
||||
version: 3.2.3
|
||||
slugify:
|
||||
specifier: ^1.6.6
|
||||
version: 1.6.6
|
||||
@@ -386,9 +425,18 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^18.17.0
|
||||
version: 18.19.42
|
||||
'@types/node-os-utils':
|
||||
specifier: 1.3.4
|
||||
version: 1.3.4
|
||||
'@types/node-schedule':
|
||||
specifier: 2.1.6
|
||||
version: 2.1.6
|
||||
'@types/nodemailer':
|
||||
specifier: ^6.4.15
|
||||
version: 6.4.16
|
||||
'@types/qrcode':
|
||||
specifier: ^1.5.5
|
||||
version: 1.5.5
|
||||
'@types/react':
|
||||
specifier: 18.3.5
|
||||
version: 18.3.5
|
||||
@@ -3503,6 +3551,11 @@ packages:
|
||||
peerDependencies:
|
||||
'@xterm/xterm': ^5.0.0
|
||||
|
||||
'@xterm/addon-clipboard@0.1.0':
|
||||
resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==}
|
||||
peerDependencies:
|
||||
'@xterm/xterm': ^5.4.0
|
||||
|
||||
'@xterm/xterm@5.5.0':
|
||||
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
|
||||
|
||||
@@ -5011,6 +5064,9 @@ packages:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
js-base64@3.7.7:
|
||||
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
||||
|
||||
js-beautify@1.15.1:
|
||||
resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -9894,6 +9950,11 @@ snapshots:
|
||||
dependencies:
|
||||
'@xterm/xterm': 5.5.0
|
||||
|
||||
'@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)':
|
||||
dependencies:
|
||||
'@xterm/xterm': 5.5.0
|
||||
js-base64: 3.7.7
|
||||
|
||||
'@xterm/xterm@5.5.0': {}
|
||||
|
||||
'@xtuc/ieee754@1.2.0': {}
|
||||
@@ -11410,6 +11471,8 @@ snapshots:
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
js-base64@3.7.7: {}
|
||||
|
||||
js-beautify@1.15.1:
|
||||
dependencies:
|
||||
config-chain: 1.1.13
|
||||
|
||||
Reference in New Issue
Block a user