ProvisionerFlow.tsx 9.8 KB

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