Canvas: Prettier

Change-Id: I620dde109df0f29f0c85c6fe150e347d2c32a03e
diff --git a/apps/canvas/front/src/lib/config.ts b/apps/canvas/front/src/lib/config.ts
index 3ea29b6..ea7f892 100644
--- a/apps/canvas/front/src/lib/config.ts
+++ b/apps/canvas/front/src/lib/config.ts
@@ -1,394 +1,525 @@
 import { AppNode, Env, GatewayHttpsNode, Message, MessageType, NodeType, ServiceType, VolumeType } from "./state";
 
 export type AuthDisabled = {
-    enabled: false;
+	enabled: false;
 };
 
 export type AuthEnabled = {
-    enabled: true;
-    groups: string[];
-    noAuthPathPatterns: string[];
+	enabled: true;
+	groups: string[];
+	noAuthPathPatterns: string[];
 };
 
 export type Auth = AuthDisabled | AuthEnabled;
 
 export type Ingress = {
-    network: string;
-    subdomain: string;
-    port: { name: string; } | { value: string; };
-    auth: Auth;
+	network: string;
+	subdomain: string;
+	port: { name: string } | { value: string };
+	auth: Auth;
 };
 
 export type Domain = {
-    network: string;
-    subdomain: string;
+	network: string;
+	subdomain: string;
 };
 
