|
|
@@ -1,6 +1,7 @@
|
|
|
import React, { useContext, useEffect, useState } from "react";
|
|
|
import {
|
|
|
AWSClusterNetwork,
|
|
|
+ CloudwatchAlarm,
|
|
|
Cluster,
|
|
|
Contract,
|
|
|
EKS,
|
|
|
@@ -27,14 +28,17 @@ import { useIntercom } from "lib/hooks/useIntercom";
|
|
|
import api from "shared/api";
|
|
|
import { Context } from "shared/Context";
|
|
|
import { pushFiltered } from "shared/routing";
|
|
|
-import { type ClusterType, type ClusterState } from "shared/types";
|
|
|
+import { type ClusterState, type ClusterType } from "shared/types";
|
|
|
import { PREFLIGHT_TO_ENUM } from "shared/util";
|
|
|
+import alert from "assets/alert-warning.svg";
|
|
|
import info from "assets/info-outlined.svg";
|
|
|
import healthy from "assets/status-healthy.png";
|
|
|
|
|
|
+import GPUProvisionSettings from "./GPUProvisionSettings";
|
|
|
import Loading from "./Loading";
|
|
|
import Button from "./porter/Button";
|
|
|
import Checkbox from "./porter/Checkbox";
|
|
|
+import Fieldset from "./porter/Fieldset";
|
|
|
import Icon from "./porter/Icon";
|
|
|
import Input from "./porter/Input";
|
|
|
import Select from "./porter/Select";
|
|
|
@@ -43,9 +47,6 @@ import Text from "./porter/Text";
|
|
|
import Tooltip from "./porter/Tooltip";
|
|
|
import VerticalSteps from "./porter/VerticalSteps";
|
|
|
import PreflightChecks from "./PreflightChecks";
|
|
|
-import { Integer } from "type-fest";
|
|
|
-import InputSlider from "./porter/InputSlider";
|
|
|
-import GPUProvisionSettings from "./GPUProvisionSettings";
|
|
|
|
|
|
const regionOptions = [
|
|
|
{ value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
|
|
|
@@ -169,6 +170,9 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
const [controlPlaneLogs, setControlPlaneLogs] = useState<EKSLogging>(
|
|
|
new EKSLogging()
|
|
|
);
|
|
|
+ const [cloudwatchAlarm, setCloudwatchAlarm] = useState<CloudwatchAlarm>(
|
|
|
+ new CloudwatchAlarm()
|
|
|
+ );
|
|
|
|
|
|
const markStepStarted = async (
|
|
|
step: string,
|
|
|
@@ -187,7 +191,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
project_id: currentProject ? currentProject.id : 0,
|
|
|
}
|
|
|
);
|
|
|
- } catch (err) { }
|
|
|
+ } catch (err) {}
|
|
|
};
|
|
|
|
|
|
const getStatus = (): React.ReactNode => {
|
|
|
@@ -264,17 +268,19 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
}
|
|
|
|
|
|
// Split the input string by comma, then reduce the resulting array to an object
|
|
|
- const tags = tagString.split(",").reduce<Record<string, string>>((obj, item) => {
|
|
|
- // Split each item by "=", and trim whitespace from both key and value
|
|
|
- const [key, value] = item.split("=").map(part => part.trim());
|
|
|
-
|
|
|
- // Only add the key-value pair to the object if both key and value are present
|
|
|
- if (key && value) {
|
|
|
- obj[key] = value;
|
|
|
- }
|
|
|
+ const tags = tagString
|
|
|
+ .split(",")
|
|
|
+ .reduce<Record<string, string>>((obj, item) => {
|
|
|
+ // Split each item by "=", and trim whitespace from both key and value
|
|
|
+ const [key, value] = item.split("=").map((part) => part.trim());
|
|
|
+
|
|
|
+ // Only add the key-value pair to the object if both key and value are present
|
|
|
+ if (key && value) {
|
|
|
+ obj[key] = value;
|
|
|
+ }
|
|
|
|
|
|
- return obj;
|
|
|
- }, {});
|
|
|
+ return obj;
|
|
|
+ }, {});
|
|
|
|
|
|
return tags;
|
|
|
}
|
|
|
@@ -314,7 +320,6 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
const nodeGroups = [
|
|
|
new EKSNodeGroup({
|
|
|
instanceType: "t3.medium",
|
|
|
@@ -344,17 +349,18 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
|
|
|
// Conditionally add the last EKSNodeGroup if gpuModal is enabled
|
|
|
if (props.gpuModal) {
|
|
|
- nodeGroups.push(new EKSNodeGroup({
|
|
|
- instanceType: clusterState.gpuInstanceType,
|
|
|
- minInstances: clusterState.gpuMinInstances || 0,
|
|
|
- maxInstances: clusterState.gpuMaxInstances || 5,
|
|
|
- nodeGroupType: NodeGroupType.CUSTOM,
|
|
|
- isStateful: false,
|
|
|
- additionalPolicies: clusterState.additionalNodePolicies,
|
|
|
- }));
|
|
|
+ nodeGroups.push(
|
|
|
+ new EKSNodeGroup({
|
|
|
+ instanceType: clusterState.gpuInstanceType,
|
|
|
+ minInstances: clusterState.gpuMinInstances || 0,
|
|
|
+ maxInstances: clusterState.gpuMaxInstances || 5,
|
|
|
+ nodeGroupType: NodeGroupType.CUSTOM,
|
|
|
+ isStateful: false,
|
|
|
+ additionalPolicies: clusterState.additionalNodePolicies,
|
|
|
+ })
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-
|
|
|
const data = new Contract({
|
|
|
cluster: new Cluster({
|
|
|
projectId: currentProject.id,
|
|
|
@@ -365,17 +371,20 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
case: "eksKind",
|
|
|
value: new EKS({
|
|
|
clusterName: clusterState.clusterName,
|
|
|
- clusterVersion: clusterState.clusterVersion || defaultClusterVersion,
|
|
|
+ clusterVersion:
|
|
|
+ clusterState.clusterVersion || defaultClusterVersion,
|
|
|
cidrRange: clusterState.cidrRangeVPC || defaultCidrVpc, // deprecated in favour of network.cidrRangeVPC: can be removed after december 2023
|
|
|
region: clusterState.awsRegion,
|
|
|
loadBalancer: loadBalancerObj,
|
|
|
logging: controlPlaneLogs,
|
|
|
+ cloudwatchAlarm,
|
|
|
enableGuardDuty: clusterState.guardDutyEnabled,
|
|
|
enableKmsEncryption: clusterState.kmsEncryptionEnabled,
|
|
|
enableEcrScanning: clusterState.ecrScanningEnabled,
|
|
|
network: new AWSClusterNetwork({
|
|
|
vpcCidr: clusterState.cidrRangeVPC || defaultCidrVpc,
|
|
|
- serviceCidr: clusterState.cidrRangeServices || defaultCidrServices,
|
|
|
+ serviceCidr:
|
|
|
+ clusterState.cidrRangeServices || defaultCidrServices,
|
|
|
}),
|
|
|
nodeGroups,
|
|
|
}),
|
|
|
@@ -422,15 +431,13 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
pushFiltered(props, "/cluster-dashboard", ["project_id"], {
|
|
|
cluster: cluster.name,
|
|
|
});
|
|
|
- }
|
|
|
- else {
|
|
|
+ } else {
|
|
|
if (props.closeModal) {
|
|
|
props.closeModal();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
})
|
|
|
.catch((err) => {
|
|
|
if (err) {
|
|
|
@@ -461,8 +468,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
useEffect(() => {
|
|
|
setIsReadOnly(
|
|
|
props.clusterId &&
|
|
|
- (currentCluster.status === "UPDATING" ||
|
|
|
- currentCluster.status === "UPDATING_UNAVAILABLE")
|
|
|
+ (currentCluster.status === "UPDATING" ||
|
|
|
+ currentCluster.status === "UPDATING_UNAVAILABLE")
|
|
|
);
|
|
|
handleClusterStateChange(
|
|
|
"clusterName",
|
|
|
@@ -523,8 +530,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
|
|
|
const awsTags = eksValues.loadBalancer.tags
|
|
|
? Object.entries(eksValues.loadBalancer.tags)
|
|
|
- .map(([key, value]) => `${key}=${value}`)
|
|
|
- .join(",")
|
|
|
+ .map(([key, value]) => `${key}=${value}`)
|
|
|
+ .join(",")
|
|
|
: "";
|
|
|
handleClusterStateChange("awsTags", awsTags);
|
|
|
|
|
|
@@ -551,6 +558,14 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
setControlPlaneLogs(logging);
|
|
|
}
|
|
|
|
|
|
+ if (eksValues.cloudwatchAlarm != null) {
|
|
|
+ const cloudwatchAlarm = new CloudwatchAlarm({
|
|
|
+ emails: eksValues.cloudwatchAlarm.emails,
|
|
|
+ enable: eksValues.cloudwatchAlarm.enable,
|
|
|
+ });
|
|
|
+ setCloudwatchAlarm(cloudwatchAlarm);
|
|
|
+ }
|
|
|
+
|
|
|
handleClusterStateChange("guardDutyEnabled", eksValues.enableGuardDuty);
|
|
|
handleClusterStateChange(
|
|
|
"kmsEncryptionEnabled",
|
|
|
@@ -580,7 +595,6 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
setStep(0);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
}
|
|
|
}, [clusterState]);
|
|
|
@@ -1072,11 +1086,11 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
|
|
|
{(clusterState.wafV2ARN === undefined ||
|
|
|
clusterState.wafV2ARN?.length === 0) && (
|
|
|
- <ErrorInLine>
|
|
|
- <i className="material-icons">error</i>
|
|
|
- {"Required if WafV2 is enabled"}
|
|
|
- </ErrorInLine>
|
|
|
- )}
|
|
|
+ <ErrorInLine>
|
|
|
+ <i className="material-icons">error</i>
|
|
|
+ {"Required if WafV2 is enabled"}
|
|
|
+ </ErrorInLine>
|
|
|
+ )}
|
|
|
</>
|
|
|
)}
|
|
|
<Spacer y={1} />
|
|
|
@@ -1185,10 +1199,9 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
setShowHelpMessage(false);
|
|
|
try {
|
|
|
await preflightChecks();
|
|
|
- } catch (err) { }
|
|
|
+ } catch (err) {}
|
|
|
};
|
|
|
|
|
|
-
|
|
|
const renderForm = (): JSX.Element => {
|
|
|
// Render simplified form if initial create
|
|
|
if (!props.clusterId) {
|
|
|
@@ -1236,8 +1249,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
</Text>
|
|
|
</CheckItemTop>
|
|
|
</CheckItemContainer>
|
|
|
-
|
|
|
- </>) :
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
<>
|
|
|
<PreflightChecks
|
|
|
provider="AWS"
|
|
|
@@ -1247,67 +1260,97 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
<Spacer y={0.5} />
|
|
|
{preflightFailed && preflightData && (
|
|
|
<>
|
|
|
- {(showHelpMessage && currentProject?.quota_increase) ? <>
|
|
|
- <Text color="helper">
|
|
|
- Your account currently is blocked from provisioning in {clusterState.awsRegion} due to a quota limit imposed by AWS. Either change the region or request to increase quotas.
|
|
|
- </Text>
|
|
|
- <Spacer y={.5} />
|
|
|
- <Text color="helper">
|
|
|
- Porter can automatically request quota increases on your behalf and email you once the cluster is provisioned.
|
|
|
- </Text>
|
|
|
- <Spacer y={.5} />
|
|
|
- <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: '15px' }}>
|
|
|
- <Button
|
|
|
- disabled={isLoading}
|
|
|
- onClick={proceedToProvision}
|
|
|
-
|
|
|
- >
|
|
|
- Auto request increase
|
|
|
- </Button>
|
|
|
+ {showHelpMessage && currentProject?.quota_increase ? (
|
|
|
+ <Fieldset>
|
|
|
+ <>
|
|
|
+ <TagIcon src={alert} />
|
|
|
+ <Text color="helper">
|
|
|
+ Your account currently is blocked from
|
|
|
+ provisioning in {clusterState.awsRegion} due to
|
|
|
+ a quota limit imposed by AWS. Either change the
|
|
|
+ region or request to increase quotas.
|
|
|
+ </Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <Text color="helper">
|
|
|
+ Porter can automatically request quota increases
|
|
|
+ on your behalf and email you once the cluster is
|
|
|
+ provisioned.
|
|
|
+ </Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ display: "flex",
|
|
|
+ justifyContent: "flex-start",
|
|
|
+ alignItems: "center",
|
|
|
+ gap: "15px",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ disabled={isLoading}
|
|
|
+ onClick={proceedToProvision}
|
|
|
+ >
|
|
|
+ Auto request increase
|
|
|
+ </Button>
|
|
|
+ {/* <Button
|
|
|
+ disabled={isLoading}
|
|
|
+ onClick={dismissPreflight}
|
|
|
+ color="#313539"
|
|
|
+ >
|
|
|
+ I'll do it myself
|
|
|
+ </Button> */}
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ </Fieldset>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <Text color="helper">
|
|
|
+ Your account currently is blocked from
|
|
|
+ provisioning in {clusterState.awsRegion} due to a
|
|
|
+ quota limit imposed by AWS. Either change the
|
|
|
+ region or request to increase quotas.
|
|
|
+ </Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
<Button
|
|
|
disabled={isLoading}
|
|
|
- onClick={dismissPreflight}
|
|
|
- color="#313539"
|
|
|
+ onClick={preflightChecks}
|
|
|
>
|
|
|
- I'll do it myself
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
-
|
|
|
- </> : (
|
|
|
- <><Text color="helper">
|
|
|
- Your account currently is blocked from provisioning in {clusterState.awsRegion} due to a quota limit imposed by AWS. Either change the region or request to increase quotas.
|
|
|
- </Text><Spacer y={.5} /><Button
|
|
|
- disabled={isLoading}
|
|
|
- onClick={preflightChecks}
|
|
|
-
|
|
|
- >
|
|
|
Retry checks
|
|
|
- </Button></>)}
|
|
|
- </>)}
|
|
|
- </>}
|
|
|
- </>, <>
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </>,
|
|
|
+ <>
|
|
|
<Text size={16}>Provision your cluster</Text>
|
|
|
<Spacer y={1} />
|
|
|
- {showEmailMessage && <>
|
|
|
- <Text color="helper">
|
|
|
- After your quota requests have been approved by AWS, Porter will email you when your cluster has been provisioned.
|
|
|
- </Text>
|
|
|
- <Spacer y={1} />
|
|
|
- </>}
|
|
|
+ {showEmailMessage && (
|
|
|
+ <>
|
|
|
+ <Text color="helper">
|
|
|
+ After your quota requests have been approved by AWS,
|
|
|
+ Porter will email you when your cluster has been
|
|
|
+ provisioned.
|
|
|
+ </Text>
|
|
|
+ <Spacer y={1} />
|
|
|
+ </>
|
|
|
+ )}
|
|
|
<Button
|
|
|
disabled={(preflightFailed && !showEmailMessage) || isLoading}
|
|
|
- onClick={showEmailMessage ? requestQuotasAndProvision : createCluster}
|
|
|
+ onClick={
|
|
|
+ showEmailMessage ? requestQuotasAndProvision : createCluster
|
|
|
+ }
|
|
|
status={getStatus()}
|
|
|
>
|
|
|
Provision
|
|
|
</Button>
|
|
|
- <Spacer y={1} /></>
|
|
|
- ,
|
|
|
-
|
|
|
+ <Spacer y={1} />
|
|
|
+ </>,
|
|
|
].filter((x) => x)}
|
|
|
/>
|
|
|
</>
|
|
|
- )
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
// If settings, update full form
|
|
|
@@ -1330,10 +1373,9 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
|
|
|
showEmailMessage={showEmailMessage}
|
|
|
requestQuotasAndProvision={requestQuotaIncrease}
|
|
|
/>
|
|
|
- )
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-
|
|
|
return (
|
|
|
<>
|
|
|
<StyledForm>
|
|
|
@@ -1390,7 +1432,7 @@ const ExpandHeader = styled.div<{ isExpanded: boolean }>`
|
|
|
margin - right: 7px;
|
|
|
margin-left: -7px;
|
|
|
transform: ${(props) =>
|
|
|
- props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
|
|
|
+ props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
|
|
|
transition: transform 0.1s ease;
|
|
|
}
|
|
|
`;
|
|
|
@@ -1450,3 +1492,9 @@ const CheckItemTop = styled.div`
|
|
|
const StatusIcon = styled.img`
|
|
|
height: 14px;
|
|
|
`;
|
|
|
+
|
|
|
+const TagIcon = styled.img`
|
|
|
+ height: 13px;
|
|
|
+ margin-right: 5px;
|
|
|
+ margin-top: 2px;
|
|
|
+`;
|