mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-27 16:55:25 +02:00
Merge branch 'canary' into feat/libsql
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllProperties } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile1 = `
|
||||
version: "3.8"
|
||||
@@ -61,7 +61,7 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFile1 = load(`
|
||||
const expectedComposeFile1 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -120,7 +120,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const composeData = parse(composeFile1) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
@@ -185,7 +185,7 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFile2 = load(`
|
||||
const expectedComposeFile2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -243,7 +243,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
@@ -308,7 +308,7 @@ secrets:
|
||||
file: ./service_secret.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFile3 = load(`
|
||||
const expectedComposeFile3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -366,7 +366,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
@@ -420,7 +420,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedComposeFile = load(`
|
||||
const expectedComposeFile = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -467,7 +467,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in Plausible compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToConfigsRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -23,7 +23,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -59,7 +59,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to multiple configs in root property", () => {
|
||||
const composeData = load(composeFileMultipleConfigs) as ComposeSpecification;
|
||||
const composeData = parse(composeFileMultipleConfigs) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -92,7 +92,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs with different properties in root property", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileDifferentProperties,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -137,7 +137,7 @@ configs:
|
||||
`;
|
||||
|
||||
// Expected compose file con el prefijo `testhash`
|
||||
const expectedComposeFileConfigRoot = load(`
|
||||
const expectedComposeFileConfigRoot = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -162,7 +162,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFileConfigRoot) as ComposeSpecification;
|
||||
const composeData = parse(composeFileConfigRoot) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToConfigsInServices,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -22,7 +22,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -54,7 +54,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in services with single config", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileSingleServiceConfig,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -108,7 +108,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in services with multiple configs", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileMultipleServicesConfigs,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
`;
|
||||
|
||||
// Expected compose file con el prefijo `testhash`
|
||||
const expectedComposeFileConfigServices = load(`
|
||||
const expectedComposeFileConfigServices = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -182,7 +182,7 @@ services:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFileConfigServices) as ComposeSpecification;
|
||||
const composeData = parse(composeFileConfigServices) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllConfigs, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -43,7 +43,7 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedConfigs = load(`
|
||||
const expectedComposeFileCombinedConfigs = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -77,7 +77,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all configs in root and services", () => {
|
||||
const composeData = load(composeFileCombinedConfigs) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombinedConfigs) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -122,7 +122,7 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`;
|
||||
|
||||
const expectedComposeFileWithEnvAndExternal = load(`
|
||||
const expectedComposeFileWithEnvAndExternal = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -159,7 +159,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs with environment and external", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileWithEnvAndExternal,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -200,7 +200,7 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`;
|
||||
|
||||
const expectedComposeFileWithTemplateDriverAndLabels = load(`
|
||||
const expectedComposeFileWithTemplateDriverAndLabels = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -231,7 +231,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs with template driver and labels", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileWithTemplateDriverAndLabels,
|
||||
) as ComposeSpecification;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToNetworksRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -35,7 +35,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
});
|
||||
|
||||
test("Add suffix to networks root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -79,7 +79,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to advanced networks root property (2 TRY)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -120,7 +120,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with external properties", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -160,7 +160,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with IPAM configurations", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -201,7 +201,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with custom options", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
const composeData = parse(composeFile5) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -264,7 +264,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with static suffix", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
const composeData = parse(composeFile6) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -273,7 +273,7 @@ test("Add suffix to networks with static suffix", () => {
|
||||
}
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
const expectedComposeData = load(
|
||||
const expectedComposeData = parse(
|
||||
expectedComposeFile6,
|
||||
) as ComposeSpecification;
|
||||
expect(networks).toStrictEqual(expectedComposeData.networks);
|
||||
@@ -293,7 +293,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToServiceNetworks,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -67,7 +67,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services with aliases", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -107,7 +107,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services (Object with simple networks)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -153,7 +153,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -196,7 +196,7 @@ services:
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -245,7 +245,7 @@ services:
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services multiples cases", () => {
|
||||
const composeData = load(composeFile8) as ComposeSpecification;
|
||||
const composeData = parse(composeFile8) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
addSuffixToServiceNetworks,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileCombined = `
|
||||
version: "3.8"
|
||||
@@ -39,7 +39,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services and root (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -89,7 +89,7 @@ test("Add suffix to networks in services and root (combined case)", () => {
|
||||
expect(redisNetworks).not.toHaveProperty("backend");
|
||||
});
|
||||
|
||||
const expectedComposeFile = load(`
|
||||
const expectedComposeFile = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -120,7 +120,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to networks in compose file", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
if (!composeData?.networks) {
|
||||
@@ -156,7 +156,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile2 = load(`
|
||||
const expectedComposeFile2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -182,7 +182,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to networks in compose file with external and internal networks", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
@@ -218,7 +218,7 @@ networks:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
`;
|
||||
|
||||
const expectedComposeFile3 = load(`
|
||||
const expectedComposeFile3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -247,7 +247,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to networks in compose file with multiple services and complex network configurations", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
@@ -289,7 +289,7 @@ networks:
|
||||
|
||||
`;
|
||||
|
||||
const expectedComposeFile4 = load(`
|
||||
const expectedComposeFile4 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -326,7 +326,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Expect don't add suffix to dokploy-network in compose file with multiple services and complex network configurations", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToSecretsRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -23,7 +23,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in root property", () => {
|
||||
const composeData = load(composeFileSecretsRoot) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsRoot) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
@@ -52,7 +52,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in root property (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsRoot1) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsRoot1) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
@@ -84,7 +84,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in root property (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsRoot2) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsRoot2) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToSecretsInServices,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileSecretsServices = `
|
||||
version: "3.8"
|
||||
@@ -21,7 +21,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in services", () => {
|
||||
const composeData = load(composeFileSecretsServices) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsServices) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
@@ -54,7 +54,9 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in services (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsServices1) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileSecretsServices1,
|
||||
) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
@@ -93,7 +95,9 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in services (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsServices2) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileSecretsServices2,
|
||||
) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllSecrets } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileCombinedSecrets = `
|
||||
version: "3.8"
|
||||
@@ -25,7 +25,7 @@ secrets:
|
||||
file: ./app_secret.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedSecrets = load(`
|
||||
const expectedComposeFileCombinedSecrets = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -48,7 +48,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all secrets", () => {
|
||||
const composeData = load(composeFileCombinedSecrets) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombinedSecrets) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
@@ -77,7 +77,7 @@ secrets:
|
||||
file: ./cache_secret.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedSecrets3 = load(`
|
||||
const expectedComposeFileCombinedSecrets3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -99,7 +99,9 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all secrets (3rd Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets3) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileCombinedSecrets3,
|
||||
) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
@@ -128,7 +130,7 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedSecrets4 = load(`
|
||||
const expectedComposeFileCombinedSecrets4 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -150,7 +152,9 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all secrets (4th Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets4) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileCombinedSecrets4,
|
||||
) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -27,7 +27,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
});
|
||||
|
||||
test("Add suffix to service names with container_name in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -32,7 +32,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with depends_on (array) in compose file", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -102,7 +102,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with depends_on (object) in compose file", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
const composeData = parse(composeFile5) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -30,7 +30,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with extends (string) in compose file", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
const composeData = parse(composeFile6) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -90,7 +90,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with extends (object) in compose file", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -31,7 +31,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with links in compose file", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -26,7 +26,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToAllServiceNames,
|
||||
addSuffixToServiceNames,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileCombinedAllCases = `
|
||||
version: "3.8"
|
||||
@@ -38,7 +38,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile = load(`
|
||||
const expectedComposeFile = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -71,7 +71,9 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to all service names in compose file", () => {
|
||||
const composeData = load(composeFileCombinedAllCases) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileCombinedAllCases,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -131,7 +133,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile1 = load(`
|
||||
const expectedComposeFile1 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -176,7 +178,7 @@ networks:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all service names in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const composeData = parse(composeFile1) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
@@ -227,7 +229,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile2 = load(`
|
||||
const expectedComposeFile2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -271,7 +273,7 @@ networks:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all service names in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
@@ -322,7 +324,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile3 = load(`
|
||||
const expectedComposeFile3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -366,7 +368,7 @@ networks:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all service names in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -35,7 +35,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with volumes_from in compose file", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
addSuffixToVolumesRoot,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
services:
|
||||
@@ -70,7 +70,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedDockerCompose = load(`
|
||||
const expectedDockerCompose = parse(`
|
||||
services:
|
||||
mail:
|
||||
image: bytemark/smtp
|
||||
@@ -143,7 +143,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
// Docker compose needs unique names for services, volumes, networks and containers
|
||||
// So base on a input which is a dockercompose file, it should replace the name with a hash and return a new dockercompose file
|
||||
test("Add suffix to volumes root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -165,7 +165,7 @@ test("Add suffix to volumes root property", () => {
|
||||
});
|
||||
|
||||
test("Expect to change the suffix in all the possible places", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -195,7 +195,7 @@ volumes:
|
||||
mongo-data:
|
||||
`;
|
||||
|
||||
const expectedDockerCompose2 = load(`
|
||||
const expectedDockerCompose2 = parse(`
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
@@ -218,7 +218,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the suffix in all the possible places (2 Try)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -248,7 +248,7 @@ volumes:
|
||||
mongo-data:
|
||||
`;
|
||||
|
||||
const expectedDockerCompose3 = load(`
|
||||
const expectedDockerCompose3 = parse(`
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
@@ -271,7 +271,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the suffix in all the possible places (3 Try)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -645,7 +645,7 @@ volumes:
|
||||
db-config:
|
||||
`;
|
||||
|
||||
const expectedDockerComposeComplex = load(`
|
||||
const expectedDockerComposeComplex = parse(`
|
||||
version: "3.8"
|
||||
services:
|
||||
studio:
|
||||
@@ -1012,7 +1012,7 @@ volumes:
|
||||
`);
|
||||
|
||||
test("Expect to change the suffix in all the possible places (4 Try)", () => {
|
||||
const composeData = load(composeFileComplex) as ComposeSpecification;
|
||||
const composeData = parse(composeFileComplex) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -1065,7 +1065,7 @@ volumes:
|
||||
db-data:
|
||||
`;
|
||||
|
||||
const expectedDockerComposeExample1 = load(`
|
||||
const expectedDockerComposeExample1 = parse(`
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
@@ -1111,7 +1111,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the suffix in all the possible places (5 Try)", () => {
|
||||
const composeData = load(composeFileExample1) as ComposeSpecification;
|
||||
const composeData = parse(composeFileExample1) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -1143,7 +1143,7 @@ volumes:
|
||||
backrest-cache:
|
||||
`;
|
||||
|
||||
const expectedDockerComposeBackrest = load(`
|
||||
const expectedDockerComposeBackrest = parse(`
|
||||
services:
|
||||
backrest:
|
||||
image: garethgeorge/backrest:v1.7.3
|
||||
@@ -1168,7 +1168,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Should handle volume paths with subdirectories correctly", () => {
|
||||
const composeData = load(composeFileBackrest) as ComposeSpecification;
|
||||
const composeData = parse(composeFileBackrest) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToVolumesRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -29,7 +29,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
});
|
||||
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -67,7 +67,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes in root property (Case 2)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -101,7 +101,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes in root property (Case 3)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -148,7 +148,7 @@ volumes:
|
||||
`;
|
||||
|
||||
// Expected compose file con el prefijo `testhash`
|
||||
const expectedComposeFile4 = load(`
|
||||
const expectedComposeFile4 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -179,7 +179,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToVolumesInServices,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes declared directly in services", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const composeData = parse(composeFile1) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -59,7 +59,7 @@ volumes:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes declared directly in services (Case 2)", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllVolumes } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileTypeVolume = `
|
||||
version: "3.8"
|
||||
@@ -23,7 +23,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume = load(`
|
||||
const expectedComposeFileTypeVolume = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -44,7 +44,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to volumes with type: volume in services", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -73,7 +73,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume1 = load(`
|
||||
const expectedComposeFileTypeVolume1 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -93,7 +93,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to mixed volumes in services", () => {
|
||||
const composeData = load(composeFileTypeVolume1) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume1) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -128,7 +128,7 @@ volumes:
|
||||
device: /path/to/app/logs
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume2 = load(`
|
||||
const expectedComposeFileTypeVolume2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -154,7 +154,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to complex volume configurations in services", () => {
|
||||
const composeData = load(composeFileTypeVolume2) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume2) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -218,7 +218,7 @@ volumes:
|
||||
device: /path/to/shared/logs
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume3 = load(`
|
||||
const expectedComposeFileTypeVolume3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -273,7 +273,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to complex nested volumes configuration in services", () => {
|
||||
const composeData = load(composeFileTypeVolume3) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume3) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import jsyaml from "js-yaml";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { parse, stringify, YAMLParseError } from "yaml";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
@@ -38,11 +38,11 @@ interface Props {
|
||||
|
||||
export const validateAndFormatYAML = (yamlText: string) => {
|
||||
try {
|
||||
const obj = jsyaml.load(yamlText);
|
||||
const formattedYaml = jsyaml.dump(obj, { indent: 4 });
|
||||
const obj = parse(yamlText);
|
||||
const formattedYaml = stringify(obj, { indent: 4 });
|
||||
return { valid: true, formattedYaml, error: null };
|
||||
} catch (error) {
|
||||
if (error instanceof jsyaml.YAMLException) {
|
||||
if (error instanceof YAMLParseError) {
|
||||
return {
|
||||
valid: false,
|
||||
formattedYaml: yamlText,
|
||||
@@ -89,7 +89,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
|
||||
if (!valid) {
|
||||
form.setError("traefikConfig", {
|
||||
type: "manual",
|
||||
message: error || "Invalid YAML",
|
||||
message: (error as string) || "Invalid YAML",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
||||
<Input
|
||||
placeholder="Search Template"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
className="w-full sm:w-[200px]"
|
||||
className="w-full"
|
||||
value={query}
|
||||
/>
|
||||
<Input
|
||||
@@ -248,7 +248,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
||||
onClick={() =>
|
||||
setViewMode(viewMode === "detailed" ? "icon" : "detailed")
|
||||
}
|
||||
className="h-9 w-9"
|
||||
className="h-9 w-9 flex-shrink-0"
|
||||
>
|
||||
{viewMode === "detailed" ? (
|
||||
<LayoutGrid className="size-4" />
|
||||
|
||||
@@ -88,7 +88,7 @@ export const StepThree = ({ templateInfo }: StepProps) => {
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">Configuration Files</h3>
|
||||
<ul className="list-disc pl-5">
|
||||
{templateInfo?.details?.configFiles.map((file, index) => (
|
||||
{templateInfo?.details?.configFiles?.map((file, index) => (
|
||||
<li key={index}>
|
||||
<strong className="text-sm font-semibold">
|
||||
{file.filePath}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Bot, Eye, EyeOff, PlusCircle, Trash2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Bot, PlusCircle, Trash2 } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { toast } from "sonner";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
@@ -27,7 +27,6 @@ export interface StepProps {
|
||||
export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
const suggestions = templateInfo.suggestions || [];
|
||||
const selectedVariant = templateInfo.details;
|
||||
const [showValues, setShowValues] = useState<Record<string, boolean>>({});
|
||||
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.ai.suggest.useMutation();
|
||||
@@ -44,7 +43,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
.then((data) => {
|
||||
setTemplateInfo({
|
||||
...templateInfo,
|
||||
suggestions: data,
|
||||
suggestions: data || [],
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -54,10 +53,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
});
|
||||
}, [templateInfo.userInput]);
|
||||
|
||||
const toggleShowValue = (name: string) => {
|
||||
setShowValues((prev) => ({ ...prev, [name]: !prev[name] }));
|
||||
};
|
||||
|
||||
const handleEnvVariableChange = (
|
||||
index: number,
|
||||
field: "name" | "value",
|
||||
@@ -308,11 +303,9 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
placeholder="Variable Name"
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex-1 relative">
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={
|
||||
showValues[env.name] ? "text" : "password"
|
||||
}
|
||||
type={"password"}
|
||||
value={env.value}
|
||||
onChange={(e) =>
|
||||
handleEnvVariableChange(
|
||||
@@ -323,19 +316,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
}
|
||||
placeholder="Variable Value"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2"
|
||||
onClick={() => toggleShowValue(env.name)}
|
||||
>
|
||||
{showValues[env.name] ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -437,13 +417,14 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
<AccordionContent>
|
||||
<ScrollArea className="w-full rounded-md border">
|
||||
<div className="p-4 space-y-4">
|
||||
{selectedVariant?.configFiles?.length > 0 ? (
|
||||
{selectedVariant?.configFiles?.length &&
|
||||
selectedVariant?.configFiles?.length > 0 ? (
|
||||
<>
|
||||
<div className="text-sm text-muted-foreground mb-4">
|
||||
This template requires the following
|
||||
configuration files to be mounted:
|
||||
</div>
|
||||
{selectedVariant.configFiles.map(
|
||||
{selectedVariant?.configFiles?.map(
|
||||
(config, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
||||
@@ -47,7 +47,7 @@ interface Details {
|
||||
envVariables: EnvVariable[];
|
||||
shortDescription: string;
|
||||
domains: Domain[];
|
||||
configFiles: Mount[];
|
||||
configFiles?: Mount[];
|
||||
}
|
||||
|
||||
interface Mount {
|
||||
|
||||
@@ -81,6 +81,29 @@ export const DuplicateProject = ({
|
||||
api.project.duplicate.useMutation({
|
||||
onSuccess: async (newProject) => {
|
||||
await utils.project.all.invalidate();
|
||||
|
||||
// If duplicating to same project+environment, invalidate the environment query
|
||||
// to refresh the services list
|
||||
if (duplicateType === "existing-environment") {
|
||||
await utils.environment.one.invalidate({
|
||||
environmentId: selectedTargetEnvironment,
|
||||
});
|
||||
await utils.environment.byProjectId.invalidate({
|
||||
projectId: selectedTargetProject,
|
||||
});
|
||||
|
||||
// If duplicating to the same environment we're currently viewing,
|
||||
// also invalidate the current environment to refresh the services list
|
||||
if (selectedTargetEnvironment === environmentId) {
|
||||
await utils.environment.one.invalidate({ environmentId });
|
||||
// Also invalidate the project query to refresh the project data
|
||||
const projectId = router.query.projectId as string;
|
||||
if (projectId) {
|
||||
await utils.project.one.invalidate({ projectId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toast.success(
|
||||
duplicateType === "new-project"
|
||||
? "Project duplicated successfully"
|
||||
|
||||
@@ -292,45 +292,48 @@ export const ShowProjects = () => {
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
{/*
|
||||
{project.compose.length > 0 && (
|
||||
{project.environments.some(
|
||||
(env) => env.compose.length > 0,
|
||||
) && (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>
|
||||
Compose
|
||||
</DropdownMenuLabel>
|
||||
{project.compose.map((comp) => (
|
||||
<div key={comp.composeId}>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
||||
{comp.name}
|
||||
<StatusTooltip
|
||||
status={comp.composeStatus}
|
||||
/>
|
||||
</DropdownMenuLabel>
|
||||
{project.environments.map((env) =>
|
||||
env.compose.map((comp) => (
|
||||
<div key={comp.composeId}>
|
||||
<DropdownMenuSeparator />
|
||||
{comp.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
||||
{comp.name}
|
||||
<StatusTooltip
|
||||
status={comp.composeStatus}
|
||||
/>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{comp.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<span className="truncate">
|
||||
{domain.host}
|
||||
</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
))}
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
>
|
||||
<span className="truncate">
|
||||
{domain.host}
|
||||
</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
)),
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
)} */}
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
|
||||
@@ -30,6 +30,9 @@ const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
appName: z.string().min(1, {
|
||||
message: "App Name is required",
|
||||
}),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
@@ -55,6 +58,7 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
appName: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
@@ -62,6 +66,7 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
name: github?.gitProvider.name || "",
|
||||
appName: github?.githubAppName || "",
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
|
||||
@@ -70,6 +75,7 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
githubId,
|
||||
name: data.name || "",
|
||||
gitProviderId: github?.gitProviderId || "",
|
||||
githubAppName: data.appName || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
@@ -124,6 +130,22 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>App Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="pp Name eg(my-personal)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full justify-between gap-4 mt-4">
|
||||
<Button
|
||||
|
||||
@@ -33,7 +33,10 @@ import { Disable2FA } from "./disable-2fa";
|
||||
import { Enable2FA } from "./enable-2fa";
|
||||
|
||||
const profileSchema = z.object({
|
||||
email: z.string(),
|
||||
email: z
|
||||
.string()
|
||||
.email("Please enter a valid email address")
|
||||
.min(1, "Email is required"),
|
||||
password: z.string().nullable(),
|
||||
currentPassword: z.string().nullable(),
|
||||
image: z.string().optional(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.25.1",
|
||||
"version": "v0.25.2",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -112,7 +112,7 @@
|
||||
"i18next": "^23.16.8",
|
||||
"input-otp": "^1.4.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-yaml": "4.1.0",
|
||||
"yaml": "2.8.1",
|
||||
"lodash": "4.17.21",
|
||||
"lucide-react": "^0.469.0",
|
||||
"micromatch": "4.0.8",
|
||||
@@ -160,7 +160,6 @@
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/lodash": "4.17.4",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "^18.19.104",
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { getPublicIpWithFallback } from "@/server/wss/terminal";
|
||||
import { getLocalServerIp } from "@/server/wss/terminal";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
export const clusterRouter = createTRPCRouter({
|
||||
getNodes: protectedProcedure
|
||||
@@ -61,7 +61,7 @@ export const clusterRouter = createTRPCRouter({
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
|
||||
let ip = await getPublicIpWithFallback();
|
||||
let ip = await getLocalServerIp();
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
ip = server?.ipAddress;
|
||||
@@ -85,7 +85,7 @@ export const clusterRouter = createTRPCRouter({
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
|
||||
let ip = await getPublicIpWithFallback();
|
||||
let ip = await getLocalServerIp();
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
ip = server?.ipAddress;
|
||||
|
||||
@@ -39,10 +39,10 @@ import {
|
||||
import { processTemplate } from "@dokploy/server/templates/processors";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { dump } from "js-yaml";
|
||||
import _ from "lodash";
|
||||
import { nanoid } from "nanoid";
|
||||
import { parse } from "toml";
|
||||
import { stringify } from "yaml";
|
||||
import { z } from "zod";
|
||||
import { slugify } from "@/lib/slug";
|
||||
import { db } from "@/server/db";
|
||||
@@ -364,7 +364,7 @@ export const composeRouter = createTRPCRouter({
|
||||
}
|
||||
const domains = await findDomainsByComposeId(input.composeId);
|
||||
const composeFile = await addDomainToCompose(compose, domains);
|
||||
return dump(composeFile, {
|
||||
return stringify(composeFile, {
|
||||
lineWidth: 1000,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
getGithubBranches,
|
||||
getGithubRepositories,
|
||||
haveGithubRequirements,
|
||||
updateGithub,
|
||||
updateGitProvider,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -134,5 +135,9 @@ export const githubRouter = createTRPCRouter({
|
||||
name: input.name,
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
});
|
||||
|
||||
await updateGithub(input.githubId, {
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -46,8 +46,8 @@ import {
|
||||
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { scheduledJobs, scheduleJob } from "node-schedule";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
@@ -658,7 +658,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
const config = readMainConfig();
|
||||
|
||||
if (!config) return false;
|
||||
const parsedConfig = load(config) as {
|
||||
const parsedConfig = parse(config) as {
|
||||
accessLog?: {
|
||||
filePath: string;
|
||||
};
|
||||
@@ -679,7 +679,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
const mainConfig = readMainConfig();
|
||||
if (!mainConfig) return false;
|
||||
|
||||
const currentConfig = load(mainConfig) as {
|
||||
const currentConfig = parse(mainConfig) as {
|
||||
accessLog?: {
|
||||
filePath: string;
|
||||
};
|
||||
@@ -702,7 +702,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
currentConfig.accessLog = undefined;
|
||||
}
|
||||
|
||||
writeMainConfig(dump(currentConfig));
|
||||
writeMainConfig(stringify(currentConfig));
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
@@ -192,7 +192,16 @@ export const userRouter = createTRPCRouter({
|
||||
})
|
||||
.where(eq(account.userId, ctx.user.id));
|
||||
}
|
||||
return await updateUser(ctx.user.id, input);
|
||||
|
||||
try {
|
||||
return await updateUser(ctx.user.id, input);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
error instanceof Error ? error.message : "Failed to update user",
|
||||
});
|
||||
}
|
||||
}),
|
||||
getUserByToken: publicProcedure
|
||||
.input(apiFindOneToken)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type http from "node:http";
|
||||
import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import {
|
||||
execAsync,
|
||||
findServerById,
|
||||
IS_CLOUD,
|
||||
validateRequest,
|
||||
} from "@dokploy/server";
|
||||
import { publicIpv4, publicIpv6 } from "public-ip";
|
||||
import { Client, type ConnectConfig } from "ssh2";
|
||||
import { WebSocketServer } from "ws";
|
||||
@@ -44,6 +49,21 @@ export const getPublicIpWithFallback = async () => {
|
||||
return ip;
|
||||
};
|
||||
|
||||
export const getLocalServerIp = async () => {
|
||||
try {
|
||||
const command = `ip addr show | grep -E "inet (192\.168\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.)" | head -n1 | awk '{print $2}' | cut -d/ -f1`;
|
||||
const { stdout } = await execAsync(command);
|
||||
const ip = stdout.trim();
|
||||
return (
|
||||
ip ||
|
||||
"We were unable to obtain the local server IP, please use your private IP address"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error to obtain local server IP", error);
|
||||
return "We were unable to obtain the local server IP, please use your private IP address";
|
||||
}
|
||||
};
|
||||
|
||||
export const setupTerminalWebSocketServer = (
|
||||
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
||||
) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { exit } from "node:process";
|
||||
import { execAsync } from "@dokploy/server";
|
||||
import { setupDirectories } from "@dokploy/server/setup/config-paths";
|
||||
import { initializePostgres } from "@dokploy/server/setup/postgres-setup";
|
||||
@@ -25,6 +26,8 @@ import {
|
||||
await initializeStandaloneTraefik();
|
||||
await initializeRedis();
|
||||
await initializePostgres();
|
||||
console.log("Dokploy setup completed");
|
||||
exit(0);
|
||||
} catch (e) {
|
||||
console.error("Error in dokploy setup", e);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "./queue.js";
|
||||
import { jobQueueSchema } from "./schema.js";
|
||||
import { initializeJobs } from "./utils.js";
|
||||
import { firstWorker, secondWorker } from "./workers.js";
|
||||
import { firstWorker, secondWorker, thirdWorker } from "./workers.js";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
@@ -91,6 +91,7 @@ export const gracefulShutdown = async (signal: string) => {
|
||||
logger.warn(`Received ${signal}, closing server...`);
|
||||
await firstWorker.close();
|
||||
await secondWorker.close();
|
||||
await thirdWorker.close();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,22 +7,34 @@ import { runJobs } from "./utils.js";
|
||||
export const firstWorker = new Worker(
|
||||
"backupQueue",
|
||||
async (job: Job<QueueJob>) => {
|
||||
logger.info({ data: job.data }, "Running job");
|
||||
logger.info({ data: job.data }, "Running job first worker");
|
||||
await runJobs(job.data);
|
||||
},
|
||||
{
|
||||
concurrency: 50,
|
||||
concurrency: 100,
|
||||
connection,
|
||||
},
|
||||
);
|
||||
export const secondWorker = new Worker(
|
||||
"backupQueue",
|
||||
async (job: Job<QueueJob>) => {
|
||||
logger.info({ data: job.data }, "Running job");
|
||||
logger.info({ data: job.data }, "Running job second worker");
|
||||
await runJobs(job.data);
|
||||
},
|
||||
{
|
||||
concurrency: 50,
|
||||
concurrency: 100,
|
||||
connection,
|
||||
},
|
||||
);
|
||||
|
||||
export const thirdWorker = new Worker(
|
||||
"backupQueue",
|
||||
async (job: Job<QueueJob>) => {
|
||||
logger.info({ data: job.data }, "Running job third worker");
|
||||
await runJobs(job.data);
|
||||
},
|
||||
{
|
||||
concurrency: 100,
|
||||
connection,
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user