-export type PortValue = {
-    name: string;
-} | {
-    value: number;
-};
+export type PortValue =
+	| {
+			name: string;
+	  }
+	| {
+			value: number;
+	  };
 
 export type PortDomain = Domain & {
-    port: PortValue;
-}
+	port: PortValue;
+};
 
 export type Service = {
-    type: ServiceType;
-    name: string;
-    source: {
-        repository: string;
-        branch: string;
-        rootDir: string;
-    };
-    ports?: {
-        name: string;
-        value: number;
-        protocol: "TCP" | "UDP";
-    }[];
-    env?: {
-        name: string;
-        alias?: string;
-    }[]
-    ingress?: Ingress[];
-    expose?: PortDomain[];
-    volume?: string[];
-    preBuildCommands?: { bin: string }[];
+	type: ServiceType;
+	name: string;
+	source: {
+		repository: string;
+		branch: string;
+		rootDir: string;
+	};
+	ports?: {
+		name: string;
+		value: number;
+		protocol: "TCP" | "UDP";
+	}[];
+	env?: {
+		name: string;
+		alias?: string;
+	}[];
+	ingress?: Ingress[];
+	expose?: PortDomain[];
+	volume?: string[];
+	preBuildCommands?: { bin: string }[];
 };
 
 export type Volume = {
-    name: string;
-    accessMode: VolumeType;
-    size: string;
+	name: string;
+	accessMode: VolumeType;
+	size: string;
 };
 
 export type PostgreSQL = {
-    name: string;
-    size: string;
-    expose?: Domain[];
+	name: string;
+	size: string;
+	expose?: Domain[];
 };
 
 export type MongoDB = {
-    name: string;
-    size: string;
-    expose?: Domain[];
+	name: string;
+	size: string;
+	expose?: Domain[];
 };
 
 export type Config = {
-    service?: Service[];
-    volume?: Volume[];
-    postgresql?: PostgreSQL[];
-    mongodb?: MongoDB[];
+	service?: Service[];
+	volume?: Volume[];
+	postgresql?: PostgreSQL[];
+	mongodb?: MongoDB[];
 };
 
 export function generateDodoConfig(nodes: AppNode[], env: Env): Config | null {
-    try {
-        const networkMap = new Map(env.networks.map((n) => [n.domain, n.name]));
-        const ingressNodes = nodes.filter((n) => n.type === "gateway-https").filter((n) => n.data.https !== undefined);
-        const tcpNodes = nodes.filter((n) => n.type === "gateway-tcp").filter((n) => n.data.exposed !== undefined);
-        const findExpose = (n: AppNode): PortDomain[] => {
-            return n.data.ports.map((p) => [n.id, p.id, p.name]).flatMap((sp) => {
-                return tcpNodes.flatMap((i) => (i.data.exposed || []).filter((t) => t.serviceId === sp[0] && t.portId === sp[1]).map(() => ({
-                    network: networkMap.get(i.data.network!)!,
-                    subdomain: i.data.subdomain!,
-                    port: { name: sp[2] },
-                })));
-            });
-        };
-        return {
-            service: nodes.filter((n) => n.type === "app").map((n): Service => {
-                return {
-                    type: n.data.type,
-                    name: n.data.label,
-                    source: {
-                        repository: nodes.filter((i) => i.type === "github").find((i) => i.id === n.data.repository.id)!.data.repository!.sshURL,
-                        branch: n.data.repository.branch,
-                        rootDir: n.data.repository.rootDir,
-                    },
-                    ports: (n.data.ports || []).map((p) => ({
-                        name: p.name,
-                        value: p.value,
-                        protocol: "TCP", // TODO(gio)
-                    })),
-                    env: (n.data.envVars || []).filter((e) => "name" in e).map((e) => ({
-                        name: e.name,
-                        alias: "alias" in e ? e.alias : undefined,
-                    })),
-                    ingress: ingressNodes.filter((i) => i.data.https!.serviceId === n.id).map((i: GatewayHttpsNode): Ingress => ({
-                        network: networkMap.get(i.data.network!)!,
-                        subdomain: i.data.subdomain!,
-                        port: {
-                            name: n.data.ports.find((p) => p.id === i.data.https!.portId)!.name,
-                        },
-                        auth: (i.data.auth?.enabled || false ? {
-                            enabled: true,
-                            groups: i.data.auth!.groups,
-                            noAuthPathPatterns: i.data.auth!.noAuthPathPatterns,
-                        } : {
-                            enabled: false,
-                        }),
-                    })),
-                    expose: findExpose(n),
-                    preBuildCommands: n.data.preBuildCommands ? n.data.preBuildCommands.split("\n").map((cmd) => ({ bin: cmd })) : [],
-                };
-            }),
-            volume: nodes.filter((n) => n.type === "volume").map((n): Volume => ({
-                name: n.data.label,
-                accessMode: n.data.type,
-                size: n.data.size,
-            })),
-            postgresql: nodes.filter((n) => n.type === "postgresql").map((n): PostgreSQL => ({
-                name: n.data.label,
-                size: "1Gi", // TODO(gio)
-                expose: findExpose(n).map((e) => ({ network: e.network, subdomain: e.subdomain })),
-            })),
-            mongodb: nodes.filter((n) => n.type === "mongodb").map((n): MongoDB => ({
-                name: n.data.label,
-                size: "1Gi", // TODO(gio)
-                expose: findExpose(n).map((e) => ({ network: e.network, subdomain: e.subdomain })),
-            })),
-        };
-    } catch (e) {
-        console.log(e);
-        return null;
-    }
+	try {
+		const networkMap = new Map(env.networks.map((n) => [n.domain, n.name]));
+		const ingressNodes = nodes.filter((n) => n.type === "gateway-https").filter((n) => n.data.https !== undefined);
+		const tcpNodes = nodes.filter((n) => n.type === "gateway-tcp").filter((n) => n.data.exposed !== undefined);
+		const findExpose = (n: AppNode): PortDomain[] => {
+			return n.data.ports
+				.map((p) => [n.id, p.id, p.name])
+				.flatMap((sp) => {
+					return tcpNodes.flatMap((i) =>
+						(i.data.exposed || [])
+							.filter((t) => t.serviceId === sp[0] && t.portId === sp[1])
+							.map(() => ({
+								network: networkMap.get(i.data.network!)!,
+								subdomain: i.data.subdomain!,
+								port: { name: sp[2] },
+							})),
+					);
+				});
+		};
+		return {
+			service: nodes
+				.filter((n) => n.type === "app")
+				.map((n): Service => {
+					return {
+						type: n.data.type,
+						name: n.data.label,
+						source: {
+							repository: nodes
+								.filter((i) => i.type === "github")
+								.find((i) => i.id === n.data.repository.id)!.data.repository!.sshURL,
+							branch: n.data.repository.branch,
+							rootDir: n.data.repository.rootDir,
+						},
+						ports: (n.data.ports || []).map((p) => ({
+							name: p.name,
+							value: p.value,
+							protocol: "TCP", // TODO(gio)
+						})),
+						env: (n.data.envVars || [])
+							.filter((e) => "name" in e)
+							.map((e) => ({
+								name: e.name,
+								alias: "alias" in e ? e.alias : undefined,
+							})),
+						ingress: ingressNodes
+							.filter((i) => i.data.https!.serviceId === n.id)
+							.map(
+								(i: GatewayHttpsNode): Ingress => ({
+									network: networkMap.get(i.data.network!)!,
+									subdomain: i.data.subdomain!,
+									port: {
+										name: n.data.ports.find((p) => p.id === i.data.https!.portId)!.name,
+									},
+									auth:
+										i.data.auth?.enabled || false
+											? {
+													enabled: true,
+													groups: i.data.auth!.groups,
+													noAuthPathPatterns: i.data.auth!.noAuthPathPatterns,
+												}
+											: {
+													enabled: false,
+												},
+								}),
+							),
+						expose: findExpose(n),
+						preBuildCommands: n.data.preBuildCommands
+							? n.data.preBuildCommands.split("\n").map((cmd) => ({ bin: cmd }))
+							: [],
+					};
+				}),
+			volume: nodes
+				.filter((n) => n.type === "volume")
+				.map(
+					(n): Volume => ({
+						name: n.data.label,
+						accessMode: n.data.type,
+						size: n.data.size,
+					}),
+				),
+			postgresql: nodes
+				.filter((n) => n.type === "postgresql")
+				.map(
+					(n): PostgreSQL => ({
+						name: n.data.label,
+						size: "1Gi", // TODO(gio)
+						expose: findExpose(n).map((e) => ({ network: e.network, subdomain: e.subdomain })),
+					}),
+				),
+			mongodb: nodes
+				.filter((n) => n.type === "mongodb")
+				.map(
+					(n): MongoDB => ({
+						name: n.data.label,
+						size: "1Gi", // TODO(gio)
+						expose: findExpose(n).map((e) => ({ network: e.network, subdomain: e.subdomain })),
+					}),
+				),
+		};
+	} catch (e) {
+		console.log(e);
+		return null;
+	}
 }
 
 export interface Validator {
-    (nodes: AppNode[]): Message[];
+	(nodes: AppNode[]): Message[];
 }
 
 function CombineValidators(...v: Validator[]): Validator {
-    return (n) => v.flatMap((v) => v(n));
+	return (n) => v.flatMap((v) => v(n));
 }
 
 function MessageTypeToNumber(t: MessageType) {
-    switch (t) {
-        case "FATAL": return 0;
-        case "WARNING": return 1;
-        case "INFO": return 2;
-    }
+	switch (t) {
+		case "FATAL":
+			return 0;
+		case "WARNING":
+			return 1;
+		case "INFO":
+			return 2;
+	}
 }
 
 function NodeTypeToNumber(t?: NodeType) {
-    switch (t) {
-        case "github": return 0;
-        case "app": return 1;
-        case "volume": return 2;
-        case "postgresql": return 3;
-        case "mongodb": return 4;
-        case "gateway-https": return 5;
-        case undefined: return 100;
-    }
+	switch (t) {
+		case "github":
+			return 0;
+		case "app":
+			return 1;
+		case "volume":
+			return 2;
+		case "postgresql":
+			return 3;
+		case "mongodb":
+			return 4;
+		case "gateway-https":
+			return 5;
+		case undefined:
+			return 100;
+	}
 }
 
 function SortingValidator(v: Validator): Validator {
-    return (n) => {
-        const nt = new Map(n.map((n) => [n.id, NodeTypeToNumber(n.type)]))
-        return v(n).sort((a, b) => {
-            const at = MessageTypeToNumber(a.type);
-            const bt = MessageTypeToNumber(b.type);
-            if (a.nodeId === undefined && b.nodeId === undefined) {
-                if (at !== bt) {
-                    return at - bt;
-                }
-                return a.id.localeCompare(b.id);
-            }
-            if (a.nodeId === undefined) {
-                return -1;
-            }
-            if (b.nodeId === undefined) {
-                return 1;
-            }
-            if (a.nodeId === b.nodeId) {
-                if (at !== bt) {
-                    return at - bt;
-                }
-                return a.id.localeCompare(b.id);
-            }
-            const ant = nt.get(a.id)!;
-            const bnt = nt.get(b.id)!;
-            if (ant !== bnt) {
-                return ant - bnt;
-            }
-            return a.id.localeCompare(b.id);
-        });
-    };
+	return (n) => {
+		const nt = new Map(n.map((n) => [n.id, NodeTypeToNumber(n.type)]));
+		return v(n).sort((a, b) => {
+			const at = MessageTypeToNumber(a.type);
+			const bt = MessageTypeToNumber(b.type);
+			if (a.nodeId === undefined && b.nodeId === undefined) {
+				if (at !== bt) {
+					return at - bt;
+				}
+				return a.id.localeCompare(b.id);
+			}
+			if (a.nodeId === undefined) {
+				return -1;
+			}
+			if (b.nodeId === undefined) {
+				return 1;
+			}
+			if (a.nodeId === b.nodeId) {
+				if (at !== bt) {
+					return at - bt;
+				}
+				return a.id.localeCompare(b.id);
+			}
+			const ant = nt.get(a.id)!;
+			const bnt = nt.get(b.id)!;
+			if (ant !== bnt) {
+				return ant - bnt;
+			}
+			return a.id.localeCompare(b.id);
+		});
+	};
 }
 
 export function CreateValidators(): Validator {
-    return SortingValidator(
-        CombineValidators(
-            EmptyValidator,
-            GitRepositoryValidator,
-            ServiceValidator,
-            GatewayHTTPSValidator,
-            GatewayTCPValidator,
-        )
-    );
+	return SortingValidator(
+		CombineValidators(
+			EmptyValidator,
+			GitRepositoryValidator,
+			ServiceValidator,
+			GatewayHTTPSValidator,
+			GatewayTCPValidator,
+		),
+	);
 }
 
 function EmptyValidator(nodes: AppNode[]): Message[] {
-    nodes = nodes.filter((n) => n.type !== "network");
-    if (nodes.length > 0) {
-        return [];
-    }
-    return [{
-        id: "no-nodes",
-        type: "FATAL",
-        message: "Start by importing application source code",
-        onHighlight: (store) => store.setHighlightCategory("repository", true),
-        onLooseHighlight: (store) => store.setHighlightCategory("repository", false),
-    }];
+	nodes = nodes.filter((n) => n.type !== "network");
+	if (nodes.length > 0) {
+		return [];
+	}
+	return [
+		{
+			id: "no-nodes",
+			type: "FATAL",
+			message: "Start by importing application source code",
+			onHighlight: (store) => store.setHighlightCategory("repository", true),
+			onLooseHighlight: (store) => store.setHighlightCategory("repository", false),
+		},
+	];
 }
 
 function GitRepositoryValidator(nodes: AppNode[]): Message[] {
-    const git = nodes.filter((n) => n.type === "github");
-    const noAddress: Message[] = git.filter((n) => n.data == null || n.data.repository == null).map((n) => ({
-        id: `${n.id}-no-address`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Configure repository address",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-    } satisfies Message));
-    const noApp = git.filter((n) => !nodes.some((i) => i.type === "app" && i.data?.repository?.id === n.id)).map((n) => ({
-        id: `${n.id}-no-app`,
-        type: "WARNING",
-        nodeId: n.id,
-        message: "Connect to service",
-        onHighlight: (store) => store.setHighlightCategory("Services", true),
-        onLooseHighlight: (store) => store.setHighlightCategory("Services", false),
-    } satisfies Message));
-    return noAddress.concat(noApp);
+	const git = nodes.filter((n) => n.type === "github");
+	const noAddress: Message[] = git
+		.filter((n) => n.data == null || n.data.repository == null)
+		.map(
+			(n) =>
+				({
+					id: `${n.id}-no-address`,
+					type: "FATAL",
+					nodeId: n.id,
+					message: "Configure repository address",
+					onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+					onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+				}) satisfies Message,
+		);
+	const noApp = git
+		.filter((n) => !nodes.some((i) => i.type === "app" && i.data?.repository?.id === n.id))
+		.map(
+			(n) =>
+				({
+					id: `${n.id}-no-app`,
+					type: "WARNING",
+					nodeId: n.id,
+					message: "Connect to service",
+					onHighlight: (store) => store.setHighlightCategory("Services", true),
+					onLooseHighlight: (store) => store.setHighlightCategory("Services", false),
+				}) satisfies Message,
+		);
+	return noAddress.concat(noApp);
 }
 
 function ServiceValidator(nodes: AppNode[]): Message[] {
-    const apps = nodes.filter((n) => n.type === "app");
-    const noName = apps.filter((n) => n.data == null || n.data.label == null || n.data.label === "").map((n): Message => ({
-        id: `${n.id}-no-name`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Name the service",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-        onClick: (store) => {
-            store.updateNode(n.id, { selected: true });
-            store.updateNodeData<"app">(n.id, {
-                activeField: "name",
-            });
-        },
-    }));
-    const noSource = apps.filter((n) => n.data == null || n.data.repository == null || n.data.repository.id === "").map((n): Message => ({
-        id: `${n.id}-no-repo`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Connect to source repository",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-    }));
-    const noRuntime = apps.filter((n) => n.data == null || n.data.type == null).map((n): Message => ({
-        id: `${n.id}-no-runtime`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Choose runtime",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-        onClick: (store) => {
-            store.updateNode(n.id, { selected: true });
-            store.updateNodeData<"app">(n.id, {
-                activeField: "type",
-            });
-        },
-    }));
-    const noPorts = apps.filter((n) => n.data == null || n.data.ports == null || n.data.ports.length === 0).map((n): Message => ({
-        id: `${n.id}-no-ports`,
-        type: "INFO",
-        nodeId: n.id,
-        message: "Expose ports",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-    }));
-    const noIngress = apps.flatMap((n): Message[] => {
-        if (n.data == null) {
-            return [];
-        }
-        return (n.data.ports || []).filter((p) => !nodes.filter((i) => i.type === "gateway-https").some((i) => {
-            if (i.data && i.data.https && i.data.https.serviceId === n.id && i.data.https.portId === p.id) {
-                return true;
-            }
-            return false;
-        })).map((p): Message => ({
-            id: `${n.id}-${p.id}-no-ingress`,
-            type: "WARNING",
-            nodeId: n.id,
-            message: `Connect to ingress: ${p.name} - ${p.value}`,
-            onHighlight: (store) => {
-                store.updateNode(n.id, { selected: true });
-                store.setHighlightCategory("gateways", true);
-            },
-            onLooseHighlight: (store) => {
-                store.updateNode(n.id, { selected: false });
-                store.setHighlightCategory("gateways", false);
-            },
-        }));
-    });
-    const multipleIngress = apps.filter((n) => n.data != null && n.data.ports != null).flatMap((n) => n.data.ports.map((p): Message | undefined => {
-        const ing = nodes.filter((i) => i.type === "gateway-https").filter((i) => i.data && i.data.https && i.data.https.serviceId === n.id && i.data.https.portId === p.id);
-        if (ing.length < 2) {
-            return undefined;
-        }
-        return {
-            id: `${n.id}-${p.id}-multiple-ingress`,
-            type: "FATAL",
-            nodeId: n.id,
-            message: `Can not expose same port using multiple ingresses: ${p.name} - ${p.value}`,
-            onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-            onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-        };
-    })).filter((m) => m !== undefined);
-    return noName.concat(noSource).concat(noRuntime).concat(noPorts).concat(noIngress).concat(multipleIngress);
+	const apps = nodes.filter((n) => n.type === "app");
+	const noName = apps
+		.filter((n) => n.data == null || n.data.label == null || n.data.label === "")
+		.map(
+			(n): Message => ({
+				id: `${n.id}-no-name`,
+				type: "FATAL",
+				nodeId: n.id,
+				message: "Name the service",
+				onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+				onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+				onClick: (store) => {
+					store.updateNode(n.id, { selected: true });
+					store.updateNodeData<"app">(n.id, {
+						activeField: "name",
+					});
+				},
+			}),
+		);
+	const noSource = apps
+		.filter((n) => n.data == null || n.data.repository == null || n.data.repository.id === "")
+		.map(
+			(n): Message => ({
+				id: `${n.id}-no-repo`,
+				type: "FATAL",
+				nodeId: n.id,
+				message: "Connect to source repository",
+				onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+				onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+			}),
+		);
+	const noRuntime = apps
+		.filter((n) => n.data == null || n.data.type == null)
+		.map(
+			(n): Message => ({
+				id: `${n.id}-no-runtime`,
+				type: "FATAL",
+				nodeId: n.id,
+				message: "Choose runtime",
+				onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+				onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+				onClick: (store) => {
+					store.updateNode(n.id, { selected: true });
+					store.updateNodeData<"app">(n.id, {
+						activeField: "type",
+					});
+				},
+			}),
+		);
+	const noPorts = apps
+		.filter((n) => n.data == null || n.data.ports == null || n.data.ports.length === 0)
+		.map(
+			(n): Message => ({
+				id: `${n.id}-no-ports`,
+				type: "INFO",
+				nodeId: n.id,
+				message: "Expose ports",
+				onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+				onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+			}),
+		);
+	const noIngress = apps.flatMap((n): Message[] => {
+		if (n.data == null) {
+			return [];
+		}
+		return (n.data.ports || [])
+			.filter(
+				(p) =>
+					!nodes
+						.filter((i) => i.type === "gateway-https")
+						.some((i) => {
+							if (
+								i.data &&
+								i.data.https &&
+								i.data.https.serviceId === n.id &&
+								i.data.https.portId === p.id
+							) {
+								return true;
+							}
+							return false;
+						}),
+			)
+			.map(
+				(p): Message => ({
+					id: `${n.id}-${p.id}-no-ingress`,
+					type: "WARNING",
+					nodeId: n.id,
+					message: `Connect to ingress: ${p.name} - ${p.value}`,
+					onHighlight: (store) => {
+						store.updateNode(n.id, { selected: true });
+						store.setHighlightCategory("gateways", true);
+					},
+					onLooseHighlight: (store) => {
+						store.updateNode(n.id, { selected: false });
+						store.setHighlightCategory("gateways", false);
+					},
+				}),
+			);
+	});
+	const multipleIngress = apps
+		.filter((n) => n.data != null && n.data.ports != null)
+		.flatMap((n) =>
+			n.data.ports.map((p): Message | undefined => {
+				const ing = nodes
+					.filter((i) => i.type === "gateway-https")
+					.filter(
+						(i) =>
+							i.data && i.data.https && i.data.https.serviceId === n.id && i.data.https.portId === p.id,
+					);
+				if (ing.length < 2) {
+					return undefined;
+				}
+				return {
+					id: `${n.id}-${p.id}-multiple-ingress`,
+					type: "FATAL",
+					nodeId: n.id,
+					message: `Can not expose same port using multiple ingresses: ${p.name} - ${p.value}`,
+					onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+					onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+				};
+			}),
+		)
+		.filter((m) => m !== undefined);
+	return noName.concat(noSource).concat(noRuntime).concat(noPorts).concat(noIngress).concat(multipleIngress);
 }
 
 function GatewayHTTPSValidator(nodes: AppNode[]): Message[] {
-    const ing = nodes.filter((n) => n.type === "gateway-https");
-    const noNetwork: Message[] = ing.filter((n) => n.data == null || n.data.network == null || n.data.network == "" || n.data.subdomain == null || n.data.subdomain == "").map((n): Message => ({
-        id: `${n.id}-no-network`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Network and subdomain must be defined",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-    }));
-    const notConnected: Message[] = ing.filter((n) => n.data == null || n.data.https == null || n.data.https.serviceId == null || n.data.https.serviceId == "" || n.data.https.portId == null || n.data.https.portId == "").map((n) => ({
-        id: `${n.id}-not-connected`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Connect to a service port",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-    }));
-    return noNetwork.concat(notConnected);
+	const ing = nodes.filter((n) => n.type === "gateway-https");
+	const noNetwork: Message[] = ing
+		.filter(
+			(n) =>
+				n.data == null ||
+				n.data.network == null ||
+				n.data.network == "" ||
+				n.data.subdomain == null ||
+				n.data.subdomain == "",
+		)
+		.map(
+			(n): Message => ({
+				id: `${n.id}-no-network`,
+				type: "FATAL",
+				nodeId: n.id,
+				message: "Network and subdomain must be defined",
+				onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+				onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+			}),
+		);
+	const notConnected: Message[] = ing
+		.filter(
+			(n) =>
+				n.data == null ||
+				n.data.https == null ||
+				n.data.https.serviceId == null ||
+				n.data.https.serviceId == "" ||
+				n.data.https.portId == null ||
+				n.data.https.portId == "",
+		)
+		.map((n) => ({
+			id: `${n.id}-not-connected`,
+			type: "FATAL",
+			nodeId: n.id,
+			message: "Connect to a service port",
+			onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+			onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+		}));
+	return noNetwork.concat(notConnected);
 }
 
 function GatewayTCPValidator(nodes: AppNode[]): Message[] {
-    const ing = nodes.filter((n) => n.type === "gateway-tcp");
-    const noNetwork: Message[] = ing.filter((n) => n.data == null || n.data.network == null || n.data.network == "" || n.data.subdomain == null || n.data.subdomain == "").map((n): Message => ({
-        id: `${n.id}-no-network`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Network and subdomain must be defined",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-    }));
-    const notConnected: Message[] = ing.filter((n) => n.data == null || n.data.exposed == null || n.data.exposed.length === 0).map((n) => ({
-        id: `${n.id}-not-connected`,
-        type: "FATAL",
-        nodeId: n.id,
-        message: "Connect to a service port",
-        onHighlight: (store) => store.updateNode(n.id, { selected: true }),
-        onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
-    }));
-    return noNetwork.concat(notConnected);
+	const ing = nodes.filter((n) => n.type === "gateway-tcp");
+	const noNetwork: Message[] = ing
+		.filter(
+			(n) =>
+				n.data == null ||
+				n.data.network == null ||
+				n.data.network == "" ||
+				n.data.subdomain == null ||
+				n.data.subdomain == "",
+		)
+		.map(
+			(n): Message => ({
+				id: `${n.id}-no-network`,
+				type: "FATAL",
+				nodeId: n.id,
+				message: "Network and subdomain must be defined",
+				onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+				onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+			}),
+		);
+	const notConnected: Message[] = ing
+		.filter((n) => n.data == null || n.data.exposed == null || n.data.exposed.length === 0)
+		.map((n) => ({
+			id: `${n.id}-not-connected`,
+			type: "FATAL",
+			nodeId: n.id,
+			message: "Connect to a service port",
+			onHighlight: (store) => store.updateNode(n.id, { selected: true }),
+			onLooseHighlight: (store) => store.updateNode(n.id, { selected: false }),
+		}));
+	return noNetwork.concat(notConnected);
 }