diff --git a/src/components/Connections/ConnectionForm.tsx b/src/components/Connections/ConnectionForm.tsx index d38a20bcb..14027f326 100644 --- a/src/components/Connections/ConnectionForm.tsx +++ b/src/components/Connections/ConnectionForm.tsx @@ -119,7 +119,7 @@ export function ConnectionForm({ }} onSubmit={handleSubmit} > - {() => ( + {({ values }) => (
{connectionType.fields.map((field, index) => { + // Only render the field if it doesn't have a condition or its condition returns true + if (field.condition && !field.condition(values)) { + return null; + } + return ( ); @@ -216,4 +221,4 @@ export function ConnectionForm({ ); } -export default ConnectionForm; +export default ConnectionForm; \ No newline at end of file diff --git a/src/components/Connections/RenderConnectionFormFields.tsx b/src/components/Connections/RenderConnectionFormFields.tsx index 3fd364a3b..8d650b215 100644 --- a/src/components/Connections/RenderConnectionFormFields.tsx +++ b/src/components/Connections/RenderConnectionFormFields.tsx @@ -4,14 +4,66 @@ import { FormikEnvVarSource } from "../Forms/Formik/FormikEnvVarSource"; import FormikSwitchField from "../Forms/Formik/FormikSwitchField"; import FormikTextInput from "../Forms/Formik/FormikTextInput"; import FormikConnectionOptionsSwitchField from "./FormikConnectionOptionsSwitchField"; -import { ConnectionFormFields } from "./connectionTypes"; +import { ConnectionFormFields, ConnectionValueType } from "./connectionTypes"; +import { useFormikContext } from "formik"; +import { useQuery } from "@tanstack/react-query"; +import { Connection } from "./ConnectionFormModal"; interface FieldViewProps { field: ConnectionFormFields; } +// Connection Select component for choosing connections by type +function ConnectionSelect({ field }: { field: ConnectionFormFields }) { + const { values, setFieldValue } = useFormikContext(); + + // Use the connections API endpoint to fetch all connections + const { data: connections, isLoading } = useQuery( + ['connections'], + async () => { + const response = await fetch('/api/connections'); + if (!response.ok) { + throw new Error('Failed to fetch connections'); + } + return response.json(); + }, + { staleTime: 30000 } + ); + + // Filter connections by the specified connection type + const filteredConnections = connections?.filter( + (connection: Connection) => connection.type === field.connectionType + ) || []; + + return ( +
+ + + {isLoading &&
Loading connections...
} + {field.hint &&

{field.hint}

} +
+ ); +} + export default function RenderConnectionFormFields({ field }: FieldViewProps) { const type = field.type ?? "input"; + switch (type) { case "input": return ( @@ -56,6 +108,9 @@ export default function RenderConnectionFormFields({ field }: FieldViewProps) { ); case "ConnectionSwitch": return ; + + case "ConnectionSelect": + return ; case "SwitchField": return ( @@ -91,4 +146,4 @@ export default function RenderConnectionFormFields({ field }: FieldViewProps) { default: return null; } -} +} \ No newline at end of file diff --git a/src/components/Connections/connectionTypes.tsx b/src/components/Connections/connectionTypes.tsx index 5f4981b28..6d7ed760e 100644 --- a/src/components/Connections/connectionTypes.tsx +++ b/src/components/Connections/connectionTypes.tsx @@ -11,7 +11,8 @@ const enum ConnectionsFieldTypes { SwitchField = "SwitchField", ConnectionSwitch = "ConnectionSwitch", Authentication = "authentication", - GroupField = "GroupField" + GroupField = "GroupField", + ConnectionSelect = "ConnectionSelect" } type Variant = "small" | "large"; @@ -44,6 +45,9 @@ export type ConnectionFormFields = { key: string; fields: Omit[]; }[]; + condition?: (data: Record) => boolean; + connectionType?: ConnectionValueType; + dependsOn?: string; }; export const enum ConnectionValueType { @@ -662,7 +666,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - port: data.properties?.port ?? 4 + port: data?.properties?.port ?? 4 } as Connection; }, preSubmitConverter: (data: Record) => { @@ -807,13 +811,361 @@ export const connectionTypes: ConnectionType[] = [ fields: [ ...commonConnectionFormFields, { - label: "Certificate", - key: "certificate", + label: "Connection Method", + key: "connectionMethod", + type: ConnectionsFieldTypes.SwitchField, + default: "kubeconfig", + switchFieldProps: { + options: [ + { + label: "Kubeconfig", + key: "kubeconfig" + }, + { + label: "EKS", + key: "eks" + }, + { + label: "GKE", + key: "gke" + }, + { + label: "CNRM", + key: "cnrm" + } + ] + }, + required: true + }, + // Kubeconfig option + { + label: "Kubeconfig", + key: "kubeconfig", type: ConnectionsFieldTypes.EnvVarSource, variant: variants.large, - required: false + required: false, + hint: "Source for kubeconfig", + condition: (data: Record) => data.connectionMethod === "kubeconfig" + }, + + // EKS Connection options - Use existing AWS connection + { + label: "Use Existing AWS Connection", + key: "eksUseExistingConnection", + type: ConnectionsFieldTypes.checkbox, + hint: "Use an existing AWS connection instead of configuring credentials directly", + condition: (data: Record) => data.connectionMethod === "eks" + }, + { + label: "AWS Connection", + key: "eksAwsConnection", + type: ConnectionsFieldTypes.ConnectionSelect, + connectionType: ConnectionValueType.AWS, + required: false, + hint: "Select an existing AWS connection to use with EKS", + condition: (data: Record) => + data.connectionMethod === "eks" && data.eksUseExistingConnection === true + }, + { + label: "Cluster Name", + key: "eksCluster", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Name of the EKS cluster", + condition: (data: Record) => data.connectionMethod === "eks" + }, + { + label: "Region", + key: "eksRegion", + type: ConnectionsFieldTypes.input, + required: false, + hint: "The AWS region", + condition: (data: Record) => data.connectionMethod === "eks" + }, + + // EKS Manual configuration options + { + label: "Access Key", + key: "eksAccessKey", + type: ConnectionsFieldTypes.EnvVarSource, + required: false, + condition: (data: Record) => + data.connectionMethod === "eks" && + (!data.eksUseExistingConnection || data.eksUseExistingConnection === false) + }, + { + label: "Secret Key", + key: "eksSecretKey", + type: ConnectionsFieldTypes.EnvVarSource, + required: false, + condition: (data: Record) => + data.connectionMethod === "eks" && + (!data.eksUseExistingConnection || data.eksUseExistingConnection === false) + }, + { + label: "Endpoint", + key: "eksEndpoint", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Custom AWS Endpoint to use", + condition: (data: Record) => + data.connectionMethod === "eks" && + (!data.eksUseExistingConnection || data.eksUseExistingConnection === false) + }, + { + label: "Skip TLS Verify", + key: "eksSkipTLSVerify", + type: ConnectionsFieldTypes.checkbox, + hint: "Skip TLS verify when connecting to AWS", + condition: (data: Record) => + data.connectionMethod === "eks" && + (!data.eksUseExistingConnection || data.eksUseExistingConnection === false) + }, + + // GKE Connection options - Use existing GCP connection + { + label: "Use Existing Google Cloud Connection", + key: "gkeUseExistingConnection", + type: ConnectionsFieldTypes.checkbox, + hint: "Use an existing Google Cloud connection instead of configuring credentials directly", + condition: (data: Record) => data.connectionMethod === "gke" + }, + { + label: "Google Cloud Connection", + key: "gkeGcpConnection", + type: ConnectionsFieldTypes.ConnectionSelect, + connectionType: ConnectionValueType.GCP, + required: false, + hint: "Select an existing Google Cloud connection to use with GKE", + condition: (data: Record) => + data.connectionMethod === "gke" && data.gkeUseExistingConnection === true + }, + { + label: "Cluster Name", + key: "gkeCluster", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Name of the GKE cluster", + condition: (data: Record) => data.connectionMethod === "gke" + }, + { + label: "Project", + key: "gkeProject", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Name of the GCP project", + condition: (data: Record) => data.connectionMethod === "gke" + }, + { + label: "Zone", + key: "gkeZone", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Name of the GCP zone", + condition: (data: Record) => data.connectionMethod === "gke" + }, + + // GKE Manual configuration options + { + label: "Credentials", + key: "gkeCredentials", + type: ConnectionsFieldTypes.EnvVarSource, + variant: variants.large, + required: false, + hint: "The credentials to use for authentication", + condition: (data: Record) => + data.connectionMethod === "gke" && + (!data.gkeUseExistingConnection || data.gkeUseExistingConnection === false) + }, + { + label: "Endpoint", + key: "gkeEndpoint", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Custom GCP Endpoint to use", + condition: (data: Record) => + data.connectionMethod === "gke" && + (!data.gkeUseExistingConnection || data.gkeUseExistingConnection === false) + }, + { + label: "Skip TLS Verify", + key: "gkeSkipTLSVerify", + type: ConnectionsFieldTypes.checkbox, + hint: "Skip TLS verification when connecting to GCP", + condition: (data: Record) => + data.connectionMethod === "gke" && + (!data.gkeUseExistingConnection || data.gkeUseExistingConnection === false) + }, + + // CNRM Connection options + { + label: "Cluster Resource", + key: "cnrmClusterResource", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Name of the cluster resource", + condition: (data: Record) => data.connectionMethod === "cnrm" + }, + { + label: "Cluster Resource Namespace", + key: "cnrmClusterResourceNamespace", + type: ConnectionsFieldTypes.input, + required: false, + hint: "Namespace of the cluster resource", + condition: (data: Record) => data.connectionMethod === "cnrm" } - ] + ], + convertToFormSpecificValue: (data: Record) => { + // Create a base connection object with standard fields + const connection: Connection = { + ...data, + name: data.name, + namespace: data.namespace, + type: ConnectionValueType.Kubernetes + }; + + // Create a form values object to store all the Kubernetes-specific fields + const formValues: Record = {}; + + // Set connection method from properties + if (data.properties?.connectionMethod) { + formValues.connectionMethod = data.properties.connectionMethod; + } + + // Handle kubeconfig + if (data.properties?.kubeconfig) { + formValues.kubeconfig = data.properties.kubeconfig; + } + + // Handle EKS configuration + if (data.properties?.eksAwsConnection) { + formValues.eksUseExistingConnection = true; + formValues.eksAwsConnection = data.properties.eksAwsConnection; + } + + if (data.properties?.eksCluster) { + formValues.eksCluster = data.properties.eksCluster; + } + + if (data.properties?.eksRegion) { + formValues.eksRegion = data.properties.eksRegion; + } + + if (data.properties?.eksAccessKey) { + formValues.eksAccessKey = data.properties.eksAccessKey; + } + + if (data.properties?.eksSecretKey) { + formValues.eksSecretKey = data.properties.eksSecretKey; + } + + if (data.properties?.eksEndpoint) { + formValues.eksEndpoint = data.properties.eksEndpoint; + } + + if (data.properties?.eksSkipTLSVerify) { + formValues.eksSkipTLSVerify = data.properties.eksSkipTLSVerify; + } + + // Handle GKE configuration + if (data.properties?.gkeGcpConnection) { + formValues.gkeUseExistingConnection = true; + formValues.gkeGcpConnection = data.properties.gkeGcpConnection; + } + + if (data.properties?.gkeCluster) { + formValues.gkeCluster = data.properties.gkeCluster; + } + + if (data.properties?.gkeProject) { + formValues.gkeProject = data.properties.gkeProject; + } + + if (data.properties?.gkeZone) { + formValues.gkeZone = data.properties.gkeZone; + } + + if (data.properties?.gkeCredentials) { + formValues.gkeCredentials = data.properties.gkeCredentials; + } + + if (data.properties?.gkeEndpoint) { + formValues.gkeEndpoint = data.properties.gkeEndpoint; + } + + if (data.properties?.gkeSkipTLSVerify) { + formValues.gkeSkipTLSVerify = data.properties.gkeSkipTLSVerify; + } + + // Handle CNRM configuration + if (data.properties?.cnrmClusterResource) { + formValues.cnrmClusterResource = data.properties.cnrmClusterResource; + } + + if (data.properties?.cnrmClusterResourceNamespace) { + formValues.cnrmClusterResourceNamespace = data.properties.cnrmClusterResourceNamespace; + } + + // Merge the form values with the connection + return { + ...connection, + ...formValues + }; + }, + preSubmitConverter: (data: Record) => { + const connectionMethod = data.connectionMethod || "kubeconfig"; + + const properties: Record = { + connectionMethod + }; + + if (connectionMethod === "kubeconfig" && data.kubeconfig) { + properties.kubeconfig = data.kubeconfig; + } + + if (connectionMethod === "eks") { + properties.eksCluster = data.eksCluster; + properties.eksRegion = data.eksRegion; + + // Handle existing connection + if (data.eksUseExistingConnection === "true" && data.eksAwsConnection) { + properties.eksUseExistingConnection = true; + properties.eksAwsConnection = data.eksAwsConnection; + } else { + properties.eksAccessKey = data.eksAccessKey; + properties.eksSecretKey = data.eksSecretKey; + properties.eksEndpoint = data.eksEndpoint; + properties.eksSkipTLSVerify = data.eksSkipTLSVerify; + } + } + + if (connectionMethod === "gke") { + properties.gkeCluster = data.gkeCluster; + properties.gkeProject = data.gkeProject; + properties.gkeZone = data.gkeZone; + + // Handle existing connection + if (data.gkeUseExistingConnection === "true" && data.gkeGcpConnection) { + properties.gkeUseExistingConnection = true; + properties.gkeGcpConnection = data.gkeGcpConnection; + } else { + properties.gkeCredentials = data.gkeCredentials; + properties.gkeEndpoint = data.gkeEndpoint; + properties.gkeSkipTLSVerify = data.gkeSkipTLSVerify; + } + } + + if (connectionMethod === "cnrm") { + properties.cnrmClusterResource = data.cnrmClusterResource; + properties.cnrmClusterResourceNamespace = data.cnrmClusterResourceNamespace; + } + + return { + name: data.name, + namespace: data.namespace, + properties + }; + } }, { title: "Azure Devops", @@ -2069,4 +2421,4 @@ export const connectionTypes: ConnectionType[] = [ .sort((v1, v2) => { return stringSortHelper(v1.title, v2.title); }) - .filter((item) => !item.hide); + .filter((item) => !item.hide); \ No newline at end of file