mirror of
https://github.com/LukeHagar/dokploy.git
synced 2025-12-06 12:27:49 +00:00
Merge branch 'canary' into fix/docker-terminal-dropdown-containers
This commit is contained in:
@@ -133,6 +133,7 @@ const baseApp: ApplicationNested = {
|
|||||||
username: null,
|
username: null,
|
||||||
dockerContextPath: null,
|
dockerContextPath: null,
|
||||||
rollbackActive: false,
|
rollbackActive: false,
|
||||||
|
stopGracePeriodSwarm: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("unzipDrop using real zip files", () => {
|
describe("unzipDrop using real zip files", () => {
|
||||||
|
|||||||
102
apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts
Normal file
102
apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import type { ApplicationNested } from "@dokploy/server/utils/builders";
|
||||||
|
import { mechanizeDockerContainer } from "@dokploy/server/utils/builders";
|
||||||
|
|
||||||
|
type MockCreateServiceOptions = {
|
||||||
|
StopGracePeriod?: number;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { inspectMock, getServiceMock, createServiceMock, getRemoteDockerMock } =
|
||||||
|
vi.hoisted(() => {
|
||||||
|
const inspect = vi.fn<[], Promise<never>>();
|
||||||
|
const getService = vi.fn(() => ({ inspect }));
|
||||||
|
const createService = vi.fn<[MockCreateServiceOptions], Promise<void>>(
|
||||||
|
async () => undefined,
|
||||||
|
);
|
||||||
|
const getRemoteDocker = vi.fn(async () => ({
|
||||||
|
getService,
|
||||||
|
createService,
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
inspectMock: inspect,
|
||||||
|
getServiceMock: getService,
|
||||||
|
createServiceMock: createService,
|
||||||
|
getRemoteDockerMock: getRemoteDocker,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("@dokploy/server/utils/servers/remote-docker", () => ({
|
||||||
|
getRemoteDocker: getRemoteDockerMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const createApplication = (
|
||||||
|
overrides: Partial<ApplicationNested> = {},
|
||||||
|
): ApplicationNested =>
|
||||||
|
({
|
||||||
|
appName: "test-app",
|
||||||
|
buildType: "dockerfile",
|
||||||
|
env: null,
|
||||||
|
mounts: [],
|
||||||
|
cpuLimit: null,
|
||||||
|
memoryLimit: null,
|
||||||
|
memoryReservation: null,
|
||||||
|
cpuReservation: null,
|
||||||
|
command: null,
|
||||||
|
ports: [],
|
||||||
|
sourceType: "docker",
|
||||||
|
dockerImage: "example:latest",
|
||||||
|
registry: null,
|
||||||
|
environment: {
|
||||||
|
project: { env: null },
|
||||||
|
env: null,
|
||||||
|
},
|
||||||
|
replicas: 1,
|
||||||
|
stopGracePeriodSwarm: 0n,
|
||||||
|
serverId: "server-id",
|
||||||
|
...overrides,
|
||||||
|
}) as unknown as ApplicationNested;
|
||||||
|
|
||||||
|
describe("mechanizeDockerContainer", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
inspectMock.mockReset();
|
||||||
|
inspectMock.mockRejectedValue(new Error("service not found"));
|
||||||
|
getServiceMock.mockClear();
|
||||||
|
createServiceMock.mockClear();
|
||||||
|
getRemoteDockerMock.mockClear();
|
||||||
|
getRemoteDockerMock.mockResolvedValue({
|
||||||
|
getService: getServiceMock,
|
||||||
|
createService: createServiceMock,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts bigint stopGracePeriodSwarm to a number and keeps zero values", async () => {
|
||||||
|
const application = createApplication({ stopGracePeriodSwarm: 0n });
|
||||||
|
|
||||||
|
await mechanizeDockerContainer(application);
|
||||||
|
|
||||||
|
expect(createServiceMock).toHaveBeenCalledTimes(1);
|
||||||
|
const call = createServiceMock.mock.calls[0];
|
||||||
|
if (!call) {
|
||||||
|
throw new Error("createServiceMock should have been called once");
|
||||||
|
}
|
||||||
|
const [settings] = call;
|
||||||
|
expect(settings.StopGracePeriod).toBe(0);
|
||||||
|
expect(typeof settings.StopGracePeriod).toBe("number");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits StopGracePeriod when stopGracePeriodSwarm is null", async () => {
|
||||||
|
const application = createApplication({ stopGracePeriodSwarm: null });
|
||||||
|
|
||||||
|
await mechanizeDockerContainer(application);
|
||||||
|
|
||||||
|
expect(createServiceMock).toHaveBeenCalledTimes(1);
|
||||||
|
const call = createServiceMock.mock.calls[0];
|
||||||
|
if (!call) {
|
||||||
|
throw new Error("createServiceMock should have been called once");
|
||||||
|
}
|
||||||
|
const [settings] = call;
|
||||||
|
expect(settings).not.toHaveProperty("StopGracePeriod");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -111,6 +111,7 @@ const baseApp: ApplicationNested = {
|
|||||||
updateConfigSwarm: null,
|
updateConfigSwarm: null,
|
||||||
username: null,
|
username: null,
|
||||||
dockerContextPath: null,
|
dockerContextPath: null,
|
||||||
|
stopGracePeriodSwarm: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseDomain: Domain = {
|
const baseDomain: Domain = {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -176,10 +177,18 @@ const addSwarmSettings = z.object({
|
|||||||
modeSwarm: createStringToJSONSchema(ServiceModeSwarmSchema).nullable(),
|
modeSwarm: createStringToJSONSchema(ServiceModeSwarmSchema).nullable(),
|
||||||
labelsSwarm: createStringToJSONSchema(LabelsSwarmSchema).nullable(),
|
labelsSwarm: createStringToJSONSchema(LabelsSwarmSchema).nullable(),
|
||||||
networkSwarm: createStringToJSONSchema(NetworkSwarmSchema).nullable(),
|
networkSwarm: createStringToJSONSchema(NetworkSwarmSchema).nullable(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
|
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
|
||||||
|
|
||||||
|
const hasStopGracePeriodSwarm = (
|
||||||
|
value: unknown,
|
||||||
|
): value is { stopGracePeriodSwarm: bigint | number | string | null } =>
|
||||||
|
typeof value === "object" &&
|
||||||
|
value !== null &&
|
||||||
|
"stopGracePeriodSwarm" in value;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
||||||
@@ -224,12 +233,22 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
modeSwarm: null,
|
modeSwarm: null,
|
||||||
labelsSwarm: null,
|
labelsSwarm: null,
|
||||||
networkSwarm: null,
|
networkSwarm: null,
|
||||||
|
stopGracePeriodSwarm: null,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addSwarmSettings),
|
resolver: zodResolver(addSwarmSettings),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
const stopGracePeriodValue = hasStopGracePeriodSwarm(data)
|
||||||
|
? data.stopGracePeriodSwarm
|
||||||
|
: null;
|
||||||
|
const normalizedStopGracePeriod =
|
||||||
|
stopGracePeriodValue === null || stopGracePeriodValue === undefined
|
||||||
|
? null
|
||||||
|
: typeof stopGracePeriodValue === "bigint"
|
||||||
|
? stopGracePeriodValue
|
||||||
|
: BigInt(stopGracePeriodValue);
|
||||||
form.reset({
|
form.reset({
|
||||||
healthCheckSwarm: data.healthCheckSwarm
|
healthCheckSwarm: data.healthCheckSwarm
|
||||||
? JSON.stringify(data.healthCheckSwarm, null, 2)
|
? JSON.stringify(data.healthCheckSwarm, null, 2)
|
||||||
@@ -255,6 +274,7 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
networkSwarm: data.networkSwarm
|
networkSwarm: data.networkSwarm
|
||||||
? JSON.stringify(data.networkSwarm, null, 2)
|
? JSON.stringify(data.networkSwarm, null, 2)
|
||||||
: null,
|
: null,
|
||||||
|
stopGracePeriodSwarm: normalizedStopGracePeriod,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form, form.reset, data]);
|
}, [form, form.reset, data]);
|
||||||
@@ -275,6 +295,7 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
modeSwarm: data.modeSwarm,
|
modeSwarm: data.modeSwarm,
|
||||||
labelsSwarm: data.labelsSwarm,
|
labelsSwarm: data.labelsSwarm,
|
||||||
networkSwarm: data.networkSwarm,
|
networkSwarm: data.networkSwarm,
|
||||||
|
stopGracePeriodSwarm: data.stopGracePeriodSwarm ?? null,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Swarm settings updated");
|
toast.success("Swarm settings updated");
|
||||||
@@ -352,9 +373,9 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
language="json"
|
language="json"
|
||||||
placeholder={`{
|
placeholder={`{
|
||||||
"Test" : ["CMD-SHELL", "curl -f http://localhost:3000/health"],
|
"Test" : ["CMD-SHELL", "curl -f http://localhost:3000/health"],
|
||||||
"Interval" : 10000,
|
"Interval" : 10000000000,
|
||||||
"Timeout" : 10000,
|
"Timeout" : 10000000000,
|
||||||
"StartPeriod" : 10000,
|
"StartPeriod" : 10000000000,
|
||||||
"Retries" : 10
|
"Retries" : 10
|
||||||
}`}
|
}`}
|
||||||
className="h-[12rem] font-mono"
|
className="h-[12rem] font-mono"
|
||||||
@@ -407,9 +428,9 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
language="json"
|
language="json"
|
||||||
placeholder={`{
|
placeholder={`{
|
||||||
"Condition" : "on-failure",
|
"Condition" : "on-failure",
|
||||||
"Delay" : 10000,
|
"Delay" : 10000000000,
|
||||||
"MaxAttempts" : 10,
|
"MaxAttempts" : 10,
|
||||||
"Window" : 10000
|
"Window" : 10000000000
|
||||||
} `}
|
} `}
|
||||||
className="h-[12rem] font-mono"
|
className="h-[12rem] font-mono"
|
||||||
{...field}
|
{...field}
|
||||||
@@ -529,9 +550,9 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
language="json"
|
language="json"
|
||||||
placeholder={`{
|
placeholder={`{
|
||||||
"Parallelism" : 1,
|
"Parallelism" : 1,
|
||||||
"Delay" : 10000,
|
"Delay" : 10000000000,
|
||||||
"FailureAction" : "continue",
|
"FailureAction" : "continue",
|
||||||
"Monitor" : 10000,
|
"Monitor" : 10000000000,
|
||||||
"MaxFailureRatio" : 10,
|
"MaxFailureRatio" : 10,
|
||||||
"Order" : "start-first"
|
"Order" : "start-first"
|
||||||
}`}
|
}`}
|
||||||
@@ -587,9 +608,9 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
language="json"
|
language="json"
|
||||||
placeholder={`{
|
placeholder={`{
|
||||||
"Parallelism" : 1,
|
"Parallelism" : 1,
|
||||||
"Delay" : 10000,
|
"Delay" : 10000000000,
|
||||||
"FailureAction" : "continue",
|
"FailureAction" : "continue",
|
||||||
"Monitor" : 10000,
|
"Monitor" : 10000000000,
|
||||||
"MaxFailureRatio" : 10,
|
"MaxFailureRatio" : 10,
|
||||||
"Order" : "start-first"
|
"Order" : "start-first"
|
||||||
}`}
|
}`}
|
||||||
@@ -774,7 +795,57 @@ export const AddSwarmSettings = ({ id, type }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="stopGracePeriodSwarm"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
||||||
|
<FormLabel>Stop Grace Period (nanoseconds)</FormLabel>
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
|
||||||
|
Duration in nanoseconds
|
||||||
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
|
</FormDescription>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent
|
||||||
|
className="w-full z-[999]"
|
||||||
|
align="start"
|
||||||
|
side="bottom"
|
||||||
|
>
|
||||||
|
<code>
|
||||||
|
<pre>
|
||||||
|
{`Enter duration in nanoseconds:
|
||||||
|
• 30000000000 - 30 seconds
|
||||||
|
• 120000000000 - 2 minutes
|
||||||
|
• 3600000000000 - 1 hour
|
||||||
|
• 0 - no grace period`}
|
||||||
|
</pre>
|
||||||
|
</code>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="30000000000"
|
||||||
|
className="font-mono"
|
||||||
|
{...field}
|
||||||
|
value={field?.value?.toString() || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
field.onChange(
|
||||||
|
e.target.value ? BigInt(e.target.value) : null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<pre>
|
||||||
|
<FormMessage />
|
||||||
|
</pre>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2 m-0 sticky bottom-0 right-0 bg-muted border">
|
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2 m-0 sticky bottom-0 right-0 bg-muted border">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { mutateAsync, isLoading } =
|
const { mutateAsync, isLoading } =
|
||||||
api.application.saveGitProdiver.useMutation();
|
api.application.saveGitProvider.useMutation();
|
||||||
|
|
||||||
const form = useForm<GitProvider>({
|
const form = useForm<GitProvider>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { type Control, useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
@@ -57,6 +57,7 @@ export const commonCronExpressions = [
|
|||||||
{ label: "Every month on the 1st at midnight", value: "0 0 1 * *" },
|
{ label: "Every month on the 1st at midnight", value: "0 0 1 * *" },
|
||||||
{ label: "Every 15 minutes", value: "*/15 * * * *" },
|
{ label: "Every 15 minutes", value: "*/15 * * * *" },
|
||||||
{ label: "Every weekday at midnight", value: "0 0 * * 1-5" },
|
{ label: "Every weekday at midnight", value: "0 0 * * 1-5" },
|
||||||
|
{ label: "Custom", value: "custom" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
@@ -115,10 +116,91 @@ interface Props {
|
|||||||
scheduleType?: "application" | "compose" | "server" | "dokploy-server";
|
scheduleType?: "application" | "compose" | "server" | "dokploy-server";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ScheduleFormField = ({
|
||||||
|
name,
|
||||||
|
formControl,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
formControl: Control<any>;
|
||||||
|
}) => {
|
||||||
|
const [selectedOption, setSelectedOption] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={formControl}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="flex items-center gap-2">
|
||||||
|
Schedule
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Cron expression format: minute hour day month weekday</p>
|
||||||
|
<p>Example: 0 0 * * * (daily at midnight)</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</FormLabel>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Select
|
||||||
|
value={selectedOption}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedOption(value);
|
||||||
|
field.onChange(value === "custom" ? "" : value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a predefined schedule" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{commonCronExpressions.map((expr) => (
|
||||||
|
<SelectItem key={expr.value} value={expr.value}>
|
||||||
|
{expr.label}
|
||||||
|
{expr.value !== "custom" && ` (${expr.value})`}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div className="relative">
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Custom cron expression (e.g., 0 0 * * *)"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
const commonExpression = commonCronExpressions.find(
|
||||||
|
(expression) => expression.value === value,
|
||||||
|
);
|
||||||
|
if (commonExpression) {
|
||||||
|
setSelectedOption(commonExpression.value);
|
||||||
|
} else {
|
||||||
|
setSelectedOption("custom");
|
||||||
|
}
|
||||||
|
field.onChange(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FormDescription>
|
||||||
|
Choose a predefined schedule or enter a custom cron expression
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
@@ -377,63 +459,9 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<ScheduleFormField
|
||||||
control={form.control}
|
|
||||||
name="cronExpression"
|
name="cronExpression"
|
||||||
render={({ field }) => (
|
formControl={form.control}
|
||||||
<FormItem>
|
|
||||||
<FormLabel className="flex items-center gap-2">
|
|
||||||
Schedule
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>
|
|
||||||
Cron expression format: minute hour day month
|
|
||||||
weekday
|
|
||||||
</p>
|
|
||||||
<p>Example: 0 0 * * * (daily at midnight)</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</FormLabel>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) => {
|
|
||||||
field.onChange(value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a predefined schedule" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{commonCronExpressions.map((expr) => (
|
|
||||||
<SelectItem key={expr.value} value={expr.value}>
|
|
||||||
{expr.label} ({expr.value})
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<div className="relative">
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder="Custom cron expression (e.g., 0 0 * * *)"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FormDescription>
|
|
||||||
Choose a predefined schedule or enter a custom cron
|
|
||||||
expression
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(scheduleTypeForm === "application" ||
|
{(scheduleTypeForm === "application" ||
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import {
|
import { DatabaseZap, PenBoxIcon, PlusCircle, RefreshCw } from "lucide-react";
|
||||||
DatabaseZap,
|
|
||||||
Info,
|
|
||||||
PenBoxIcon,
|
|
||||||
PlusCircle,
|
|
||||||
RefreshCw,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -47,7 +41,7 @@ import {
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import type { CacheType } from "../domains/handle-domain";
|
import type { CacheType } from "../domains/handle-domain";
|
||||||
import { commonCronExpressions } from "../schedules/handle-schedules";
|
import { ScheduleFormField } from "../schedules/handle-schedules";
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -306,64 +300,9 @@ export const HandleVolumeBackups = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<ScheduleFormField
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="cronExpression"
|
name="cronExpression"
|
||||||
render={({ field }) => (
|
formControl={form.control}
|
||||||
<FormItem>
|
|
||||||
<FormLabel className="flex items-center gap-2">
|
|
||||||
Schedule
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>
|
|
||||||
Cron expression format: minute hour day month
|
|
||||||
weekday
|
|
||||||
</p>
|
|
||||||
<p>Example: 0 0 * * * (daily at midnight)</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</FormLabel>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) => {
|
|
||||||
field.onChange(value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a predefined schedule" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{commonCronExpressions.map((expr) => (
|
|
||||||
<SelectItem key={expr.value} value={expr.value}>
|
|
||||||
{expr.label} ({expr.value})
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<div className="relative">
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder="Custom cron expression (e.g., 0 0 * * *)"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FormDescription>
|
|
||||||
Choose a predefined schedule or enter a custom cron
|
|
||||||
expression
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -35,6 +35,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = api.compose.update.useMutation();
|
const { mutateAsync, isLoading } = api.compose.update.useMutation();
|
||||||
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||||
|
|
||||||
const form = useForm<AddComposeFile>({
|
const form = useForm<AddComposeFile>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -53,6 +54,12 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
}
|
}
|
||||||
}, [form, form.reset, data]);
|
}, [form, form.reset, data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.composeFile !== undefined) {
|
||||||
|
setHasUnsavedChanges(composeFile !== data.composeFile);
|
||||||
|
}
|
||||||
|
}, [composeFile, data?.composeFile]);
|
||||||
|
|
||||||
const onSubmit = async (data: AddComposeFile) => {
|
const onSubmit = async (data: AddComposeFile) => {
|
||||||
const { valid, error } = validateAndFormatYAML(data.composeFile);
|
const { valid, error } = validateAndFormatYAML(data.composeFile);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
@@ -71,6 +78,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Compose config Updated");
|
toast.success("Compose config Updated");
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
refetch();
|
refetch();
|
||||||
await utils.compose.getConvertedCompose.invalidate({
|
await utils.compose.getConvertedCompose.invalidate({
|
||||||
composeId,
|
composeId,
|
||||||
@@ -99,6 +107,19 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex flex-col gap-4 ">
|
<div className="w-full flex flex-col gap-4 ">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-medium">Compose File</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Configure your Docker Compose file for this service.
|
||||||
|
{hasUnsavedChanges && (
|
||||||
|
<span className="text-yellow-500 ml-2">
|
||||||
|
(You have unsaved changes)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
id="hook-form-save-compose-file"
|
id="hook-form-save-compose-file"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
DatabaseZap,
|
DatabaseZap,
|
||||||
Info,
|
|
||||||
PenBoxIcon,
|
PenBoxIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
@@ -62,7 +61,7 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { commonCronExpressions } from "../../application/schedules/handle-schedules";
|
import { ScheduleFormField } from "../../application/schedules/handle-schedules";
|
||||||
|
|
||||||
type CacheType = "cache" | "fetch";
|
type CacheType = "cache" | "fetch";
|
||||||
|
|
||||||
@@ -579,66 +578,9 @@ export const HandleBackup = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
<ScheduleFormField name="schedule" formControl={form.control} />
|
||||||
name="schedule"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel className="flex items-center gap-2">
|
|
||||||
Schedule
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>
|
|
||||||
Cron expression format: minute hour day month
|
|
||||||
weekday
|
|
||||||
</p>
|
|
||||||
<p>Example: 0 0 * * * (daily at midnight)</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</FormLabel>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) => {
|
|
||||||
field.onChange(value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a predefined schedule" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{commonCronExpressions.map((expr) => (
|
|
||||||
<SelectItem key={expr.value} value={expr.value}>
|
|
||||||
{expr.label} ({expr.value})
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<div className="relative">
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder="Custom cron expression (e.g., 0 0 * * *)"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FormDescription>
|
|
||||||
Choose a predefined schedule or enter a custom cron
|
|
||||||
expression
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="prefix"
|
name="prefix"
|
||||||
|
|||||||
@@ -63,13 +63,20 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
|
|
||||||
// API mutations
|
// Get current user's permissions
|
||||||
const { data: environment } = api.environment.one.useQuery(
|
const { data: currentUser } = api.user.get.useQuery();
|
||||||
{ environmentId: currentEnvironmentId || "" },
|
|
||||||
{
|
// Check if user can create environments
|
||||||
enabled: !!currentEnvironmentId,
|
const canCreateEnvironments =
|
||||||
},
|
currentUser?.role === "owner" ||
|
||||||
);
|
currentUser?.role === "admin" ||
|
||||||
|
currentUser?.canCreateEnvironments === true;
|
||||||
|
|
||||||
|
// Check if user can delete environments
|
||||||
|
const canDeleteEnvironments =
|
||||||
|
currentUser?.role === "owner" ||
|
||||||
|
currentUser?.role === "admin" ||
|
||||||
|
currentUser?.canDeleteEnvironments === true;
|
||||||
|
|
||||||
const haveServices =
|
const haveServices =
|
||||||
selectedEnvironment &&
|
selectedEnvironment &&
|
||||||
@@ -267,17 +274,19 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
<PencilIcon className="h-3 w-3" />
|
<PencilIcon className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
{canDeleteEnvironments && (
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
className="h-6 w-6 p-0 text-red-600 hover:text-red-700"
|
size="sm"
|
||||||
onClick={(e) => {
|
className="h-6 w-6 p-0 text-red-600 hover:text-red-700"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
openDeleteDialog(environment);
|
e.stopPropagation();
|
||||||
}}
|
openDeleteDialog(environment);
|
||||||
>
|
}}
|
||||||
<TrashIcon className="h-3 w-3" />
|
>
|
||||||
</Button>
|
<TrashIcon className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -285,13 +294,15 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
{canCreateEnvironments && (
|
||||||
className="cursor-pointer"
|
<DropdownMenuItem
|
||||||
onClick={() => setIsCreateDialogOpen(true)}
|
className="cursor-pointer"
|
||||||
>
|
onClick={() => setIsCreateDialogOpen(true)}
|
||||||
<PlusIcon className="h-4 w-4 mr-2" />
|
>
|
||||||
Create Environment
|
<PlusIcon className="h-4 w-4 mr-2" />
|
||||||
</DropdownMenuItem>
|
Create Environment
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
|
|||||||
@@ -96,8 +96,30 @@ export const ShowProjects = () => {
|
|||||||
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||||
break;
|
break;
|
||||||
case "services": {
|
case "services": {
|
||||||
const aTotalServices = a.environments.length;
|
const aTotalServices = a.environments.reduce((total, env) => {
|
||||||
const bTotalServices = b.environments.length;
|
return (
|
||||||
|
total +
|
||||||
|
(env.applications?.length || 0) +
|
||||||
|
(env.mariadb?.length || 0) +
|
||||||
|
(env.mongo?.length || 0) +
|
||||||
|
(env.mysql?.length || 0) +
|
||||||
|
(env.postgres?.length || 0) +
|
||||||
|
(env.redis?.length || 0) +
|
||||||
|
(env.compose?.length || 0)
|
||||||
|
);
|
||||||
|
}, 0);
|
||||||
|
const bTotalServices = b.environments.reduce((total, env) => {
|
||||||
|
return (
|
||||||
|
total +
|
||||||
|
(env.applications?.length || 0) +
|
||||||
|
(env.mariadb?.length || 0) +
|
||||||
|
(env.mongo?.length || 0) +
|
||||||
|
(env.mysql?.length || 0) +
|
||||||
|
(env.postgres?.length || 0) +
|
||||||
|
(env.redis?.length || 0) +
|
||||||
|
(env.compose?.length || 0)
|
||||||
|
);
|
||||||
|
}, 0);
|
||||||
comparison = aTotalServices - bTotalServices;
|
comparison = aTotalServices - bTotalServices;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { toast } from "sonner";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
DiscordIcon,
|
DiscordIcon,
|
||||||
|
GotifyIcon,
|
||||||
|
NtfyIcon,
|
||||||
SlackIcon,
|
SlackIcon,
|
||||||
TelegramIcon,
|
TelegramIcon,
|
||||||
} from "@/components/icons/notification-icons";
|
} from "@/components/icons/notification-icons";
|
||||||
@@ -130,11 +132,11 @@ export const notificationsMap = {
|
|||||||
label: "Email",
|
label: "Email",
|
||||||
},
|
},
|
||||||
gotify: {
|
gotify: {
|
||||||
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
icon: <GotifyIcon />,
|
||||||
label: "Gotify",
|
label: "Gotify",
|
||||||
},
|
},
|
||||||
ntfy: {
|
ntfy: {
|
||||||
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
icon: <NtfyIcon />,
|
||||||
label: "ntfy",
|
label: "ntfy",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Bell, Loader2, Mail, MessageCircleMore, Trash2 } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
DiscordIcon,
|
DiscordIcon,
|
||||||
|
GotifyIcon,
|
||||||
|
NtfyIcon,
|
||||||
SlackIcon,
|
SlackIcon,
|
||||||
TelegramIcon,
|
TelegramIcon,
|
||||||
} from "@/components/icons/notification-icons";
|
} from "@/components/icons/notification-icons";
|
||||||
@@ -85,12 +87,12 @@ export const ShowNotifications = () => {
|
|||||||
)}
|
)}
|
||||||
{notification.notificationType === "gotify" && (
|
{notification.notificationType === "gotify" && (
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
<GotifyIcon className="size-6" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "ntfy" && (
|
{notification.notificationType === "ntfy" && (
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
<NtfyIcon className="size-6" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -257,8 +257,16 @@ export const ProfileForm = () => {
|
|||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
field.onChange(e);
|
field.onChange(e);
|
||||||
}}
|
}}
|
||||||
defaultValue={field.value}
|
defaultValue={
|
||||||
value={field.value}
|
field.value?.startsWith("data:")
|
||||||
|
? "upload"
|
||||||
|
: field.value
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
field.value?.startsWith("data:")
|
||||||
|
? "upload"
|
||||||
|
: field.value
|
||||||
|
}
|
||||||
className="flex flex-row flex-wrap gap-2 max-xl:justify-center"
|
className="flex flex-row flex-wrap gap-2 max-xl:justify-center"
|
||||||
>
|
>
|
||||||
<FormItem key="no-avatar">
|
<FormItem key="no-avatar">
|
||||||
@@ -279,6 +287,71 @@ export const ProfileForm = () => {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem key="custom-upload">
|
||||||
|
<FormLabel className="[&:has([data-state=checked])>.upload-avatar]:border-primary [&:has([data-state=checked])>.upload-avatar]:border-1 [&:has([data-state=checked])>.upload-avatar]:p-px cursor-pointer">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem
|
||||||
|
value="upload"
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div
|
||||||
|
className="upload-avatar h-12 w-12 rounded-full border border-dashed border-muted-foreground hover:border-primary transition-colors flex items-center justify-center bg-muted/50 hover:bg-muted overflow-hidden"
|
||||||
|
onClick={() =>
|
||||||
|
document
|
||||||
|
.getElementById("avatar-upload")
|
||||||
|
?.click()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{field.value?.startsWith("data:") ? (
|
||||||
|
<img
|
||||||
|
src={field.value}
|
||||||
|
alt="Custom avatar"
|
||||||
|
className="h-full w-full object-cover rounded-full"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
className="h-5 w-5 text-muted-foreground"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 4v16m8-8H4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="avatar-upload"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
className="hidden"
|
||||||
|
onChange={async (e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
// max file size 2mb
|
||||||
|
if (file.size > 2 * 1024 * 1024) {
|
||||||
|
toast.error(
|
||||||
|
"Image size must be less than 2MB",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const result = event.target
|
||||||
|
?.result as string;
|
||||||
|
field.onChange(result);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
{availableAvatars.map((image) => (
|
{availableAvatars.map((image) => (
|
||||||
<FormItem key={image}>
|
<FormItem key={image}>
|
||||||
<FormLabel className="[&:has([data-state=checked])>img]:border-primary [&:has([data-state=checked])>img]:border-1 [&:has([data-state=checked])>img]:p-px cursor-pointer">
|
<FormLabel className="[&:has([data-state=checked])>img]:border-primary [&:has([data-state=checked])>img]:border-1 [&:has([data-state=checked])>img]:p-px cursor-pointer">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { findEnvironmentById } from "@dokploy/server/index";
|
import type { findEnvironmentById } from "@dokploy/server/index";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -161,11 +161,13 @@ const addPermissions = z.object({
|
|||||||
canCreateServices: z.boolean().optional().default(false),
|
canCreateServices: z.boolean().optional().default(false),
|
||||||
canDeleteProjects: z.boolean().optional().default(false),
|
canDeleteProjects: z.boolean().optional().default(false),
|
||||||
canDeleteServices: z.boolean().optional().default(false),
|
canDeleteServices: z.boolean().optional().default(false),
|
||||||
|
canDeleteEnvironments: z.boolean().optional().default(false),
|
||||||
canAccessToTraefikFiles: z.boolean().optional().default(false),
|
canAccessToTraefikFiles: z.boolean().optional().default(false),
|
||||||
canAccessToDocker: z.boolean().optional().default(false),
|
canAccessToDocker: z.boolean().optional().default(false),
|
||||||
canAccessToAPI: z.boolean().optional().default(false),
|
canAccessToAPI: z.boolean().optional().default(false),
|
||||||
canAccessToSSHKeys: z.boolean().optional().default(false),
|
canAccessToSSHKeys: z.boolean().optional().default(false),
|
||||||
canAccessToGitProviders: z.boolean().optional().default(false),
|
canAccessToGitProviders: z.boolean().optional().default(false),
|
||||||
|
canCreateEnvironments: z.boolean().optional().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddPermissions = z.infer<typeof addPermissions>;
|
type AddPermissions = z.infer<typeof addPermissions>;
|
||||||
@@ -175,6 +177,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AddUserPermissions = ({ userId }: Props) => {
|
export const AddUserPermissions = ({ userId }: Props) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const { data: projects } = api.project.all.useQuery();
|
const { data: projects } = api.project.all.useQuery();
|
||||||
|
|
||||||
const { data, refetch } = api.user.one.useQuery(
|
const { data, refetch } = api.user.one.useQuery(
|
||||||
@@ -192,13 +195,25 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
const form = useForm<AddPermissions>({
|
const form = useForm<AddPermissions>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
accessedProjects: [],
|
accessedProjects: [],
|
||||||
|
accessedEnvironments: [],
|
||||||
accessedServices: [],
|
accessedServices: [],
|
||||||
|
canDeleteEnvironments: false,
|
||||||
|
canCreateProjects: false,
|
||||||
|
canCreateServices: false,
|
||||||
|
canDeleteProjects: false,
|
||||||
|
canDeleteServices: false,
|
||||||
|
canAccessToTraefikFiles: false,
|
||||||
|
canAccessToDocker: false,
|
||||||
|
canAccessToAPI: false,
|
||||||
|
canAccessToSSHKeys: false,
|
||||||
|
canAccessToGitProviders: false,
|
||||||
|
canCreateEnvironments: false,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addPermissions),
|
resolver: zodResolver(addPermissions),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data && isOpen) {
|
||||||
form.reset({
|
form.reset({
|
||||||
accessedProjects: data.accessedProjects || [],
|
accessedProjects: data.accessedProjects || [],
|
||||||
accessedEnvironments: data.accessedEnvironments || [],
|
accessedEnvironments: data.accessedEnvironments || [],
|
||||||
@@ -207,14 +222,16 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
canCreateServices: data.canCreateServices,
|
canCreateServices: data.canCreateServices,
|
||||||
canDeleteProjects: data.canDeleteProjects,
|
canDeleteProjects: data.canDeleteProjects,
|
||||||
canDeleteServices: data.canDeleteServices,
|
canDeleteServices: data.canDeleteServices,
|
||||||
|
canDeleteEnvironments: data.canDeleteEnvironments || false,
|
||||||
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
||||||
canAccessToDocker: data.canAccessToDocker,
|
canAccessToDocker: data.canAccessToDocker,
|
||||||
canAccessToAPI: data.canAccessToAPI,
|
canAccessToAPI: data.canAccessToAPI,
|
||||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||||
canAccessToGitProviders: data.canAccessToGitProviders,
|
canAccessToGitProviders: data.canAccessToGitProviders,
|
||||||
|
canCreateEnvironments: data.canCreateEnvironments,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form, form.formState.isSubmitSuccessful, form.reset, data]);
|
}, [form, form.reset, data, isOpen]);
|
||||||
|
|
||||||
const onSubmit = async (data: AddPermissions) => {
|
const onSubmit = async (data: AddPermissions) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
@@ -223,6 +240,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
canCreateProjects: data.canCreateProjects,
|
canCreateProjects: data.canCreateProjects,
|
||||||
canDeleteServices: data.canDeleteServices,
|
canDeleteServices: data.canDeleteServices,
|
||||||
canDeleteProjects: data.canDeleteProjects,
|
canDeleteProjects: data.canDeleteProjects,
|
||||||
|
canDeleteEnvironments: data.canDeleteEnvironments,
|
||||||
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
||||||
accessedProjects: data.accessedProjects || [],
|
accessedProjects: data.accessedProjects || [],
|
||||||
accessedEnvironments: data.accessedEnvironments || [],
|
accessedEnvironments: data.accessedEnvironments || [],
|
||||||
@@ -231,17 +249,19 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
canAccessToAPI: data.canAccessToAPI,
|
canAccessToAPI: data.canAccessToAPI,
|
||||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||||
canAccessToGitProviders: data.canAccessToGitProviders,
|
canAccessToGitProviders: data.canAccessToGitProviders,
|
||||||
|
canCreateEnvironments: data.canCreateEnvironments,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Permissions updated");
|
toast.success("Permissions updated");
|
||||||
refetch();
|
refetch();
|
||||||
|
setIsOpen(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error updating the permissions");
|
toast.error("Error updating the permissions");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
@@ -343,6 +363,46 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="canCreateEnvironments"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Create Environments</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Allow the user to create environments
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="canDeleteEnvironments"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Delete Environments</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Allow the user to delete environments
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="canAccessToTraefikFiles"
|
name="canAccessToTraefikFiles"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -76,6 +77,9 @@ export const WebDomain = () => {
|
|||||||
resolver: zodResolver(addServerDomain),
|
resolver: zodResolver(addServerDomain),
|
||||||
});
|
});
|
||||||
const https = form.watch("https");
|
const https = form.watch("https");
|
||||||
|
const domain = form.watch("domain") || "";
|
||||||
|
const host = data?.user?.host || "";
|
||||||
|
const hasChanged = domain !== host;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
@@ -119,6 +123,19 @@ export const WebDomain = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2 py-6 border-t">
|
<CardContent className="space-y-2 py-6 border-t">
|
||||||
|
{/* Warning for GitHub webhook URL changes */}
|
||||||
|
{hasChanged && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="font-medium">⚠️ Important: URL Change Impact</p>
|
||||||
|
<p>
|
||||||
|
If you change the Dokploy Server URL make sure to update
|
||||||
|
your Github Apps to keep the auto-deploy working and preview
|
||||||
|
deployments working.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
|||||||
@@ -88,3 +88,121 @@ export const DiscordIcon = ({ className }: Props) => {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GotifyIcon = ({ className }: Props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 500 500"
|
||||||
|
className={cn("size-8", className)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
{`
|
||||||
|
.gotify-st0{fill:#DDCBA2;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}
|
||||||
|
.gotify-st1{fill:#71CAEE;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}
|
||||||
|
.gotify-st2{fill:#FFFFFF;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}
|
||||||
|
.gotify-st3{fill:#888E93;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}
|
||||||
|
.gotify-st4{fill:#F0F0F0;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}
|
||||||
|
.gotify-st5{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}
|
||||||
|
.gotify-st8{fill:#FFFFFF;}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
<linearGradient
|
||||||
|
id="gotify-gradient"
|
||||||
|
x1="265"
|
||||||
|
y1="280"
|
||||||
|
x2="275"
|
||||||
|
y2="302"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0" stopColor="#71CAEE" />
|
||||||
|
<stop offset="0.04" stopColor="#83CAE2" />
|
||||||
|
<stop offset="0.12" stopColor="#9FCACE" />
|
||||||
|
<stop offset="0.21" stopColor="#B6CBBE" />
|
||||||
|
<stop offset="0.31" stopColor="#C7CBB1" />
|
||||||
|
<stop offset="0.44" stopColor="#D4CBA8" />
|
||||||
|
<stop offset="0.61" stopColor="#DBCBA3" />
|
||||||
|
<stop offset="1" stopColor="#DDCBA2" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g transform="matrix(2.33,0,0,2.33,-432,-323)">
|
||||||
|
<g transform="translate(-25,26)">
|
||||||
|
<path
|
||||||
|
className="gotify-st1"
|
||||||
|
d="m258.9,119.7c-3,-0.9-6,-1.8-9,-2.7-4.6,-1.4-9.2,-2.8-14,-2.5-2.8,0.2-6.1,1.3-6.9,4-0.6,2-1.6,7.3-1.3,7.9 1.5,3.4 13.9,6.7 18.3,6.7"
|
||||||
|
/>
|
||||||
|
<path d="m392.6,177.9c-1.4,1.4-2.2,3.5-2.5,5.5-0.2,1.4-0.1,3 0.5,4.3 0.6,1.3 1.8,2.3 3.1,3 1.3,0.6 2.8,0.9 4.3,0.9 1.1,0 2.3,-0.1 3.1,-0.9 0.6,-0.7 0.8,-1.6 0.9,-2.5 0.2,-2.3-0.1,-4.7-0.9,-6.9-0.4,-1.1-0.9,-2.3-1.8,-3.1-1.7,-1.8-4.5,-2.2-6.4,-0.5-0.1,0-0.2,0.1-0.3,0.2z" />
|
||||||
|
<path
|
||||||
|
className="gotify-st2"
|
||||||
|
d="m358.5,164.2c-1,-1 0,-2.7 1,-3.7 5.8,-5.2 15.1,-4.6 21.8,-0.6 10.9,6.6 15.6,19.9 17.2,32.5 0.6,5.2 0.9,10.6-0.5,15.7-1.4,5.1-4.6,9.9-9.3,12.1-1.1,0.5-2.3,0.9-3.4,0.5-1.1,-0.4-1.9,-1.8-1.2,-2.8-9.4,-13.6-19,-26.8-20.9,-43.2-0.5,-4.1-1.8,-7.4-4.7,-10.5z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st1"
|
||||||
|
d="m220.1,133c34.6,-18 79.3,-19.6 112.2,-8.7 23.7,7.9 41.3,26.7 49.5,50 7.1,20.6 7.1,43.6 3,65.7-7.5,40.2-26.2,77.9-49,112.6-12.6,19-24.6,36-44.2,48.5-38.7,24.6-88.9,22.1-129.3,11.5-19.5,-5.1-38.4,-17.3-44.3,-37.3-3.8,-12.8-2.1,-27.6 4.6,-40 13.5,-24.8 46.2,-38.4 50.8,-67.9 1.4,-8.7-0.3,-17.3-1.6,-25.7-3.8,-23.4-5.4,-45.8 6.7,-68.7 9.5,-17.7 24.3,-31 41.7,-40z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st2"
|
||||||
|
d="m264.5,174.9c-0.5,0.5-0.9,1-1.3,1.6-9,11.6-12,27.9-9.3,42.1 1.7,9 5.9,17.9 13.2,23.4 19.3,14.6 51.5,13.5 68.4,-1.5 24.4,-21.7 13,-67.6-14,-78.8-17.6,-7.2-43.7,-1.6-57,13.2z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st2"
|
||||||
|
d="m382.1,237.1c1.4,-0.1 2.9,-0.1 4.3,0.1 0.3,0 0.7,0.1 1,0.4 0.2,0.3 0.4,0.7 0.5,1.1 1,3.9 0.5,8.2 0.1,12.4-0.1,0.9-0.2,1.8-0.6,2.6-1,2.1-3.1,2.7-4.7,2.7-0.1,0-0.2,0-0.3,-0.1-0.3,-0.2-0.3,-0.7-0.2,-1.2 0.3,-5.9-0.1,-11.9-0.1,-18z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st2"
|
||||||
|
d="m378.7,236.8c-1.4,0.4-2.5,2-2.8,4.4-0.5,4.4-0.7,8.9-0.5,13.4 0,0.9 0.1,1.9 0.5,2.4 0.2,0.3 0.5,0.4 0.8,0.4 1.6,0.3 4.1,-0.6 5.6,-1 0,0 0,-5.2-0.1,-8-0.1,-2.8-0.1,-6.1-0.2,-8.9 0,-0.6 0,-1.5 0,-2.2 0.1,-0.7-2.6,-0.7-3.3,-0.5z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st0"
|
||||||
|
d="m358.3,231.8c-0.3,2.2 0.1,4.7 1.7,7.4 2.6,4.4 7,6.1 11.9,5.8 8.9,-0.6 25.3,-5.4 27.5,-15.7 0.6,-3-0.3,-6.1-2.2,-8.5-6.2,-7.8-17.8,-5.7-25.6,-2-5.9,2.7-12.4,7-13.3,13z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st3"
|
||||||
|
d="m386.4,208.6c2.2,1.4 3.7,3.8 4,7 0.3,3.6-1.4,7.5-5,8.8-2.9,1.1-6.2,0.6-9.1,-0.4-2.9,-1-5.8,-2.8-6.8,-5.7-0.7,-2-0.3,-4.3 0.7,-6.1 1.1,-1.8 2.8,-3.2 4.7,-4.1 3.9,-1.8 8.4,-1.6 11.5,0.5z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st0"
|
||||||
|
d="m414.7,262.6c2.4,0.6 4.8,2.1 5.6,4.4 0.8,2.3 0.1,4.9-1.6,6.7-1.7,1.8-4.2,2.5-6.6,2.5-0.8,0-1.7,-0.1-2.4,-0.5-2.5,-1.1-3.5,-4-4.2,-6.6-1.8,-6.8 3.6,-7.8 9.2,-6.5z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st4"
|
||||||
|
d="m267.1,284.7c2.3,-4.5 141.3,-36.2 144.7,-31.6 3.4,4.5 15.8,88.2 9,90.4-6.8,2.3-119.8,37.3-126.6,35-6.8,-2.3-29.4,-89.3-27.1,-93.8z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st5"
|
||||||
|
d="m294.2,378.5c0,0 54.3,-74.6 59.9,-76.9 5.7,-2.3 67.3,41.3 67.3,41.3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="gotify-st4"
|
||||||
|
d="m267,287.7c0,0 86,38.8 91.6,36.6 5.7,-2.3 53.1,-71.2 53.1,-71.2"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="url(#gotify-gradient)"
|
||||||
|
d="m261.9,283.5c-0.1,4.2 4.3,7.3 8.4,7.6 4.1,0.3 8.2,-1.3 12.2,-2.6 1.4,-0.4 2.9,-0.8 4.2,-0.2 1.8,0.9 2.7,4.1 1.8,5.9-0.9,1.8-3.4,3.5-5.3,4.4-6.5,3-12.9,3.6-19.9,2-5.3,-1.2-11.3,-4.3-13,-13.5"
|
||||||
|
/>
|
||||||
|
<path d="m318.4,198.4c-2,-0.3-4.1,0.1-5.9,1.3-3.2,2.1-4.7,6.2-4.7,9.9 0,1.9 0.4,3.8 1.4,5.3 1.2,1.7 3.1,2.9 5.2,3.4 3.4,0.8 8.2,0.7 10.5,-2.5 1,-1.5 1.4,-3.3 1.5,-5.1 0.5,-5.7-1.8,-11.4-8,-12.3z" />
|
||||||
|
<path
|
||||||
|
className="gotify-st8"
|
||||||
|
d="m320.4,203.3c0.9,0.3 1.7,0.8 2.1,1.7 0.4,0.8 0.4,1.7 0.3,2.5-0.1,1-0.6,2-1.5,2.7-0.7,0.5-1.7,0.7-2.6,0.5-0.9,-0.2-1.7,-0.8-2.2,-1.6-1.1,-1.6-0.9,-4.4 0.9,-5.5 0.9,-0.4 2,-0.6 3,-0.3z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NtfyIcon = ({ className }: Props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className={cn("size-8", className)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12.597 13.693v2.156h6.205v-2.156ZM5.183 6.549v2.363l3.591 1.901 0.023 0.01 -0.023 0.009 -3.591 1.901v2.35l0.386 -0.211 5.456 -2.969V9.729ZM3.659 2.037C1.915 2.037 0.42 3.41 0.42 5.154v0.002L0.438 18.73 0 21.963l5.956 -1.583h14.806c1.744 0 3.238 -1.374 3.238 -3.118V5.154c0 -1.744 -1.493 -3.116 -3.237 -3.117h-0.001zm0 2.2h17.104c0.613 0.001 1.037 0.447 1.037 0.917v12.108c0 0.47 -0.424 0.916 -1.038 0.916H5.633l-3.026 0.915 0.031 -0.179 -0.017 -13.76c0 -0.47 0.424 -0.917 1.038 -0.917z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ import {
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
status: "running" | "error" | "done" | "idle" | undefined | null;
|
status:
|
||||||
|
| "running"
|
||||||
|
| "error"
|
||||||
|
| "done"
|
||||||
|
| "idle"
|
||||||
|
| "cancelled"
|
||||||
|
| undefined
|
||||||
|
| null;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +41,14 @@ export const StatusTooltip = ({ status, className }: Props) => {
|
|||||||
className={cn("size-3.5 rounded-full bg-green-500", className)}
|
className={cn("size-3.5 rounded-full bg-green-500", className)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{status === "cancelled" && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"size-3.5 rounded-full bg-muted-foreground",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{status === "running" && (
|
{status === "running" && (
|
||||||
<div
|
<div
|
||||||
className={cn("size-3.5 rounded-full bg-yellow-500", className)}
|
className={cn("size-3.5 rounded-full bg-yellow-500", className)}
|
||||||
@@ -46,6 +61,7 @@ export const StatusTooltip = ({ status, className }: Props) => {
|
|||||||
{status === "error" && "Error"}
|
{status === "error" && "Error"}
|
||||||
{status === "done" && "Done"}
|
{status === "done" && "Done"}
|
||||||
{status === "running" && "Running"}
|
{status === "running" && "Running"}
|
||||||
|
{status === "cancelled" && "Cancelled"}
|
||||||
</span>
|
</span>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0113_complete_rafael_vega.sql
Normal file
1
apps/dokploy/drizzle/0113_complete_rafael_vega.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TYPE "public"."deploymentStatus" ADD VALUE 'cancelled';
|
||||||
6
apps/dokploy/drizzle/0114_dry_black_tom.sql
Normal file
6
apps/dokploy/drizzle/0114_dry_black_tom.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "stopGracePeriodSwarm" bigint;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mariadb" ADD COLUMN "stopGracePeriodSwarm" bigint;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mongo" ADD COLUMN "stopGracePeriodSwarm" bigint;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mysql" ADD COLUMN "stopGracePeriodSwarm" bigint;--> statement-breakpoint
|
||||||
|
ALTER TABLE "postgres" ADD COLUMN "stopGracePeriodSwarm" bigint;--> statement-breakpoint
|
||||||
|
ALTER TABLE "redis" ADD COLUMN "stopGracePeriodSwarm" bigint;
|
||||||
1
apps/dokploy/drizzle/0115_serious_black_bird.sql
Normal file
1
apps/dokploy/drizzle/0115_serious_black_bird.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "member" ADD COLUMN "canCreateEnvironments" boolean DEFAULT false NOT NULL;
|
||||||
1
apps/dokploy/drizzle/0116_amusing_firedrake.sql
Normal file
1
apps/dokploy/drizzle/0116_amusing_firedrake.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "member" ADD COLUMN "canDeleteEnvironments" boolean DEFAULT false NOT NULL;
|
||||||
6572
apps/dokploy/drizzle/meta/0113_snapshot.json
Normal file
6572
apps/dokploy/drizzle/meta/0113_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
6608
apps/dokploy/drizzle/meta/0114_snapshot.json
Normal file
6608
apps/dokploy/drizzle/meta/0114_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
6615
apps/dokploy/drizzle/meta/0115_snapshot.json
Normal file
6615
apps/dokploy/drizzle/meta/0115_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
6622
apps/dokploy/drizzle/meta/0116_snapshot.json
Normal file
6622
apps/dokploy/drizzle/meta/0116_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -792,6 +792,34 @@
|
|||||||
"when": 1758483520214,
|
"when": 1758483520214,
|
||||||
"tag": "0112_freezing_skrulls",
|
"tag": "0112_freezing_skrulls",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 113,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1758960816504,
|
||||||
|
"tag": "0113_complete_rafael_vega",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 114,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1759643172958,
|
||||||
|
"tag": "0114_dry_black_tom",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 115,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1759644540829,
|
||||||
|
"tag": "0115_serious_black_bird",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 116,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1759645163834,
|
||||||
|
"tag": "0116_amusing_firedrake",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.25.3",
|
"version": "v0.25.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ const Service = (
|
|||||||
<TabsTrigger value="general">General</TabsTrigger>
|
<TabsTrigger value="general">General</TabsTrigger>
|
||||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||||
<TabsTrigger value="domains">Domains</TabsTrigger>
|
<TabsTrigger value="domains">Domains</TabsTrigger>
|
||||||
|
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
||||||
<TabsTrigger value="preview-deployments">
|
<TabsTrigger value="preview-deployments">
|
||||||
Preview Deployments
|
Preview Deployments
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@@ -233,7 +234,6 @@ const Service = (
|
|||||||
<TabsTrigger value="volume-backups">
|
<TabsTrigger value="volume-backups">
|
||||||
Volume Backups
|
Volume Backups
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
|
||||||
<TabsTrigger value="logs">Logs</TabsTrigger>
|
<TabsTrigger value="logs">Logs</TabsTrigger>
|
||||||
{((data?.serverId && isCloud) || !data?.server) && (
|
{((data?.serverId && isCloud) || !data?.server) && (
|
||||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
saveGitProdiver: protectedProcedure
|
saveGitProvider: protectedProcedure
|
||||||
.input(apiSaveGitProvider)
|
.input(apiSaveGitProvider)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
addNewEnvironment,
|
addNewEnvironment,
|
||||||
checkEnvironmentAccess,
|
checkEnvironmentAccess,
|
||||||
|
checkEnvironmentCreationPermission,
|
||||||
|
checkEnvironmentDeletionPermission,
|
||||||
createEnvironment,
|
createEnvironment,
|
||||||
deleteEnvironment,
|
deleteEnvironment,
|
||||||
duplicateEnvironment,
|
duplicateEnvironment,
|
||||||
@@ -54,9 +56,12 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
.input(apiCreateEnvironment)
|
.input(apiCreateEnvironment)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
// Check if user has access to the project
|
// Check if user has permission to create environments
|
||||||
// This would typically involve checking project ownership/membership
|
await checkEnvironmentCreationPermission(
|
||||||
// For now, we'll use a basic organization check
|
ctx.user.id,
|
||||||
|
input.projectId,
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
|
|
||||||
if (input.name === "production") {
|
if (input.name === "production") {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -76,6 +81,9 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
return environment;
|
return environment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof TRPCError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: `Error creating the environment: ${error instanceof Error ? error.message : error}`,
|
message: `Error creating the environment: ${error instanceof Error ? error.message : error}`,
|
||||||
@@ -187,14 +195,6 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
.input(apiRemoveEnvironment)
|
.input(apiRemoveEnvironment)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
await checkEnvironmentAccess(
|
|
||||||
ctx.user.id,
|
|
||||||
input.environmentId,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
"access",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
const environment = await findEnvironmentById(input.environmentId);
|
||||||
if (
|
if (
|
||||||
environment.project.organizationId !==
|
environment.project.organizationId !==
|
||||||
@@ -206,27 +206,33 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check environment access for members
|
// Check environment deletion permission
|
||||||
if (ctx.user.role === "member") {
|
await checkEnvironmentDeletionPermission(
|
||||||
const { accessedEnvironments } = await findMemberById(
|
ctx.user.id,
|
||||||
ctx.user.id,
|
environment.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!accessedEnvironments.includes(environment.environmentId)) {
|
// Additional check for environment access for members
|
||||||
throw new TRPCError({
|
if (ctx.user.role === "member") {
|
||||||
code: "FORBIDDEN",
|
await checkEnvironmentAccess(
|
||||||
message: "You are not allowed to delete this environment",
|
ctx.user.id,
|
||||||
});
|
input.environmentId,
|
||||||
}
|
ctx.session.activeOrganizationId,
|
||||||
|
"access",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedEnvironment = await deleteEnvironment(input.environmentId);
|
const deletedEnvironment = await deleteEnvironment(input.environmentId);
|
||||||
return deletedEnvironment;
|
return deletedEnvironment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof TRPCError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: `Error deleting the environment: ${error instanceof Error ? error.message : error}`,
|
message: `Error deleting the environment: ${error instanceof Error ? error.message : error}`,
|
||||||
|
cause: error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
addNewEnvironment,
|
||||||
addNewProject,
|
addNewProject,
|
||||||
checkProjectAccess,
|
checkProjectAccess,
|
||||||
createApplication,
|
createApplication,
|
||||||
@@ -85,6 +86,12 @@ export const projectRouter = createTRPCRouter({
|
|||||||
project.project.projectId,
|
project.project.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await addNewEnvironment(
|
||||||
|
ctx.user.id,
|
||||||
|
project?.environment?.environmentId || "",
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return project;
|
return project;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
initializeNetwork,
|
initializeNetwork,
|
||||||
initSchedules,
|
initSchedules,
|
||||||
initVolumeBackupsCronJobs,
|
initVolumeBackupsCronJobs,
|
||||||
|
initCancelDeployments,
|
||||||
sendDokployRestartNotifications,
|
sendDokployRestartNotifications,
|
||||||
setupDirectories,
|
setupDirectories,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
@@ -52,6 +53,7 @@ void app.prepare().then(async () => {
|
|||||||
await migration();
|
await migration();
|
||||||
await initCronJobs();
|
await initCronJobs();
|
||||||
await initSchedules();
|
await initSchedules();
|
||||||
|
await initCancelDeployments();
|
||||||
await initVolumeBackupsCronJobs();
|
await initVolumeBackupsCronJobs();
|
||||||
await sendDokployRestartNotifications();
|
await sendDokployRestartNotifications();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ export const member = pgTable("member", {
|
|||||||
canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
|
canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(false),
|
.default(false),
|
||||||
|
canDeleteEnvironments: boolean("canDeleteEnvironments")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
canCreateEnvironments: boolean("canCreateEnvironments")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
accessedProjects: text("accesedProjects")
|
accessedProjects: text("accesedProjects")
|
||||||
.array()
|
.array()
|
||||||
.notNull()
|
.notNull()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
|
bigint,
|
||||||
boolean,
|
boolean,
|
||||||
integer,
|
integer,
|
||||||
json,
|
json,
|
||||||
@@ -20,7 +21,6 @@ import { gitlab } from "./gitlab";
|
|||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
import { ports } from "./port";
|
import { ports } from "./port";
|
||||||
import { previewDeployments } from "./preview-deployments";
|
import { previewDeployments } from "./preview-deployments";
|
||||||
import { projects } from "./project";
|
|
||||||
import { redirects } from "./redirects";
|
import { redirects } from "./redirects";
|
||||||
import { registry } from "./registry";
|
import { registry } from "./registry";
|
||||||
import { security } from "./security";
|
import { security } from "./security";
|
||||||
@@ -164,6 +164,7 @@ export const applications = pgTable("application", {
|
|||||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||||
//
|
//
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
applicationStatus: applicationStatus("applicationStatus")
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
@@ -312,6 +313,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
previewLabels: z.array(z.string()).optional(),
|
previewLabels: z.array(z.string()).optional(),
|
||||||
cleanCache: z.boolean().optional(),
|
cleanCache: z.boolean().optional(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateApplication = createSchema.pick({
|
export const apiCreateApplication = createSchema.pick({
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const deploymentStatus = pgEnum("deploymentStatus", [
|
|||||||
"running",
|
"running",
|
||||||
"done",
|
"done",
|
||||||
"error",
|
"error",
|
||||||
|
"cancelled",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const deployments = pgTable("deployment", {
|
export const deployments = pgTable("deployment", {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -62,6 +62,7 @@ export const mariadb = pgTable("mariadb", {
|
|||||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -128,6 +129,7 @@ const createSchema = createInsertSchema(mariadb, {
|
|||||||
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
labelsSwarm: LabelsSwarmSchema.nullable(),
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMariaDB = createSchema
|
export const apiCreateMariaDB = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { boolean, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import {
|
||||||
|
bigint,
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
json,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -58,6 +65,7 @@ export const mongo = pgTable("mongo", {
|
|||||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -118,6 +126,7 @@ const createSchema = createInsertSchema(mongo, {
|
|||||||
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
labelsSwarm: LabelsSwarmSchema.nullable(),
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMongo = createSchema
|
export const apiCreateMongo = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -60,6 +60,7 @@ export const mysql = pgTable("mysql", {
|
|||||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -125,6 +126,7 @@ const createSchema = createInsertSchema(mysql, {
|
|||||||
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
labelsSwarm: LabelsSwarmSchema.nullable(),
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMySql = createSchema
|
export const apiCreateMySql = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -60,6 +60,7 @@ export const postgres = pgTable("postgres", {
|
|||||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -118,6 +119,7 @@ const createSchema = createInsertSchema(postgres, {
|
|||||||
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
labelsSwarm: LabelsSwarmSchema.nullable(),
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreatePostgres = createSchema
|
export const apiCreatePostgres = createSchema
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -60,6 +60,7 @@ export const redis = pgTable("redis", {
|
|||||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
|
|
||||||
environmentId: text("environmentId")
|
environmentId: text("environmentId")
|
||||||
@@ -108,6 +109,7 @@ const createSchema = createInsertSchema(redis, {
|
|||||||
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
labelsSwarm: LabelsSwarmSchema.nullable(),
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateRedis = createSchema
|
export const apiCreateRedis = createSchema
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ export const apiAssignPermissions = createSchema
|
|||||||
canAccessToAPI: z.boolean().optional(),
|
canAccessToAPI: z.boolean().optional(),
|
||||||
canAccessToSSHKeys: z.boolean().optional(),
|
canAccessToSSHKeys: z.boolean().optional(),
|
||||||
canAccessToGitProviders: z.boolean().optional(),
|
canAccessToGitProviders: z.boolean().optional(),
|
||||||
|
canDeleteEnvironments: z.boolean().optional(),
|
||||||
|
canCreateEnvironments: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export * from "./utils/backups/postgres";
|
|||||||
export * from "./utils/backups/utils";
|
export * from "./utils/backups/utils";
|
||||||
export * from "./utils/backups/web-server";
|
export * from "./utils/backups/web-server";
|
||||||
export * from "./utils/builders/compose";
|
export * from "./utils/builders/compose";
|
||||||
|
export * from "./utils/startup/cancell-deployments";
|
||||||
export * from "./utils/builders/docker-file";
|
export * from "./utils/builders/docker-file";
|
||||||
export * from "./utils/builders/drop";
|
export * from "./utils/builders/drop";
|
||||||
export * from "./utils/builders/heroku";
|
export * from "./utils/builders/heroku";
|
||||||
|
|||||||
@@ -603,6 +603,21 @@ const BUNNY_CDN_IPS = new Set([
|
|||||||
"89.187.184.176",
|
"89.187.184.176",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Arvancloud IP ranges
|
||||||
|
// https://www.arvancloud.ir/fa/ips.txt
|
||||||
|
const ARVANCLOUD_IP_RANGES = [
|
||||||
|
"185.143.232.0/22",
|
||||||
|
"188.229.116.16/29",
|
||||||
|
"94.101.182.0/27",
|
||||||
|
"2.144.3.128/28",
|
||||||
|
"89.45.48.64/28",
|
||||||
|
"37.32.16.0/27",
|
||||||
|
"37.32.17.0/27",
|
||||||
|
"37.32.18.0/27",
|
||||||
|
"37.32.19.0/27",
|
||||||
|
"185.215.232.0/22",
|
||||||
|
];
|
||||||
|
|
||||||
const CDN_PROVIDERS: CDNProvider[] = [
|
const CDN_PROVIDERS: CDNProvider[] = [
|
||||||
{
|
{
|
||||||
name: "cloudflare",
|
name: "cloudflare",
|
||||||
@@ -627,6 +642,14 @@ const CDN_PROVIDERS: CDNProvider[] = [
|
|||||||
warningMessage:
|
warningMessage:
|
||||||
"Domain is behind Fastly - actual IP is masked by CDN proxy",
|
"Domain is behind Fastly - actual IP is masked by CDN proxy",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "arvancloud",
|
||||||
|
displayName: "Arvancloud",
|
||||||
|
checkIp: (ip: string) =>
|
||||||
|
ARVANCLOUD_IP_RANGES.some((range) => isIPInCIDR(ip, range)),
|
||||||
|
warningMessage:
|
||||||
|
"Domain is behind Arvancloud - actual IP is masked by CDN proxy",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const detectCDNProvider = (ip: string): CDNProvider | null => {
|
export const detectCDNProvider = (ip: string): CDNProvider | null => {
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ export const deployCompose = async ({
|
|||||||
|
|
||||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
||||||
compose.environment.projectId
|
compose.environment.projectId
|
||||||
}/services/compose/${compose.composeId}?tab=deployments`;
|
}/environment/${compose.environmentId}/services/compose/${compose.composeId}?tab=deployments`;
|
||||||
const deployment = await createDeploymentCompose({
|
const deployment = await createDeploymentCompose({
|
||||||
composeId: composeId,
|
composeId: composeId,
|
||||||
title: titleLog,
|
title: titleLog,
|
||||||
@@ -335,7 +335,7 @@ export const deployRemoteCompose = async ({
|
|||||||
|
|
||||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
||||||
compose.environment.projectId
|
compose.environment.projectId
|
||||||
}/services/compose/${compose.composeId}?tab=deployments`;
|
}/environment/${compose.environmentId}/services/compose/${compose.composeId}?tab=deployments`;
|
||||||
const deployment = await createDeploymentCompose({
|
const deployment = await createDeploymentCompose({
|
||||||
composeId: composeId,
|
composeId: composeId,
|
||||||
title: titleLog,
|
title: titleLog,
|
||||||
|
|||||||
@@ -163,6 +163,24 @@ export const canPerformAccessEnvironment = async (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const canPerformDeleteEnvironment = async (
|
||||||
|
userId: string,
|
||||||
|
projectId: string,
|
||||||
|
organizationId: string,
|
||||||
|
) => {
|
||||||
|
const { accessedProjects, canDeleteEnvironments } = await findMemberById(
|
||||||
|
userId,
|
||||||
|
organizationId,
|
||||||
|
);
|
||||||
|
const haveAccessToProject = accessedProjects.includes(projectId);
|
||||||
|
|
||||||
|
if (canDeleteEnvironments && haveAccessToProject) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const canAccessToTraefikFiles = async (
|
export const canAccessToTraefikFiles = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
@@ -240,6 +258,42 @@ export const checkEnvironmentAccess = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkEnvironmentDeletionPermission = async (
|
||||||
|
userId: string,
|
||||||
|
projectId: string,
|
||||||
|
organizationId: string,
|
||||||
|
) => {
|
||||||
|
const member = await findMemberById(userId, organizationId);
|
||||||
|
|
||||||
|
if (!member) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "User not found in organization",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.role === "owner" || member.role === "admin") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!member.canDeleteEnvironments) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You don't have permission to delete environments",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasProjectAccess = member.accessedProjects.includes(projectId);
|
||||||
|
if (!hasProjectAccess) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You don't have access to this project",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
export const checkProjectAccess = async (
|
export const checkProjectAccess = async (
|
||||||
authId: string,
|
authId: string,
|
||||||
action: "create" | "delete" | "access",
|
action: "create" | "delete" | "access",
|
||||||
@@ -272,6 +326,46 @@ export const checkProjectAccess = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkEnvironmentCreationPermission = async (
|
||||||
|
userId: string,
|
||||||
|
projectId: string,
|
||||||
|
organizationId: string,
|
||||||
|
) => {
|
||||||
|
// Get user's member record
|
||||||
|
const member = await findMemberById(userId, organizationId);
|
||||||
|
|
||||||
|
if (!member) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "User not found in organization",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owners and admins can always create environments
|
||||||
|
if (member.role === "owner" || member.role === "admin") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has canCreateEnvironments permission
|
||||||
|
if (!member.canCreateEnvironments) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You don't have permission to create environments",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has access to the project
|
||||||
|
const hasProjectAccess = member.accessedProjects.includes(projectId);
|
||||||
|
if (!hasProjectAccess) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You don't have access to this project",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
export const findMemberById = async (
|
export const findMemberById = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export const mechanizeDockerContainer = async (
|
|||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
Networks,
|
||||||
|
StopGracePeriod,
|
||||||
} = generateConfigContainer(application);
|
} = generateConfigContainer(application);
|
||||||
|
|
||||||
const bindsMount = generateBindMounts(mounts);
|
const bindsMount = generateBindMounts(mounts);
|
||||||
@@ -191,6 +192,8 @@ export const mechanizeDockerContainer = async (
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
|
...(StopGracePeriod !== undefined &&
|
||||||
|
StopGracePeriod !== null && { StopGracePeriod }),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
Networks,
|
||||||
|
StopGracePeriod,
|
||||||
} = generateConfigContainer(mariadb);
|
} = generateConfigContainer(mariadb);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
@@ -102,6 +103,8 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
|
...(StopGracePeriod !== undefined &&
|
||||||
|
StopGracePeriod !== null && { StopGracePeriod }),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const service = docker.getService(appName);
|
const service = docker.getService(appName);
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
Networks,
|
||||||
|
StopGracePeriod,
|
||||||
} = generateConfigContainer(mongo);
|
} = generateConfigContainer(mongo);
|
||||||
|
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
@@ -155,6 +156,8 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
|
...(StopGracePeriod !== undefined &&
|
||||||
|
StopGracePeriod !== null && { StopGracePeriod }),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
Networks,
|
||||||
|
StopGracePeriod,
|
||||||
} = generateConfigContainer(mysql);
|
} = generateConfigContainer(mysql);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
@@ -108,6 +109,8 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
|
...(StopGracePeriod !== undefined &&
|
||||||
|
StopGracePeriod !== null && { StopGracePeriod }),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const service = docker.getService(appName);
|
const service = docker.getService(appName);
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
Networks,
|
||||||
|
StopGracePeriod,
|
||||||
} = generateConfigContainer(postgres);
|
} = generateConfigContainer(postgres);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
@@ -101,6 +102,8 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
|
...(StopGracePeriod !== undefined &&
|
||||||
|
StopGracePeriod !== null && { StopGracePeriod }),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const service = docker.getService(appName);
|
const service = docker.getService(appName);
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
Networks,
|
Networks,
|
||||||
|
StopGracePeriod,
|
||||||
} = generateConfigContainer(redis);
|
} = generateConfigContainer(redis);
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
@@ -98,6 +99,8 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
|
...(StopGracePeriod !== undefined &&
|
||||||
|
StopGracePeriod !== null && { StopGracePeriod }),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -394,8 +394,14 @@ export const generateConfigContainer = (
|
|||||||
replicas,
|
replicas,
|
||||||
mounts,
|
mounts,
|
||||||
networkSwarm,
|
networkSwarm,
|
||||||
|
stopGracePeriodSwarm,
|
||||||
} = application;
|
} = application;
|
||||||
|
|
||||||
|
const sanitizedStopGracePeriodSwarm =
|
||||||
|
typeof stopGracePeriodSwarm === "bigint"
|
||||||
|
? Number(stopGracePeriodSwarm)
|
||||||
|
: stopGracePeriodSwarm;
|
||||||
|
|
||||||
const haveMounts = mounts && mounts.length > 0;
|
const haveMounts = mounts && mounts.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -444,6 +450,10 @@ export const generateConfigContainer = (
|
|||||||
Order: "start-first",
|
Order: "start-first",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
...(sanitizedStopGracePeriodSwarm !== null &&
|
||||||
|
sanitizedStopGracePeriodSwarm !== undefined && {
|
||||||
|
StopGracePeriod: sanitizedStopGracePeriodSwarm,
|
||||||
|
}),
|
||||||
...(networkSwarm
|
...(networkSwarm
|
||||||
? {
|
? {
|
||||||
Networks: networkSwarm,
|
Networks: networkSwarm,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export const sendEmailNotification = async (
|
|||||||
to: toAddresses.join(", "),
|
to: toAddresses.join(", "),
|
||||||
subject,
|
subject,
|
||||||
html: htmlContent,
|
html: htmlContent,
|
||||||
|
textEncoding: "base64",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|||||||
@@ -31,29 +31,51 @@ export const getBitbucketCloneUrl = (
|
|||||||
apiToken?: string | null;
|
apiToken?: string | null;
|
||||||
bitbucketUsername?: string | null;
|
bitbucketUsername?: string | null;
|
||||||
appPassword?: string | null;
|
appPassword?: string | null;
|
||||||
|
bitbucketEmail?: string | null;
|
||||||
|
bitbucketWorkspaceName?: string | null;
|
||||||
} | null,
|
} | null,
|
||||||
repoClone: string,
|
repoClone: string,
|
||||||
) => {
|
) => {
|
||||||
if (!bitbucketProvider) {
|
if (!bitbucketProvider) {
|
||||||
throw new Error("Bitbucket provider is required");
|
throw new Error("Bitbucket provider is required");
|
||||||
}
|
}
|
||||||
return bitbucketProvider.apiToken
|
|
||||||
? `https://x-token-auth:${bitbucketProvider.apiToken}@${repoClone}`
|
if (bitbucketProvider.apiToken) {
|
||||||
: `https://${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}@${repoClone}`;
|
return `https://x-bitbucket-api-token-auth:${bitbucketProvider.apiToken}@${repoClone}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For app passwords, use username:app_password format
|
||||||
|
if (!bitbucketProvider.bitbucketUsername || !bitbucketProvider.appPassword) {
|
||||||
|
throw new Error(
|
||||||
|
"Username and app password are required when not using API token",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return `https://${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}@${repoClone}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBitbucketHeaders = (bitbucketProvider: Bitbucket) => {
|
export const getBitbucketHeaders = (bitbucketProvider: Bitbucket) => {
|
||||||
if (bitbucketProvider.apiToken) {
|
if (bitbucketProvider.apiToken) {
|
||||||
// For API tokens, use HTTP Basic auth with email and token
|
// According to Bitbucket official docs, for API calls with API tokens:
|
||||||
// According to Bitbucket docs: email:token for API calls
|
// "You will need both your Atlassian account email and an API token"
|
||||||
const email =
|
// Use: {atlassian_account_email}:{api_token}
|
||||||
bitbucketProvider.bitbucketEmail || bitbucketProvider.bitbucketUsername;
|
|
||||||
|
if (!bitbucketProvider.bitbucketEmail) {
|
||||||
|
throw new Error(
|
||||||
|
"Atlassian account email is required when using API token for API calls",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Authorization: `Basic ${Buffer.from(`${email}:${bitbucketProvider.apiToken}`).toString("base64")}`,
|
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketEmail}:${bitbucketProvider.apiToken}`).toString("base64")}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// For app passwords, use HTTP Basic auth with username and app password
|
// For app passwords, use HTTP Basic auth with username and app password
|
||||||
|
if (!bitbucketProvider.bitbucketUsername || !bitbucketProvider.appPassword) {
|
||||||
|
throw new Error(
|
||||||
|
"Username and app password are required when not using API token",
|
||||||
|
);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`,
|
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -99,6 +99,19 @@ export const refreshGiteaToken = async (giteaProviderId: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildGiteaCloneUrl = (
|
||||||
|
giteaUrl: string,
|
||||||
|
accessToken: string,
|
||||||
|
owner: string,
|
||||||
|
repository: string,
|
||||||
|
) => {
|
||||||
|
const protocol = giteaUrl.startsWith("http://") ? "http" : "https";
|
||||||
|
const baseUrl = giteaUrl.replace(/^https?:\/\//, "");
|
||||||
|
const repoClone = `${owner}/${repository}.git`;
|
||||||
|
const cloneUrl = `${protocol}://oauth2:${accessToken}@${baseUrl}/${repoClone}`;
|
||||||
|
return cloneUrl;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApplicationWithGitea = InferResultType<
|
export type ApplicationWithGitea = InferResultType<
|
||||||
"applications",
|
"applications",
|
||||||
{ gitea: true }
|
{ gitea: true }
|
||||||
@@ -148,9 +161,13 @@ export const getGiteaCloneCommand = async (
|
|||||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
|
|
||||||
const baseUrl = gitea?.giteaUrl.replace(/^https?:\/\//, "");
|
|
||||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
||||||
const cloneUrl = `https://oauth2:${gitea?.accessToken}@${baseUrl}/${repoClone}`;
|
const cloneUrl = buildGiteaCloneUrl(
|
||||||
|
gitea?.giteaUrl!,
|
||||||
|
gitea?.accessToken!,
|
||||||
|
giteaOwner!,
|
||||||
|
giteaRepository!,
|
||||||
|
);
|
||||||
|
|
||||||
const cloneCommand = `
|
const cloneCommand = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
@@ -205,8 +222,12 @@ export const cloneGiteaRepository = async (
|
|||||||
await recreateDirectory(outputPath);
|
await recreateDirectory(outputPath);
|
||||||
|
|
||||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
||||||
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
|
const cloneUrl = buildGiteaCloneUrl(
|
||||||
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
|
giteaProvider.giteaUrl,
|
||||||
|
giteaProvider.accessToken!,
|
||||||
|
giteaOwner!,
|
||||||
|
giteaRepository!,
|
||||||
|
);
|
||||||
|
|
||||||
writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`);
|
writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`);
|
||||||
|
|
||||||
@@ -269,9 +290,12 @@ export const cloneRawGiteaRepository = async (entity: Compose) => {
|
|||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
await recreateDirectory(outputPath);
|
await recreateDirectory(outputPath);
|
||||||
|
|
||||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
const cloneUrl = buildGiteaCloneUrl(
|
||||||
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
|
giteaProvider.giteaUrl,
|
||||||
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
|
giteaProvider.accessToken!,
|
||||||
|
giteaOwner!,
|
||||||
|
giteaRepository!,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await spawnAsync("git", [
|
await spawnAsync("git", [
|
||||||
@@ -317,9 +341,13 @@ export const cloneRawGiteaRepositoryRemote = async (compose: Compose) => {
|
|||||||
const giteaProvider = await findGiteaById(giteaId);
|
const giteaProvider = await findGiteaById(giteaId);
|
||||||
const basePath = COMPOSE_PATH;
|
const basePath = COMPOSE_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
const cloneUrl = buildGiteaCloneUrl(
|
||||||
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
|
giteaProvider.giteaUrl,
|
||||||
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
|
giteaProvider.accessToken!,
|
||||||
|
giteaOwner!,
|
||||||
|
giteaRepository!,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const command = `
|
const command = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
|
|||||||
21
packages/server/src/utils/startup/cancell-deployments.ts
Normal file
21
packages/server/src/utils/startup/cancell-deployments.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { deployments } from "@dokploy/server/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../../db/index";
|
||||||
|
|
||||||
|
export const initCancelDeployments = async () => {
|
||||||
|
try {
|
||||||
|
console.log("Setting up cancel deployments....");
|
||||||
|
|
||||||
|
const result = await db
|
||||||
|
.update(deployments)
|
||||||
|
.set({
|
||||||
|
status: "cancelled",
|
||||||
|
})
|
||||||
|
.where(eq(deployments.status, "running"))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
console.log(`Cancelled ${result.length} deployments`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
219
pnpm-lock.yaml
generated
219
pnpm-lock.yaml
generated
@@ -306,10 +306,10 @@ importers:
|
|||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.39.3
|
specifier: ^0.39.3
|
||||||
version: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4)
|
version: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4)
|
||||||
drizzle-zod:
|
drizzle-zod:
|
||||||
specifier: 0.5.1
|
specifier: 0.5.1
|
||||||
version: 0.5.1(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4))(zod@3.25.32)
|
version: 0.5.1(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4))(zod@3.25.32)
|
||||||
fancy-ansi:
|
fancy-ansi:
|
||||||
specifier: ^0.1.3
|
specifier: ^0.1.3
|
||||||
version: 0.1.3
|
version: 0.1.3
|
||||||
@@ -550,7 +550,7 @@ importers:
|
|||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.39.3
|
specifier: ^0.39.3
|
||||||
version: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4)
|
version: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4)
|
||||||
hono:
|
hono:
|
||||||
specifier: ^4.7.10
|
specifier: ^4.7.10
|
||||||
version: 4.7.10
|
version: 4.7.10
|
||||||
@@ -668,13 +668,13 @@ importers:
|
|||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
drizzle-dbml-generator:
|
drizzle-dbml-generator:
|
||||||
specifier: 0.10.0
|
specifier: 0.10.0
|
||||||
version: 0.10.0(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4))
|
version: 0.10.0(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4))
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.39.3
|
specifier: ^0.39.3
|
||||||
version: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4)
|
version: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4)
|
||||||
drizzle-zod:
|
drizzle-zod:
|
||||||
specifier: 0.5.1
|
specifier: 0.5.1
|
||||||
version: 0.5.1(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4))(zod@3.25.32)
|
version: 0.5.1(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4))(zod@3.25.32)
|
||||||
hi-base32:
|
hi-base32:
|
||||||
specifier: ^0.5.1
|
specifier: ^0.5.1
|
||||||
version: 0.5.1
|
version: 0.5.1
|
||||||
@@ -904,6 +904,9 @@ packages:
|
|||||||
'@better-auth/utils@0.2.5':
|
'@better-auth/utils@0.2.5':
|
||||||
resolution: {integrity: sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==}
|
resolution: {integrity: sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==}
|
||||||
|
|
||||||
|
'@better-auth/utils@0.3.0':
|
||||||
|
resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==}
|
||||||
|
|
||||||
'@better-fetch/fetch@1.1.18':
|
'@better-fetch/fetch@1.1.18':
|
||||||
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
|
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
|
||||||
|
|
||||||
@@ -1987,10 +1990,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
|
resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
|
||||||
engines: {node: ^14.21.3 || >=16}
|
engines: {node: ^14.21.3 || >=16}
|
||||||
|
|
||||||
'@noble/hashes@1.8.0':
|
|
||||||
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
|
|
||||||
engines: {node: ^14.21.3 || >=16}
|
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -2629,17 +2628,38 @@ packages:
|
|||||||
'@peculiar/asn1-android@2.3.16':
|
'@peculiar/asn1-android@2.3.16':
|
||||||
resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==}
|
resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==}
|
||||||
|
|
||||||
'@peculiar/asn1-ecc@2.3.15':
|
'@peculiar/asn1-cms@2.5.0':
|
||||||
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
|
resolution: {integrity: sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A==}
|
||||||
|
|
||||||
'@peculiar/asn1-rsa@2.3.15':
|
'@peculiar/asn1-csr@2.5.0':
|
||||||
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
|
resolution: {integrity: sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ==}
|
||||||
|
|
||||||
'@peculiar/asn1-schema@2.3.15':
|
'@peculiar/asn1-ecc@2.5.0':
|
||||||
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
|
resolution: {integrity: sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==}
|
||||||
|
|
||||||
'@peculiar/asn1-x509@2.3.15':
|
'@peculiar/asn1-pfx@2.5.0':
|
||||||
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
|
resolution: {integrity: sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs8@2.5.0':
|
||||||
|
resolution: {integrity: sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs9@2.5.0':
|
||||||
|
resolution: {integrity: sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-rsa@2.5.0':
|
||||||
|
resolution: {integrity: sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-schema@2.5.0':
|
||||||
|
resolution: {integrity: sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-x509-attr@2.5.0':
|
||||||
|
resolution: {integrity: sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-x509@2.5.0':
|
||||||
|
resolution: {integrity: sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==}
|
||||||
|
|
||||||
|
'@peculiar/x509@1.14.0':
|
||||||
|
resolution: {integrity: sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==}
|
||||||
|
|
||||||
'@petamoriken/float16@3.9.2':
|
'@petamoriken/float16@3.9.2':
|
||||||
resolution: {integrity: sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==}
|
resolution: {integrity: sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==}
|
||||||
@@ -3673,11 +3693,11 @@ packages:
|
|||||||
'@selderee/plugin-htmlparser2@0.11.0':
|
'@selderee/plugin-htmlparser2@0.11.0':
|
||||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||||
|
|
||||||
'@simplewebauthn/browser@13.1.0':
|
'@simplewebauthn/browser@13.2.2':
|
||||||
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
|
resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
|
||||||
|
|
||||||
'@simplewebauthn/server@13.1.1':
|
'@simplewebauthn/server@13.2.2':
|
||||||
resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
|
resolution: {integrity: sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@sinclair/typebox@0.27.8':
|
'@sinclair/typebox@0.27.8':
|
||||||
@@ -4306,8 +4326,8 @@ packages:
|
|||||||
better-auth@1.2.8-beta.7:
|
better-auth@1.2.8-beta.7:
|
||||||
resolution: {integrity: sha512-gVApvvhnPVqMCYYLMhxUfbTi5fJYfp9rcsoJSjjTOMV+CIc7KVlYN6Qo8E7ju1JeRU5ae1Wl1NdXrolRJHjmaQ==}
|
resolution: {integrity: sha512-gVApvvhnPVqMCYYLMhxUfbTi5fJYfp9rcsoJSjjTOMV+CIc7KVlYN6Qo8E7ju1JeRU5ae1Wl1NdXrolRJHjmaQ==}
|
||||||
|
|
||||||
better-call@1.0.9:
|
better-call@1.0.19:
|
||||||
resolution: {integrity: sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==}
|
resolution: {integrity: sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw==}
|
||||||
|
|
||||||
bignumber.js@9.3.1:
|
bignumber.js@9.3.1:
|
||||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||||
@@ -5754,9 +5774,9 @@ packages:
|
|||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
kysely@0.28.2:
|
kysely@0.28.7:
|
||||||
resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==}
|
resolution: {integrity: sha512-u/cAuTL4DRIiO2/g4vNGRgklEKNIj5Q3CG7RoUB5DV5SfEC2hMvPxKi0GWPmnzwL2ryIeud2VTcEEmqzTzEPNw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
leac@0.6.0:
|
leac@0.6.0:
|
||||||
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||||
@@ -6926,6 +6946,9 @@ packages:
|
|||||||
redux@5.0.1:
|
redux@5.0.1:
|
||||||
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
|
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
|
||||||
|
|
||||||
|
reflect-metadata@0.2.2:
|
||||||
|
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
|
||||||
|
|
||||||
refractor@3.6.0:
|
refractor@3.6.0:
|
||||||
resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==}
|
resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==}
|
||||||
|
|
||||||
@@ -7446,6 +7469,9 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
tslib@1.14.1:
|
||||||
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
|
|
||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
@@ -7454,6 +7480,10 @@ packages:
|
|||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
tsyringe@4.10.0:
|
||||||
|
resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==}
|
||||||
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
||||||
tweetnacl@0.14.5:
|
tweetnacl@0.14.5:
|
||||||
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
|
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
|
||||||
|
|
||||||
@@ -7913,6 +7943,8 @@ snapshots:
|
|||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
uncrypto: 0.1.3
|
uncrypto: 0.1.3
|
||||||
|
|
||||||
|
'@better-auth/utils@0.3.0': {}
|
||||||
|
|
||||||
'@better-fetch/fetch@1.1.18': {}
|
'@better-fetch/fetch@1.1.18': {}
|
||||||
|
|
||||||
'@biomejs/biome@2.1.1':
|
'@biomejs/biome@2.1.1':
|
||||||
@@ -8733,8 +8765,6 @@ snapshots:
|
|||||||
|
|
||||||
'@noble/hashes@1.7.1': {}
|
'@noble/hashes@1.7.1': {}
|
||||||
|
|
||||||
'@noble/hashes@1.8.0': {}
|
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
@@ -9637,37 +9667,100 @@ snapshots:
|
|||||||
|
|
||||||
'@peculiar/asn1-android@2.3.16':
|
'@peculiar/asn1-android@2.3.16':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-ecc@2.3.15':
|
'@peculiar/asn1-cms@2.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
'@peculiar/asn1-x509': 2.3.15
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
'@peculiar/asn1-x509-attr': 2.5.0
|
||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-rsa@2.3.15':
|
'@peculiar/asn1-csr@2.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
'@peculiar/asn1-x509': 2.3.15
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-schema@2.3.15':
|
'@peculiar/asn1-ecc@2.5.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-pfx@2.5.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-cms': 2.5.0
|
||||||
|
'@peculiar/asn1-pkcs8': 2.5.0
|
||||||
|
'@peculiar/asn1-rsa': 2.5.0
|
||||||
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs8@2.5.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs9@2.5.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-cms': 2.5.0
|
||||||
|
'@peculiar/asn1-pfx': 2.5.0
|
||||||
|
'@peculiar/asn1-pkcs8': 2.5.0
|
||||||
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
'@peculiar/asn1-x509-attr': 2.5.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-rsa@2.5.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-schema@2.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
pvtsutils: 1.3.6
|
pvtsutils: 1.3.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-x509@2.3.15':
|
'@peculiar/asn1-x509-attr@2.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-x509@2.5.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
pvtsutils: 1.3.6
|
pvtsutils: 1.3.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/x509@1.14.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-cms': 2.5.0
|
||||||
|
'@peculiar/asn1-csr': 2.5.0
|
||||||
|
'@peculiar/asn1-ecc': 2.5.0
|
||||||
|
'@peculiar/asn1-pkcs9': 2.5.0
|
||||||
|
'@peculiar/asn1-rsa': 2.5.0
|
||||||
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
pvtsutils: 1.3.6
|
||||||
|
reflect-metadata: 0.2.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
tsyringe: 4.10.0
|
||||||
|
|
||||||
'@petamoriken/float16@3.9.2': {}
|
'@petamoriken/float16@3.9.2': {}
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
@@ -10678,17 +10771,18 @@ snapshots:
|
|||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
selderee: 0.11.0
|
selderee: 0.11.0
|
||||||
|
|
||||||
'@simplewebauthn/browser@13.1.0': {}
|
'@simplewebauthn/browser@13.2.2': {}
|
||||||
|
|
||||||
'@simplewebauthn/server@13.1.1':
|
'@simplewebauthn/server@13.2.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@hexagon/base64': 1.1.28
|
'@hexagon/base64': 1.1.28
|
||||||
'@levischuck/tiny-cbor': 0.2.11
|
'@levischuck/tiny-cbor': 0.2.11
|
||||||
'@peculiar/asn1-android': 2.3.16
|
'@peculiar/asn1-android': 2.3.16
|
||||||
'@peculiar/asn1-ecc': 2.3.15
|
'@peculiar/asn1-ecc': 2.5.0
|
||||||
'@peculiar/asn1-rsa': 2.3.15
|
'@peculiar/asn1-rsa': 2.5.0
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.5.0
|
||||||
'@peculiar/asn1-x509': 2.3.15
|
'@peculiar/asn1-x509': 2.5.0
|
||||||
|
'@peculiar/x509': 1.14.0
|
||||||
|
|
||||||
'@sinclair/typebox@0.27.8': {}
|
'@sinclair/typebox@0.27.8': {}
|
||||||
|
|
||||||
@@ -11602,18 +11696,19 @@ snapshots:
|
|||||||
'@better-auth/utils': 0.2.5
|
'@better-auth/utils': 0.2.5
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.18
|
||||||
'@noble/ciphers': 0.6.0
|
'@noble/ciphers': 0.6.0
|
||||||
'@noble/hashes': 1.8.0
|
'@noble/hashes': 1.7.1
|
||||||
'@simplewebauthn/browser': 13.1.0
|
'@simplewebauthn/browser': 13.2.2
|
||||||
'@simplewebauthn/server': 13.1.1
|
'@simplewebauthn/server': 13.2.2
|
||||||
better-call: 1.0.9
|
better-call: 1.0.19
|
||||||
defu: 6.1.4
|
defu: 6.1.4
|
||||||
jose: 5.10.0
|
jose: 5.10.0
|
||||||
kysely: 0.28.2
|
kysely: 0.28.7
|
||||||
nanostores: 0.11.4
|
nanostores: 0.11.4
|
||||||
zod: 3.25.32
|
zod: 3.25.32
|
||||||
|
|
||||||
better-call@1.0.9:
|
better-call@1.0.19:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@better-auth/utils': 0.3.0
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.18
|
||||||
rou3: 0.5.1
|
rou3: 0.5.1
|
||||||
set-cookie-parser: 2.7.1
|
set-cookie-parser: 2.7.1
|
||||||
@@ -12181,9 +12276,9 @@ snapshots:
|
|||||||
|
|
||||||
drange@1.1.1: {}
|
drange@1.1.1: {}
|
||||||
|
|
||||||
drizzle-dbml-generator@0.10.0(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4)):
|
drizzle-dbml-generator@0.10.0(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4)):
|
||||||
dependencies:
|
dependencies:
|
||||||
drizzle-orm: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4)
|
drizzle-orm: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4)
|
||||||
|
|
||||||
drizzle-kit@0.30.6:
|
drizzle-kit@0.30.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12195,16 +12290,16 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4):
|
drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
'@types/pg': 8.6.1
|
'@types/pg': 8.6.1
|
||||||
kysely: 0.28.2
|
kysely: 0.28.7
|
||||||
postgres: 3.4.4
|
postgres: 3.4.4
|
||||||
|
|
||||||
drizzle-zod@0.5.1(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4))(zod@3.25.32):
|
drizzle-zod@0.5.1(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4))(zod@3.25.32):
|
||||||
dependencies:
|
dependencies:
|
||||||
drizzle-orm: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.4)
|
drizzle-orm: 0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4)
|
||||||
zod: 3.25.32
|
zod: 3.25.32
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
@@ -13138,7 +13233,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
|
|
||||||
kysely@0.28.2: {}
|
kysely@0.28.7: {}
|
||||||
|
|
||||||
leac@0.6.0: {}
|
leac@0.6.0: {}
|
||||||
|
|
||||||
@@ -14424,6 +14519,8 @@ snapshots:
|
|||||||
|
|
||||||
redux@5.0.1: {}
|
redux@5.0.1: {}
|
||||||
|
|
||||||
|
reflect-metadata@0.2.2: {}
|
||||||
|
|
||||||
refractor@3.6.0:
|
refractor@3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
hastscript: 6.0.0
|
hastscript: 6.0.0
|
||||||
@@ -15031,6 +15128,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
|
tslib@1.14.1: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
tsx@4.16.2:
|
tsx@4.16.2:
|
||||||
@@ -15040,6 +15139,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
tsyringe@4.10.0:
|
||||||
|
dependencies:
|
||||||
|
tslib: 1.14.1
|
||||||
|
|
||||||
tweetnacl@0.14.5: {}
|
tweetnacl@0.14.5: {}
|
||||||
|
|
||||||
type-detect@4.1.0: {}
|
type-detect@4.1.0: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user