CredentialsForm.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import React, { useEffect, useState, useContext, useMemo } from "react";
  2. import styled from "styled-components";
  3. import api from "shared/api";
  4. import aws from "assets/aws.png";
  5. import credsIcon from "assets/creds.png";
  6. import addCircle from "assets/add-circle.png";
  7. import { Context } from "shared/Context";
  8. import Heading from "components/form-components/Heading";
  9. import Helper from "./form-components/Helper";
  10. import InputRow from "./form-components/InputRow";
  11. import SaveButton from "./SaveButton";
  12. import Button from "components/porter/Button";
  13. import Loading from "./Loading";
  14. import Error from "./porter/Error";
  15. import Modal from "./porter/Modal";
  16. import Text from "./porter/Text";
  17. import Spacer from "./porter/Spacer";
  18. type Props = {
  19. goBack: () => void;
  20. proceed: (cloud_provider_credentials_id: string) => void;
  21. };
  22. type AWSCredential = {
  23. created_at: string;
  24. id: number;
  25. user_id: number;
  26. project_id: number;
  27. aws_arn: string;
  28. };
  29. const CredentialsForm: React.FC<Props> = ({
  30. goBack,
  31. proceed,
  32. }) => {
  33. const { currentProject } = useContext(Context);
  34. const [awsCredentials, setAWSCredentials] = useState<AWSCredential[]>(null);
  35. const [isLoading, setIsLoading] = useState(true);
  36. const [awsAccessKeyID, setAWSAccessKeyID] = useState("");
  37. const [awsSecretAccessKey, setAWSSecretAccessKey] = useState("");
  38. const [selectedCredentials, setSelectedCredentials] = useState(null);
  39. const [showCreateForm, setShowCreateForm] = useState(false);
  40. const [createStatus, setCreateStatus] = useState("");
  41. useEffect(() => {
  42. api
  43. .getAWSIntegration(
  44. "<token>",
  45. {},
  46. {
  47. project_id: currentProject.id,
  48. }
  49. )
  50. .then(({ data }) => {
  51. if (!Array.isArray(data)) {
  52. setAWSCredentials([]);
  53. } else {
  54. setAWSCredentials(data);
  55. }
  56. setIsLoading(false);
  57. })
  58. .catch((err) => {
  59. console.error(err);
  60. });
  61. }, [currentProject]);
  62. const createCreds = () => {
  63. setCreateStatus("loading");
  64. api
  65. .createAWSIntegration(
  66. "<token>",
  67. {
  68. // Hardcoded for backward-compatibility
  69. // TODO: remove
  70. aws_region: "us-east-f",
  71. aws_access_key_id: awsAccessKeyID,
  72. aws_secret_access_key: awsSecretAccessKey,
  73. },
  74. {
  75. id: currentProject.id,
  76. }
  77. )
  78. .then(({ data }) => {
  79. setCreateStatus("successful");
  80. proceed(data.cloud_provider_credentials_id);
  81. })
  82. .catch((err) => {
  83. console.error(err);
  84. setCreateStatus("Error creating credentials");
  85. });
  86. };
  87. const renderContent = () => {
  88. if (awsCredentials.length > 0 && !showCreateForm) {
  89. return (
  90. <>
  91. <CredentialList>
  92. {
  93. awsCredentials.map((cred: AWSCredential, i: number) => {
  94. return (
  95. <Credential
  96. key={cred.id}
  97. isSelected={cred.id === selectedCredentials?.id}
  98. onClick={() => {
  99. if (cred.id === selectedCredentials?.id) {
  100. setSelectedCredentials(null);
  101. } else {
  102. setSelectedCredentials(cred);
  103. }
  104. }}
  105. >
  106. <Icon src={credsIcon} />
  107. <Name>{cred.aws_arn || "n/a"}</Name>
  108. </Credential>
  109. );
  110. })
  111. }
  112. <CreateRow onClick={() => {
  113. setShowCreateForm(true);
  114. setSelectedCredentials(null);
  115. }}>
  116. <Icon src={addCircle} />
  117. Add new AWS credentials
  118. </CreateRow>
  119. </CredentialList>
  120. <Br height="34px" />
  121. <SaveButton
  122. disabled={!selectedCredentials && true}
  123. onClick={() => proceed(selectedCredentials.aws_arn)}
  124. clearPosition
  125. text="Continue"
  126. />
  127. </>
  128. );
  129. }
  130. return (
  131. <>
  132. <StyledForm>
  133. {
  134. awsCredentials.length > 0 && (
  135. <CloseButton onClick={() => setShowCreateForm(false)}>
  136. <i className="material-icons">close</i>
  137. </CloseButton>
  138. )
  139. }
  140. <InputRow
  141. type="string"
  142. value={awsAccessKeyID}
  143. setValue={(e: string) => setAWSAccessKeyID(e)}
  144. label="👤 AWS access ID"
  145. placeholder="ex: AKIAIOSFODNN7EXAMPLE"
  146. isRequired
  147. />
  148. <InputRow
  149. type="password"
  150. value={awsSecretAccessKey}
  151. setValue={(e: string) => {
  152. setAWSSecretAccessKey(e)
  153. }}
  154. label="🔒 AWS secret key"
  155. placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
  156. isRequired
  157. />
  158. </StyledForm>
  159. <Button
  160. disabled={awsAccessKeyID === "" || awsSecretAccessKey === ""}
  161. onClick={createCreds}
  162. status={createStatus}
  163. >
  164. Continue
  165. </Button>
  166. </>
  167. );
  168. }
  169. return (
  170. <>
  171. <Text size={16}>
  172. <BackButton width="140px" onClick={goBack}>
  173. <i className="material-icons">first_page</i>
  174. Select cloud
  175. </BackButton>
  176. <HSpacer />
  177. <Img src={aws} />
  178. Set AWS credentials
  179. <HelperButton onClick={() => window.open("https://docs.porter.run/getting-started/provisioning-on-aws/", "_blank")}>
  180. <i className="material-icons">help_outline</i>
  181. </HelperButton>
  182. </Text>
  183. <Spacer y={1} />
  184. <Text color="helper">
  185. Select your credentials from the list below, or add a new set of credentials:
  186. </Text>
  187. <Spacer y={1} />
  188. {
  189. isLoading ? (
  190. <Loading height="150px" />
  191. ) : (
  192. renderContent()
  193. )
  194. }
  195. </>
  196. );
  197. };
  198. export default CredentialsForm;
  199. const HelperButton = styled.div`
  200. cursor: pointer;
  201. display: flex;
  202. align-items: center;
  203. margin-left: 10px;
  204. justify-content: center;
  205. > i {
  206. color: #aaaabb;
  207. width: 24px;
  208. height: 24px;
  209. font-size: 20px;
  210. border-radius: 20px;
  211. }
  212. `;
  213. const CloseButton = styled.div`
  214. position: absolute;
  215. top: 15px;
  216. right: 15px;
  217. padding: 5px;
  218. border-radius: 100px;
  219. display: flex;
  220. align-items: center;
  221. justify-content: center;
  222. cursor: pointer;
  223. background: #ffffff11;
  224. :hover {
  225. background: #ffffff22;
  226. > i {
  227. color: #ffffff;
  228. }
  229. }
  230. > i {
  231. font-size: 20px;
  232. color: #aaaabb;
  233. }
  234. `;
  235. const HSpacer = styled.div`
  236. height: 1px;
  237. width: 17px;
  238. `;
  239. const Icon = styled.img`
  240. width: 15px;
  241. margin-right: 15px;
  242. `;
  243. const CreateRow = styled.div`
  244. height: 50px;
  245. display: flex;
  246. cursor: pointer;
  247. align-items: center;
  248. font-size: 13px;
  249. padding: 20px;
  250. background: #ffffff11;
  251. :hover {
  252. background: #ffffff18;
  253. }
  254. `;
  255. const Br = styled.div<{ height?: string }>`
  256. width: 100%;
  257. height: ${props => props.height || "20px"};
  258. `;
  259. const Img = styled.img`
  260. height: 18px;
  261. margin-right: 15px;
  262. `;
  263. const BackButton = styled.div`
  264. display: flex;
  265. align-items: center;
  266. justify-content: space-between;
  267. cursor: pointer;
  268. font-size: 13px;
  269. height: 35px;
  270. padding: 5px 13px;
  271. padding-right: 15px;
  272. border: 1px solid #ffffff55;
  273. border-radius: 100px;
  274. width: ${(props: { width: string }) => props.width};
  275. color: white;
  276. background: #ffffff11;
  277. :hover {
  278. background: #ffffff22;
  279. }
  280. > i {
  281. color: white;
  282. font-size: 16px;
  283. margin-right: 6px;
  284. margin-left: -2px;
  285. }
  286. `;
  287. const Name = styled.div`
  288. font-size: 13px;
  289. font-weight: 500;
  290. `;
  291. const Credential = styled.div<{ isLast?: boolean; isSelected?: boolean }>`
  292. height: 50px;
  293. display: flex;
  294. cursor: pointer;
  295. align-items: center;
  296. padding: 20px;
  297. border-bottom: ${props => props.isLast ? "" : "1px solid #7a7b80"};
  298. background: ${props => props.isSelected ? "#ffffff33" : "#ffffff11"};
  299. :hover {
  300. background: ${props => props.isSelected ? "" : "#ffffff18"};
  301. }
  302. `;
  303. const CredentialList = styled.div`
  304. width: 100%;
  305. border: 1px solid #7a7b80;
  306. border-radius: 5px;
  307. `;
  308. const StyledForm = styled.div`
  309. position: relative;
  310. padding: 15px 30px 25px;
  311. border-radius: 5px;
  312. background: #26292e;
  313. border: 1px solid #494b4f;
  314. font-size: 13px;
  315. margin-bottom: 30px;
  316. `;