diff --git a/src/App.jsx b/src/App.jsx index caf892a..0ad603a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,5 @@ import './App.css' -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; // Reusable input with tooltip component const InputWithTooltip = ({ type, name, placeholder, value, onChange, required, tooltip, className = "" }) => { @@ -36,23 +36,79 @@ const InputWithTooltip = ({ type, name, placeholder, value, onChange, required, ); }; +// SelectWithTooltip component +const SelectWithTooltip = ({ name, value, onChange, options, required, tooltip }) => { + const [showTooltip, setShowTooltip] = useState(false); + + return ( +
+
+ +
setShowTooltip(true)} + onMouseLeave={() => setShowTooltip(false)} + > + + + +
+
+ {showTooltip && ( +
+ {tooltip} +
+ )} +
+ ); +}; + export default function HcpCliAssistant() { - const steps = [ - "Cluster Details", - "OpenStack Authentication", - "Networking", - "Node Configuration", - "Review & Generate Command" + // Platform config + const platforms = [ + { value: "openstack", label: "OpenStack" }, + // Future platforms can be added here + // { value: "aws", label: "AWS" }, + // { value: "azure", label: "Azure" }, ]; + // Step configurations for each platform + const platformSteps = { + openstack: [ + "OpenStack Authentication", + "OpenStack Networking", + "OpenStack Node Configuration", + "Review & Generate Command" + ], + // Add more platform steps as they become available + }; + + // Common initial steps const [step, setStep] = useState(0); const [copied, setCopied] = useState(false); const [form, setForm] = useState({ + // Common fields name: "", baseDomain: "", - nodePoolReplicas: "", + nodePoolReplicas: "2", // Set a default value pullSecret: "", sshKey: "", + platform: "", + + // OpenStack specific fields osCloudSet: true, openstackCredentialsFile: "", openstackCaCertFile: "", @@ -63,9 +119,36 @@ export default function HcpCliAssistant() { nodeAZ: "", nodeImageName: "", dnsNameservers: "", - additionalPorts: "", + additionalPorts: JSON.stringify([]), }); + // Add this to your component + useEffect(() => { + // If we're on the platform selection step and there's only one platform available, select it automatically + if (step === 1 && platforms.length === 1 && !form.platform) { + setForm(prev => ({ + ...prev, + platform: platforms[0].value + })); + } + }, [step, platforms.length, form.platform]); + + // Get steps based on selected platform + const getSteps = () => { + const commonSteps = ["Cluster Details", "Platform Selection"]; + if (!form.platform) return commonSteps; + + // Ensure the selected platform exists in platformSteps + if (platformSteps[form.platform]) { + return [...commonSteps, ...platformSteps[form.platform]]; + } + + // Fallback if platform not found + return [...commonSteps, "Platform Not Supported"]; + }; + + const steps = getSteps(); + const handleChange = (e) => { const { name, value, type, checked } = e.target; setForm({ @@ -76,98 +159,126 @@ export default function HcpCliAssistant() { const isStepValid = () => { switch (step) { - case 0: - return form.name.trim() !== "" && form.baseDomain.trim() !== "" && form.nodePoolReplicas.trim() !== "" && form.pullSecret.trim() !== "" && form.sshKey.trim() !== ""; - case 1: - return form.osCloudSet || form.openstackCredentialsFile.trim() !== ""; - case 2: - if (form.additionalPorts) { - try { - const ports = JSON.parse(form.additionalPorts); - return ports.every((port) => port.networkId.trim() !== ""); - } catch (e) { - return false; + case 0: // Cluster Details + return form.name.trim() !== "" && + form.baseDomain.trim() !== "" && + form.nodePoolReplicas.trim() !== "" && + form.pullSecret.trim() !== "" && + form.sshKey.trim() !== ""; + case 1: // Platform Selection + return form.platform && form.platform.trim() !== "" && platformSteps[form.platform] !== undefined; + default: + // Platform specific validations - only if the platform is supported + if (form.platform === "openstack") { + const platformStep = step - 2; // Adjust for common steps + + switch (platformStep) { + case 0: // OpenStack Authentication + return form.osCloudSet || form.openstackCredentialsFile.trim() !== ""; + case 1: // OpenStack Networking + if (form.additionalPorts) { + try { + const ports = JSON.parse(form.additionalPorts); + // Only validate ports that have a networkId entered + const portsWithNetworkId = ports.filter(port => port.networkId && port.networkId.trim() !== ""); + return portsWithNetworkId.length === 0 || + portsWithNetworkId.every(port => port.networkId && port.networkId.trim() !== ""); + } catch (e) { + console.error("Error parsing additionalPorts:", e); + return false; + } + } + return true; + case 2: // OpenStack Node Configuration + return form.nodeFlavor.trim() !== ""; + default: + return true; } } - return true; - case 3: - return form.nodeFlavor.trim() !== ""; - default: - return true; + return false; // If the platform is not handled, validation fails } }; const generateCommand = () => { - let cmd = `hcp create cluster openstack \ - --name ${form.name} \ - --base-domain ${form.baseDomain} \ - --node-pool-replicas ${form.nodePoolReplicas} \ - --pull-secret ${form.pullSecret} \ - --ssh-key ${form.sshKey}`; + if (form.platform === "openstack") { + let cmd = `hcp create cluster openstack \ + --name ${form.name} \ + --base-domain ${form.baseDomain} \ + --node-pool-replicas ${form.nodePoolReplicas} \ + --pull-secret ${form.pullSecret} \ + --ssh-key ${form.sshKey}`; - if (!form.osCloudSet) { - cmd += ` \ - --openstack-credentials-file ${form.openstackCredentialsFile}`; - } - if (form.openstackCaCertFile) { - cmd += ` \ - --openstack-ca-cert-file ${form.openstackCaCertFile}`; - } - if (form.openstackCloud) { - cmd += ` \ - --openstack-cloud ${form.openstackCloud}`; - } - if (form.externalNetworkId) { - cmd += ` \ - --openstack-external-network-id ${form.externalNetworkId}`; - } - if (form.ingressFloatingIp) { - cmd += ` \ - --openstack-ingress-floating-ip ${form.ingressFloatingIp}`; - } - cmd += ` \ - --openstack-node-flavor ${form.nodeFlavor}`; - - if (form.dnsNameservers) { - cmd += ` \ - --openstack-dns-nameservers ${form.dnsNameservers}`; - } - - if (form.nodeAZ) { - cmd += ` \ - --openstack-node-availability-zone ${form.nodeAZ}`; - } - - if (form.nodeImageName) { + if (!form.osCloudSet) { + cmd += ` \ + --openstack-credentials-file ${form.openstackCredentialsFile}`; + } + if (form.openstackCaCertFile) { + cmd += ` \ + --openstack-ca-cert-file ${form.openstackCaCertFile}`; + } + if (form.openstackCloud) { + cmd += ` \ + --openstack-cloud ${form.openstackCloud}`; + } + if (form.externalNetworkId) { + cmd += ` \ + --openstack-external-network-id ${form.externalNetworkId}`; + } + if (form.ingressFloatingIp) { + cmd += ` \ + --openstack-ingress-floating-ip ${form.ingressFloatingIp}`; + } cmd += ` \ - --openstack-node-image-name ${form.nodeImageName}`; - } + --openstack-node-flavor ${form.nodeFlavor}`; - if (form.additionalPorts) { - const ports = JSON.parse(form.additionalPorts); - ports.forEach((port, index) => { - let portConfig = `--openstack-node-additional-port=network-id:${port.networkId}`; - - if (port.vnicType) { - portConfig += `,vnic-type:${port.vnicType}`; + if (form.dnsNameservers) { + cmd += ` \ + --openstack-dns-nameservers ${form.dnsNameservers}`; } - - if (port.addressPairs) { - portConfig += `,address-pairs:${port.addressPairs}`; + + if (form.nodeAZ) { + cmd += ` \ + --openstack-node-availability-zone ${form.nodeAZ}`; } - - if (port.disablePortSecurity) { - portConfig += `,disable-port-security:true`; + + if (form.nodeImageName) { + cmd += ` \ + --openstack-node-image-name ${form.nodeImageName}`; } - - cmd += ` \ - ${portConfig}`; - }); - } - cmd = cmd.replace(/\s+/g, ' ').trim(); + if (form.additionalPorts) { + try { + const ports = JSON.parse(form.additionalPorts); + ports.forEach((port) => { + if (port.networkId && port.networkId.trim() !== "") { + let portConfig = `--openstack-node-additional-port=network-id:${port.networkId}`; + + if (port.vnicType) { + portConfig += `,vnic-type:${port.vnicType}`; + } + + if (port.addressPairs) { + portConfig += `,address-pairs:${port.addressPairs}`; + } + + if (port.disablePortSecurity) { + portConfig += `,disable-port-security:true`; + } + + cmd += ` \ + ${portConfig}`; + } + }); + } catch (e) { + console.error("Error parsing additionalPorts during command generation:", e); + } + } - return cmd; + cmd = cmd.replace(/\s+/g, ' ').trim(); + return cmd; + } + + return "Platform command generation not implemented"; }; const handleCopy = () => { @@ -181,121 +292,150 @@ export default function HcpCliAssistant() { setStep((prev) => Math.min(prev + 1, steps.length - 1)); } }; + const prevStep = () => setStep((prev) => Math.max(prev - 1, 0)); - return ( -
-

Hypershift on OpenStack CLI assistant

-

Step {step + 1}: {steps[step]}

- - {step === 4 ? ( -
-

Generated Command:

-
{generateCommand()}
- -
- ) : ( -
- {step === 0 && <> - - - - - - } - {step === 1 && <> - - - {!form.osCloudSet && ( + - )} - - - - - } - - {step === 2 && ( + + ); + case 1: // OpenStack Networking + return ( <> - {/* Additional ports section remains the same */} + {/* Additional ports section */}

Additional Nodepool Ports (optional) @@ -345,12 +485,16 @@ export default function HcpCliAssistant() { Port {index + 1}

- )} - - {step === 3 && <> - - - - - - } + ); + case 2: // OpenStack Node Configuration + return ( + <> + + + + + + + ); + case 3: // Review & Generate Command (final step for OpenStack) + return ( +
+

Generated Command:

+
{generateCommand()}
+ +
+ ); + default: + return ( +
+

Unknown step for the OpenStack platform. Please go back and try again.

+
+ ); + } + } else if (form.platform) { + // For any platform that is selected but not implemented + return ( +
+

Support for the {form.platform} platform is coming soon!

- )} + ); + } + + return null; + }; + + // Determine if we're on the final review step + const isFinalStep = step === steps.length - 1; + + return ( +
+

Hypershift CLI Assistant

+ + {/* Progress bar */} +
+
+ {steps.map((stepName, idx) => ( +
+ {stepName} +
+ ))} +
+
+
+
+
+ +

Step {step + 1}: {steps[step]}

+ +
+ {renderStepContent()} +
{step > 0 && } - {step < steps.length - 1 && } + {!isFinalStep && ( + + )}
);