ProvisionerFlow.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import React, { useContext, useState } from "react";
  2. import styled from "styled-components";
  3. import AzureCredentialForm from "components/AzureCredentialForm";
  4. import CloudFormationForm from "components/CloudFormationForm";
  5. import CredentialsForm from "components/CredentialsForm";
  6. import GCPCredentialsForm from "components/GCPCredentialsForm";
  7. import ProvisionerForm from "components/ProvisionerForm";
  8. import api from "shared/api";
  9. import { integrationList } from "shared/common";
  10. import { Context } from "shared/Context";
  11. import AWSCostConsent from "./AWSCostConsent";
  12. import AzureCostConsent from "./AzureCostConsent";
  13. import GCPCostConsent from "./GCPCostConsent";
  14. import Button from "./porter/Button";
  15. import DashboardPlaceholder from "./porter/DashboardPlaceholder";
  16. import Link from "./porter/Link";
  17. import Spacer from "./porter/Spacer";
  18. import Text from "./porter/Text";
  19. const providers = ["aws", "gcp", "azure"];
  20. type Props = {};
  21. const ProvisionerFlow: React.FC<Props> = ({}) => {
  22. const { usage, hasBillingEnabled, currentProject, featurePreview } =
  23. useContext(Context);
  24. const [currentStep, setCurrentStep] = useState("cloud");
  25. const [credentialId, setCredentialId] = useState("");
  26. const [showCostConfirmModal, setShowCostConfirmModal] = useState(false);
  27. const [confirmCost, setConfirmCost] = useState("");
  28. const [useCloudFormationForm, setUseCloudFormationForm] = useState(true);
  29. const [selectedProvider, setSelectedProvider] = useState("");
  30. const markStepCostConsent = async (step: string, provider: string) => {
  31. try {
  32. await api.updateOnboardingStep(
  33. "<token>",
  34. { step, provider },
  35. { project_id: currentProject.id }
  36. );
  37. } catch (err) {
  38. console.log(err);
  39. }
  40. };
  41. const openCostConsentModal = (provider: string) => {
  42. setSelectedProvider(provider);
  43. setShowCostConfirmModal(true);
  44. markStepCostConsent("cost-consent-opened", provider);
  45. };
  46. if (currentStep === "cloud") {
  47. return (
  48. <>
  49. <StyledProvisionerFlow>
  50. <BlockList>
  51. {providers.map((provider: string, i: number) => {
  52. const providerInfo = integrationList[provider];
  53. return (
  54. <Block
  55. key={i}
  56. disabled={
  57. !currentProject?.multi_cluster &&
  58. provider === "gcp" &&
  59. !currentProject?.azure_enabled
  60. }
  61. onClick={() => {
  62. if (provider != "gcp" || currentProject?.azure_enabled) {
  63. openCostConsentModal(provider);
  64. // setSelectedProvider(provider);
  65. // setCurrentStep("credentials");
  66. }
  67. }}
  68. >
  69. <Icon src={providerInfo.icon} />
  70. <BlockTitle>{providerInfo.label}</BlockTitle>
  71. <BlockDescription>
  72. {provider === "gcp" && !currentProject?.azure_enabled
  73. ? providerInfo.tagline
  74. : "Hosted in your own cloud"}
  75. </BlockDescription>
  76. </Block>
  77. );
  78. })}
  79. </BlockList>
  80. <DashboardPlaceholder>
  81. <Text size={16}>
  82. Want to test Porter without linking your own cloud account?
  83. </Text>
  84. <Spacer y={0.5} />
  85. <Text color={"helper"}>Get started on the Porter Cloud.</Text>
  86. <Spacer y={1} />
  87. <Link to="https://cloud.porter.run">
  88. <Button alt height="35px">
  89. Deploy on the Porter Cloud <Spacer inline x={1} />{" "}
  90. <i className="material-icons" style={{ fontSize: "18px" }}>
  91. east
  92. </i>
  93. </Button>
  94. </Link>
  95. </DashboardPlaceholder>
  96. </StyledProvisionerFlow>
  97. {showCostConfirmModal &&
  98. ((selectedProvider === "aws" && (
  99. <AWSCostConsent
  100. setCurrentStep={setCurrentStep}
  101. setShowCostConfirmModal={setShowCostConfirmModal}
  102. markCostConsentComplete={() => {
  103. try {
  104. markStepCostConsent("cost-consent-complete", "aws");
  105. } catch (err) {
  106. console.log(err);
  107. }
  108. if (currentProject != null) {
  109. try {
  110. api.inviteAdmin(
  111. "<token>",
  112. {},
  113. { project_id: currentProject.id }
  114. );
  115. } catch (err) {
  116. console.log(err);
  117. }
  118. }
  119. }}
  120. />
  121. )) ||
  122. (selectedProvider === "gcp" && (
  123. <GCPCostConsent
  124. setCurrentStep={setCurrentStep}
  125. setShowCostConfirmModal={setShowCostConfirmModal}
  126. markCostConsentComplete={() => {
  127. try {
  128. markStepCostConsent("cost-consent-complete", "gcp");
  129. } catch (err) {
  130. console.log(err);
  131. }
  132. if (currentProject != null) {
  133. try {
  134. api.inviteAdmin(
  135. "<token>",
  136. {},
  137. { project_id: currentProject.id }
  138. );
  139. } catch (err) {
  140. console.log(err);
  141. }
  142. }
  143. }}
  144. />
  145. )) ||
  146. (selectedProvider === "azure" && (
  147. <AzureCostConsent
  148. setCurrentStep={setCurrentStep}
  149. setShowCostConfirmModal={setShowCostConfirmModal}
  150. markCostConsentComplete={() => {
  151. try {
  152. markStepCostConsent("cost-consent-complete", "azure");
  153. } catch (err) {
  154. console.log(err);
  155. }
  156. if (currentProject != null) {
  157. try {
  158. api.inviteAdmin(
  159. "<token>",
  160. {},
  161. { project_id: currentProject.id }
  162. );
  163. } catch (err) {
  164. console.log(err);
  165. }
  166. }
  167. }}
  168. />
  169. )))}
  170. </>
  171. );
  172. } else if (currentStep === "credentials") {
  173. return (
  174. (selectedProvider === "aws" &&
  175. (useCloudFormationForm ? (
  176. <CloudFormationForm
  177. goBack={() => {
  178. setCurrentStep("cloud");
  179. }}
  180. proceed={(id) => {
  181. setCredentialId(id);
  182. setCurrentStep("cluster");
  183. }}
  184. switchToCredentialFlow={() => {
  185. setUseCloudFormationForm(false);
  186. }}
  187. />
  188. ) : (
  189. <CredentialsForm
  190. goBack={() => {
  191. setCurrentStep("cloud");
  192. }}
  193. proceed={(id) => {
  194. setCredentialId(id);
  195. setCurrentStep("cluster");
  196. }}
  197. />
  198. ))) ||
  199. (selectedProvider === "azure" && (
  200. <AzureCredentialForm
  201. goBack={() => {
  202. setCurrentStep("cloud");
  203. }}
  204. proceed={(id) => {
  205. setCredentialId(id);
  206. setCurrentStep("cluster");
  207. }}
  208. />
  209. )) ||
  210. (selectedProvider === "gcp" && (
  211. <GCPCredentialsForm
  212. goBack={() => {
  213. setCurrentStep("cloud");
  214. }}
  215. proceed={(id) => {
  216. setCredentialId(id);
  217. setCurrentStep("cluster");
  218. }}
  219. />
  220. ))
  221. );
  222. } else if (currentStep === "cluster") {
  223. return (
  224. <ProvisionerForm
  225. goBack={() => {
  226. setCurrentStep("credentials");
  227. }}
  228. credentialId={credentialId}
  229. provider={selectedProvider}
  230. />
  231. );
  232. }
  233. };
  234. export default ProvisionerFlow;
  235. const Cost = styled.div`
  236. font-weight: 600;
  237. font-size: 20px;
  238. `;
  239. const Tab = styled.span`
  240. margin-left: 20px;
  241. height: 1px;
  242. `;
  243. const BlockList = styled.div`
  244. overflow: visible;
  245. margin-top: 25px;
  246. margin-bottom: 27px;
  247. display: grid;
  248. grid-column-gap: 25px;
  249. grid-row-gap: 25px;
  250. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  251. `;
  252. const Icon = styled.img<{ bw?: boolean }>`
  253. height: 30px;
  254. margin-top: 30px;
  255. margin-bottom: 15px;
  256. filter: ${(props) => (props.bw ? "grayscale(1)" : "")};
  257. `;
  258. const BlockDescription = styled.div`
  259. margin-bottom: 12px;
  260. color: #ffffff66;
  261. text-align: center;
  262. font-weight: 400;
  263. font-size: 13px;
  264. padding: 0px 25px;
  265. height: 2.4em;
  266. font-size: 12px;
  267. display: -webkit-box;
  268. overflow: hidden;
  269. -webkit-line-clamp: 2;
  270. -webkit-box-orient: vertical;
  271. `;
  272. const BlockTitle = styled.div`
  273. margin-bottom: 12px;
  274. width: 80%;
  275. text-align: center;
  276. font-size: 14px;
  277. white-space: nowrap;
  278. overflow: hidden;
  279. text-overflow: ellipsis;
  280. `;
  281. const Block = styled.div<{ disabled?: boolean }>`
  282. align-items: center;
  283. user-select: none;
  284. display: flex;
  285. font-size: 13px;
  286. overflow: hidden;
  287. font-weight: 500;
  288. padding: 3px 0px 5px;
  289. flex-direction: column;
  290. align-items: center;
  291. justify-content: space-between;
  292. height: 170px;
  293. filter: ${({ disabled }) => (disabled ? "brightness(0.8) grayscale(1)" : "")};
  294. cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
  295. color: #ffffff;
  296. position: relative;
  297. border-radius: 5px;
  298. background: ${({ theme }) => theme.clickable.bg};
  299. border: 1px solid #494b4f;
  300. :hover {
  301. border: ${(props) => (props.disabled ? "" : "1px solid #7a7b80")};
  302. }
  303. animation: fadeIn 0.3s 0s;
  304. @keyframes fadeIn {
  305. from {
  306. opacity: 0;
  307. }
  308. to {
  309. opacity: 1;
  310. }
  311. }
  312. `;
  313. const StyledProvisionerFlow = styled.div`
  314. margin-top: -24px;
  315. `;