|
|
@@ -1,4 +1,4 @@
|
|
|
-import React, { Component } from "react";
|
|
|
+import React, { Component, useContext, useEffect, useState } from "react";
|
|
|
import styled from "styled-components";
|
|
|
|
|
|
import close from "assets/close.png";
|
|
|
@@ -15,25 +15,15 @@ import Helper from "components/form-components/Helper";
|
|
|
import Heading from "components/form-components/Heading";
|
|
|
import SaveButton from "components/SaveButton";
|
|
|
import CheckboxList from "components/form-components/CheckboxList";
|
|
|
-import { RouteComponentProps, withRouter } from "react-router";
|
|
|
+import { useHistory, useLocation } from "react-router";
|
|
|
|
|
|
-type PropsType = RouteComponentProps & {
|
|
|
+type PropsType = {
|
|
|
setSelectedProvisioner: (x: string | null) => void;
|
|
|
handleError: () => void;
|
|
|
projectName: string;
|
|
|
infras: InfraType[];
|
|
|
-};
|
|
|
-
|
|
|
-type StateType = {
|
|
|
- awsRegion: string;
|
|
|
- awsMachineType: string;
|
|
|
- awsAccessId: string;
|
|
|
- awsSecretKey: string;
|
|
|
- clusterName: string;
|
|
|
- clusterNameSet: boolean;
|
|
|
- selectedInfras: { value: string; label: string }[];
|
|
|
- buttonStatus: string;
|
|
|
- provisionConfirmed: boolean;
|
|
|
+ highlightCosts?: boolean;
|
|
|
+ trackOnSave: () => void;
|
|
|
};
|
|
|
|
|
|
const provisionOptions = [
|
|
|
@@ -73,29 +63,49 @@ const machineTypeOptions = [
|
|
|
{ value: "t3.2xlarge", label: "t3.2xlarge" },
|
|
|
];
|
|
|
|
|
|
-// TODO: Consolidate across forms w/ HOC
|
|
|
-class AWSFormSection extends Component<PropsType, StateType> {
|
|
|
- state = {
|
|
|
- awsRegion: "us-east-1",
|
|
|
- awsMachineType: "t2.medium",
|
|
|
- awsAccessId: "",
|
|
|
- awsSecretKey: "",
|
|
|
- clusterName: "",
|
|
|
- clusterNameSet: false,
|
|
|
- selectedInfras: [...provisionOptions],
|
|
|
- buttonStatus: "",
|
|
|
- provisionConfirmed: false,
|
|
|
- };
|
|
|
+const costMapping: Record<string, number> = {
|
|
|
+ "t2.medium": 35,
|
|
|
+ "t2.xlarge": 135,
|
|
|
+ "t2.2xlarge": 270,
|
|
|
+ "t3.medium": 30,
|
|
|
+ "t3.xlarge": 120,
|
|
|
+ "t3.2xlarge": 240,
|
|
|
+};
|
|
|
+
|
|
|
+const AWSFormSectionFC: React.FC<PropsType> = (props) => {
|
|
|
+ const [awsRegion, setAwsRegion] = useState("us-east-1");
|
|
|
+ const [awsMachineType, setAwsMachineType] = useState("t2.medium");
|
|
|
+ const [awsAccessId, setAwsAccessId] = useState("");
|
|
|
+ const [awsSecretKey, setAwsSecretKey] = useState("");
|
|
|
+ const [clusterName, setClusterName] = useState("");
|
|
|
+ const [clusterNameSet, setClusterNameSet] = useState(false);
|
|
|
+ const [selectedInfras, setSelectedInfras] = useState([...provisionOptions]);
|
|
|
+ const [buttonStatus, setButtonStatus] = useState("");
|
|
|
+ const [provisionConfirmed, setProvisionConfirmed] = useState(false);
|
|
|
+ // This is added only for tracking purposes
|
|
|
+ // With this prop we will track down if the user has had an intent of filling the formulary
|
|
|
+ const [isFormDirty, setIsFormDirty] = useState(false);
|
|
|
+
|
|
|
+ const context = useContext(Context);
|
|
|
+
|
|
|
+ const location = useLocation();
|
|
|
+ const history = useHistory();
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (!isFormDirty) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- componentDidMount = () => {
|
|
|
- let { infras } = this.props;
|
|
|
- let { selectedInfras } = this.state;
|
|
|
- this.setClusterNameIfNotSet();
|
|
|
+ window.analytics?.track("provision_form-dirty", {
|
|
|
+ provider: "aws",
|
|
|
+ });
|
|
|
+ }, [isFormDirty]);
|
|
|
|
|
|
- if (infras) {
|
|
|
+ useEffect(() => {
|
|
|
+ if (props.infras) {
|
|
|
// From the dashboard, only uncheck and disable if "creating" or "created"
|
|
|
let filtered = selectedInfras;
|
|
|
- infras.forEach((infra: InfraType, i: number) => {
|
|
|
+ props.infras.forEach((infra: InfraType, i: number) => {
|
|
|
let { kind, status } = infra;
|
|
|
if (status === "creating" || status === "created") {
|
|
|
filtered = filtered.filter((item: any) => {
|
|
|
@@ -103,45 +113,30 @@ class AWSFormSection extends Component<PropsType, StateType> {
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
- this.setState({ selectedInfras: filtered });
|
|
|
+ setSelectedInfras(filtered);
|
|
|
}
|
|
|
- };
|
|
|
+ }, [props.infras]);
|
|
|
|
|
|
- componentDidUpdate = (prevProps: PropsType, prevState: StateType) => {
|
|
|
- if (prevProps.projectName != this.props.projectName) {
|
|
|
- this.setClusterNameIfNotSet();
|
|
|
- }
|
|
|
- };
|
|
|
+ useEffect(() => {
|
|
|
+ setClusterNameIfNotSet();
|
|
|
+ }, [props.projectName]);
|
|
|
|
|
|
- setClusterNameIfNotSet = () => {
|
|
|
- let projectName =
|
|
|
- this.props.projectName || this.context.currentProject?.name;
|
|
|
+ const setClusterNameIfNotSet = () => {
|
|
|
+ let projectName = props.projectName || context.currentProject?.name;
|
|
|
|
|
|
- if (
|
|
|
- !this.state.clusterNameSet &&
|
|
|
- !this.state.clusterName.includes(`${projectName}-cluster`)
|
|
|
- ) {
|
|
|
- this.setState({
|
|
|
- clusterName: `${projectName}-cluster-${Math.random()
|
|
|
- .toString(36)
|
|
|
- .substring(2, 8)}`,
|
|
|
- });
|
|
|
+ if (!clusterNameSet && !clusterName.includes(`${projectName}-cluster`)) {
|
|
|
+ setClusterName(
|
|
|
+ `${projectName}-cluster-${Math.random().toString(36).substring(2, 8)}`
|
|
|
+ );
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- checkFormDisabled = () => {
|
|
|
- if (!this.state.provisionConfirmed) {
|
|
|
+ const checkFormDisabled = () => {
|
|
|
+ if (!provisionConfirmed) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- let {
|
|
|
- awsRegion,
|
|
|
- awsAccessId,
|
|
|
- awsSecretKey,
|
|
|
- selectedInfras,
|
|
|
- clusterName,
|
|
|
- } = this.state;
|
|
|
- let { projectName } = this.props;
|
|
|
+ const { projectName } = props;
|
|
|
if (projectName || projectName === "") {
|
|
|
return (
|
|
|
!isAlphanumeric(projectName) ||
|
|
|
@@ -165,172 +160,144 @@ class AWSFormSection extends Component<PropsType, StateType> {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- catchError = (err: any) => {
|
|
|
+ const catchError = (err: any) => {
|
|
|
console.log(err);
|
|
|
- this.props.handleError();
|
|
|
+ props.handleError();
|
|
|
};
|
|
|
|
|
|
// Step 1: Create a project
|
|
|
// TODO: promisify this function
|
|
|
- createProject = (callback?: any) => {
|
|
|
- let { projectName } = this.props;
|
|
|
- let { user, setProjects, setCurrentProject } = this.context;
|
|
|
-
|
|
|
- api
|
|
|
- .createProject("<token>", { name: projectName }, {})
|
|
|
- .then((res) => {
|
|
|
- let proj = res.data;
|
|
|
- // Need to set project list for dropdown
|
|
|
- // TODO: consolidate into ProjectSection (case on exists in list on set)
|
|
|
- api
|
|
|
- .getProjects(
|
|
|
- "<token>",
|
|
|
- {},
|
|
|
- {
|
|
|
- id: user.userId,
|
|
|
- }
|
|
|
- )
|
|
|
- .then((res) => {
|
|
|
- setProjects(res.data);
|
|
|
- setCurrentProject(proj, () => {
|
|
|
- callback && callback();
|
|
|
- });
|
|
|
- })
|
|
|
- .catch(this.catchError);
|
|
|
- })
|
|
|
- .catch(this.catchError);
|
|
|
+ const createProject = async () => {
|
|
|
+ const { projectName } = props;
|
|
|
+ const { user, setProjects, setCurrentProject } = context;
|
|
|
+ try {
|
|
|
+ const project = await api
|
|
|
+ .createProject("<token>", { name: projectName }, {})
|
|
|
+ .then((res) => res.data);
|
|
|
+
|
|
|
+ // Need to set project list for dropdown
|
|
|
+ // TODO: consolidate into ProjectSection (case on exists in list on set)
|
|
|
+ const projectList = await api
|
|
|
+ .getProjects(
|
|
|
+ "<token>",
|
|
|
+ {},
|
|
|
+ {
|
|
|
+ id: user.userId,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then((res) => res.data);
|
|
|
+ setProjects(projectList);
|
|
|
+ setCurrentProject(project);
|
|
|
+ } catch (error) {
|
|
|
+ catchError(error);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
- provisionECR = () => {
|
|
|
- console.log("Provisioning ECR");
|
|
|
- let { awsAccessId, awsSecretKey, awsRegion } = this.state;
|
|
|
- let { currentProject } = this.context;
|
|
|
-
|
|
|
- return api
|
|
|
- .createAWSIntegration(
|
|
|
+ const getAwsIntegrationId = async () => {
|
|
|
+ const { currentProject } = context;
|
|
|
+ try {
|
|
|
+ const res = await api.createAWSIntegration(
|
|
|
"<token>",
|
|
|
{
|
|
|
aws_region: awsRegion,
|
|
|
aws_access_key_id: awsAccessId,
|
|
|
aws_secret_access_key: awsSecretKey,
|
|
|
+ aws_cluster_id: clusterName,
|
|
|
},
|
|
|
{ id: currentProject.id }
|
|
|
- )
|
|
|
- .then((res) =>
|
|
|
- api.provisionECR(
|
|
|
- "<token>",
|
|
|
- {
|
|
|
- aws_integration_id: res.data.id,
|
|
|
- ecr_name: `${currentProject.name}-registry`,
|
|
|
- },
|
|
|
- { id: currentProject.id }
|
|
|
- )
|
|
|
- )
|
|
|
- .catch(this.catchError);
|
|
|
+ );
|
|
|
+ return res.data;
|
|
|
+ } catch (error) {
|
|
|
+ catchError(error);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
- provisionEKS = () => {
|
|
|
- let {
|
|
|
- awsAccessId,
|
|
|
- awsSecretKey,
|
|
|
- awsRegion,
|
|
|
- awsMachineType,
|
|
|
- clusterName,
|
|
|
- } = this.state;
|
|
|
- let { currentProject } = this.context;
|
|
|
-
|
|
|
- api
|
|
|
- .createAWSIntegration(
|
|
|
+ const provisionECR = async (awsIntegrationId: string) => {
|
|
|
+ console.log("Started provision ECR");
|
|
|
+ const { currentProject } = context;
|
|
|
+ try {
|
|
|
+ await api.provisionECR(
|
|
|
"<token>",
|
|
|
{
|
|
|
- aws_region: awsRegion,
|
|
|
- aws_access_key_id: awsAccessId,
|
|
|
- aws_secret_access_key: awsSecretKey,
|
|
|
- aws_cluster_id: clusterName,
|
|
|
+ aws_integration_id: awsIntegrationId,
|
|
|
+ ecr_name: `${currentProject.name}-registry`,
|
|
|
},
|
|
|
{ id: currentProject.id }
|
|
|
- )
|
|
|
- .then((res) =>
|
|
|
- api.provisionEKS(
|
|
|
- "<token>",
|
|
|
- {
|
|
|
- aws_integration_id: res.data.id,
|
|
|
- eks_name: clusterName,
|
|
|
- machine_type: awsMachineType,
|
|
|
- },
|
|
|
- { id: currentProject.id }
|
|
|
- )
|
|
|
- )
|
|
|
- .then(() =>
|
|
|
- pushFiltered(this.props, "/dashboard", ["project_id"], {
|
|
|
- tab: "provisioner",
|
|
|
- })
|
|
|
- )
|
|
|
- .catch(this.catchError);
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ catchError(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const provisionEKS = async (awsIntegrationId: string) => {
|
|
|
+ const { currentProject } = context;
|
|
|
+ try {
|
|
|
+ await api.provisionEKS(
|
|
|
+ "<token>",
|
|
|
+ {
|
|
|
+ aws_integration_id: awsIntegrationId,
|
|
|
+ eks_name: clusterName,
|
|
|
+ machine_type: awsMachineType,
|
|
|
+ },
|
|
|
+ { id: currentProject.id }
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ catchError(error);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
// TODO: handle generically (with > 2 steps)
|
|
|
- onCreateAWS = () => {
|
|
|
- this.setState({ buttonStatus: "loading" });
|
|
|
- let { projectName } = this.props;
|
|
|
- let { selectedInfras } = this.state;
|
|
|
-
|
|
|
- if (!projectName) {
|
|
|
- if (selectedInfras.length === 2) {
|
|
|
- // Case: project exists, provision ECR + EKS
|
|
|
- this.provisionECR().then(this.provisionEKS);
|
|
|
- } else if (selectedInfras[0].value === "ecr") {
|
|
|
- // Case: project exists, only provision ECR
|
|
|
- this.provisionECR().then(() =>
|
|
|
- pushFiltered(this.props, "/dashboard", ["project_id"], {
|
|
|
- tab: "provisioner",
|
|
|
- })
|
|
|
- );
|
|
|
- } else {
|
|
|
- // Case: project exists, only provision EKS
|
|
|
- this.provisionEKS();
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (selectedInfras.length === 2) {
|
|
|
- // Case: project DNE, provision ECR + EKS
|
|
|
- this.createProject(() => this.provisionECR().then(this.provisionEKS));
|
|
|
- } else if (selectedInfras[0].value === "ecr") {
|
|
|
- // Case: project DNE, only provision ECR
|
|
|
- this.createProject(() =>
|
|
|
- this.provisionECR().then(() =>
|
|
|
- pushFiltered(this.props, "/dashboard", ["project_id"], {
|
|
|
- tab: "provisioner",
|
|
|
- })
|
|
|
- )
|
|
|
- );
|
|
|
- } else {
|
|
|
- // Case: project DNE, only provision EKS
|
|
|
- this.createProject(this.provisionEKS);
|
|
|
- }
|
|
|
+ const onCreateAWS = async () => {
|
|
|
+ // Track to segment the intent of provision cluster
|
|
|
+ props?.trackOnSave();
|
|
|
+ setButtonStatus("loading");
|
|
|
+ const { projectName } = props;
|
|
|
+
|
|
|
+ if (projectName) {
|
|
|
+ await createProject();
|
|
|
}
|
|
|
+
|
|
|
+ const awsIntegrationId = await getAwsIntegrationId();
|
|
|
+
|
|
|
+ const filterNonAWSInfras = (infra: any) =>
|
|
|
+ ["ecr", "eks"].includes(infra.value);
|
|
|
+
|
|
|
+ const infraCreationRequests = selectedInfras
|
|
|
+ // Check that we don't include any other key into the infra creation than ecr and eks
|
|
|
+ .filter(filterNonAWSInfras)
|
|
|
+ .map((infra) => {
|
|
|
+ if (infra.value === "ecr") {
|
|
|
+ return provisionECR(awsIntegrationId?.id);
|
|
|
+ }
|
|
|
+ return provisionEKS(awsIntegrationId?.id);
|
|
|
+ });
|
|
|
+ // Wait for all promises to be completed (could be just one)
|
|
|
+ await Promise.all(infraCreationRequests);
|
|
|
+
|
|
|
+ pushFiltered({ history, location }, "/dashboard", ["project_id"], {
|
|
|
+ tab: "provisioner",
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
- getButtonStatus = () => {
|
|
|
- if (this.props.projectName) {
|
|
|
- if (!isAlphanumeric(this.props.projectName)) {
|
|
|
+ const getButtonStatus = () => {
|
|
|
+ if (props.projectName) {
|
|
|
+ if (!isAlphanumeric(props.projectName)) {
|
|
|
return "Project name contains illegal characters";
|
|
|
}
|
|
|
}
|
|
|
if (
|
|
|
- !this.state.awsAccessId ||
|
|
|
- !this.state.awsSecretKey ||
|
|
|
- !this.state.provisionConfirmed ||
|
|
|
- !this.state.clusterName ||
|
|
|
- this.props.projectName === ""
|
|
|
+ !awsAccessId ||
|
|
|
+ !awsSecretKey ||
|
|
|
+ !provisionConfirmed ||
|
|
|
+ !clusterName ||
|
|
|
+ props.projectName === ""
|
|
|
) {
|
|
|
return "Required fields missing";
|
|
|
}
|
|
|
- return this.state.buttonStatus;
|
|
|
+ return buttonStatus;
|
|
|
};
|
|
|
|
|
|
- renderClusterNameSection = () => {
|
|
|
- let { selectedInfras, clusterName } = this.state;
|
|
|
-
|
|
|
+ const renderClusterNameSection = () => {
|
|
|
if (
|
|
|
selectedInfras.length == 2 ||
|
|
|
(selectedInfras.length == 1 && selectedInfras[0].value === "eks")
|
|
|
@@ -339,9 +306,11 @@ class AWSFormSection extends Component<PropsType, StateType> {
|
|
|
<InputRow
|
|
|
type="text"
|
|
|
value={clusterName}
|
|
|
- setValue={(x: string) =>
|
|
|
- this.setState({ clusterName: x, clusterNameSet: true })
|
|
|
- }
|
|
|
+ setValue={(x: string) => {
|
|
|
+ setIsFormDirty(true);
|
|
|
+ setClusterName(x);
|
|
|
+ setClusterNameSet(true);
|
|
|
+ }}
|
|
|
label="Cluster Name"
|
|
|
placeholder="ex: porter-cluster"
|
|
|
width="100%"
|
|
|
@@ -351,121 +320,155 @@ class AWSFormSection extends Component<PropsType, StateType> {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- render() {
|
|
|
- let { setSelectedProvisioner } = this.props;
|
|
|
- let {
|
|
|
- awsRegion,
|
|
|
- awsMachineType,
|
|
|
- awsAccessId,
|
|
|
- awsSecretKey,
|
|
|
- selectedInfras,
|
|
|
- } = this.state;
|
|
|
-
|
|
|
- return (
|
|
|
- <StyledAWSFormSection>
|
|
|
- <FormSection>
|
|
|
- <CloseButton onClick={() => setSelectedProvisioner(null)}>
|
|
|
- <CloseButtonImg src={close} />
|
|
|
- </CloseButton>
|
|
|
- <Heading isAtTop={true}>
|
|
|
- AWS Credentials
|
|
|
- <GuideButton
|
|
|
- href="https://docs.getporter.dev/docs/getting-started-with-porter-on-aws"
|
|
|
- target="_blank"
|
|
|
- >
|
|
|
- <i className="material-icons-outlined">help</i>
|
|
|
- Guide
|
|
|
- </GuideButton>
|
|
|
- </Heading>
|
|
|
- <SelectRow
|
|
|
- options={regionOptions}
|
|
|
- width="100%"
|
|
|
- value={awsRegion}
|
|
|
- dropdownMaxHeight="240px"
|
|
|
- setActiveValue={(x: string) => this.setState({ awsRegion: x })}
|
|
|
- label="📍 AWS Region"
|
|
|
- />
|
|
|
- <SelectRow
|
|
|
- options={machineTypeOptions}
|
|
|
- width="100%"
|
|
|
- value={awsMachineType}
|
|
|
- dropdownMaxHeight="240px"
|
|
|
- setActiveValue={(x: string) => this.setState({ awsMachineType: x })}
|
|
|
- label="⚙️ AWS Machine Type"
|
|
|
- />
|
|
|
- <InputRow
|
|
|
- type="text"
|
|
|
- value={awsAccessId}
|
|
|
- setValue={(x: string) => this.setState({ awsAccessId: x })}
|
|
|
- label="👤 AWS Access ID"
|
|
|
- placeholder="ex: AKIAIOSFODNN7EXAMPLE"
|
|
|
- width="100%"
|
|
|
- isRequired={true}
|
|
|
- />
|
|
|
- <InputRow
|
|
|
- type="password"
|
|
|
- value={awsSecretKey}
|
|
|
- setValue={(x: string) => this.setState({ awsSecretKey: x })}
|
|
|
- label="🔒 AWS Secret Key"
|
|
|
- placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
|
|
|
- width="100%"
|
|
|
- isRequired={true}
|
|
|
- />
|
|
|
- <Br />
|
|
|
- <Heading>AWS Resources</Heading>
|
|
|
- <Helper>
|
|
|
- Porter will provision the following AWS resources in your own cloud.
|
|
|
- </Helper>
|
|
|
- <CheckboxList
|
|
|
- options={provisionOptions}
|
|
|
- selected={selectedInfras}
|
|
|
- setSelected={(x: { value: string; label: string }[]) => {
|
|
|
- this.setState({ selectedInfras: x });
|
|
|
- }}
|
|
|
- />
|
|
|
- {this.renderClusterNameSection()}
|
|
|
- <Helper>
|
|
|
- By default, Porter creates a cluster with three t2.medium instances
|
|
|
- (2vCPUs and 4GB RAM each). AWS will bill you for any provisioned
|
|
|
- resources. Learn more about EKS pricing
|
|
|
- <Highlight
|
|
|
- href="https://aws.amazon.com/eks/pricing/"
|
|
|
- target="_blank"
|
|
|
- >
|
|
|
- here
|
|
|
- </Highlight>
|
|
|
- .
|
|
|
- </Helper>
|
|
|
- <CheckboxRow
|
|
|
- isRequired={true}
|
|
|
- checked={this.state.provisionConfirmed}
|
|
|
- toggle={() =>
|
|
|
- this.setState({
|
|
|
- provisionConfirmed: !this.state.provisionConfirmed,
|
|
|
- })
|
|
|
- }
|
|
|
- label="I understand and wish to proceed"
|
|
|
- />
|
|
|
- </FormSection>
|
|
|
- {this.props.children ? this.props.children : <Padding />}
|
|
|
- <SaveButton
|
|
|
- text="Submit"
|
|
|
- disabled={
|
|
|
- this.checkFormDisabled() || this.state.buttonStatus === "loading"
|
|
|
- }
|
|
|
- onClick={this.onCreateAWS}
|
|
|
- makeFlush={true}
|
|
|
- status={this.getButtonStatus()}
|
|
|
- helper="Note: Provisioning can take up to 15 minutes"
|
|
|
- />
|
|
|
- </StyledAWSFormSection>
|
|
|
+ const goToGuide = () => {
|
|
|
+ window?.analytics?.track("provision_go-to-guide", {
|
|
|
+ hosting: "aws",
|
|
|
+ });
|
|
|
+
|
|
|
+ window.open(
|
|
|
+ "https://docs.getporter.dev/docs/getting-started-with-porter-on-aws"
|
|
|
);
|
|
|
- }
|
|
|
-}
|
|
|
+ };
|
|
|
|
|
|
-AWSFormSection.contextType = Context;
|
|
|
+ return (
|
|
|
+ <StyledAWSFormSection>
|
|
|
+ <FormSection>
|
|
|
+ <CloseButton onClick={() => props.setSelectedProvisioner(null)}>
|
|
|
+ <CloseButtonImg src={close} />
|
|
|
+ </CloseButton>
|
|
|
+ <Heading isAtTop={true}>
|
|
|
+ AWS Credentials
|
|
|
+ <GuideButton onClick={() => goToGuide()}>
|
|
|
+ <i className="material-icons-outlined">help</i>
|
|
|
+ Guide
|
|
|
+ </GuideButton>
|
|
|
+ </Heading>
|
|
|
+ <SelectRow
|
|
|
+ options={regionOptions}
|
|
|
+ width="100%"
|
|
|
+ value={awsRegion}
|
|
|
+ dropdownMaxHeight="240px"
|
|
|
+ setActiveValue={(x: string) => {
|
|
|
+ setIsFormDirty(true);
|
|
|
+ setAwsRegion(x);
|
|
|
+ }}
|
|
|
+ label="📍 AWS Region"
|
|
|
+ />
|
|
|
+ <SelectRow
|
|
|
+ options={machineTypeOptions}
|
|
|
+ width="100%"
|
|
|
+ value={awsMachineType}
|
|
|
+ dropdownMaxHeight="240px"
|
|
|
+ setActiveValue={(x: string) => {
|
|
|
+ setIsFormDirty(true);
|
|
|
+ setAwsMachineType(x);
|
|
|
+ }}
|
|
|
+ label="⚙️ AWS Machine Type"
|
|
|
+ />
|
|
|
+ {/*
|
|
|
+ <Helper>
|
|
|
+ Estimated Cost:{" "}
|
|
|
+ <CostHighlight highlight={this.props.highlightCosts}>
|
|
|
+ {`\$${
|
|
|
+ 70 + 3 * costMapping[this.state.awsMachineType] + 30
|
|
|
+ }/Month`}
|
|
|
+ </CostHighlight>
|
|
|
+ <Tooltip
|
|
|
+ title={
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ fontFamily: "Work Sans, sans-serif",
|
|
|
+ fontSize: "12px",
|
|
|
+ fontWeight: "normal",
|
|
|
+ padding: "5px 6px",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ EKS cost: ~$70/month <br />
|
|
|
+ Machine (x3) cost: ~$
|
|
|
+ {`${3 * costMapping[this.state.awsMachineType]}`}/month <br />
|
|
|
+ Networking cost: ~$30/month
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <StyledInfoTooltip>
|
|
|
+ <i className="material-icons">help_outline</i>
|
|
|
+ </StyledInfoTooltip>
|
|
|
+ </Tooltip>
|
|
|
+ </Helper>
|
|
|
+ */}
|
|
|
+ <InputRow
|
|
|
+ type="text"
|
|
|
+ value={awsAccessId}
|
|
|
+ setValue={(x: string) => {
|
|
|
+ setIsFormDirty(true);
|
|
|
+ setAwsAccessId(x);
|
|
|
+ }}
|
|
|
+ label="👤 AWS Access ID"
|
|
|
+ placeholder="ex: AKIAIOSFODNN7EXAMPLE"
|
|
|
+ width="100%"
|
|
|
+ isRequired={true}
|
|
|
+ />
|
|
|
+ <InputRow
|
|
|
+ type="password"
|
|
|
+ value={awsSecretKey}
|
|
|
+ setValue={(x: string) => {
|
|
|
+ setIsFormDirty(true);
|
|
|
+ setAwsSecretKey(x);
|
|
|
+ }}
|
|
|
+ label="🔒 AWS Secret Key"
|
|
|
+ placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
|
|
|
+ width="100%"
|
|
|
+ isRequired={true}
|
|
|
+ />
|
|
|
+ <Br />
|
|
|
+ <Heading>AWS Resources</Heading>
|
|
|
+ <Helper>
|
|
|
+ Porter will provision the following AWS resources in your own cloud.
|
|
|
+ </Helper>
|
|
|
+ <CheckboxList
|
|
|
+ options={provisionOptions}
|
|
|
+ selected={selectedInfras}
|
|
|
+ setSelected={(x: { value: string; label: string }[]) => {
|
|
|
+ setIsFormDirty(true);
|
|
|
+ console.log(x);
|
|
|
+ setSelectedInfras(x);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ {renderClusterNameSection()}
|
|
|
+ <Helper>
|
|
|
+ By default, Porter creates a cluster with three t2.medium instances
|
|
|
+ (2vCPUs and 4GB RAM each). AWS will bill you for any provisioned
|
|
|
+ resources. Learn more about EKS pricing
|
|
|
+ <Highlight href="https://aws.amazon.com/eks/pricing/" target="_blank">
|
|
|
+ here
|
|
|
+ </Highlight>
|
|
|
+ .
|
|
|
+ </Helper>
|
|
|
+ <CheckboxRow
|
|
|
+ isRequired={true}
|
|
|
+ checked={provisionConfirmed}
|
|
|
+ toggle={() => {
|
|
|
+ setIsFormDirty(true);
|
|
|
+ setProvisionConfirmed(!provisionConfirmed);
|
|
|
+ }}
|
|
|
+ label="I understand and wish to proceed"
|
|
|
+ />
|
|
|
+ </FormSection>
|
|
|
+ {props.children ? props.children : <Padding />}
|
|
|
+ <SaveButton
|
|
|
+ text="Submit"
|
|
|
+ disabled={checkFormDisabled() || buttonStatus === "loading"}
|
|
|
+ onClick={onCreateAWS}
|
|
|
+ makeFlush={true}
|
|
|
+ status={getButtonStatus()}
|
|
|
+ helper="Note: Provisioning can take up to 15 minutes"
|
|
|
+ />
|
|
|
+ </StyledAWSFormSection>
|
|
|
+ );
|
|
|
+};
|
|
|
|
|
|
-export default withRouter(AWSFormSection);
|
|
|
+export default AWSFormSectionFC;
|
|
|
|
|
|
const Highlight = styled.a`
|
|
|
color: #8590ff;
|
|
|
@@ -518,7 +521,7 @@ const CloseButton = styled.div`
|
|
|
}
|
|
|
`;
|
|
|
|
|
|
-const GuideButton = styled.a`
|
|
|
+const GuideButton = styled.div`
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
margin-left: 20px;
|
|
|
@@ -527,7 +530,7 @@ const GuideButton = styled.a`
|
|
|
margin-bottom: -1px;
|
|
|
border: 1px solid #aaaabb;
|
|
|
padding: 5px 10px;
|
|
|
- padding-left: 6px;
|
|
|
+ padding-left: 8px;
|
|
|
border-radius: 5px;
|
|
|
cursor: pointer;
|
|
|
:hover {
|
|
|
@@ -543,7 +546,7 @@ const GuideButton = styled.a`
|
|
|
> i {
|
|
|
color: #aaaabb;
|
|
|
font-size: 16px;
|
|
|
- margin-right: 6px;
|
|
|
+ margin-right: 7px;
|
|
|
}
|
|
|
`;
|
|
|
|
|
|
@@ -551,3 +554,25 @@ const CloseButtonImg = styled.img`
|
|
|
width: 14px;
|
|
|
margin: 0 auto;
|
|
|
`;
|
|
|
+
|
|
|
+const CostHighlight = styled.span<{ highlight: boolean }>`
|
|
|
+ background-color: ${(props) => props.highlight && "yellow"};
|
|
|
+`;
|
|
|
+
|
|
|
+const StyledInfoTooltip = styled.div`
|
|
|
+ display: inline-block;
|
|
|
+ position: relative;
|
|
|
+ margin-right: 2px;
|
|
|
+ > i {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ position: absolute;
|
|
|
+ top: -10px;
|
|
|
+ font-size: 10px;
|
|
|
+ color: #858faaaa;
|
|
|
+ cursor: pointer;
|
|
|
+ :hover {
|
|
|
+ color: #aaaabb;
|
|
|
+ }
|
|
|
+ }
|
|
|
+`;
|