GPUProvisionSettings.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import React, { useContext, useState } from "react";
  2. import {
  3. type EKSPreflightValues,
  4. } from "@porter-dev/api-contracts";
  5. import { withRouter, type RouteComponentProps } from "react-router";
  6. import styled from "styled-components";
  7. import Heading from "components/form-components/Heading";
  8. import { type ClusterState } from "shared/types";
  9. import healthy from "assets/status-healthy.png";
  10. import Button from "./porter/Button";
  11. import Select from "./porter/Select";
  12. import Spacer from "./porter/Spacer";
  13. import Text from "./porter/Text";
  14. import VerticalSteps from "./porter/VerticalSteps";
  15. import PreflightChecks from "./PreflightChecks";
  16. import InputSlider from "./porter/InputSlider";
  17. import { Context } from "shared/Context";
  18. const gpuMachineTypeOptions = [
  19. { value: "g4dn.xlarge", label: "g4dn.xlarge" },
  20. { value: "g4dn.2xlarge", label: "g4dn.2xlarge" },
  21. { value: "p4d.24xlarge", label: "p4d.24xlarge" },
  22. ];
  23. type Props = RouteComponentProps & {
  24. handleClusterStateChange: <K extends keyof ClusterState>(key: K, value: ClusterState[K]) => void;
  25. clusterState: ClusterState;
  26. isReadOnly: boolean;
  27. isLoading: boolean;
  28. preflightData: EKSPreflightValues | null;
  29. preflightError: string | undefined;
  30. preflightFailed: boolean;
  31. showHelpMessage: boolean;
  32. showEmailMessage: boolean;
  33. proceedToProvision: () => void;
  34. getStatus: () => React.ReactNode;
  35. createCluster: () => void;
  36. preflightChecks: () => void;
  37. dismissPreflight: () => void;
  38. requestQuotasAndProvision: () => void;
  39. };
  40. const GPUProvisionerSettings: React.FC<Props> = ({
  41. handleClusterStateChange,
  42. isReadOnly,
  43. clusterState,
  44. preflightChecks,
  45. isLoading,
  46. preflightData,
  47. createCluster,
  48. preflightError,
  49. preflightFailed,
  50. showEmailMessage,
  51. showHelpMessage,
  52. proceedToProvision,
  53. dismissPreflight,
  54. getStatus,
  55. requestQuotasAndProvision,
  56. }) => {
  57. const [gpuStep, setGPUStep] = useState(0);
  58. const {
  59. currentProject,
  60. } = useContext(Context);
  61. const renderGPUSettings = (): JSX.Element => {
  62. return (
  63. <VerticalSteps
  64. currentStep={gpuStep}
  65. onlyShowCurrentStep={true}
  66. steps={[
  67. <>
  68. <Heading isAtTop> Select GPU Instance Type </Heading>
  69. <Spacer y={.5} />
  70. <Select
  71. options={gpuMachineTypeOptions}
  72. width="350px"
  73. disabled={isReadOnly}
  74. value={clusterState.gpuInstanceType}
  75. setValue={(x: string) => {
  76. handleClusterStateChange("gpuInstanceType", x)
  77. // handleClusterStateChange("machineType", x)
  78. }
  79. }
  80. label="Machine type"
  81. />
  82. <Spacer y={1} />
  83. <InputSlider
  84. label="Max Instances: "
  85. unit="nodes"
  86. min={0}
  87. max={5}
  88. step={1}
  89. width="350px"
  90. disabled={isReadOnly || isLoading}
  91. value={clusterState.gpuMaxInstances.toString()}
  92. setValue={(x: number) => {
  93. handleClusterStateChange("gpuMaxInstances", x)
  94. }}
  95. />
  96. <Button onClick={() => {
  97. setGPUStep(1)
  98. preflightChecks();
  99. }}>
  100. Continue
  101. </Button>
  102. <Spacer y={.5} />
  103. </>,
  104. <>
  105. {showEmailMessage ?
  106. <>
  107. <CheckItemContainer>
  108. <CheckItemTop>
  109. <StatusIcon src={healthy} />
  110. <Spacer inline x={1} />
  111. <Text style={{ marginLeft: '10px', flex: 1 }}>{"Porter will request to increase quotas when you provision"}</Text>
  112. </CheckItemTop>
  113. </CheckItemContainer>
  114. </> :
  115. <>
  116. <PreflightChecks provider='AWS' preflightData={preflightData} error={preflightError} />
  117. <Spacer y={.5} />
  118. {(preflightFailed && preflightData) &&
  119. <>
  120. {(showHelpMessage && currentProject?.quota_increase) ? <>
  121. <Text color="helper">
  122. 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.
  123. </Text>
  124. <Spacer y={.5} />
  125. <Text color="helper">
  126. Porter can automatically request quota increases on your behalf and email you once the cluster is provisioned.
  127. </Text>
  128. <Spacer y={.5} />
  129. <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: '15px' }}>
  130. <Button
  131. disabled={isLoading}
  132. onClick={proceedToProvision}
  133. >
  134. Auto request increase
  135. </Button>
  136. <Button
  137. disabled={isLoading}
  138. onClick={dismissPreflight}
  139. color="#313539"
  140. >
  141. I'll do it myself
  142. </Button>
  143. </div>
  144. </> : (
  145. <><Text color="helper">
  146. 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.
  147. </Text><Spacer y={.5} /><Button
  148. disabled={isLoading}
  149. onClick={preflightChecks}
  150. >
  151. Retry checks
  152. </Button></>)}
  153. </>}
  154. </>}
  155. <Spacer y={1} />
  156. {showEmailMessage && <>
  157. <Text color="helper">
  158. After your quota requests have been approved by AWS, Porter will email you when your cluster has been provisioned.
  159. </Text>
  160. <Spacer y={1} />
  161. </>}
  162. <StepChangeButtonsContainer>
  163. <Button
  164. disabled={(preflightFailed && !showEmailMessage) || isLoading}
  165. onClick={showEmailMessage ? requestQuotasAndProvision : createCluster}
  166. status={getStatus()}
  167. >
  168. Provision
  169. </Button>
  170. <Spacer inline x={0.5} />
  171. <Button onClick={() => { setGPUStep(0); }} color="#222222">Back</Button>
  172. </StepChangeButtonsContainer>
  173. <Spacer y={1} /></>,
  174. ].filter((x) => x)}
  175. />
  176. );
  177. };
  178. return (
  179. <>
  180. {renderGPUSettings()}
  181. </>
  182. );
  183. };
  184. export default withRouter(GPUProvisionerSettings);
  185. const CheckItemContainer = styled.div`
  186. display: flex;
  187. flex-direction: column;
  188. border: 1px solid ${(props) => props.theme.border};
  189. border-radius: 5px;
  190. font-size: 13px;
  191. width: 100%;
  192. margin-bottom: 10px;
  193. padding-left: 10px;
  194. cursor: ${(props) => (props.hasMessage ? "pointer" : "default")};
  195. background: ${(props) => props.theme.clickable.bg};
  196. `;
  197. const CheckItemTop = styled.div`
  198. display: flex;
  199. align-items: center;
  200. padding: 10px;
  201. background: ${(props) => props.theme.clickable.bg};
  202. `;
  203. const StatusIcon = styled.img`
  204. height: 14px;
  205. `;
  206. const StepChangeButtonsContainer = styled.div`
  207. display: flex;
  208. `;