CredentialsForm.tsx 8.1 KB

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