|
|
|
|
@ -1,6 +1,6 @@
|
|
|
|
|
import { useEffect, useState, useRef } from "react";
|
|
|
|
|
import { useParams } from "react-router-dom";
|
|
|
|
|
import { Dictionary, omit, remove } from "lodash";
|
|
|
|
|
import { Dictionary, omit } from "lodash";
|
|
|
|
|
import {
|
|
|
|
|
GlobeAltIcon,
|
|
|
|
|
CubeIcon,
|
|
|
|
|
@ -223,39 +223,119 @@ export default function Project(props: IProjectProps) {
|
|
|
|
|
return dependsOnKeys;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onUpdateEndpoint = (nodeItem: IServiceNodeItem) => {
|
|
|
|
|
const getVolumeNodeName = (node: IVolumeNodeItem | undefined): string => {
|
|
|
|
|
if (!node) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (node.canvasConfig.node_name ||
|
|
|
|
|
node.volumeConfig.name ||
|
|
|
|
|
"") as string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getVolumeMountSourceName = (mount: any): string | null => {
|
|
|
|
|
if (typeof mount === "string") {
|
|
|
|
|
const sourceName = mount.split(":", 1)[0].trim();
|
|
|
|
|
return sourceName || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
mount &&
|
|
|
|
|
mount.constructor === Object &&
|
|
|
|
|
typeof mount.source === "string"
|
|
|
|
|
) {
|
|
|
|
|
const sourceName = mount.source.trim();
|
|
|
|
|
return sourceName || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getServiceVolumeMountNames = (volumesData: any): string[] => {
|
|
|
|
|
if (!Array.isArray(volumesData)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const names: string[] = [];
|
|
|
|
|
volumesData.forEach((mount) => {
|
|
|
|
|
const sourceName = getVolumeMountSourceName(mount);
|
|
|
|
|
if (sourceName && !names.includes(sourceName)) {
|
|
|
|
|
names.push(sourceName);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return names;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeServiceVolumeMount = (
|
|
|
|
|
volumesData: any,
|
|
|
|
|
volumeName: string
|
|
|
|
|
): any[] => {
|
|
|
|
|
if (!Array.isArray(volumesData)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return volumesData.filter((mount) => {
|
|
|
|
|
return getVolumeMountSourceName(mount) !== volumeName;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onUpdateEndpoint = (nodeItem: IServiceNodeItem | IVolumeNodeItem) => {
|
|
|
|
|
const key = nodeItem.key;
|
|
|
|
|
|
|
|
|
|
if (connections.length) {
|
|
|
|
|
const _connections = [...connections];
|
|
|
|
|
if (nodeItem.type === "SERVICE") {
|
|
|
|
|
const serviceNode = nodeItem as IServiceNodeItem;
|
|
|
|
|
|
|
|
|
|
if (connections.length) {
|
|
|
|
|
const filteredConnections = connections.filter((conn: any) => {
|
|
|
|
|
if (key === conn[0]) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (key === conn[1] && nodes[conn[0]]?.type === "VOLUME") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}) as any;
|
|
|
|
|
|
|
|
|
|
_connections.forEach((conn: any) => {
|
|
|
|
|
if (key === conn[0]) {
|
|
|
|
|
const filtered = connections.filter((conn: any) => {
|
|
|
|
|
return key !== conn[0];
|
|
|
|
|
}) as any;
|
|
|
|
|
setConnections(filteredConnections);
|
|
|
|
|
stateConnectionsRef.current = filteredConnections;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setConnections(filtered);
|
|
|
|
|
stateConnectionsRef.current = filtered;
|
|
|
|
|
const dependsOnData = serviceNode.serviceConfig.depends_on;
|
|
|
|
|
const dependsOnKeys = getDependsOnKeys(dependsOnData);
|
|
|
|
|
|
|
|
|
|
dependsOnKeys.forEach((dep: string) => {
|
|
|
|
|
const depObject = Object.keys(nodes).find((nodeKey: string) => {
|
|
|
|
|
const node = nodes[nodeKey];
|
|
|
|
|
return node.type === "SERVICE" && node.canvasConfig.node_name === dep;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (depObject) {
|
|
|
|
|
onConnectionAttached([key, depObject]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dependsOnData = nodeItem.serviceConfig.depends_on;
|
|
|
|
|
const dependsOnKeys = getDependsOnKeys(dependsOnData);
|
|
|
|
|
const volumeNames = getServiceVolumeMountNames(
|
|
|
|
|
serviceNode.serviceConfig.volumes
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
volumeNames.forEach((volumeName) => {
|
|
|
|
|
const sourceVolumeKey = Object.keys(nodes).find((nodeKey: string) => {
|
|
|
|
|
const node = nodes[nodeKey];
|
|
|
|
|
if (node.type !== "VOLUME") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return getVolumeNodeName(node as IVolumeNodeItem) === volumeName;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
dependsOnKeys.forEach((dep: string) => {
|
|
|
|
|
const depObject = Object.keys(nodes).find((key: string) => {
|
|
|
|
|
const node = nodes[key];
|
|
|
|
|
if (node.canvasConfig.node_name === dep) {
|
|
|
|
|
return node;
|
|
|
|
|
if (sourceVolumeKey) {
|
|
|
|
|
onConnectionAttached([sourceVolumeKey, key]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (depObject) {
|
|
|
|
|
onConnectionAttached([key, depObject]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setNodes({ ...nodes, [nodeItem.key]: nodeItem });
|
|
|
|
|
};
|
|
|
|
|
@ -269,32 +349,88 @@ export default function Project(props: IProjectProps) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stateNodesRef.current) {
|
|
|
|
|
const sourceNode = {
|
|
|
|
|
...stateNodesRef.current[data[0]]
|
|
|
|
|
} as IServiceNodeItem;
|
|
|
|
|
const sourceNode = stateNodesRef.current[data[0]];
|
|
|
|
|
const targetNode = stateNodesRef.current[data[1]];
|
|
|
|
|
const targetServiceName = targetNode.canvasConfig.node_name;
|
|
|
|
|
const sourceDependsOn = sourceNode.serviceConfig.depends_on as any;
|
|
|
|
|
const updatedNodes = { ...stateNodesRef.current };
|
|
|
|
|
let hasNodeUpdates = false;
|
|
|
|
|
|
|
|
|
|
if (targetServiceName) {
|
|
|
|
|
const dependsOnKeys = getDependsOnKeys(sourceDependsOn);
|
|
|
|
|
if (sourceNode?.type === "SERVICE" && targetNode?.type === "SERVICE") {
|
|
|
|
|
const sourceServiceNode = sourceNode as IServiceNodeItem;
|
|
|
|
|
const targetServiceName = targetNode.canvasConfig.node_name;
|
|
|
|
|
let sourceDependsOn = sourceServiceNode.serviceConfig.depends_on as any;
|
|
|
|
|
|
|
|
|
|
if (targetServiceName) {
|
|
|
|
|
const dependsOnKeys = getDependsOnKeys(sourceDependsOn);
|
|
|
|
|
|
|
|
|
|
dependsOnKeys.forEach((key: string) => {
|
|
|
|
|
if (key === targetServiceName) {
|
|
|
|
|
if (dependsOnKeys.includes(targetServiceName)) {
|
|
|
|
|
if (Array.isArray(sourceDependsOn)) {
|
|
|
|
|
remove(sourceDependsOn, (key) => key === targetServiceName);
|
|
|
|
|
sourceDependsOn = sourceDependsOn.filter(
|
|
|
|
|
(name: string) => name !== targetServiceName
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sourceDependsOn && sourceDependsOn.constructor === Object) {
|
|
|
|
|
delete sourceDependsOn[key];
|
|
|
|
|
sourceDependsOn = { ...sourceDependsOn };
|
|
|
|
|
delete sourceDependsOn[targetServiceName];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nextServiceConfig = {
|
|
|
|
|
...sourceServiceNode.serviceConfig
|
|
|
|
|
} as any;
|
|
|
|
|
|
|
|
|
|
if (getDependsOnKeys(sourceDependsOn).length) {
|
|
|
|
|
nextServiceConfig.depends_on = sourceDependsOn;
|
|
|
|
|
} else {
|
|
|
|
|
delete nextServiceConfig.depends_on;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updatedNodes[data[0]] = {
|
|
|
|
|
...sourceServiceNode,
|
|
|
|
|
serviceConfig: nextServiceConfig
|
|
|
|
|
};
|
|
|
|
|
hasNodeUpdates = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sourceNode?.type === "VOLUME" && targetNode?.type === "SERVICE") {
|
|
|
|
|
const volumeNodeName = getVolumeNodeName(sourceNode as IVolumeNodeItem);
|
|
|
|
|
const targetServiceNode = targetNode as IServiceNodeItem;
|
|
|
|
|
|
|
|
|
|
if (volumeNodeName) {
|
|
|
|
|
const nextVolumes = removeServiceVolumeMount(
|
|
|
|
|
targetServiceNode.serviceConfig.volumes,
|
|
|
|
|
volumeNodeName
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
Array.isArray(targetServiceNode.serviceConfig.volumes) &&
|
|
|
|
|
nextVolumes.length !==
|
|
|
|
|
targetServiceNode.serviceConfig.volumes.length
|
|
|
|
|
) {
|
|
|
|
|
const nextServiceConfig = {
|
|
|
|
|
...targetServiceNode.serviceConfig
|
|
|
|
|
} as any;
|
|
|
|
|
|
|
|
|
|
if (nextVolumes.length) {
|
|
|
|
|
nextServiceConfig.volumes = nextVolumes;
|
|
|
|
|
} else {
|
|
|
|
|
delete nextServiceConfig.volumes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!getDependsOnKeys(sourceDependsOn).length) {
|
|
|
|
|
delete sourceNode.serviceConfig.depends_on;
|
|
|
|
|
updatedNodes[data[1]] = {
|
|
|
|
|
...targetServiceNode,
|
|
|
|
|
serviceConfig: nextServiceConfig
|
|
|
|
|
};
|
|
|
|
|
hasNodeUpdates = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasNodeUpdates) {
|
|
|
|
|
setNodes(updatedNodes);
|
|
|
|
|
stateNodesRef.current = updatedNodes as any;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const _connections: [[string, string]] = [
|
|
|
|
|
@ -311,35 +447,82 @@ export default function Project(props: IProjectProps) {
|
|
|
|
|
|
|
|
|
|
const onConnectionAttached = (data: any) => {
|
|
|
|
|
if (stateNodesRef.current) {
|
|
|
|
|
const sourceNode = {
|
|
|
|
|
...stateNodesRef.current[data[0]]
|
|
|
|
|
} as IServiceNodeItem;
|
|
|
|
|
const sourceNode = stateNodesRef.current[data[0]];
|
|
|
|
|
const targetNode = stateNodesRef.current[data[1]];
|
|
|
|
|
const targetServiceName = targetNode.canvasConfig.node_name;
|
|
|
|
|
let sourceDependsOn = sourceNode.serviceConfig.depends_on as any;
|
|
|
|
|
const dependsOnKeys = getDependsOnKeys(sourceDependsOn);
|
|
|
|
|
const updatedNodes = { ...stateNodesRef.current };
|
|
|
|
|
let hasNodeUpdates = false;
|
|
|
|
|
|
|
|
|
|
if (sourceNode?.type === "SERVICE" && targetNode?.type === "SERVICE") {
|
|
|
|
|
const sourceServiceNode = sourceNode as IServiceNodeItem;
|
|
|
|
|
const targetServiceName = targetNode.canvasConfig.node_name;
|
|
|
|
|
let sourceDependsOn = sourceServiceNode.serviceConfig.depends_on as any;
|
|
|
|
|
const dependsOnKeys = getDependsOnKeys(sourceDependsOn);
|
|
|
|
|
|
|
|
|
|
if (sourceDependsOn) {
|
|
|
|
|
if (targetServiceName) {
|
|
|
|
|
if (!dependsOnKeys.includes(targetServiceName)) {
|
|
|
|
|
if (Array.isArray(sourceDependsOn)) {
|
|
|
|
|
sourceDependsOn.push(targetServiceName);
|
|
|
|
|
}
|
|
|
|
|
if (sourceDependsOn) {
|
|
|
|
|
if (!dependsOnKeys.includes(targetServiceName)) {
|
|
|
|
|
if (Array.isArray(sourceDependsOn)) {
|
|
|
|
|
sourceDependsOn = [...sourceDependsOn, targetServiceName];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sourceDependsOn.constructor === Object) {
|
|
|
|
|
sourceDependsOn[targetServiceName] = {
|
|
|
|
|
condition: "service_healthy"
|
|
|
|
|
};
|
|
|
|
|
if (sourceDependsOn.constructor === Object) {
|
|
|
|
|
sourceDependsOn = { ...sourceDependsOn };
|
|
|
|
|
sourceDependsOn[targetServiceName] = {
|
|
|
|
|
condition: "service_healthy"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
sourceDependsOn = [targetServiceName];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updatedNodes[data[0]] = {
|
|
|
|
|
...sourceServiceNode,
|
|
|
|
|
serviceConfig: {
|
|
|
|
|
...sourceServiceNode.serviceConfig,
|
|
|
|
|
depends_on: sourceDependsOn
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
hasNodeUpdates = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (targetServiceName) {
|
|
|
|
|
sourceDependsOn = [targetServiceName];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sourceNode?.type === "VOLUME" && targetNode?.type === "SERVICE") {
|
|
|
|
|
const sourceVolumeName = getVolumeNodeName(
|
|
|
|
|
sourceNode as IVolumeNodeItem
|
|
|
|
|
);
|
|
|
|
|
const targetServiceNode = targetNode as IServiceNodeItem;
|
|
|
|
|
const currentVolumeNames = getServiceVolumeMountNames(
|
|
|
|
|
targetServiceNode.serviceConfig.volumes
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
sourceVolumeName &&
|
|
|
|
|
!currentVolumeNames.includes(sourceVolumeName)
|
|
|
|
|
) {
|
|
|
|
|
const currentVolumes = Array.isArray(
|
|
|
|
|
targetServiceNode.serviceConfig.volumes
|
|
|
|
|
)
|
|
|
|
|
? [...targetServiceNode.serviceConfig.volumes]
|
|
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
currentVolumes.push(sourceVolumeName);
|
|
|
|
|
|
|
|
|
|
updatedNodes[data[1]] = {
|
|
|
|
|
...targetServiceNode,
|
|
|
|
|
serviceConfig: {
|
|
|
|
|
...targetServiceNode.serviceConfig,
|
|
|
|
|
volumes: currentVolumes
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
hasNodeUpdates = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sourceNode.serviceConfig.depends_on = sourceDependsOn;
|
|
|
|
|
if (hasNodeUpdates) {
|
|
|
|
|
setNodes(updatedNodes);
|
|
|
|
|
stateNodesRef.current = updatedNodes as any;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stateConnectionsRef.current && stateConnectionsRef.current.length > 0) {
|
|
|
|
|
@ -351,8 +534,10 @@ export default function Project(props: IProjectProps) {
|
|
|
|
|
_connections.push(data);
|
|
|
|
|
}
|
|
|
|
|
setConnections(_connections);
|
|
|
|
|
stateConnectionsRef.current = _connections;
|
|
|
|
|
} else {
|
|
|
|
|
setConnections([data]);
|
|
|
|
|
stateConnectionsRef.current = [data] as any;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|