feat(networks): enhance network management and UI components

- Added functionality for assigning Docker networks to services, allowing users to manage network associations directly from the UI.
- Introduced new components for assigning networks to both individual services and compose files, improving user experience and flexibility.
- Updated existing components to integrate network assignment features, ensuring seamless interaction within the dashboard.
- Enhanced the database schema to support new network-related fields, including `networkIds` and `detachDokployNetwork`, for better service configuration.
- Improved the layout and responsiveness of various UI elements to accommodate new features and enhance usability.
This commit is contained in:
Mauricio Siu
2026-07-25 03:09:54 -06:00
parent 8a37626636
commit 97885ee44a
57 changed files with 1382 additions and 307 deletions
+102 -4
View File
@@ -1,8 +1,11 @@
import fs, { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import { network } from "@dokploy/server/db/schema";
import type { Compose } from "@dokploy/server/services/compose";
import type { Domain } from "@dokploy/server/services/domain";
import { inArray } from "drizzle-orm";
import { parse, stringify } from "yaml";
import { execAsyncRemote } from "../process/execAsync";
import { cloneBitbucketRepository } from "../providers/bitbucket";
@@ -228,14 +231,79 @@ export const addDomainToCompose = async (
}
}
// Add dokploy-network to the root of the compose file
const injectedNetworkNames = await applyServiceNetworks(result, compose);
if (!compose.isolatedDeployment) {
result.networks = addDokployNetworkToRoot(result.networks);
declareUsedNetworksInRoot(result, injectedNetworkNames);
}
return result;
};
const applyServiceNetworks = async (
result: ComposeSpecification,
compose: Compose,
) => {
const injectedNetworkNames = new Set<string>();
const serviceNetworks = compose.serviceNetworks ?? [];
if (serviceNetworks.length === 0) return injectedNetworkNames;
const allNetworkIds = [
...new Set(serviceNetworks.flatMap((s) => s.networkIds)),
];
const networks =
allNetworkIds.length > 0
? await db.query.network.findMany({
where: inArray(network.networkId, allNetworkIds),
})
: [];
for (const config of serviceNetworks) {
const service = result.services?.[config.serviceName];
if (!service) continue;
for (const networkId of config.networkIds) {
const match = networks.find((n) => n.networkId === networkId);
if (!match) continue;
service.networks = addDokployNetworkToService(
service.networks,
match.name,
);
injectedNetworkNames.add(match.name);
}
if (config.detachDokployNetwork) {
removeNetworkFromService(service, "dokploy-network");
removeNetworkFromService(service, "default");
removeDokployNetworkLabel(service);
}
}
return injectedNetworkNames;
};
const declareUsedNetworksInRoot = (
result: ComposeSpecification,
injectedNetworkNames: Set<string>,
) => {
const isUsed = (name: string) =>
Object.values(result.services ?? {}).some((service) => {
const nets = service?.networks;
if (Array.isArray(nets)) return nets.includes(name);
if (nets && typeof nets === "object") return name in nets;
return false;
});
if (isUsed("dokploy-network")) {
result.networks = addDokployNetworkToRoot(result.networks);
}
for (const name of injectedNetworkNames) {
if (isUsed(name)) {
result.networks = addDokployNetworkToRoot(result.networks, name);
}
}
};
export const writeComposeFile = async (
compose: Compose,
composeSpec: ComposeSpecification,
@@ -349,9 +417,10 @@ export const createDomainLabels = (
export const addDokployNetworkToService = (
networkService: DefinitionsService["networks"],
networkName = "dokploy-network",
) => {
let networks = networkService;
const network = "dokploy-network";
const network = networkName;
const defaultNetwork = "default";
if (!networks) {
networks = [];
@@ -376,11 +445,40 @@ export const addDokployNetworkToService = (
return networks;
};
export const removeNetworkFromService = (
service: DefinitionsService,
networkName: string,
) => {
const networks = service.networks;
if (Array.isArray(networks)) {
service.networks = networks.filter((n) => n !== networkName);
} else if (networks && typeof networks === "object") {
delete networks[networkName];
}
};
const removeDokployNetworkLabel = (service: DefinitionsService) => {
const stripped = (labels: DefinitionsService["labels"]) => {
if (Array.isArray(labels)) {
return labels.filter(
(l) =>
l !== "traefik.docker.network=dokploy-network" &&
l !== "traefik.swarm.network=dokploy-network",
);
}
return labels;
};
if (service.labels) service.labels = stripped(service.labels);
if (service.deploy?.labels)
service.deploy.labels = stripped(service.deploy.labels);
};
export const addDokployNetworkToRoot = (
networkRoot: PropertiesNetworks | undefined,
networkName = "dokploy-network",
) => {
let networks = networkRoot;
const network = "dokploy-network";
const network = networkName;
if (!networks) {
networks = {};