mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
1 Commits
v0.21.8
...
1541-regis
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd8230b0e5 |
@@ -52,7 +52,7 @@ feat: add new feature
|
|||||||
|
|
||||||
Before you start, please make the clone based on the `canary` branch, since the `main` branch is the source of truth and should always reflect the latest stable release, also the PRs will be merged to the `canary` branch.
|
Before you start, please make the clone based on the `canary` branch, since the `main` branch is the source of truth and should always reflect the latest stable release, also the PRs will be merged to the `canary` branch.
|
||||||
|
|
||||||
We use Node v20.9.0 and recommend this specific version. If you have nvm installed, you can run `nvm install 20.9.0 && nvm use` in the root directory.
|
We use Node v20.9.0
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/dokploy/dokploy.git
|
git clone https://github.com/dokploy/dokploy.git
|
||||||
@@ -87,8 +87,6 @@ pnpm run dokploy:dev
|
|||||||
|
|
||||||
Go to http://localhost:3000 to see the development server
|
Go to http://localhost:3000 to see the development server
|
||||||
|
|
||||||
Note: this project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off.
|
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -147,9 +145,11 @@ curl -sSL https://railpack.com/install.sh | sh
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install Buildpacks
|
# Install Buildpacks
|
||||||
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.35.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Pull Request
|
## Pull Request
|
||||||
|
|
||||||
- The `main` branch is the source of truth and should always reflect the latest stable release.
|
- The `main` branch is the source of truth and should always reflect the latest stable release.
|
||||||
@@ -167,6 +167,7 @@ Thank you for your contribution!
|
|||||||
|
|
||||||
To add a new template, go to `https://github.com/Dokploy/templates` repository and read the README.md file.
|
To add a new template, go to `https://github.com/Dokploy/templates` repository and read the README.md file.
|
||||||
|
|
||||||
|
|
||||||
### Recommendations
|
### Recommendations
|
||||||
|
|
||||||
- Use the same name of the folder as the id of the template.
|
- Use the same name of the folder as the id of the template.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm
|
|||||||
# Install Nixpacks and tsx
|
# Install Nixpacks and tsx
|
||||||
# | VERBOSE=1 VERSION=1.21.0 bash
|
# | VERBOSE=1 VERSION=1.21.0 bash
|
||||||
|
|
||||||
ARG NIXPACKS_VERSION=1.35.0
|
ARG NIXPACKS_VERSION=1.29.1
|
||||||
RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
||||||
&& chmod +x install.sh \
|
&& chmod +x install.sh \
|
||||||
&& ./install.sh \
|
&& ./install.sh \
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ const baseApp: ApplicationNested = {
|
|||||||
giteaRepository: "",
|
giteaRepository: "",
|
||||||
cleanCache: false,
|
cleanCache: false,
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
applicationStatus: "done",
|
applicationStatus: "done",
|
||||||
appName: "",
|
appName: "",
|
||||||
autoDeploy: true,
|
autoDeploy: true,
|
||||||
|
|||||||
@@ -51,35 +51,6 @@ describe("processTemplate", () => {
|
|||||||
expect(result.domains).toHaveLength(0);
|
expect(result.domains).toHaveLength(0);
|
||||||
expect(result.mounts).toHaveLength(0);
|
expect(result.mounts).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow creation of real jwt secret", () => {
|
|
||||||
const template: CompleteTemplate = {
|
|
||||||
metadata: {} as any,
|
|
||||||
variables: {
|
|
||||||
jwt_secret: "cQsdycq1hDLopQonF6jUTqgQc5WEZTwWLL02J6XJ",
|
|
||||||
anon_payload: JSON.stringify({
|
|
||||||
role: "tester",
|
|
||||||
iss: "dockploy",
|
|
||||||
iat: "${timestamps:2025-01-01T00:00:00Z}",
|
|
||||||
exp: "${timestamps:2030-01-01T00:00:00Z}",
|
|
||||||
}),
|
|
||||||
anon_key: "${jwt:jwt_secret:anon_payload}",
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
domains: [],
|
|
||||||
env: {
|
|
||||||
ANON_KEY: "${anon_key}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = processTemplate(template, mockSchema);
|
|
||||||
expect(result.envs).toHaveLength(1);
|
|
||||||
expect(result.envs).toContain(
|
|
||||||
"ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIxNzM1Njg5NjAwIiwiZXhwIjoiMTg5MzQ1NjAwMCIsInJvbGUiOiJ0ZXN0ZXIiLCJpc3MiOiJkb2NrcGxveSJ9.BG5JoxL2_NaTFbPgyZdm3kRWenf_O3su_HIRKGCJ_kY",
|
|
||||||
);
|
|
||||||
expect(result.mounts).toHaveLength(0);
|
|
||||||
expect(result.domains).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("domains processing", () => {
|
describe("domains processing", () => {
|
||||||
|
|||||||
@@ -1,232 +0,0 @@
|
|||||||
import type { Schema } from "@dokploy/server/templates";
|
|
||||||
import { processValue } from "@dokploy/server/templates/processors";
|
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
describe("helpers functions", () => {
|
|
||||||
// Mock schema for testing
|
|
||||||
const mockSchema: Schema = {
|
|
||||||
projectName: "test",
|
|
||||||
serverIp: "127.0.0.1",
|
|
||||||
};
|
|
||||||
// some helpers to test jwt
|
|
||||||
type JWTParts = [string, string, string];
|
|
||||||
const jwtMatchExp = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/;
|
|
||||||
const jwtBase64Decode = (str: string) => {
|
|
||||||
const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
||||||
const padding = "=".repeat((4 - (base64.length % 4)) % 4);
|
|
||||||
const decoded = Buffer.from(base64 + padding, "base64").toString("utf-8");
|
|
||||||
return JSON.parse(decoded);
|
|
||||||
};
|
|
||||||
const jwtCheckHeader = (jwtHeader: string) => {
|
|
||||||
const decodedHeader = jwtBase64Decode(jwtHeader);
|
|
||||||
expect(decodedHeader).toHaveProperty("alg");
|
|
||||||
expect(decodedHeader).toHaveProperty("typ");
|
|
||||||
expect(decodedHeader.alg).toEqual("HS256");
|
|
||||||
expect(decodedHeader.typ).toEqual("JWT");
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("${domain}", () => {
|
|
||||||
it("should generate a random domain", () => {
|
|
||||||
const domain = processValue("${domain}", {}, mockSchema);
|
|
||||||
expect(domain.startsWith(`${mockSchema.projectName}-`)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
domain.endsWith(
|
|
||||||
`${mockSchema.serverIp.replaceAll(".", "-")}.traefik.me`,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${base64}", () => {
|
|
||||||
it("should generate a base64 string", () => {
|
|
||||||
const base64 = processValue("${base64}", {}, mockSchema);
|
|
||||||
expect(base64).toMatch(/^[A-Za-z0-9+=/]+={0,2}$/);
|
|
||||||
});
|
|
||||||
it.each([
|
|
||||||
[4, 8],
|
|
||||||
[8, 12],
|
|
||||||
[16, 24],
|
|
||||||
[32, 44],
|
|
||||||
[64, 88],
|
|
||||||
[128, 172],
|
|
||||||
])(
|
|
||||||
"should generate a base64 string from parameter %d bytes length",
|
|
||||||
(length, finalLength) => {
|
|
||||||
const base64 = processValue(`\${base64:${length}}`, {}, mockSchema);
|
|
||||||
expect(base64).toMatch(/^[A-Za-z0-9+=/]+={0,2}$/);
|
|
||||||
expect(base64.length).toBe(finalLength);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${password}", () => {
|
|
||||||
it("should generate a password string", () => {
|
|
||||||
const password = processValue("${password}", {}, mockSchema);
|
|
||||||
expect(password).toMatch(/^[A-Za-z0-9]+$/);
|
|
||||||
});
|
|
||||||
it.each([6, 8, 12, 16, 32])(
|
|
||||||
"should generate a password string respecting parameter %d length",
|
|
||||||
(length) => {
|
|
||||||
const password = processValue(`\${password:${length}}`, {}, mockSchema);
|
|
||||||
expect(password).toMatch(/^[A-Za-z0-9]+$/);
|
|
||||||
expect(password.length).toBe(length);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${hash}", () => {
|
|
||||||
it("should generate a hash string", () => {
|
|
||||||
const hash = processValue("${hash}", {}, mockSchema);
|
|
||||||
expect(hash).toMatch(/^[A-Za-z0-9]+$/);
|
|
||||||
});
|
|
||||||
it.each([6, 8, 12, 16, 32])(
|
|
||||||
"should generate a hash string respecting parameter %d length",
|
|
||||||
(length) => {
|
|
||||||
const hash = processValue(`\${hash:${length}}`, {}, mockSchema);
|
|
||||||
expect(hash).toMatch(/^[A-Za-z0-9]+$/);
|
|
||||||
expect(hash.length).toBe(length);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${uuid}", () => {
|
|
||||||
it("should generate a UUID string", () => {
|
|
||||||
const uuid = processValue("${uuid}", {}, mockSchema);
|
|
||||||
expect(uuid).toMatch(
|
|
||||||
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${timestamp}", () => {
|
|
||||||
it("should generate a timestamp string in milliseconds", () => {
|
|
||||||
const timestamp = processValue("${timestamp}", {}, mockSchema);
|
|
||||||
const nowLength = Math.floor(Date.now()).toString().length;
|
|
||||||
expect(timestamp).toMatch(/^\d+$/);
|
|
||||||
expect(timestamp.length).toBe(nowLength);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("${timestampms}", () => {
|
|
||||||
it("should generate a timestamp string in milliseconds", () => {
|
|
||||||
const timestamp = processValue("${timestampms}", {}, mockSchema);
|
|
||||||
const nowLength = Date.now().toString().length;
|
|
||||||
expect(timestamp).toMatch(/^\d+$/);
|
|
||||||
expect(timestamp.length).toBe(nowLength);
|
|
||||||
});
|
|
||||||
it("should generate a timestamp string in milliseconds from parameter", () => {
|
|
||||||
const timestamp = processValue(
|
|
||||||
"${timestampms:2025-01-01}",
|
|
||||||
{},
|
|
||||||
mockSchema,
|
|
||||||
);
|
|
||||||
expect(timestamp).toEqual("1735689600000");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("${timestamps}", () => {
|
|
||||||
it("should generate a timestamp string in seconds", () => {
|
|
||||||
const timestamps = processValue("${timestamps}", {}, mockSchema);
|
|
||||||
const nowLength = Math.floor(Date.now() / 1000).toString().length;
|
|
||||||
expect(timestamps).toMatch(/^\d+$/);
|
|
||||||
expect(timestamps.length).toBe(nowLength);
|
|
||||||
});
|
|
||||||
it("should generate a timestamp string in seconds from parameter", () => {
|
|
||||||
const timestamps = processValue(
|
|
||||||
"${timestamps:2025-01-01}",
|
|
||||||
{},
|
|
||||||
mockSchema,
|
|
||||||
);
|
|
||||||
expect(timestamps).toEqual("1735689600");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${randomPort}", () => {
|
|
||||||
it("should generate a random port string", () => {
|
|
||||||
const randomPort = processValue("${randomPort}", {}, mockSchema);
|
|
||||||
expect(randomPort).toMatch(/^\d+$/);
|
|
||||||
expect(Number(randomPort)).toBeLessThan(65536);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${username}", () => {
|
|
||||||
it("should generate a username string", () => {
|
|
||||||
const username = processValue("${username}", {}, mockSchema);
|
|
||||||
expect(username).toMatch(/^[a-zA-Z0-9._-]{3,}$/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${email}", () => {
|
|
||||||
it("should generate an email string", () => {
|
|
||||||
const email = processValue("${email}", {}, mockSchema);
|
|
||||||
expect(email).toMatch(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("${jwt}", () => {
|
|
||||||
it("should generate a JWT string", () => {
|
|
||||||
const jwt = processValue("${jwt}", {}, mockSchema);
|
|
||||||
expect(jwt).toMatch(jwtMatchExp);
|
|
||||||
const parts = jwt.split(".") as JWTParts;
|
|
||||||
const decodedPayload = jwtBase64Decode(parts[1]);
|
|
||||||
jwtCheckHeader(parts[0]);
|
|
||||||
expect(decodedPayload).toHaveProperty("iat");
|
|
||||||
expect(decodedPayload).toHaveProperty("iss");
|
|
||||||
expect(decodedPayload).toHaveProperty("exp");
|
|
||||||
expect(decodedPayload.iss).toEqual("dokploy");
|
|
||||||
});
|
|
||||||
it.each([6, 8, 12, 16, 32])(
|
|
||||||
"should generate a random hex string from parameter %d byte length",
|
|
||||||
(length) => {
|
|
||||||
const jwt = processValue(`\${jwt:${length}}`, {}, mockSchema);
|
|
||||||
expect(jwt).toMatch(/^[A-Za-z0-9-_.]+$/);
|
|
||||||
expect(jwt.length).toBeGreaterThanOrEqual(length); // bytes translated to hex can take up to 2x the length
|
|
||||||
expect(jwt.length).toBeLessThanOrEqual(length * 2);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
describe("${jwt:secret}", () => {
|
|
||||||
it("should generate a JWT string respecting parameter secret from variable", () => {
|
|
||||||
const jwt = processValue(
|
|
||||||
"${jwt:secret}",
|
|
||||||
{ secret: "mysecret" },
|
|
||||||
mockSchema,
|
|
||||||
);
|
|
||||||
expect(jwt).toMatch(jwtMatchExp);
|
|
||||||
const parts = jwt.split(".") as JWTParts;
|
|
||||||
const decodedPayload = jwtBase64Decode(parts[1]);
|
|
||||||
jwtCheckHeader(parts[0]);
|
|
||||||
expect(decodedPayload).toHaveProperty("iat");
|
|
||||||
expect(decodedPayload).toHaveProperty("iss");
|
|
||||||
expect(decodedPayload).toHaveProperty("exp");
|
|
||||||
expect(decodedPayload.iss).toEqual("dokploy");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("${jwt:secret:payload}", () => {
|
|
||||||
it("should generate a JWT string respecting parameters secret and payload from variables", () => {
|
|
||||||
const iat = Math.floor(new Date("2025-01-01T00:00:00Z").getTime() / 1000);
|
|
||||||
const expiry = iat + 3600;
|
|
||||||
const jwt = processValue(
|
|
||||||
"${jwt:secret:payload}",
|
|
||||||
{
|
|
||||||
secret: "mysecret",
|
|
||||||
payload: `{"iss": "test-issuer", "iat": ${iat}, "exp": ${expiry}, "customprop": "customvalue"}`,
|
|
||||||
},
|
|
||||||
mockSchema,
|
|
||||||
);
|
|
||||||
expect(jwt).toMatch(jwtMatchExp);
|
|
||||||
const parts = jwt.split(".") as JWTParts;
|
|
||||||
jwtCheckHeader(parts[0]);
|
|
||||||
const decodedPayload = jwtBase64Decode(parts[1]);
|
|
||||||
expect(decodedPayload).toHaveProperty("iat");
|
|
||||||
expect(decodedPayload.iat).toEqual(iat);
|
|
||||||
expect(decodedPayload).toHaveProperty("iss");
|
|
||||||
expect(decodedPayload.iss).toEqual("test-issuer");
|
|
||||||
expect(decodedPayload).toHaveProperty("exp");
|
|
||||||
expect(decodedPayload.exp).toEqual(expiry);
|
|
||||||
expect(decodedPayload).toHaveProperty("customprop");
|
|
||||||
expect(decodedPayload.customprop).toEqual("customvalue");
|
|
||||||
expect(jwt).toEqual(
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTczNTY5MzIwMCwiaXNzIjoidGVzdC1pc3N1ZXIiLCJjdXN0b21wcm9wIjoiY3VzdG9tdmFsdWUifQ.m42U7PZSUSCf7gBOJrxJir0rQmyPq4rA59Dydr_QahI",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -16,7 +16,6 @@ const baseApp: ApplicationNested = {
|
|||||||
applicationStatus: "done",
|
applicationStatus: "done",
|
||||||
appName: "",
|
appName: "",
|
||||||
autoDeploy: true,
|
autoDeploy: true,
|
||||||
enableSubmodules: false,
|
|
||||||
serverId: "",
|
serverId: "",
|
||||||
branch: null,
|
branch: null,
|
||||||
dockerBuildStage: "",
|
dockerBuildStage: "",
|
||||||
|
|||||||
@@ -89,8 +89,6 @@ export const AddDomain = ({
|
|||||||
serverId: application?.serverId || "",
|
serverId: application?.serverId || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("canGenerateTraefikMeDomains", canGenerateTraefikMeDomains);
|
|
||||||
|
|
||||||
const form = useForm<Domain>({
|
const form = useForm<Domain>({
|
||||||
resolver: zodResolver(domain),
|
resolver: zodResolver(domain),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -59,7 +58,6 @@ const BitbucketProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
|
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
|
||||||
@@ -86,7 +84,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
|
|||||||
bitbucketId: "",
|
bitbucketId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(BitbucketProviderSchema),
|
resolver: zodResolver(BitbucketProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -133,7 +130,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
|
|||||||
buildPath: data.bitbucketBuildPath || "/",
|
buildPath: data.bitbucketBuildPath || "/",
|
||||||
bitbucketId: data.bitbucketId || "",
|
bitbucketId: data.bitbucketId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules || false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -147,7 +143,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
|
|||||||
bitbucketId: data.bitbucketId,
|
bitbucketId: data.bitbucketId,
|
||||||
applicationId,
|
applicationId,
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules || false,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provided Saved");
|
toast.success("Service Provided Saved");
|
||||||
@@ -472,21 +467,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
|
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
|
||||||
@@ -45,7 +44,6 @@ const GitProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch required"),
|
branch: z.string().min(1, "Branch required"),
|
||||||
sshKey: z.string().optional(),
|
sshKey: z.string().optional(),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GitProvider = z.infer<typeof GitProviderSchema>;
|
type GitProvider = z.infer<typeof GitProviderSchema>;
|
||||||
@@ -69,7 +67,6 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
repositoryURL: "",
|
repositoryURL: "",
|
||||||
sshKey: undefined,
|
sshKey: undefined,
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GitProviderSchema),
|
resolver: zodResolver(GitProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -82,7 +79,6 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
buildPath: data.customGitBuildPath || "/",
|
buildPath: data.customGitBuildPath || "/",
|
||||||
repositoryURL: data.customGitUrl || "",
|
repositoryURL: data.customGitUrl || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -95,7 +91,6 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
|
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
|
||||||
applicationId,
|
applicationId,
|
||||||
watchPaths: values.watchPaths || [],
|
watchPaths: values.watchPaths || [],
|
||||||
enableSubmodules: values.enableSubmodules,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Git Provider Saved");
|
toast.success("Git Provider Saved");
|
||||||
@@ -299,22 +294,6 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row justify-end">
|
<div className="flex flex-row justify-end">
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -75,7 +74,6 @@ const GiteaProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
giteaId: z.string().min(1, "Gitea Provider is required"),
|
giteaId: z.string().min(1, "Gitea Provider is required"),
|
||||||
watchPaths: z.array(z.string()).default([]),
|
watchPaths: z.array(z.string()).default([]),
|
||||||
enableSubmodules: z.boolean().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GiteaProvider = z.infer<typeof GiteaProviderSchema>;
|
type GiteaProvider = z.infer<typeof GiteaProviderSchema>;
|
||||||
@@ -101,7 +99,6 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
|
|||||||
giteaId: "",
|
giteaId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GiteaProviderSchema),
|
resolver: zodResolver(GiteaProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -155,7 +152,6 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
|
|||||||
buildPath: data.giteaBuildPath || "/",
|
buildPath: data.giteaBuildPath || "/",
|
||||||
giteaId: data.giteaId || "",
|
giteaId: data.giteaId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules || false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -169,7 +165,6 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
|
|||||||
giteaId: data.giteaId,
|
giteaId: data.giteaId,
|
||||||
applicationId,
|
applicationId,
|
||||||
watchPaths: data.watchPaths,
|
watchPaths: data.watchPaths,
|
||||||
enableSubmodules: data.enableSubmodules || false,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provider Saved");
|
toast.success("Service Provider Saved");
|
||||||
@@ -503,21 +498,6 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -58,7 +57,6 @@ const GithubProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
githubId: z.string().min(1, "Github Provider is required"),
|
githubId: z.string().min(1, "Github Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GithubProvider = z.infer<typeof GithubProviderSchema>;
|
type GithubProvider = z.infer<typeof GithubProviderSchema>;
|
||||||
@@ -83,7 +81,6 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
|||||||
},
|
},
|
||||||
githubId: "",
|
githubId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GithubProviderSchema),
|
resolver: zodResolver(GithubProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -127,7 +124,6 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
|||||||
buildPath: data.buildPath || "/",
|
buildPath: data.buildPath || "/",
|
||||||
githubId: data.githubId || "",
|
githubId: data.githubId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -141,7 +137,6 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
|||||||
buildPath: data.buildPath,
|
buildPath: data.buildPath,
|
||||||
githubId: data.githubId,
|
githubId: data.githubId,
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provided Saved");
|
toast.success("Service Provided Saved");
|
||||||
@@ -463,22 +458,6 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -61,7 +60,6 @@ const GitlabProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
|
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
|
||||||
@@ -88,7 +86,6 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
|||||||
},
|
},
|
||||||
gitlabId: "",
|
gitlabId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GitlabProviderSchema),
|
resolver: zodResolver(GitlabProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -138,7 +135,6 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
|||||||
buildPath: data.gitlabBuildPath || "/",
|
buildPath: data.gitlabBuildPath || "/",
|
||||||
gitlabId: data.gitlabId || "",
|
gitlabId: data.gitlabId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -154,7 +150,6 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
|||||||
gitlabProjectId: data.repository.id,
|
gitlabProjectId: data.repository.id,
|
||||||
gitlabPathNamespace: data.repository.gitlabPathNamespace,
|
gitlabPathNamespace: data.repository.gitlabPathNamespace,
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provided Saved");
|
toast.success("Service Provided Saved");
|
||||||
@@ -488,21 +483,6 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
|||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList className="md:grid md:w-fit md:grid-cols-7 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
|
<TabsList className="md:grid md:w-fit md:grid-cols-7 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="github"
|
value="github"
|
||||||
|
|||||||
@@ -298,11 +298,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
refetch();
|
refetch();
|
||||||
toast.success(
|
toast.success("Preview deployments enabled");
|
||||||
checked
|
|
||||||
? "Preview deployments enabled"
|
|
||||||
: "Preview deployments disabled",
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
|
|||||||
@@ -79,22 +79,6 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
toast.error("Error updating the Compose config");
|
toast.error("Error updating the Compose config");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add keyboard shortcut for Ctrl+S/Cmd+S
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "s" && !isLoading) {
|
|
||||||
e.preventDefault();
|
|
||||||
form.handleSubmit(onSubmit)();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("keydown", handleKeyDown);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [form, onSubmit, isLoading]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex flex-col gap-4 ">
|
<div className="w-full flex flex-col gap-4 ">
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -59,7 +58,6 @@ const BitbucketProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
|
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
|
||||||
@@ -86,7 +84,6 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
|||||||
bitbucketId: "",
|
bitbucketId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(BitbucketProviderSchema),
|
resolver: zodResolver(BitbucketProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -133,7 +130,6 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
|||||||
composePath: data.composePath,
|
composePath: data.composePath,
|
||||||
bitbucketId: data.bitbucketId || "",
|
bitbucketId: data.bitbucketId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -149,7 +145,6 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
|||||||
sourceType: "bitbucket",
|
sourceType: "bitbucket",
|
||||||
composeStatus: "idle",
|
composeStatus: "idle",
|
||||||
watchPaths: data.watchPaths,
|
watchPaths: data.watchPaths,
|
||||||
enableSubmodules: data.enableSubmodules,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provided Saved");
|
toast.success("Service Provided Saved");
|
||||||
@@ -474,21 +469,6 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -44,7 +43,6 @@ const GitProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch required"),
|
branch: z.string().min(1, "Branch required"),
|
||||||
sshKey: z.string().optional(),
|
sshKey: z.string().optional(),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GitProvider = z.infer<typeof GitProviderSchema>;
|
type GitProvider = z.infer<typeof GitProviderSchema>;
|
||||||
@@ -67,7 +65,6 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
|||||||
composePath: "./docker-compose.yml",
|
composePath: "./docker-compose.yml",
|
||||||
sshKey: undefined,
|
sshKey: undefined,
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GitProviderSchema),
|
resolver: zodResolver(GitProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -80,7 +77,6 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
|||||||
repositoryURL: data.customGitUrl || "",
|
repositoryURL: data.customGitUrl || "",
|
||||||
composePath: data.composePath,
|
composePath: data.composePath,
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -95,7 +91,6 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
|||||||
composePath: values.composePath,
|
composePath: values.composePath,
|
||||||
composeStatus: "idle",
|
composeStatus: "idle",
|
||||||
watchPaths: values.watchPaths || [],
|
watchPaths: values.watchPaths || [],
|
||||||
enableSubmodules: values.enableSubmodules,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Git Provider Saved");
|
toast.success("Git Provider Saved");
|
||||||
@@ -300,21 +295,6 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row justify-end">
|
<div className="flex flex-row justify-end">
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -60,7 +59,6 @@ const GiteaProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
giteaId: z.string().min(1, "Gitea Provider is required"),
|
giteaId: z.string().min(1, "Gitea Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GiteaProvider = z.infer<typeof GiteaProviderSchema>;
|
type GiteaProvider = z.infer<typeof GiteaProviderSchema>;
|
||||||
@@ -85,7 +83,6 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => {
|
|||||||
giteaId: "",
|
giteaId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GiteaProviderSchema),
|
resolver: zodResolver(GiteaProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -139,7 +136,6 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => {
|
|||||||
composePath: data.composePath || "./docker-compose.yml",
|
composePath: data.composePath || "./docker-compose.yml",
|
||||||
giteaId: data.giteaId || "",
|
giteaId: data.giteaId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -155,7 +151,6 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => {
|
|||||||
sourceType: "gitea",
|
sourceType: "gitea",
|
||||||
composeStatus: "idle",
|
composeStatus: "idle",
|
||||||
watchPaths: data.watchPaths,
|
watchPaths: data.watchPaths,
|
||||||
enableSubmodules: data.enableSubmodules,
|
|
||||||
} as any)
|
} as any)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provider Saved");
|
toast.success("Service Provider Saved");
|
||||||
@@ -474,21 +469,6 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -58,7 +57,6 @@ const GithubProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
githubId: z.string().min(1, "Github Provider is required"),
|
githubId: z.string().min(1, "Github Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GithubProvider = z.infer<typeof GithubProviderSchema>;
|
type GithubProvider = z.infer<typeof GithubProviderSchema>;
|
||||||
@@ -84,7 +82,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
|||||||
githubId: "",
|
githubId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GithubProviderSchema),
|
resolver: zodResolver(GithubProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -128,7 +125,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
|||||||
composePath: data.composePath,
|
composePath: data.composePath,
|
||||||
githubId: data.githubId || "",
|
githubId: data.githubId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -144,7 +140,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
|||||||
sourceType: "github",
|
sourceType: "github",
|
||||||
composeStatus: "idle",
|
composeStatus: "idle",
|
||||||
watchPaths: data.watchPaths,
|
watchPaths: data.watchPaths,
|
||||||
enableSubmodules: data.enableSubmodules,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provided Saved");
|
toast.success("Service Provided Saved");
|
||||||
@@ -465,21 +460,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -61,7 +60,6 @@ const GitlabProviderSchema = z.object({
|
|||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z.string().min(1, "Branch is required"),
|
||||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
|
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
|
||||||
@@ -89,7 +87,6 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
|||||||
gitlabId: "",
|
gitlabId: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
watchPaths: [],
|
watchPaths: [],
|
||||||
enableSubmodules: false,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GitlabProviderSchema),
|
resolver: zodResolver(GitlabProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -139,7 +136,6 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
|||||||
composePath: data.composePath,
|
composePath: data.composePath,
|
||||||
gitlabId: data.gitlabId || "",
|
gitlabId: data.gitlabId || "",
|
||||||
watchPaths: data.watchPaths || [],
|
watchPaths: data.watchPaths || [],
|
||||||
enableSubmodules: data.enableSubmodules ?? false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form.reset, data, form]);
|
}, [form.reset, data, form]);
|
||||||
@@ -157,7 +153,6 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
|||||||
sourceType: "gitlab",
|
sourceType: "gitlab",
|
||||||
composeStatus: "idle",
|
composeStatus: "idle",
|
||||||
watchPaths: data.watchPaths,
|
watchPaths: data.watchPaths,
|
||||||
enableSubmodules: data.enableSubmodules,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Provided Saved");
|
toast.success("Service Provided Saved");
|
||||||
@@ -490,21 +485,6 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableSubmodules"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-center space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="!mt-0">Enable Submodules</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -77,14 +77,6 @@ const RestoreBackupSchema = z.object({
|
|||||||
|
|
||||||
type RestoreBackup = z.infer<typeof RestoreBackupSchema>;
|
type RestoreBackup = z.infer<typeof RestoreBackupSchema>;
|
||||||
|
|
||||||
const formatBytes = (bytes: number): string => {
|
|
||||||
if (bytes === 0) return "0 Bytes";
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RestoreBackup = ({
|
export const RestoreBackup = ({
|
||||||
databaseId,
|
databaseId,
|
||||||
databaseType,
|
databaseType,
|
||||||
@@ -92,7 +84,6 @@ export const RestoreBackup = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
|
||||||
|
|
||||||
const { data: destinations = [] } = api.destination.all.useQuery();
|
const { data: destinations = [] } = api.destination.all.useQuery();
|
||||||
|
|
||||||
@@ -108,18 +99,13 @@ export const RestoreBackup = ({
|
|||||||
const destionationId = form.watch("destinationId");
|
const destionationId = form.watch("destinationId");
|
||||||
|
|
||||||
const debouncedSetSearch = debounce((value: string) => {
|
const debouncedSetSearch = debounce((value: string) => {
|
||||||
setDebouncedSearchTerm(value);
|
|
||||||
}, 350);
|
|
||||||
|
|
||||||
const handleSearchChange = (value: string) => {
|
|
||||||
setSearch(value);
|
setSearch(value);
|
||||||
debouncedSetSearch(value);
|
}, 300);
|
||||||
};
|
|
||||||
|
|
||||||
const { data: files = [], isLoading } = api.backup.listBackupFiles.useQuery(
|
const { data: files = [], isLoading } = api.backup.listBackupFiles.useQuery(
|
||||||
{
|
{
|
||||||
destinationId: destionationId,
|
destinationId: destionationId,
|
||||||
search: debouncedSearchTerm,
|
search,
|
||||||
serverId: serverId ?? "",
|
serverId: serverId ?? "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -279,7 +265,7 @@ export const RestoreBackup = ({
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Popover modal>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Button
|
<Button
|
||||||
@@ -298,8 +284,7 @@ export const RestoreBackup = ({
|
|||||||
<Command>
|
<Command>
|
||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder="Search backup files..."
|
placeholder="Search backup files..."
|
||||||
value={search}
|
onValueChange={debouncedSetSearch}
|
||||||
onValueChange={handleSearchChange}
|
|
||||||
className="h-9"
|
className="h-9"
|
||||||
/>
|
/>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -316,51 +301,26 @@ export const RestoreBackup = ({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ScrollArea className="h-64">
|
<ScrollArea className="h-64">
|
||||||
<CommandGroup className="w-96">
|
<CommandGroup>
|
||||||
{files?.map((file) => (
|
{files.map((file) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
value={file.Path}
|
value={file}
|
||||||
key={file.Path}
|
key={file}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
form.setValue("backupFile", file.Path);
|
form.setValue("backupFile", file);
|
||||||
if (file.IsDir) {
|
|
||||||
setSearch(`${file.Path}/`);
|
|
||||||
setDebouncedSearchTerm(`${file.Path}/`);
|
|
||||||
} else {
|
|
||||||
setSearch(file.Path);
|
|
||||||
setDebouncedSearchTerm(file.Path);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-col gap-1">
|
<div className="flex w-full justify-between">
|
||||||
<div className="flex w-full justify-between">
|
<span>{file}</span>
|
||||||
<span className="font-medium">
|
|
||||||
{file.Path}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<CheckIcon
|
|
||||||
className={cn(
|
|
||||||
"ml-auto h-4 w-4",
|
|
||||||
file.Path === field.value
|
|
||||||
? "opacity-100"
|
|
||||||
: "opacity-0",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
|
||||||
<span>
|
|
||||||
Size: {formatBytes(file.Size)}
|
|
||||||
</span>
|
|
||||||
{file.IsDir && (
|
|
||||||
<span className="text-blue-500">
|
|
||||||
Directory
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{file.Hashes?.MD5 && (
|
|
||||||
<span>MD5: {file.Hashes.MD5}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
file === field.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const AiForm = () => {
|
|||||||
key={config.aiId}
|
key={config.aiId}
|
||||||
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
{config.name}
|
{config.name}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const ShowCertificates = () => {
|
|||||||
key={certificate.certificateId}
|
key={certificate.certificateId}
|
||||||
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex gap-2 flex-col">
|
<div className="flex gap-2 flex-col">
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
|
|||||||
@@ -13,65 +13,53 @@ export const extractExpirationDate = (certData: string): Date | null => {
|
|||||||
bytes[i] = binaryStr.charCodeAt(i);
|
bytes[i] = binaryStr.charCodeAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASN.1 tag for UTCTime is 0x17, GeneralizedTime is 0x18
|
let dateFound = 0;
|
||||||
// We need to find the second occurrence of either tag as it's the "not after" (expiration) date
|
|
||||||
let dateFound = false;
|
|
||||||
for (let i = 0; i < bytes.length - 2; i++) {
|
for (let i = 0; i < bytes.length - 2; i++) {
|
||||||
// Look for sequence containing validity period (0x30)
|
if (bytes[i] === 0x17 || bytes[i] === 0x18) {
|
||||||
if (bytes[i] === 0x30) {
|
const dateType = bytes[i];
|
||||||
// Check next bytes for UTCTime or GeneralizedTime
|
const dateLength = bytes[i + 1];
|
||||||
let j = i + 1;
|
if (typeof dateLength === "undefined") continue;
|
||||||
while (j < bytes.length - 2) {
|
|
||||||
if (bytes[j] === 0x17 || bytes[j] === 0x18) {
|
|
||||||
const dateType = bytes[j];
|
|
||||||
const dateLength = bytes[j + 1];
|
|
||||||
if (typeof dateLength === "undefined") break;
|
|
||||||
|
|
||||||
if (!dateFound) {
|
if (dateFound === 0) {
|
||||||
// Skip "not before" date
|
dateFound++;
|
||||||
dateFound = true;
|
i += dateLength + 1;
|
||||||
j += dateLength + 2;
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Found "not after" date
|
|
||||||
let dateStr = "";
|
|
||||||
for (let k = 0; k < dateLength; k++) {
|
|
||||||
const charCode = bytes[j + 2 + k];
|
|
||||||
if (typeof charCode === "undefined") continue;
|
|
||||||
dateStr += String.fromCharCode(charCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dateType === 0x17) {
|
|
||||||
// UTCTime (YYMMDDhhmmssZ)
|
|
||||||
const year = Number.parseInt(dateStr.slice(0, 2));
|
|
||||||
const fullYear = year >= 50 ? 1900 + year : 2000 + year;
|
|
||||||
return new Date(
|
|
||||||
Date.UTC(
|
|
||||||
fullYear,
|
|
||||||
Number.parseInt(dateStr.slice(2, 4)) - 1,
|
|
||||||
Number.parseInt(dateStr.slice(4, 6)),
|
|
||||||
Number.parseInt(dateStr.slice(6, 8)),
|
|
||||||
Number.parseInt(dateStr.slice(8, 10)),
|
|
||||||
Number.parseInt(dateStr.slice(10, 12)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GeneralizedTime (YYYYMMDDhhmmssZ)
|
|
||||||
return new Date(
|
|
||||||
Date.UTC(
|
|
||||||
Number.parseInt(dateStr.slice(0, 4)),
|
|
||||||
Number.parseInt(dateStr.slice(4, 6)) - 1,
|
|
||||||
Number.parseInt(dateStr.slice(6, 8)),
|
|
||||||
Number.parseInt(dateStr.slice(8, 10)),
|
|
||||||
Number.parseInt(dateStr.slice(10, 12)),
|
|
||||||
Number.parseInt(dateStr.slice(12, 14)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
j++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dateStr = "";
|
||||||
|
for (let j = 0; j < dateLength; j++) {
|
||||||
|
const charCode = bytes[i + 2 + j];
|
||||||
|
if (typeof charCode === "undefined") continue;
|
||||||
|
dateStr += String.fromCharCode(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateType === 0x17) {
|
||||||
|
// UTCTime (YYMMDDhhmmssZ)
|
||||||
|
const year = Number.parseInt(dateStr.slice(0, 2));
|
||||||
|
const fullYear = year >= 50 ? 1900 + year : 2000 + year;
|
||||||
|
return new Date(
|
||||||
|
Date.UTC(
|
||||||
|
fullYear,
|
||||||
|
Number.parseInt(dateStr.slice(2, 4)) - 1,
|
||||||
|
Number.parseInt(dateStr.slice(4, 6)),
|
||||||
|
Number.parseInt(dateStr.slice(6, 8)),
|
||||||
|
Number.parseInt(dateStr.slice(8, 10)),
|
||||||
|
Number.parseInt(dateStr.slice(10, 12)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneralizedTime (YYYYMMDDhhmmssZ)
|
||||||
|
return new Date(
|
||||||
|
Date.UTC(
|
||||||
|
Number.parseInt(dateStr.slice(0, 4)),
|
||||||
|
Number.parseInt(dateStr.slice(4, 6)) - 1,
|
||||||
|
Number.parseInt(dateStr.slice(6, 8)),
|
||||||
|
Number.parseInt(dateStr.slice(8, 10)),
|
||||||
|
Number.parseInt(dateStr.slice(10, 12)),
|
||||||
|
Number.parseInt(dateStr.slice(12, 14)),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const ShowRegistry = () => {
|
|||||||
key={registry.registryId}
|
key={registry.registryId}
|
||||||
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex gap-2 flex-col">
|
<div className="flex gap-2 flex-col">
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const ShowDestinations = () => {
|
|||||||
key={destination.destinationId}
|
key={destination.destinationId}
|
||||||
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
{index + 1}. {destination.name}
|
{index + 1}. {destination.name}
|
||||||
|
|||||||
@@ -248,9 +248,7 @@ export const AddGitlabProvider = () => {
|
|||||||
name="groupName"
|
name="groupName"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>Group Name (Optional)</FormLabel>
|
||||||
Group Name (Optional, Comma-Separated List)
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="For organization/group access use the slugish name of the group eg: my-org"
|
placeholder="For organization/group access use the slugish name of the group eg: my-org"
|
||||||
|
|||||||
@@ -156,9 +156,7 @@ export const EditGitlabProvider = ({ gitlabId }: Props) => {
|
|||||||
name="groupName"
|
name="groupName"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>Group Name (Optional)</FormLabel>
|
||||||
Group Name (Optional, Comma-Separated List)
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="For organization/group access use the slugish name of the group eg: my-org"
|
placeholder="For organization/group access use the slugish name of the group eg: my-org"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const ShowNotifications = () => {
|
|||||||
key={notification.notificationId}
|
key={notification.notificationId}
|
||||||
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
||||||
<span className="text-sm flex flex-row items-center gap-4">
|
<span className="text-sm flex flex-row items-center gap-4">
|
||||||
{notification.notificationType === "slack" && (
|
{notification.notificationType === "slack" && (
|
||||||
<div className="flex items-center justify-center rounded-lg">
|
<div className="flex items-center justify-center rounded-lg">
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const PasswordSchema = z.object({
|
|||||||
password: z.string().min(8, {
|
password: z.string().min(8, {
|
||||||
message: "Password is required",
|
message: "Password is required",
|
||||||
}),
|
}),
|
||||||
issuer: z.string().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const PinSchema = z.object({
|
const PinSchema = z.object({
|
||||||
@@ -61,86 +60,12 @@ export const Enable2FA = () => {
|
|||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [step, setStep] = useState<"password" | "verify">("password");
|
const [step, setStep] = useState<"password" | "verify">("password");
|
||||||
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
|
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
|
||||||
const [otpValue, setOtpValue] = useState("");
|
|
||||||
|
|
||||||
const handleVerifySubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
const result = await authClient.twoFactor.verifyTotp({
|
|
||||||
code: otpValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") {
|
|
||||||
toast.error("Invalid verification code");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw result.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.data) {
|
|
||||||
throw new Error("No response received from server");
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success("2FA configured successfully");
|
|
||||||
utils.user.get.invalidate();
|
|
||||||
setIsDialogOpen(false);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
const errorMessage =
|
|
||||||
error.message === "Failed to fetch"
|
|
||||||
? "Connection error. Please check your internet connection."
|
|
||||||
: error.message;
|
|
||||||
|
|
||||||
toast.error(errorMessage);
|
|
||||||
} else {
|
|
||||||
toast.error("Error verifying 2FA code", {
|
|
||||||
description: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const passwordForm = useForm<PasswordForm>({
|
|
||||||
resolver: zodResolver(PasswordSchema),
|
|
||||||
defaultValues: {
|
|
||||||
password: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const pinForm = useForm<PinForm>({
|
|
||||||
resolver: zodResolver(PinSchema),
|
|
||||||
defaultValues: {
|
|
||||||
pin: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isDialogOpen) {
|
|
||||||
setStep("password");
|
|
||||||
setData(null);
|
|
||||||
setBackupCodes([]);
|
|
||||||
setOtpValue("");
|
|
||||||
passwordForm.reset({
|
|
||||||
password: "",
|
|
||||||
issuer: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isDialogOpen, passwordForm]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (step === "verify") {
|
|
||||||
setOtpValue("");
|
|
||||||
}
|
|
||||||
}, [step]);
|
|
||||||
|
|
||||||
const handlePasswordSubmit = async (formData: PasswordForm) => {
|
const handlePasswordSubmit = async (formData: PasswordForm) => {
|
||||||
setIsPasswordLoading(true);
|
setIsPasswordLoading(true);
|
||||||
try {
|
try {
|
||||||
const { data: enableData, error } = await authClient.twoFactor.enable({
|
const { data: enableData, error } = await authClient.twoFactor.enable({
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
issuer: formData.issuer,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!enableData) {
|
if (!enableData) {
|
||||||
@@ -178,6 +103,75 @@ export const Enable2FA = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVerifySubmit = async (formData: PinForm) => {
|
||||||
|
try {
|
||||||
|
const result = await authClient.twoFactor.verifyTotp({
|
||||||
|
code: formData.pin,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") {
|
||||||
|
pinForm.setError("pin", {
|
||||||
|
message: "Invalid code. Please try again.",
|
||||||
|
});
|
||||||
|
toast.error("Invalid verification code");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.data) {
|
||||||
|
throw new Error("No response received from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("2FA configured successfully");
|
||||||
|
utils.user.get.invalidate();
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
const errorMessage =
|
||||||
|
error.message === "Failed to fetch"
|
||||||
|
? "Connection error. Please check your internet connection."
|
||||||
|
: error.message;
|
||||||
|
|
||||||
|
pinForm.setError("pin", {
|
||||||
|
message: errorMessage,
|
||||||
|
});
|
||||||
|
toast.error(errorMessage);
|
||||||
|
} else {
|
||||||
|
pinForm.setError("pin", {
|
||||||
|
message: "Error verifying code",
|
||||||
|
});
|
||||||
|
toast.error("Error verifying 2FA code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const passwordForm = useForm<PasswordForm>({
|
||||||
|
resolver: zodResolver(PasswordSchema),
|
||||||
|
defaultValues: {
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const pinForm = useForm<PinForm>({
|
||||||
|
resolver: zodResolver(PinSchema),
|
||||||
|
defaultValues: {
|
||||||
|
pin: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDialogOpen) {
|
||||||
|
setStep("password");
|
||||||
|
setData(null);
|
||||||
|
setBackupCodes([]);
|
||||||
|
passwordForm.reset();
|
||||||
|
pinForm.reset();
|
||||||
|
}
|
||||||
|
}, [isDialogOpen, passwordForm, pinForm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@@ -223,27 +217,6 @@ export const Enable2FA = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={passwordForm.control}
|
|
||||||
name="issuer"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Issuer</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter your issuer"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
Use a custom issuer to identify the service you're
|
|
||||||
authenticating with.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -255,7 +228,11 @@ export const Enable2FA = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
) : (
|
) : (
|
||||||
<Form {...pinForm}>
|
<Form {...pinForm}>
|
||||||
<form onSubmit={handleVerifySubmit} className="space-y-6">
|
<form
|
||||||
|
id="pin-form"
|
||||||
|
onSubmit={pinForm.handleSubmit(handleVerifySubmit)}
|
||||||
|
className="space-y-6"
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-6 justify-center items-center">
|
<div className="flex flex-col gap-6 justify-center items-center">
|
||||||
{data?.qrCodeUrl ? (
|
{data?.qrCodeUrl ? (
|
||||||
<>
|
<>
|
||||||
@@ -307,33 +284,36 @@ export const Enable2FA = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col justify-center items-center">
|
<FormField
|
||||||
<FormLabel>Verification Code</FormLabel>
|
control={pinForm.control}
|
||||||
<InputOTP
|
name="pin"
|
||||||
maxLength={6}
|
render={({ field }) => (
|
||||||
value={otpValue}
|
<FormItem className="flex flex-col justify-center items-center">
|
||||||
onChange={setOtpValue}
|
<FormLabel>Verification Code</FormLabel>
|
||||||
autoComplete="off"
|
<FormControl>
|
||||||
>
|
<InputOTP maxLength={6} {...field}>
|
||||||
<InputOTPGroup>
|
<InputOTPGroup>
|
||||||
<InputOTPSlot index={0} />
|
<InputOTPSlot index={0} />
|
||||||
<InputOTPSlot index={1} />
|
<InputOTPSlot index={1} />
|
||||||
<InputOTPSlot index={2} />
|
<InputOTPSlot index={2} />
|
||||||
<InputOTPSlot index={3} />
|
<InputOTPSlot index={3} />
|
||||||
<InputOTPSlot index={4} />
|
<InputOTPSlot index={4} />
|
||||||
<InputOTPSlot index={5} />
|
<InputOTPSlot index={5} />
|
||||||
</InputOTPGroup>
|
</InputOTPGroup>
|
||||||
</InputOTP>
|
</InputOTP>
|
||||||
<FormDescription>
|
</FormControl>
|
||||||
Enter the 6-digit code from your authenticator app
|
<FormDescription>
|
||||||
</FormDescription>
|
Enter the 6-digit code from your authenticator app
|
||||||
</div>
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
isLoading={isPasswordLoading}
|
isLoading={isPasswordLoading}
|
||||||
disabled={otpValue.length !== 6}
|
|
||||||
>
|
>
|
||||||
Enable 2FA
|
Enable 2FA
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ const randomImages = [
|
|||||||
export const ProfileForm = () => {
|
export const ProfileForm = () => {
|
||||||
const _utils = api.useUtils();
|
const _utils = api.useUtils();
|
||||||
const { data, refetch, isLoading } = api.user.get.useQuery();
|
const { data, refetch, isLoading } = api.user.get.useQuery();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutateAsync,
|
mutateAsync,
|
||||||
isLoading: isUpdating,
|
isLoading: isUpdating,
|
||||||
@@ -85,17 +84,12 @@ export const ProfileForm = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset(
|
form.reset({
|
||||||
{
|
email: data?.user?.email || "",
|
||||||
email: data?.user?.email || "",
|
password: "",
|
||||||
password: form.getValues("password") || "",
|
image: data?.user?.image || "",
|
||||||
image: data?.user?.image || "",
|
currentPassword: "",
|
||||||
currentPassword: form.getValues("currentPassword") || "",
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
keepValues: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data.user.email) {
|
if (data.user.email) {
|
||||||
generateSHA256Hash(data.user.email).then((hash) => {
|
generateSHA256Hash(data.user.email).then((hash) => {
|
||||||
@@ -103,7 +97,8 @@ export const ProfileForm = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [form, data]);
|
form.reset();
|
||||||
|
}, [form, form.reset, data]);
|
||||||
|
|
||||||
const onSubmit = async (values: Profile) => {
|
const onSubmit = async (values: Profile) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
@@ -115,12 +110,7 @@ export const ProfileForm = () => {
|
|||||||
.then(async () => {
|
.then(async () => {
|
||||||
await refetch();
|
await refetch();
|
||||||
toast.success("Profile Updated");
|
toast.success("Profile Updated");
|
||||||
form.reset({
|
form.reset();
|
||||||
email: values.email,
|
|
||||||
password: "",
|
|
||||||
image: values.image,
|
|
||||||
currentPassword: "",
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error updating the profile");
|
toast.error("Error updating the profile");
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ export const ShowDokployActions = () => {
|
|||||||
const { mutateAsync: reloadServer, isLoading } =
|
const { mutateAsync: reloadServer, isLoading } =
|
||||||
api.settings.reloadServer.useMutation();
|
api.settings.reloadServer.useMutation();
|
||||||
|
|
||||||
const { mutateAsync: cleanRedis } = api.settings.cleanRedis.useMutation();
|
|
||||||
const { mutateAsync: reloadRedis } = api.settings.reloadRedis.useMutation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild disabled={isLoading}>
|
<DropdownMenuTrigger asChild disabled={isLoading}>
|
||||||
@@ -72,36 +69,6 @@ export const ShowDokployActions = () => {
|
|||||||
{t("settings.server.webServer.updateServerIp")}
|
{t("settings.server.webServer.updateServerIp")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</UpdateServerIp>
|
</UpdateServerIp>
|
||||||
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={async () => {
|
|
||||||
await cleanRedis()
|
|
||||||
.then(async () => {
|
|
||||||
toast.success("Redis cleaned");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error cleaning Redis");
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clean Redis
|
|
||||||
</DropdownMenuItem>
|
|
||||||
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={async () => {
|
|
||||||
await reloadRedis()
|
|
||||||
.then(async () => {
|
|
||||||
toast.success("Redis reloaded");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error reloading Redis");
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reload Redis
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const ShowDestinations = () => {
|
|||||||
key={sshKey.sshKeyId}
|
key={sshKey.sshKeyId}
|
||||||
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
|
|||||||
@@ -133,6 +133,17 @@ export const UserNav = () => {
|
|||||||
Servers
|
Servers
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{data?.role === "owner" && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
router.push("/dashboard/settings");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
|
|||||||
@@ -26,20 +26,15 @@ const dockerComposeServices = [
|
|||||||
{ label: "secrets", type: "keyword", info: "Define secrets" },
|
{ label: "secrets", type: "keyword", info: "Define secrets" },
|
||||||
].map((opt) => ({
|
].map((opt) => ({
|
||||||
...opt,
|
...opt,
|
||||||
apply: (
|
apply: (view: EditorView, completion: Completion) => {
|
||||||
view: EditorView,
|
|
||||||
completion: Completion,
|
|
||||||
from: number,
|
|
||||||
to: number,
|
|
||||||
) => {
|
|
||||||
const insert = `${completion.label}:`;
|
const insert = `${completion.label}:`;
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from,
|
from: view.state.selection.main.from,
|
||||||
to,
|
to: view.state.selection.main.to,
|
||||||
insert,
|
insert,
|
||||||
},
|
},
|
||||||
selection: { anchor: from + insert.length },
|
selection: { anchor: view.state.selection.main.from + insert.length },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -79,20 +74,15 @@ const dockerComposeServiceOptions = [
|
|||||||
{ label: "networks", type: "keyword", info: "Networks to join" },
|
{ label: "networks", type: "keyword", info: "Networks to join" },
|
||||||
].map((opt) => ({
|
].map((opt) => ({
|
||||||
...opt,
|
...opt,
|
||||||
apply: (
|
apply: (view: EditorView, completion: Completion) => {
|
||||||
view: EditorView,
|
|
||||||
completion: Completion,
|
|
||||||
from: number,
|
|
||||||
to: number,
|
|
||||||
) => {
|
|
||||||
const insert = `${completion.label}: `;
|
const insert = `${completion.label}: `;
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from,
|
from: view.state.selection.main.from,
|
||||||
to,
|
to: view.state.selection.main.to,
|
||||||
insert,
|
insert,
|
||||||
},
|
},
|
||||||
selection: { anchor: from + insert.length },
|
selection: { anchor: view.state.selection.main.from + insert.length },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -109,7 +99,6 @@ function dockerComposeComplete(
|
|||||||
const line = context.state.doc.lineAt(context.pos);
|
const line = context.state.doc.lineAt(context.pos);
|
||||||
const indentation = /^\s*/.exec(line.text)?.[0].length || 0;
|
const indentation = /^\s*/.exec(line.text)?.[0].length || 0;
|
||||||
|
|
||||||
// If we're at the root level
|
|
||||||
if (indentation === 0) {
|
if (indentation === 0) {
|
||||||
return {
|
return {
|
||||||
from: word.from,
|
from: word.from,
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "application" ADD COLUMN "enableSubmodules" boolean DEFAULT false;--> statement-breakpoint
|
|
||||||
ALTER TABLE "compose" ADD COLUMN "enableSubmodules" boolean DEFAULT false;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "application" ALTER COLUMN "enableSubmodules" SET NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "compose" ALTER COLUMN "enableSubmodules" SET NOT NULL;
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -596,20 +596,6 @@
|
|||||||
"when": 1743923992280,
|
"when": 1743923992280,
|
||||||
"tag": "0084_thin_iron_lad",
|
"tag": "0084_thin_iron_lad",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 85,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1745705609181,
|
|
||||||
"tag": "0085_equal_captain_stacy",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 86,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1745706676004,
|
|
||||||
"tag": "0086_rainy_gertrude_yorkes",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,23 @@
|
|||||||
/**
|
|
||||||
* Sorted list based off of population of the country / speakers of the language.
|
|
||||||
*/
|
|
||||||
export const Languages = {
|
export const Languages = {
|
||||||
english: { code: "en", name: "English" },
|
english: { code: "en", name: "English" },
|
||||||
spanish: { code: "es", name: "Español" },
|
|
||||||
chineseSimplified: { code: "zh-Hans", name: "简体中文" },
|
|
||||||
chineseTraditional: { code: "zh-Hant", name: "繁體中文" },
|
|
||||||
portuguese: { code: "pt-br", name: "Português" },
|
|
||||||
russian: { code: "ru", name: "Русский" },
|
|
||||||
japanese: { code: "ja", name: "日本語" },
|
|
||||||
german: { code: "de", name: "Deutsch" },
|
|
||||||
korean: { code: "ko", name: "한국어" },
|
|
||||||
french: { code: "fr", name: "Français" },
|
|
||||||
turkish: { code: "tr", name: "Türkçe" },
|
|
||||||
italian: { code: "it", name: "Italiano" },
|
|
||||||
polish: { code: "pl", name: "Polski" },
|
polish: { code: "pl", name: "Polski" },
|
||||||
ukrainian: { code: "uk", name: "Українська" },
|
ukrainian: { code: "uk", name: "Українська" },
|
||||||
persian: { code: "fa", name: "فارسی" },
|
russian: { code: "ru", name: "Русский" },
|
||||||
dutch: { code: "nl", name: "Nederlands" },
|
french: { code: "fr", name: "Français" },
|
||||||
indonesian: { code: "id", name: "Bahasa Indonesia" },
|
german: { code: "de", name: "Deutsch" },
|
||||||
|
chineseTraditional: { code: "zh-Hant", name: "繁體中文" },
|
||||||
|
chineseSimplified: { code: "zh-Hans", name: "简体中文" },
|
||||||
|
turkish: { code: "tr", name: "Türkçe" },
|
||||||
kazakh: { code: "kz", name: "Қазақ" },
|
kazakh: { code: "kz", name: "Қазақ" },
|
||||||
|
persian: { code: "fa", name: "فارسی" },
|
||||||
|
korean: { code: "ko", name: "한국어" },
|
||||||
|
portuguese: { code: "pt-br", name: "Português" },
|
||||||
|
italian: { code: "it", name: "Italiano" },
|
||||||
|
japanese: { code: "ja", name: "日本語" },
|
||||||
|
spanish: { code: "es", name: "Español" },
|
||||||
norwegian: { code: "no", name: "Norsk" },
|
norwegian: { code: "no", name: "Norsk" },
|
||||||
azerbaijani: { code: "az", name: "Azərbaycan" },
|
azerbaijani: { code: "az", name: "Azərbaycan" },
|
||||||
|
indonesian: { code: "id", name: "Bahasa Indonesia" },
|
||||||
malayalam: { code: "ml", name: "മലയാളം" },
|
malayalam: { code: "ml", name: "മലയാളം" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.21.8",
|
"version": "v0.21.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"adm-zip": "^0.5.14",
|
"adm-zip": "^0.5.14",
|
||||||
"ai": "^4.0.23",
|
"ai": "^4.0.23",
|
||||||
"bcrypt": "5.1.1",
|
"bcrypt": "5.1.1",
|
||||||
"better-auth": "1.2.6",
|
"better-auth": "1.2.4",
|
||||||
"bl": "6.0.11",
|
"bl": "6.0.11",
|
||||||
"boxen": "^7.1.1",
|
"boxen": "^7.1.1",
|
||||||
"bullmq": "5.4.2",
|
"bullmq": "5.4.2",
|
||||||
|
|||||||
@@ -365,9 +365,7 @@ const Project = (
|
|||||||
|
|
||||||
switch (service.type) {
|
switch (service.type) {
|
||||||
case "application":
|
case "application":
|
||||||
await applicationActions.start.mutateAsync({
|
await applicationActions.start.mutateAsync({ applicationId: serviceId });
|
||||||
applicationId: serviceId,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case "compose":
|
case "compose":
|
||||||
await composeActions.start.mutateAsync({ composeId: serviceId });
|
await composeActions.start.mutateAsync({ composeId: serviceId });
|
||||||
@@ -412,9 +410,7 @@ const Project = (
|
|||||||
|
|
||||||
switch (service.type) {
|
switch (service.type) {
|
||||||
case "application":
|
case "application":
|
||||||
await applicationActions.stop.mutateAsync({
|
await applicationActions.stop.mutateAsync({ applicationId: serviceId });
|
||||||
applicationId: serviceId,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case "compose":
|
case "compose":
|
||||||
await composeActions.stop.mutateAsync({ composeId: serviceId });
|
await composeActions.stop.mutateAsync({ composeId: serviceId });
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ const Service = (
|
|||||||
router.push(newPath);
|
router.push(newPath);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex gap-8 justify-start max-xl:overflow-x-scroll overflow-y-hidden",
|
"flex gap-8 justify-start max-xl:overflow-x-scroll overflow-y-hidden",
|
||||||
|
|||||||
@@ -212,15 +212,15 @@ const Service = (
|
|||||||
router.push(newPath);
|
router.push(newPath);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"lg:grid lg:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
isCloud && data?.serverId
|
isCloud && data?.serverId
|
||||||
? "lg:grid-cols-7"
|
? "md:grid-cols-7"
|
||||||
: data?.serverId
|
: data?.serverId
|
||||||
? "lg:grid-cols-6"
|
? "md:grid-cols-6"
|
||||||
: "lg:grid-cols-7",
|
: "md:grid-cols-7",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TabsTrigger value="general">General</TabsTrigger>
|
<TabsTrigger value="general">General</TabsTrigger>
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const Mariadb = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const Mongo = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const MySql = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start ",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start ",
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const Postgresql = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const Redis = (
|
|||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"settings.common.save": "Opslaan",
|
|
||||||
"settings.common.enterTerminal": "Terminal",
|
|
||||||
"settings.server.domain.title": "Server Domein",
|
|
||||||
"settings.server.domain.description": "Voeg een domein toe aan jouw server applicatie.",
|
|
||||||
"settings.server.domain.form.domain": "Domein",
|
|
||||||
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Email",
|
|
||||||
"settings.server.domain.form.certificate.label": "Certificaat Aanbieder",
|
|
||||||
"settings.server.domain.form.certificate.placeholder": "Select een certificaat",
|
|
||||||
"settings.server.domain.form.certificateOptions.none": "Geen",
|
|
||||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
|
|
||||||
|
|
||||||
"settings.server.webServer.title": "Web Server",
|
|
||||||
"settings.server.webServer.description": "Herlaad of maak de web server schoon.",
|
|
||||||
"settings.server.webServer.actions": "Acties",
|
|
||||||
"settings.server.webServer.reload": "Herladen",
|
|
||||||
"settings.server.webServer.watchLogs": "Bekijk Logs",
|
|
||||||
"settings.server.webServer.updateServerIp": "Update de Server IP",
|
|
||||||
"settings.server.webServer.server.label": "Server",
|
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
|
||||||
"settings.server.webServer.traefik.modifyEnv": "Bewerk Omgeving",
|
|
||||||
"settings.server.webServer.traefik.managePorts": "Extra Poort Mappings",
|
|
||||||
"settings.server.webServer.traefik.managePortsDescription": "Bewerk extra Poorten voor Traefik",
|
|
||||||
"settings.server.webServer.traefik.targetPort": "Doel Poort",
|
|
||||||
"settings.server.webServer.traefik.publishedPort": "Gepubliceerde Poort",
|
|
||||||
"settings.server.webServer.traefik.addPort": "Voeg Poort toe",
|
|
||||||
"settings.server.webServer.traefik.portsUpdated": "Poorten succesvol aangepast",
|
|
||||||
"settings.server.webServer.traefik.portsUpdateError": "Poorten niet succesvol aangepast",
|
|
||||||
"settings.server.webServer.traefik.publishMode": "Publiceer Mode",
|
|
||||||
"settings.server.webServer.storage.label": "Opslag",
|
|
||||||
"settings.server.webServer.storage.cleanUnusedImages": "Maak ongebruikte images schoon",
|
|
||||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Maak ongebruikte volumes schoon",
|
|
||||||
"settings.server.webServer.storage.cleanStoppedContainers": "Maak gestopte containers schoon",
|
|
||||||
"settings.server.webServer.storage.cleanDockerBuilder": "Maak Docker Builder & Systeem schoon",
|
|
||||||
"settings.server.webServer.storage.cleanMonitoring": "Maak monitoor schoon",
|
|
||||||
"settings.server.webServer.storage.cleanAll": "Maak alles schoon",
|
|
||||||
|
|
||||||
"settings.profile.title": "Account",
|
|
||||||
"settings.profile.description": "Veramder details van account.",
|
|
||||||
"settings.profile.email": "Email",
|
|
||||||
"settings.profile.password": "Wachtwoord",
|
|
||||||
"settings.profile.avatar": "Profiel Icoon",
|
|
||||||
|
|
||||||
"settings.appearance.title": "Uiterlijk",
|
|
||||||
"settings.appearance.description": "Verander het thema van je dashboard.",
|
|
||||||
"settings.appearance.theme": "Thema",
|
|
||||||
"settings.appearance.themeDescription": "Selecteer een thema voor je dashboard.",
|
|
||||||
"settings.appearance.themes.light": "Licht",
|
|
||||||
"settings.appearance.themes.dark": "Donker",
|
|
||||||
"settings.appearance.themes.system": "Systeem",
|
|
||||||
"settings.appearance.language": "Taal",
|
|
||||||
"settings.appearance.languageDescription": "Selecteer een taal voor je dashboard.",
|
|
||||||
|
|
||||||
"settings.terminal.connectionSettings": "Verbindings instellingen",
|
|
||||||
"settings.terminal.ipAddress": "IP Address",
|
|
||||||
"settings.terminal.port": "Poort",
|
|
||||||
"settings.terminal.username": "Gebruikersnaam"
|
|
||||||
}
|
|
||||||
@@ -1,78 +1 @@
|
|||||||
{
|
{}
|
||||||
"dashboard.title": "仪表盘",
|
|
||||||
"dashboard.overview": "概览",
|
|
||||||
"dashboard.projects": "项目",
|
|
||||||
"dashboard.servers": "服务器",
|
|
||||||
"dashboard.docker": "Docker",
|
|
||||||
"dashboard.monitoring": "监控",
|
|
||||||
"dashboard.settings": "设置",
|
|
||||||
"dashboard.logout": "退出登录",
|
|
||||||
"dashboard.profile": "个人资料",
|
|
||||||
"dashboard.terminal": "终端",
|
|
||||||
"dashboard.containers": "容器",
|
|
||||||
"dashboard.images": "镜像",
|
|
||||||
"dashboard.volumes": "卷",
|
|
||||||
"dashboard.networks": "网络",
|
|
||||||
"button.create": "创建",
|
|
||||||
"button.edit": "编辑",
|
|
||||||
"button.delete": "删除",
|
|
||||||
"button.cancel": "取消",
|
|
||||||
"button.save": "保存",
|
|
||||||
"button.confirm": "确认",
|
|
||||||
"button.back": "返回",
|
|
||||||
"button.next": "下一步",
|
|
||||||
"button.finish": "完成",
|
|
||||||
"status.running": "运行中",
|
|
||||||
"status.stopped": "已停止",
|
|
||||||
"status.error": "错误",
|
|
||||||
"status.pending": "等待中",
|
|
||||||
"status.success": "成功",
|
|
||||||
"status.failed": "失败",
|
|
||||||
"form.required": "必填",
|
|
||||||
"form.invalid": "无效",
|
|
||||||
"form.submit": "提交",
|
|
||||||
"form.reset": "重置",
|
|
||||||
"notification.success": "操作成功",
|
|
||||||
"notification.error": "操作失败",
|
|
||||||
"notification.warning": "警告",
|
|
||||||
"notification.info": "信息",
|
|
||||||
"time.now": "刚刚",
|
|
||||||
"time.minutes": "分钟前",
|
|
||||||
"time.hours": "小时前",
|
|
||||||
"time.days": "天前",
|
|
||||||
"filter.all": "全部",
|
|
||||||
"filter.active": "活跃",
|
|
||||||
"filter.inactive": "不活跃",
|
|
||||||
"sort.asc": "升序",
|
|
||||||
"sort.desc": "降序",
|
|
||||||
"search.placeholder": "搜索...",
|
|
||||||
"search.noResults": "无结果",
|
|
||||||
"pagination.prev": "上一页",
|
|
||||||
"pagination.next": "下一页",
|
|
||||||
"pagination.of": "共 {0} 页",
|
|
||||||
"error.notFound": "未找到",
|
|
||||||
"error.serverError": "服务器错误",
|
|
||||||
"error.unauthorized": "未授权",
|
|
||||||
"error.forbidden": "禁止访问",
|
|
||||||
"loading": "加载中...",
|
|
||||||
"empty": "暂无数据",
|
|
||||||
"more": "更多",
|
|
||||||
"less": "收起",
|
|
||||||
"project.create": "创建项目",
|
|
||||||
"project.edit": "编辑项目",
|
|
||||||
"project.delete": "删除项目",
|
|
||||||
"project.name": "项目名称",
|
|
||||||
"project.description": "项目描述",
|
|
||||||
"service.create": "创建服务",
|
|
||||||
"service.edit": "编辑服务",
|
|
||||||
"service.delete": "删除服务",
|
|
||||||
"service.name": "服务名称",
|
|
||||||
"service.type": "服务类型",
|
|
||||||
"domain.add": "添加域名",
|
|
||||||
"domain.remove": "移除域名",
|
|
||||||
"environment.variables": "环境变量",
|
|
||||||
"environment.add": "添加环境变量",
|
|
||||||
"environment.edit": "编辑环境变量",
|
|
||||||
"environment.name": "变量名",
|
|
||||||
"environment.value": "变量值"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"settings.common.save": "保存",
|
"settings.common.save": "保存",
|
||||||
"settings.common.enterTerminal": "终端",
|
"settings.common.enterTerminal": "进入终端",
|
||||||
"settings.server.domain.title": "服务器域名",
|
"settings.server.domain.title": "域名设置",
|
||||||
"settings.server.domain.description": "为您的服务器应用添加域名。",
|
"settings.server.domain.description": "添加域名到服务器",
|
||||||
"settings.server.domain.form.domain": "域名",
|
"settings.server.domain.form.domain": "域名",
|
||||||
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 邮箱",
|
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 邮箱",
|
||||||
"settings.server.domain.form.certificate.label": "证书提供商",
|
"settings.server.domain.form.certificate.label": "证书",
|
||||||
"settings.server.domain.form.certificate.placeholder": "选择证书",
|
"settings.server.domain.form.certificate.placeholder": "选择一个证书",
|
||||||
"settings.server.domain.form.certificateOptions.none": "无",
|
"settings.server.domain.form.certificateOptions.none": "无",
|
||||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
|
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
|
||||||
"settings.server.webServer.title": "Web 服务器",
|
|
||||||
"settings.server.webServer.description": "重载或清理 Web 服务器。",
|
"settings.server.webServer.title": "服务器设置",
|
||||||
|
"settings.server.webServer.description": "管理服务器",
|
||||||
"settings.server.webServer.actions": "操作",
|
"settings.server.webServer.actions": "操作",
|
||||||
"settings.server.webServer.reload": "重新加载",
|
"settings.server.webServer.reload": "重新加载",
|
||||||
"settings.server.webServer.watchLogs": "查看日志",
|
"settings.server.webServer.watchLogs": "查看日志",
|
||||||
@@ -18,50 +19,40 @@
|
|||||||
"settings.server.webServer.server.label": "服务器",
|
"settings.server.webServer.server.label": "服务器",
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
"settings.server.webServer.traefik.label": "Traefik",
|
||||||
"settings.server.webServer.traefik.modifyEnv": "修改环境变量",
|
"settings.server.webServer.traefik.modifyEnv": "修改环境变量",
|
||||||
"settings.server.webServer.traefik.managePorts": "额外端口映射",
|
"settings.server.webServer.traefik.managePorts": "端口转发",
|
||||||
"settings.server.webServer.traefik.managePortsDescription": "为 Traefik 添加或删除额外端口",
|
"settings.server.webServer.traefik.managePortsDescription": "添加或删除 Traefik 的其他端口",
|
||||||
"settings.server.webServer.traefik.targetPort": "目标端口",
|
"settings.server.webServer.traefik.targetPort": "目标端口",
|
||||||
"settings.server.webServer.traefik.publishedPort": "发布端口",
|
"settings.server.webServer.traefik.publishedPort": "对外端口",
|
||||||
"settings.server.webServer.traefik.addPort": "添加端口",
|
"settings.server.webServer.traefik.addPort": "添加端口",
|
||||||
"settings.server.webServer.traefik.portsUpdated": "端口更新成功",
|
"settings.server.webServer.traefik.portsUpdated": "端口更新成功",
|
||||||
"settings.server.webServer.traefik.portsUpdateError": "端口更新失败",
|
"settings.server.webServer.traefik.portsUpdateError": "端口更新失败",
|
||||||
"settings.server.webServer.traefik.publishMode": "发布模式",
|
"settings.server.webServer.traefik.publishMode": "端口映射",
|
||||||
"settings.server.webServer.storage.label": "存储空间",
|
"settings.server.webServer.storage.label": "存储空间",
|
||||||
"settings.server.webServer.storage.cleanUnusedImages": "清理未使用的镜像",
|
"settings.server.webServer.storage.cleanUnusedImages": "清理未使用的镜像",
|
||||||
"settings.server.webServer.storage.cleanUnusedVolumes": "清理未使用的卷",
|
"settings.server.webServer.storage.cleanUnusedVolumes": "清理未使用的卷",
|
||||||
"settings.server.webServer.storage.cleanStoppedContainers": "清理已停止的容器",
|
"settings.server.webServer.storage.cleanStoppedContainers": "清理已停止的容器",
|
||||||
"settings.server.webServer.storage.cleanDockerBuilder": "清理 Docker Builder 和系统",
|
"settings.server.webServer.storage.cleanDockerBuilder": "清理 Docker Builder 与 系统缓存",
|
||||||
"settings.server.webServer.storage.cleanMonitoring": "清理监控数据",
|
"settings.server.webServer.storage.cleanMonitoring": "清理监控数据",
|
||||||
"settings.server.webServer.storage.cleanAll": "清理所有内容",
|
"settings.server.webServer.storage.cleanAll": "清理所有内容",
|
||||||
|
|
||||||
"settings.profile.title": "账户",
|
"settings.profile.title": "账户",
|
||||||
"settings.profile.description": "在此更改您的个人资料详情。",
|
"settings.profile.description": "更改您的个人资料",
|
||||||
"settings.profile.email": "邮箱",
|
"settings.profile.email": "邮箱",
|
||||||
"settings.profile.password": "密码",
|
"settings.profile.password": "密码",
|
||||||
"settings.profile.avatar": "头像",
|
"settings.profile.avatar": "头像",
|
||||||
|
|
||||||
"settings.appearance.title": "外观",
|
"settings.appearance.title": "外观",
|
||||||
"settings.appearance.description": "自定义您的仪表盘主题。",
|
"settings.appearance.description": "自定义面板主题",
|
||||||
"settings.appearance.theme": "主题",
|
"settings.appearance.theme": "主题",
|
||||||
"settings.appearance.themeDescription": "为您的仪表盘选择主题",
|
"settings.appearance.themeDescription": "选择面板主题",
|
||||||
"settings.appearance.themes.light": "明亮",
|
"settings.appearance.themes.light": "明亮",
|
||||||
"settings.appearance.themes.dark": "暗黑",
|
"settings.appearance.themes.dark": "黑暗",
|
||||||
"settings.appearance.themes.system": "跟随系统",
|
"settings.appearance.themes.system": "系统主题",
|
||||||
"settings.appearance.language": "语言",
|
"settings.appearance.language": "语言",
|
||||||
"settings.appearance.languageDescription": "为您的仪表盘选择语言",
|
"settings.appearance.languageDescription": "选择面板语言",
|
||||||
"settings.terminal.connectionSettings": "连接设置",
|
|
||||||
"settings.terminal.ipAddress": "IP 地址",
|
"settings.terminal.connectionSettings": "终端设置",
|
||||||
|
"settings.terminal.ipAddress": "IP",
|
||||||
"settings.terminal.port": "端口",
|
"settings.terminal.port": "端口",
|
||||||
"settings.terminal.username": "用户名",
|
"settings.terminal.username": "用户名"
|
||||||
"settings.settings": "设置",
|
|
||||||
"settings.general": "通用设置",
|
|
||||||
"settings.security": "安全",
|
|
||||||
"settings.users": "用户管理",
|
|
||||||
"settings.roles": "角色管理",
|
|
||||||
"settings.permissions": "权限",
|
|
||||||
"settings.api": "API设置",
|
|
||||||
"settings.certificates": "证书管理",
|
|
||||||
"settings.ssh": "SSH密钥",
|
|
||||||
"settings.backups": "备份",
|
|
||||||
"settings.logs": "日志",
|
|
||||||
"settings.updates": "更新",
|
|
||||||
"settings.network": "网络"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,7 +355,6 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
applicationStatus: "idle",
|
applicationStatus: "idle",
|
||||||
githubId: input.githubId,
|
githubId: input.githubId,
|
||||||
watchPaths: input.watchPaths,
|
watchPaths: input.watchPaths,
|
||||||
enableSubmodules: input.enableSubmodules,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -383,7 +382,6 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
gitlabProjectId: input.gitlabProjectId,
|
gitlabProjectId: input.gitlabProjectId,
|
||||||
gitlabPathNamespace: input.gitlabPathNamespace,
|
gitlabPathNamespace: input.gitlabPathNamespace,
|
||||||
watchPaths: input.watchPaths,
|
watchPaths: input.watchPaths,
|
||||||
enableSubmodules: input.enableSubmodules,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -409,7 +407,6 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
applicationStatus: "idle",
|
applicationStatus: "idle",
|
||||||
bitbucketId: input.bitbucketId,
|
bitbucketId: input.bitbucketId,
|
||||||
watchPaths: input.watchPaths,
|
watchPaths: input.watchPaths,
|
||||||
enableSubmodules: input.enableSubmodules,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -435,7 +432,6 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
applicationStatus: "idle",
|
applicationStatus: "idle",
|
||||||
giteaId: input.giteaId,
|
giteaId: input.giteaId,
|
||||||
watchPaths: input.watchPaths,
|
watchPaths: input.watchPaths,
|
||||||
enableSubmodules: input.enableSubmodules,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -483,7 +479,6 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
sourceType: "git",
|
sourceType: "git",
|
||||||
applicationStatus: "idle",
|
applicationStatus: "idle",
|
||||||
watchPaths: input.watchPaths,
|
watchPaths: input.watchPaths,
|
||||||
enableSubmodules: input.enableSubmodules,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -50,18 +50,6 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
interface RcloneFile {
|
|
||||||
Path: string;
|
|
||||||
Name: string;
|
|
||||||
Size: number;
|
|
||||||
IsDir: boolean;
|
|
||||||
Tier?: string;
|
|
||||||
Hashes?: {
|
|
||||||
MD5?: string;
|
|
||||||
SHA1?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const backupRouter = createTRPCRouter({
|
export const backupRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
.input(apiCreateBackup)
|
.input(apiCreateBackup)
|
||||||
@@ -280,7 +268,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
: input.search;
|
: input.search;
|
||||||
|
|
||||||
const searchPath = baseDir ? `${bucketPath}/${baseDir}` : bucketPath;
|
const searchPath = baseDir ? `${bucketPath}/${baseDir}` : bucketPath;
|
||||||
const listCommand = `rclone lsjson ${rcloneFlags.join(" ")} "${searchPath}" --no-mimetype --no-modtime 2>/dev/null`;
|
const listCommand = `rclone lsf ${rcloneFlags.join(" ")} "${searchPath}" | head -n 100`;
|
||||||
|
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
|
|
||||||
@@ -292,35 +280,20 @@ export const backupRouter = createTRPCRouter({
|
|||||||
stdout = result.stdout;
|
stdout = result.stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
let files: RcloneFile[] = [];
|
const files = stdout.split("\n").filter(Boolean);
|
||||||
try {
|
|
||||||
files = JSON.parse(stdout) as RcloneFile[];
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error parsing JSON response:", error);
|
|
||||||
console.error("Raw stdout:", stdout);
|
|
||||||
throw new Error("Failed to parse backup files list");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit to first 100 files
|
|
||||||
|
|
||||||
const results = baseDir
|
const results = baseDir
|
||||||
? files.map((file) => ({
|
? files.map((file) => `${baseDir}${file}`)
|
||||||
...file,
|
|
||||||
Path: `${baseDir}${file.Path}`,
|
|
||||||
}))
|
|
||||||
: files;
|
: files;
|
||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
return results
|
return results.filter((file) =>
|
||||||
.filter((file) =>
|
file.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
file.Path.toLowerCase().includes(searchTerm.toLowerCase()),
|
);
|
||||||
)
|
|
||||||
.slice(0, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.slice(0, 100);
|
return results;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in listBackupFiles:", error);
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const mountRouter = createTRPCRouter({
|
|||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateMount)
|
.input(apiUpdateMount)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await updateMount(input.mountId, input);
|
await updateMount(input.mountId, input);
|
||||||
|
return true;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -79,33 +79,6 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
await execAsync(`docker service update --force ${stdout.trim()}`);
|
await execAsync(`docker service update --force ${stdout.trim()}`);
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
cleanRedis: adminProcedure.mutation(async () => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { stdout: containerId } = await execAsync(
|
|
||||||
`docker ps --filter "name=dokploy-redis" --filter "status=running" -q | head -n 1`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!containerId) {
|
|
||||||
throw new Error("Redis container not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const redisContainerId = containerId.trim();
|
|
||||||
|
|
||||||
await execAsync(`docker exec -i ${redisContainerId} redis-cli flushall`);
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
reloadRedis: adminProcedure.mutation(async () => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await execAsync("docker service scale dokploy-redis=0");
|
|
||||||
await execAsync("docker service scale dokploy-redis=1");
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
reloadTraefik: adminProcedure
|
reloadTraefik: adminProcedure
|
||||||
.input(apiServerSchema)
|
.input(apiServerSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { setupTerminalWebSocketServer } from "./wss/terminal";
|
|||||||
|
|
||||||
config({ path: ".env" });
|
config({ path: ".env" });
|
||||||
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
|
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
|
||||||
const HOST = process.env.HOST || "0.0.0.0";
|
|
||||||
const dev = process.env.NODE_ENV !== "production";
|
const dev = process.env.NODE_ENV !== "production";
|
||||||
const app = next({ dev, turbopack: process.env.TURBOPACK === "1" });
|
const app = next({ dev, turbopack: process.env.TURBOPACK === "1" });
|
||||||
const handle = app.getRequestHandler();
|
const handle = app.getRequestHandler();
|
||||||
@@ -56,8 +55,8 @@ void app.prepare().then(async () => {
|
|||||||
await migration();
|
await migration();
|
||||||
}
|
}
|
||||||
|
|
||||||
server.listen(PORT, HOST);
|
server.listen(PORT);
|
||||||
console.log(`Server Started on: http://${HOST}:${PORT}`);
|
console.log("Server Started:", PORT);
|
||||||
if (!IS_CLOUD) {
|
if (!IS_CLOUD) {
|
||||||
console.log("Starting Deployment Worker");
|
console.log("Starting Deployment Worker");
|
||||||
const { deploymentWorker } = await import("./queues/deployments-queue");
|
const { deploymentWorker } = await import("./queues/deployments-queue");
|
||||||
|
|||||||
@@ -36,11 +36,11 @@
|
|||||||
"@ai-sdk/mistral": "^1.0.6",
|
"@ai-sdk/mistral": "^1.0.6",
|
||||||
"@ai-sdk/openai": "^1.0.12",
|
"@ai-sdk/openai": "^1.0.12",
|
||||||
"@ai-sdk/openai-compatible": "^0.0.13",
|
"@ai-sdk/openai-compatible": "^0.0.13",
|
||||||
"@better-auth/utils": "0.2.4",
|
"@better-auth/utils": "0.2.3",
|
||||||
"@oslojs/encoding": "1.1.0",
|
"@oslojs/encoding": "1.1.0",
|
||||||
"@oslojs/crypto": "1.0.1",
|
"@oslojs/crypto": "1.0.1",
|
||||||
"drizzle-dbml-generator": "0.10.0",
|
"drizzle-dbml-generator": "0.10.0",
|
||||||
"better-auth": "1.2.6",
|
"better-auth": "1.2.4",
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^8.4.1",
|
||||||
"@octokit/auth-app": "^6.0.4",
|
"@octokit/auth-app": "^6.0.4",
|
||||||
"@react-email/components": "^0.0.21",
|
"@react-email/components": "^0.0.21",
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ export const applications = pgTable("application", {
|
|||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
enableSubmodules: boolean("enableSubmodules").notNull().default(false),
|
|
||||||
dockerfile: text("dockerfile"),
|
dockerfile: text("dockerfile"),
|
||||||
dockerContextPath: text("dockerContextPath"),
|
dockerContextPath: text("dockerContextPath"),
|
||||||
dockerBuildStage: text("dockerBuildStage"),
|
dockerBuildStage: text("dockerBuildStage"),
|
||||||
@@ -471,7 +470,6 @@ export const apiSaveGithubProvider = createSchema
|
|||||||
buildPath: true,
|
buildPath: true,
|
||||||
githubId: true,
|
githubId: true,
|
||||||
watchPaths: true,
|
watchPaths: true,
|
||||||
enableSubmodules: true,
|
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
@@ -486,7 +484,6 @@ export const apiSaveGitlabProvider = createSchema
|
|||||||
gitlabProjectId: true,
|
gitlabProjectId: true,
|
||||||
gitlabPathNamespace: true,
|
gitlabPathNamespace: true,
|
||||||
watchPaths: true,
|
watchPaths: true,
|
||||||
enableSubmodules: true,
|
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
@@ -499,7 +496,6 @@ export const apiSaveBitbucketProvider = createSchema
|
|||||||
bitbucketId: true,
|
bitbucketId: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
watchPaths: true,
|
watchPaths: true,
|
||||||
enableSubmodules: true,
|
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
@@ -512,7 +508,6 @@ export const apiSaveGiteaProvider = createSchema
|
|||||||
giteaRepository: true,
|
giteaRepository: true,
|
||||||
giteaId: true,
|
giteaId: true,
|
||||||
watchPaths: true,
|
watchPaths: true,
|
||||||
enableSubmodules: true,
|
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
@@ -533,7 +528,6 @@ export const apiSaveGitProvider = createSchema
|
|||||||
customGitBuildPath: true,
|
customGitBuildPath: true,
|
||||||
customGitUrl: true,
|
customGitUrl: true,
|
||||||
watchPaths: true,
|
watchPaths: true,
|
||||||
enableSubmodules: true,
|
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
.merge(
|
.merge(
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export const compose = pgTable("compose", {
|
|||||||
),
|
),
|
||||||
command: text("command").notNull().default(""),
|
command: text("command").notNull().default(""),
|
||||||
//
|
//
|
||||||
enableSubmodules: boolean("enableSubmodules").notNull().default(false),
|
|
||||||
composePath: text("composePath").notNull().default("./docker-compose.yml"),
|
composePath: text("composePath").notNull().default("./docker-compose.yml"),
|
||||||
suffix: text("suffix").notNull().default(""),
|
suffix: text("suffix").notNull().default(""),
|
||||||
randomize: boolean("randomize").notNull().default(false),
|
randomize: boolean("randomize").notNull().default(false),
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ const { handler, api } = betterAuth({
|
|||||||
const host =
|
const host =
|
||||||
process.env.NODE_ENV === "development"
|
process.env.NODE_ENV === "development"
|
||||||
? "http://localhost:3000"
|
? "http://localhost:3000"
|
||||||
: "https://app.dokploy.com";
|
: "https://dokploy.com";
|
||||||
const inviteLink = `${host}/invitation?token=${data.id}`;
|
const inviteLink = `${host}/invitation?token=${data.id}`;
|
||||||
|
|
||||||
await sendEmail({
|
await sendEmail({
|
||||||
|
|||||||
@@ -356,7 +356,6 @@ export const deployRemoteCompose = async ({
|
|||||||
deployment.logPath,
|
deployment.logPath,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
console.log(command);
|
|
||||||
} else if (compose.sourceType === "raw") {
|
} else if (compose.sourceType === "raw") {
|
||||||
command += getCreateComposeFileCommand(compose, deployment.logPath);
|
command += getCreateComposeFileCommand(compose, deployment.logPath);
|
||||||
} else if (compose.sourceType === "gitea") {
|
} else if (compose.sourceType === "gitea") {
|
||||||
|
|||||||
@@ -144,8 +144,7 @@ export const updateMount = async (
|
|||||||
await deleteFileMount(mountId);
|
await deleteFileMount(mountId);
|
||||||
await createFileMount(mountId);
|
await createFileMount(mountId);
|
||||||
}
|
}
|
||||||
|
return mount;
|
||||||
return await findMountById(mountId);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ CURRENT_USER=$USER
|
|||||||
|
|
||||||
echo "Installing requirements for: OS: $OS_TYPE"
|
echo "Installing requirements for: OS: $OS_TYPE"
|
||||||
if [ $EUID != 0 ]; then
|
if [ $EUID != 0 ]; then
|
||||||
echo "Please run this script as root or with sudo ❌"
|
echo "Please run this script as root or with sudo ❌"
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ const setupMainDirectory = () => `
|
|||||||
# Create the /etc/dokploy directory
|
# Create the /etc/dokploy directory
|
||||||
mkdir -p /etc/dokploy
|
mkdir -p /etc/dokploy
|
||||||
chmod 777 /etc/dokploy
|
chmod 777 /etc/dokploy
|
||||||
|
|
||||||
echo "Directory /etc/dokploy created ✅"
|
echo "Directory /etc/dokploy created ✅"
|
||||||
fi
|
fi
|
||||||
`;
|
`;
|
||||||
@@ -276,16 +276,16 @@ export const setupSwarm = () => `
|
|||||||
# Get IP address
|
# Get IP address
|
||||||
get_ip() {
|
get_ip() {
|
||||||
local ip=""
|
local ip=""
|
||||||
|
|
||||||
# Try IPv4 with multiple services
|
# Try IPv4 with multiple services
|
||||||
# First attempt: ifconfig.io
|
# First attempt: ifconfig.io
|
||||||
ip=\$(curl -4s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
|
ip=\$(curl -4s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
|
||||||
|
|
||||||
# Second attempt: icanhazip.com
|
# Second attempt: icanhazip.com
|
||||||
if [ -z "\$ip" ]; then
|
if [ -z "\$ip" ]; then
|
||||||
ip=\$(curl -4s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
|
ip=\$(curl -4s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Third attempt: ipecho.net
|
# Third attempt: ipecho.net
|
||||||
if [ -z "\$ip" ]; then
|
if [ -z "\$ip" ]; then
|
||||||
ip=\$(curl -4s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)
|
ip=\$(curl -4s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)
|
||||||
@@ -295,12 +295,12 @@ export const setupSwarm = () => `
|
|||||||
if [ -z "\$ip" ]; then
|
if [ -z "\$ip" ]; then
|
||||||
# Try IPv6 with ifconfig.io
|
# Try IPv6 with ifconfig.io
|
||||||
ip=\$(curl -6s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
|
ip=\$(curl -6s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
|
||||||
|
|
||||||
# Try IPv6 with icanhazip.com
|
# Try IPv6 with icanhazip.com
|
||||||
if [ -z "\$ip" ]; then
|
if [ -z "\$ip" ]; then
|
||||||
ip=\$(curl -6s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
|
ip=\$(curl -6s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try IPv6 with ipecho.net
|
# Try IPv6 with ipecho.net
|
||||||
if [ -z "\$ip" ]; then
|
if [ -z "\$ip" ]; then
|
||||||
ip=\$(curl -6s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)
|
ip=\$(curl -6s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)
|
||||||
@@ -549,7 +549,7 @@ export const createTraefikInstance = () => {
|
|||||||
sleep 8
|
sleep 8
|
||||||
echo "Traefik migrated to Standalone ✅"
|
echo "Traefik migrated to Standalone ✅"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if docker inspect dokploy-traefik > /dev/null 2>&1; then
|
if docker inspect dokploy-traefik > /dev/null 2>&1; then
|
||||||
echo "Traefik already exists ✅"
|
echo "Traefik already exists ✅"
|
||||||
else
|
else
|
||||||
@@ -577,7 +577,7 @@ const installNixpacks = () => `
|
|||||||
if command_exists nixpacks; then
|
if command_exists nixpacks; then
|
||||||
echo "Nixpacks already installed ✅"
|
echo "Nixpacks already installed ✅"
|
||||||
else
|
else
|
||||||
export NIXPACKS_VERSION=1.35.0
|
export NIXPACKS_VERSION=1.29.1
|
||||||
bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
|
bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
|
||||||
echo "Nixpacks version $NIXPACKS_VERSION installed ✅"
|
echo "Nixpacks version $NIXPACKS_VERSION installed ✅"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { randomBytes, createHmac } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
@@ -24,12 +24,6 @@ export interface Template {
|
|||||||
domains: DomainSchema[];
|
domains: DomainSchema[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenerateJWTOptions {
|
|
||||||
length?: number;
|
|
||||||
secret?: string;
|
|
||||||
payload?: Record<string, unknown> | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateRandomDomain = ({
|
export const generateRandomDomain = ({
|
||||||
serverIp,
|
serverIp,
|
||||||
projectName,
|
projectName,
|
||||||
@@ -67,48 +61,8 @@ export function generateBase64(bytes = 32): string {
|
|||||||
return randomBytes(bytes).toString("base64");
|
return randomBytes(bytes).toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeBase64(str: string): string {
|
export function generateJwt(length = 256): string {
|
||||||
return str.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
return randomBytes(length).toString("hex");
|
||||||
}
|
|
||||||
function objToJWTBase64(obj: any): string {
|
|
||||||
return safeBase64(
|
|
||||||
Buffer.from(JSON.stringify(obj), "utf8").toString("base64"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateJwt(options: GenerateJWTOptions = {}): string {
|
|
||||||
let { length, secret, payload = {} } = options;
|
|
||||||
if (length) {
|
|
||||||
return randomBytes(length).toString("hex");
|
|
||||||
}
|
|
||||||
const encodedHeader = objToJWTBase64({
|
|
||||||
alg: "HS256",
|
|
||||||
typ: "JWT",
|
|
||||||
});
|
|
||||||
if (!payload.iss) {
|
|
||||||
payload.iss = "dokploy";
|
|
||||||
}
|
|
||||||
if (!payload.iat) {
|
|
||||||
payload.iat = Math.floor(Date.now() / 1000);
|
|
||||||
}
|
|
||||||
if (!payload.exp) {
|
|
||||||
payload.exp = Math.floor(new Date("2030-01-01T00:00:00Z").getTime() / 1000);
|
|
||||||
}
|
|
||||||
const encodedPayload = objToJWTBase64({
|
|
||||||
iat: Math.floor(Date.now() / 1000),
|
|
||||||
exp: Math.floor(new Date("2030-01-01T00:00:00Z").getTime() / 1000),
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
if (!secret) {
|
|
||||||
secret = randomBytes(32).toString("hex");
|
|
||||||
}
|
|
||||||
const signature = safeBase64(
|
|
||||||
createHmac("SHA256", secret)
|
|
||||||
.update(`${encodedHeader}.${encodedPayload}`)
|
|
||||||
.digest("base64"),
|
|
||||||
);
|
|
||||||
|
|
||||||
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export interface Template {
|
|||||||
/**
|
/**
|
||||||
* Process a string value and replace variables
|
* Process a string value and replace variables
|
||||||
*/
|
*/
|
||||||
export function processValue(
|
function processValue(
|
||||||
value: string,
|
value: string,
|
||||||
variables: Record<string, string>,
|
variables: Record<string, string>,
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
@@ -84,11 +84,11 @@ export function processValue(
|
|||||||
const length = Number.parseInt(varName.split(":")[1], 10) || 32;
|
const length = Number.parseInt(varName.split(":")[1], 10) || 32;
|
||||||
return generateBase64(length);
|
return generateBase64(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (varName.startsWith("password:")) {
|
if (varName.startsWith("password:")) {
|
||||||
const length = Number.parseInt(varName.split(":")[1], 10) || 16;
|
const length = Number.parseInt(varName.split(":")[1], 10) || 16;
|
||||||
return generatePassword(length);
|
return generatePassword(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (varName === "password") {
|
if (varName === "password") {
|
||||||
return generatePassword(16);
|
return generatePassword(16);
|
||||||
}
|
}
|
||||||
@@ -97,31 +97,14 @@ export function processValue(
|
|||||||
const length = Number.parseInt(varName.split(":")[1], 10) || 8;
|
const length = Number.parseInt(varName.split(":")[1], 10) || 8;
|
||||||
return generateHash(length);
|
return generateHash(length);
|
||||||
}
|
}
|
||||||
if (varName === "hash") {
|
|
||||||
return generateHash();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (varName === "uuid") {
|
if (varName === "uuid") {
|
||||||
return crypto.randomUUID();
|
return crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (varName === "timestamp" || varName === "timestampms") {
|
if (varName === "timestamp") {
|
||||||
return Date.now().toString();
|
return Date.now().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (varName === "timestamps") {
|
|
||||||
return Math.round(Date.now() / 1000).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (varName.startsWith("timestampms:")) {
|
|
||||||
return new Date(varName.slice(12)).getTime().toString();
|
|
||||||
}
|
|
||||||
if (varName.startsWith("timestamps:")) {
|
|
||||||
return Math.round(
|
|
||||||
new Date(varName.slice(11)).getTime() / 1000,
|
|
||||||
).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (varName === "randomPort") {
|
if (varName === "randomPort") {
|
||||||
return Math.floor(Math.random() * 65535).toString();
|
return Math.floor(Math.random() * 65535).toString();
|
||||||
}
|
}
|
||||||
@@ -131,34 +114,8 @@ export function processValue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (varName.startsWith("jwt:")) {
|
if (varName.startsWith("jwt:")) {
|
||||||
const params: string[] = varName.split(":").slice(1);
|
const length = Number.parseInt(varName.split(":")[1], 10) || 256;
|
||||||
if (params.length === 1 && params[0] && params[0].match(/^\d{1,3}$/)) {
|
return generateJwt(length);
|
||||||
return generateJwt({ length: Number.parseInt(params[0], 10) });
|
|
||||||
}
|
|
||||||
let [secret, payload] = params;
|
|
||||||
if (typeof payload === "string" && variables[payload]) {
|
|
||||||
payload = variables[payload];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof payload === "string" &&
|
|
||||||
payload.startsWith("{") &&
|
|
||||||
payload.endsWith("}")
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(payload);
|
|
||||||
} catch (e) {
|
|
||||||
// If payload is not a valid JSON, invalid it
|
|
||||||
payload = undefined;
|
|
||||||
console.error("Invalid JWT payload", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof payload !== "object") {
|
|
||||||
payload = undefined;
|
|
||||||
}
|
|
||||||
return generateJwt({
|
|
||||||
secret: secret ? variables[secret] || secret : undefined,
|
|
||||||
payload: payload as any,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (varName === "username") {
|
if (varName === "username") {
|
||||||
@@ -190,7 +147,7 @@ export function processVariables(
|
|||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
const variables: Record<string, string> = {};
|
const variables: Record<string, string> = {};
|
||||||
|
|
||||||
// First pass: Process some variables that don't depend on other variables
|
// First pass: Process variables that don't depend on other variables
|
||||||
for (const [key, value] of Object.entries(template.variables)) {
|
for (const [key, value] of Object.entries(template.variables)) {
|
||||||
if (typeof value !== "string") continue;
|
if (typeof value !== "string") continue;
|
||||||
|
|
||||||
@@ -204,8 +161,6 @@ export function processVariables(
|
|||||||
const match = value.match(/\${password:(\d+)}/);
|
const match = value.match(/\${password:(\d+)}/);
|
||||||
const length = match?.[1] ? Number.parseInt(match[1], 10) : 16;
|
const length = match?.[1] ? Number.parseInt(match[1], 10) : 16;
|
||||||
variables[key] = generatePassword(length);
|
variables[key] = generatePassword(length);
|
||||||
} else if (value === "${hash}") {
|
|
||||||
variables[key] = generateHash();
|
|
||||||
} else if (value.startsWith("${hash:")) {
|
} else if (value.startsWith("${hash:")) {
|
||||||
const match = value.match(/\${hash:(\d+)}/);
|
const match = value.match(/\${hash:(\d+)}/);
|
||||||
const length = match?.[1] ? Number.parseInt(match[1], 10) : 8;
|
const length = match?.[1] ? Number.parseInt(match[1], 10) : 8;
|
||||||
|
|||||||
@@ -23,25 +23,13 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
|
|||||||
try {
|
try {
|
||||||
await execAsync(`mkdir -p ${tempDir}/filesystem`);
|
await execAsync(`mkdir -p ${tempDir}/filesystem`);
|
||||||
|
|
||||||
// First get the container ID
|
const postgresCommand = `docker exec $(docker ps --filter "name=dokploy-postgres" -q) pg_dump -v -Fc -U dokploy -d dokploy > ${tempDir}/database.sql`;
|
||||||
const { stdout: containerId } = await execAsync(
|
|
||||||
`docker ps --filter "name=dokploy-postgres" --filter "status=running" -q | head -n 1`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!containerId) {
|
|
||||||
throw new Error("PostgreSQL container not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const postgresContainerId = containerId.trim();
|
|
||||||
|
|
||||||
const postgresCommand = `docker exec ${postgresContainerId} pg_dump -v -Fc -U dokploy -d dokploy > '${tempDir}/database.sql'`;
|
|
||||||
await execAsync(postgresCommand);
|
await execAsync(postgresCommand);
|
||||||
|
|
||||||
await execAsync(`cp -r ${BASE_PATH}/* ${tempDir}/filesystem/`);
|
await execAsync(`cp -r ${BASE_PATH}/* ${tempDir}/filesystem/`);
|
||||||
|
|
||||||
await execAsync(
|
await execAsync(
|
||||||
// Zip all .sql files since we created more than one
|
`cd ${tempDir} && zip -r ${backupFileName} database.sql filesystem/ > /dev/null 2>&1`,
|
||||||
`cd ${tempDir} && zip -r ${backupFileName} *.sql filesystem/ > /dev/null 2>&1`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${tempDir}/${backupFileName}" "${s3Path}"`;
|
const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${tempDir}/${backupFileName}" "${s3Path}"`;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const buildRailpack = async (
|
|||||||
for (const envVar of envVariables) {
|
for (const envVar of envVariables) {
|
||||||
const [key, value] = envVar.split("=");
|
const [key, value] = envVar.split("=");
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
buildArgs.push("--secret", `id=${key},env='${key}'`);
|
buildArgs.push("--secret", `id=${key},env=${key}`);
|
||||||
env[key] = value;
|
env[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ export const getRailpackCommand = (
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const env of envVariables) {
|
for (const env of envVariables) {
|
||||||
prepareArgs.push("--env", `'${env}'`);
|
prepareArgs.push("--env", env);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate secrets hash for layer invalidation
|
// Calculate secrets hash for layer invalidation
|
||||||
@@ -164,7 +164,7 @@ export const getRailpackCommand = (
|
|||||||
for (const envVar of envVariables) {
|
for (const envVar of envVariables) {
|
||||||
const [key, value] = envVar.split("=");
|
const [key, value] = envVar.split("=");
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
buildArgs.push("--secret", `id=${key},env='${key}'`);
|
buildArgs.push("--secret", `id=${key},env=${key}`);
|
||||||
exportEnvs.push(`export ${key}=${value}`);
|
exportEnvs.push(`export ${key}=${value}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,11 +249,6 @@ export const addDomainToCompose = async (
|
|||||||
labels.unshift("traefik.enable=true");
|
labels.unshift("traefik.enable=true");
|
||||||
}
|
}
|
||||||
labels.unshift(...httpLabels);
|
labels.unshift(...httpLabels);
|
||||||
if (!compose.isolatedDeployment) {
|
|
||||||
if (!labels.includes("traefik.docker.network=dokploy-network")) {
|
|
||||||
labels.unshift("traefik.docker.network=dokploy-network");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compose.isolatedDeployment) {
|
if (!compose.isolatedDeployment) {
|
||||||
|
|||||||
@@ -40,84 +40,68 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
||||||
|
|
||||||
try {
|
await sendDiscordNotification(discord, {
|
||||||
await sendDiscordNotification(discord, {
|
title: decorate(">", "`✅` Dokploy Server Restarted"),
|
||||||
title: decorate(">", "`✅` Dokploy Server Restarted"),
|
color: 0x57f287,
|
||||||
color: 0x57f287,
|
fields: [
|
||||||
fields: [
|
{
|
||||||
{
|
name: decorate("`📅`", "Date"),
|
||||||
name: decorate("`📅`", "Date"),
|
value: `<t:${unixDate}:D>`,
|
||||||
value: `<t:${unixDate}:D>`,
|
inline: true,
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: decorate("`⌚`", "Time"),
|
|
||||||
value: `<t:${unixDate}:t>`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: decorate("`❓`", "Type"),
|
|
||||||
value: "Successful",
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamp: date.toISOString(),
|
|
||||||
footer: {
|
|
||||||
text: "Dokploy Restart Notification",
|
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
} catch (error) {
|
name: decorate("`⌚`", "Time"),
|
||||||
console.log(error);
|
value: `<t:${unixDate}:t>`,
|
||||||
}
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: decorate("`❓`", "Type"),
|
||||||
|
value: "Successful",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Restart Notification",
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gotify) {
|
if (gotify) {
|
||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||||
try {
|
await sendGotifyNotification(
|
||||||
await sendGotifyNotification(
|
gotify,
|
||||||
gotify,
|
decorate("✅", "Dokploy Server Restarted"),
|
||||||
decorate("✅", "Dokploy Server Restarted"),
|
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}`,
|
||||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}`,
|
);
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
try {
|
await sendTelegramNotification(
|
||||||
await sendTelegramNotification(
|
telegram,
|
||||||
telegram,
|
`<b>✅ Dokploy Server Restarted</b>\n\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||||
`<b>✅ Dokploy Server Restarted</b>\n\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
|
);
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slack) {
|
if (slack) {
|
||||||
const { channel } = slack;
|
const { channel } = slack;
|
||||||
try {
|
await sendSlackNotification(slack, {
|
||||||
await sendSlackNotification(slack, {
|
channel: channel,
|
||||||
channel: channel,
|
attachments: [
|
||||||
attachments: [
|
{
|
||||||
{
|
color: "#00FF00",
|
||||||
color: "#00FF00",
|
pretext: ":white_check_mark: *Dokploy Server Restarted*",
|
||||||
pretext: ":white_check_mark: *Dokploy Server Restarted*",
|
fields: [
|
||||||
fields: [
|
{
|
||||||
{
|
title: "Time",
|
||||||
title: "Time",
|
value: date.toLocaleString(),
|
||||||
value: date.toLocaleString(),
|
short: true,
|
||||||
short: true,
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
],
|
});
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const cloneBitbucketRepository = async (
|
|||||||
bitbucketBranch,
|
bitbucketBranch,
|
||||||
bitbucketId,
|
bitbucketId,
|
||||||
bitbucket,
|
bitbucket,
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
} = entity;
|
||||||
|
|
||||||
if (!bitbucketId) {
|
if (!bitbucketId) {
|
||||||
@@ -54,23 +53,25 @@ export const cloneBitbucketRepository = async (
|
|||||||
const cloneUrl = `https://${bitbucket?.bitbucketUsername}:${bitbucket?.appPassword}@${repoclone}`;
|
const cloneUrl = `https://${bitbucket?.bitbucketUsername}:${bitbucket?.appPassword}@${repoclone}`;
|
||||||
try {
|
try {
|
||||||
writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
||||||
const cloneArgs = [
|
await spawnAsync(
|
||||||
"clone",
|
"git",
|
||||||
"--branch",
|
[
|
||||||
bitbucketBranch!,
|
"clone",
|
||||||
"--depth",
|
"--branch",
|
||||||
"1",
|
bitbucketBranch!,
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--depth",
|
||||||
cloneUrl,
|
"1",
|
||||||
outputPath,
|
"--recurse-submodules",
|
||||||
"--progress",
|
cloneUrl,
|
||||||
];
|
outputPath,
|
||||||
|
"--progress",
|
||||||
await spawnAsync("git", cloneArgs, (data) => {
|
],
|
||||||
if (writeStream.writable) {
|
(data) => {
|
||||||
writeStream.write(data);
|
if (writeStream.writable) {
|
||||||
}
|
writeStream.write(data);
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
writeStream.write(`\nCloned ${repoclone} to ${outputPath}: ✅\n`);
|
writeStream.write(`\nCloned ${repoclone} to ${outputPath}: ✅\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
||||||
@@ -88,7 +89,6 @@ export const cloneRawBitbucketRepository = async (entity: Compose) => {
|
|||||||
bitbucketOwner,
|
bitbucketOwner,
|
||||||
bitbucketBranch,
|
bitbucketBranch,
|
||||||
bitbucketId,
|
bitbucketId,
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
} = entity;
|
||||||
|
|
||||||
if (!bitbucketId) {
|
if (!bitbucketId) {
|
||||||
@@ -106,19 +106,17 @@ export const cloneRawBitbucketRepository = async (entity: Compose) => {
|
|||||||
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cloneArgs = [
|
await spawnAsync("git", [
|
||||||
"clone",
|
"clone",
|
||||||
"--branch",
|
"--branch",
|
||||||
bitbucketBranch!,
|
bitbucketBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
];
|
]);
|
||||||
|
|
||||||
await spawnAsync("git", cloneArgs);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -133,7 +131,6 @@ export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
|
|||||||
bitbucketBranch,
|
bitbucketBranch,
|
||||||
bitbucketId,
|
bitbucketId,
|
||||||
serverId,
|
serverId,
|
||||||
enableSubmodules,
|
|
||||||
} = compose;
|
} = compose;
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
@@ -156,11 +153,11 @@ export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
|
|||||||
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cloneCommand = `
|
const command = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
git clone --branch ${bitbucketBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
|
git clone --branch ${bitbucketBranch} --depth 1 --recurse-submodules ${cloneUrl} ${outputPath}
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, cloneCommand);
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -179,7 +176,6 @@ export const getBitbucketCloneCommand = async (
|
|||||||
bitbucketBranch,
|
bitbucketBranch,
|
||||||
bitbucketId,
|
bitbucketId,
|
||||||
serverId,
|
serverId,
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
} = entity;
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
@@ -211,7 +207,7 @@ export const getBitbucketCloneCommand = async (
|
|||||||
const cloneCommand = `
|
const cloneCommand = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
if ! git clone --branch ${bitbucketBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
if ! git clone --branch ${bitbucketBranch} --depth 1 --recurse-submodules --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -17,19 +17,12 @@ export const cloneGitRepository = async (
|
|||||||
customGitUrl?: string | null;
|
customGitUrl?: string | null;
|
||||||
customGitBranch?: string | null;
|
customGitBranch?: string | null;
|
||||||
customGitSSHKeyId?: string | null;
|
customGitSSHKeyId?: string | null;
|
||||||
enableSubmodules?: boolean;
|
|
||||||
},
|
},
|
||||||
logPath: string,
|
logPath: string,
|
||||||
isCompose = false,
|
isCompose = false,
|
||||||
) => {
|
) => {
|
||||||
const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths();
|
const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths();
|
||||||
const {
|
const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
|
||||||
appName,
|
|
||||||
customGitUrl,
|
|
||||||
customGitBranch,
|
|
||||||
customGitSSHKeyId,
|
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
|
||||||
|
|
||||||
if (!customGitUrl || !customGitBranch) {
|
if (!customGitUrl || !customGitBranch) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -77,21 +70,19 @@ export const cloneGitRepository = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
||||||
const cloneArgs = [
|
|
||||||
"clone",
|
|
||||||
"--branch",
|
|
||||||
customGitBranch,
|
|
||||||
"--depth",
|
|
||||||
"1",
|
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
|
||||||
customGitUrl,
|
|
||||||
outputPath,
|
|
||||||
"--progress",
|
|
||||||
];
|
|
||||||
|
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"git",
|
"git",
|
||||||
cloneArgs,
|
[
|
||||||
|
"clone",
|
||||||
|
"--branch",
|
||||||
|
customGitBranch,
|
||||||
|
"--depth",
|
||||||
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
|
customGitUrl,
|
||||||
|
outputPath,
|
||||||
|
"--progress",
|
||||||
|
],
|
||||||
(data) => {
|
(data) => {
|
||||||
if (writeStream.writable) {
|
if (writeStream.writable) {
|
||||||
writeStream.write(data);
|
writeStream.write(data);
|
||||||
@@ -123,7 +114,6 @@ export const getCustomGitCloneCommand = async (
|
|||||||
customGitBranch?: string | null;
|
customGitBranch?: string | null;
|
||||||
customGitSSHKeyId?: string | null;
|
customGitSSHKeyId?: string | null;
|
||||||
serverId: string | null;
|
serverId: string | null;
|
||||||
enableSubmodules: boolean;
|
|
||||||
},
|
},
|
||||||
logPath: string,
|
logPath: string,
|
||||||
isCompose = false,
|
isCompose = false,
|
||||||
@@ -135,7 +125,6 @@ export const getCustomGitCloneCommand = async (
|
|||||||
customGitBranch,
|
customGitBranch,
|
||||||
customGitSSHKeyId,
|
customGitSSHKeyId,
|
||||||
serverId,
|
serverId,
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
} = entity;
|
||||||
|
|
||||||
if (!customGitUrl || !customGitBranch) {
|
if (!customGitUrl || !customGitBranch) {
|
||||||
@@ -192,7 +181,7 @@ export const getCustomGitCloneCommand = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
command.push(
|
command.push(
|
||||||
`if ! git clone --branch ${customGitBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then
|
`if ! git clone --branch ${customGitBranch} --depth 1 --recurse-submodules --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
|
echo "❌ [ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
@@ -272,15 +261,8 @@ export const cloneGitRawRepository = async (entity: {
|
|||||||
customGitUrl?: string | null;
|
customGitUrl?: string | null;
|
||||||
customGitBranch?: string | null;
|
customGitBranch?: string | null;
|
||||||
customGitSSHKeyId?: string | null;
|
customGitSSHKeyId?: string | null;
|
||||||
enableSubmodules?: boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
|
||||||
appName,
|
|
||||||
customGitUrl,
|
|
||||||
customGitBranch,
|
|
||||||
customGitSSHKeyId,
|
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
|
||||||
|
|
||||||
if (!customGitUrl || !customGitBranch) {
|
if (!customGitUrl || !customGitBranch) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -325,26 +307,29 @@ export const cloneGitRawRepository = async (entity: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
const { port } = sanitizeRepoPathSSH(customGitUrl);
|
||||||
const cloneArgs = [
|
await spawnAsync(
|
||||||
"clone",
|
"git",
|
||||||
"--branch",
|
[
|
||||||
customGitBranch,
|
"clone",
|
||||||
"--depth",
|
"--branch",
|
||||||
"1",
|
customGitBranch,
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--depth",
|
||||||
customGitUrl,
|
"1",
|
||||||
outputPath,
|
"--recurse-submodules",
|
||||||
"--progress",
|
customGitUrl,
|
||||||
];
|
outputPath,
|
||||||
|
"--progress",
|
||||||
await spawnAsync("git", cloneArgs, (_data) => {}, {
|
],
|
||||||
env: {
|
(_data) => {},
|
||||||
...process.env,
|
{
|
||||||
...(customGitSSHKeyId && {
|
env: {
|
||||||
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
|
...process.env,
|
||||||
}),
|
...(customGitSSHKeyId && {
|
||||||
|
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -357,7 +342,6 @@ export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
|
|||||||
customGitUrl,
|
customGitUrl,
|
||||||
customGitSSHKeyId,
|
customGitSSHKeyId,
|
||||||
serverId,
|
serverId,
|
||||||
enableSubmodules,
|
|
||||||
} = compose;
|
} = compose;
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
@@ -412,7 +396,7 @@ export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
command.push(
|
command.push(
|
||||||
`if ! git clone --branch ${customGitBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${customGitUrl} ${outputPath} ; then
|
`if ! git clone --branch ${customGitBranch} --depth 1 --recurse-submodules --progress ${customGitUrl} ${outputPath} ; then
|
||||||
echo "[ERROR] Fail to clone the repository ";
|
echo "[ERROR] Fail to clone the repository ";
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ export const getGiteaCloneCommand = async (
|
|||||||
giteaRepository,
|
giteaRepository,
|
||||||
serverId,
|
serverId,
|
||||||
gitea,
|
gitea,
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
} = entity;
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
@@ -156,7 +155,7 @@ export const getGiteaCloneCommand = async (
|
|||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
|
|
||||||
if ! git clone --branch ${giteaBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
if ! git clone --branch ${giteaBranch} --depth 1 --recurse-submodules ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Failed to clone the repository ${repoClone}" >> ${logPath};
|
echo "❌ [ERROR] Failed to clone the repository ${repoClone}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
@@ -175,14 +174,7 @@ export const cloneGiteaRepository = async (
|
|||||||
const { APPLICATIONS_PATH, COMPOSE_PATH } = paths();
|
const { APPLICATIONS_PATH, COMPOSE_PATH } = paths();
|
||||||
|
|
||||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||||
const {
|
const { appName, giteaBranch, giteaId, giteaOwner, giteaRepository } = entity;
|
||||||
appName,
|
|
||||||
giteaBranch,
|
|
||||||
giteaId,
|
|
||||||
giteaOwner,
|
|
||||||
giteaRepository,
|
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
|
||||||
|
|
||||||
if (!giteaId) {
|
if (!giteaId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -219,7 +211,7 @@ export const cloneGiteaRepository = async (
|
|||||||
giteaBranch!,
|
giteaBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -240,14 +232,7 @@ export const cloneGiteaRepository = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const cloneRawGiteaRepository = async (entity: Compose) => {
|
export const cloneRawGiteaRepository = async (entity: Compose) => {
|
||||||
const {
|
const { appName, giteaRepository, giteaOwner, giteaBranch, giteaId } = entity;
|
||||||
appName,
|
|
||||||
giteaRepository,
|
|
||||||
giteaOwner,
|
|
||||||
giteaBranch,
|
|
||||||
giteaId,
|
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
|
||||||
const { COMPOSE_PATH } = paths();
|
const { COMPOSE_PATH } = paths();
|
||||||
|
|
||||||
if (!giteaId) {
|
if (!giteaId) {
|
||||||
@@ -280,7 +265,7 @@ export const cloneRawGiteaRepository = async (entity: Compose) => {
|
|||||||
giteaBranch!,
|
giteaBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -298,7 +283,6 @@ export const cloneRawGiteaRepositoryRemote = async (compose: Compose) => {
|
|||||||
giteaBranch,
|
giteaBranch,
|
||||||
giteaId,
|
giteaId,
|
||||||
serverId,
|
serverId,
|
||||||
enableSubmodules,
|
|
||||||
} = compose;
|
} = compose;
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
@@ -323,7 +307,7 @@ export const cloneRawGiteaRepositoryRemote = async (compose: Compose) => {
|
|||||||
try {
|
try {
|
||||||
const command = `
|
const command = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
git clone --branch ${giteaBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
|
git clone --branch ${giteaBranch} --depth 1 ${cloneUrl} ${outputPath}
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ interface CloneGithubRepository {
|
|||||||
repository: string | null;
|
repository: string | null;
|
||||||
logPath: string;
|
logPath: string;
|
||||||
type?: "application" | "compose";
|
type?: "application" | "compose";
|
||||||
enableSubmodules: boolean;
|
|
||||||
}
|
}
|
||||||
export const cloneGithubRepository = async ({
|
export const cloneGithubRepository = async ({
|
||||||
logPath,
|
logPath,
|
||||||
@@ -93,8 +92,7 @@ export const cloneGithubRepository = async ({
|
|||||||
const isCompose = type === "compose";
|
const isCompose = type === "compose";
|
||||||
const { APPLICATIONS_PATH, COMPOSE_PATH } = paths();
|
const { APPLICATIONS_PATH, COMPOSE_PATH } = paths();
|
||||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||||
const { appName, repository, owner, branch, githubId, enableSubmodules } =
|
const { appName, repository, owner, branch, githubId } = entity;
|
||||||
entity;
|
|
||||||
|
|
||||||
if (!githubId) {
|
if (!githubId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -130,23 +128,25 @@ export const cloneGithubRepository = async ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
||||||
const cloneArgs = [
|
await spawnAsync(
|
||||||
"clone",
|
"git",
|
||||||
"--branch",
|
[
|
||||||
branch!,
|
"clone",
|
||||||
"--depth",
|
"--branch",
|
||||||
"1",
|
branch!,
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--depth",
|
||||||
cloneUrl,
|
"1",
|
||||||
outputPath,
|
"--recurse-submodules",
|
||||||
"--progress",
|
cloneUrl,
|
||||||
];
|
outputPath,
|
||||||
|
"--progress",
|
||||||
await spawnAsync("git", cloneArgs, (data) => {
|
],
|
||||||
if (writeStream.writable) {
|
(data) => {
|
||||||
writeStream.write(data);
|
if (writeStream.writable) {
|
||||||
}
|
writeStream.write(data);
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
||||||
@@ -161,15 +161,7 @@ export const getGithubCloneCommand = async ({
|
|||||||
type = "application",
|
type = "application",
|
||||||
...entity
|
...entity
|
||||||
}: CloneGithubRepository & { serverId: string }) => {
|
}: CloneGithubRepository & { serverId: string }) => {
|
||||||
const {
|
const { appName, repository, owner, branch, githubId, serverId } = entity;
|
||||||
appName,
|
|
||||||
repository,
|
|
||||||
owner,
|
|
||||||
branch,
|
|
||||||
githubId,
|
|
||||||
serverId,
|
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
|
||||||
const isCompose = type === "compose";
|
const isCompose = type === "compose";
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -224,7 +216,7 @@ export const getGithubCloneCommand = async ({
|
|||||||
const cloneCommand = `
|
const cloneCommand = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
if ! git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
if ! git clone --branch ${branch} --depth 1 --recurse-submodules --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fail to clone repository ${repoclone}" >> ${logPath};
|
echo "❌ [ERROR] Fail to clone repository ${repoclone}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
@@ -235,8 +227,7 @@ echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const cloneRawGithubRepository = async (entity: Compose) => {
|
export const cloneRawGithubRepository = async (entity: Compose) => {
|
||||||
const { appName, repository, owner, branch, githubId, enableSubmodules } =
|
const { appName, repository, owner, branch, githubId } = entity;
|
||||||
entity;
|
|
||||||
|
|
||||||
if (!githubId) {
|
if (!githubId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -254,33 +245,24 @@ export const cloneRawGithubRepository = async (entity: Compose) => {
|
|||||||
await recreateDirectory(outputPath);
|
await recreateDirectory(outputPath);
|
||||||
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
|
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
|
||||||
try {
|
try {
|
||||||
const cloneArgs = [
|
await spawnAsync("git", [
|
||||||
"clone",
|
"clone",
|
||||||
"--branch",
|
"--branch",
|
||||||
branch!,
|
branch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
];
|
]);
|
||||||
await spawnAsync("git", cloneArgs);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
|
export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
|
||||||
const {
|
const { appName, repository, owner, branch, githubId, serverId } = compose;
|
||||||
appName,
|
|
||||||
repository,
|
|
||||||
owner,
|
|
||||||
branch,
|
|
||||||
githubId,
|
|
||||||
serverId,
|
|
||||||
enableSubmodules,
|
|
||||||
} = compose;
|
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -306,7 +288,7 @@ export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
|
|||||||
try {
|
try {
|
||||||
const command = `
|
const command = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
|
git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -90,14 +90,8 @@ export const cloneGitlabRepository = async (
|
|||||||
isCompose = false,
|
isCompose = false,
|
||||||
) => {
|
) => {
|
||||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||||
const {
|
const { appName, gitlabBranch, gitlabId, gitlab, gitlabPathNamespace } =
|
||||||
appName,
|
entity;
|
||||||
gitlabBranch,
|
|
||||||
gitlabId,
|
|
||||||
gitlab,
|
|
||||||
gitlabPathNamespace,
|
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
|
||||||
|
|
||||||
if (!gitlabId) {
|
if (!gitlabId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -133,23 +127,25 @@ export const cloneGitlabRepository = async (
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
||||||
const cloneArgs = [
|
await spawnAsync(
|
||||||
"clone",
|
"git",
|
||||||
"--branch",
|
[
|
||||||
gitlabBranch!,
|
"clone",
|
||||||
"--depth",
|
"--branch",
|
||||||
"1",
|
gitlabBranch!,
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--depth",
|
||||||
cloneUrl,
|
"1",
|
||||||
outputPath,
|
"--recurse-submodules",
|
||||||
"--progress",
|
cloneUrl,
|
||||||
];
|
outputPath,
|
||||||
|
"--progress",
|
||||||
await spawnAsync("git", cloneArgs, (data) => {
|
],
|
||||||
if (writeStream.writable) {
|
(data) => {
|
||||||
writeStream.write(data);
|
if (writeStream.writable) {
|
||||||
}
|
writeStream.write(data);
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
||||||
@@ -171,7 +167,6 @@ export const getGitlabCloneCommand = async (
|
|||||||
gitlabId,
|
gitlabId,
|
||||||
serverId,
|
serverId,
|
||||||
gitlab,
|
gitlab,
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
} = entity;
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
@@ -227,7 +222,7 @@ export const getGitlabCloneCommand = async (
|
|||||||
const cloneCommand = `
|
const cloneCommand = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
if ! git clone --branch ${gitlabBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
if ! git clone --branch ${gitlabBranch} --depth 1 --recurse-submodules --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
@@ -269,11 +264,7 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
|
|||||||
const groupName = gitlabProvider.groupName?.toLowerCase();
|
const groupName = gitlabProvider.groupName?.toLowerCase();
|
||||||
|
|
||||||
if (groupName) {
|
if (groupName) {
|
||||||
const isIncluded = groupName
|
return full_path.toLowerCase().includes(groupName) && kind === "group";
|
||||||
.split(",")
|
|
||||||
.some((name) => full_path.toLowerCase().includes(name));
|
|
||||||
|
|
||||||
return isIncluded && kind === "group";
|
|
||||||
}
|
}
|
||||||
return kind === "user";
|
return kind === "user";
|
||||||
});
|
});
|
||||||
@@ -335,13 +326,7 @@ export const getGitlabBranches = async (input: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const cloneRawGitlabRepository = async (entity: Compose) => {
|
export const cloneRawGitlabRepository = async (entity: Compose) => {
|
||||||
const {
|
const { appName, gitlabBranch, gitlabId, gitlabPathNamespace } = entity;
|
||||||
appName,
|
|
||||||
gitlabBranch,
|
|
||||||
gitlabId,
|
|
||||||
gitlabPathNamespace,
|
|
||||||
enableSubmodules,
|
|
||||||
} = entity;
|
|
||||||
|
|
||||||
if (!gitlabId) {
|
if (!gitlabId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -362,32 +347,24 @@ export const cloneRawGitlabRepository = async (entity: Compose) => {
|
|||||||
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
|
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cloneArgs = [
|
await spawnAsync("git", [
|
||||||
"clone",
|
"clone",
|
||||||
"--branch",
|
"--branch",
|
||||||
gitlabBranch!,
|
gitlabBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
...(enableSubmodules ? ["--recurse-submodules"] : []),
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
];
|
]);
|
||||||
await spawnAsync("git", cloneArgs);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
||||||
const {
|
const { appName, gitlabPathNamespace, branch, gitlabId, serverId } = compose;
|
||||||
appName,
|
|
||||||
gitlabPathNamespace,
|
|
||||||
branch,
|
|
||||||
gitlabId,
|
|
||||||
serverId,
|
|
||||||
enableSubmodules,
|
|
||||||
} = compose;
|
|
||||||
|
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -411,7 +388,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
|||||||
try {
|
try {
|
||||||
const command = `
|
const command = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
|
git clone --branch ${branch} --depth 1 --recurse-submodules ${cloneUrl} ${outputPath}
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -454,9 +431,7 @@ export const testGitlabConnection = async (
|
|||||||
const { full_path, kind } = repo.namespace;
|
const { full_path, kind } = repo.namespace;
|
||||||
|
|
||||||
if (groupName) {
|
if (groupName) {
|
||||||
return groupName
|
return full_path.toLowerCase().includes(groupName) && kind === "group";
|
||||||
.split(",")
|
|
||||||
.some((name) => full_path.toLowerCase().includes(name));
|
|
||||||
}
|
}
|
||||||
return kind === "user";
|
return kind === "user";
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,54 +83,44 @@ export const restoreWebServerBackup = async (
|
|||||||
throw new Error("Database file not found after extraction");
|
throw new Error("Database file not found after extraction");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stdout: postgresContainer } = await execAsync(
|
|
||||||
`docker ps --filter "name=dokploy-postgres" --filter "status=running" -q | head -n 1`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!postgresContainer) {
|
|
||||||
throw new Error("Dokploy Postgres container not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const postgresContainerId = postgresContainer.trim();
|
|
||||||
|
|
||||||
// Drop and recreate database
|
// Drop and recreate database
|
||||||
emit("Disconnecting all users from database...");
|
emit("Disconnecting all users from database...");
|
||||||
await execAsync(
|
await execAsync(
|
||||||
`docker exec ${postgresContainerId} psql -U dokploy postgres -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'dokploy' AND pid <> pg_backend_pid();"`,
|
`docker exec $(docker ps --filter "name=dokploy-postgres" -q) psql -U dokploy postgres -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'dokploy' AND pid <> pg_backend_pid();"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit("Dropping existing database...");
|
emit("Dropping existing database...");
|
||||||
await execAsync(
|
await execAsync(
|
||||||
`docker exec ${postgresContainerId} psql -U dokploy postgres -c "DROP DATABASE IF EXISTS dokploy;"`,
|
`docker exec $(docker ps --filter "name=dokploy-postgres" -q) psql -U dokploy postgres -c "DROP DATABASE IF EXISTS dokploy;"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit("Creating fresh database...");
|
emit("Creating fresh database...");
|
||||||
await execAsync(
|
await execAsync(
|
||||||
`docker exec ${postgresContainerId} psql -U dokploy postgres -c "CREATE DATABASE dokploy;"`,
|
`docker exec $(docker ps --filter "name=dokploy-postgres" -q) psql -U dokploy postgres -c "CREATE DATABASE dokploy;"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Copy the backup file into the container
|
// Copy the backup file into the container
|
||||||
emit("Copying backup file into container...");
|
emit("Copying backup file into container...");
|
||||||
await execAsync(
|
await execAsync(
|
||||||
`docker cp ${tempDir}/database.sql ${postgresContainerId}:/tmp/database.sql`,
|
`docker cp ${tempDir}/database.sql $(docker ps --filter "name=dokploy-postgres" -q):/tmp/database.sql`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify file in container
|
// Verify file in container
|
||||||
emit("Verifying file in container...");
|
emit("Verifying file in container...");
|
||||||
await execAsync(
|
await execAsync(
|
||||||
`docker exec ${postgresContainerId} ls -l /tmp/database.sql`,
|
`docker exec $(docker ps --filter "name=dokploy-postgres" -q) ls -l /tmp/database.sql`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restore from the copied file
|
// Restore from the copied file
|
||||||
emit("Running database restore...");
|
emit("Running database restore...");
|
||||||
await execAsync(
|
await execAsync(
|
||||||
`docker exec ${postgresContainerId} pg_restore -v -U dokploy -d dokploy /tmp/database.sql`,
|
`docker exec $(docker ps --filter "name=dokploy-postgres" -q) pg_restore -v -U dokploy -d dokploy /tmp/database.sql`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cleanup the temporary file in the container
|
// Cleanup the temporary file in the container
|
||||||
emit("Cleaning up container temp file...");
|
emit("Cleaning up container temp file...");
|
||||||
await execAsync(
|
await execAsync(
|
||||||
`docker exec ${postgresContainerId} rm /tmp/database.sql`,
|
`docker exec $(docker ps --filter "name=dokploy-postgres" -q) rm /tmp/database.sql`,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit("Restore completed successfully!");
|
emit("Restore completed successfully!");
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ export const updateServerTraefik = (
|
|||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
url: `http://dokploy:${process.env.PORT || 3000}`,
|
url: `http://dokploy:${process.env.PORT || 3000}`,
|
||||||
|
passHostHeader: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
passHostHeader: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
88
pnpm-lock.yaml
generated
88
pnpm-lock.yaml
generated
@@ -17,7 +17,7 @@ importers:
|
|||||||
version: 1.9.4
|
version: 1.9.4
|
||||||
'@commitlint/cli':
|
'@commitlint/cli':
|
||||||
specifier: ^19.3.0
|
specifier: ^19.3.0
|
||||||
version: 19.3.0(@types/node@18.19.42)(typescript@5.8.3)
|
version: 19.3.0(@types/node@18.19.42)(typescript@5.7.2)
|
||||||
'@commitlint/config-conventional':
|
'@commitlint/config-conventional':
|
||||||
specifier: ^19.2.2
|
specifier: ^19.2.2
|
||||||
version: 19.2.2
|
version: 19.2.2
|
||||||
@@ -266,8 +266,8 @@ importers:
|
|||||||
specifier: 5.1.1
|
specifier: 5.1.1
|
||||||
version: 5.1.1(encoding@0.1.13)
|
version: 5.1.1(encoding@0.1.13)
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.2.6
|
specifier: 1.2.4
|
||||||
version: 1.2.6
|
version: 1.2.4(typescript@5.5.3)
|
||||||
bl:
|
bl:
|
||||||
specifier: 6.0.11
|
specifier: 6.0.11
|
||||||
version: 6.0.11
|
version: 6.0.11
|
||||||
@@ -607,8 +607,8 @@ importers:
|
|||||||
specifier: ^0.0.13
|
specifier: ^0.0.13
|
||||||
version: 0.0.13(zod@3.23.8)
|
version: 0.0.13(zod@3.23.8)
|
||||||
'@better-auth/utils':
|
'@better-auth/utils':
|
||||||
specifier: 0.2.4
|
specifier: 0.2.3
|
||||||
version: 0.2.4
|
version: 0.2.3
|
||||||
'@faker-js/faker':
|
'@faker-js/faker':
|
||||||
specifier: ^8.4.1
|
specifier: ^8.4.1
|
||||||
version: 8.4.1
|
version: 8.4.1
|
||||||
@@ -640,8 +640,8 @@ importers:
|
|||||||
specifier: 5.1.1
|
specifier: 5.1.1
|
||||||
version: 5.1.1(encoding@0.1.13)
|
version: 5.1.1(encoding@0.1.13)
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.2.6
|
specifier: 1.2.4
|
||||||
version: 1.2.6
|
version: 1.2.4(typescript@5.5.3)
|
||||||
bl:
|
bl:
|
||||||
specifier: 6.0.11
|
specifier: 6.0.11
|
||||||
version: 6.0.11
|
version: 6.0.11
|
||||||
@@ -924,11 +924,11 @@ packages:
|
|||||||
'@balena/dockerignore@1.0.2':
|
'@balena/dockerignore@1.0.2':
|
||||||
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
|
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
|
||||||
|
|
||||||
'@better-auth/utils@0.2.4':
|
'@better-auth/utils@0.2.3':
|
||||||
resolution: {integrity: sha512-ayiX87Xd5sCHEplAdeMgwkA0FgnXsEZBgDn890XHHwSWNqqRZDYOq3uj2Ei2leTv1I2KbG5HHn60Ah1i2JWZjQ==}
|
resolution: {integrity: sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==}
|
||||||
|
|
||||||
'@better-fetch/fetch@1.1.18':
|
'@better-fetch/fetch@1.1.15':
|
||||||
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
|
resolution: {integrity: sha512-0Bl8YYj1f8qCTNHeSn5+1DWv2hy7rLBrQ8rS8Y9XYloiwZEfc3k4yspIG0llRxafxqhGCwlGRg+F8q1HZRCMXA==}
|
||||||
|
|
||||||
'@biomejs/biome@1.9.4':
|
'@biomejs/biome@1.9.4':
|
||||||
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
|
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
|
||||||
@@ -3836,11 +3836,11 @@ packages:
|
|||||||
before-after-hook@2.2.3:
|
before-after-hook@2.2.3:
|
||||||
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
||||||
|
|
||||||
better-auth@1.2.6:
|
better-auth@1.2.4:
|
||||||
resolution: {integrity: sha512-RVy6nfNCXpohx49zP2ChUO3zN0nvz5UXuETJIhWU+dshBKpFMk4P4hAQauM3xqTJdd9hfeB5y+segmG1oYGTJQ==}
|
resolution: {integrity: sha512-/ZK2jbUjm8JwdeCLFrUWUBmexPyI9PkaLVXWLWtN60sMDHTY8B5G72wcHglo1QMFBaw4G0qFkP5ayl9k6XfDaA==}
|
||||||
|
|
||||||
better-call@1.0.7:
|
better-call@1.0.3:
|
||||||
resolution: {integrity: sha512-p5kEthErx3HsW9dCCvvEx+uuEdncn0ZrlqrOG3TkR1aVYgynpwYbTVU90nY8/UwfMhROzqZWs8vryainSQxrNg==}
|
resolution: {integrity: sha512-DUKImKoDIy5UtCvQbHTg0wuBRse6gu1Yvznn7+1B3I5TeY8sclRPFce0HI+4WF2bcb+9PqmkET8nXZubrHQh9A==}
|
||||||
|
|
||||||
binary-extensions@2.3.0:
|
binary-extensions@2.3.0:
|
||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||||
@@ -7093,8 +7093,8 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
typescript@5.8.3:
|
typescript@5.7.2:
|
||||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -7210,6 +7210,14 @@ packages:
|
|||||||
v8-compile-cache-lib@3.0.1:
|
v8-compile-cache-lib@3.0.1:
|
||||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||||
|
|
||||||
|
valibot@1.0.0-beta.15:
|
||||||
|
resolution: {integrity: sha512-BKy8XosZkDHWmYC+cJG74LBzP++Gfntwi33pP3D3RKztz2XV9jmFWnkOi21GoqARP8wAWARwhV6eTr1JcWzjGw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=5'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
vfile-message@4.0.2:
|
vfile-message@4.0.2:
|
||||||
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
|
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
|
||||||
|
|
||||||
@@ -7559,12 +7567,11 @@ snapshots:
|
|||||||
|
|
||||||
'@balena/dockerignore@1.0.2': {}
|
'@balena/dockerignore@1.0.2': {}
|
||||||
|
|
||||||
'@better-auth/utils@0.2.4':
|
'@better-auth/utils@0.2.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.8.3
|
|
||||||
uncrypto: 0.1.3
|
uncrypto: 0.1.3
|
||||||
|
|
||||||
'@better-fetch/fetch@1.1.18': {}
|
'@better-fetch/fetch@1.1.15': {}
|
||||||
|
|
||||||
'@biomejs/biome@1.9.4':
|
'@biomejs/biome@1.9.4':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -7671,11 +7678,11 @@ snapshots:
|
|||||||
style-mod: 4.1.2
|
style-mod: 4.1.2
|
||||||
w3c-keyname: 2.2.8
|
w3c-keyname: 2.2.8
|
||||||
|
|
||||||
'@commitlint/cli@19.3.0(@types/node@18.19.42)(typescript@5.8.3)':
|
'@commitlint/cli@19.3.0(@types/node@18.19.42)(typescript@5.7.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@commitlint/format': 19.3.0
|
'@commitlint/format': 19.3.0
|
||||||
'@commitlint/lint': 19.2.2
|
'@commitlint/lint': 19.2.2
|
||||||
'@commitlint/load': 19.2.0(@types/node@18.19.42)(typescript@5.8.3)
|
'@commitlint/load': 19.2.0(@types/node@18.19.42)(typescript@5.7.2)
|
||||||
'@commitlint/read': 19.2.1
|
'@commitlint/read': 19.2.1
|
||||||
'@commitlint/types': 19.0.3
|
'@commitlint/types': 19.0.3
|
||||||
execa: 8.0.1
|
execa: 8.0.1
|
||||||
@@ -7722,15 +7729,15 @@ snapshots:
|
|||||||
'@commitlint/rules': 19.0.3
|
'@commitlint/rules': 19.0.3
|
||||||
'@commitlint/types': 19.0.3
|
'@commitlint/types': 19.0.3
|
||||||
|
|
||||||
'@commitlint/load@19.2.0(@types/node@18.19.42)(typescript@5.8.3)':
|
'@commitlint/load@19.2.0(@types/node@18.19.42)(typescript@5.7.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@commitlint/config-validator': 19.0.3
|
'@commitlint/config-validator': 19.0.3
|
||||||
'@commitlint/execute-rule': 19.0.0
|
'@commitlint/execute-rule': 19.0.0
|
||||||
'@commitlint/resolve-extends': 19.1.0
|
'@commitlint/resolve-extends': 19.1.0
|
||||||
'@commitlint/types': 19.0.3
|
'@commitlint/types': 19.0.3
|
||||||
chalk: 5.3.0
|
chalk: 5.3.0
|
||||||
cosmiconfig: 9.0.0(typescript@5.8.3)
|
cosmiconfig: 9.0.0(typescript@5.7.2)
|
||||||
cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3)
|
cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2)
|
||||||
lodash.isplainobject: 4.0.6
|
lodash.isplainobject: 4.0.6
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
lodash.uniq: 4.5.0
|
lodash.uniq: 4.5.0
|
||||||
@@ -10540,24 +10547,27 @@ snapshots:
|
|||||||
|
|
||||||
before-after-hook@2.2.3: {}
|
before-after-hook@2.2.3: {}
|
||||||
|
|
||||||
better-auth@1.2.6:
|
better-auth@1.2.4(typescript@5.5.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/utils': 0.2.4
|
'@better-auth/utils': 0.2.3
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.15
|
||||||
'@noble/ciphers': 0.6.0
|
'@noble/ciphers': 0.6.0
|
||||||
'@noble/hashes': 1.7.1
|
'@noble/hashes': 1.7.1
|
||||||
'@simplewebauthn/browser': 13.1.0
|
'@simplewebauthn/browser': 13.1.0
|
||||||
'@simplewebauthn/server': 13.1.1
|
'@simplewebauthn/server': 13.1.1
|
||||||
better-call: 1.0.7
|
better-call: 1.0.3
|
||||||
defu: 6.1.4
|
defu: 6.1.4
|
||||||
jose: 5.9.6
|
jose: 5.9.6
|
||||||
kysely: 0.27.6
|
kysely: 0.27.6
|
||||||
nanostores: 0.11.3
|
nanostores: 0.11.3
|
||||||
|
valibot: 1.0.0-beta.15(typescript@5.5.3)
|
||||||
zod: 3.24.1
|
zod: 3.24.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- typescript
|
||||||
|
|
||||||
better-call@1.0.7:
|
better-call@1.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.15
|
||||||
rou3: 0.5.1
|
rou3: 0.5.1
|
||||||
set-cookie-parser: 2.7.1
|
set-cookie-parser: 2.7.1
|
||||||
uncrypto: 0.1.3
|
uncrypto: 0.1.3
|
||||||
@@ -10932,21 +10942,21 @@ snapshots:
|
|||||||
|
|
||||||
core-js@3.39.0: {}
|
core-js@3.39.0: {}
|
||||||
|
|
||||||
cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3):
|
cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.19.42
|
'@types/node': 18.19.42
|
||||||
cosmiconfig: 9.0.0(typescript@5.8.3)
|
cosmiconfig: 9.0.0(typescript@5.7.2)
|
||||||
jiti: 1.21.6
|
jiti: 1.21.6
|
||||||
typescript: 5.8.3
|
typescript: 5.7.2
|
||||||
|
|
||||||
cosmiconfig@9.0.0(typescript@5.8.3):
|
cosmiconfig@9.0.0(typescript@5.7.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
env-paths: 2.2.1
|
env-paths: 2.2.1
|
||||||
import-fresh: 3.3.0
|
import-fresh: 3.3.0
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
parse-json: 5.2.0
|
parse-json: 5.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
typescript: 5.7.2
|
||||||
|
|
||||||
cpu-features@0.0.10:
|
cpu-features@0.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14161,7 +14171,7 @@ snapshots:
|
|||||||
|
|
||||||
typescript@5.5.3: {}
|
typescript@5.5.3: {}
|
||||||
|
|
||||||
typescript@5.8.3: {}
|
typescript@5.7.2: {}
|
||||||
|
|
||||||
ufo@1.5.4: {}
|
ufo@1.5.4: {}
|
||||||
|
|
||||||
@@ -14282,6 +14292,10 @@ snapshots:
|
|||||||
v8-compile-cache-lib@3.0.1:
|
v8-compile-cache-lib@3.0.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
valibot@1.0.0-beta.15(typescript@5.5.3):
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.5.3
|
||||||
|
|
||||||
vfile-message@4.0.2:
|
vfile-message@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|||||||
Reference in New Issue
Block a user