mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-18 13:45:23 +02:00
Compare commits
1 Commits
2702-add-d
...
dosu/doc-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4406e0542 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -7,7 +7,7 @@ Please describe in a short paragraph what this PR is about.
|
||||
Before submitting this PR, please make sure that:
|
||||
|
||||
- [ ] You created a dedicated branch based on the `canary` branch.
|
||||
- [ ] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
|
||||
- [ ] You have read the suggestions in the CONTRIBUTING.md file https://github.com/MkinG2k0/dokploy/blob/canary/CONTRIBUTING.md#pull-request
|
||||
- [ ] You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.
|
||||
|
||||
## Issues related (if applicable)
|
||||
|
||||
@@ -32,7 +32,6 @@ describe("Host rule format regression tests", () => {
|
||||
previewDeploymentId: "",
|
||||
internalPath: "/",
|
||||
stripPath: false,
|
||||
customEntrypoint: null,
|
||||
};
|
||||
|
||||
describe("Host rule format validation", () => {
|
||||
|
||||
@@ -7,7 +7,6 @@ describe("createDomainLabels", () => {
|
||||
const baseDomain: Domain = {
|
||||
host: "example.com",
|
||||
port: 8080,
|
||||
customEntrypoint: null,
|
||||
https: false,
|
||||
uniqueConfigKey: 1,
|
||||
customCertResolver: null,
|
||||
@@ -241,134 +240,4 @@ describe("createDomainLabels", () => {
|
||||
"traefik.http.routers.test-app-1-websecure.middlewares=stripprefix-test-app-1,addprefix-test-app-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("should create basic labels for custom entrypoint", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{ ...baseDomain, customEntrypoint: "custom" },
|
||||
"custom",
|
||||
);
|
||||
expect(labels).toEqual([
|
||||
"traefik.http.routers.test-app-1-custom.rule=Host(`example.com`)",
|
||||
"traefik.http.routers.test-app-1-custom.entrypoints=custom",
|
||||
"traefik.http.services.test-app-1-custom.loadbalancer.server.port=8080",
|
||||
"traefik.http.routers.test-app-1-custom.service=test-app-1-custom",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should create https labels for custom entrypoint", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
https: true,
|
||||
customEntrypoint: "custom",
|
||||
certificateType: "letsencrypt",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
expect(labels).toEqual([
|
||||
"traefik.http.routers.test-app-1-custom.rule=Host(`example.com`)",
|
||||
"traefik.http.routers.test-app-1-custom.entrypoints=custom",
|
||||
"traefik.http.services.test-app-1-custom.loadbalancer.server.port=8080",
|
||||
"traefik.http.routers.test-app-1-custom.service=test-app-1-custom",
|
||||
"traefik.http.routers.test-app-1-custom.tls.certresolver=letsencrypt",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should add stripPath middleware for custom entrypoint", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
customEntrypoint: "custom",
|
||||
path: "/api",
|
||||
stripPath: true,
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(labels).toContain(
|
||||
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
|
||||
);
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-custom.middlewares=stripprefix-test-app-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("should add internalPath middleware for custom entrypoint", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
customEntrypoint: "custom",
|
||||
internalPath: "/hello",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(labels).toContain(
|
||||
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||
);
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-custom.middlewares=addprefix-test-app-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("should add path prefix in rule for custom entrypoint", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
customEntrypoint: "custom",
|
||||
path: "/api",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-custom.rule=Host(`example.com`) && PathPrefix(`/api`)",
|
||||
);
|
||||
});
|
||||
|
||||
it("should combine all middlewares for custom entrypoint", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
customEntrypoint: "custom",
|
||||
path: "/api",
|
||||
stripPath: true,
|
||||
internalPath: "/hello",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(labels).toContain(
|
||||
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
|
||||
);
|
||||
expect(labels).toContain(
|
||||
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||
);
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-custom.middlewares=stripprefix-test-app-1,addprefix-test-app-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("should not add redirect-to-https for custom entrypoint even with https", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
customEntrypoint: "custom",
|
||||
https: true,
|
||||
certificateType: "letsencrypt",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
const middlewareLabel = labels.find((l) => l.includes(".middlewares="));
|
||||
// Should not contain redirect-to-https since there's only one router
|
||||
expect(middlewareLabel).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -292,7 +292,7 @@ networks:
|
||||
dokploy-network:
|
||||
`;
|
||||
|
||||
test("It shouldn't add suffix to dokploy-network", () => {
|
||||
test("It shoudn't add suffix to dokploy-network", () => {
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -195,7 +195,7 @@ services:
|
||||
- dokploy-network
|
||||
`;
|
||||
|
||||
test("It shouldn't add suffix to dokploy-network in services", () => {
|
||||
test("It shoudn't add suffix to dokploy-network in services", () => {
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
@@ -241,10 +241,10 @@ services:
|
||||
dokploy-network:
|
||||
aliases:
|
||||
- apid
|
||||
|
||||
|
||||
`;
|
||||
|
||||
test("It shouldn't add suffix to dokploy-network in services multiples cases", () => {
|
||||
test("It shoudn't add suffix to dokploy-network in services multiples cases", () => {
|
||||
const composeData = parse(composeFile8) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getEnvironmentVariablesObject } from "@dokploy/server/index";
|
||||
import { getEnviromentVariablesObject } from "@dokploy/server/index";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const projectEnv = `
|
||||
@@ -15,7 +15,7 @@ DATABASE_NAME=dev_database
|
||||
SECRET_KEY=env-secret-123
|
||||
`;
|
||||
|
||||
describe("getEnvironmentVariablesObject with environment variables (Stack compose)", () => {
|
||||
describe("getEnviromentVariablesObject with environment variables (Stack compose)", () => {
|
||||
it("resolves environment variables correctly for Stack compose", () => {
|
||||
const serviceEnv = `
|
||||
FOO=\${{environment.NODE_ENV}}
|
||||
@@ -23,7 +23,7 @@ BAR=\${{environment.API_URL}}
|
||||
BAZ=test
|
||||
`;
|
||||
|
||||
const result = getEnvironmentVariablesObject(
|
||||
const result = getEnviromentVariablesObject(
|
||||
serviceEnv,
|
||||
projectEnv,
|
||||
environmentEnv,
|
||||
@@ -45,7 +45,7 @@ DATABASE_URL=\${{project.DATABASE_URL}}
|
||||
SERVICE_PORT=4000
|
||||
`;
|
||||
|
||||
const result = getEnvironmentVariablesObject(
|
||||
const result = getEnviromentVariablesObject(
|
||||
serviceEnv,
|
||||
projectEnv,
|
||||
environmentEnv,
|
||||
@@ -72,7 +72,7 @@ PASSWORD=secret123
|
||||
DATABASE_URL=postgresql://\${{environment.USERNAME}}:\${{environment.PASSWORD}}@\${{environment.HOST}}:\${{environment.PORT}}/mydb
|
||||
`;
|
||||
|
||||
const result = getEnvironmentVariablesObject(serviceEnv, "", multiRefEnv);
|
||||
const result = getEnviromentVariablesObject(serviceEnv, "", multiRefEnv);
|
||||
|
||||
expect(result).toEqual({
|
||||
DATABASE_URL: "postgresql://postgres:secret123@localhost:5432/mydb",
|
||||
@@ -85,7 +85,7 @@ UNDEFINED_VAR=\${{environment.UNDEFINED_VAR}}
|
||||
`;
|
||||
|
||||
expect(() =>
|
||||
getEnvironmentVariablesObject(serviceWithUndefined, "", environmentEnv),
|
||||
getEnviromentVariablesObject(serviceWithUndefined, "", environmentEnv),
|
||||
).toThrow("Invalid environment variable: environment.UNDEFINED_VAR");
|
||||
});
|
||||
|
||||
@@ -95,7 +95,7 @@ NODE_ENV=production
|
||||
API_URL=\${{environment.API_URL}}
|
||||
`;
|
||||
|
||||
const result = getEnvironmentVariablesObject(
|
||||
const result = getEnviromentVariablesObject(
|
||||
serviceOverrideEnv,
|
||||
"",
|
||||
environmentEnv,
|
||||
@@ -115,7 +115,7 @@ SERVICE_NAME=my-service
|
||||
COMPLEX_VAR=\${{SERVICE_NAME}}-\${{environment.NODE_ENV}}-\${{project.ENVIRONMENT}}
|
||||
`;
|
||||
|
||||
const result = getEnvironmentVariablesObject(
|
||||
const result = getEnviromentVariablesObject(
|
||||
complexServiceEnv,
|
||||
projectEnv,
|
||||
environmentEnv,
|
||||
@@ -150,7 +150,7 @@ ENV_VAR=\${{environment.API_URL}}
|
||||
DB_NAME=\${{environment.DATABASE_NAME}}
|
||||
`;
|
||||
|
||||
const result = getEnvironmentVariablesObject(
|
||||
const result = getEnviromentVariablesObject(
|
||||
serviceWithConflicts,
|
||||
conflictingProjectEnv,
|
||||
conflictingEnvironmentEnv,
|
||||
@@ -170,7 +170,7 @@ SERVICE_VAR=test
|
||||
PROJECT_VAR=\${{project.ENVIRONMENT}}
|
||||
`;
|
||||
|
||||
const result = getEnvironmentVariablesObject(
|
||||
const result = getEnviromentVariablesObject(
|
||||
serviceWithEmpty,
|
||||
projectEnv,
|
||||
"",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
enterpriseOnlyResources,
|
||||
statements,
|
||||
} from "@dokploy/server/lib/access-control";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const FREE_TIER_RESOURCES = [
|
||||
"organization",
|
||||
|
||||
@@ -137,7 +137,6 @@ const baseDomain: Domain = {
|
||||
https: false,
|
||||
path: null,
|
||||
port: null,
|
||||
customEntrypoint: null,
|
||||
serviceName: "",
|
||||
composeId: "",
|
||||
customCertResolver: null,
|
||||
@@ -277,110 +276,6 @@ test("CertificateType on websecure entrypoint", async () => {
|
||||
expect(router.tls?.certResolver).toBe("letsencrypt");
|
||||
});
|
||||
|
||||
test("Custom entrypoint on http domain", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{ ...baseDomain, https: false, customEntrypoint: "custom" },
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(router.entryPoints).toEqual(["custom"]);
|
||||
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||
expect(router.tls).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Custom entrypoint on https domain", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{
|
||||
...baseDomain,
|
||||
https: true,
|
||||
customEntrypoint: "custom",
|
||||
certificateType: "letsencrypt",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(router.entryPoints).toEqual(["custom"]);
|
||||
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||
expect(router.tls?.certResolver).toBe("letsencrypt");
|
||||
});
|
||||
|
||||
test("Custom entrypoint with path includes PathPrefix in rule", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{ ...baseDomain, customEntrypoint: "custom", path: "/api" },
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(router.rule).toContain("PathPrefix(`/api`)");
|
||||
expect(router.entryPoints).toEqual(["custom"]);
|
||||
});
|
||||
|
||||
test("Custom entrypoint with stripPath adds stripprefix middleware", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{
|
||||
...baseDomain,
|
||||
customEntrypoint: "custom",
|
||||
path: "/api",
|
||||
stripPath: true,
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(router.middlewares).toContain("stripprefix--1");
|
||||
expect(router.entryPoints).toEqual(["custom"]);
|
||||
});
|
||||
|
||||
test("Custom entrypoint with internalPath adds addprefix middleware", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{
|
||||
...baseDomain,
|
||||
customEntrypoint: "custom",
|
||||
internalPath: "/hello",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(router.middlewares).toContain("addprefix--1");
|
||||
expect(router.entryPoints).toEqual(["custom"]);
|
||||
});
|
||||
|
||||
test("Custom entrypoint with https and custom cert resolver", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{
|
||||
...baseDomain,
|
||||
https: true,
|
||||
customEntrypoint: "custom",
|
||||
certificateType: "custom",
|
||||
customCertResolver: "myresolver",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(router.entryPoints).toEqual(["custom"]);
|
||||
expect(router.tls?.certResolver).toBe("myresolver");
|
||||
});
|
||||
|
||||
test("Custom entrypoint without https should not have tls", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{
|
||||
...baseDomain,
|
||||
https: false,
|
||||
customEntrypoint: "custom",
|
||||
certificateType: "letsencrypt",
|
||||
},
|
||||
"custom",
|
||||
);
|
||||
|
||||
expect(router.entryPoints).toEqual(["custom"]);
|
||||
expect(router.tls).toBeUndefined();
|
||||
});
|
||||
|
||||
/** IDN/Punycode */
|
||||
|
||||
test("Internationalized domain name is converted to punycode", async () => {
|
||||
|
||||
@@ -40,12 +40,12 @@ interface Props {
|
||||
type: "application" | "mariadb" | "mongo" | "mysql" | "postgres" | "redis";
|
||||
}
|
||||
|
||||
const AddRedirectSchema = z.object({
|
||||
const AddRedirectchema = z.object({
|
||||
replicas: z.number().min(1, "Replicas must be at least 1"),
|
||||
registryId: z.string().optional(),
|
||||
});
|
||||
|
||||
type AddCommand = z.infer<typeof AddRedirectSchema>;
|
||||
type AddCommand = z.infer<typeof AddRedirectchema>;
|
||||
|
||||
export const ShowClusterSettings = ({ id, type }: Props) => {
|
||||
const queryMap = {
|
||||
@@ -87,7 +87,7 @@ export const ShowClusterSettings = ({ id, type }: Props) => {
|
||||
: {}),
|
||||
replicas: data?.replicas || 1,
|
||||
},
|
||||
resolver: zodResolver(AddRedirectSchema),
|
||||
resolver: zodResolver(AddRedirectchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -37,13 +37,13 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
const AddRedirectSchema = z.object({
|
||||
const AddRedirectchema = z.object({
|
||||
regex: z.string().min(1, "Regex required"),
|
||||
permanent: z.boolean().default(false),
|
||||
replacement: z.string().min(1, "Replacement required"),
|
||||
});
|
||||
|
||||
type AddRedirect = z.infer<typeof AddRedirectSchema>;
|
||||
type AddRedirect = z.infer<typeof AddRedirectchema>;
|
||||
|
||||
// Default presets
|
||||
const redirectPresets = [
|
||||
@@ -110,7 +110,7 @@ export const HandleRedirect = ({
|
||||
regex: "",
|
||||
replacement: "",
|
||||
},
|
||||
resolver: zodResolver(AddRedirectSchema),
|
||||
resolver: zodResolver(AddRedirectchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -149,7 +149,7 @@ export const HandleRedirect = ({
|
||||
|
||||
const onDialogToggle = (open: boolean) => {
|
||||
setIsOpen(open);
|
||||
// commented for the moment because not resetting the form if accidentally closed the dialog can be considered as a feature instead of a bug
|
||||
// commented for the moment because not reseting the form if accidentally closed the dialog can be considered as a feature instead of a bug
|
||||
// setPresetSelected("");
|
||||
// form.reset();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
|
||||
@@ -61,8 +61,6 @@ export const domain = z
|
||||
.min(1, { message: "Port must be at least 1" })
|
||||
.max(65535, { message: "Port must be 65535 or below" })
|
||||
.optional(),
|
||||
useCustomEntrypoint: z.boolean(),
|
||||
customEntrypoint: z.string().optional(),
|
||||
https: z.boolean().optional(),
|
||||
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
||||
customCertResolver: z.string().optional(),
|
||||
@@ -116,14 +114,6 @@ export const domain = z
|
||||
message: "Internal path must start with '/'",
|
||||
});
|
||||
}
|
||||
|
||||
if (input.useCustomEntrypoint && !input.customEntrypoint) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["customEntrypoint"],
|
||||
message: "Custom entry point must be specified",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type Domain = z.infer<typeof domain>;
|
||||
@@ -206,8 +196,6 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
internalPath: undefined,
|
||||
stripPath: false,
|
||||
port: undefined,
|
||||
useCustomEntrypoint: false,
|
||||
customEntrypoint: undefined,
|
||||
https: false,
|
||||
certificateType: undefined,
|
||||
customCertResolver: undefined,
|
||||
@@ -218,7 +206,6 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
});
|
||||
|
||||
const certificateType = form.watch("certificateType");
|
||||
const useCustomEntrypoint = form.watch("useCustomEntrypoint");
|
||||
const https = form.watch("https");
|
||||
const domainType = form.watch("domainType");
|
||||
const host = form.watch("host");
|
||||
@@ -233,8 +220,6 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
internalPath: data?.internalPath || undefined,
|
||||
stripPath: data?.stripPath || false,
|
||||
port: data?.port || undefined,
|
||||
useCustomEntrypoint: !!data.customEntrypoint,
|
||||
customEntrypoint: data.customEntrypoint || undefined,
|
||||
certificateType: data?.certificateType || undefined,
|
||||
customCertResolver: data?.customCertResolver || undefined,
|
||||
serviceName: data?.serviceName || undefined,
|
||||
@@ -249,8 +234,6 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
internalPath: undefined,
|
||||
stripPath: false,
|
||||
port: undefined,
|
||||
useCustomEntrypoint: false,
|
||||
customEntrypoint: undefined,
|
||||
https: false,
|
||||
certificateType: undefined,
|
||||
customCertResolver: undefined,
|
||||
@@ -652,50 +635,6 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="useCustomEntrypoint"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Custom Entrypoint</FormLabel>
|
||||
<FormDescription>
|
||||
Use custom entrypoint for domina
|
||||
<br />
|
||||
"web" and/or "websecure" is used by default.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{useCustomEntrypoint && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customEntrypoint"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Entrypoint Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter entrypoint name manually"
|
||||
{...field}
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="https"
|
||||
|
||||
@@ -91,7 +91,7 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
|
||||
}, [option, services, containers]);
|
||||
|
||||
const isLoading = option === "native" ? containersLoading : servicesLoading;
|
||||
const containersLength =
|
||||
const containersLenght =
|
||||
option === "native" ? containers?.length : services?.length;
|
||||
|
||||
return (
|
||||
@@ -167,7 +167,7 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<SelectLabel>Containers ({containersLength})</SelectLabel>
|
||||
<SelectLabel>Containers ({containersLenght})</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./patch-editor";
|
||||
export * from "./show-patches";
|
||||
export * from "./patch-editor";
|
||||
|
||||
@@ -483,7 +483,7 @@ export const HandleVolumeBackups = ({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
Choose the volume to backup. If you do not see the
|
||||
Choose the volume to backup, if you dont see the
|
||||
volume here, you can type the volume name manually
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
@@ -518,7 +518,7 @@ export const HandleVolumeBackups = ({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
Choose the volume to backup. If you do not see the volume
|
||||
Choose the volume to backup, if you dont see the volume
|
||||
here, you can type the volume name manually
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
|
||||
@@ -77,7 +77,7 @@ export const ShowDockerLogsStack = ({ appName, serverId }: Props) => {
|
||||
}, [option, services, containers]);
|
||||
|
||||
const isLoading = option === "native" ? containersLoading : servicesLoading;
|
||||
const containersLength =
|
||||
const containersLenght =
|
||||
option === "native" ? containers?.length : services?.length;
|
||||
|
||||
return (
|
||||
@@ -152,7 +152,7 @@ export const ShowDockerLogsStack = ({ appName, serverId }: Props) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<SelectLabel>Containers ({containersLength})</SelectLabel>
|
||||
<SelectLabel>Containers ({containersLenght})</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
@@ -225,7 +225,7 @@ export const RestoreBackup = ({
|
||||
resolver: zodResolver(RestoreBackupSchema),
|
||||
});
|
||||
|
||||
const destinationId = form.watch("destinationId");
|
||||
const destionationId = form.watch("destinationId");
|
||||
const currentDatabaseType = form.watch("databaseType");
|
||||
const metadata = form.watch("metadata");
|
||||
|
||||
@@ -240,12 +240,12 @@ export const RestoreBackup = ({
|
||||
|
||||
const { data: files = [], isPending } = api.backup.listBackupFiles.useQuery(
|
||||
{
|
||||
destinationId: destinationId,
|
||||
destinationId: destionationId,
|
||||
search: debouncedSearchTerm,
|
||||
serverId: serverId ?? "",
|
||||
},
|
||||
{
|
||||
enabled: isOpen && !!destinationId,
|
||||
enabled: isOpen && !!destionationId,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import type { inferRouterOutputs } from "@trpc/server";
|
||||
import { ArrowRight, ListTodo, Loader2, XCircle } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, ListTodo, Loader2, XCircle } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
||||
@@ -103,7 +103,7 @@ export function TerminalLine({ log, noTimestamp, searchTerm }: LogLineProps) {
|
||||
>
|
||||
{" "}
|
||||
<div className="flex items-start gap-x-2">
|
||||
{/* Icon to expand the log item maybe implement a collapsible later */}
|
||||
{/* Icon to expand the log item maybe implement a colapsible later */}
|
||||
{/* <Square className="size-4 text-muted-foreground opacity-0 group-hover/logitem:opacity-100 transition-opacity" /> */}
|
||||
{tooltip(color, rawTimestamp)}
|
||||
{!noTimestamp && (
|
||||
|
||||
@@ -130,7 +130,7 @@ export const columns: ColumnDef<Container>[] = [
|
||||
</DockerTerminalModal>
|
||||
<RemoveContainerDialog
|
||||
containerId={container.containerId}
|
||||
serverId={container.serverId ?? undefined}
|
||||
serverId={container.serverId}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { api, type RouterOutputs } from "@/utils/api";
|
||||
import { columns } from "./columns";
|
||||
import { columns } from "./colums";
|
||||
export type Container = NonNullable<
|
||||
RouterOutputs["docker"]["getContainers"]
|
||||
>[0];
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
||||
|
||||
interface Props {
|
||||
accumulativeData: DockerStatsJSON["block"];
|
||||
acummulativeData: DockerStatsJSON["block"];
|
||||
}
|
||||
|
||||
export const DockerBlockChart = ({ accumulativeData }: Props) => {
|
||||
const transformedData = accumulativeData.map((item, index) => {
|
||||
export const DockerBlockChart = ({ acummulativeData }: Props) => {
|
||||
const transformedData = acummulativeData.map((item, index) => {
|
||||
return {
|
||||
time: item.time,
|
||||
name: `Point ${index + 1}`,
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
||||
|
||||
interface Props {
|
||||
accumulativeData: DockerStatsJSON["cpu"];
|
||||
acummulativeData: DockerStatsJSON["cpu"];
|
||||
}
|
||||
|
||||
export const DockerCpuChart = ({ accumulativeData }: Props) => {
|
||||
const transformedData = accumulativeData.map((item, index) => {
|
||||
export const DockerCpuChart = ({ acummulativeData }: Props) => {
|
||||
const transformedData = acummulativeData.map((item, index) => {
|
||||
return {
|
||||
name: `Point ${index + 1}`,
|
||||
time: item.time,
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
||||
|
||||
interface Props {
|
||||
accumulativeData: DockerStatsJSON["disk"];
|
||||
acummulativeData: DockerStatsJSON["disk"];
|
||||
diskTotal: number;
|
||||
}
|
||||
|
||||
export const DockerDiskChart = ({ accumulativeData, diskTotal }: Props) => {
|
||||
const transformedData = accumulativeData.map((item, index) => {
|
||||
export const DockerDiskChart = ({ acummulativeData, diskTotal }: Props) => {
|
||||
const transformedData = acummulativeData.map((item, index) => {
|
||||
return {
|
||||
time: item.time,
|
||||
name: `Point ${index + 1}`,
|
||||
|
||||
@@ -12,15 +12,15 @@ import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
||||
import { convertMemoryToBytes } from "./show-free-container-monitoring";
|
||||
|
||||
interface Props {
|
||||
accumulativeData: DockerStatsJSON["memory"];
|
||||
acummulativeData: DockerStatsJSON["memory"];
|
||||
memoryLimitGB: number;
|
||||
}
|
||||
|
||||
export const DockerMemoryChart = ({
|
||||
accumulativeData,
|
||||
acummulativeData,
|
||||
memoryLimitGB,
|
||||
}: Props) => {
|
||||
const transformedData = accumulativeData.map((item, index) => {
|
||||
const transformedData = acummulativeData.map((item, index) => {
|
||||
return {
|
||||
time: item.time,
|
||||
name: `Point ${index + 1}`,
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
||||
|
||||
interface Props {
|
||||
accumulativeData: DockerStatsJSON["network"];
|
||||
acummulativeData: DockerStatsJSON["network"];
|
||||
}
|
||||
|
||||
export const DockerNetworkChart = ({ accumulativeData }: Props) => {
|
||||
const transformedData = accumulativeData.map((item, index) => {
|
||||
export const DockerNetworkChart = ({ acummulativeData }: Props) => {
|
||||
const transformedData = acummulativeData.map((item, index) => {
|
||||
return {
|
||||
time: item.time,
|
||||
name: `Point ${index + 1}`,
|
||||
|
||||
@@ -124,7 +124,7 @@ export const ContainerFreeMonitoring = ({
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
);
|
||||
const [accumulativeData, setAccumulativeData] = useState<DockerStatsJSON>({
|
||||
const [acummulativeData, setAcummulativeData] = useState<DockerStatsJSON>({
|
||||
cpu: [],
|
||||
memory: [],
|
||||
block: [],
|
||||
@@ -136,7 +136,7 @@ export const ContainerFreeMonitoring = ({
|
||||
useEffect(() => {
|
||||
setCurrentData(defaultData);
|
||||
|
||||
setAccumulativeData({
|
||||
setAcummulativeData({
|
||||
cpu: [],
|
||||
memory: [],
|
||||
block: [],
|
||||
@@ -155,7 +155,7 @@ export const ContainerFreeMonitoring = ({
|
||||
network: data.network[data.network.length - 1] ?? currentData.network,
|
||||
disk: data.disk[data.disk.length - 1] ?? currentData.disk,
|
||||
});
|
||||
setAccumulativeData({
|
||||
setAcummulativeData({
|
||||
block: data?.block || [],
|
||||
cpu: data?.cpu || [],
|
||||
disk: data?.disk || [],
|
||||
@@ -184,7 +184,7 @@ export const ContainerFreeMonitoring = ({
|
||||
setCurrentData(data);
|
||||
|
||||
const MAX_DATA_POINTS = 300;
|
||||
setAccumulativeData((prevData) => ({
|
||||
setAcummulativeData((prevData) => ({
|
||||
cpu: [...prevData.cpu, data.cpu].slice(-MAX_DATA_POINTS),
|
||||
memory: [...prevData.memory, data.memory].slice(-MAX_DATA_POINTS),
|
||||
block: [...prevData.block, data.block].slice(-MAX_DATA_POINTS),
|
||||
@@ -228,7 +228,7 @@ export const ContainerFreeMonitoring = ({
|
||||
)}
|
||||
className="w-[100%]"
|
||||
/>
|
||||
<DockerCpuChart accumulativeData={accumulativeData.cpu} />
|
||||
<DockerCpuChart acummulativeData={acummulativeData.cpu} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -252,7 +252,7 @@ export const ContainerFreeMonitoring = ({
|
||||
className="w-[100%]"
|
||||
/>
|
||||
<DockerMemoryChart
|
||||
accumulativeData={accumulativeData.memory}
|
||||
acummulativeData={acummulativeData.memory}
|
||||
memoryLimitGB={
|
||||
// @ts-ignore
|
||||
convertMemoryToBytes(currentData.memory.value.total) /
|
||||
@@ -277,7 +277,7 @@ export const ContainerFreeMonitoring = ({
|
||||
className="w-[100%]"
|
||||
/>
|
||||
<DockerDiskChart
|
||||
accumulativeData={accumulativeData.disk}
|
||||
acummulativeData={acummulativeData.disk}
|
||||
diskTotal={currentData.disk.value.diskTotal}
|
||||
/>
|
||||
</div>
|
||||
@@ -294,7 +294,7 @@ export const ContainerFreeMonitoring = ({
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{`Read: ${currentData.block.value.readMb} / Write: ${currentData.block.value.writeMb} `}
|
||||
</span>
|
||||
<DockerBlockChart accumulativeData={accumulativeData.block} />
|
||||
<DockerBlockChart acummulativeData={acummulativeData.block} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -307,7 +307,7 @@ export const ContainerFreeMonitoring = ({
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{`In MB: ${currentData.network.value.inputMb} / Out MB: ${currentData.network.value.outputMb} `}
|
||||
</span>
|
||||
<DockerNetworkChart accumulativeData={accumulativeData.network} />
|
||||
<DockerNetworkChart acummulativeData={acummulativeData.network} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
AlertCircle,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Link,
|
||||
Loader2,
|
||||
ShieldCheck,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { AlertCircle, Link, Loader2, ShieldCheck, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
@@ -21,19 +12,13 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { AddCertificate } from "./add-certificate";
|
||||
import {
|
||||
extractLeafCommonName,
|
||||
getCertificateChainExpirationDetails,
|
||||
getCertificateChainInfo,
|
||||
getExpirationStatus,
|
||||
} from "./utils";
|
||||
import { getCertificateChainInfo, getExpirationStatus } from "./utils";
|
||||
|
||||
export const ShowCertificates = () => {
|
||||
const { mutateAsync, isPending: isRemoving } =
|
||||
api.certificates.remove.useMutation();
|
||||
const { data, isPending, refetch } = api.certificates.all.useQuery();
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
const [expandedChains, setExpandedChains] = useState<Set<string>>(new Set());
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
@@ -81,30 +66,6 @@ export const ShowCertificates = () => {
|
||||
const chainInfo = getCertificateChainInfo(
|
||||
certificate.certificateData,
|
||||
);
|
||||
const commonName = extractLeafCommonName(
|
||||
certificate.certificateData,
|
||||
);
|
||||
const chainDetails = chainInfo.isChain
|
||||
? getCertificateChainExpirationDetails(
|
||||
certificate.certificateData,
|
||||
)
|
||||
: null;
|
||||
const isExpanded = expandedChains.has(
|
||||
certificate.certificateId,
|
||||
);
|
||||
|
||||
const toggleChain = () => {
|
||||
setExpandedChains((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(certificate.certificateId)) {
|
||||
next.delete(certificate.certificateId);
|
||||
} else {
|
||||
next.add(certificate.certificateId);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={certificate.certificateId}
|
||||
@@ -116,52 +77,12 @@ export const ShowCertificates = () => {
|
||||
<span className="text-sm font-medium">
|
||||
{index + 1}. {certificate.name}
|
||||
</span>
|
||||
{commonName && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
CN: {commonName}
|
||||
</span>
|
||||
)}
|
||||
{chainInfo.isChain && (
|
||||
<div className="flex flex-col gap-1.5 mt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleChain}
|
||||
className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-muted/50 w-fit hover:bg-muted transition-colors"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="size-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="size-3 text-muted-foreground" />
|
||||
)}
|
||||
<Link className="size-3 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Chain ({chainInfo.count} certificates)
|
||||
</span>
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="flex flex-col gap-3 pl-2 border-l-2 border-muted">
|
||||
{chainDetails?.map((cert) => (
|
||||
<div
|
||||
key={cert.index}
|
||||
className="flex flex-col gap-1 p-2 rounded-md bg-muted/30"
|
||||
>
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
{cert.label}
|
||||
</span>
|
||||
{cert.commonName && (
|
||||
<span className="text-xs text-muted-foreground/80">
|
||||
CN: {cert.commonName}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className={`text-xs ${cert.className}`}
|
||||
>
|
||||
{cert.message}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-muted/50">
|
||||
<Link className="size-3 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Chain ({chainInfo.count})
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// Split certificate chain into individual certificates
|
||||
export const splitCertificateChain = (certData: string): string[] => {
|
||||
const certRegex =
|
||||
/(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)/g;
|
||||
const matches = certData.match(certRegex);
|
||||
return matches || [];
|
||||
};
|
||||
|
||||
export const extractExpirationDate = (certData: string): Date | null => {
|
||||
try {
|
||||
// Decode PEM base64 to DER binary
|
||||
@@ -102,156 +94,8 @@ export const extractExpirationDate = (certData: string): Date | null => {
|
||||
}
|
||||
};
|
||||
|
||||
export const extractCommonName = (certData: string): string | null => {
|
||||
try {
|
||||
// Decode PEM base64 to DER binary
|
||||
const b64 = certData.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
|
||||
const binStr = atob(b64);
|
||||
const der = new Uint8Array(binStr.length);
|
||||
for (let i = 0; i < binStr.length; i++) {
|
||||
der[i] = binStr.charCodeAt(i);
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
|
||||
// Helper: read ASN.1 length field
|
||||
function readLength(pos: number): { length: number; offset: number } {
|
||||
// biome-ignore lint/style/noParameterAssign: <explanation>
|
||||
let len = der[pos++];
|
||||
if (len & 0x80) {
|
||||
const bytes = len & 0x7f;
|
||||
len = 0;
|
||||
for (let i = 0; i < bytes; i++) {
|
||||
// biome-ignore lint/style/noParameterAssign: <explanation>
|
||||
len = (len << 8) + der[pos++];
|
||||
}
|
||||
}
|
||||
return { length: len, offset: pos };
|
||||
}
|
||||
|
||||
// Helper: skip a field
|
||||
function skipField(pos: number): number {
|
||||
// biome-ignore lint/style/noParameterAssign: <explanation>
|
||||
pos++;
|
||||
const fieldLen = readLength(pos);
|
||||
return fieldLen.offset + fieldLen.length;
|
||||
}
|
||||
|
||||
// Skip the outer certificate sequence
|
||||
if (der[offset++] !== 0x30) throw new Error("Expected sequence");
|
||||
({ offset } = readLength(offset));
|
||||
|
||||
// Skip tbsCertificate sequence
|
||||
if (der[offset++] !== 0x30) throw new Error("Expected tbsCertificate");
|
||||
({ offset } = readLength(offset));
|
||||
|
||||
// Check for optional version field (context-specific tag [0])
|
||||
if (der[offset] === 0xa0) {
|
||||
offset++;
|
||||
const versionLen = readLength(offset);
|
||||
offset = versionLen.offset + versionLen.length;
|
||||
}
|
||||
|
||||
// Skip serialNumber
|
||||
offset = skipField(offset);
|
||||
|
||||
// Skip signature
|
||||
offset = skipField(offset);
|
||||
|
||||
// Skip issuer
|
||||
offset = skipField(offset);
|
||||
|
||||
// Skip validity
|
||||
offset = skipField(offset);
|
||||
|
||||
// Subject sequence - where we find the CN
|
||||
if (der[offset++] !== 0x30) throw new Error("Expected subject sequence");
|
||||
const subjectLen = readLength(offset);
|
||||
const subjectEnd = subjectLen.offset + subjectLen.length;
|
||||
offset = subjectLen.offset;
|
||||
|
||||
// Parse subject RDNs looking for CN (OID 2.5.4.3)
|
||||
while (offset < subjectEnd) {
|
||||
if (der[offset++] !== 0x31) continue; // SET
|
||||
const setLen = readLength(offset);
|
||||
offset = setLen.offset;
|
||||
|
||||
if (der[offset++] !== 0x30) continue; // SEQUENCE
|
||||
const seqLen = readLength(offset);
|
||||
offset = seqLen.offset;
|
||||
|
||||
if (der[offset++] !== 0x06) continue; // OID
|
||||
const oidLen = readLength(offset);
|
||||
offset = oidLen.offset;
|
||||
|
||||
// Check if OID is 2.5.4.3 (commonName)
|
||||
const oid = Array.from(der.slice(offset, offset + oidLen.length));
|
||||
offset += oidLen.length;
|
||||
|
||||
// OID 2.5.4.3 in DER: [0x55, 0x04, 0x03]
|
||||
if (
|
||||
oid.length === 3 &&
|
||||
oid[0] === 0x55 &&
|
||||
oid[1] === 0x04 &&
|
||||
oid[2] === 0x03
|
||||
) {
|
||||
// Next should be the string value
|
||||
const strType = der[offset++];
|
||||
const strLen = readLength(offset);
|
||||
const cnBytes = der.slice(strLen.offset, strLen.offset + strLen.length);
|
||||
return new TextDecoder().decode(cnBytes);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error parsing certificate CN:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Extract the Common Name from the first (leaf) certificate in a chain
|
||||
export const extractLeafCommonName = (certData: string): string | null => {
|
||||
const certs = splitCertificateChain(certData);
|
||||
if (certs.length === 0) return null;
|
||||
return extractCommonName(certs[0]);
|
||||
};
|
||||
|
||||
// Extract expiration dates from all certificates in a chain
|
||||
export const extractAllExpirationDates = (
|
||||
certData: string,
|
||||
): Array<{
|
||||
cert: string;
|
||||
index: number;
|
||||
expirationDate: Date | null;
|
||||
commonName: string | null;
|
||||
}> => {
|
||||
const certs = splitCertificateChain(certData);
|
||||
return certs.map((cert, index) => ({
|
||||
cert,
|
||||
index,
|
||||
expirationDate: extractExpirationDate(cert),
|
||||
commonName: extractCommonName(cert),
|
||||
}));
|
||||
};
|
||||
|
||||
// Get the earliest expiration date from a certificate chain
|
||||
export const getEarliestExpirationDate = (certData: string): Date | null => {
|
||||
const expirationDates = extractAllExpirationDates(certData);
|
||||
const validDates = expirationDates
|
||||
.filter((item) => item.expirationDate !== null)
|
||||
.map((item) => item.expirationDate as Date);
|
||||
|
||||
if (validDates.length === 0) return null;
|
||||
|
||||
return new Date(Math.min(...validDates.map((date) => date.getTime())));
|
||||
};
|
||||
|
||||
export const getExpirationStatus = (certData: string) => {
|
||||
const chainInfo = getCertificateChainInfo(certData);
|
||||
const expirationDate = chainInfo.isChain
|
||||
? getEarliestExpirationDate(certData)
|
||||
: extractExpirationDate(certData);
|
||||
const expirationDate = extractExpirationDate(certData);
|
||||
|
||||
if (!expirationDate)
|
||||
return {
|
||||
@@ -309,67 +153,3 @@ export const getCertificateChainInfo = (certData: string) => {
|
||||
count: 1,
|
||||
};
|
||||
};
|
||||
|
||||
// Get detailed expiration information for all certificates in a chain
|
||||
export const getCertificateChainExpirationDetails = (certData: string) => {
|
||||
const allExpirations = extractAllExpirationDates(certData);
|
||||
const now = new Date();
|
||||
|
||||
return allExpirations.map(({ index, expirationDate, commonName }) => {
|
||||
if (!expirationDate) {
|
||||
return {
|
||||
index,
|
||||
label: `Certificate ${index + 1}`,
|
||||
commonName,
|
||||
status: "unknown" as const,
|
||||
className: "text-muted-foreground",
|
||||
message: "Could not determine expiration",
|
||||
expirationDate: null,
|
||||
};
|
||||
}
|
||||
|
||||
const daysUntilExpiration = Math.ceil(
|
||||
(expirationDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
|
||||
);
|
||||
|
||||
let status: "expired" | "warning" | "valid";
|
||||
let className: string;
|
||||
let message: string;
|
||||
|
||||
if (daysUntilExpiration < 0) {
|
||||
status = "expired";
|
||||
className = "text-red-500";
|
||||
message = `Expired on ${expirationDate.toLocaleDateString([], {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}`;
|
||||
} else if (daysUntilExpiration <= 30) {
|
||||
status = "warning";
|
||||
className = "text-yellow-500";
|
||||
message = `Expires in ${daysUntilExpiration} days`;
|
||||
} else {
|
||||
status = "valid";
|
||||
className = "text-muted-foreground";
|
||||
message = `Expires ${expirationDate.toLocaleDateString([], {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}`;
|
||||
}
|
||||
|
||||
return {
|
||||
index,
|
||||
label:
|
||||
index === 0
|
||||
? `Certificate ${index + 1} (Leaf)`
|
||||
: `Certificate ${index + 1}`,
|
||||
commonName,
|
||||
status,
|
||||
className,
|
||||
message,
|
||||
expirationDate,
|
||||
daysUntilExpiration,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import {
|
||||
ADDITIONAL_FLAG_ERROR,
|
||||
ADDITIONAL_FLAG_REGEX,
|
||||
} from "@dokploy/server/db/validations/destination";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { PenBoxIcon, PlusIcon, Trash2 } from "lucide-react";
|
||||
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
@@ -50,16 +46,6 @@ const addDestination = z.object({
|
||||
region: z.string(),
|
||||
endpoint: z.string().min(1, "Endpoint is required"),
|
||||
serverId: z.string().optional(),
|
||||
additionalFlags: z
|
||||
.array(
|
||||
z.object({
|
||||
value: z
|
||||
.string()
|
||||
.min(1, "Flag cannot be empty")
|
||||
.regex(ADDITIONAL_FLAG_REGEX, ADDITIONAL_FLAG_ERROR),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
type AddDestination = z.infer<typeof addDestination>;
|
||||
@@ -103,16 +89,9 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
region: "",
|
||||
secretAccessKey: "",
|
||||
endpoint: "",
|
||||
additionalFlags: [],
|
||||
},
|
||||
resolver: zodResolver(addDestination),
|
||||
});
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control: form.control,
|
||||
name: "additionalFlags",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (destination) {
|
||||
form.reset({
|
||||
@@ -123,8 +102,6 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
bucket: destination.bucket,
|
||||
region: destination.region,
|
||||
endpoint: destination.endpoint,
|
||||
additionalFlags:
|
||||
destination.additionalFlags?.map((f) => ({ value: f })) ?? [],
|
||||
});
|
||||
} else {
|
||||
form.reset();
|
||||
@@ -141,7 +118,6 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
region: data.region,
|
||||
secretAccessKey: data.secretAccessKey,
|
||||
destinationId: destinationId || "",
|
||||
additionalFlags: data.additionalFlags?.map((f) => f.value) ?? [],
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success(`Destination ${destinationId ? "Updated" : "Created"}`);
|
||||
@@ -151,12 +127,9 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
}
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
`Error ${destinationId ? "Updating" : "Creating"} the Destination`,
|
||||
{
|
||||
description: e.message,
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -168,7 +141,6 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
"secretAccessKey",
|
||||
"bucket",
|
||||
"endpoint",
|
||||
"additionalFlags",
|
||||
]);
|
||||
|
||||
if (!result) {
|
||||
@@ -207,8 +179,6 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
region,
|
||||
secretAccessKey: secretKey,
|
||||
serverId,
|
||||
additionalFlags:
|
||||
form.getValues("additionalFlags")?.map((f) => f.value) ?? [],
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Connection Success");
|
||||
@@ -388,48 +358,6 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<FormLabel>Additional Flags (Optional)</FormLabel>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => append({ value: "" })}
|
||||
>
|
||||
<PlusIcon className="size-4" />
|
||||
Add Flag
|
||||
</Button>
|
||||
</div>
|
||||
{fields.map((field, index) => (
|
||||
<FormField
|
||||
key={field.id}
|
||||
control={form.control}
|
||||
name={`additionalFlags.${index}.value`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="--s3-sign-accept-encoding=false"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<Trash2 className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<DialogFooter
|
||||
|
||||
@@ -283,7 +283,7 @@ export const AddGitlabProvider = () => {
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="For organization/group access use the slug name of the group eg: my-org"
|
||||
placeholder="For organization/group access use the slugish name of the group eg: my-org"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -192,7 +192,7 @@ export const EditGitlabProvider = ({ gitlabId }: Props) => {
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="For organization/group access use the slug name of the group eg: my-org"
|
||||
placeholder="For organization/group access use the slugish name of the group eg: my-org"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ImportIcon,
|
||||
Loader2,
|
||||
Trash2,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { toast } from "sonner";
|
||||
@@ -25,13 +24,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider";
|
||||
@@ -47,8 +39,6 @@ export const ShowGitProviders = () => {
|
||||
const { data, isPending, refetch } = api.gitProvider.getAll.useQuery();
|
||||
const { mutateAsync, isPending: isRemoving } =
|
||||
api.gitProvider.remove.useMutation();
|
||||
const { mutateAsync: toggleShare, isPending: isToggling } =
|
||||
api.gitProvider.toggleShare.useMutation();
|
||||
const url = useUrl();
|
||||
|
||||
const getGitlabUrl = (
|
||||
@@ -164,62 +154,10 @@ export const ShowGitProviders = () => {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{!gitProvider.isOwner && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-xs"
|
||||
>
|
||||
<Users className="size-3 mr-1" />
|
||||
Shared
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
{gitProvider.isOwner && (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1.5 mr-2">
|
||||
<Users className="size-4 text-muted-foreground" />
|
||||
<Switch
|
||||
disabled={isToggling}
|
||||
checked={
|
||||
gitProvider.sharedWithOrganization
|
||||
}
|
||||
onCheckedChange={async (
|
||||
checked,
|
||||
) => {
|
||||
await toggleShare({
|
||||
gitProviderId:
|
||||
gitProvider.gitProviderId,
|
||||
sharedWithOrganization: checked,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
checked
|
||||
? "Provider shared with organization"
|
||||
: "Provider unshared",
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error updating sharing",
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Share with entire organization
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{isBitbucket &&
|
||||
gitProvider.bitbucket?.appPassword &&
|
||||
!gitProvider.bitbucket?.apiToken ? (
|
||||
@@ -284,71 +222,62 @@ export const ShowGitProviders = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gitProvider.isOwner && (
|
||||
<>
|
||||
{isGithub && haveGithubRequirements && (
|
||||
<EditGithubProvider
|
||||
githubId={gitProvider.github?.githubId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isGitlab && (
|
||||
<EditGitlabProvider
|
||||
gitlabId={gitProvider.gitlab?.gitlabId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isBitbucket && (
|
||||
<EditBitbucketProvider
|
||||
bitbucketId={
|
||||
gitProvider.bitbucket?.bitbucketId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isGitea && (
|
||||
<EditGiteaProvider
|
||||
giteaId={gitProvider.gitea?.giteaId}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DialogAction
|
||||
title="Delete Git Provider"
|
||||
description={
|
||||
gitProvider.sharedWithOrganization
|
||||
? "This provider is shared with the organization. Deleting it will remove access for all members. Are you sure?"
|
||||
: "Are you sure you want to delete this Git Provider?"
|
||||
}
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
gitProviderId:
|
||||
gitProvider.gitProviderId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
"Git Provider deleted successfully",
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error deleting Git Provider",
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10"
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
</>
|
||||
{isGithub && haveGithubRequirements && (
|
||||
<EditGithubProvider
|
||||
githubId={gitProvider.github?.githubId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isGitlab && (
|
||||
<EditGitlabProvider
|
||||
gitlabId={gitProvider.gitlab?.gitlabId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isBitbucket && (
|
||||
<EditBitbucketProvider
|
||||
bitbucketId={
|
||||
gitProvider.bitbucket?.bitbucketId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isGitea && (
|
||||
<EditGiteaProvider
|
||||
giteaId={gitProvider.gitea?.giteaId}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DialogAction
|
||||
title="Delete Git Provider"
|
||||
description="Are you sure you want to delete this Git Provider?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
gitProviderId: gitProvider.gitProviderId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
"Git Provider deleted successfully",
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"Error deleting Git Provider",
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10"
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,9 +12,9 @@ import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
DiscordIcon,
|
||||
MattermostIcon,
|
||||
GotifyIcon,
|
||||
LarkIcon,
|
||||
MattermostIcon,
|
||||
NtfyIcon,
|
||||
PushoverIcon,
|
||||
ResendIcon,
|
||||
@@ -54,7 +54,6 @@ const notificationBaseSchema = z.object({
|
||||
appDeploy: z.boolean().default(false),
|
||||
appBuildError: z.boolean().default(false),
|
||||
databaseBackup: z.boolean().default(false),
|
||||
dokployBackup: z.boolean().default(false),
|
||||
volumeBackup: z.boolean().default(false),
|
||||
dokployRestart: z.boolean().default(false),
|
||||
dockerCleanup: z.boolean().default(false),
|
||||
@@ -356,7 +355,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
webhookUrl: notification.slack?.webhookUrl,
|
||||
@@ -371,7 +369,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
botToken: notification.telegram?.botToken,
|
||||
messageThreadId: notification.telegram?.messageThreadId || "",
|
||||
@@ -387,7 +384,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.discord?.webhookUrl,
|
||||
@@ -402,7 +398,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
smtpServer: notification.email?.smtpServer,
|
||||
@@ -421,7 +416,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
apiKey: notification.resend?.apiKey,
|
||||
@@ -437,7 +431,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
appToken: notification.gotify?.appToken,
|
||||
@@ -453,7 +446,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
accessToken: notification.ntfy?.accessToken || "",
|
||||
@@ -470,7 +462,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.mattermost?.webhookUrl,
|
||||
@@ -486,7 +477,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.lark?.webhookUrl,
|
||||
name: notification.name,
|
||||
@@ -500,7 +490,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
webhookUrl: notification.teams?.webhookUrl,
|
||||
@@ -514,7 +503,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
type: notification.notificationType,
|
||||
endpoint: notification.custom?.endpoint || "",
|
||||
headers: notification.custom?.headers
|
||||
@@ -536,7 +524,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
dokployBackup: notification.dokployBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
userKey: notification.pushover?.userKey,
|
||||
@@ -575,7 +562,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy,
|
||||
dokployRestart,
|
||||
databaseBackup,
|
||||
dokployBackup,
|
||||
volumeBackup,
|
||||
dockerCleanup,
|
||||
serverThreshold,
|
||||
@@ -587,7 +573,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
channel: data.channel,
|
||||
@@ -603,7 +588,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
botToken: data.botToken,
|
||||
messageThreadId: data.messageThreadId || "",
|
||||
@@ -620,7 +604,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
decoration: data.decoration,
|
||||
@@ -636,7 +619,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
smtpServer: data.smtpServer,
|
||||
smtpPort: data.smtpPort,
|
||||
@@ -656,7 +638,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
apiKey: data.apiKey,
|
||||
fromAddress: data.fromAddress,
|
||||
@@ -673,7 +654,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
serverUrl: data.serverUrl,
|
||||
appToken: data.appToken,
|
||||
@@ -690,7 +670,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
serverUrl: data.serverUrl,
|
||||
accessToken: data.accessToken || "",
|
||||
@@ -707,7 +686,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
channel: data.channel || undefined,
|
||||
@@ -724,7 +702,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
name: data.name,
|
||||
@@ -739,7 +716,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
webhookUrl: data.webhookUrl,
|
||||
name: data.name,
|
||||
@@ -766,7 +742,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
endpoint: data.endpoint,
|
||||
headers: headersRecord,
|
||||
@@ -786,7 +761,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
dokployBackup: dokployBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
userKey: data.userKey,
|
||||
apiToken: data.apiToken,
|
||||
@@ -1882,27 +1856,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dokployBackup"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Dokploy Backup</FormLabel>
|
||||
<FormDescription>
|
||||
Trigger the action when a dokploy backup is created.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="volumeBackup"
|
||||
|
||||
@@ -161,7 +161,7 @@ export const SetupServer = ({ serverId, asButton = false }: Props) => {
|
||||
<ul>
|
||||
<li>
|
||||
1. Add the public SSH Key when you create a server in your
|
||||
preferred provider (Hostinger, Digital Ocean, Hetzner,
|
||||
preffered provider (Hostinger, Digital Ocean, Hetzner,
|
||||
etc){" "}
|
||||
</li>
|
||||
<li>2. Add The SSH Key to Server Manually</li>
|
||||
|
||||
@@ -48,7 +48,7 @@ import { ShowMonitoringModal } from "./show-monitoring-modal";
|
||||
import { ShowSchedulesModal } from "./show-schedules-modal";
|
||||
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
|
||||
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
|
||||
import { WelcomeSubscription } from "./welcome-stripe/welcome-subscription";
|
||||
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
|
||||
|
||||
export const ShowServers = () => {
|
||||
const router = useRouter();
|
||||
@@ -63,7 +63,7 @@ export const ShowServers = () => {
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{query?.success && isCloud && <WelcomeSubscription />}
|
||||
{query?.success && isCloud && <WelcomeSuscription />}
|
||||
<Card className="h-full p-2.5 rounded-xl max-w-5xl mx-auto">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<CardHeader className="">
|
||||
|
||||
@@ -51,7 +51,7 @@ export const { useStepper, steps, Scoped } = defineStepper(
|
||||
{ id: "complete", title: "Complete", description: "Checkout complete" },
|
||||
);
|
||||
|
||||
export const WelcomeSubscription = () => {
|
||||
export const WelcomeSuscription = () => {
|
||||
const [showConfetti, setShowConfetti] = useState(false);
|
||||
const stepper = useStepper();
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { EnterpriseFeatureLocked } from "@/components/proprietary/enterprise-feature-gate";
|
||||
import { api, type RouterOutputs } from "@/utils/api";
|
||||
|
||||
/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */
|
||||
@@ -171,7 +170,6 @@ const addPermissions = z.object({
|
||||
accessedProjects: z.array(z.string()).optional(),
|
||||
accessedEnvironments: z.array(z.string()).optional(),
|
||||
accessedServices: z.array(z.string()).optional(),
|
||||
accessedGitProviders: z.array(z.string()).optional(),
|
||||
canCreateProjects: z.boolean().optional().default(false),
|
||||
canCreateServices: z.boolean().optional().default(false),
|
||||
canDeleteProjects: z.boolean().optional().default(false),
|
||||
@@ -198,15 +196,6 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
const { data: projects } = api.project.allForPermissions.useQuery(undefined, {
|
||||
enabled: isOpen,
|
||||
});
|
||||
const { data: haveValidLicense } =
|
||||
api.licenseKey.haveValidLicenseKey.useQuery();
|
||||
|
||||
const { data: gitProviders } = api.gitProvider.allForPermissions.useQuery(
|
||||
undefined,
|
||||
{
|
||||
enabled: isOpen && !!haveValidLicense,
|
||||
},
|
||||
);
|
||||
|
||||
const { data, refetch } = api.user.one.useQuery(
|
||||
{
|
||||
@@ -225,7 +214,6 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
accessedProjects: [],
|
||||
accessedEnvironments: [],
|
||||
accessedServices: [],
|
||||
accessedGitProviders: [],
|
||||
canDeleteEnvironments: false,
|
||||
canCreateProjects: false,
|
||||
canCreateServices: false,
|
||||
@@ -247,7 +235,6 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedEnvironments: data.accessedEnvironments || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
accessedGitProviders: data.accessedGitProviders || [],
|
||||
canCreateProjects: data.canCreateProjects,
|
||||
canCreateServices: data.canCreateServices,
|
||||
canDeleteProjects: data.canDeleteProjects,
|
||||
@@ -275,7 +262,6 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedEnvironments: data.accessedEnvironments || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
accessedGitProviders: data.accessedGitProviders || [],
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||
@@ -884,78 +870,6 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{haveValidLicense ? (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accessedGitProviders"
|
||||
render={() => (
|
||||
<FormItem className="md:col-span-2">
|
||||
<div className="mb-4">
|
||||
<FormLabel className="text-base">Git Providers</FormLabel>
|
||||
<FormDescription>
|
||||
Select the Git Providers that the user can access
|
||||
</FormDescription>
|
||||
</div>
|
||||
{gitProviders?.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No git providers found
|
||||
</p>
|
||||
)}
|
||||
<div className="grid md:grid-cols-1 gap-2">
|
||||
{gitProviders?.map((provider) => (
|
||||
<FormField
|
||||
key={provider.gitProviderId}
|
||||
control={form.control}
|
||||
name="accessedGitProviders"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center space-x-3 space-y-0 rounded-lg border p-3">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(
|
||||
provider.gitProviderId,
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
field.onChange([
|
||||
...(field.value || []),
|
||||
provider.gitProviderId,
|
||||
]);
|
||||
} else {
|
||||
field.onChange(
|
||||
field.value?.filter(
|
||||
(v) => v !== provider.gitProviderId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel className="text-sm cursor-pointer">
|
||||
{provider.name}
|
||||
</FormLabel>
|
||||
<span className="text-xs text-muted-foreground capitalize">
|
||||
({provider.providerType})
|
||||
</span>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className="md:col-span-2">
|
||||
<EnterpriseFeatureLocked
|
||||
compact
|
||||
title="Git Provider Assignment"
|
||||
description="Assign specific Git Providers to users with an Enterprise license."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2">
|
||||
<Button
|
||||
isLoading={isPending}
|
||||
|
||||
@@ -153,7 +153,7 @@ export const ChangeRole = ({ memberId, currentRole, userEmail }: Props) => {
|
||||
)}
|
||||
<br />
|
||||
<em className="text-muted-foreground text-xs">
|
||||
Note: Owner role is nontransferable.
|
||||
Note: Owner role is intransferible.
|
||||
</em>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
|
||||
@@ -122,7 +122,7 @@ export const ShowUsers = () => {
|
||||
// Can change role based on hierarchy:
|
||||
// - Owner: Can change anyone's role (except themselves and other owners)
|
||||
// - Admin: Can only change member/custom roles (not other admins or owners)
|
||||
// - Owner role is nontransferable
|
||||
// - Owner role is intransferible
|
||||
const canChangeRole =
|
||||
member.role !== "owner" &&
|
||||
member.user.id !== session?.user?.id &&
|
||||
|
||||
@@ -101,7 +101,7 @@ const BreadcrumbEllipsis = ({
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
);
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis";
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
|
||||
@@ -19,7 +19,7 @@ interface TreeDataItem {
|
||||
|
||||
type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||
data: TreeDataItem[] | TreeDataItem;
|
||||
initialSelectedItemId?: string;
|
||||
initialSlelectedItemId?: string;
|
||||
onSelectChange?: (item: TreeDataItem | undefined) => void;
|
||||
expandAll?: boolean;
|
||||
folderIcon?: LucideIcon;
|
||||
@@ -30,7 +30,7 @@ const Tree = React.forwardRef<HTMLDivElement, TreeProps>(
|
||||
(
|
||||
{
|
||||
data,
|
||||
initialSelectedItemId,
|
||||
initialSlelectedItemId,
|
||||
onSelectChange,
|
||||
expandAll,
|
||||
folderIcon,
|
||||
@@ -42,7 +42,7 @@ const Tree = React.forwardRef<HTMLDivElement, TreeProps>(
|
||||
) => {
|
||||
const [selectedItemId, setSelectedItemId] = React.useState<
|
||||
string | undefined
|
||||
>(initialSelectedItemId);
|
||||
>(initialSlelectedItemId);
|
||||
|
||||
const handleSelectChange = React.useCallback(
|
||||
(item: TreeDataItem | undefined) => {
|
||||
@@ -55,7 +55,7 @@ const Tree = React.forwardRef<HTMLDivElement, TreeProps>(
|
||||
);
|
||||
|
||||
const expandedItemIds = React.useMemo(() => {
|
||||
if (!initialSelectedItemId) {
|
||||
if (!initialSlelectedItemId) {
|
||||
return [] as string[];
|
||||
}
|
||||
|
||||
@@ -81,9 +81,9 @@ const Tree = React.forwardRef<HTMLDivElement, TreeProps>(
|
||||
}
|
||||
}
|
||||
|
||||
walkTreeItems(data, initialSelectedItemId);
|
||||
walkTreeItems(data, initialSlelectedItemId);
|
||||
return ids;
|
||||
}, [data, initialSelectedItemId]);
|
||||
}, [data, initialSlelectedItemId]);
|
||||
|
||||
const { ref: refRoot } = useResizeObserver();
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE "domain" ADD COLUMN "customEntrypoint" text;
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE "destination" ADD COLUMN "additionalFlags" text[];
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE "notification" ADD COLUMN "dokployBackup" boolean DEFAULT false NOT NULL;
|
||||
@@ -1,2 +0,0 @@
|
||||
ALTER TABLE "member" ADD COLUMN "accessedGitProviders" text[] DEFAULT ARRAY[]::text[] NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "git_provider" ADD COLUMN "sharedWithOrganization" boolean DEFAULT false NOT NULL;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1086,27 +1086,6 @@
|
||||
"when": 1774337356154,
|
||||
"tag": "0154_careful_eternals",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 155,
|
||||
"version": "7",
|
||||
"when": 1774794547865,
|
||||
"tag": "0155_careless_clea",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 156,
|
||||
"version": "7",
|
||||
"when": 1774910955774,
|
||||
"tag": "0156_fair_vargas",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 157,
|
||||
"version": "7",
|
||||
"when": 1775246622387,
|
||||
"tag": "0157_stiff_misty_knight",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { apiKeyClient } from "@better-auth/api-key/client";
|
||||
import { ssoClient } from "@better-auth/sso/client";
|
||||
import { apiKeyClient } from "@better-auth/api-key/client";
|
||||
import {
|
||||
adminClient,
|
||||
inferAdditionalFields,
|
||||
|
||||
@@ -196,7 +196,7 @@ export default async function handler(
|
||||
return;
|
||||
}
|
||||
|
||||
const committedPaths = await extractCommittedPaths(
|
||||
const commitedPaths = await extractCommitedPaths(
|
||||
req.body,
|
||||
application.bitbucket,
|
||||
application.bitbucketRepositorySlug ||
|
||||
@@ -206,7 +206,7 @@ export default async function handler(
|
||||
|
||||
const shouldDeployPaths = shouldDeploy(
|
||||
application.watchPaths,
|
||||
committedPaths,
|
||||
commitedPaths,
|
||||
);
|
||||
|
||||
if (!shouldDeployPaths) {
|
||||
@@ -538,7 +538,7 @@ export const getProviderByHeader = (headers: any) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export const extractCommittedPaths = async (
|
||||
export const extractCommitedPaths = async (
|
||||
body: any,
|
||||
bitbucket: Bitbucket | null,
|
||||
repository: string,
|
||||
@@ -548,7 +548,7 @@ export const extractCommittedPaths = async (
|
||||
const commitHashes = changes
|
||||
.map((change: any) => change.new?.target?.hash)
|
||||
.filter(Boolean);
|
||||
const committedPaths: string[] = [];
|
||||
const commitedPaths: string[] = [];
|
||||
const username =
|
||||
bitbucket?.bitbucketWorkspaceName || bitbucket?.bitbucketUsername || "";
|
||||
for (const commit of commitHashes) {
|
||||
@@ -559,7 +559,7 @@ export const extractCommittedPaths = async (
|
||||
});
|
||||
const data = await response.json();
|
||||
for (const value of data.values) {
|
||||
if (value?.new?.path) committedPaths.push(value.new.path);
|
||||
if (value?.new?.path) commitedPaths.push(value.new.path);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -571,5 +571,5 @@ export const extractCommittedPaths = async (
|
||||
}
|
||||
}
|
||||
|
||||
return committedPaths;
|
||||
return commitedPaths;
|
||||
};
|
||||
|
||||
@@ -8,8 +8,8 @@ import { myQueue } from "@/server/queues/queueSetup";
|
||||
import { deploy } from "@/server/utils/deploy";
|
||||
import {
|
||||
extractBranchName,
|
||||
extractCommitedPaths,
|
||||
extractCommitMessage,
|
||||
extractCommittedPaths,
|
||||
extractHash,
|
||||
getProviderByHeader,
|
||||
} from "../[refreshToken]";
|
||||
@@ -97,7 +97,7 @@ export default async function handler(
|
||||
return;
|
||||
}
|
||||
|
||||
const committedPaths = await extractCommittedPaths(
|
||||
const commitedPaths = await extractCommitedPaths(
|
||||
req.body,
|
||||
composeResult.bitbucket,
|
||||
composeResult.bitbucketRepositorySlug ||
|
||||
@@ -107,7 +107,7 @@ export default async function handler(
|
||||
|
||||
const shouldDeployPaths = shouldDeploy(
|
||||
composeResult.watchPaths,
|
||||
committedPaths,
|
||||
commitedPaths,
|
||||
);
|
||||
|
||||
if (!shouldDeployPaths) {
|
||||
|
||||
@@ -174,27 +174,27 @@ export default async function handler(
|
||||
case "invoice.payment_succeeded": {
|
||||
const newInvoice = event.data.object as Stripe.Invoice;
|
||||
|
||||
const subscription = await stripe.subscriptions.retrieve(
|
||||
const suscription = await stripe.subscriptions.retrieve(
|
||||
newInvoice.subscription as string,
|
||||
);
|
||||
|
||||
if (subscription.status !== "active") {
|
||||
if (suscription.status !== "active") {
|
||||
console.log(
|
||||
`Skipping invoice.payment_succeeded for subscription ${subscription.id} with status ${subscription.status}`,
|
||||
`Skipping invoice.payment_succeeded for subscription ${suscription.id} with status ${suscription.status}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
const serversQuantity = getSubscriptionServersQuantity(
|
||||
subscription?.items?.data ?? [],
|
||||
suscription?.items?.data ?? [],
|
||||
);
|
||||
await db
|
||||
.update(user)
|
||||
.set({ serversQuantity })
|
||||
.where(eq(user.stripeCustomerId, subscription.customer as string));
|
||||
.where(eq(user.stripeCustomerId, suscription.customer as string));
|
||||
|
||||
const admin = await findUserByStripeCustomerId(
|
||||
subscription.customer as string,
|
||||
suscription.customer as string,
|
||||
);
|
||||
|
||||
if (!admin) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ShowImport } from "@/components/dashboard/application/advanced/import/s
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowDeployments } from "@/components/dashboard/application/deployments/show-deployments";
|
||||
import { ShowDomains } from "@/components/dashboard/application/domains/show-domains";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowPatches } from "@/components/dashboard/application/patches/show-patches";
|
||||
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
||||
import { ShowVolumeBackups } from "@/components/dashboard/application/volume-backups/show-volume-backups";
|
||||
|
||||
@@ -10,7 +10,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
|
||||
@@ -10,7 +10,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
|
||||
@@ -10,7 +10,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
|
||||
@@ -10,7 +10,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
|
||||
@@ -10,7 +10,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
|
||||
@@ -10,7 +10,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
|
||||
@@ -3,10 +3,10 @@ import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ShowInvitations } from "@/components/dashboard/settings/users/show-invitations";
|
||||
import { ShowUsers } from "@/components/dashboard/settings/users/show-users";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { ManageCustomRoles } from "@/components/proprietary/roles/manage-custom-roles";
|
||||
import { ShowInvitations } from "@/components/dashboard/settings/users/show-invitations";
|
||||
import { ShowUsers } from "@/components/dashboard/settings/users/show-users";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ import {
|
||||
suggestVariants,
|
||||
} from "@dokploy/server/services/ai";
|
||||
import { createComposeByTemplate } from "@dokploy/server/services/compose";
|
||||
import { findProjectById } from "@dokploy/server/services/project";
|
||||
import {
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
} from "@dokploy/server/services/permission";
|
||||
import { findProjectById } from "@dokploy/server/services/project";
|
||||
import {
|
||||
getProviderHeaders,
|
||||
getProviderName,
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
updateBackupById,
|
||||
} from "@dokploy/server";
|
||||
import { findDestinationById } from "@dokploy/server/services/destination";
|
||||
import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission";
|
||||
import { runComposeBackup } from "@dokploy/server/utils/backups/compose";
|
||||
import {
|
||||
getS3Credentials,
|
||||
@@ -54,6 +53,7 @@ import {
|
||||
protectedProcedure,
|
||||
withPermission,
|
||||
} from "@/server/api/trpc";
|
||||
import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
apiCreateBackup,
|
||||
@@ -545,42 +545,52 @@ export const backupRouter = createTRPCRouter({
|
||||
}
|
||||
const destination = await findDestinationById(input.destinationId);
|
||||
const queue: string[] = [];
|
||||
let done = false;
|
||||
const onLog = (log: string) => queue.push(log);
|
||||
const runRestore = async () => {
|
||||
if (input.backupType === "database") {
|
||||
if (input.databaseType === "postgres") {
|
||||
const postgres = await findPostgresById(input.databaseId);
|
||||
await restorePostgresBackup(postgres, destination, input, onLog);
|
||||
} else if (input.databaseType === "mysql") {
|
||||
const mysql = await findMySqlById(input.databaseId);
|
||||
await restoreMySqlBackup(mysql, destination, input, onLog);
|
||||
} else if (input.databaseType === "mariadb") {
|
||||
const mariadb = await findMariadbById(input.databaseId);
|
||||
await restoreMariadbBackup(mariadb, destination, input, onLog);
|
||||
} else if (input.databaseType === "mongo") {
|
||||
const mongo = await findMongoById(input.databaseId);
|
||||
await restoreMongoBackup(mongo, destination, input, onLog);
|
||||
} else if (input.databaseType === "libsql") {
|
||||
const libsql = await findLibsqlById(input.databaseId);
|
||||
await restoreLibsqlBackup(libsql, destination, input, onLog);
|
||||
} else if (input.databaseType === "web-server") {
|
||||
await restoreWebServerBackup(destination, input.backupFile, onLog);
|
||||
}
|
||||
} else if (input.backupType === "compose") {
|
||||
const compose = await findComposeById(input.databaseId);
|
||||
await restoreComposeBackup(compose, destination, input, onLog);
|
||||
const done = false;
|
||||
if (input.backupType === "database") {
|
||||
if (input.databaseType === "postgres") {
|
||||
const postgres = await findPostgresById(input.databaseId);
|
||||
|
||||
restorePostgresBackup(postgres, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
};
|
||||
runRestore()
|
||||
.catch((error) => {
|
||||
onLog(
|
||||
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
|
||||
if (input.databaseType === "mysql") {
|
||||
const mysql = await findMySqlById(input.databaseId);
|
||||
restoreMySqlBackup(mysql, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "mariadb") {
|
||||
const mariadb = await findMariadbById(input.databaseId);
|
||||
restoreMariadbBackup(mariadb, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "mongo") {
|
||||
const mongo = await findMongoById(input.databaseId);
|
||||
restoreMongoBackup(mongo, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "libsql") {
|
||||
const libsql = await findLibsqlById(input.databaseId);
|
||||
restoreLibsqlBackup(libsql, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
if (input.databaseType === "web-server") {
|
||||
restoreWebServerBackup(destination, input.backupFile, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (input.backupType === "compose") {
|
||||
const compose = await findComposeById(input.databaseId);
|
||||
restoreComposeBackup(compose, destination, input, (log) => {
|
||||
queue.push(log);
|
||||
});
|
||||
}
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
yield queue.shift()!;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
createBitbucket,
|
||||
findBitbucketById,
|
||||
getAccessibleGitProviderIds,
|
||||
getBitbucketBranches,
|
||||
getBitbucketRepositories,
|
||||
testBitbucketConnection,
|
||||
@@ -55,8 +54,6 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
return await findBitbucketById(input.bitbucketId);
|
||||
}),
|
||||
bitbucketProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.bitbucket.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -70,7 +67,7 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
return (
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId)
|
||||
provider.gitProvider.userId === ctx.session.userId
|
||||
);
|
||||
});
|
||||
return result;
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createTRPCRouter, withPermission } from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { createTRPCRouter, withPermission } from "@/server/api/trpc";
|
||||
import {
|
||||
apiCreateCertificate,
|
||||
apiFindCertificate,
|
||||
|
||||
@@ -31,13 +31,13 @@ import {
|
||||
updateCompose,
|
||||
updateDeploymentStatus,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
checkServicePermissionAndAccess,
|
||||
findMemberByUserId,
|
||||
} from "@dokploy/server/services/permission";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
type CompleteTemplate,
|
||||
fetchTemplateFiles,
|
||||
@@ -75,8 +75,8 @@ import {
|
||||
} from "@/server/queues/queueSetup";
|
||||
import { cancelDeployment, deploy } from "@/server/utils/deploy";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||
import { audit } from "../utils/audit";
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||
|
||||
export const composeRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
createDestination,
|
||||
createDestintation,
|
||||
execAsync,
|
||||
execAsyncRemote,
|
||||
findDestinationById,
|
||||
@@ -25,7 +25,7 @@ export const destinationRouter = createTRPCRouter({
|
||||
.input(apiCreateDestination)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const result = await createDestination(
|
||||
const result = await createDestintation(
|
||||
input,
|
||||
ctx.session.activeOrganizationId,
|
||||
);
|
||||
@@ -47,15 +47,8 @@ export const destinationRouter = createTRPCRouter({
|
||||
testConnection: withPermission("destination", "create")
|
||||
.input(apiCreateDestination)
|
||||
.mutation(async ({ input }) => {
|
||||
const {
|
||||
secretAccessKey,
|
||||
bucket,
|
||||
region,
|
||||
endpoint,
|
||||
accessKey,
|
||||
provider,
|
||||
additionalFlags,
|
||||
} = input;
|
||||
const { secretAccessKey, bucket, region, endpoint, accessKey, provider } =
|
||||
input;
|
||||
try {
|
||||
const rcloneFlags = [
|
||||
`--s3-access-key-id="${accessKey}"`,
|
||||
@@ -72,9 +65,6 @@ export const destinationRouter = createTRPCRouter({
|
||||
if (provider) {
|
||||
rcloneFlags.unshift(`--s3-provider="${provider}"`);
|
||||
}
|
||||
if (additionalFlags?.length) {
|
||||
rcloneFlags.push(...additionalFlags);
|
||||
}
|
||||
const rcloneDestination = `:s3:${bucket}`;
|
||||
const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
|
||||
|
||||
@@ -169,14 +159,7 @@ export const destinationRouter = createTRPCRouter({
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
error instanceof Error
|
||||
? error?.message
|
||||
: "Error connecting to bucket",
|
||||
cause: error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,34 +1,18 @@
|
||||
import {
|
||||
findGitProviderById,
|
||||
getAccessibleGitProviderIds,
|
||||
removeGitProvider,
|
||||
updateGitProvider,
|
||||
} from "@dokploy/server";
|
||||
import { findGitProviderById, removeGitProvider } from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { desc, eq, inArray } from "drizzle-orm";
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
withPermission,
|
||||
} from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
apiRemoveGitProvider,
|
||||
apiToggleShareGitProvider,
|
||||
gitProvider,
|
||||
} from "@/server/db/schema";
|
||||
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
|
||||
|
||||
export const gitProviderRouter = createTRPCRouter({
|
||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
if (accessibleIds.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = await db.query.gitProvider.findMany({
|
||||
return await db.query.gitProvider.findMany({
|
||||
with: {
|
||||
gitlab: true,
|
||||
bitbucket: true,
|
||||
@@ -36,65 +20,12 @@ export const gitProviderRouter = createTRPCRouter({
|
||||
gitea: true,
|
||||
},
|
||||
orderBy: desc(gitProvider.createdAt),
|
||||
where: inArray(gitProvider.gitProviderId, [...accessibleIds]),
|
||||
where: and(
|
||||
eq(gitProvider.userId, ctx.session.userId),
|
||||
eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
),
|
||||
});
|
||||
|
||||
return results.map((r) => ({
|
||||
...r,
|
||||
isOwner: r.userId === ctx.session.userId,
|
||||
}));
|
||||
}),
|
||||
|
||||
toggleShare: protectedProcedure
|
||||
.input(apiToggleShareGitProvider)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const provider = await findGitProviderById(input.gitProviderId);
|
||||
|
||||
if (
|
||||
provider.userId !== ctx.session.userId ||
|
||||
provider.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Only the owner can share this provider",
|
||||
});
|
||||
}
|
||||
|
||||
await audit(ctx, {
|
||||
action: "update",
|
||||
resourceType: "gitProvider",
|
||||
resourceId: provider.gitProviderId,
|
||||
resourceName: provider.name ?? provider.gitProviderId,
|
||||
});
|
||||
|
||||
return await updateGitProvider(input.gitProviderId, {
|
||||
sharedWithOrganization: input.sharedWithOrganization,
|
||||
});
|
||||
}),
|
||||
|
||||
allForPermissions: withPermission("member", "update")
|
||||
.use(async ({ ctx, next }) => {
|
||||
const licensed = await hasValidLicense(ctx.session.activeOrganizationId);
|
||||
if (!licensed) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Valid enterprise license required",
|
||||
});
|
||||
}
|
||||
return next();
|
||||
})
|
||||
.query(async ({ ctx }) => {
|
||||
return await db.query.gitProvider.findMany({
|
||||
columns: {
|
||||
gitProviderId: true,
|
||||
name: true,
|
||||
providerType: true,
|
||||
},
|
||||
orderBy: desc(gitProvider.createdAt),
|
||||
where: eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
});
|
||||
}),
|
||||
|
||||
remove: withPermission("gitProviders", "delete")
|
||||
.input(apiRemoveGitProvider)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
createGitea,
|
||||
findGiteaById,
|
||||
getAccessibleGitProviderIds,
|
||||
getGiteaBranches,
|
||||
getGiteaRepositories,
|
||||
haveGiteaRequirements,
|
||||
@@ -58,8 +57,6 @@ export const giteaRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
giteaProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.gitea.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -70,7 +67,7 @@ export const giteaRouter = createTRPCRouter({
|
||||
(provider) =>
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId),
|
||||
provider.gitProvider.userId === ctx.session.userId,
|
||||
);
|
||||
|
||||
const filtered = result
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
findGithubById,
|
||||
getAccessibleGitProviderIds,
|
||||
getGithubBranches,
|
||||
getGithubRepositories,
|
||||
haveGithubRequirements,
|
||||
@@ -36,8 +35,6 @@ export const githubRouter = createTRPCRouter({
|
||||
return await getGithubBranches(input);
|
||||
}),
|
||||
githubProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.github.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -48,7 +45,7 @@ export const githubRouter = createTRPCRouter({
|
||||
(provider) =>
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId),
|
||||
provider.gitProvider.userId === ctx.session.userId,
|
||||
);
|
||||
|
||||
const filtered = result
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
createGitlab,
|
||||
findGitlabById,
|
||||
getAccessibleGitProviderIds,
|
||||
getGitlabBranches,
|
||||
getGitlabRepositories,
|
||||
haveGitlabRequirements,
|
||||
@@ -55,8 +54,6 @@ export const gitlabRouter = createTRPCRouter({
|
||||
return await findGitlabById(input.gitlabId);
|
||||
}),
|
||||
gitlabProviders: protectedProcedure.query(async ({ ctx }) => {
|
||||
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
|
||||
|
||||
let result = await db.query.gitlab.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
@@ -67,7 +64,7 @@ export const gitlabRouter = createTRPCRouter({
|
||||
return (
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId &&
|
||||
accessibleIds.has(provider.gitProvider.gitProviderId)
|
||||
provider.gitProvider.userId === ctx.session.userId
|
||||
);
|
||||
});
|
||||
const filtered = result
|
||||
|
||||
@@ -246,15 +246,11 @@ export const libsqlRouter = createTRPCRouter({
|
||||
deployment: ["create"],
|
||||
});
|
||||
const queue: string[] = [];
|
||||
let done = false;
|
||||
const done = false;
|
||||
|
||||
deployLibsql(input.libsqlId, (log) => {
|
||||
queue.push(log);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -17,18 +17,18 @@ import {
|
||||
stopServiceRemote,
|
||||
updateMongoById,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
checkServicePermissionAndAccess,
|
||||
findMemberByUserId,
|
||||
} from "@dokploy/server/services/permission";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import {
|
||||
apiChangeMongoStatus,
|
||||
apiCreateMongo,
|
||||
@@ -39,10 +39,9 @@ import {
|
||||
apiSaveEnvironmentVariablesMongo,
|
||||
apiSaveExternalPortMongo,
|
||||
apiUpdateMongo,
|
||||
environments,
|
||||
mongo as mongoTable,
|
||||
projects,
|
||||
} from "@/server/db/schema";
|
||||
import { environments, projects } from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
export const mongoRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -229,15 +228,11 @@ export const mongoRouter = createTRPCRouter({
|
||||
deployment: ["create"],
|
||||
});
|
||||
const queue: string[] = [];
|
||||
let done = false;
|
||||
const done = false;
|
||||
|
||||
deployMongo(input.mongoId, (log) => {
|
||||
queue.push(log);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -17,18 +17,18 @@ import {
|
||||
stopServiceRemote,
|
||||
updateMySqlById,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
checkServicePermissionAndAccess,
|
||||
findMemberByUserId,
|
||||
} from "@dokploy/server/services/permission";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import {
|
||||
apiChangeMySqlStatus,
|
||||
apiCreateMySql,
|
||||
@@ -230,15 +230,11 @@ export const mysqlRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
const queue: string[] = [];
|
||||
let done = false;
|
||||
const done = false;
|
||||
|
||||
deployMySql(input.mysqlId, (log) => {
|
||||
queue.push(log);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { IS_CLOUD } from "@dokploy/server/index";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, desc, eq, exists } from "drizzle-orm";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
invitation,
|
||||
member,
|
||||
@@ -409,11 +409,11 @@ export const organizationRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
// Owner role is nontransferable - cannot change to or from owner
|
||||
// Owner role is intransferible - cannot change to or from owner
|
||||
if (target.role === "owner" || input.role === "owner") {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "The owner role is nontransferable",
|
||||
message: "The owner role is intransferible",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,18 +18,18 @@ import {
|
||||
stopServiceRemote,
|
||||
updatePostgresById,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
checkServicePermissionAndAccess,
|
||||
findMemberByUserId,
|
||||
} from "@dokploy/server/services/permission";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import {
|
||||
apiChangePostgresStatus,
|
||||
apiCreatePostgres,
|
||||
@@ -40,10 +40,9 @@ import {
|
||||
apiSaveEnvironmentVariablesPostgres,
|
||||
apiSaveExternalPortPostgres,
|
||||
apiUpdatePostgres,
|
||||
environments,
|
||||
postgres as postgresTable,
|
||||
projects,
|
||||
} from "@/server/db/schema";
|
||||
import { environments, projects } from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
|
||||
export const postgresRouter = createTRPCRouter({
|
||||
@@ -234,15 +233,11 @@ export const postgresRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
const queue: string[] = [];
|
||||
let done = false;
|
||||
const done = false;
|
||||
|
||||
deployPostgres(input.postgresId, (log) => {
|
||||
queue.push(log);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -143,12 +143,7 @@ export const licenseKeyRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await deactivateLicenseKey(currentUser.licenseKey);
|
||||
} catch (err) {
|
||||
console.error("Failed to deactivate license key remotely:", err);
|
||||
}
|
||||
|
||||
await deactivateLicenseKey(currentUser.licenseKey);
|
||||
await db
|
||||
.update(user)
|
||||
.set({
|
||||
|
||||
@@ -16,18 +16,18 @@ import {
|
||||
stopServiceRemote,
|
||||
updateRedisById,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
checkServicePermissionAndAccess,
|
||||
findMemberByUserId,
|
||||
} from "@dokploy/server/services/permission";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import {
|
||||
apiChangeRedisStatus,
|
||||
apiCreateRedis,
|
||||
@@ -38,10 +38,9 @@ import {
|
||||
apiSaveEnvironmentVariablesRedis,
|
||||
apiSaveExternalPortRedis,
|
||||
apiUpdateRedis,
|
||||
environments,
|
||||
projects,
|
||||
redis as redisTable,
|
||||
} from "@/server/db/schema";
|
||||
import { environments, projects } from "@/server/db/schema";
|
||||
export const redisRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateRedis)
|
||||
@@ -252,15 +251,11 @@ export const redisRouter = createTRPCRouter({
|
||||
deployment: ["create"],
|
||||
});
|
||||
const queue: string[] = [];
|
||||
let done = false;
|
||||
const done = false;
|
||||
|
||||
deployRedis(input.redisId, (log) => {
|
||||
queue.push(log);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
});
|
||||
|
||||
while (!done || queue.length > 0) {
|
||||
if (queue.length > 0) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
apiCreateRegistry,
|
||||
apiFindOneRegistry,
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
apiUpdateRegistry,
|
||||
registry,
|
||||
} from "@/server/db/schema";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { createTRPCRouter, withPermission } from "../trpc";
|
||||
export const registryRouter = createTRPCRouter({
|
||||
create: withPermission("registry", "create")
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
updateScheduleSchema,
|
||||
} from "@dokploy/server/db/schema/schedule";
|
||||
import { runCommand } from "@dokploy/server/index";
|
||||
import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission";
|
||||
import {
|
||||
createSchedule,
|
||||
deleteSchedule,
|
||||
@@ -15,10 +14,11 @@ import {
|
||||
updateSchedule,
|
||||
} from "@dokploy/server/services/schedule";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { asc, desc, eq } from "drizzle-orm";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { removeJob, schedule } from "@/server/utils/backup";
|
||||
import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
export const scheduleRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -157,7 +157,6 @@ export const scheduleRouter = createTRPCRouter({
|
||||
};
|
||||
return db.query.schedules.findMany({
|
||||
where: where[input.scheduleType],
|
||||
orderBy: [asc(schedules.createdAt)],
|
||||
with: {
|
||||
application: true,
|
||||
server: true,
|
||||
|
||||
@@ -21,12 +21,12 @@ import { observable } from "@trpc/server/observable";
|
||||
import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { updateServersBasedOnQuantity } from "@/pages/api/stripe/webhook";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
withPermission,
|
||||
} from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
apiCreateServer,
|
||||
apiFindOneServer,
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { createTRPCRouter, withPermission } from "@/server/api/trpc";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import { createTRPCRouter, withPermission } from "@/server/api/trpc";
|
||||
import {
|
||||
apiCreateSshKey,
|
||||
apiFindOneSshKey,
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
updateUser,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||
import {
|
||||
account,
|
||||
apiAssignPermissions,
|
||||
@@ -345,19 +344,12 @@ export const userRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const { id, accessedGitProviders, ...rest } = input;
|
||||
|
||||
const licensed = await hasValidLicense(
|
||||
ctx.session?.activeOrganizationId || "",
|
||||
);
|
||||
const { id, ...rest } = input;
|
||||
|
||||
await db
|
||||
.update(member)
|
||||
.set({
|
||||
...rest,
|
||||
...(licensed && accessedGitProviders !== undefined
|
||||
? { accessedGitProviders }
|
||||
: {}),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
|
||||
@@ -106,7 +106,7 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
|
||||
* 2. INITIALIZATION
|
||||
*
|
||||
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse
|
||||
* ZodErrors so that you get type safety on the frontend if your procedure fails due to validation
|
||||
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
|
||||
* errors on the backend.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AuditAction, AuditResourceType } from "@dokploy/server/db/schema";
|
||||
import { createAuditLog } from "@dokploy/server/services/proprietary/audit-log";
|
||||
import type { AuditAction, AuditResourceType } from "@dokploy/server/db/schema";
|
||||
|
||||
interface AuditCtx {
|
||||
user: { id: string; email: string; role: string };
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
boolean,
|
||||
integer,
|
||||
index,
|
||||
uniqueIndex,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
|
||||
@@ -163,10 +163,6 @@ export const member = pgTable("member", {
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`ARRAY[]::text[]`),
|
||||
accessedGitProviders: text("accessedGitProviders")
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`ARRAY[]::text[]`),
|
||||
});
|
||||
|
||||
export const memberRelations = relations(member, ({ one }) => ({
|
||||
|
||||
@@ -3,10 +3,6 @@ import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
ADDITIONAL_FLAG_ERROR,
|
||||
ADDITIONAL_FLAG_REGEX,
|
||||
} from "../validations/destination";
|
||||
import { organization } from "./account";
|
||||
import { backups } from "./backups";
|
||||
|
||||
@@ -22,7 +18,6 @@ export const destinations = pgTable("destination", {
|
||||
bucket: text("bucket").notNull(),
|
||||
region: text("region").notNull(),
|
||||
endpoint: text("endpoint").notNull(),
|
||||
additionalFlags: text("additionalFlags").array(),
|
||||
organizationId: text("organizationId")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
@@ -49,9 +44,6 @@ const createSchema = createInsertSchema(destinations, {
|
||||
endpoint: z.string(),
|
||||
secretAccessKey: z.string(),
|
||||
region: z.string(),
|
||||
additionalFlags: z
|
||||
.array(z.string().regex(ADDITIONAL_FLAG_REGEX, ADDITIONAL_FLAG_ERROR))
|
||||
.default([]),
|
||||
});
|
||||
|
||||
export const apiCreateDestination = createSchema
|
||||
@@ -63,7 +55,6 @@ export const apiCreateDestination = createSchema
|
||||
region: true,
|
||||
endpoint: true,
|
||||
secretAccessKey: true,
|
||||
additionalFlags: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
@@ -90,7 +81,6 @@ export const apiUpdateDestination = createSchema
|
||||
secretAccessKey: true,
|
||||
destinationId: true,
|
||||
provider: true,
|
||||
additionalFlags: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
|
||||
@@ -31,7 +31,6 @@ export const domains = pgTable("domain", {
|
||||
host: text("host").notNull(),
|
||||
https: boolean("https").notNull().default(false),
|
||||
port: integer("port").default(3000),
|
||||
customEntrypoint: text("customEntrypoint"),
|
||||
path: text("path").default("/"),
|
||||
serviceName: text("serviceName"),
|
||||
domainType: domainType("domainType").default("application"),
|
||||
@@ -81,7 +80,6 @@ export const apiCreateDomain = createSchema.pick({
|
||||
host: true,
|
||||
path: true,
|
||||
port: true,
|
||||
customEntrypoint: true,
|
||||
https: true,
|
||||
applicationId: true,
|
||||
certificateType: true,
|
||||
@@ -115,7 +113,6 @@ export const apiUpdateDomain = createSchema
|
||||
host: true,
|
||||
path: true,
|
||||
port: true,
|
||||
customEntrypoint: true,
|
||||
https: true,
|
||||
certificateType: true,
|
||||
customCertResolver: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { organization } from "./account";
|
||||
@@ -32,9 +32,6 @@ export const gitProvider = pgTable("git_provider", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
sharedWithOrganization: boolean("sharedWithOrganization")
|
||||
.notNull()
|
||||
.default(false),
|
||||
});
|
||||
|
||||
export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
|
||||
@@ -67,8 +64,3 @@ export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
|
||||
export const apiRemoveGitProvider = z.object({
|
||||
gitProviderId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiToggleShareGitProvider = z.object({
|
||||
gitProviderId: z.string().min(1),
|
||||
sharedWithOrganization: z.boolean(),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from "./account";
|
||||
export * from "./ai";
|
||||
export * from "./application";
|
||||
export * from "./audit-log";
|
||||
export * from "./application";
|
||||
export * from "./backups";
|
||||
export * from "./bitbucket";
|
||||
export * from "./certificate";
|
||||
|
||||
@@ -179,9 +179,11 @@ export const apiCreateLibsql = createSchema
|
||||
}
|
||||
});
|
||||
|
||||
export const apiFindOneLibsql = z.object({
|
||||
libsqlId: z.string().min(1),
|
||||
});
|
||||
export const apiFindOneLibsql = createSchema
|
||||
.pick({
|
||||
libsqlId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiChangeLibsqlStatus = createSchema
|
||||
.pick({
|
||||
|
||||
@@ -149,13 +149,14 @@ export const apiRemoveMount = createSchema
|
||||
// })
|
||||
.required();
|
||||
|
||||
export const apiFindMountByApplicationId = z.object({
|
||||
serviceType: z
|
||||
.string()
|
||||
.min(1)
|
||||
.transform((val) => val as ServiceType),
|
||||
serviceId: z.string().min(1),
|
||||
});
|
||||
export const apiFindMountByApplicationId = createSchema
|
||||
.pick({
|
||||
serviceType: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
serviceId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdateMount = createSchema.partial().extend({
|
||||
mountId: z.string().min(1),
|
||||
|
||||
@@ -38,7 +38,6 @@ export const notifications = pgTable("notification", {
|
||||
databaseBackup: boolean("databaseBackup").notNull().default(false),
|
||||
volumeBackup: boolean("volumeBackup").notNull().default(false),
|
||||
dokployRestart: boolean("dokployRestart").notNull().default(false),
|
||||
dokployBackup: boolean("dokployBackup").notNull().default(false),
|
||||
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
|
||||
serverThreshold: boolean("serverThreshold").notNull().default(false),
|
||||
notificationType: notificationType("notificationType").notNull(),
|
||||
@@ -267,7 +266,6 @@ export const apiCreateSlack = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -296,7 +294,6 @@ export const apiCreateTelegram = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -327,7 +324,6 @@ export const apiCreateDiscord = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -359,7 +355,6 @@ export const apiCreateEmail = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -396,7 +391,6 @@ export const apiCreateResend = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -427,7 +421,6 @@ export const apiCreateGotify = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -462,7 +455,6 @@ export const apiCreateNtfy = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -494,7 +486,6 @@ export const apiCreateMattermost = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -512,7 +503,6 @@ export const apiCreateMattermost = notificationsSchema
|
||||
webhookUrl: true,
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
appDeploy: true,
|
||||
@@ -545,7 +535,6 @@ export const apiCreateCustom = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -573,7 +562,6 @@ export const apiCreateLark = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -600,7 +588,6 @@ export const apiCreateTeams = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -627,7 +614,6 @@ export const apiCreatePushover = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
@@ -662,7 +648,6 @@ export const apiUpdatePushover = z.object({
|
||||
expire: z.number().min(1).max(10800).nullish(),
|
||||
appBuildError: z.boolean().optional(),
|
||||
databaseBackup: z.boolean().optional(),
|
||||
dokployBackup: z.boolean().optional(),
|
||||
volumeBackup: z.boolean().optional(),
|
||||
dokployRestart: z.boolean().optional(),
|
||||
name: z.string().optional(),
|
||||
|
||||
@@ -126,7 +126,6 @@ export const apiAssignPermissions = createSchema
|
||||
accessedProjects: z.array(z.string()).optional(),
|
||||
accessedEnvironments: z.array(z.string()).optional(),
|
||||
accessedServices: z.array(z.string()).optional(),
|
||||
accessedGitProviders: z.array(z.string()).optional(),
|
||||
canCreateProjects: z.boolean().optional(),
|
||||
canCreateServices: z.boolean().optional(),
|
||||
canDeleteProjects: z.boolean().optional(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { serviceType } from "./mount";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@@ -10,7 +11,6 @@ import { destinations } from "./destination";
|
||||
import { libsql } from "./libsql";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { mongo } from "./mongo";
|
||||
import { serviceType } from "./mount";
|
||||
import { mysql } from "./mysql";
|
||||
import { postgres } from "./postgres";
|
||||
import { redis } from "./redis";
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const ADDITIONAL_FLAG_REGEX = /^--[a-zA-Z0-9-]+(=[a-zA-Z0-9._:/@-]+)?$/;
|
||||
export const ADDITIONAL_FLAG_ERROR =
|
||||
"Invalid flag format. Must start with -- (e.g. --s3-sign-accept-encoding=false)";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user