CredentialsForm.tsx 7.9 KB

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