mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-03 04:55:23 +02:00
Merge branch 'canary' into feat/requests
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { addPrefixToAllProperties } from "@/server/utils/docker/compose";
|
||||
import { addSuffixToAllProperties } from "@/server/utils/docker/compose";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -119,11 +119,11 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in compose file 1", () => {
|
||||
test("Add suffix to all properties in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile1);
|
||||
});
|
||||
@@ -242,11 +242,11 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in compose file 2", () => {
|
||||
test("Add suffix to all properties in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile2);
|
||||
});
|
||||
@@ -365,11 +365,11 @@ secrets:
|
||||
file: ./service_secret.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in compose file 3", () => {
|
||||
test("Add suffix to all properties in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile3);
|
||||
});
|
||||
@@ -466,11 +466,11 @@ volumes:
|
||||
driver: local
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in Plausible compose file", () => {
|
||||
test("Add suffix to all properties in Plausible compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToConfigsRoot } from "@/server/utils/docker/compose/configs";
|
||||
import { addSuffixToConfigsRoot } from "@/server/utils/docker/compose/configs";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -23,19 +23,19 @@ configs:
|
||||
file: ./web-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in root property", () => {
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
|
||||
expect(configs).toBeDefined();
|
||||
for (const configKey of Object.keys(configs)) {
|
||||
expect(configKey).toContain(`-${prefix}`);
|
||||
expect(configKey).toContain(`-${suffix}`);
|
||||
expect(configs[configKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -59,23 +59,23 @@ configs:
|
||||
file: ./another-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to multiple configs in root property", () => {
|
||||
test("Add suffix to multiple configs in root property", () => {
|
||||
const composeData = load(composeFileMultipleConfigs) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
|
||||
expect(configs).toBeDefined();
|
||||
for (const configKey of Object.keys(configs)) {
|
||||
expect(configKey).toContain(`-${prefix}`);
|
||||
expect(configKey).toContain(`-${suffix}`);
|
||||
expect(configs[configKey]).toBeDefined();
|
||||
}
|
||||
expect(configs).toHaveProperty(`web-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`another-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`web-config-${suffix}`);
|
||||
expect(configs).toHaveProperty(`another-config-${suffix}`);
|
||||
});
|
||||
|
||||
const composeFileDifferentProperties = `
|
||||
@@ -92,25 +92,25 @@ configs:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to configs with different properties in root property", () => {
|
||||
test("Add suffix to configs with different properties in root property", () => {
|
||||
const composeData = load(
|
||||
composeFileDifferentProperties,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
|
||||
expect(configs).toBeDefined();
|
||||
for (const configKey of Object.keys(configs)) {
|
||||
expect(configKey).toContain(`-${prefix}`);
|
||||
expect(configKey).toContain(`-${suffix}`);
|
||||
expect(configs[configKey]).toBeDefined();
|
||||
}
|
||||
expect(configs).toHaveProperty(`web-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`special-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`web-config-${suffix}`);
|
||||
expect(configs).toHaveProperty(`special-config-${suffix}`);
|
||||
});
|
||||
|
||||
const composeFileConfigRoot = `
|
||||
@@ -162,15 +162,15 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs in root property", () => {
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFileConfigRoot) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
const updatedComposeData = { ...composeData, configs };
|
||||
|
||||
// Verificar que el resultado coincide con el archivo esperado
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToConfigsInServices } from "@/server/utils/docker/compose/configs";
|
||||
import { addSuffixToConfigsInServices } from "@/server/utils/docker/compose/configs";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -19,19 +19,19 @@ configs:
|
||||
file: ./web-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in services", () => {
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToConfigsInServices(composeData.services, prefix);
|
||||
const services = addSuffixToConfigsInServices(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.web?.configs).toContainEqual({
|
||||
source: `web-config-${prefix}`,
|
||||
source: `web-config-${suffix}`,
|
||||
target: "/etc/nginx/nginx.conf",
|
||||
});
|
||||
});
|
||||
@@ -51,17 +51,17 @@ configs:
|
||||
file: ./web-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in services with single config", () => {
|
||||
test("Add suffix to configs in services with single config", () => {
|
||||
const composeData = load(
|
||||
composeFileSingleServiceConfig,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToConfigsInServices(composeData.services, prefix);
|
||||
const services = addSuffixToConfigsInServices(composeData.services, suffix);
|
||||
|
||||
expect(services).toBeDefined();
|
||||
for (const serviceKey of Object.keys(services)) {
|
||||
@@ -69,7 +69,7 @@ test("Add prefix to configs in services with single config", () => {
|
||||
if (serviceConfigs) {
|
||||
for (const config of serviceConfigs) {
|
||||
if (typeof config === "object") {
|
||||
expect(config.source).toContain(`-${prefix}`);
|
||||
expect(config.source).toContain(`-${suffix}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,17 +105,17 @@ configs:
|
||||
file: ./common-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in services with multiple configs", () => {
|
||||
test("Add suffix to configs in services with multiple configs", () => {
|
||||
const composeData = load(
|
||||
composeFileMultipleServicesConfigs,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToConfigsInServices(composeData.services, prefix);
|
||||
const services = addSuffixToConfigsInServices(composeData.services, suffix);
|
||||
|
||||
expect(services).toBeDefined();
|
||||
for (const serviceKey of Object.keys(services)) {
|
||||
@@ -123,7 +123,7 @@ test("Add prefix to configs in services with multiple configs", () => {
|
||||
if (serviceConfigs) {
|
||||
for (const config of serviceConfigs) {
|
||||
if (typeof config === "object") {
|
||||
expect(config.source).toContain(`-${prefix}`);
|
||||
expect(config.source).toContain(`-${suffix}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,17 +179,17 @@ services:
|
||||
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs in services", () => {
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFileConfigServices) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToConfigsInServices(
|
||||
const updatedComposeData = addSuffixToConfigsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
addPrefixToAllConfigs,
|
||||
addPrefixToConfigsRoot,
|
||||
addSuffixToAllConfigs,
|
||||
addSuffixToConfigsRoot,
|
||||
} from "@/server/utils/docker/compose/configs";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
@@ -80,12 +80,12 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all configs in root and services", () => {
|
||||
test("Add suffix to all configs in root and services", () => {
|
||||
const composeData = load(composeFileCombinedConfigs) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllConfigs(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllConfigs(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedConfigs);
|
||||
});
|
||||
@@ -162,14 +162,14 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs with environment and external", () => {
|
||||
test("Add suffix to configs with environment and external", () => {
|
||||
const composeData = load(
|
||||
composeFileWithEnvAndExternal,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllConfigs(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllConfigs(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileWithEnvAndExternal);
|
||||
});
|
||||
@@ -234,14 +234,14 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs with template driver and labels", () => {
|
||||
test("Add suffix to configs with template driver and labels", () => {
|
||||
const composeData = load(
|
||||
composeFileWithTemplateDriverAndLabels,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllConfigs(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllConfigs(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(
|
||||
expectedComposeFileWithTemplateDriverAndLabels,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToNetworksRoot } from "@/server/utils/docker/compose/network";
|
||||
import { addSuffixToNetworksRoot } from "@/server/utils/docker/compose/network";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -35,19 +35,19 @@ test("Generate random hash with 8 characters", () => {
|
||||
expect(hash.length).toBe(8);
|
||||
});
|
||||
|
||||
test("Add prefix to networks root property", () => {
|
||||
test("Add suffix to networks root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const volumeKey of Object.keys(networks)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -79,19 +79,19 @@ networks:
|
||||
internal: true
|
||||
`;
|
||||
|
||||
test("Add prefix to advanced networks root property (2 TRY)", () => {
|
||||
test("Add suffix to advanced networks root property (2 TRY)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,19 +120,19 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with external properties", () => {
|
||||
test("Add suffix to networks with external properties", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,19 +160,19 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with IPAM configurations", () => {
|
||||
test("Add suffix to networks with IPAM configurations", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,19 +201,19 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with custom options", () => {
|
||||
test("Add suffix to networks with custom options", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -240,7 +240,7 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
// Expected compose file with static prefix `testhash`
|
||||
// Expected compose file with static suffix `testhash`
|
||||
const expectedComposeFile6 = `
|
||||
version: "3.8"
|
||||
|
||||
@@ -264,18 +264,70 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with static prefix", () => {
|
||||
test("Add suffix to networks with static suffix", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
const expectedComposeData = load(
|
||||
expectedComposeFile6,
|
||||
) as ComposeSpecification;
|
||||
expect(networks).toStrictEqual(expectedComposeData.networks);
|
||||
});
|
||||
|
||||
const composeFile7 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
`;
|
||||
|
||||
const expectedComposeFile7 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.driver.mtu: 1200
|
||||
|
||||
backend:
|
||||
driver: bridge
|
||||
attachable: true
|
||||
|
||||
external_network:
|
||||
external: true
|
||||
name: dokploy-network
|
||||
`;
|
||||
test("It shoudn't add suffix to dokploy-network", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain("dokploy-network");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNetworks } from "@/server/utils/docker/compose/network";
|
||||
import { addSuffixToServiceNetworks } from "@/server/utils/docker/compose/network";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -20,30 +20,30 @@ services:
|
||||
- backend
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services", () => {
|
||||
test("Add suffix to networks in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData?.services?.web?.networks).toContain(
|
||||
`frontend-${prefix}`,
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
|
||||
expect(actualComposeData?.services?.api?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
|
||||
const apiNetworks = actualComposeData?.services?.api?.networks;
|
||||
|
||||
expect(apiNetworks).toBeDefined();
|
||||
expect(actualComposeData?.services?.api?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -64,26 +64,26 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services with aliases", () => {
|
||||
test("Add suffix to networks in services with aliases", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.api?.networks).toHaveProperty(
|
||||
`frontend-${prefix}`,
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
|
||||
const networkConfig = actualComposeData?.services?.api?.networks as {
|
||||
[key: string]: { aliases?: string[] };
|
||||
};
|
||||
expect(networkConfig[`frontend-${prefix}`]).toBeDefined();
|
||||
expect(networkConfig[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||
expect(networkConfig[`frontend-${suffix}`]).toBeDefined();
|
||||
expect(networkConfig[`frontend-${suffix}`]?.aliases).toContain("api");
|
||||
|
||||
expect(actualComposeData.services?.api?.networks).not.toHaveProperty(
|
||||
"frontend-ash",
|
||||
@@ -104,19 +104,19 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services (Object with simple networks)", () => {
|
||||
test("Add suffix to networks in services (Object with simple networks)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.redis?.networks).toHaveProperty(
|
||||
`backend-${prefix}`,
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -150,35 +150,124 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services (combined case)", () => {
|
||||
test("Add suffix to networks in services (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
// Caso 1: ListOfStrings
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`frontend-${prefix}`,
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(apiNetworks[`frontend-${prefix}`]).toBeDefined();
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${suffix}`);
|
||||
expect(apiNetworks[`frontend-${suffix}`]).toBeDefined();
|
||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||
|
||||
// Caso 3: Objeto con redes simples
|
||||
const redisNetworks = actualComposeData.services?.redis?.networks;
|
||||
expect(redisNetworks).toHaveProperty(`backend-${prefix}`);
|
||||
expect(redisNetworks).toHaveProperty(`backend-${suffix}`);
|
||||
expect(redisNetworks).not.toHaveProperty("backend");
|
||||
});
|
||||
|
||||
const composeFile7 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- dokploy-network
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const networks = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const service = networks.web;
|
||||
|
||||
expect(service).toBeDefined();
|
||||
expect(service?.networks).toContain("dokploy-network");
|
||||
});
|
||||
|
||||
const composeFile8 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
- dokploy-network
|
||||
|
||||
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- api
|
||||
dokploy-network:
|
||||
aliases:
|
||||
- api
|
||||
redis:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
dokploy-network:
|
||||
db:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
dokploy-network:
|
||||
aliases:
|
||||
- apid
|
||||
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services multiples cases", () => {
|
||||
const composeData = load(composeFile8) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const networks = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const service = networks.web;
|
||||
const api = networks.api;
|
||||
const redis = networks.redis;
|
||||
const db = networks.db;
|
||||
|
||||
const dbNetworks = db?.networks as {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const apiNetworks = api?.networks as {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
expect(service).toBeDefined();
|
||||
expect(service?.networks).toContain("dokploy-network");
|
||||
|
||||
expect(redis?.networks).toHaveProperty("dokploy-network");
|
||||
expect(dbNetworks["dokploy-network"]).toBeDefined();
|
||||
expect(apiNetworks["dokploy-network"]).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
addPrefixToAllNetworks,
|
||||
addPrefixToServiceNetworks,
|
||||
addSuffixToAllNetworks,
|
||||
addSuffixToServiceNetworks,
|
||||
} from "@/server/utils/docker/compose/network";
|
||||
import { addPrefixToNetworksRoot } from "@/server/utils/docker/compose/network";
|
||||
import { addSuffixToNetworksRoot } from "@/server/utils/docker/compose/network";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -38,54 +38,54 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services and root (combined case)", () => {
|
||||
test("Add suffix to networks in services and root (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
// Prefijo para redes definidas en el root
|
||||
if (composeData.networks) {
|
||||
composeData.networks = addPrefixToNetworksRoot(
|
||||
composeData.networks = addSuffixToNetworksRoot(
|
||||
composeData.networks,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
}
|
||||
|
||||
// Prefijo para redes definidas en los servicios
|
||||
if (composeData.services) {
|
||||
composeData.services = addPrefixToServiceNetworks(
|
||||
composeData.services = addSuffixToServiceNetworks(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
}
|
||||
|
||||
const actualComposeData = { ...composeData };
|
||||
|
||||
// Verificar redes en root
|
||||
expect(actualComposeData.networks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(actualComposeData.networks).toHaveProperty(`backend-${prefix}`);
|
||||
expect(actualComposeData.networks).toHaveProperty(`frontend-${suffix}`);
|
||||
expect(actualComposeData.networks).toHaveProperty(`backend-${suffix}`);
|
||||
expect(actualComposeData.networks).not.toHaveProperty("frontend");
|
||||
expect(actualComposeData.networks).not.toHaveProperty("backend");
|
||||
|
||||
// Caso 1: ListOfStrings
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`frontend-${prefix}`,
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||
[key: string]: { aliases?: string[] };
|
||||
};
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(apiNetworks?.[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${suffix}`);
|
||||
expect(apiNetworks?.[`frontend-${suffix}`]?.aliases).toContain("api");
|
||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||
|
||||
// Caso 3: Objeto con redes simples
|
||||
const redisNetworks = actualComposeData.services?.redis?.networks;
|
||||
expect(redisNetworks).toHaveProperty(`backend-${prefix}`);
|
||||
expect(redisNetworks).toHaveProperty(`backend-${suffix}`);
|
||||
expect(redisNetworks).not.toHaveProperty("backend");
|
||||
});
|
||||
|
||||
@@ -119,14 +119,14 @@ networks:
|
||||
driver: bridge
|
||||
`);
|
||||
|
||||
test("Add prefix to networks in compose file", () => {
|
||||
test("Add suffix to networks in compose file", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToAllNetworks(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile);
|
||||
});
|
||||
@@ -181,11 +181,11 @@ networks:
|
||||
driver: bridge
|
||||
`);
|
||||
|
||||
test("Add prefix to networks in compose file with external and internal networks", () => {
|
||||
test("Add suffix to networks in compose file with external and internal networks", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const updatedComposeData = addPrefixToAllNetworks(composeData, prefix);
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile2);
|
||||
});
|
||||
@@ -246,11 +246,90 @@ networks:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
`);
|
||||
|
||||
test("Add prefix to networks in compose file with multiple services and complex network configurations", () => {
|
||||
test("Add suffix to networks in compose file with multiple services and complex network configurations", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const updatedComposeData = addPrefixToAllNetworks(composeData, prefix);
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile3);
|
||||
});
|
||||
|
||||
const composeFile4 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: myapp:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- app
|
||||
backend:
|
||||
dokploy-network:
|
||||
|
||||
worker:
|
||||
image: worker:latest
|
||||
networks:
|
||||
- backend
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
attachable: true
|
||||
|
||||
backend:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
|
||||
dokploy-network:
|
||||
driver: bridge
|
||||
|
||||
`;
|
||||
|
||||
const expectedComposeFile4 = load(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: myapp:latest
|
||||
networks:
|
||||
frontend-testhash:
|
||||
aliases:
|
||||
- app
|
||||
backend-testhash:
|
||||
dokploy-network:
|
||||
|
||||
worker:
|
||||
image: worker:latest
|
||||
networks:
|
||||
- backend-testhash
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
frontend-testhash:
|
||||
driver: bridge
|
||||
attachable: true
|
||||
|
||||
backend-testhash:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
|
||||
dokploy-network:
|
||||
driver: bridge
|
||||
|
||||
|
||||
|
||||
`);
|
||||
|
||||
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 suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
console.log(updatedComposeData);
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile4);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToSecretsRoot } from "@/server/utils/docker/compose/secrets";
|
||||
import { addSuffixToSecretsRoot } from "@/server/utils/docker/compose/secrets";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -23,18 +23,18 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in root property", () => {
|
||||
test("Add suffix to secrets in root property", () => {
|
||||
const composeData = load(composeFileSecretsRoot) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
return;
|
||||
}
|
||||
const secrets = addPrefixToSecretsRoot(composeData.secrets, prefix);
|
||||
const secrets = addSuffixToSecretsRoot(composeData.secrets, suffix);
|
||||
expect(secrets).toBeDefined();
|
||||
if (secrets) {
|
||||
for (const secretKey of Object.keys(secrets)) {
|
||||
expect(secretKey).toContain(`-${prefix}`);
|
||||
expect(secretKey).toContain(`-${suffix}`);
|
||||
expect(secrets[secretKey]).toBeDefined();
|
||||
}
|
||||
}
|
||||
@@ -52,19 +52,19 @@ secrets:
|
||||
file: ./api_key.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in root property (Test 1)", () => {
|
||||
test("Add suffix to secrets in root property (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsRoot1) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
return;
|
||||
}
|
||||
const secrets = addPrefixToSecretsRoot(composeData.secrets, prefix);
|
||||
const secrets = addSuffixToSecretsRoot(composeData.secrets, suffix);
|
||||
expect(secrets).toBeDefined();
|
||||
|
||||
if (secrets) {
|
||||
for (const secretKey of Object.keys(secrets)) {
|
||||
expect(secretKey).toContain(`-${prefix}`);
|
||||
expect(secretKey).toContain(`-${suffix}`);
|
||||
expect(secrets[secretKey]).toBeDefined();
|
||||
}
|
||||
}
|
||||
@@ -84,19 +84,19 @@ secrets:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in root property (Test 2)", () => {
|
||||
test("Add suffix to secrets in root property (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsRoot2) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
return;
|
||||
}
|
||||
const secrets = addPrefixToSecretsRoot(composeData.secrets, prefix);
|
||||
const secrets = addSuffixToSecretsRoot(composeData.secrets, suffix);
|
||||
expect(secrets).toBeDefined();
|
||||
|
||||
if (secrets) {
|
||||
for (const secretKey of Object.keys(secrets)) {
|
||||
expect(secretKey).toContain(`-${prefix}`);
|
||||
expect(secretKey).toContain(`-${suffix}`);
|
||||
expect(secrets[secretKey]).toBeDefined();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToSecretsInServices } from "@/server/utils/docker/compose/secrets";
|
||||
import { addSuffixToSecretsInServices } from "@/server/utils/docker/compose/secrets";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -18,22 +18,22 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in services", () => {
|
||||
test("Add suffix to secrets in services", () => {
|
||||
const composeData = load(composeFileSecretsServices) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToSecretsInServices(
|
||||
const updatedComposeData = addSuffixToSecretsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.db?.secrets).toContain(
|
||||
`db_password-${prefix}`,
|
||||
`db_password-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -51,22 +51,22 @@ secrets:
|
||||
file: ./app_secret.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in services (Test 1)", () => {
|
||||
test("Add suffix to secrets in services (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsServices1) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToSecretsInServices(
|
||||
const updatedComposeData = addSuffixToSecretsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.app?.secrets).toContain(
|
||||
`app_secret-${prefix}`,
|
||||
`app_secret-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -90,24 +90,24 @@ secrets:
|
||||
file: ./frontend_secret.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in services (Test 2)", () => {
|
||||
test("Add suffix to secrets in services (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsServices2) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToSecretsInServices(
|
||||
const updatedComposeData = addSuffixToSecretsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.backend?.secrets).toContain(
|
||||
`backend_secret-${prefix}`,
|
||||
`backend_secret-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.frontend?.secrets).toContain(
|
||||
`frontend_secret-${prefix}`,
|
||||
`frontend_secret-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addPrefixToAllSecrets } from "@/server/utils/docker/compose/secrets";
|
||||
import { addSuffixToAllSecrets } from "@/server/utils/docker/compose/secrets";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -47,11 +47,11 @@ secrets:
|
||||
file: ./app_secret.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all secrets", () => {
|
||||
test("Add suffix to all secrets", () => {
|
||||
const composeData = load(composeFileCombinedSecrets) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllSecrets(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedSecrets);
|
||||
});
|
||||
@@ -98,11 +98,11 @@ secrets:
|
||||
file: ./cache_secret.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all secrets (3rd Case)", () => {
|
||||
test("Add suffix to all secrets (3rd Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllSecrets(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedSecrets3);
|
||||
});
|
||||
@@ -149,11 +149,11 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all secrets (4th Case)", () => {
|
||||
test("Add suffix to all secrets (4th Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets4) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllSecrets(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedSecrets4);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -27,33 +27,33 @@ test("Generate random hash with 8 characters", () => {
|
||||
expect(hash.length).toBe(8);
|
||||
});
|
||||
|
||||
test("Add prefix to service names with container_name in compose file", () => {
|
||||
test("Add suffix to service names with container_name in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que el nombre del contenedor ha cambiado correctamente
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.container_name).toBe(
|
||||
`web_container-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.container_name).toBe(
|
||||
`web_container-${suffix}`,
|
||||
);
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -32,49 +32,49 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with depends_on (array) in compose file", () => {
|
||||
test("Add suffix to service names with depends_on (array) in compose file", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en depends_on tienen el prefijo
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.depends_on).toContain(
|
||||
`db-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.depends_on).toContain(
|
||||
`db-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.depends_on).toContain(
|
||||
`api-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.depends_on).toContain(
|
||||
`api-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${suffix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
@@ -102,49 +102,49 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with depends_on (object) in compose file", () => {
|
||||
test("Add suffix to service names with depends_on (object) in compose file", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en depends_on tienen el prefijo
|
||||
const webDependsOn = actualComposeData.services?.[`web-${prefix}`]
|
||||
const webDependsOn = actualComposeData.services?.[`web-${suffix}`]
|
||||
?.depends_on as Record<string, any>;
|
||||
expect(webDependsOn).toHaveProperty(`db-${prefix}`);
|
||||
expect(webDependsOn).toHaveProperty(`api-${prefix}`);
|
||||
expect(webDependsOn[`db-${prefix}`].condition).toBe("service_healthy");
|
||||
expect(webDependsOn[`api-${prefix}`].condition).toBe("service_started");
|
||||
expect(webDependsOn).toHaveProperty(`db-${suffix}`);
|
||||
expect(webDependsOn).toHaveProperty(`api-${suffix}`);
|
||||
expect(webDependsOn[`db-${suffix}`].condition).toBe("service_healthy");
|
||||
expect(webDependsOn[`api-${suffix}`].condition).toBe("service_started");
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${suffix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -30,41 +30,41 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with extends (string) in compose file", () => {
|
||||
test("Add suffix to service names with extends (string) in compose file", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que el nombre en extends tiene el prefijo
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.extends).toBe(
|
||||
`base_service-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.extends).toBe(
|
||||
`base_service-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||
expect(actualComposeData.services?.[`base_service-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`base_service-${suffix}`]?.image).toBe(
|
||||
"base:latest",
|
||||
);
|
||||
});
|
||||
@@ -90,42 +90,42 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with extends (object) in compose file", () => {
|
||||
test("Add suffix to service names with extends (object) in compose file", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que el nombre en extends.service tiene el prefijo
|
||||
const webExtends = actualComposeData.services?.[`web-${prefix}`]?.extends;
|
||||
const webExtends = actualComposeData.services?.[`web-${suffix}`]?.extends;
|
||||
if (typeof webExtends !== "string") {
|
||||
expect(webExtends?.service).toBe(`base_service-${prefix}`);
|
||||
expect(webExtends?.service).toBe(`base_service-${suffix}`);
|
||||
}
|
||||
|
||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||
expect(actualComposeData.services?.[`base_service-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`base_service-${suffix}`]?.image).toBe(
|
||||
"base:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -31,46 +31,46 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with links in compose file", () => {
|
||||
test("Add suffix to service names with links in compose file", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en links tienen el prefijo
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.links).toContain(
|
||||
`db-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.links).toContain(
|
||||
`db-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${suffix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -26,23 +26,23 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names in compose file", () => {
|
||||
test("Add suffix to service names in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que los nombres de los servicios han cambiado correctamente
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
// Verificar que las claves originales no existen
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
addPrefixToAllServiceNames,
|
||||
addPrefixToServiceNames,
|
||||
addSuffixToAllServiceNames,
|
||||
addSuffixToServiceNames,
|
||||
} from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
@@ -70,17 +70,17 @@ networks:
|
||||
driver: bridge
|
||||
`);
|
||||
|
||||
test("Add prefix to all service names in compose file", () => {
|
||||
test("Add suffix to all service names in compose file", () => {
|
||||
const composeData = load(composeFileCombinedAllCases) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
@@ -175,11 +175,11 @@ networks:
|
||||
driver: bridge
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all service names in compose file 1", () => {
|
||||
test("Add suffix to all service names in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllServiceNames(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile1);
|
||||
});
|
||||
@@ -270,11 +270,11 @@ networks:
|
||||
driver: bridge
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all service names in compose file 2", () => {
|
||||
test("Add suffix to all service names in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllServiceNames(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile2);
|
||||
});
|
||||
@@ -365,11 +365,11 @@ networks:
|
||||
driver: bridge
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all service names in compose file 3", () => {
|
||||
test("Add suffix to all service names in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllServiceNames(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile3);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import { addSuffixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -35,44 +35,44 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with volumes_from in compose file", () => {
|
||||
test("Add suffix to service names with volumes_from in compose file", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en volumes_from tienen el prefijo
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.volumes_from).toContain(
|
||||
`shared-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.volumes_from).toContain(
|
||||
`shared-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.volumes_from).toContain(
|
||||
`shared-${prefix}`,
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.volumes_from).toContain(
|
||||
`shared-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que el servicio shared también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`shared-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`shared-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("shared");
|
||||
expect(actualComposeData.services?.[`shared-${prefix}`]?.image).toBe(
|
||||
expect(actualComposeData.services?.[`shared-${suffix}`]?.image).toBe(
|
||||
"busybox",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
addPrefixToAllVolumes,
|
||||
addPrefixToVolumesRoot,
|
||||
addSuffixToAllVolumes,
|
||||
addSuffixToVolumesRoot,
|
||||
} from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
@@ -142,15 +142,15 @@ 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 prefix to volumes root property", () => {
|
||||
test("Add suffix to volumes root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
|
||||
// {
|
||||
// 'db-data-af045046': { driver: 'local' },
|
||||
@@ -160,15 +160,15 @@ test("Add prefix to volumes root property", () => {
|
||||
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("Expect to change the prefix in all the possible places", () => {
|
||||
test("Expect to change the suffix in all the possible places", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerCompose);
|
||||
});
|
||||
@@ -217,11 +217,11 @@ volumes:
|
||||
mongo-data-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (2 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (2 Try)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerCompose2);
|
||||
});
|
||||
@@ -270,11 +270,11 @@ volumes:
|
||||
mongo-data-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (3 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (3 Try)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerCompose3);
|
||||
});
|
||||
@@ -1011,11 +1011,11 @@ volumes:
|
||||
db-config-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (4 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (4 Try)", () => {
|
||||
const composeData = load(composeFileComplex) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerComposeComplex);
|
||||
});
|
||||
@@ -1110,11 +1110,11 @@ volumes:
|
||||
db-data-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (5 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (5 Try)", () => {
|
||||
const composeData = load(composeFileExample1) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerComposeExample1);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToVolumesRoot } from "@/server/utils/docker/compose/volume";
|
||||
import { addSuffixToVolumesRoot } from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -29,18 +29,18 @@ test("Generate random hash with 8 characters", () => {
|
||||
expect(hash.length).toBe(8);
|
||||
});
|
||||
|
||||
test("Add prefix to volumes in root property", () => {
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
expect(volumes[volumeKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -67,18 +67,18 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes in root property (Case 2)", () => {
|
||||
test("Add suffix to volumes in root property (Case 2)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
expect(volumes[volumeKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -101,19 +101,19 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes in root property (Case 3)", () => {
|
||||
test("Add suffix to volumes in root property (Case 3)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
expect(volumes[volumeKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -179,15 +179,15 @@ volumes:
|
||||
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to volumes in root property", () => {
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
const updatedComposeData = { ...composeData, volumes };
|
||||
|
||||
// Verificar que el resultado coincide con el archivo esperado
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToVolumesInServices } from "@/server/utils/docker/compose/volume";
|
||||
import { addSuffixToVolumesInServices } from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
@@ -21,22 +21,22 @@ services:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes declared directly in services", () => {
|
||||
test("Add suffix to volumes declared directly in services", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToVolumesInServices(
|
||||
const updatedComposeData = addSuffixToVolumesInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
expect(actualComposeData.services?.db?.volumes).toContain(
|
||||
`db_data-${prefix}:/var/lib/postgresql/data`,
|
||||
`db_data-${suffix}:/var/lib/postgresql/data`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -56,25 +56,25 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes declared directly in services (Case 2)", () => {
|
||||
test("Add suffix to volumes declared directly in services (Case 2)", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToVolumesInServices(
|
||||
const updatedComposeData = addSuffixToVolumesInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.db?.volumes).toEqual([
|
||||
{
|
||||
type: "volume",
|
||||
source: `db-test-${prefix}`,
|
||||
source: `db-test-${suffix}`,
|
||||
target: "/var/lib/postgresql/data",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
addPrefixToAllVolumes,
|
||||
addPrefixToVolumesInServices,
|
||||
addSuffixToAllVolumes,
|
||||
addSuffixToVolumesInServices,
|
||||
} from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
@@ -47,12 +47,12 @@ volumes:
|
||||
driver: local
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to volumes with type: volume in services", () => {
|
||||
test("Add suffix to volumes with type: volume in services", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume);
|
||||
@@ -96,12 +96,12 @@ volumes:
|
||||
driver: local
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to mixed volumes in services", () => {
|
||||
test("Add suffix to mixed volumes in services", () => {
|
||||
const composeData = load(composeFileTypeVolume1) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume1);
|
||||
@@ -157,12 +157,12 @@ volumes:
|
||||
device: /path/to/app/logs
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to complex volume configurations in services", () => {
|
||||
test("Add suffix to complex volume configurations in services", () => {
|
||||
const composeData = load(composeFileTypeVolume2) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume2);
|
||||
@@ -276,12 +276,12 @@ volumes:
|
||||
device: /path/to/shared/logs
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to complex nested volumes configuration in services", () => {
|
||||
test("Add suffix to complex nested volumes configuration in services", () => {
|
||||
const composeData = load(composeFileTypeVolume3) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume3);
|
||||
|
||||
@@ -16,16 +16,9 @@ const baseAdmin: Admin = {
|
||||
createdAt: "",
|
||||
authId: "",
|
||||
adminId: "string",
|
||||
githubAppId: null,
|
||||
githubAppName: null,
|
||||
serverIp: null,
|
||||
certificateType: "none",
|
||||
host: null,
|
||||
githubClientId: null,
|
||||
githubClientSecret: null,
|
||||
githubInstallationId: null,
|
||||
githubPrivateKey: null,
|
||||
githubWebhookSecret: null,
|
||||
letsEncryptEmail: null,
|
||||
sshPrivateKey: null,
|
||||
enableDockerCleanup: false,
|
||||
|
||||
@@ -10,9 +10,23 @@ const baseApp: ApplicationNested = {
|
||||
appName: "",
|
||||
autoDeploy: true,
|
||||
branch: null,
|
||||
dockerBuildStage: "",
|
||||
buildArgs: null,
|
||||
buildPath: "/",
|
||||
gitlabPathNamespace: "",
|
||||
buildType: "nixpacks",
|
||||
bitbucketBranch: "",
|
||||
bitbucketBuildPath: "",
|
||||
bitbucketId: "",
|
||||
bitbucketRepository: "",
|
||||
bitbucketOwner: "",
|
||||
githubId: "",
|
||||
gitlabProjectId: 0,
|
||||
gitlabBranch: "",
|
||||
gitlabBuildPath: "",
|
||||
gitlabId: "",
|
||||
gitlabRepository: "",
|
||||
gitlabOwner: "",
|
||||
command: null,
|
||||
cpuLimit: null,
|
||||
cpuReservation: null,
|
||||
|
||||
@@ -37,6 +37,7 @@ const mySchema = z.discriminatedUnion("buildType", [
|
||||
})
|
||||
.min(1, "Dockerfile required"),
|
||||
dockerContextPath: z.string().nullable().default(""),
|
||||
dockerBuildStage: z.string().nullable().default(""),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("heroku_buildpacks"),
|
||||
@@ -86,6 +87,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
...(data.buildType && {
|
||||
dockerfile: data.dockerfile || "",
|
||||
dockerContextPath: data.dockerContextPath || "",
|
||||
dockerBuildStage: data.dockerBuildStage || "",
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
@@ -106,6 +108,8 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||
dockerContextPath:
|
||||
data.buildType === "dockerfile" ? data.dockerContextPath : null,
|
||||
dockerBuildStage:
|
||||
data.buildType === "dockerfile" ? data.dockerBuildStage : null,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Build type saved");
|
||||
@@ -241,6 +245,32 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerBuildStage"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Docker Build Stage</FormLabel>
|
||||
<FormDescription>
|
||||
Allows you to target a specific stage in a
|
||||
Multi-stage Dockerfile. If empty, Docker defaults to
|
||||
build the last defined stage.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"E.g. production"}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ export const AddDomain = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Host</FormLabel>
|
||||
<div className="flex max-lg:flex-wrap sm:flex-row gap-2">
|
||||
<div className="flex gap-2">
|
||||
<FormControl>
|
||||
<Input placeholder="api.dokploy.com" {...field} />
|
||||
</FormControl>
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const BitbucketProviderSchema = z.object({
|
||||
buildPath: z.string().min(1, "Path is required").default("/"),
|
||||
repository: z
|
||||
.object({
|
||||
repo: z.string().min(1, "Repo is required"),
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||
});
|
||||
|
||||
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
export const SaveBitbucketProvider = ({ applicationId }: Props) => {
|
||||
const { data: bitbucketProviders } =
|
||||
api.bitbucket.bitbucketProviders.useQuery();
|
||||
const { data, refetch } = api.application.one.useQuery({ applicationId });
|
||||
|
||||
const { mutateAsync, isLoading: isSavingBitbucketProvider } =
|
||||
api.application.saveBitbucketProvider.useMutation();
|
||||
|
||||
const form = useForm<BitbucketProvider>({
|
||||
defaultValues: {
|
||||
buildPath: "/",
|
||||
repository: {
|
||||
owner: "",
|
||||
repo: "",
|
||||
},
|
||||
bitbucketId: "",
|
||||
branch: "",
|
||||
},
|
||||
resolver: zodResolver(BitbucketProviderSchema),
|
||||
});
|
||||
|
||||
const repository = form.watch("repository");
|
||||
const bitbucketId = form.watch("bitbucketId");
|
||||
|
||||
const {
|
||||
data: repositories,
|
||||
isLoading: isLoadingRepositories,
|
||||
error,
|
||||
isError,
|
||||
} = api.bitbucket.getBitbucketRepositories.useQuery(
|
||||
{
|
||||
bitbucketId,
|
||||
},
|
||||
{
|
||||
enabled: !!bitbucketId,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
fetchStatus,
|
||||
status,
|
||||
} = api.bitbucket.getBitbucketBranches.useQuery(
|
||||
{
|
||||
owner: repository?.owner,
|
||||
repo: repository?.repo,
|
||||
bitbucketId,
|
||||
},
|
||||
{
|
||||
enabled: !!repository?.owner && !!repository?.repo && !!bitbucketId,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
branch: data.bitbucketBranch || "",
|
||||
repository: {
|
||||
repo: data.bitbucketRepository || "",
|
||||
owner: data.bitbucketOwner || "",
|
||||
},
|
||||
buildPath: data.bitbucketBuildPath || "/",
|
||||
bitbucketId: data.bitbucketId || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form]);
|
||||
|
||||
const onSubmit = async (data: BitbucketProvider) => {
|
||||
await mutateAsync({
|
||||
bitbucketBranch: data.branch,
|
||||
bitbucketRepository: data.repository.repo,
|
||||
bitbucketOwner: data.repository.owner,
|
||||
bitbucketBuildPath: data.buildPath,
|
||||
bitbucketId: data.bitbucketId,
|
||||
applicationId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Provided Saved");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to save the Bitbucket provider");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 py-3"
|
||||
>
|
||||
{error && (
|
||||
<AlertBlock type="error">Repositories: {error.message}</AlertBlock>
|
||||
)}
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bitbucketId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Bitbucket Account</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
form.setValue("repository", {
|
||||
owner: "",
|
||||
repo: "",
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a Bitbucket Account" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{bitbucketProviders?.map((bitbucketProvider) => (
|
||||
<SelectItem
|
||||
key={bitbucketProvider.bitbucketId}
|
||||
value={bitbucketProvider.bitbucketId}
|
||||
>
|
||||
{bitbucketProvider.gitProvider.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repository"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Repository</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
"w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isLoadingRepositories
|
||||
? "Loading...."
|
||||
: field.value.owner
|
||||
? repositories?.find(
|
||||
(repo) => repo.name === field.value.repo,
|
||||
)?.name
|
||||
: "Select repository"}
|
||||
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search repository..."
|
||||
className="h-9"
|
||||
/>
|
||||
{isLoadingRepositories && (
|
||||
<span className="py-6 text-center text-sm">
|
||||
Loading Repositories....
|
||||
</span>
|
||||
)}
|
||||
<CommandEmpty>No repositories found.</CommandEmpty>
|
||||
<ScrollArea className="h-96">
|
||||
<CommandGroup>
|
||||
{repositories?.map((repo) => (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
owner: repo.owner.username as string,
|
||||
repo: repo.name,
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
repo.name === field.value.repo
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{form.formState.errors.repository && (
|
||||
<p className={cn("text-sm font-medium text-destructive")}>
|
||||
Repository is required
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="branch"
|
||||
render={({ field }) => (
|
||||
<FormItem className="block w-full">
|
||||
<FormLabel>Branch</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
" w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{status === "loading" && fetchStatus === "fetching"
|
||||
? "Loading...."
|
||||
: field.value
|
||||
? branches?.find(
|
||||
(branch) => branch.name === field.value,
|
||||
)?.name
|
||||
: "Select branch"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search branch..."
|
||||
className="h-9"
|
||||
/>
|
||||
{status === "loading" && fetchStatus === "fetching" && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Loading Branches....
|
||||
</span>
|
||||
)}
|
||||
{!repository?.owner && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Select a repository
|
||||
</span>
|
||||
)}
|
||||
<ScrollArea className="h-96">
|
||||
<CommandEmpty>No branch found.</CommandEmpty>
|
||||
|
||||
<CommandGroup>
|
||||
{branches?.map((branch) => (
|
||||
<CommandItem
|
||||
value={branch.name}
|
||||
key={branch.commit.sha}
|
||||
onSelect={() => {
|
||||
form.setValue("branch", branch.name);
|
||||
}}
|
||||
>
|
||||
{branch.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
branch.name === field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
||||
<FormMessage />
|
||||
</Popover>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="buildPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Build Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="/" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
isLoading={isSavingBitbucketProvider}
|
||||
type="submit"
|
||||
className="w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -21,6 +21,13 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -39,6 +46,7 @@ const GithubProviderSchema = z.object({
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
githubId: z.string().min(1, "Github Provider is required"),
|
||||
});
|
||||
|
||||
type GithubProvider = z.infer<typeof GithubProviderSchema>;
|
||||
@@ -48,6 +56,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const SaveGithubProvider = ({ applicationId }: Props) => {
|
||||
const { data: githubProviders } = api.github.githubProviders.useQuery();
|
||||
const { data, refetch } = api.application.one.useQuery({ applicationId });
|
||||
|
||||
const { mutateAsync, isLoading: isSavingGithubProvider } =
|
||||
@@ -60,26 +69,38 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
||||
owner: "",
|
||||
repo: "",
|
||||
},
|
||||
githubId: "",
|
||||
branch: "",
|
||||
},
|
||||
resolver: zodResolver(GithubProviderSchema),
|
||||
});
|
||||
|
||||
const repository = form.watch("repository");
|
||||
const githubId = form.watch("githubId");
|
||||
|
||||
const { data: repositories, isLoading: isLoadingRepositories } =
|
||||
api.admin.getRepositories.useQuery();
|
||||
api.github.getGithubRepositories.useQuery(
|
||||
{
|
||||
githubId,
|
||||
},
|
||||
{
|
||||
enabled: !!githubId,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
fetchStatus,
|
||||
status,
|
||||
} = api.admin.getBranches.useQuery(
|
||||
} = api.github.getGithubBranches.useQuery(
|
||||
{
|
||||
owner: repository?.owner,
|
||||
repo: repository?.repo,
|
||||
githubId,
|
||||
},
|
||||
{
|
||||
enabled: !!repository?.owner && !!repository?.repo && !!githubId,
|
||||
},
|
||||
{ enabled: !!repository?.owner && !!repository?.repo },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -91,6 +112,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
||||
owner: data.owner || "",
|
||||
},
|
||||
buildPath: data.buildPath || "/",
|
||||
githubId: data.githubId || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form]);
|
||||
@@ -102,6 +124,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
||||
applicationId,
|
||||
owner: data.repository.owner,
|
||||
buildPath: data.buildPath,
|
||||
githubId: data.githubId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Provided Saved");
|
||||
@@ -120,6 +143,45 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
|
||||
className="grid w-full gap-4 py-3"
|
||||
>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="githubId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Github Account</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
form.setValue("repository", {
|
||||
owner: "",
|
||||
repo: "",
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a Github Account" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{githubProviders?.map((githubProvider) => (
|
||||
<SelectItem
|
||||
key={githubProvider.githubId}
|
||||
value={githubProvider.githubId}
|
||||
>
|
||||
{githubProvider.gitProvider.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repository"
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const GitlabProviderSchema = z.object({
|
||||
buildPath: z.string().min(1, "Path is required").default("/"),
|
||||
repository: z
|
||||
.object({
|
||||
repo: z.string().min(1, "Repo is required"),
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
gitlabPathNamespace: z.string().min(1),
|
||||
id: z.number().nullable(),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||
});
|
||||
|
||||
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
||||
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
|
||||
const { data, refetch } = api.application.one.useQuery({ applicationId });
|
||||
|
||||
const { mutateAsync, isLoading: isSavingGitlabProvider } =
|
||||
api.application.saveGitlabProvider.useMutation();
|
||||
|
||||
const form = useForm<GitlabProvider>({
|
||||
defaultValues: {
|
||||
buildPath: "/",
|
||||
repository: {
|
||||
owner: "",
|
||||
repo: "",
|
||||
gitlabPathNamespace: "",
|
||||
id: null,
|
||||
},
|
||||
gitlabId: "",
|
||||
branch: "",
|
||||
},
|
||||
resolver: zodResolver(GitlabProviderSchema),
|
||||
});
|
||||
|
||||
const repository = form.watch("repository");
|
||||
const gitlabId = form.watch("gitlabId");
|
||||
|
||||
const {
|
||||
data: repositories,
|
||||
isLoading: isLoadingRepositories,
|
||||
error,
|
||||
} = api.gitlab.getGitlabRepositories.useQuery(
|
||||
{
|
||||
gitlabId,
|
||||
},
|
||||
{
|
||||
enabled: !!gitlabId,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
fetchStatus,
|
||||
status,
|
||||
} = api.gitlab.getGitlabBranches.useQuery(
|
||||
{
|
||||
owner: repository?.owner,
|
||||
repo: repository?.repo,
|
||||
id: repository?.id || 0,
|
||||
gitlabId: gitlabId,
|
||||
},
|
||||
{
|
||||
enabled: !!repository?.owner && !!repository?.repo && !!gitlabId,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
branch: data.gitlabBranch || "",
|
||||
repository: {
|
||||
repo: data.gitlabRepository || "",
|
||||
owner: data.gitlabOwner || "",
|
||||
gitlabPathNamespace: data.gitlabPathNamespace || "",
|
||||
id: data.gitlabProjectId,
|
||||
},
|
||||
buildPath: data.gitlabBuildPath || "/",
|
||||
gitlabId: data.gitlabId || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form]);
|
||||
|
||||
const onSubmit = async (data: GitlabProvider) => {
|
||||
await mutateAsync({
|
||||
gitlabBranch: data.branch,
|
||||
gitlabRepository: data.repository.repo,
|
||||
gitlabOwner: data.repository.owner,
|
||||
gitlabBuildPath: data.buildPath,
|
||||
gitlabId: data.gitlabId,
|
||||
applicationId,
|
||||
gitlabProjectId: data.repository.id,
|
||||
gitlabPathNamespace: data.repository.gitlabPathNamespace,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Provided Saved");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to save the gitlab provider");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 py-3"
|
||||
>
|
||||
{error && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="gitlabId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Gitlab Account</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
form.setValue("repository", {
|
||||
owner: "",
|
||||
repo: "",
|
||||
id: null,
|
||||
gitlabPathNamespace: "",
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a Gitlab Account" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{gitlabProviders?.map((gitlabProvider) => (
|
||||
<SelectItem
|
||||
key={gitlabProvider.gitlabId}
|
||||
value={gitlabProvider.gitlabId}
|
||||
>
|
||||
{gitlabProvider.gitProvider.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repository"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Repository</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
"w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isLoadingRepositories
|
||||
? "Loading...."
|
||||
: field.value.owner
|
||||
? repositories?.find(
|
||||
(repo) => repo.name === field.value.repo,
|
||||
)?.name
|
||||
: "Select repository"}
|
||||
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search repository..."
|
||||
className="h-9"
|
||||
/>
|
||||
{isLoadingRepositories && (
|
||||
<span className="py-6 text-center text-sm">
|
||||
Loading Repositories....
|
||||
</span>
|
||||
)}
|
||||
<CommandEmpty>No repositories found.</CommandEmpty>
|
||||
<ScrollArea className="h-96">
|
||||
<CommandGroup>
|
||||
{repositories && repositories.length === 0 && (
|
||||
<CommandEmpty>
|
||||
No repositories found.
|
||||
</CommandEmpty>
|
||||
)}
|
||||
{repositories?.map((repo) => {
|
||||
return (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
owner: repo.owner.username as string,
|
||||
repo: repo.name,
|
||||
id: repo.id,
|
||||
gitlabPathNamespace: repo.url,
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
repo.name === field.value.repo
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{form.formState.errors.repository && (
|
||||
<p className={cn("text-sm font-medium text-destructive")}>
|
||||
Repository is required
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="branch"
|
||||
render={({ field }) => (
|
||||
<FormItem className="block w-full">
|
||||
<FormLabel>Branch</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
" w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{status === "loading" && fetchStatus === "fetching"
|
||||
? "Loading...."
|
||||
: field.value
|
||||
? branches?.find(
|
||||
(branch) => branch.name === field.value,
|
||||
)?.name
|
||||
: "Select branch"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search branch..."
|
||||
className="h-9"
|
||||
/>
|
||||
{status === "loading" && fetchStatus === "fetching" && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Loading Branches....
|
||||
</span>
|
||||
)}
|
||||
{!repository?.owner && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Select a repository
|
||||
</span>
|
||||
)}
|
||||
<ScrollArea className="h-96">
|
||||
<CommandEmpty>No branch found.</CommandEmpty>
|
||||
|
||||
<CommandGroup>
|
||||
{branches?.map((branch) => (
|
||||
<CommandItem
|
||||
value={branch.name}
|
||||
key={branch.commit.id}
|
||||
onSelect={() => {
|
||||
form.setValue("branch", branch.name);
|
||||
}}
|
||||
>
|
||||
{branch.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
branch.name === field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
||||
<FormMessage />
|
||||
</Popover>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="buildPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Build Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="/" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
isLoading={isSavingGitlabProvider}
|
||||
type="submit"
|
||||
className="w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,23 +1,34 @@
|
||||
import { SaveDockerProvider } from "@/components/dashboard/application/general/generic/save-docker-provider";
|
||||
import { SaveGitProvider } from "@/components/dashboard/application/general/generic/save-git-provider";
|
||||
import { SaveGithubProvider } from "@/components/dashboard/application/general/generic/save-github-provider";
|
||||
import {
|
||||
BitbucketIcon,
|
||||
DockerIcon,
|
||||
GitIcon,
|
||||
GithubIcon,
|
||||
GitlabIcon,
|
||||
} from "@/components/icons/data-tools-icons";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { api } from "@/utils/api";
|
||||
import { GitBranch, LockIcon } from "lucide-react";
|
||||
import { GitBranch, LockIcon, UploadCloud } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { SaveBitbucketProvider } from "./save-bitbucket-provider";
|
||||
import { SaveDragNDrop } from "./save-drag-n-drop";
|
||||
import { SaveGitlabProvider } from "./save-gitlab-provider";
|
||||
|
||||
type TabState = "github" | "docker" | "git" | "drop";
|
||||
type TabState = "github" | "docker" | "git" | "drop" | "gitlab" | "bitbucket";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
export const ShowProviderForm = ({ applicationId }: Props) => {
|
||||
const { data: haveGithubConfigured } =
|
||||
api.admin.haveGithubConfigured.useQuery();
|
||||
const { data: githubProviders } = api.github.githubProviders.useQuery();
|
||||
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
|
||||
const { data: bitbucketProviders } =
|
||||
api.bitbucket.bitbucketProviders.useQuery();
|
||||
|
||||
const { data: application } = api.application.one.useQuery({ applicationId });
|
||||
const [tab, setSab] = useState<TabState>(application?.sourceType || "github");
|
||||
@@ -44,43 +55,104 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
||||
setSab(e as TabState);
|
||||
}}
|
||||
>
|
||||
<TabsList className="grid w-fit grid-cols-4 bg-transparent">
|
||||
<TabsTrigger
|
||||
value="github"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Github
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="docker"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Docker
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="git"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Git
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="drop"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Drop
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<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">
|
||||
<TabsTrigger
|
||||
value="github"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<GithubIcon className="size-4 text-current fill-current" />
|
||||
Github
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="gitlab"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<GitlabIcon className="size-4 text-current fill-current" />
|
||||
Gitlab
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="bitbucket"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<BitbucketIcon className="size-4 text-current fill-current" />
|
||||
Bitbucket
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="docker"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<DockerIcon className="size-5 text-current" />
|
||||
Docker
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="git"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<GitIcon />
|
||||
Git
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="drop"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<UploadCloud className="size-5 text-current" />
|
||||
Drop
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<TabsContent value="github" className="w-full p-2">
|
||||
{haveGithubConfigured ? (
|
||||
{githubProviders && githubProviders?.length > 0 ? (
|
||||
<SaveGithubProvider applicationId={applicationId} />
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<LockIcon className="size-8 text-muted-foreground" />
|
||||
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
|
||||
<GithubIcon className="size-8 text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
To deploy using GitHub, you need to configure your account
|
||||
first. Please, go to{" "}
|
||||
<Link
|
||||
href="/dashboard/settings/server"
|
||||
href="/dashboard/settings/git-providers"
|
||||
className="text-foreground"
|
||||
>
|
||||
Settings
|
||||
</Link>{" "}
|
||||
to do so.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="gitlab" className="w-full p-2">
|
||||
{gitlabProviders && gitlabProviders?.length > 0 ? (
|
||||
<SaveGitlabProvider applicationId={applicationId} />
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
|
||||
<GitlabIcon className="size-8 text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
To deploy using GitLab, you need to configure your account
|
||||
first. Please, go to{" "}
|
||||
<Link
|
||||
href="/dashboard/settings/git-providers"
|
||||
className="text-foreground"
|
||||
>
|
||||
Settings
|
||||
</Link>{" "}
|
||||
to do so.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="bitbucket" className="w-full p-2">
|
||||
{bitbucketProviders && bitbucketProviders?.length > 0 ? (
|
||||
<SaveBitbucketProvider applicationId={applicationId} />
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
|
||||
<BitbucketIcon className="size-8 text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
To deploy using Bitbucket, you need to configure your account
|
||||
first. Please, go to{" "}
|
||||
<Link
|
||||
href="/dashboard/settings/git-providers"
|
||||
className="text-foreground"
|
||||
>
|
||||
Settings
|
||||
@@ -93,6 +165,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
||||
<TabsContent value="docker" className="w-full p-2">
|
||||
<SaveDockerProvider applicationId={applicationId} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="git" className="w-full p-2">
|
||||
<SaveGitProvider applicationId={applicationId} />
|
||||
</TabsContent>
|
||||
|
||||
@@ -166,7 +166,13 @@ export const AddDomainCompose = ({
|
||||
<DialogTitle>Domain</DialogTitle>
|
||||
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<div className="flex flex-col gap-4">
|
||||
<AlertBlock type="info">
|
||||
Deploy is required to apply changes after creating or updating a
|
||||
domain.
|
||||
</AlertBlock>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
</div>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
@@ -191,7 +197,7 @@ export const AddDomainCompose = ({
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Service Name</FormLabel>
|
||||
<div className="flex max-lg:flex-wrap sm:flex-row gap-2">
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value || ""}
|
||||
@@ -291,7 +297,7 @@ export const AddDomainCompose = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Host</FormLabel>
|
||||
<div className="flex max-lg:flex-wrap sm:flex-row gap-2">
|
||||
<div className="flex gap-2">
|
||||
<FormControl>
|
||||
<Input placeholder="api.dokploy.com" {...field} />
|
||||
</FormControl>
|
||||
|
||||
@@ -83,9 +83,10 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className="w-full flex flex-col lg:flex-row gap-4 ">
|
||||
<div className="w-full flex flex-col gap-4 ">
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-save-compose-file"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="w-full relative space-y-4"
|
||||
>
|
||||
@@ -121,21 +122,21 @@ services:
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-between flex-col lg:flex-row gap-2">
|
||||
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
|
||||
<RandomizeCompose composeId={composeId} />
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
className="lg:w-fit w-full"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<div className="flex justify-between flex-col lg:flex-row gap-2">
|
||||
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
|
||||
<RandomizeCompose composeId={composeId} />
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
form="hook-form-save-compose-file"
|
||||
isLoading={isLoading}
|
||||
className="lg:w-fit w-full"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const BitbucketProviderSchema = z.object({
|
||||
composePath: z.string().min(1),
|
||||
repository: z
|
||||
.object({
|
||||
repo: z.string().min(1, "Repo is required"),
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||
});
|
||||
|
||||
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
||||
const { data: bitbucketProviders } =
|
||||
api.bitbucket.bitbucketProviders.useQuery();
|
||||
const { data, refetch } = api.compose.one.useQuery({ composeId });
|
||||
|
||||
const { mutateAsync, isLoading: isSavingBitbucketProvider } =
|
||||
api.compose.update.useMutation();
|
||||
|
||||
const form = useForm<BitbucketProvider>({
|
||||
defaultValues: {
|
||||
composePath: "./docker-compose.yml",
|
||||
repository: {
|
||||
owner: "",
|
||||
repo: "",
|
||||
},
|
||||
bitbucketId: "",
|
||||
branch: "",
|
||||
},
|
||||
resolver: zodResolver(BitbucketProviderSchema),
|
||||
});
|
||||
|
||||
const repository = form.watch("repository");
|
||||
const bitbucketId = form.watch("bitbucketId");
|
||||
|
||||
const {
|
||||
data: repositories,
|
||||
isLoading: isLoadingRepositories,
|
||||
error,
|
||||
isError,
|
||||
} = api.bitbucket.getBitbucketRepositories.useQuery(
|
||||
{
|
||||
bitbucketId,
|
||||
},
|
||||
{
|
||||
enabled: !!bitbucketId,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
fetchStatus,
|
||||
status,
|
||||
} = api.bitbucket.getBitbucketBranches.useQuery(
|
||||
{
|
||||
owner: repository?.owner,
|
||||
repo: repository?.repo,
|
||||
bitbucketId,
|
||||
},
|
||||
{
|
||||
enabled: !!repository?.owner && !!repository?.repo && !!bitbucketId,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
branch: data.bitbucketBranch || "",
|
||||
repository: {
|
||||
repo: data.bitbucketRepository || "",
|
||||
owner: data.bitbucketOwner || "",
|
||||
},
|
||||
composePath: data.composePath,
|
||||
bitbucketId: data.bitbucketId || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form]);
|
||||
|
||||
const onSubmit = async (data: BitbucketProvider) => {
|
||||
await mutateAsync({
|
||||
bitbucketBranch: data.branch,
|
||||
bitbucketRepository: data.repository.repo,
|
||||
bitbucketOwner: data.repository.owner,
|
||||
bitbucketId: data.bitbucketId,
|
||||
composePath: data.composePath,
|
||||
composeId,
|
||||
sourceType: "bitbucket",
|
||||
composeStatus: "idle",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Provided Saved");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to save the Bitbucket provider");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 py-3"
|
||||
>
|
||||
{error && (
|
||||
<AlertBlock type="error">Repositories: {error.message}</AlertBlock>
|
||||
)}
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bitbucketId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Bitbucket Account</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
form.setValue("repository", {
|
||||
owner: "",
|
||||
repo: "",
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a Bitbucket Account" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{bitbucketProviders?.map((bitbucketProvider) => (
|
||||
<SelectItem
|
||||
key={bitbucketProvider.bitbucketId}
|
||||
value={bitbucketProvider.bitbucketId}
|
||||
>
|
||||
{bitbucketProvider.gitProvider.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repository"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Repository</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
"w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isLoadingRepositories
|
||||
? "Loading...."
|
||||
: field.value.owner
|
||||
? repositories?.find(
|
||||
(repo) => repo.name === field.value.repo,
|
||||
)?.name
|
||||
: "Select repository"}
|
||||
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search repository..."
|
||||
className="h-9"
|
||||
/>
|
||||
{isLoadingRepositories && (
|
||||
<span className="py-6 text-center text-sm">
|
||||
Loading Repositories....
|
||||
</span>
|
||||
)}
|
||||
<CommandEmpty>No repositories found.</CommandEmpty>
|
||||
<ScrollArea className="h-96">
|
||||
<CommandGroup>
|
||||
{repositories?.map((repo) => (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
owner: repo.owner.username as string,
|
||||
repo: repo.name,
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
repo.name === field.value.repo
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{form.formState.errors.repository && (
|
||||
<p className={cn("text-sm font-medium text-destructive")}>
|
||||
Repository is required
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="branch"
|
||||
render={({ field }) => (
|
||||
<FormItem className="block w-full">
|
||||
<FormLabel>Branch</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
" w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{status === "loading" && fetchStatus === "fetching"
|
||||
? "Loading...."
|
||||
: field.value
|
||||
? branches?.find(
|
||||
(branch) => branch.name === field.value,
|
||||
)?.name
|
||||
: "Select branch"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search branch..."
|
||||
className="h-9"
|
||||
/>
|
||||
{status === "loading" && fetchStatus === "fetching" && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Loading Branches....
|
||||
</span>
|
||||
)}
|
||||
{!repository?.owner && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Select a repository
|
||||
</span>
|
||||
)}
|
||||
<ScrollArea className="h-96">
|
||||
<CommandEmpty>No branch found.</CommandEmpty>
|
||||
|
||||
<CommandGroup>
|
||||
{branches?.map((branch) => (
|
||||
<CommandItem
|
||||
value={branch.name}
|
||||
key={branch.commit.sha}
|
||||
onSelect={() => {
|
||||
form.setValue("branch", branch.name);
|
||||
}}
|
||||
>
|
||||
{branch.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
branch.name === field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
||||
<FormMessage />
|
||||
</Popover>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="composePath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Compose Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="docker-compose.yml" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
isLoading={isSavingBitbucketProvider}
|
||||
type="submit"
|
||||
className="w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -21,6 +21,13 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -39,6 +46,7 @@ const GithubProviderSchema = z.object({
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
githubId: z.string().min(1, "Github Provider is required"),
|
||||
});
|
||||
|
||||
type GithubProvider = z.infer<typeof GithubProviderSchema>;
|
||||
@@ -48,6 +56,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
const { data: githubProviders } = api.github.githubProviders.useQuery();
|
||||
const { data, refetch } = api.compose.one.useQuery({ composeId });
|
||||
|
||||
const { mutateAsync, isLoading: isSavingGithubProvider } =
|
||||
@@ -60,26 +69,38 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
owner: "",
|
||||
repo: "",
|
||||
},
|
||||
githubId: "",
|
||||
branch: "",
|
||||
},
|
||||
resolver: zodResolver(GithubProviderSchema),
|
||||
});
|
||||
|
||||
const repository = form.watch("repository");
|
||||
const githubId = form.watch("githubId");
|
||||
|
||||
const { data: repositories, isLoading: isLoadingRepositories } =
|
||||
api.admin.getRepositories.useQuery();
|
||||
api.github.getGithubRepositories.useQuery(
|
||||
{
|
||||
githubId,
|
||||
},
|
||||
{
|
||||
enabled: !!githubId,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
fetchStatus,
|
||||
status,
|
||||
} = api.admin.getBranches.useQuery(
|
||||
} = api.github.getGithubBranches.useQuery(
|
||||
{
|
||||
owner: repository?.owner,
|
||||
repo: repository?.repo,
|
||||
githubId,
|
||||
},
|
||||
{
|
||||
enabled: !!repository?.owner && !!repository?.repo && !!githubId,
|
||||
},
|
||||
{ enabled: !!repository?.owner && !!repository?.repo },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -91,19 +112,21 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
owner: data.owner || "",
|
||||
},
|
||||
composePath: data.composePath,
|
||||
githubId: data.githubId || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form]);
|
||||
|
||||
const onSubmit = async (data: GithubProvider) => {
|
||||
console.log(data);
|
||||
await mutateAsync({
|
||||
branch: data.branch,
|
||||
repository: data.repository.repo,
|
||||
composeId: composeId,
|
||||
composeId,
|
||||
owner: data.repository.owner,
|
||||
sourceType: "github",
|
||||
composePath: data.composePath,
|
||||
githubId: data.githubId,
|
||||
sourceType: "github",
|
||||
composeStatus: "idle",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Provided Saved");
|
||||
@@ -122,6 +145,45 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
className="grid w-full gap-4 py-3"
|
||||
>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="githubId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Github Account</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
form.setValue("repository", {
|
||||
owner: "",
|
||||
repo: "",
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a Github Account" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{githubProviders?.map((githubProvider) => (
|
||||
<SelectItem
|
||||
key={githubProvider.githubId}
|
||||
value={githubProvider.githubId}
|
||||
>
|
||||
{githubProvider.gitProvider.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repository"
|
||||
@@ -278,7 +340,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="composePath"
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const GitlabProviderSchema = z.object({
|
||||
composePath: z.string().min(1),
|
||||
repository: z
|
||||
.object({
|
||||
repo: z.string().min(1, "Repo is required"),
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
id: z.number().nullable(),
|
||||
gitlabPathNamespace: z.string().min(1),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||
});
|
||||
|
||||
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
||||
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
|
||||
const { data, refetch } = api.compose.one.useQuery({ composeId });
|
||||
|
||||
const { mutateAsync, isLoading: isSavingGitlabProvider } =
|
||||
api.compose.update.useMutation();
|
||||
|
||||
const form = useForm<GitlabProvider>({
|
||||
defaultValues: {
|
||||
composePath: "./docker-compose.yml",
|
||||
repository: {
|
||||
owner: "",
|
||||
repo: "",
|
||||
gitlabPathNamespace: "",
|
||||
id: null,
|
||||
},
|
||||
gitlabId: "",
|
||||
branch: "",
|
||||
},
|
||||
resolver: zodResolver(GitlabProviderSchema),
|
||||
});
|
||||
|
||||
const repository = form.watch("repository");
|
||||
const gitlabId = form.watch("gitlabId");
|
||||
|
||||
const {
|
||||
data: repositories,
|
||||
isLoading: isLoadingRepositories,
|
||||
error,
|
||||
} = api.gitlab.getGitlabRepositories.useQuery(
|
||||
{
|
||||
gitlabId,
|
||||
},
|
||||
{
|
||||
enabled: !!gitlabId,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
fetchStatus,
|
||||
status,
|
||||
} = api.gitlab.getGitlabBranches.useQuery(
|
||||
{
|
||||
owner: repository?.owner,
|
||||
repo: repository?.repo,
|
||||
id: repository?.id || 0,
|
||||
gitlabId: gitlabId,
|
||||
},
|
||||
{
|
||||
enabled: !!repository?.owner && !!repository?.repo && !!gitlabId,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
branch: data.gitlabBranch || "",
|
||||
repository: {
|
||||
repo: data.gitlabRepository || "",
|
||||
owner: data.gitlabOwner || "",
|
||||
id: data.gitlabProjectId,
|
||||
gitlabPathNamespace: data.gitlabPathNamespace || "",
|
||||
},
|
||||
composePath: data.composePath,
|
||||
gitlabId: data.gitlabId || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form]);
|
||||
|
||||
const onSubmit = async (data: GitlabProvider) => {
|
||||
await mutateAsync({
|
||||
gitlabBranch: data.branch,
|
||||
gitlabRepository: data.repository.repo,
|
||||
gitlabOwner: data.repository.owner,
|
||||
composePath: data.composePath,
|
||||
gitlabId: data.gitlabId,
|
||||
composeId,
|
||||
gitlabProjectId: data.repository.id,
|
||||
gitlabPathNamespace: data.repository.gitlabPathNamespace,
|
||||
sourceType: "gitlab",
|
||||
composeStatus: "idle",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Provided Saved");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to save the gitlab provider");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 py-3"
|
||||
>
|
||||
{error && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="gitlabId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Gitlab Account</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
form.setValue("repository", {
|
||||
owner: "",
|
||||
repo: "",
|
||||
gitlabPathNamespace: "",
|
||||
id: null,
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a Gitlab Account" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{gitlabProviders?.map((gitlabProvider) => (
|
||||
<SelectItem
|
||||
key={gitlabProvider.gitlabId}
|
||||
value={gitlabProvider.gitlabId}
|
||||
>
|
||||
{gitlabProvider.gitProvider.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repository"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2 flex flex-col">
|
||||
<FormLabel>Repository</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
"w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isLoadingRepositories
|
||||
? "Loading...."
|
||||
: field.value.owner
|
||||
? repositories?.find(
|
||||
(repo) => repo.name === field.value.repo,
|
||||
)?.name
|
||||
: "Select repository"}
|
||||
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search repository..."
|
||||
className="h-9"
|
||||
/>
|
||||
{isLoadingRepositories && (
|
||||
<span className="py-6 text-center text-sm">
|
||||
Loading Repositories....
|
||||
</span>
|
||||
)}
|
||||
<CommandEmpty>No repositories found.</CommandEmpty>
|
||||
<ScrollArea className="h-96">
|
||||
<CommandGroup>
|
||||
{repositories && repositories.length === 0 && (
|
||||
<CommandEmpty>
|
||||
No repositories found.
|
||||
</CommandEmpty>
|
||||
)}
|
||||
{repositories?.map((repo) => {
|
||||
return (
|
||||
<CommandItem
|
||||
value={repo.url}
|
||||
key={repo.url}
|
||||
onSelect={() => {
|
||||
form.setValue("repository", {
|
||||
owner: repo.owner.username as string,
|
||||
repo: repo.name,
|
||||
id: repo.id,
|
||||
gitlabPathNamespace: repo.url,
|
||||
});
|
||||
form.setValue("branch", "");
|
||||
}}
|
||||
>
|
||||
{repo.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
repo.name === field.value.repo
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{form.formState.errors.repository && (
|
||||
<p className={cn("text-sm font-medium text-destructive")}>
|
||||
Repository is required
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="branch"
|
||||
render={({ field }) => (
|
||||
<FormItem className="block w-full">
|
||||
<FormLabel>Branch</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
" w-full justify-between !bg-input",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{status === "loading" && fetchStatus === "fetching"
|
||||
? "Loading...."
|
||||
: field.value
|
||||
? branches?.find(
|
||||
(branch) => branch.name === field.value,
|
||||
)?.name
|
||||
: "Select branch"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search branch..."
|
||||
className="h-9"
|
||||
/>
|
||||
{status === "loading" && fetchStatus === "fetching" && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Loading Branches....
|
||||
</span>
|
||||
)}
|
||||
{!repository?.owner && (
|
||||
<span className="py-6 text-center text-sm text-muted-foreground">
|
||||
Select a repository
|
||||
</span>
|
||||
)}
|
||||
<ScrollArea className="h-96">
|
||||
<CommandEmpty>No branch found.</CommandEmpty>
|
||||
|
||||
<CommandGroup>
|
||||
{branches?.map((branch) => (
|
||||
<CommandItem
|
||||
value={branch.name}
|
||||
key={branch.commit.id}
|
||||
onSelect={() => {
|
||||
form.setValue("branch", branch.name);
|
||||
}}
|
||||
>
|
||||
{branch.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
branch.name === field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
||||
<FormMessage />
|
||||
</Popover>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="composePath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Compose Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="docker-compose.yml" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
isLoading={isSavingGitlabProvider}
|
||||
type="submit"
|
||||
className="w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +1,32 @@
|
||||
import {
|
||||
BitbucketIcon,
|
||||
GitIcon,
|
||||
GithubIcon,
|
||||
GitlabIcon,
|
||||
} from "@/components/icons/data-tools-icons";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { api } from "@/utils/api";
|
||||
import { GitBranch, LockIcon } from "lucide-react";
|
||||
import { CodeIcon, GitBranch, LockIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { ComposeFileEditor } from "../compose-file-editor";
|
||||
import { ShowConvertedCompose } from "../show-converted-compose";
|
||||
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
|
||||
import { SaveGitProviderCompose } from "./save-git-provider-compose";
|
||||
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
||||
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
|
||||
|
||||
type TabState = "github" | "git" | "raw";
|
||||
type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket";
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
export const ShowProviderFormCompose = ({ composeId }: Props) => {
|
||||
const { data: haveGithubConfigured } =
|
||||
api.admin.haveGithubConfigured.useQuery();
|
||||
const { data: githubProviders } = api.github.githubProviders.useQuery();
|
||||
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
|
||||
const { data: bitbucketProviders } =
|
||||
api.bitbucket.bitbucketProviders.useQuery();
|
||||
|
||||
const { data: compose } = api.compose.one.useQuery({ composeId });
|
||||
const [tab, setSab] = useState<TabState>(compose?.sourceType || "github");
|
||||
@@ -44,38 +54,97 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
|
||||
setSab(e as TabState);
|
||||
}}
|
||||
>
|
||||
<TabsList className="grid w-fit grid-cols-4 bg-transparent">
|
||||
<TabsTrigger
|
||||
value="github"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Github
|
||||
</TabsTrigger>
|
||||
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||
<TabsList className="md:grid md:w-fit md:grid-cols-5 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
|
||||
<TabsTrigger
|
||||
value="github"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<GithubIcon className="size-4 text-current fill-current" />
|
||||
Github
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="gitlab"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<GitlabIcon className="size-4 text-current fill-current" />
|
||||
Gitlab
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="bitbucket"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<BitbucketIcon className="size-4 text-current fill-current" />
|
||||
Bitbucket
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger
|
||||
value="git"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Git
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="raw"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Raw
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsTrigger
|
||||
value="git"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<GitIcon />
|
||||
Git
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="raw"
|
||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
<CodeIcon className="size-4 " />
|
||||
Raw
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<TabsContent value="github" className="w-full p-2">
|
||||
{haveGithubConfigured ? (
|
||||
{githubProviders && githubProviders?.length > 0 ? (
|
||||
<SaveGithubProviderCompose composeId={composeId} />
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<LockIcon className="size-8 text-muted-foreground" />
|
||||
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
|
||||
<GithubIcon className="size-8 text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
To deploy using GitHub, you need to configure your account
|
||||
first. Please, go to{" "}
|
||||
<Link
|
||||
href="/dashboard/settings/server"
|
||||
href="/dashboard/settings/git-providers"
|
||||
className="text-foreground"
|
||||
>
|
||||
Settings
|
||||
</Link>{" "}
|
||||
to do so.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="gitlab" className="w-full p-2">
|
||||
{gitlabProviders && gitlabProviders?.length > 0 ? (
|
||||
<SaveGitlabProviderCompose composeId={composeId} />
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
|
||||
<GitlabIcon className="size-8 text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
To deploy using GitLab, you need to configure your account
|
||||
first. Please, go to{" "}
|
||||
<Link
|
||||
href="/dashboard/settings/git-providers"
|
||||
className="text-foreground"
|
||||
>
|
||||
Settings
|
||||
</Link>{" "}
|
||||
to do so.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="bitbucket" className="w-full p-2">
|
||||
{bitbucketProviders && bitbucketProviders?.length > 0 ? (
|
||||
<SaveBitbucketProviderCompose composeId={composeId} />
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
|
||||
<BitbucketIcon className="size-8 text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
To deploy using Bitbucket, you need to configure your account
|
||||
first. Please, go to{" "}
|
||||
<Link
|
||||
href="/dashboard/settings/git-providers"
|
||||
className="text-foreground"
|
||||
>
|
||||
Settings
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -8,28 +10,94 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { Dices } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle, Dices } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
suffix: z.string(),
|
||||
randomize: z.boolean().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof schema>;
|
||||
|
||||
export const RandomizeCompose = ({ composeId }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [prefix, setPrefix] = useState<string>("");
|
||||
const [compose, setCompose] = useState<string>("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { mutateAsync, error, isError } =
|
||||
api.compose.randomizeCompose.useMutation();
|
||||
|
||||
const onSubmit = async () => {
|
||||
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
|
||||
|
||||
const { data, refetch } = api.compose.one.useQuery(
|
||||
{ composeId },
|
||||
{ enabled: !!composeId },
|
||||
);
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
suffix: "",
|
||||
randomize: false,
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
const suffix = form.watch("suffix");
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
suffix: data?.suffix || "",
|
||||
randomize: data?.randomize || false,
|
||||
});
|
||||
}
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||
|
||||
const onSubmit = async (formData: Schema) => {
|
||||
await updateCompose({
|
||||
composeId,
|
||||
suffix: formData?.suffix || "",
|
||||
randomize: formData?.randomize || false,
|
||||
})
|
||||
.then(async (data) => {
|
||||
randomizeCompose();
|
||||
refetch();
|
||||
toast.success("Compose updated");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to randomize the compose");
|
||||
});
|
||||
};
|
||||
|
||||
const randomizeCompose = async () => {
|
||||
await mutateAsync({
|
||||
composeId,
|
||||
prefix,
|
||||
suffix,
|
||||
})
|
||||
.then(async (data) => {
|
||||
await utils.project.all.invalidate();
|
||||
@@ -43,7 +111,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild onClick={() => onSubmit()}>
|
||||
<DialogTrigger asChild onClick={() => randomizeCompose()}>
|
||||
<Button className="max-lg:w-full" variant="outline">
|
||||
<Dices className="h-4 w-4" />
|
||||
Randomize Compose
|
||||
@@ -59,7 +127,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
|
||||
</DialogHeader>
|
||||
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
||||
<span>
|
||||
This will randomize the compose file and will add a prefix to the
|
||||
This will randomize the compose file and will add a suffix to the
|
||||
property to avoid conflicts
|
||||
</span>
|
||||
<ul className="list-disc list-inside">
|
||||
@@ -69,29 +137,98 @@ export const RandomizeCompose = ({ composeId }: Props) => {
|
||||
<li>configs</li>
|
||||
<li>secrets</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row gap-2">
|
||||
<Input
|
||||
placeholder="Enter a prefix (Optional, example: prod)"
|
||||
onChange={(e) => setPrefix(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={async () => {
|
||||
await onSubmit();
|
||||
}}
|
||||
className="lg:w-fit w-full"
|
||||
>
|
||||
Random
|
||||
</Button>
|
||||
<AlertBlock type="info">
|
||||
When you activate this option, we will include a env
|
||||
`COMPOSE_PREFIX` variable to the compose file so you can use it in
|
||||
your compose file.
|
||||
</AlertBlock>
|
||||
</div>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
id="hook-form-add-project"
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
{isError && (
|
||||
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
|
||||
<AlertTriangle className="text-red-600 dark:text-red-400" />
|
||||
<span className="text-sm text-red-600 dark:text-red-400">
|
||||
{error?.message}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-4 bg-secondary rounded-lg">
|
||||
<pre>
|
||||
<code className="language-yaml">{compose}</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-col gap-4 w-full ">
|
||||
<div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="suffix"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col justify-center max-sm:items-center w-full">
|
||||
<FormLabel>Suffix</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter a suffix (Optional, example: prod)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="randomize"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Apply Randomize</FormLabel>
|
||||
<FormDescription>
|
||||
Apply randomize to the compose file.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
||||
<Button
|
||||
form="hook-form-add-project"
|
||||
type="submit"
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
await randomizeCompose();
|
||||
}}
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Random
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<pre>
|
||||
<CodeEditor
|
||||
value={compose || ""}
|
||||
language="yaml"
|
||||
readOnly
|
||||
height="50rem"
|
||||
/>
|
||||
</pre>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { DockerMonitoring } from "../../monitoring/docker/show";
|
||||
|
||||
interface Props {
|
||||
@@ -42,9 +44,15 @@ export const ShowMonitoringCompose = ({
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
const [containerId, setContainerId] = useState<string | undefined>();
|
||||
|
||||
const { mutateAsync: restart, isLoading } =
|
||||
api.docker.restartContainer.useMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data?.length > 0) {
|
||||
setContainerAppName(data[0]?.name);
|
||||
setContainerId(data[0]?.containerId);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
@@ -57,24 +65,48 @@ export const ShowMonitoringCompose = ({
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<Label>Select a container to watch the monitoring</Label>
|
||||
<Select onValueChange={setContainerAppName} value={containerAppName}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a container" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{data?.map((container) => (
|
||||
<SelectItem
|
||||
key={container.containerId}
|
||||
value={container.name}
|
||||
>
|
||||
{container.name} ({container.containerId}) {container.state}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
setContainerAppName(value);
|
||||
setContainerId(
|
||||
data?.find((container) => container.name === value)
|
||||
?.containerId,
|
||||
);
|
||||
}}
|
||||
value={containerAppName}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a container" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{data?.map((container) => (
|
||||
<SelectItem
|
||||
key={container.containerId}
|
||||
value={container.name}
|
||||
>
|
||||
{container.name} ({container.containerId}){" "}
|
||||
{container.state}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
onClick={async () => {
|
||||
if (!containerId) return;
|
||||
toast.success(`Restarting container ${containerAppName}`);
|
||||
await restart({ containerId }).then(() => {
|
||||
toast.success("Container restarted");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</div>
|
||||
<DockerMonitoring
|
||||
appName={containerAppName || ""}
|
||||
appType={appType}
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
import {
|
||||
BitbucketIcon,
|
||||
GithubIcon,
|
||||
GitlabIcon,
|
||||
} from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
username: z.string().min(1, {
|
||||
message: "Username is required",
|
||||
}),
|
||||
password: z.string().min(1, {
|
||||
message: "App Password is required",
|
||||
}),
|
||||
workspaceName: z.string().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
|
||||
export const AddBitbucketProvider = () => {
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const url = useUrl();
|
||||
const { mutateAsync, error, isError } = api.bitbucket.create.useMutation();
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const router = useRouter();
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
workspaceName: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
username: "",
|
||||
password: "",
|
||||
workspaceName: "",
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
bitbucketUsername: data.username,
|
||||
appPassword: data.password,
|
||||
bitbucketWorkspaceName: data.workspaceName || "",
|
||||
authId: auth?.id || "",
|
||||
name: data.name || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
toast.success("Bitbucket configured successfully");
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error configuring Bitbucket");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center space-x-1 bg-blue-700 text-white hover:bg-blue-600"
|
||||
>
|
||||
<BitbucketIcon />
|
||||
<span>Bitbucket</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Bitbucket Provider <BitbucketIcon className="size-5" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-bitbucket"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-1"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
To integrate your Bitbucket account, you need to create a new
|
||||
App Password in your Bitbucket settings. Follow these steps:
|
||||
</p>
|
||||
<ol className="list-decimal list-inside text-sm text-muted-foreground">
|
||||
<li className="flex flex-row gap-2 items-center">
|
||||
Create new App Password{" "}
|
||||
<Link
|
||||
href="https://bitbucket.org/account/settings/app-passwords/new"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="w-fit text-primary size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
When creating the App Password, ensure you grant the
|
||||
following permissions:
|
||||
<ul className="list-disc list-inside ml-4">
|
||||
<li>Account: Read</li>
|
||||
<li>Workspace membership: Read</li>
|
||||
<li>Projects: Read</li>
|
||||
<li>Repositories: Read</li>
|
||||
<li>Pull requests: Read</li>
|
||||
<li>Webhooks: Read and write</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
After creating, you'll receive an App Password. Copy it and
|
||||
paste it below along with your Bitbucket username.
|
||||
</li>
|
||||
</ol>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Random Name eg(my-personal-account)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bitbucket Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Your Bitbucket username"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>App Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="ATBBPDYUC94nR96Nj7Cqpp4pfwKk03573DD2"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="workspaceName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Workspace Name (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="For organization accounts"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button isLoading={form.formState.isSubmitting}>
|
||||
Configure Bitbucket
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Edit } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
username: z.string().min(1, {
|
||||
message: "Username is required",
|
||||
}),
|
||||
workspaceName: z.string().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
|
||||
interface Props {
|
||||
bitbucketId: string;
|
||||
}
|
||||
|
||||
export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
|
||||
const { data: bitbucket } = api.bitbucket.one.useQuery(
|
||||
{
|
||||
bitbucketId,
|
||||
},
|
||||
{
|
||||
enabled: !!bitbucketId,
|
||||
},
|
||||
);
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { mutateAsync, error, isError } = api.bitbucket.update.useMutation();
|
||||
const { mutateAsync: testConnection, isLoading } =
|
||||
api.bitbucket.testConnection.useMutation();
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
username: "",
|
||||
workspaceName: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
|
||||
const username = form.watch("username");
|
||||
const workspaceName = form.watch("workspaceName");
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
username: bitbucket?.bitbucketUsername || "",
|
||||
workspaceName: bitbucket?.bitbucketWorkspaceName || "",
|
||||
name: bitbucket?.gitProvider.name || "",
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
bitbucketId,
|
||||
gitProviderId: bitbucket?.gitProviderId || "",
|
||||
bitbucketUsername: data.username,
|
||||
bitbucketWorkspaceName: data.workspaceName || "",
|
||||
name: data.name || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
toast.success("Bitbucket updated successfully");
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update Bitbucket");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<Edit className="size-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Update Bitbucket Provider <BitbucketIcon className="size-5" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-bitbucket"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-1"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Random Name eg(my-personal-account)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bitbucket Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Your Bitbucket username"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="workspaceName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Workspace Name (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="For organization accounts"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full justify-end gap-4 mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant={"secondary"}
|
||||
isLoading={isLoading}
|
||||
onClick={async () => {
|
||||
await testConnection({
|
||||
bitbucketId,
|
||||
bitbucketUsername: username,
|
||||
workspaceName: workspaceName,
|
||||
})
|
||||
.then(async (message) => {
|
||||
toast.info(`Message: ${message}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Test Connection
|
||||
</Button>
|
||||
<Button type="submit" isLoading={form.formState.isSubmitting}>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
import { format } from "date-fns";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const AddGithubProvider = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const url = useUrl();
|
||||
const { data } = api.auth.get.useQuery();
|
||||
const [manifest, setManifest] = useState("");
|
||||
const [isOrganization, setIsOrganization] = useState(false);
|
||||
const [organizationName, setOrganization] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const url = document.location.origin;
|
||||
const manifest = JSON.stringify(
|
||||
{
|
||||
redirect_url: `${origin}/api/providers/github/setup?authId=${data?.id}`,
|
||||
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`,
|
||||
url: origin,
|
||||
hook_attributes: {
|
||||
url: `${url}/api/deploy/github`,
|
||||
},
|
||||
callback_urls: [`${origin}/api/providers/github/setup`],
|
||||
public: false,
|
||||
request_oauth_on_install: true,
|
||||
default_permissions: {
|
||||
contents: "read",
|
||||
metadata: "read",
|
||||
emails: "read",
|
||||
pull_requests: "write",
|
||||
},
|
||||
default_events: ["pull_request", "push"],
|
||||
},
|
||||
null,
|
||||
4,
|
||||
);
|
||||
|
||||
setManifest(manifest);
|
||||
}, [data?.id]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary" className="flex items-center space-x-1">
|
||||
<GithubIcon />
|
||||
<span>Github</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-2xl ">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Github Provider <GithubIcon className="size-5" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div id="hook-form-add-project" className="grid w-full gap-1">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col ">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
To integrate your GitHub account with our services, you'll need
|
||||
to create and install a GitHub app. This process is
|
||||
straightforward and only takes a few minutes. Click the button
|
||||
below to get started.
|
||||
</p>
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
<div className="flex flex-row gap-4">
|
||||
<span>Organization?</span>
|
||||
<Switch
|
||||
checked={isOrganization}
|
||||
onCheckedChange={(checked) => setIsOrganization(checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isOrganization && (
|
||||
<Input
|
||||
required
|
||||
placeholder="Organization name"
|
||||
onChange={(e) => setOrganization(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<form
|
||||
action={
|
||||
isOrganization
|
||||
? `https://github.com/organizations/${organizationName}/settings/apps/new?state=gh_init:${data?.id}`
|
||||
: `https://github.com/settings/apps/new?state=gh_init:${data?.id}`
|
||||
}
|
||||
method="post"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
name="manifest"
|
||||
id="manifest"
|
||||
defaultValue={manifest}
|
||||
className="invisible"
|
||||
/>
|
||||
<br />
|
||||
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
disabled={isOrganization && organizationName.length < 1}
|
||||
type="submit"
|
||||
className="self-end"
|
||||
>
|
||||
Create GitHub App
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Edit } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
|
||||
interface Props {
|
||||
githubId: string;
|
||||
}
|
||||
|
||||
export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
const { data: github } = api.github.one.useQuery(
|
||||
{
|
||||
githubId,
|
||||
},
|
||||
{
|
||||
enabled: !!githubId,
|
||||
},
|
||||
);
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { mutateAsync, error, isError } = api.github.update.useMutation();
|
||||
const { mutateAsync: testConnection, isLoading } =
|
||||
api.github.testConnection.useMutation();
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
name: github?.gitProvider.name || "",
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
githubId,
|
||||
name: data.name || "",
|
||||
gitProviderId: github?.gitProviderId || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
toast.success("Github updated successfully");
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update Github");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<Edit className="size-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Update Github Provider <GithubIcon className="size-5" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-github"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-1"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Random Name eg(my-personal-account)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full justify-end gap-4 mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant={"secondary"}
|
||||
isLoading={isLoading}
|
||||
onClick={async () => {
|
||||
await testConnection({
|
||||
githubId,
|
||||
})
|
||||
.then(async (message) => {
|
||||
toast.info(`Message: ${message}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Test Connection
|
||||
</Button>
|
||||
<Button type="submit" isLoading={form.formState.isSubmitting}>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,249 @@
|
||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
applicationId: z.string().min(1, {
|
||||
message: "Application ID is required",
|
||||
}),
|
||||
applicationSecret: z.string().min(1, {
|
||||
message: "Application Secret is required",
|
||||
}),
|
||||
|
||||
redirectUri: z.string().min(1, {
|
||||
message: "Redirect URI is required",
|
||||
}),
|
||||
groupName: z.string().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
|
||||
export const AddGitlabProvider = () => {
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const url = useUrl();
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { mutateAsync, error, isError } = api.gitlab.create.useMutation();
|
||||
const webhookUrl = `${url}/api/providers/gitlab/callback`;
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
applicationId: "",
|
||||
applicationSecret: "",
|
||||
groupName: "",
|
||||
redirectUri: webhookUrl,
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
applicationId: "",
|
||||
applicationSecret: "",
|
||||
groupName: "",
|
||||
redirectUri: webhookUrl,
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
applicationId: data.applicationId || "",
|
||||
secret: data.applicationSecret || "",
|
||||
groupName: data.groupName || "",
|
||||
authId: auth?.id || "",
|
||||
name: data.name || "",
|
||||
redirectUri: data.redirectUri || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
toast.success("GitLab created successfully");
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error configuring GitLab");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="flex items-center space-x-1 bg-purple-700 text-white hover:bg-purple-600"
|
||||
>
|
||||
<GitlabIcon />
|
||||
<span>GitLab</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen ">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
GitLab Provider <GitlabIcon className="size-5" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-gitlab"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-1"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
To integrate your GitLab account, you need to create a new
|
||||
application in your GitLab settings. Follow these steps:
|
||||
</p>
|
||||
<ol className="list-decimal list-inside text-sm text-muted-foreground">
|
||||
<li className="flex flex-row gap-2 items-center">
|
||||
Go to your GitLab profile settings{" "}
|
||||
<Link
|
||||
href="https://gitlab.com/-/profile/applications"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="w-fit text-primary size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>Navigate to Applications</li>
|
||||
<li>
|
||||
Create a new application with the following details:
|
||||
<ul className="list-disc list-inside ml-4">
|
||||
<li>Name: Dokploy</li>
|
||||
<li>
|
||||
Redirect URI:{" "}
|
||||
<span className="text-primary">{webhookUrl}</span>{" "}
|
||||
</li>
|
||||
<li>Scopes: api, read_user, read_repository</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
After creating, you'll receive an Application ID and Secret,
|
||||
copy them and paste them below.
|
||||
</li>
|
||||
</ol>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Random Name eg(my-personal-account)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="redirectUri"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Redirect URI</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
disabled
|
||||
placeholder="Random Name eg(my-personal-account)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="applicationId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Application ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Application ID" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="applicationSecret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Application Secret</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Application Secret"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="groupName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Group Name (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="For organization/group access"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button isLoading={form.formState.isSubmitting}>
|
||||
Configure GitLab App
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Edit } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
groupName: z.string().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
|
||||
interface Props {
|
||||
gitlabId: string;
|
||||
}
|
||||
|
||||
export const EditGitlabProvider = ({ gitlabId }: Props) => {
|
||||
const { data: gitlab } = api.gitlab.one.useQuery(
|
||||
{
|
||||
gitlabId,
|
||||
},
|
||||
{
|
||||
enabled: !!gitlabId,
|
||||
},
|
||||
);
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { mutateAsync, error, isError } = api.gitlab.update.useMutation();
|
||||
const { mutateAsync: testConnection, isLoading } =
|
||||
api.gitlab.testConnection.useMutation();
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
groupName: "",
|
||||
name: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
|
||||
const groupName = form.watch("groupName");
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
groupName: gitlab?.groupName || "",
|
||||
name: gitlab?.gitProvider.name || "",
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
gitlabId,
|
||||
gitProviderId: gitlab?.gitProviderId || "",
|
||||
groupName: data.groupName || "",
|
||||
name: data.name || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
toast.success("Gitlab updated successfully");
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update Gitlab");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<Edit className="size-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Update GitLab Provider <GitlabIcon className="size-5" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-bitbucket"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-1"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Random Name eg(my-personal-account)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="groupName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Group Name (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="For organization/group access"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full justify-end gap-4 mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant={"secondary"}
|
||||
isLoading={isLoading}
|
||||
onClick={async () => {
|
||||
await testConnection({
|
||||
gitlabId,
|
||||
groupName: groupName || "",
|
||||
})
|
||||
.then(async (message) => {
|
||||
toast.info(`Message: ${message}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Test Connection
|
||||
</Button>
|
||||
<Button type="submit" isLoading={form.formState.isSubmitting}>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -17,31 +17,40 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { InfoIcon, TrashIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const RemoveGithubApp = () => {
|
||||
const { refetch } = api.auth.get.useQuery();
|
||||
interface Props {
|
||||
gitProviderId: string;
|
||||
gitProviderType: "github" | "gitlab" | "bitbucket";
|
||||
}
|
||||
|
||||
export const RemoveGitProvider = ({
|
||||
gitProviderId,
|
||||
gitProviderType,
|
||||
}: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const { mutateAsync } = api.admin.cleanGithubApp.useMutation();
|
||||
const { mutateAsync } = api.gitProvider.remove.useMutation();
|
||||
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive">
|
||||
Remove Current Github App
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="size-4 fill-muted-destructive text-muted-destructive" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
We recommend deleting the GitHub app first, and then removing
|
||||
the current one from here.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Button variant="ghost">
|
||||
<TrashIcon className="size-4 text-muted-destructive" />
|
||||
{gitProviderType === "github" && (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="size-4 fill-muted-destructive text-muted-destructive" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
We recommend deleting the GitHub app first, and then removing
|
||||
the current one from here.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
@@ -56,15 +65,15 @@ export const RemoveGithubApp = () => {
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync()
|
||||
await mutateAsync({
|
||||
gitProviderId: gitProviderId,
|
||||
})
|
||||
.then(async () => {
|
||||
await refetch();
|
||||
utils.admin.one.invalidate();
|
||||
await utils.admin.haveGithubConfigured.invalidate();
|
||||
toast.success("Github application deleted succesfully.");
|
||||
utils.gitProvider.getAll.invalidate();
|
||||
toast.success("Git Provider deleted succesfully.");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete your github application.");
|
||||
toast.error("Error to delete your git provider.");
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -0,0 +1,185 @@
|
||||
import {
|
||||
BitbucketIcon,
|
||||
GitIcon,
|
||||
GithubIcon,
|
||||
GitlabIcon,
|
||||
} from "@/components/icons/data-tools-icons";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
import { formatDate } from "date-fns";
|
||||
import Link from "next/link";
|
||||
import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider";
|
||||
import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider";
|
||||
import { AddGithubProvider } from "./github/add-github-provider";
|
||||
import { EditGithubProvider } from "./github/edit-github-provider";
|
||||
import { AddGitlabProvider } from "./gitlab/add-gitlab-provider";
|
||||
import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider";
|
||||
import { RemoveGitProvider } from "./remove-git-provider";
|
||||
|
||||
export const ShowGitProviders = () => {
|
||||
const { data } = api.gitProvider.getAll.useQuery();
|
||||
|
||||
const url = useUrl();
|
||||
|
||||
const getGitlabUrl = (clientId: string, gitlabId: string) => {
|
||||
const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`;
|
||||
|
||||
const scope = "api read_user read_repository";
|
||||
|
||||
const authUrl = `https://gitlab.com/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`;
|
||||
|
||||
return authUrl;
|
||||
};
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-bold">Git Providers</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Connect your Git provider for authentication.
|
||||
</p>
|
||||
</div>
|
||||
<Card className=" bg-transparent">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex gap-4 sm:flex-row flex-col w-full">
|
||||
<AddGithubProvider />
|
||||
<AddGitlabProvider />
|
||||
<AddBitbucketProvider />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="grid gap-4 sm:grid-cols-1 md:grid-cols-1">
|
||||
{data && data.length === 0 && (
|
||||
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
|
||||
<GitIcon className="size-8" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
No Git Providers found. To add a provider, create a new one such
|
||||
as GitHub, GitLab, or Bitbucket.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{data?.map((gitProvider, index) => {
|
||||
const isGithub = gitProvider.providerType === "github";
|
||||
const isGitlab = gitProvider.providerType === "gitlab";
|
||||
const isBitbucket = gitProvider.providerType === "bitbucket";
|
||||
const haveGithubRequirements =
|
||||
gitProvider.providerType === "github" &&
|
||||
gitProvider.github?.githubPrivateKey &&
|
||||
gitProvider.github?.githubAppId &&
|
||||
gitProvider.github?.githubInstallationId;
|
||||
|
||||
const haveGitlabRequirements =
|
||||
gitProvider.gitlab?.accessToken && gitProvider.gitlab?.refreshToken;
|
||||
return (
|
||||
<div
|
||||
className="space-y-4"
|
||||
key={`${gitProvider.gitProviderId}-${index}`}
|
||||
>
|
||||
<Card className="flex sm:flex-row max-sm:gap-2 flex-col justify-between items-center p-4 h-full bg-transparent">
|
||||
<div className="flex items-center space-x-4 w-full">
|
||||
{gitProvider.providerType === "github" && (
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
)}
|
||||
{gitProvider.providerType === "gitlab" && (
|
||||
<GitlabIcon className="w-6 h-6" />
|
||||
)}
|
||||
{gitProvider.providerType === "bitbucket" && (
|
||||
<BitbucketIcon className="w-6 h-6" />
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="font-medium">
|
||||
{gitProvider.providerType === "github"
|
||||
? "GitHub"
|
||||
: gitProvider.providerType === "gitlab"
|
||||
? "GitLab"
|
||||
: "Bitbucket"}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{gitProvider.name}
|
||||
</p>
|
||||
<span>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Created{" "}
|
||||
{formatDate(
|
||||
gitProvider.createdAt,
|
||||
"yyyy-MM-dd hh:mm:ss a",
|
||||
)}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex sm:gap-4 sm:flex-row flex-col">
|
||||
{!haveGithubRequirements && isGithub && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Link
|
||||
href={`${gitProvider?.github?.githubAppName}/installations/new?state=gh_setup:${gitProvider?.github.githubId}`}
|
||||
className={buttonVariants({ className: "w-fit" })}
|
||||
>
|
||||
Install
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{haveGithubRequirements && isGithub && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Link
|
||||
href={`${gitProvider?.github?.githubAppName}`}
|
||||
target="_blank"
|
||||
className={buttonVariants({
|
||||
className: "w-fit",
|
||||
variant: "secondary",
|
||||
})}
|
||||
>
|
||||
<span className="text-sm">Manage</span>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!haveGitlabRequirements && isGitlab && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Link
|
||||
href={getGitlabUrl(
|
||||
gitProvider.gitlab?.applicationId || "",
|
||||
gitProvider.gitlab?.gitlabId || "",
|
||||
)}
|
||||
target="_blank"
|
||||
className={buttonVariants({
|
||||
className: "w-fit",
|
||||
variant: "secondary",
|
||||
})}
|
||||
>
|
||||
<span className="text-sm">Install</span>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row gap-1">
|
||||
{isBitbucket && (
|
||||
<EditBitbucketProvider
|
||||
bitbucketId={gitProvider.bitbucket.bitbucketId}
|
||||
/>
|
||||
)}
|
||||
{isGitlab && haveGitlabRequirements && (
|
||||
<EditGitlabProvider
|
||||
gitlabId={gitProvider.gitlab.gitlabId}
|
||||
/>
|
||||
)}
|
||||
{isGithub && haveGithubRequirements && (
|
||||
<EditGithubProvider
|
||||
githubId={gitProvider.github.githubId}
|
||||
/>
|
||||
)}
|
||||
<RemoveGitProvider
|
||||
gitProviderId={gitProvider.gitProviderId}
|
||||
gitProviderType={gitProvider.providerType}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,166 +0,0 @@
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { format } from "date-fns";
|
||||
import { BadgeCheck } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { RemoveGithubApp } from "./remove-github-app";
|
||||
|
||||
export const GithubSetup = () => {
|
||||
const [isOrganization, setIsOrganization] = useState(false);
|
||||
const { data: haveGithubConfigured } =
|
||||
api.admin.haveGithubConfigured.useQuery();
|
||||
const [manifest, setManifest] = useState<string>("");
|
||||
const [organizationName, setOrganization] = useState<string>("");
|
||||
const { data } = api.admin.one.useQuery();
|
||||
useEffect(() => {
|
||||
const url = document.location.origin;
|
||||
const manifest = JSON.stringify(
|
||||
{
|
||||
redirect_url: `${origin}/api/redirect?authId=${data?.authId}`,
|
||||
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`,
|
||||
url: origin,
|
||||
hook_attributes: {
|
||||
url: `${url}/api/deploy/github`,
|
||||
// url: `${origin}/api/webhook`, // Aquí especificas la URL del endpoint de tu webhook
|
||||
},
|
||||
callback_urls: [`${origin}/api/redirect`], // Los URLs de callback para procesos de autenticación
|
||||
public: false,
|
||||
request_oauth_on_install: true,
|
||||
default_permissions: {
|
||||
contents: "read",
|
||||
metadata: "read",
|
||||
emails: "read",
|
||||
pull_requests: "write",
|
||||
},
|
||||
default_events: ["pull_request", "push"],
|
||||
},
|
||||
null,
|
||||
4,
|
||||
);
|
||||
|
||||
setManifest(manifest);
|
||||
}, [data?.authId]);
|
||||
return (
|
||||
<Card className="bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Configure Github </CardTitle>
|
||||
<CardDescription>
|
||||
Setup your github account to access to your repositories.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="h-full space-y-2">
|
||||
{haveGithubConfigured ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Github account configured succesfully.
|
||||
</span>
|
||||
<BadgeCheck className="size-4 text-green-700" />
|
||||
</div>
|
||||
<div className="flex items-end gap-4 flex-wrap">
|
||||
<RemoveGithubApp />
|
||||
<Link
|
||||
href={`${data?.githubAppName}`}
|
||||
target="_blank"
|
||||
className={buttonVariants({
|
||||
className: "w-fit",
|
||||
variant: "secondary",
|
||||
})}
|
||||
>
|
||||
<span className="text-sm">Manage Github App</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{data?.githubAppName ? (
|
||||
<div className="flex w-fit flex-col gap-4">
|
||||
<span className="text-muted-foreground">
|
||||
You've successfully created a github app named{" "}
|
||||
<strong>{data.githubAppName}</strong>! The next step is to
|
||||
install this app in your GitHub account.
|
||||
</span>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<Link
|
||||
href={`${
|
||||
data.githubAppName
|
||||
}/installations/new?state=gh_setup:${data?.authId}`}
|
||||
className={buttonVariants({ className: "w-fit" })}
|
||||
>
|
||||
Install Github App
|
||||
</Link>
|
||||
<RemoveGithubApp />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
To integrate your GitHub account with our services, you'll
|
||||
need to create and install a GitHub app. This process is
|
||||
straightforward and only takes a few minutes. Click the
|
||||
button below to get started.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
<div className="flex flex-row gap-4">
|
||||
<span>Organization?</span>
|
||||
<Switch
|
||||
checked={isOrganization}
|
||||
onCheckedChange={(checked) => setIsOrganization(checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isOrganization && (
|
||||
<Input
|
||||
required
|
||||
placeholder="Organization name"
|
||||
onChange={(e) => setOrganization(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<form
|
||||
action={
|
||||
isOrganization
|
||||
? `https://github.com/organizations/${organizationName}/settings/apps/new?state=gh_init:${data?.authId}`
|
||||
: `https://github.com/settings/apps/new?state=gh_init:${data?.authId}`
|
||||
}
|
||||
method="post"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
name="manifest"
|
||||
id="manifest"
|
||||
defaultValue={manifest}
|
||||
className="invisible"
|
||||
/>
|
||||
<br />
|
||||
|
||||
<Button
|
||||
disabled={isOrganization && organizationName.length < 1}
|
||||
type="submit"
|
||||
>
|
||||
Create GitHub App
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import React from "react";
|
||||
import { AppearanceForm } from "./appearance-form";
|
||||
import { ShowCertificates } from "./certificates/show-certificates";
|
||||
import { ShowDestinations } from "./destination/show-destinations";
|
||||
import { GithubSetup } from "./github/github-setup";
|
||||
import { ProfileForm } from "./profile/profile-form";
|
||||
import { ShowUsers } from "./users/show-users";
|
||||
import { WebDomain } from "./web-domain";
|
||||
import { WebServer } from "./web-server";
|
||||
|
||||
export const ShowSettings = () => {
|
||||
const { data } = api.auth.get.useQuery();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mt-6 md:grid flex flex-col gap-4 pb-20 md:grid-cols-2",
|
||||
data?.rol === "user" && "col-span-2",
|
||||
)}
|
||||
>
|
||||
<div className={cn(data?.rol === "user" && "col-span-2")}>
|
||||
<ProfileForm />
|
||||
</div>
|
||||
|
||||
{data?.rol === "admin" && (
|
||||
<>
|
||||
<GithubSetup />
|
||||
<AppearanceForm />
|
||||
<ShowDestinations />
|
||||
<ShowCertificates />
|
||||
<WebDomain />
|
||||
<WebServer />
|
||||
<ShowUsers />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -40,6 +40,7 @@ const addPermissions = z.object({
|
||||
canAccessToDocker: z.boolean().optional().default(false),
|
||||
canAccessToAPI: z.boolean().optional().default(false),
|
||||
canAccessToSSHKeys: z.boolean().optional().default(false),
|
||||
canAccessToGitProviders: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
type AddPermissions = z.infer<typeof addPermissions>;
|
||||
@@ -84,6 +85,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||
canAccessToGitProviders: data.canAccessToGitProviders,
|
||||
});
|
||||
}
|
||||
}, [form, form.formState.isSubmitSuccessful, form.reset, data]);
|
||||
@@ -101,6 +103,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||
canAccessToGitProviders: data.canAccessToGitProviders,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Permissions updated");
|
||||
@@ -293,6 +296,26 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="canAccessToGitProviders"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Access to Git Providers</FormLabel>
|
||||
<FormDescription>
|
||||
Allow to users to access to the Git Providers section
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accesedProjects"
|
||||
|
||||
@@ -60,7 +60,9 @@ export const EditTraefikEnv = ({ children }: Props) => {
|
||||
}, [form, form.reset, data]);
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync(data.env)
|
||||
await mutateAsync({
|
||||
env: data.env,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Traefik Env Updated");
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
// https://worldvectorlogo.com/downloaded/redis Ref
|
||||
@@ -155,3 +156,118 @@ export const RedisIcon = ({ className }: Props) => {
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const GitlabIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg
|
||||
aria-label="gitlab"
|
||||
height="14"
|
||||
viewBox="0 0 24 22"
|
||||
width="14"
|
||||
className={cn("fill-white text-white", className)}
|
||||
>
|
||||
<path
|
||||
d="M1.279 8.29L.044 12.294c-.117.367 0 .78.325 1.014l11.323 8.23-.009-.012-.03-.039L1.279 8.29zM22.992 13.308a.905.905 0 00.325-1.014L22.085 8.29 11.693 21.52l11.299-8.212z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M1.279 8.29l10.374 13.197.03.039.01-.006L22.085 8.29H1.28z"
|
||||
fill="currentColor"
|
||||
opacity="0.4"
|
||||
/>
|
||||
<path
|
||||
d="M15.982 8.29l-4.299 13.236-.004.011.014-.017L22.085 8.29h-6.103zM7.376 8.29H1.279l10.374 13.197L7.376 8.29z"
|
||||
fill="currentColor"
|
||||
opacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M18.582.308l-2.6 7.982h6.103L19.48.308c-.133-.41-.764-.41-.897 0zM1.279 8.29L3.88.308c.133-.41.764-.41.897 0l2.6 7.982H1.279z"
|
||||
fill="currentColor"
|
||||
opacity="0.4"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const GithubIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg
|
||||
aria-label="github"
|
||||
height="18"
|
||||
viewBox="0 0 14 14"
|
||||
width="18"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M7 .175c-3.872 0-7 3.128-7 7 0 3.084 2.013 5.71 4.79 6.65.35.066.482-.153.482-.328v-1.181c-1.947.415-2.363-.941-2.363-.941-.328-.81-.787-1.028-.787-1.028-.634-.438.044-.416.044-.416.7.044 1.071.722 1.071.722.635 1.072 1.641.766 2.035.59.066-.459.24-.765.437-.94-1.553-.175-3.193-.787-3.193-3.456 0-.766.262-1.378.721-1.881-.065-.175-.306-.897.066-1.86 0 0 .59-.197 1.925.722a6.754 6.754 0 0 1 1.75-.24c.59 0 1.203.087 1.75.24 1.335-.897 1.925-.722 1.925-.722.372.963.131 1.685.066 1.86.46.48.722 1.115.722 1.88 0 2.691-1.641 3.282-3.194 3.457.24.219.481.634.481 1.29v1.926c0 .197.131.415.481.328C11.988 12.884 14 10.259 14 7.175c0-3.872-3.128-7-7-7z"
|
||||
fill="#fff"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const BitbucketIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg height="14" viewBox="-2 -2 65 59" width="14" className={className}>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="bitbucket-:R7aq37rqjt7rrrmpjtuj7l9qjtsr:"
|
||||
x1="104.953%"
|
||||
x2="46.569%"
|
||||
y1="21.921%"
|
||||
y2="75.234%"
|
||||
>
|
||||
<stop offset="7%" stopColor="currentColor" stopOpacity=".4" />
|
||||
<stop offset="100%" stopColor="currentColor" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M59.696 18.86h-18.77l-3.15 18.39h-13L9.426 55.47a2.71 2.71 0 001.75.66h40.74a2 2 0 002-1.68l5.78-35.59z"
|
||||
fill="url(#bitbucket-:R7aq37rqjt7rrrmpjtuj7l9qjtsr:)"
|
||||
fillRule="nonzero"
|
||||
transform="translate(-.026 .82)"
|
||||
/>
|
||||
<path
|
||||
d="M2 .82a2 2 0 00-2 2.32l8.49 51.54a2.7 2.7 0 00.91 1.61 2.71 2.71 0 001.75.66l15.76-18.88H24.7l-3.47-18.39h38.44l2.7-16.53a2 2 0 00-2-2.32L2 .82z"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const DockerIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg
|
||||
height="24"
|
||||
viewBox="-.557 117.607 598.543 423.631"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g fill="#0091e2">
|
||||
<path d="m592.162 277.804c-1.664-1.37-16.642-12.597-48.815-12.597-8.321 0-16.92.822-25.24 2.191-6.102-41.898-41.327-62.162-42.714-63.257l-8.598-4.93-5.547 7.942c-6.934 10.68-12.204 22.729-15.255 35.052-5.824 23.824-2.219 46.279 9.985 65.447-14.7 8.216-38.553 10.133-43.545 10.406h-393.853c-10.262 0-18.583 8.216-18.583 18.348-.554 33.956 5.27 67.912 17.197 99.951 13.59 35.052 33.838 61.067 59.91 76.95 29.4 17.799 77.383 27.931 131.468 27.931 24.408 0 48.815-2.19 72.946-6.572 33.56-6.025 65.734-17.526 95.412-34.23a260.485 260.485 0 0 0 64.902-52.577c31.342-34.778 49.925-73.663 63.515-108.167h5.547c34.116 0 55.195-13.418 66.844-24.92 7.766-7.12 13.59-15.882 17.751-25.74l2.497-7.12z" />
|
||||
<path d="m55.193 306.83h52.698c2.497 0 4.716-1.916 4.716-4.654v-46.553c0-2.465-1.942-4.655-4.716-4.655h-52.698c-2.496 0-4.715 1.916-4.715 4.655v46.553c.277 2.738 2.219 4.655 4.715 4.655zm72.668 0h52.699c2.496 0 4.715-1.916 4.715-4.654v-46.553c0-2.465-1.942-4.655-4.715-4.655h-52.7c-2.496 0-4.715 1.916-4.715 4.655v46.553c.278 2.738 2.22 4.655 4.715 4.655m74.055 0h52.699c2.496 0 4.715-1.917 4.715-4.655v-46.553c0-2.465-1.942-4.655-4.715-4.655h-52.699c-2.496 0-4.715 1.916-4.715 4.655v46.553c0 2.738 1.942 4.655 4.715 4.655zm72.946 0h52.699c2.496 0 4.715-1.917 4.715-4.655v-46.553c0-2.465-1.942-4.655-4.715-4.655h-52.699c-2.496 0-4.715 1.916-4.715 4.655v46.553c0 2.738 2.219 4.655 4.715 4.655zm-147-66.543h52.698c2.496 0 4.715-2.19 4.715-4.655v-46.553c0-2.465-1.942-4.656-4.715-4.656h-52.699c-2.496 0-4.715 1.917-4.715 4.656v46.553c.278 2.464 2.22 4.655 4.715 4.655m74.055 0h52.699c2.496 0 4.715-2.19 4.715-4.655v-46.553c0-2.465-1.942-4.656-4.715-4.656h-52.699c-2.496 0-4.715 1.917-4.715 4.656v46.553c0 2.464 1.942 4.655 4.715 4.655m72.946 0h52.699c2.496 0 4.715-2.19 4.715-4.655v-46.553c0-2.465-2.22-4.656-4.715-4.656h-52.699c-2.496 0-4.715 1.917-4.715 4.656v46.553c0 2.464 2.219 4.655 4.715 4.655m0-66.817h52.699c2.496 0 4.715-1.917 4.715-4.655v-46.553c0-2.465-2.22-4.656-4.715-4.656h-52.699c-2.496 0-4.715 1.917-4.715 4.656v46.553c0 2.464 2.219 4.655 4.715 4.655m73.5 133.36h52.699c2.496 0 4.715-1.917 4.715-4.655v-46.553c0-2.465-1.941-4.655-4.715-4.655h-52.698c-2.497 0-4.716 1.916-4.716 4.655v46.553c.278 2.738 2.22 4.655 4.716 4.655" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const GitIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 256 256"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMinYMin meet"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M251.172 116.594L139.4 4.828c-6.433-6.437-16.873-6.437-23.314 0l-23.21 23.21 29.443 29.443c6.842-2.312 14.688-.761 20.142 4.693 5.48 5.489 7.02 13.402 4.652 20.266l28.375 28.376c6.865-2.365 14.786-.835 20.269 4.657 7.663 7.66 7.663 20.075 0 27.74-7.665 7.666-20.08 7.666-27.749 0-5.764-5.77-7.188-14.235-4.27-21.336l-26.462-26.462-.003 69.637a19.82 19.82 0 0 1 5.188 3.71c7.663 7.66 7.663 20.076 0 27.747-7.665 7.662-20.086 7.662-27.74 0-7.663-7.671-7.663-20.086 0-27.746a19.654 19.654 0 0 1 6.421-4.281V94.196a19.378 19.378 0 0 1-6.421-4.281c-5.806-5.798-7.202-14.317-4.227-21.446L81.47 39.442l-76.64 76.635c-6.44 6.443-6.44 16.884 0 23.322l111.774 111.768c6.435 6.438 16.873 6.438 23.316 0l111.251-111.249c6.438-6.44 6.438-16.887 0-23.324"
|
||||
fill="#DE4C36"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,6 +59,12 @@ export const SettingsLayout = ({ children }: Props) => {
|
||||
icon: KeyRound,
|
||||
href: "/dashboard/settings/ssh-keys",
|
||||
},
|
||||
{
|
||||
title: "Git ",
|
||||
label: "",
|
||||
icon: GitBranch,
|
||||
href: "/dashboard/settings/git-providers",
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
label: "",
|
||||
@@ -95,6 +101,16 @@ export const SettingsLayout = ({ children }: Props) => {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(user?.canAccessToGitProviders
|
||||
? [
|
||||
{
|
||||
title: "Git",
|
||||
label: "",
|
||||
icon: GitBranch,
|
||||
href: "/dashboard/settings/git-providers",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -109,6 +125,7 @@ import {
|
||||
Bell,
|
||||
Database,
|
||||
KeyIcon,
|
||||
GitBranch,
|
||||
KeyRound,
|
||||
type LucideIcon,
|
||||
Route,
|
||||
|
||||
142
apps/dokploy/drizzle/0033_white_hawkeye.sql
Normal file
142
apps/dokploy/drizzle/0033_white_hawkeye.sql
Normal file
@@ -0,0 +1,142 @@
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."gitProviderType" AS ENUM('github', 'gitlab', 'bitbucket');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
ALTER TYPE "sourceType" ADD VALUE 'gitlab';--> statement-breakpoint
|
||||
ALTER TYPE "sourceType" ADD VALUE 'bitbucket';--> statement-breakpoint
|
||||
ALTER TYPE "sourceTypeCompose" ADD VALUE 'gitlab';--> statement-breakpoint
|
||||
ALTER TYPE "sourceTypeCompose" ADD VALUE 'bitbucket';--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "git_provider" (
|
||||
"gitProviderId" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"providerType" "gitProviderType" DEFAULT 'github' NOT NULL,
|
||||
"createdAt" text NOT NULL,
|
||||
"authId" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "bitbucket" (
|
||||
"bitbucketId" text PRIMARY KEY NOT NULL,
|
||||
"bitbucketUsername" text,
|
||||
"appPassword" text,
|
||||
"bitbucketWorkspaceName" text,
|
||||
"gitProviderId" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "github" (
|
||||
"githubId" text PRIMARY KEY NOT NULL,
|
||||
"githubAppName" text,
|
||||
"githubAppId" integer,
|
||||
"githubClientId" text,
|
||||
"githubClientSecret" text,
|
||||
"githubInstallationId" text,
|
||||
"githubPrivateKey" text,
|
||||
"githubWebhookSecret" text,
|
||||
"gitProviderId" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "gitlab" (
|
||||
"gitlabId" text PRIMARY KEY NOT NULL,
|
||||
"application_id" text,
|
||||
"redirect_uri" text,
|
||||
"secret" text,
|
||||
"access_token" text,
|
||||
"refresh_token" text,
|
||||
"group_name" text,
|
||||
"expires_at" integer,
|
||||
"gitProviderId" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "gitlabProjectId" integer;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "gitlabRepository" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "gitlabOwner" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "gitlabBranch" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "gitlabBuildPath" text DEFAULT '/';--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "gitlabPathNamespace" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "bitbucketRepository" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "bitbucketOwner" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "bitbucketBranch" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "bitbucketBuildPath" text DEFAULT '/';--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "githubId" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "gitlabId" text;--> statement-breakpoint
|
||||
ALTER TABLE "application" ADD COLUMN "bitbucketId" text;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN "canAccessToGitProviders" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "gitlabProjectId" integer;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "gitlabRepository" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "gitlabOwner" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "gitlabBranch" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "gitlabPathNamespace" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "bitbucketRepository" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "bitbucketOwner" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "bitbucketBranch" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "githubId" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "gitlabId" text;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "bitbucketId" text;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "git_provider" ADD CONSTRAINT "git_provider_authId_auth_id_fk" FOREIGN KEY ("authId") REFERENCES "public"."auth"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "bitbucket" ADD CONSTRAINT "bitbucket_gitProviderId_git_provider_gitProviderId_fk" FOREIGN KEY ("gitProviderId") REFERENCES "public"."git_provider"("gitProviderId") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "github" ADD CONSTRAINT "github_gitProviderId_git_provider_gitProviderId_fk" FOREIGN KEY ("gitProviderId") REFERENCES "public"."git_provider"("gitProviderId") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "gitlab" ADD CONSTRAINT "gitlab_gitProviderId_git_provider_gitProviderId_fk" FOREIGN KEY ("gitProviderId") REFERENCES "public"."git_provider"("gitProviderId") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "application" ADD CONSTRAINT "application_githubId_github_githubId_fk" FOREIGN KEY ("githubId") REFERENCES "public"."github"("githubId") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "application" ADD CONSTRAINT "application_gitlabId_gitlab_gitlabId_fk" FOREIGN KEY ("gitlabId") REFERENCES "public"."gitlab"("gitlabId") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "application" ADD CONSTRAINT "application_bitbucketId_bitbucket_bitbucketId_fk" FOREIGN KEY ("bitbucketId") REFERENCES "public"."bitbucket"("bitbucketId") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "compose" ADD CONSTRAINT "compose_githubId_github_githubId_fk" FOREIGN KEY ("githubId") REFERENCES "public"."github"("githubId") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "compose" ADD CONSTRAINT "compose_gitlabId_gitlab_gitlabId_fk" FOREIGN KEY ("gitlabId") REFERENCES "public"."gitlab"("gitlabId") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "compose" ADD CONSTRAINT "compose_bitbucketId_bitbucket_bitbucketId_fk" FOREIGN KEY ("bitbucketId") REFERENCES "public"."bitbucket"("bitbucketId") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "githubAppId";--> statement-breakpoint
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "githubAppName";--> statement-breakpoint
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "githubClientId";--> statement-breakpoint
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "githubClientSecret";--> statement-breakpoint
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "githubInstallationId";--> statement-breakpoint
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "githubPrivateKey";--> statement-breakpoint
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "githubWebhookSecret";
|
||||
1
apps/dokploy/drizzle/0034_aspiring_secret_warriors.sql
Normal file
1
apps/dokploy/drizzle/0034_aspiring_secret_warriors.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "application" ADD COLUMN "dockerBuildStage" text;
|
||||
2
apps/dokploy/drizzle/0035_cool_gravity.sql
Normal file
2
apps/dokploy/drizzle/0035_cool_gravity.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "compose" ADD COLUMN "suffix" text DEFAULT '' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "compose" ADD COLUMN "randomize" boolean DEFAULT false NOT NULL;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "1db6392e-e3bf-424b-bbcd-0e687b2a3ff5",
|
||||
"id": "4b757666-9e18-454e-9cfa-762d03bf378f",
|
||||
"prevId": "ce8a8861-2970-4889-ac2e-3cfe60d12736",
|
||||
"version": "6",
|
||||
"dialect": "postgresql",
|
||||
@@ -137,6 +137,68 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabProjectId": {
|
||||
"name": "gitlabProjectId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabRepository": {
|
||||
"name": "gitlabRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabOwner": {
|
||||
"name": "gitlabOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabBranch": {
|
||||
"name": "gitlabBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabBuildPath": {
|
||||
"name": "gitlabBuildPath",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'/'"
|
||||
},
|
||||
"gitlabPathNamespace": {
|
||||
"name": "gitlabPathNamespace",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketRepository": {
|
||||
"name": "bitbucketRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketOwner": {
|
||||
"name": "bitbucketOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketBranch": {
|
||||
"name": "bitbucketBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketBuildPath": {
|
||||
"name": "bitbucketBuildPath",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'/'"
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
@@ -291,6 +353,24 @@
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"githubId": {
|
||||
"name": "githubId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabId": {
|
||||
"name": "gitlabId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketId": {
|
||||
"name": "bitbucketId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
@@ -333,6 +413,45 @@
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"application_githubId_github_githubId_fk": {
|
||||
"name": "application_githubId_github_githubId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "github",
|
||||
"columnsFrom": [
|
||||
"githubId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"githubId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"application_gitlabId_gitlab_gitlabId_fk": {
|
||||
"name": "application_gitlabId_gitlab_gitlabId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "gitlab",
|
||||
"columnsFrom": [
|
||||
"gitlabId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitlabId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"application_bitbucketId_bitbucket_bitbucketId_fk": {
|
||||
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "bitbucket",
|
||||
"columnsFrom": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
@@ -572,6 +691,13 @@
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"canAccessToGitProviders": {
|
||||
"name": "canAccessToGitProviders",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"canAccessToTraefikFiles": {
|
||||
"name": "canAccessToTraefikFiles",
|
||||
"type": "boolean",
|
||||
@@ -648,18 +774,6 @@
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"githubAppId": {
|
||||
"name": "githubAppId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubAppName": {
|
||||
"name": "githubAppName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"serverIp": {
|
||||
"name": "serverIp",
|
||||
"type": "text",
|
||||
@@ -680,36 +794,6 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientId": {
|
||||
"name": "githubClientId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientSecret": {
|
||||
"name": "githubClientSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubInstallationId": {
|
||||
"name": "githubInstallationId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubPrivateKey": {
|
||||
"name": "githubPrivateKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubWebhookSecret": {
|
||||
"name": "githubWebhookSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"letsEncryptEmail": {
|
||||
"name": "letsEncryptEmail",
|
||||
"type": "text",
|
||||
@@ -729,13 +813,6 @@
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"enableLogRotation": {
|
||||
"name": "enableLogRotation",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"authId": {
|
||||
"name": "authId",
|
||||
"type": "text",
|
||||
@@ -2439,6 +2516,54 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabProjectId": {
|
||||
"name": "gitlabProjectId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabRepository": {
|
||||
"name": "gitlabRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabOwner": {
|
||||
"name": "gitlabOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabBranch": {
|
||||
"name": "gitlabBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabPathNamespace": {
|
||||
"name": "gitlabPathNamespace",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketRepository": {
|
||||
"name": "bitbucketRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketOwner": {
|
||||
"name": "bitbucketOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketBranch": {
|
||||
"name": "bitbucketBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"customGitUrl": {
|
||||
"name": "customGitUrl",
|
||||
"type": "text",
|
||||
@@ -2490,6 +2615,24 @@
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"githubId": {
|
||||
"name": "githubId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabId": {
|
||||
"name": "gitlabId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketId": {
|
||||
"name": "bitbucketId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
@@ -2519,6 +2662,45 @@
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"compose_githubId_github_githubId_fk": {
|
||||
"name": "compose_githubId_github_githubId_fk",
|
||||
"tableFrom": "compose",
|
||||
"tableTo": "github",
|
||||
"columnsFrom": [
|
||||
"githubId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"githubId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"compose_gitlabId_gitlab_gitlabId_fk": {
|
||||
"name": "compose_gitlabId_gitlab_gitlabId_fk",
|
||||
"tableFrom": "compose",
|
||||
"tableTo": "gitlab",
|
||||
"columnsFrom": [
|
||||
"gitlabId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitlabId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"compose_bitbucketId_bitbucket_bitbucketId_fk": {
|
||||
"name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
|
||||
"tableFrom": "compose",
|
||||
"tableTo": "bitbucket",
|
||||
"columnsFrom": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
@@ -2926,6 +3108,272 @@
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.git_provider": {
|
||||
"name": "git_provider",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerType": {
|
||||
"name": "providerType",
|
||||
"type": "gitProviderType",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'github'"
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"authId": {
|
||||
"name": "authId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"git_provider_authId_auth_id_fk": {
|
||||
"name": "git_provider_authId_auth_id_fk",
|
||||
"tableFrom": "git_provider",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.bitbucket": {
|
||||
"name": "bitbucket",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"bitbucketId": {
|
||||
"name": "bitbucketId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"bitbucketUsername": {
|
||||
"name": "bitbucketUsername",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"appPassword": {
|
||||
"name": "appPassword",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketWorkspaceName": {
|
||||
"name": "bitbucketWorkspaceName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
|
||||
"name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
|
||||
"tableFrom": "bitbucket",
|
||||
"tableTo": "git_provider",
|
||||
"columnsFrom": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.github": {
|
||||
"name": "github",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"githubId": {
|
||||
"name": "githubId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"githubAppName": {
|
||||
"name": "githubAppName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubAppId": {
|
||||
"name": "githubAppId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientId": {
|
||||
"name": "githubClientId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientSecret": {
|
||||
"name": "githubClientSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubInstallationId": {
|
||||
"name": "githubInstallationId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubPrivateKey": {
|
||||
"name": "githubPrivateKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubWebhookSecret": {
|
||||
"name": "githubWebhookSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"github_gitProviderId_git_provider_gitProviderId_fk": {
|
||||
"name": "github_gitProviderId_git_provider_gitProviderId_fk",
|
||||
"tableFrom": "github",
|
||||
"tableTo": "git_provider",
|
||||
"columnsFrom": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.gitlab": {
|
||||
"name": "gitlab",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"gitlabId": {
|
||||
"name": "gitlabId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"application_id": {
|
||||
"name": "application_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"redirect_uri": {
|
||||
"name": "redirect_uri",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"secret": {
|
||||
"name": "secret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"group_name": {
|
||||
"name": "group_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"gitlab_gitProviderId_git_provider_gitProviderId_fk": {
|
||||
"name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
|
||||
"tableFrom": "gitlab",
|
||||
"tableTo": "git_provider",
|
||||
"columnsFrom": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
@@ -2947,6 +3395,8 @@
|
||||
"docker",
|
||||
"git",
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"drop"
|
||||
]
|
||||
},
|
||||
@@ -3047,6 +3497,8 @@
|
||||
"values": [
|
||||
"git",
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"raw"
|
||||
]
|
||||
},
|
||||
@@ -3067,6 +3519,15 @@
|
||||
"discord",
|
||||
"email"
|
||||
]
|
||||
},
|
||||
"public.gitProviderType": {
|
||||
"name": "gitProviderType",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "d9f4f668-e2a8-4a4e-bccf-67011b0a4a0d",
|
||||
"prevId": "1db6392e-e3bf-424b-bbcd-0e687b2a3ff5",
|
||||
"id": "6a7ce86e-f628-4786-ab30-e160dcdb0a39",
|
||||
"prevId": "4b757666-9e18-454e-9cfa-762d03bf378f",
|
||||
"version": "6",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -137,6 +137,68 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabProjectId": {
|
||||
"name": "gitlabProjectId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabRepository": {
|
||||
"name": "gitlabRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabOwner": {
|
||||
"name": "gitlabOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabBranch": {
|
||||
"name": "gitlabBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabBuildPath": {
|
||||
"name": "gitlabBuildPath",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'/'"
|
||||
},
|
||||
"gitlabPathNamespace": {
|
||||
"name": "gitlabPathNamespace",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketRepository": {
|
||||
"name": "bitbucketRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketOwner": {
|
||||
"name": "bitbucketOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketBranch": {
|
||||
"name": "bitbucketBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketBuildPath": {
|
||||
"name": "bitbucketBuildPath",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'/'"
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
@@ -191,6 +253,12 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"dockerBuildStage": {
|
||||
"name": "dockerBuildStage",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"dropBuildPath": {
|
||||
"name": "dropBuildPath",
|
||||
"type": "text",
|
||||
@@ -291,6 +359,24 @@
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"githubId": {
|
||||
"name": "githubId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabId": {
|
||||
"name": "gitlabId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketId": {
|
||||
"name": "bitbucketId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
@@ -333,6 +419,45 @@
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"application_githubId_github_githubId_fk": {
|
||||
"name": "application_githubId_github_githubId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "github",
|
||||
"columnsFrom": [
|
||||
"githubId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"githubId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"application_gitlabId_gitlab_gitlabId_fk": {
|
||||
"name": "application_gitlabId_gitlab_gitlabId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "gitlab",
|
||||
"columnsFrom": [
|
||||
"gitlabId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitlabId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"application_bitbucketId_bitbucket_bitbucketId_fk": {
|
||||
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "bitbucket",
|
||||
"columnsFrom": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
@@ -572,6 +697,13 @@
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"canAccessToGitProviders": {
|
||||
"name": "canAccessToGitProviders",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"canAccessToTraefikFiles": {
|
||||
"name": "canAccessToTraefikFiles",
|
||||
"type": "boolean",
|
||||
@@ -648,18 +780,6 @@
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"githubAppId": {
|
||||
"name": "githubAppId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubAppName": {
|
||||
"name": "githubAppName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"serverIp": {
|
||||
"name": "serverIp",
|
||||
"type": "text",
|
||||
@@ -680,36 +800,6 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientId": {
|
||||
"name": "githubClientId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientSecret": {
|
||||
"name": "githubClientSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubInstallationId": {
|
||||
"name": "githubInstallationId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubPrivateKey": {
|
||||
"name": "githubPrivateKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubWebhookSecret": {
|
||||
"name": "githubWebhookSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"letsEncryptEmail": {
|
||||
"name": "letsEncryptEmail",
|
||||
"type": "text",
|
||||
@@ -729,19 +819,6 @@
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"enableLogRotation": {
|
||||
"name": "enableLogRotation",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"licenseKey": {
|
||||
"name": "licenseKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"authId": {
|
||||
"name": "authId",
|
||||
"type": "text",
|
||||
@@ -2445,6 +2522,54 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabProjectId": {
|
||||
"name": "gitlabProjectId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabRepository": {
|
||||
"name": "gitlabRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabOwner": {
|
||||
"name": "gitlabOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabBranch": {
|
||||
"name": "gitlabBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabPathNamespace": {
|
||||
"name": "gitlabPathNamespace",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketRepository": {
|
||||
"name": "bitbucketRepository",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketOwner": {
|
||||
"name": "bitbucketOwner",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketBranch": {
|
||||
"name": "bitbucketBranch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"customGitUrl": {
|
||||
"name": "customGitUrl",
|
||||
"type": "text",
|
||||
@@ -2496,6 +2621,24 @@
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"githubId": {
|
||||
"name": "githubId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitlabId": {
|
||||
"name": "gitlabId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketId": {
|
||||
"name": "bitbucketId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
@@ -2525,6 +2668,45 @@
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"compose_githubId_github_githubId_fk": {
|
||||
"name": "compose_githubId_github_githubId_fk",
|
||||
"tableFrom": "compose",
|
||||
"tableTo": "github",
|
||||
"columnsFrom": [
|
||||
"githubId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"githubId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"compose_gitlabId_gitlab_gitlabId_fk": {
|
||||
"name": "compose_gitlabId_gitlab_gitlabId_fk",
|
||||
"tableFrom": "compose",
|
||||
"tableTo": "gitlab",
|
||||
"columnsFrom": [
|
||||
"gitlabId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitlabId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"compose_bitbucketId_bitbucket_bitbucketId_fk": {
|
||||
"name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
|
||||
"tableFrom": "compose",
|
||||
"tableTo": "bitbucket",
|
||||
"columnsFrom": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"bitbucketId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
@@ -2932,6 +3114,272 @@
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.git_provider": {
|
||||
"name": "git_provider",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerType": {
|
||||
"name": "providerType",
|
||||
"type": "gitProviderType",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'github'"
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"authId": {
|
||||
"name": "authId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"git_provider_authId_auth_id_fk": {
|
||||
"name": "git_provider_authId_auth_id_fk",
|
||||
"tableFrom": "git_provider",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.bitbucket": {
|
||||
"name": "bitbucket",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"bitbucketId": {
|
||||
"name": "bitbucketId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"bitbucketUsername": {
|
||||
"name": "bitbucketUsername",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"appPassword": {
|
||||
"name": "appPassword",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bitbucketWorkspaceName": {
|
||||
"name": "bitbucketWorkspaceName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
|
||||
"name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
|
||||
"tableFrom": "bitbucket",
|
||||
"tableTo": "git_provider",
|
||||
"columnsFrom": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.github": {
|
||||
"name": "github",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"githubId": {
|
||||
"name": "githubId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"githubAppName": {
|
||||
"name": "githubAppName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubAppId": {
|
||||
"name": "githubAppId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientId": {
|
||||
"name": "githubClientId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubClientSecret": {
|
||||
"name": "githubClientSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubInstallationId": {
|
||||
"name": "githubInstallationId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubPrivateKey": {
|
||||
"name": "githubPrivateKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"githubWebhookSecret": {
|
||||
"name": "githubWebhookSecret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"github_gitProviderId_git_provider_gitProviderId_fk": {
|
||||
"name": "github_gitProviderId_git_provider_gitProviderId_fk",
|
||||
"tableFrom": "github",
|
||||
"tableTo": "git_provider",
|
||||
"columnsFrom": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.gitlab": {
|
||||
"name": "gitlab",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"gitlabId": {
|
||||
"name": "gitlabId",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"application_id": {
|
||||
"name": "application_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"redirect_uri": {
|
||||
"name": "redirect_uri",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"secret": {
|
||||
"name": "secret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"group_name": {
|
||||
"name": "group_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gitProviderId": {
|
||||
"name": "gitProviderId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"gitlab_gitProviderId_git_provider_gitProviderId_fk": {
|
||||
"name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
|
||||
"tableFrom": "gitlab",
|
||||
"tableTo": "git_provider",
|
||||
"columnsFrom": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"gitProviderId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
@@ -2953,6 +3401,8 @@
|
||||
"docker",
|
||||
"git",
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"drop"
|
||||
]
|
||||
},
|
||||
@@ -3053,6 +3503,8 @@
|
||||
"values": [
|
||||
"git",
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"raw"
|
||||
]
|
||||
},
|
||||
@@ -3073,6 +3525,15 @@
|
||||
"discord",
|
||||
"email"
|
||||
]
|
||||
},
|
||||
"public.gitProviderType": {
|
||||
"name": "gitProviderType",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
|
||||
3559
apps/dokploy/drizzle/meta/0035_snapshot.json
Normal file
3559
apps/dokploy/drizzle/meta/0035_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -236,15 +236,22 @@
|
||||
{
|
||||
"idx": 33,
|
||||
"version": "6",
|
||||
"when": 1724555040199,
|
||||
"tag": "0033_sweet_black_bird",
|
||||
"when": 1725250322137,
|
||||
"tag": "0033_white_hawkeye",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 34,
|
||||
"version": "6",
|
||||
"when": 1724631497207,
|
||||
"tag": "0034_silent_slayback",
|
||||
"when": 1725256397019,
|
||||
"tag": "0034_aspiring_secret_warriors",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 35,
|
||||
"version": "6",
|
||||
"when": 1725429324584,
|
||||
"tag": "0035_cool_gravity",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -66,6 +66,18 @@ export default async function handler(
|
||||
res.status(301).json({ message: "Branch Not Match" });
|
||||
return;
|
||||
}
|
||||
} else if (sourceType === "gitlab") {
|
||||
const branchName = extractBranchName(req.headers, req.body);
|
||||
if (!branchName || branchName !== application.gitlabBranch) {
|
||||
res.status(301).json({ message: "Branch Not Match" });
|
||||
return;
|
||||
}
|
||||
} else if (sourceType === "bitbucket") {
|
||||
const branchName = extractBranchName(req.headers, req.body);
|
||||
if (!branchName || branchName !== application.bitbucketBranch) {
|
||||
res.status(301).json({ message: "Branch Not Match" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { findAdmin } from "@/server/api/services/admin";
|
||||
import { db } from "@/server/db";
|
||||
import { applications, compose } from "@/server/db/schema";
|
||||
import { applications, compose, github } from "@/server/db/schema";
|
||||
import type { DeploymentJob } from "@/server/queues/deployments-queue";
|
||||
import { myQueue } from "@/server/queues/queueSetup";
|
||||
import { Webhooks } from "@octokit/webhooks";
|
||||
@@ -19,20 +19,33 @@ export default async function handler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!admin.githubWebhookSecret) {
|
||||
res.status(200).json({ message: "Github Webhook Secret not set" });
|
||||
const signature = req.headers["x-hub-signature-256"];
|
||||
const githubBody = req.body;
|
||||
|
||||
if (!githubBody?.installation.id) {
|
||||
res.status(400).json({ message: "Github Installation not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const webhooks = new Webhooks({
|
||||
secret: admin.githubWebhookSecret,
|
||||
const githubResult = await db.query.github.findFirst({
|
||||
where: eq(github.githubInstallationId, githubBody.installation.id),
|
||||
});
|
||||
|
||||
const signature = req.headers["x-hub-signature-256"];
|
||||
const github = req.body;
|
||||
if (!githubResult) {
|
||||
res.status(400).json({ message: "Github Installation not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!githubResult.githubWebhookSecret) {
|
||||
res.status(400).json({ message: "Github Webhook Secret not set" });
|
||||
return;
|
||||
}
|
||||
const webhooks = new Webhooks({
|
||||
secret: githubResult.githubWebhookSecret,
|
||||
});
|
||||
|
||||
const verified = await webhooks.verify(
|
||||
JSON.stringify(github),
|
||||
JSON.stringify(githubBody),
|
||||
signature as string,
|
||||
);
|
||||
|
||||
@@ -52,8 +65,8 @@ export default async function handler(
|
||||
}
|
||||
|
||||
try {
|
||||
const branchName = github?.ref?.replace("refs/heads/", "");
|
||||
const repository = github?.repository?.name;
|
||||
const branchName = githubBody?.ref?.replace("refs/heads/", "");
|
||||
const repository = githubBody?.repository?.name;
|
||||
const deploymentTitle = extractCommitMessage(req.headers, req.body);
|
||||
const deploymentHash = extractHash(req.headers, req.body);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createGithub } from "@/server/api/services/github";
|
||||
import { db } from "@/server/db";
|
||||
import { admins } from "@/server/db/schema";
|
||||
import { github } from "@/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { Octokit } from "octokit";
|
||||
@@ -17,10 +18,12 @@ export default async function handler(
|
||||
) {
|
||||
const { code, state, installation_id, setup_action }: Query =
|
||||
req.query as Query;
|
||||
|
||||
if (!code) {
|
||||
return res.status(400).json({ error: "Missing code parameter" });
|
||||
}
|
||||
const [action, authId] = state?.split(":");
|
||||
const [action, value] = state?.split(":");
|
||||
// Value could be the authId or the githubProviderId
|
||||
|
||||
if (action === "gh_init") {
|
||||
const octokit = new Octokit({});
|
||||
@@ -31,27 +34,25 @@ export default async function handler(
|
||||
},
|
||||
);
|
||||
|
||||
const result = await db
|
||||
.update(admins)
|
||||
.set({
|
||||
githubAppId: data.id,
|
||||
githubAppName: data.html_url,
|
||||
githubClientId: data.client_id,
|
||||
githubClientSecret: data.client_secret,
|
||||
githubWebhookSecret: data.webhook_secret,
|
||||
githubPrivateKey: data.pem,
|
||||
})
|
||||
.where(eq(admins.authId, authId as string))
|
||||
.returning();
|
||||
await createGithub({
|
||||
name: data.name,
|
||||
githubAppName: data.html_url,
|
||||
githubAppId: data.id,
|
||||
githubClientId: data.client_id,
|
||||
githubClientSecret: data.client_secret,
|
||||
githubWebhookSecret: data.webhook_secret,
|
||||
githubPrivateKey: data.pem,
|
||||
authId: value as string,
|
||||
});
|
||||
} else if (action === "gh_setup") {
|
||||
await db
|
||||
.update(admins)
|
||||
.update(github)
|
||||
.set({
|
||||
githubInstallationId: installation_id,
|
||||
})
|
||||
.where(eq(admins.authId, authId as string))
|
||||
.where(eq(github.githubId, value as string))
|
||||
.returning();
|
||||
}
|
||||
|
||||
res.redirect(307, "/dashboard/settings/server");
|
||||
res.redirect(307, "/dashboard/settings/git-providers");
|
||||
}
|
||||
@@ -8,9 +8,9 @@ export default async function handler(
|
||||
const xGitHubEvent = req.headers["x-github-event"];
|
||||
|
||||
if (xGitHubEvent === "ping") {
|
||||
res.redirect(307, "/dashboard/settings");
|
||||
res.redirect(307, "/dashboard/settings/git-providers");
|
||||
} else {
|
||||
res.redirect(307, "/dashboard/settings");
|
||||
res.redirect(307, "/dashboard/settings/git-providers");
|
||||
}
|
||||
} else {
|
||||
res.setHeader("Allow", ["POST"]);
|
||||
44
apps/dokploy/pages/api/providers/gitlab/callback.ts
Normal file
44
apps/dokploy/pages/api/providers/gitlab/callback.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { findGitlabById, updateGitlab } from "@/server/api/services/gitlab";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const { code, gitlabId } = req.query;
|
||||
|
||||
if (!code || Array.isArray(code)) {
|
||||
return res.status(400).json({ error: "Missing or invalid code" });
|
||||
}
|
||||
|
||||
const gitlab = await findGitlabById(gitlabId as string);
|
||||
|
||||
const response = await fetch("https://gitlab.com/oauth/token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: gitlab.applicationId as string,
|
||||
client_secret: gitlab.secret as string,
|
||||
code: code as string,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: `${gitlab.redirectUri}?gitlabId=${gitlabId}`,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.access_token || !result.refresh_token) {
|
||||
return res.status(400).json({ error: "Missing or invalid code" });
|
||||
}
|
||||
|
||||
const expiresAt = Math.floor(Date.now() / 1000) + result.expires_in;
|
||||
await updateGitlab(gitlab.gitlabId, {
|
||||
accessToken: result.access_token,
|
||||
refreshToken: result.refresh_token,
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
return res.redirect(307, "/dashboard/settings/git-providers");
|
||||
}
|
||||
81
apps/dokploy/pages/dashboard/settings/git-providers.tsx
Normal file
81
apps/dokploy/pages/dashboard/settings/git-providers.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { ShowGitProviders } from "@/components/dashboard/settings/git/show-git-providers";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@/server/auth/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<ShowGitProviders />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
Page.getLayout = (page: ReactElement) => {
|
||||
return (
|
||||
<DashboardLayout tab={"settings"}>
|
||||
<SettingsLayout>{page}</SettingsLayout>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
const { req, res } = ctx;
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
ctx: {
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
|
||||
try {
|
||||
await helpers.project.all.prefetch();
|
||||
const auth = await helpers.auth.get.fetch();
|
||||
|
||||
if (auth.rol === "user") {
|
||||
const user = await helpers.user.byAuthId.fetch({
|
||||
authId: auth.id,
|
||||
});
|
||||
|
||||
if (!user.canAccessToGitProviders) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { GithubSetup } from "@/components/dashboard/settings/github/github-setup";
|
||||
import { WebDomain } from "@/components/dashboard/settings/web-domain";
|
||||
import { WebServer } from "@/components/dashboard/settings/web-server";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
@@ -11,7 +10,6 @@ const Page = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<WebDomain />
|
||||
<GithubSetup />
|
||||
<WebServer />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createTRPCRouter } from "../api/trpc";
|
||||
import { adminRouter } from "./routers/admin";
|
||||
import { applicationRouter } from "./routers/application";
|
||||
import { backupRouter } from "./routers/backup";
|
||||
import { bitbucketRouter } from "./routers/bitbucket";
|
||||
import { certificateRouter } from "./routers/certificate";
|
||||
import { clusterRouter } from "./routers/cluster";
|
||||
import { composeRouter } from "./routers/compose";
|
||||
@@ -10,6 +11,9 @@ import { deploymentRouter } from "./routers/deployment";
|
||||
import { destinationRouter } from "./routers/destination";
|
||||
import { dockerRouter } from "./routers/docker";
|
||||
import { domainRouter } from "./routers/domain";
|
||||
import { gitProviderRouter } from "./routers/git-provider";
|
||||
import { githubRouter } from "./routers/github";
|
||||
import { gitlabRouter } from "./routers/gitlab";
|
||||
import { mariadbRouter } from "./routers/mariadb";
|
||||
import { mongoRouter } from "./routers/mongo";
|
||||
import { mountRouter } from "./routers/mount";
|
||||
@@ -60,6 +64,10 @@ export const appRouter = createTRPCRouter({
|
||||
notification: notificationRouter,
|
||||
sshKey: sshRouter,
|
||||
license: licenseRouter,
|
||||
gitProvider: gitProviderRouter,
|
||||
bitbucket: bitbucketRouter,
|
||||
gitlab: gitlabRouter,
|
||||
github: githubRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -3,28 +3,18 @@ import {
|
||||
apiAssignPermissions,
|
||||
apiCreateUserInvitation,
|
||||
apiFindOneToken,
|
||||
apiGetBranches,
|
||||
apiRemoveUser,
|
||||
users,
|
||||
} from "@/server/db/schema";
|
||||
import { haveGithubRequirements } from "@/server/utils/providers/github";
|
||||
import { createAppAuth } from "@octokit/auth-app";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Octokit } from "octokit";
|
||||
import {
|
||||
createInvitation,
|
||||
findAdmin,
|
||||
getUserByToken,
|
||||
removeUserByAuthId,
|
||||
updateAdmin,
|
||||
} from "../services/admin";
|
||||
import {
|
||||
adminProcedure,
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
publicProcedure,
|
||||
} from "../trpc";
|
||||
import { adminProcedure, createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
export const adminRouter = createTRPCRouter({
|
||||
one: adminProcedure.query(async () => {
|
||||
@@ -83,91 +73,4 @@ export const adminRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
cleanGithubApp: adminProcedure.mutation(async ({ ctx }) => {
|
||||
try {
|
||||
return await updateAdmin(ctx.user.authId, {
|
||||
githubAppName: "",
|
||||
githubClientId: "",
|
||||
githubClientSecret: "",
|
||||
githubInstallationId: "",
|
||||
});
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to delete this github app",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
getRepositories: protectedProcedure.query(async () => {
|
||||
const admin = await findAdmin();
|
||||
|
||||
const completeRequirements = haveGithubRequirements(admin);
|
||||
|
||||
if (!completeRequirements) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Admin need to setup correctly github account",
|
||||
});
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
authStrategy: createAppAuth,
|
||||
auth: {
|
||||
appId: admin.githubAppId,
|
||||
privateKey: admin.githubPrivateKey,
|
||||
installationId: admin.githubInstallationId,
|
||||
},
|
||||
});
|
||||
|
||||
const repositories = (await octokit.paginate(
|
||||
octokit.rest.apps.listReposAccessibleToInstallation,
|
||||
)) as unknown as Awaited<
|
||||
ReturnType<typeof octokit.rest.apps.listReposAccessibleToInstallation>
|
||||
>["data"]["repositories"];
|
||||
|
||||
return repositories;
|
||||
}),
|
||||
getBranches: protectedProcedure
|
||||
.input(apiGetBranches)
|
||||
.query(async ({ input }) => {
|
||||
const admin = await findAdmin();
|
||||
|
||||
const completeRequirements = haveGithubRequirements(admin);
|
||||
|
||||
if (!completeRequirements) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Admin need to setup correctly github account",
|
||||
});
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
authStrategy: createAppAuth,
|
||||
auth: {
|
||||
appId: admin.githubAppId,
|
||||
privateKey: admin.githubPrivateKey,
|
||||
installationId: admin.githubInstallationId,
|
||||
},
|
||||
});
|
||||
|
||||
const branches = (await octokit.paginate(
|
||||
octokit.rest.repos.listBranches,
|
||||
{
|
||||
owner: input.owner,
|
||||
repo: input.repo,
|
||||
},
|
||||
)) as unknown as Awaited<
|
||||
ReturnType<typeof octokit.rest.repos.listBranches>
|
||||
>["data"];
|
||||
|
||||
return branches;
|
||||
}),
|
||||
haveGithubConfigured: protectedProcedure.query(async () => {
|
||||
const adminResponse = await findAdmin();
|
||||
|
||||
return haveGithubRequirements(adminResponse);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -9,11 +9,13 @@ import {
|
||||
apiFindMonitoringStats,
|
||||
apiFindOneApplication,
|
||||
apiReloadApplication,
|
||||
apiSaveBitbucketProvider,
|
||||
apiSaveBuildType,
|
||||
apiSaveDockerProvider,
|
||||
apiSaveEnvironmentVariables,
|
||||
apiSaveGitProvider,
|
||||
apiSaveGithubProvider,
|
||||
apiSaveGitlabProvider,
|
||||
apiUpdateApplication,
|
||||
applications,
|
||||
} from "@/server/db/schema/application";
|
||||
@@ -193,6 +195,7 @@ export const applicationRouter = createTRPCRouter({
|
||||
dockerfile: input.dockerfile,
|
||||
publishDirectory: input.publishDirectory,
|
||||
dockerContextPath: input.dockerContextPath,
|
||||
dockerBuildStage: input.dockerBuildStage,
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -207,6 +210,39 @@ export const applicationRouter = createTRPCRouter({
|
||||
owner: input.owner,
|
||||
buildPath: input.buildPath,
|
||||
applicationStatus: "idle",
|
||||
githubId: input.githubId,
|
||||
});
|
||||
|
||||
return true;
|
||||
}),
|
||||
saveGitlabProvider: protectedProcedure
|
||||
.input(apiSaveGitlabProvider)
|
||||
.mutation(async ({ input }) => {
|
||||
await updateApplication(input.applicationId, {
|
||||
gitlabRepository: input.gitlabRepository,
|
||||
gitlabOwner: input.gitlabOwner,
|
||||
gitlabBranch: input.gitlabBranch,
|
||||
gitlabBuildPath: input.gitlabBuildPath,
|
||||
sourceType: "gitlab",
|
||||
applicationStatus: "idle",
|
||||
gitlabId: input.gitlabId,
|
||||
gitlabProjectId: input.gitlabProjectId,
|
||||
gitlabPathNamespace: input.gitlabPathNamespace,
|
||||
});
|
||||
|
||||
return true;
|
||||
}),
|
||||
saveBitbucketProvider: protectedProcedure
|
||||
.input(apiSaveBitbucketProvider)
|
||||
.mutation(async ({ input }) => {
|
||||
await updateApplication(input.applicationId, {
|
||||
bitbucketRepository: input.bitbucketRepository,
|
||||
bitbucketOwner: input.bitbucketOwner,
|
||||
bitbucketBranch: input.bitbucketBranch,
|
||||
bitbucketBuildPath: input.bitbucketBuildPath,
|
||||
sourceType: "bitbucket",
|
||||
applicationStatus: "idle",
|
||||
bitbucketId: input.bitbucketId,
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import { db } from "../../db";
|
||||
import { getUserByToken } from "../services/admin";
|
||||
import {
|
||||
createAdmin,
|
||||
createUser,
|
||||
@@ -61,6 +62,13 @@ export const authRouter = createTRPCRouter({
|
||||
.input(apiCreateUser)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const token = await getUserByToken(input.token);
|
||||
if (token.isExpired) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Invalid token",
|
||||
});
|
||||
}
|
||||
const newUser = await createUser(input);
|
||||
const session = await lucia.createSession(newUser?.authId || "", {});
|
||||
ctx.res.appendHeader(
|
||||
|
||||
82
apps/dokploy/server/api/routers/bitbucket.ts
Normal file
82
apps/dokploy/server/api/routers/bitbucket.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
apiBitbucketTestConnection,
|
||||
apiCreateBitbucket,
|
||||
apiFindBitbucketBranches,
|
||||
apiFindOneBitbucket,
|
||||
apiUpdateBitbucket,
|
||||
} from "@/server/db/schema";
|
||||
import {
|
||||
getBitbucketBranches,
|
||||
getBitbucketRepositories,
|
||||
testBitbucketConnection,
|
||||
} from "@/server/utils/providers/bitbucket";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import {
|
||||
createBitbucket,
|
||||
findBitbucketById,
|
||||
updateBitbucket,
|
||||
} from "../services/bitbucket";
|
||||
|
||||
export const bitbucketRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateBitbucket)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
return await createBitbucket(input);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to create this bitbucket provider",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
one: protectedProcedure
|
||||
.input(apiFindOneBitbucket)
|
||||
.query(async ({ input }) => {
|
||||
return await findBitbucketById(input.bitbucketId);
|
||||
}),
|
||||
bitbucketProviders: protectedProcedure.query(async () => {
|
||||
const result = await db.query.bitbucket.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
},
|
||||
columns: {
|
||||
bitbucketId: true,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}),
|
||||
|
||||
getBitbucketRepositories: protectedProcedure
|
||||
.input(apiFindOneBitbucket)
|
||||
.query(async ({ input }) => {
|
||||
return await getBitbucketRepositories(input.bitbucketId);
|
||||
}),
|
||||
getBitbucketBranches: protectedProcedure
|
||||
.input(apiFindBitbucketBranches)
|
||||
.query(async ({ input }) => {
|
||||
return await getBitbucketBranches(input);
|
||||
}),
|
||||
testConnection: protectedProcedure
|
||||
.input(apiBitbucketTestConnection)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const result = await testBitbucketConnection(input);
|
||||
|
||||
return `Found ${result} repositories`;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: error instanceof Error ? error?.message : `Error: ${error}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(apiUpdateBitbucket)
|
||||
.mutation(async ({ input }) => {
|
||||
return await updateBitbucket(input.bitbucketId, input);
|
||||
}),
|
||||
});
|
||||
@@ -15,7 +15,10 @@ import {
|
||||
} from "@/server/queues/deployments-queue";
|
||||
import { myQueue } from "@/server/queues/queueSetup";
|
||||
import { createCommand } from "@/server/utils/builders/compose";
|
||||
import { randomizeComposeFile } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
randomizeComposeFile,
|
||||
randomizeSpecificationFile,
|
||||
} from "@/server/utils/docker/compose";
|
||||
import { addDomainToCompose, cloneCompose } from "@/server/utils/docker/domain";
|
||||
import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
|
||||
import { templates } from "@/templates/templates";
|
||||
@@ -141,7 +144,7 @@ export const composeRouter = createTRPCRouter({
|
||||
randomizeCompose: protectedProcedure
|
||||
.input(apiRandomizeCompose)
|
||||
.mutation(async ({ input }) => {
|
||||
return await randomizeComposeFile(input.composeId, input.prefix);
|
||||
return await randomizeComposeFile(input.composeId, input.suffix);
|
||||
}),
|
||||
getConvertedCompose: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
@@ -150,6 +153,7 @@ export const composeRouter = createTRPCRouter({
|
||||
const domains = await findDomainsByComposeId(input.composeId);
|
||||
|
||||
const composeFile = await addDomainToCompose(compose, domains);
|
||||
|
||||
return dump(composeFile, {
|
||||
lineWidth: 1000,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
containerRestart,
|
||||
getConfig,
|
||||
getContainers,
|
||||
getContainersByAppLabel,
|
||||
@@ -12,6 +13,16 @@ export const dockerRouter = createTRPCRouter({
|
||||
return await getContainers();
|
||||
}),
|
||||
|
||||
restartContainer: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
containerId: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await containerRestart(input.containerId);
|
||||
}),
|
||||
|
||||
getConfig: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
||||
31
apps/dokploy/server/api/routers/git-provider.ts
Normal file
31
apps/dokploy/server/api/routers/git-provider.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { asc, desc } from "drizzle-orm";
|
||||
import { removeGitProvider } from "../services/git-provider";
|
||||
|
||||
export const gitProviderRouter = createTRPCRouter({
|
||||
getAll: protectedProcedure.query(async () => {
|
||||
return await db.query.gitProvider.findMany({
|
||||
with: {
|
||||
gitlab: true,
|
||||
bitbucket: true,
|
||||
github: true,
|
||||
},
|
||||
orderBy: desc(gitProvider.createdAt),
|
||||
});
|
||||
}),
|
||||
remove: protectedProcedure
|
||||
.input(apiRemoveGitProvider)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
return await removeGitProvider(input.gitProviderId);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to delete this git provider",
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
71
apps/dokploy/server/api/routers/github.ts
Normal file
71
apps/dokploy/server/api/routers/github.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
apiFindGithubBranches,
|
||||
apiFindOneGithub,
|
||||
apiUpdateGithub,
|
||||
} from "@/server/db/schema";
|
||||
import {
|
||||
getGithubBranches,
|
||||
getGithubRepositories,
|
||||
} from "@/server/utils/providers/github";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { updateGitProvider } from "../services/git-provider";
|
||||
import { findGithubById, haveGithubRequirements } from "../services/github";
|
||||
|
||||
export const githubRouter = createTRPCRouter({
|
||||
one: protectedProcedure.input(apiFindOneGithub).query(async ({ input }) => {
|
||||
return await findGithubById(input.githubId);
|
||||
}),
|
||||
getGithubRepositories: protectedProcedure
|
||||
.input(apiFindOneGithub)
|
||||
.query(async ({ input }) => {
|
||||
return await getGithubRepositories(input.githubId);
|
||||
}),
|
||||
getGithubBranches: protectedProcedure
|
||||
.input(apiFindGithubBranches)
|
||||
.query(async ({ input }) => {
|
||||
return await getGithubBranches(input);
|
||||
}),
|
||||
githubProviders: protectedProcedure.query(async () => {
|
||||
const result = await db.query.github.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
},
|
||||
});
|
||||
|
||||
const filtered = result
|
||||
.filter((provider) => haveGithubRequirements(provider))
|
||||
.map((provider) => {
|
||||
return {
|
||||
githubId: provider.githubId,
|
||||
gitProvider: {
|
||||
...provider.gitProvider,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}),
|
||||
|
||||
testConnection: protectedProcedure
|
||||
.input(apiFindOneGithub)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const result = await getGithubRepositories(input.githubId);
|
||||
return `Found ${result.length} repositories`;
|
||||
} catch (err) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: err instanceof Error ? err?.message : `Error: ${err}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(apiUpdateGithub)
|
||||
.mutation(async ({ input }) => {
|
||||
await updateGitProvider(input.gitProviderId, {
|
||||
name: input.name,
|
||||
});
|
||||
}),
|
||||
});
|
||||
93
apps/dokploy/server/api/routers/gitlab.ts
Normal file
93
apps/dokploy/server/api/routers/gitlab.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import {
|
||||
apiCreateGitlab,
|
||||
apiFindGitlabBranches,
|
||||
apiFindOneGitlab,
|
||||
apiGitlabTestConnection,
|
||||
apiUpdateGitlab,
|
||||
} from "@/server/db/schema";
|
||||
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
getGitlabBranches,
|
||||
getGitlabRepositories,
|
||||
haveGitlabRequirements,
|
||||
testGitlabConnection,
|
||||
} from "@/server/utils/providers/gitlab";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { updateGitProvider } from "../services/git-provider";
|
||||
import { createGitlab, findGitlabById, updateGitlab } from "../services/gitlab";
|
||||
|
||||
export const gitlabRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateGitlab)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
return await createGitlab(input);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to create this gitlab provider",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
one: protectedProcedure.input(apiFindOneGitlab).query(async ({ input }) => {
|
||||
return await findGitlabById(input.gitlabId);
|
||||
}),
|
||||
gitlabProviders: protectedProcedure.query(async () => {
|
||||
const result = await db.query.gitlab.findMany({
|
||||
with: {
|
||||
gitProvider: true,
|
||||
},
|
||||
});
|
||||
const filtered = result
|
||||
.filter((provider) => haveGitlabRequirements(provider))
|
||||
.map((provider) => {
|
||||
return {
|
||||
gitlabId: provider.gitlabId,
|
||||
gitProvider: {
|
||||
...provider.gitProvider,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}),
|
||||
getGitlabRepositories: protectedProcedure
|
||||
.input(apiFindOneGitlab)
|
||||
.query(async ({ input }) => {
|
||||
return await getGitlabRepositories(input.gitlabId);
|
||||
}),
|
||||
|
||||
getGitlabBranches: protectedProcedure
|
||||
.input(apiFindGitlabBranches)
|
||||
.query(async ({ input }) => {
|
||||
return await getGitlabBranches(input);
|
||||
}),
|
||||
testConnection: protectedProcedure
|
||||
.input(apiGitlabTestConnection)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const result = await testGitlabConnection(input);
|
||||
|
||||
return `Found ${result} repositories`;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: error instanceof Error ? error?.message : `Error: ${error}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(apiUpdateGitlab)
|
||||
.mutation(async ({ input }) => {
|
||||
if (input.name) {
|
||||
await updateGitProvider(input.gitProviderId, {
|
||||
name: input.name,
|
||||
});
|
||||
} else {
|
||||
await updateGitlab(input.gitlabId, input);
|
||||
}
|
||||
}),
|
||||
});
|
||||
@@ -305,6 +305,10 @@ export const settingsRouter = createTRPCRouter({
|
||||
"mongo",
|
||||
"mariadb",
|
||||
"sshRouter",
|
||||
"gitProvider",
|
||||
"bitbucket",
|
||||
"github",
|
||||
"gitlab",
|
||||
],
|
||||
});
|
||||
|
||||
@@ -326,9 +330,9 @@ export const settingsRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
writeTraefikEnv: adminProcedure
|
||||
.input(z.string())
|
||||
.input(z.object({ env: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const envs = prepareEnvironmentVariables(input);
|
||||
const envs = prepareEnvironmentVariables(input.env);
|
||||
await initializeTraefik({
|
||||
env: envs,
|
||||
});
|
||||
|
||||
@@ -129,13 +129,9 @@ export const getUserByToken = async (token: string) => {
|
||||
message: "Invitation not found",
|
||||
});
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const isExpired = isAfter(now, new Date(user.expirationDate));
|
||||
|
||||
return {
|
||||
...user,
|
||||
isExpired,
|
||||
isExpired: user.isRegistered,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import { createDeployment, updateDeploymentStatus } from "./deployment";
|
||||
|
||||
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
|
||||
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
|
||||
import { cloneBitbucketRepository } from "@/server/utils/providers/bitbucket";
|
||||
import { cloneGitlabRepository } from "@/server/utils/providers/gitlab";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
export type Application = typeof applications.$inferSelect;
|
||||
|
||||
@@ -81,6 +83,9 @@ export const findApplicationById = async (applicationId: string) => {
|
||||
security: true,
|
||||
ports: true,
|
||||
registry: true,
|
||||
gitlab: true,
|
||||
github: true,
|
||||
bitbucket: true,
|
||||
},
|
||||
});
|
||||
if (!application) {
|
||||
@@ -141,7 +146,6 @@ export const deployApplication = async ({
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
|
||||
const admin = await findAdmin();
|
||||
const deployment = await createDeployment({
|
||||
applicationId: applicationId,
|
||||
title: titleLog,
|
||||
@@ -150,7 +154,13 @@ export const deployApplication = async ({
|
||||
|
||||
try {
|
||||
if (application.sourceType === "github") {
|
||||
await cloneGithubRepository(admin, application, deployment.logPath);
|
||||
await cloneGithubRepository(application, deployment.logPath);
|
||||
await buildApplication(application, deployment.logPath);
|
||||
} else if (application.sourceType === "gitlab") {
|
||||
await cloneGitlabRepository(application, deployment.logPath);
|
||||
await buildApplication(application, deployment.logPath);
|
||||
} else if (application.sourceType === "bitbucket") {
|
||||
await cloneBitbucketRepository(application, deployment.logPath);
|
||||
await buildApplication(application, deployment.logPath);
|
||||
} else if (application.sourceType === "docker") {
|
||||
await buildDocker(application, deployment.logPath);
|
||||
@@ -214,6 +224,10 @@ export const rebuildApplication = async ({
|
||||
try {
|
||||
if (application.sourceType === "github") {
|
||||
await buildApplication(application, deployment.logPath);
|
||||
} else if (application.sourceType === "gitlab") {
|
||||
await buildApplication(application, deployment.logPath);
|
||||
} else if (application.sourceType === "bitbucket") {
|
||||
await buildApplication(application, deployment.logPath);
|
||||
} else if (application.sourceType === "docker") {
|
||||
await buildDocker(application, deployment.logPath);
|
||||
} else if (application.sourceType === "git") {
|
||||
|
||||
@@ -72,7 +72,7 @@ export const createUser = async (input: typeof apiCreateUser._type) => {
|
||||
.update(users)
|
||||
.set({
|
||||
isRegistered: true,
|
||||
expirationDate: new Date().toISOString(),
|
||||
expirationDate: undefined,
|
||||
})
|
||||
.where(eq(users.token, input.token))
|
||||
.returning()
|
||||
|
||||
88
apps/dokploy/server/api/services/bitbucket.ts
Normal file
88
apps/dokploy/server/api/services/bitbucket.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
type apiCreateBitbucket,
|
||||
type apiUpdateBitbucket,
|
||||
bitbucket,
|
||||
gitProvider,
|
||||
} from "@/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type Bitbucket = typeof bitbucket.$inferSelect;
|
||||
|
||||
export const createBitbucket = async (
|
||||
input: typeof apiCreateBitbucket._type,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
.insert(gitProvider)
|
||||
.values({
|
||||
providerType: "bitbucket",
|
||||
authId: input.authId,
|
||||
name: input.name,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
|
||||
if (!newGitProvider) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to create the git provider",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.insert(bitbucket)
|
||||
.values({
|
||||
...input,
|
||||
gitProviderId: newGitProvider?.gitProviderId,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
});
|
||||
};
|
||||
|
||||
export const findBitbucketById = async (bitbucketId: string) => {
|
||||
const bitbucketProviderResult = await db.query.bitbucket.findFirst({
|
||||
where: eq(bitbucket.bitbucketId, bitbucketId),
|
||||
with: {
|
||||
gitProvider: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!bitbucketProviderResult) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Bitbucket Provider not found",
|
||||
});
|
||||
}
|
||||
|
||||
return bitbucketProviderResult;
|
||||
};
|
||||
|
||||
export const updateBitbucket = async (
|
||||
bitbucketId: string,
|
||||
input: typeof apiUpdateBitbucket._type,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const result = await tx
|
||||
.update(bitbucket)
|
||||
.set({
|
||||
...input,
|
||||
})
|
||||
.where(eq(bitbucket.bitbucketId, bitbucketId))
|
||||
.returning();
|
||||
|
||||
if (input.name) {
|
||||
await tx
|
||||
.update(gitProvider)
|
||||
.set({
|
||||
name: input.name,
|
||||
})
|
||||
.where(eq(gitProvider.gitProviderId, input.gitProviderId))
|
||||
.returning();
|
||||
}
|
||||
|
||||
return result[0];
|
||||
});
|
||||
};
|
||||
@@ -4,18 +4,20 @@ import { db } from "@/server/db";
|
||||
import { type apiCreateCompose, compose } from "@/server/db/schema";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
import { buildCompose } from "@/server/utils/builders/compose";
|
||||
import { randomizeSpecificationFile } from "@/server/utils/docker/compose";
|
||||
import { cloneCompose, loadDockerCompose } from "@/server/utils/docker/domain";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
|
||||
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
|
||||
import { execAsync } from "@/server/utils/process/execAsync";
|
||||
import { cloneBitbucketRepository } from "@/server/utils/providers/bitbucket";
|
||||
import { cloneGitRepository } from "@/server/utils/providers/git";
|
||||
import { cloneGithubRepository } from "@/server/utils/providers/github";
|
||||
import { cloneGitlabRepository } from "@/server/utils/providers/gitlab";
|
||||
import { createComposeFile } from "@/server/utils/providers/raw";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { findAdmin, getDokployUrl } from "./admin";
|
||||
import { getDokployUrl } from "./admin";
|
||||
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
|
||||
@@ -92,6 +94,9 @@ export const findComposeById = async (composeId: string) => {
|
||||
deployments: true,
|
||||
mounts: true,
|
||||
domains: true,
|
||||
github: true,
|
||||
gitlab: true,
|
||||
bitbucket: true,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
@@ -113,7 +118,16 @@ export const loadServices = async (
|
||||
await cloneCompose(compose);
|
||||
}
|
||||
|
||||
const composeData = await loadDockerCompose(compose);
|
||||
let composeData = await loadDockerCompose(compose);
|
||||
|
||||
if (compose.randomize && composeData) {
|
||||
const randomizedCompose = randomizeSpecificationFile(
|
||||
composeData,
|
||||
compose.suffix,
|
||||
);
|
||||
composeData = randomizedCompose;
|
||||
}
|
||||
|
||||
if (!composeData?.services) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
@@ -151,7 +165,6 @@ export const deployCompose = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const compose = await findComposeById(composeId);
|
||||
const admin = await findAdmin();
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`;
|
||||
const deployment = await createDeploymentCompose({
|
||||
composeId: composeId,
|
||||
@@ -161,7 +174,11 @@ export const deployCompose = async ({
|
||||
|
||||
try {
|
||||
if (compose.sourceType === "github") {
|
||||
await cloneGithubRepository(admin, compose, deployment.logPath, true);
|
||||
await cloneGithubRepository(compose, deployment.logPath);
|
||||
} else if (compose.sourceType === "gitlab") {
|
||||
await cloneGitlabRepository(compose, deployment.logPath);
|
||||
} else if (compose.sourceType === "bitbucket") {
|
||||
await cloneBitbucketRepository(compose, deployment.logPath);
|
||||
} else if (compose.sourceType === "git") {
|
||||
await cloneGitRepository(compose, deployment.logPath, true);
|
||||
} else if (compose.sourceType === "raw") {
|
||||
|
||||
@@ -150,3 +150,20 @@ export const getContainersByAppLabel = async (appName: string) => {
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const containerRestart = async (containerId: string) => {
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(
|
||||
`docker container restart ${containerId}`,
|
||||
);
|
||||
|
||||
if (stderr) {
|
||||
console.error(`Error: ${stderr}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const config = JSON.parse(stdout);
|
||||
|
||||
return config;
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
29
apps/dokploy/server/api/services/git-provider.ts
Normal file
29
apps/dokploy/server/api/services/git-provider.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { db } from "@/server/db";
|
||||
import { type apiCreateGithub, gitProvider, github } from "@/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type GitProvider = typeof gitProvider.$inferSelect;
|
||||
|
||||
export const removeGitProvider = async (gitProviderId: string) => {
|
||||
const result = await db
|
||||
.delete(gitProvider)
|
||||
.where(eq(gitProvider.gitProviderId, gitProviderId))
|
||||
.returning();
|
||||
|
||||
return result[0];
|
||||
};
|
||||
|
||||
export const updateGitProvider = async (
|
||||
gitProviderId: string,
|
||||
input: Partial<GitProvider>,
|
||||
) => {
|
||||
return await db
|
||||
.update(gitProvider)
|
||||
.set({
|
||||
...input,
|
||||
})
|
||||
.where(eq(gitProvider.gitProviderId, gitProviderId))
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
};
|
||||
75
apps/dokploy/server/api/services/github.ts
Normal file
75
apps/dokploy/server/api/services/github.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { db } from "@/server/db";
|
||||
import { type apiCreateGithub, gitProvider, github } from "@/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type Github = typeof github.$inferSelect;
|
||||
export const createGithub = async (input: typeof apiCreateGithub._type) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
.insert(gitProvider)
|
||||
.values({
|
||||
providerType: "github",
|
||||
authId: input.authId,
|
||||
name: input.name,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
|
||||
if (!newGitProvider) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to create the git provider",
|
||||
});
|
||||
}
|
||||
|
||||
return await tx
|
||||
.insert(github)
|
||||
.values({
|
||||
...input,
|
||||
gitProviderId: newGitProvider?.gitProviderId,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
});
|
||||
};
|
||||
|
||||
export const findGithubById = async (githubId: string) => {
|
||||
const githubProviderResult = await db.query.github.findFirst({
|
||||
where: eq(github.githubId, githubId),
|
||||
with: {
|
||||
gitProvider: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!githubProviderResult) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Github Provider not found",
|
||||
});
|
||||
}
|
||||
|
||||
return githubProviderResult;
|
||||
};
|
||||
|
||||
export const haveGithubRequirements = (github: Github) => {
|
||||
return !!(
|
||||
github?.githubAppId &&
|
||||
github?.githubPrivateKey &&
|
||||
github?.githubInstallationId
|
||||
);
|
||||
};
|
||||
|
||||
export const updateGithub = async (
|
||||
githubId: string,
|
||||
input: Partial<Github>,
|
||||
) => {
|
||||
return await db
|
||||
.update(github)
|
||||
.set({
|
||||
...input,
|
||||
})
|
||||
.where(eq(github.githubId, githubId))
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
};
|
||||
76
apps/dokploy/server/api/services/gitlab.ts
Normal file
76
apps/dokploy/server/api/services/gitlab.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
type apiCreateGitlab,
|
||||
type bitbucket,
|
||||
gitProvider,
|
||||
type github,
|
||||
gitlab,
|
||||
} from "@/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type Github = typeof github.$inferSelect;
|
||||
export type Bitbucket = typeof bitbucket.$inferSelect;
|
||||
export type Gitlab = typeof gitlab.$inferSelect;
|
||||
|
||||
export const createGitlab = async (input: typeof apiCreateGitlab._type) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
.insert(gitProvider)
|
||||
.values({
|
||||
providerType: "gitlab",
|
||||
authId: input.authId,
|
||||
name: input.name,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
|
||||
if (!newGitProvider) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to create the git provider",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.insert(gitlab)
|
||||
.values({
|
||||
...input,
|
||||
gitProviderId: newGitProvider?.gitProviderId,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
});
|
||||
};
|
||||
|
||||
export const findGitlabById = async (gitlabId: string) => {
|
||||
const gitlabProviderResult = await db.query.gitlab.findFirst({
|
||||
where: eq(gitlab.gitlabId, gitlabId),
|
||||
with: {
|
||||
gitProvider: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!gitlabProviderResult) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Gitlab Provider not found",
|
||||
});
|
||||
}
|
||||
|
||||
return gitlabProviderResult;
|
||||
};
|
||||
|
||||
export const updateGitlab = async (
|
||||
gitlabId: string,
|
||||
input: Partial<Gitlab>,
|
||||
) => {
|
||||
return await db
|
||||
.update(gitlab)
|
||||
.set({
|
||||
...input,
|
||||
})
|
||||
.where(eq(gitlab.gitlabId, gitlabId))
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { boolean, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@@ -13,17 +13,9 @@ export const admins = pgTable("admin", {
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
|
||||
githubAppId: integer("githubAppId"),
|
||||
githubAppName: text("githubAppName"),
|
||||
serverIp: text("serverIp"),
|
||||
certificateType: certificateType("certificateType").notNull().default("none"),
|
||||
host: text("host"),
|
||||
githubClientId: text("githubClientId"),
|
||||
githubClientSecret: text("githubClientSecret"),
|
||||
githubInstallationId: text("githubInstallationId"),
|
||||
githubPrivateKey: text("githubPrivateKey"),
|
||||
githubWebhookSecret: text("githubWebhookSecret"),
|
||||
letsEncryptEmail: text("letsEncryptEmail"),
|
||||
sshPrivateKey: text("sshPrivateKey"),
|
||||
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
|
||||
@@ -48,12 +40,6 @@ export const adminsRelations = relations(admins, ({ one, many }) => ({
|
||||
|
||||
const createSchema = createInsertSchema(admins, {
|
||||
adminId: z.string(),
|
||||
githubAppName: z.string().optional(),
|
||||
githubClientId: z.string().optional(),
|
||||
githubClientSecret: z.string().optional(),
|
||||
githubInstallationId: z.string().optional(),
|
||||
githubPrivateKey: z.string().optional(),
|
||||
githubAppId: z.number().optional(),
|
||||
enableDockerCleanup: z.boolean().optional(),
|
||||
sshPrivateKey: z.string().optional(),
|
||||
certificateType: z.enum(["letsencrypt", "none"]).default("none"),
|
||||
@@ -84,10 +70,6 @@ export const apiTraefikConfig = z.object({
|
||||
traefikConfig: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiGetBranches = z.object({
|
||||
repo: z.string().min(1),
|
||||
owner: z.string().min(1),
|
||||
});
|
||||
export const apiModifyTraefikConfig = z.object({
|
||||
path: z.string().min(1),
|
||||
traefikConfig: z.string().min(1),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { bitbucket, github, gitlab } from ".";
|
||||
import { deployments } from "./deployment";
|
||||
import { domains } from "./domain";
|
||||
import { mounts } from "./mount";
|
||||
@@ -27,6 +27,8 @@ export const sourceType = pgEnum("sourceType", [
|
||||
"docker",
|
||||
"git",
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"drop",
|
||||
]);
|
||||
|
||||
@@ -126,6 +128,18 @@ export const applications = pgTable("application", {
|
||||
branch: text("branch"),
|
||||
buildPath: text("buildPath").default("/"),
|
||||
autoDeploy: boolean("autoDeploy").$defaultFn(() => true),
|
||||
// Gitlab
|
||||
gitlabProjectId: integer("gitlabProjectId"),
|
||||
gitlabRepository: text("gitlabRepository"),
|
||||
gitlabOwner: text("gitlabOwner"),
|
||||
gitlabBranch: text("gitlabBranch"),
|
||||
gitlabBuildPath: text("gitlabBuildPath").default("/"),
|
||||
gitlabPathNamespace: text("gitlabPathNamespace"),
|
||||
// Bitbucket
|
||||
bitbucketRepository: text("bitbucketRepository"),
|
||||
bitbucketOwner: text("bitbucketOwner"),
|
||||
bitbucketBranch: text("bitbucketBranch"),
|
||||
bitbucketBuildPath: text("bitbucketBuildPath").default("/"),
|
||||
// Docker
|
||||
username: text("username"),
|
||||
password: text("password"),
|
||||
@@ -142,6 +156,7 @@ export const applications = pgTable("application", {
|
||||
),
|
||||
dockerfile: text("dockerfile"),
|
||||
dockerContextPath: text("dockerContextPath"),
|
||||
dockerBuildStage: text("dockerBuildStage"),
|
||||
// Drop
|
||||
dropBuildPath: text("dropBuildPath"),
|
||||
// Docker swarm json
|
||||
@@ -169,6 +184,15 @@ export const applications = pgTable("application", {
|
||||
projectId: text("projectId")
|
||||
.notNull()
|
||||
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||
githubId: text("githubId").references(() => github.githubId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
gitlabId: text("gitlabId").references(() => gitlab.gitlabId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
});
|
||||
|
||||
export const applicationsRelations = relations(
|
||||
@@ -192,6 +216,18 @@ export const applicationsRelations = relations(
|
||||
fields: [applications.registryId],
|
||||
references: [registry.registryId],
|
||||
}),
|
||||
github: one(github, {
|
||||
fields: [applications.githubId],
|
||||
references: [github.githubId],
|
||||
}),
|
||||
gitlab: one(gitlab, {
|
||||
fields: [applications.gitlabId],
|
||||
references: [gitlab.gitlabId],
|
||||
}),
|
||||
bitbucket: one(bitbucket, {
|
||||
fields: [applications.bitbucketId],
|
||||
references: [bitbucket.bitbucketId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -358,6 +394,7 @@ export const apiSaveBuildType = createSchema
|
||||
buildType: true,
|
||||
dockerfile: true,
|
||||
dockerContextPath: true,
|
||||
dockerBuildStage: true,
|
||||
})
|
||||
.required()
|
||||
.merge(createSchema.pick({ publishDirectory: true }));
|
||||
@@ -369,6 +406,31 @@ export const apiSaveGithubProvider = createSchema
|
||||
branch: true,
|
||||
owner: true,
|
||||
buildPath: true,
|
||||
githubId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiSaveGitlabProvider = createSchema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
gitlabBranch: true,
|
||||
gitlabBuildPath: true,
|
||||
gitlabOwner: true,
|
||||
gitlabRepository: true,
|
||||
gitlabId: true,
|
||||
gitlabProjectId: true,
|
||||
gitlabPathNamespace: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiSaveBitbucketProvider = createSchema
|
||||
.pick({
|
||||
bitbucketBranch: true,
|
||||
bitbucketBuildPath: true,
|
||||
bitbucketOwner: true,
|
||||
bitbucketRepository: true,
|
||||
bitbucketId: true,
|
||||
applicationId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ export const apiCreateUser = createSchema
|
||||
.pick({
|
||||
password: true,
|
||||
id: true,
|
||||
token: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
|
||||
64
apps/dokploy/server/db/schema/bitbucket.ts
Normal file
64
apps/dokploy/server/db/schema/bitbucket.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { gitProvider } from "./git-provider";
|
||||
|
||||
export const bitbucket = pgTable("bitbucket", {
|
||||
bitbucketId: text("bitbucketId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
bitbucketUsername: text("bitbucketUsername"),
|
||||
appPassword: text("appPassword"),
|
||||
bitbucketWorkspaceName: text("bitbucketWorkspaceName"),
|
||||
gitProviderId: text("gitProviderId")
|
||||
.notNull()
|
||||
.references(() => gitProvider.gitProviderId, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const bitbucketProviderRelations = relations(bitbucket, ({ one }) => ({
|
||||
gitProvider: one(gitProvider, {
|
||||
fields: [bitbucket.gitProviderId],
|
||||
references: [gitProvider.gitProviderId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(bitbucket);
|
||||
|
||||
export const apiCreateBitbucket = createSchema.extend({
|
||||
bitbucketUsername: z.string().optional(),
|
||||
appPassword: z.string().optional(),
|
||||
bitbucketWorkspaceName: z.string().optional(),
|
||||
gitProviderId: z.string().optional(),
|
||||
authId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindOneBitbucket = createSchema
|
||||
.extend({
|
||||
bitbucketId: z.string().min(1),
|
||||
})
|
||||
.pick({ bitbucketId: true });
|
||||
|
||||
export const apiBitbucketTestConnection = createSchema
|
||||
.extend({
|
||||
bitbucketId: z.string().min(1),
|
||||
bitbucketUsername: z.string().optional(),
|
||||
workspaceName: z.string().optional(),
|
||||
})
|
||||
.pick({ bitbucketId: true, bitbucketUsername: true, workspaceName: true });
|
||||
|
||||
export const apiFindBitbucketBranches = z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
bitbucketId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiUpdateBitbucket = createSchema.extend({
|
||||
bitbucketId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
bitbucketUsername: z.string().optional(),
|
||||
bitbucketWorkspaceName: z.string().optional(),
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
import { sshKeys } from "@/server/db/schema/ssh-key";
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { bitbucket, github, gitlab } from ".";
|
||||
import { deployments } from "./deployment";
|
||||
import { domains } from "./domain";
|
||||
import { mounts } from "./mount";
|
||||
@@ -14,6 +15,8 @@ import { generateAppName } from "./utils";
|
||||
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
|
||||
"git",
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"raw",
|
||||
]);
|
||||
|
||||
@@ -39,6 +42,16 @@ export const compose = pgTable("compose", {
|
||||
owner: text("owner"),
|
||||
branch: text("branch"),
|
||||
autoDeploy: boolean("autoDeploy").$defaultFn(() => true),
|
||||
// Gitlab
|
||||
gitlabProjectId: integer("gitlabProjectId"),
|
||||
gitlabRepository: text("gitlabRepository"),
|
||||
gitlabOwner: text("gitlabOwner"),
|
||||
gitlabBranch: text("gitlabBranch"),
|
||||
gitlabPathNamespace: text("gitlabPathNamespace"),
|
||||
// Bitbucket
|
||||
bitbucketRepository: text("bitbucketRepository"),
|
||||
bitbucketOwner: text("bitbucketOwner"),
|
||||
bitbucketBranch: text("bitbucketBranch"),
|
||||
// Git
|
||||
customGitUrl: text("customGitUrl"),
|
||||
customGitBranch: text("customGitBranch"),
|
||||
@@ -48,10 +61,11 @@ export const compose = pgTable("compose", {
|
||||
onDelete: "set null",
|
||||
},
|
||||
),
|
||||
//
|
||||
command: text("command").notNull().default(""),
|
||||
//
|
||||
composePath: text("composePath").notNull().default("./docker-compose.yml"),
|
||||
suffix: text("suffix").notNull().default(""),
|
||||
randomize: boolean("randomize").notNull().default(false),
|
||||
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
|
||||
projectId: text("projectId")
|
||||
.notNull()
|
||||
@@ -59,6 +73,16 @@ export const compose = pgTable("compose", {
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
|
||||
githubId: text("githubId").references(() => github.githubId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
gitlabId: text("gitlabId").references(() => gitlab.gitlabId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
});
|
||||
|
||||
export const composeRelations = relations(compose, ({ one, many }) => ({
|
||||
@@ -73,6 +97,18 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
|
||||
references: [sshKeys.sshKeyId],
|
||||
}),
|
||||
domains: many(domains),
|
||||
github: one(github, {
|
||||
fields: [compose.githubId],
|
||||
references: [github.githubId],
|
||||
}),
|
||||
gitlab: one(gitlab, {
|
||||
fields: [compose.gitlabId],
|
||||
references: [gitlab.gitlabId],
|
||||
}),
|
||||
bitbucket: one(bitbucket, {
|
||||
fields: [compose.bitbucketId],
|
||||
references: [bitbucket.bitbucketId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(compose, {
|
||||
@@ -123,6 +159,6 @@ export const apiRandomizeCompose = createSchema
|
||||
composeId: true,
|
||||
})
|
||||
.extend({
|
||||
prefix: z.string().optional(),
|
||||
suffix: z.string().optional(),
|
||||
composeId: z.string().min(1),
|
||||
});
|
||||
|
||||
57
apps/dokploy/server/db/schema/git-provider.ts
Normal file
57
apps/dokploy/server/db/schema/git-provider.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { auth } from "./auth";
|
||||
import { bitbucket } from "./bitbucket";
|
||||
import { github } from "./github";
|
||||
import { gitlab } from "./gitlab";
|
||||
|
||||
export const gitProviderType = pgEnum("gitProviderType", [
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
]);
|
||||
|
||||
export const gitProvider = pgTable("git_provider", {
|
||||
gitProviderId: text("gitProviderId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull(),
|
||||
providerType: gitProviderType("providerType").notNull().default("github"),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
authId: text("authId")
|
||||
.notNull()
|
||||
.references(() => auth.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({
|
||||
github: one(github, {
|
||||
fields: [gitProvider.gitProviderId],
|
||||
references: [github.gitProviderId],
|
||||
}),
|
||||
gitlab: one(gitlab, {
|
||||
fields: [gitProvider.gitProviderId],
|
||||
references: [gitlab.gitProviderId],
|
||||
}),
|
||||
bitbucket: one(bitbucket, {
|
||||
fields: [gitProvider.gitProviderId],
|
||||
references: [bitbucket.gitProviderId],
|
||||
}),
|
||||
auth: one(auth, {
|
||||
fields: [gitProvider.authId],
|
||||
references: [auth.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(gitProvider);
|
||||
|
||||
export const apiRemoveGitProvider = createSchema
|
||||
.extend({
|
||||
gitProviderId: z.string().min(1),
|
||||
})
|
||||
.pick({ gitProviderId: true });
|
||||
62
apps/dokploy/server/db/schema/github.ts
Normal file
62
apps/dokploy/server/db/schema/github.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { gitProvider } from "./git-provider";
|
||||
|
||||
export const github = pgTable("github", {
|
||||
githubId: text("githubId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
githubAppName: text("githubAppName"),
|
||||
githubAppId: integer("githubAppId"),
|
||||
githubClientId: text("githubClientId"),
|
||||
githubClientSecret: text("githubClientSecret"),
|
||||
githubInstallationId: text("githubInstallationId"),
|
||||
githubPrivateKey: text("githubPrivateKey"),
|
||||
githubWebhookSecret: text("githubWebhookSecret"),
|
||||
gitProviderId: text("gitProviderId")
|
||||
.notNull()
|
||||
.references(() => gitProvider.gitProviderId, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const githubProviderRelations = relations(github, ({ one }) => ({
|
||||
gitProvider: one(gitProvider, {
|
||||
fields: [github.gitProviderId],
|
||||
references: [gitProvider.gitProviderId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(github);
|
||||
export const apiCreateGithub = createSchema.extend({
|
||||
githubAppName: z.string().optional(),
|
||||
githubAppId: z.number().optional(),
|
||||
githubClientId: z.string().optional(),
|
||||
githubClientSecret: z.string().optional(),
|
||||
githubInstallationId: z.string().optional(),
|
||||
githubPrivateKey: z.string().optional(),
|
||||
githubWebhookSecret: z.string().nullable(),
|
||||
gitProviderId: z.string().optional(),
|
||||
name: z.string().min(1),
|
||||
authId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindGithubBranches = z.object({
|
||||
repo: z.string().min(1),
|
||||
owner: z.string().min(1),
|
||||
githubId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiFindOneGithub = createSchema
|
||||
.extend({
|
||||
githubId: z.string().min(1),
|
||||
})
|
||||
.pick({ githubId: true });
|
||||
|
||||
export const apiUpdateGithub = createSchema.extend({
|
||||
githubId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
gitProviderId: z.string().min(1),
|
||||
});
|
||||
70
apps/dokploy/server/db/schema/gitlab.ts
Normal file
70
apps/dokploy/server/db/schema/gitlab.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { gitProvider } from "./git-provider";
|
||||
|
||||
export const gitlab = pgTable("gitlab", {
|
||||
gitlabId: text("gitlabId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
applicationId: text("application_id"),
|
||||
redirectUri: text("redirect_uri"),
|
||||
secret: text("secret"),
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
groupName: text("group_name"),
|
||||
expiresAt: integer("expires_at"),
|
||||
gitProviderId: text("gitProviderId")
|
||||
.notNull()
|
||||
.references(() => gitProvider.gitProviderId, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const gitlabProviderRelations = relations(gitlab, ({ one }) => ({
|
||||
gitProvider: one(gitProvider, {
|
||||
fields: [gitlab.gitProviderId],
|
||||
references: [gitProvider.gitProviderId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(gitlab);
|
||||
|
||||
export const apiCreateGitlab = createSchema.extend({
|
||||
applicationId: z.string().optional(),
|
||||
secret: z.string().optional(),
|
||||
groupName: z.string().optional(),
|
||||
gitProviderId: z.string().optional(),
|
||||
redirectUri: z.string().optional(),
|
||||
authId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindOneGitlab = createSchema
|
||||
.extend({
|
||||
gitlabId: z.string().min(1),
|
||||
})
|
||||
.pick({ gitlabId: true });
|
||||
|
||||
export const apiGitlabTestConnection = createSchema
|
||||
.extend({
|
||||
groupName: z.string().optional(),
|
||||
})
|
||||
.pick({ gitlabId: true, groupName: true });
|
||||
|
||||
export const apiFindGitlabBranches = z.object({
|
||||
id: z.number().optional(),
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
gitlabId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiUpdateGitlab = createSchema.extend({
|
||||
applicationId: z.string().optional(),
|
||||
secret: z.string().optional(),
|
||||
groupName: z.string().optional(),
|
||||
redirectUri: z.string().optional(),
|
||||
name: z.string().min(1),
|
||||
gitlabId: z.string().min(1),
|
||||
});
|
||||
@@ -23,3 +23,7 @@ export * from "./compose";
|
||||
export * from "./registry";
|
||||
export * from "./notification";
|
||||
export * from "./ssh-key";
|
||||
export * from "./git-provider";
|
||||
export * from "./bitbucket";
|
||||
export * from "./github";
|
||||
export * from "./gitlab";
|
||||
|
||||
@@ -34,6 +34,9 @@ export const users = pgTable("user", {
|
||||
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
|
||||
canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
|
||||
canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
|
||||
canAccessToGitProviders: boolean("canAccessToGitProviders")
|
||||
.notNull()
|
||||
.default(false),
|
||||
canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
|
||||
.notNull()
|
||||
.default(false),
|
||||
@@ -109,6 +112,7 @@ export const apiAssignPermissions = createSchema
|
||||
canAccessToDocker: true,
|
||||
canAccessToAPI: true,
|
||||
canAccessToSSHKeys: true,
|
||||
canAccessToGitProviders: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const initializeTraefik = async ({
|
||||
enableDashboard = false,
|
||||
env,
|
||||
}: TraefikOptions = {}) => {
|
||||
const imageName = "traefik:v2.5";
|
||||
const imageName = "traefik:v3.1.2";
|
||||
const containerName = "dokploy-traefik";
|
||||
const settings: CreateServiceOptions = {
|
||||
Name: containerName,
|
||||
@@ -56,6 +56,9 @@ export const initializeTraefik = async ({
|
||||
Replicas: 1,
|
||||
},
|
||||
},
|
||||
Labels: {
|
||||
"traefik.enable": "true",
|
||||
},
|
||||
EndpointSpec: {
|
||||
Ports: [
|
||||
{
|
||||
@@ -171,6 +174,10 @@ export const createDefaultTraefikConfig = () => {
|
||||
},
|
||||
}
|
||||
: {
|
||||
swarm: {
|
||||
exposedByDefault: false,
|
||||
watch: false,
|
||||
},
|
||||
docker: {
|
||||
exposedByDefault: false,
|
||||
},
|
||||
|
||||
@@ -18,15 +18,7 @@ export type ComposeNested = InferResultType<
|
||||
>;
|
||||
export const buildCompose = async (compose: ComposeNested, logPath: string) => {
|
||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||
const {
|
||||
sourceType,
|
||||
appName,
|
||||
mounts,
|
||||
composeType,
|
||||
env,
|
||||
composePath,
|
||||
domains,
|
||||
} = compose;
|
||||
const { sourceType, appName, mounts, composeType, domains } = compose;
|
||||
try {
|
||||
const command = createCommand(compose);
|
||||
await writeDomainsToCompose(compose, domains);
|
||||
@@ -117,6 +109,10 @@ const createEnvFile = (compose: ComposeNested) => {
|
||||
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
|
||||
}
|
||||
|
||||
if (compose.randomize) {
|
||||
envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
|
||||
}
|
||||
|
||||
const envFileContent = prepareEnvironmentVariables(envContent).join("\n");
|
||||
|
||||
if (!existsSync(dirname(envFilePath))) {
|
||||
|
||||
@@ -12,7 +12,8 @@ export const buildCustomDocker = async (
|
||||
application: ApplicationNested,
|
||||
writeStream: WriteStream,
|
||||
) => {
|
||||
const { appName, env, publishDirectory, buildArgs } = application;
|
||||
const { appName, env, publishDirectory, buildArgs, dockerBuildStage } =
|
||||
application;
|
||||
const dockerFilePath = getBuildAppDirectory(application);
|
||||
try {
|
||||
const image = `${appName}`;
|
||||
@@ -25,6 +26,10 @@ export const buildCustomDocker = async (
|
||||
|
||||
const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
|
||||
|
||||
if (dockerBuildStage) {
|
||||
commandArgs.push("--target", dockerBuildStage);
|
||||
}
|
||||
|
||||
for (const arg of args) {
|
||||
commandArgs.push("--build-arg", arg);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import crypto from "node:crypto";
|
||||
import { findComposeById } from "@/server/api/services/compose";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { addPrefixToAllConfigs } from "./compose/configs";
|
||||
import { addPrefixToAllNetworks } from "./compose/network";
|
||||
import { addPrefixToAllSecrets } from "./compose/secrets";
|
||||
import { addPrefixToAllServiceNames } from "./compose/service";
|
||||
import { addPrefixToAllVolumes } from "./compose/volume";
|
||||
import { addSuffixToAllConfigs } from "./compose/configs";
|
||||
import { addSuffixToAllNetworks } from "./compose/network";
|
||||
import { addSuffixToAllSecrets } from "./compose/secrets";
|
||||
import { addSuffixToAllServiceNames } from "./compose/service";
|
||||
import { addSuffixToAllVolumes } from "./compose/volume";
|
||||
import type { ComposeSpecification } from "./types";
|
||||
|
||||
export const generateRandomHash = (): string => {
|
||||
@@ -14,32 +14,43 @@ export const generateRandomHash = (): string => {
|
||||
|
||||
export const randomizeComposeFile = async (
|
||||
composeId: string,
|
||||
prefix?: string,
|
||||
suffix?: string,
|
||||
) => {
|
||||
const compose = await findComposeById(composeId);
|
||||
const composeFile = compose.composeFile;
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const randomPrefix = prefix || generateRandomHash();
|
||||
const randomSuffix = suffix || generateRandomHash();
|
||||
|
||||
const newComposeFile = addPrefixToAllProperties(composeData, randomPrefix);
|
||||
const newComposeFile = addSuffixToAllProperties(composeData, randomSuffix);
|
||||
|
||||
return dump(newComposeFile);
|
||||
};
|
||||
|
||||
export const addPrefixToAllProperties = (
|
||||
export const randomizeSpecificationFile = (
|
||||
composeSpec: ComposeSpecification,
|
||||
suffix?: string,
|
||||
) => {
|
||||
if (!suffix) {
|
||||
return composeSpec;
|
||||
}
|
||||
const newComposeFile = addSuffixToAllProperties(composeSpec, suffix);
|
||||
return newComposeFile;
|
||||
};
|
||||
|
||||
export const addSuffixToAllProperties = (
|
||||
composeData: ComposeSpecification,
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
): ComposeSpecification => {
|
||||
let updatedComposeData = { ...composeData };
|
||||
|
||||
updatedComposeData = addPrefixToAllServiceNames(updatedComposeData, prefix);
|
||||
updatedComposeData = addSuffixToAllServiceNames(updatedComposeData, suffix);
|
||||
|
||||
updatedComposeData = addPrefixToAllVolumes(updatedComposeData, prefix);
|
||||
updatedComposeData = addSuffixToAllVolumes(updatedComposeData, suffix);
|
||||
|
||||
updatedComposeData = addPrefixToAllNetworks(updatedComposeData, prefix);
|
||||
updatedComposeData = addPrefixToAllConfigs(updatedComposeData, prefix);
|
||||
updatedComposeData = addSuffixToAllNetworks(updatedComposeData, suffix);
|
||||
updatedComposeData = addSuffixToAllConfigs(updatedComposeData, suffix);
|
||||
|
||||
updatedComposeData = addPrefixToAllSecrets(updatedComposeData, prefix);
|
||||
updatedComposeData = addSuffixToAllSecrets(updatedComposeData, suffix);
|
||||
return updatedComposeData;
|
||||
};
|
||||
|
||||
@@ -5,23 +5,23 @@ import type {
|
||||
DefinitionsService,
|
||||
} from "../types";
|
||||
|
||||
export const addPrefixToConfigsRoot = (
|
||||
export const addSuffixToConfigsRoot = (
|
||||
configs: { [key: string]: DefinitionsConfig },
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
): { [key: string]: DefinitionsConfig } => {
|
||||
const newConfigs: { [key: string]: DefinitionsConfig } = {};
|
||||
|
||||
_.forEach(configs, (config, configName) => {
|
||||
const newConfigName = `${configName}-${prefix}`;
|
||||
const newConfigName = `${configName}-${suffix}`;
|
||||
newConfigs[newConfigName] = _.cloneDeep(config);
|
||||
});
|
||||
|
||||
return newConfigs;
|
||||
};
|
||||
|
||||
export const addPrefixToConfigsInServices = (
|
||||
export const addSuffixToConfigsInServices = (
|
||||
services: { [key: string]: DefinitionsService },
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
): { [key: string]: DefinitionsService } => {
|
||||
const newServices: { [key: string]: DefinitionsService } = {};
|
||||
|
||||
@@ -32,12 +32,12 @@ export const addPrefixToConfigsInServices = (
|
||||
if (_.has(newServiceConfig, "configs")) {
|
||||
newServiceConfig.configs = _.map(newServiceConfig.configs, (config) => {
|
||||
if (_.isString(config)) {
|
||||
return `${config}-${prefix}`;
|
||||
return `${config}-${suffix}`;
|
||||
}
|
||||
if (_.isObject(config) && config.source) {
|
||||
return {
|
||||
...config,
|
||||
source: `${config.source}-${prefix}`,
|
||||
source: `${config.source}-${suffix}`,
|
||||
};
|
||||
}
|
||||
return config;
|
||||
@@ -50,22 +50,22 @@ export const addPrefixToConfigsInServices = (
|
||||
return newServices;
|
||||
};
|
||||
|
||||
export const addPrefixToAllConfigs = (
|
||||
export const addSuffixToAllConfigs = (
|
||||
composeData: ComposeSpecification,
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
): ComposeSpecification => {
|
||||
const updatedComposeData = { ...composeData };
|
||||
if (composeData?.configs) {
|
||||
updatedComposeData.configs = addPrefixToConfigsRoot(
|
||||
updatedComposeData.configs = addSuffixToConfigsRoot(
|
||||
composeData.configs,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
}
|
||||
|
||||
if (composeData?.services) {
|
||||
updatedComposeData.services = addPrefixToConfigsInServices(
|
||||
updatedComposeData.services = addSuffixToConfigsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user