DatabasesList.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import CopyToClipboard from "components/CopyToClipboard";
  2. import Table from "components/Table";
  3. import React, { useContext, useEffect, useMemo, useState } from "react";
  4. import { useRouteMatch } from "react-router";
  5. import { Link } from "react-router-dom";
  6. import { Column, Row } from "react-table";
  7. import api from "shared/api";
  8. import useAuth from "shared/auth/useAuth";
  9. import { Context } from "shared/Context";
  10. import { useRouting } from "shared/routing";
  11. import styled from "styled-components";
  12. import { mock_database_list } from "./mock_data";
  13. export type DatabaseObject = {
  14. cluster_id: number;
  15. project_id: number;
  16. infra_id: number;
  17. instance_id: string;
  18. instance_name: string;
  19. status: string;
  20. instance_endpoint: string;
  21. };
  22. const DatabasesList = () => {
  23. const {
  24. currentCluster,
  25. currentProject,
  26. setCurrentError,
  27. setCurrentModal,
  28. setCurrentOverlay,
  29. user,
  30. } = useContext(Context);
  31. const { url } = useRouteMatch();
  32. const [isLoading, setIsLoading] = useState(true);
  33. const [databases, setDatabases] = useState<DatabaseObject[]>([]);
  34. const [isAuth] = useAuth();
  35. const { pushQueryParams } = useRouting();
  36. useEffect(() => {
  37. let isSubscribed = true;
  38. api
  39. .getDatabases(
  40. "<token>",
  41. {},
  42. { project_id: currentProject.id, cluster_id: currentCluster.id }
  43. )
  44. .then((res) => {
  45. if (isSubscribed) {
  46. setDatabases(res.data);
  47. setIsLoading(false);
  48. }
  49. })
  50. .catch((error) => {
  51. console.error(error);
  52. setCurrentError(error);
  53. });
  54. // if (isSubscribed) {
  55. // setDatabases(mock_database_list);
  56. // setIsLoading(false);
  57. // }
  58. return () => {
  59. isSubscribed = false;
  60. };
  61. }, [currentCluster, currentProject]);
  62. const handleDeleteDatabase = async (project_id: number, infra_id: number) => {
  63. try {
  64. await api.destroyInfra(
  65. "<token>",
  66. {},
  67. {
  68. project_id,
  69. infra_id,
  70. }
  71. );
  72. // call an endpoint for updating the database status
  73. await api.updateDatabaseStatus(
  74. "<token>",
  75. {
  76. status: "destroying",
  77. },
  78. {
  79. project_id,
  80. infra_id,
  81. }
  82. );
  83. setCurrentOverlay(null);
  84. pushQueryParams({ current_tab: "provisioner-status" });
  85. } catch (error) {
  86. console.error(error);
  87. setCurrentError("We couldn't delete the infra, please try again.");
  88. }
  89. };
  90. const columns = useMemo<Column<DatabaseObject>[]>(() => {
  91. let columns: Column<DatabaseObject>[] = [
  92. {
  93. Header: "Instance id",
  94. accessor: "instance_id",
  95. },
  96. {
  97. Header: "Name",
  98. accessor: "instance_name",
  99. },
  100. {
  101. Header: "Status",
  102. accessor: "status",
  103. Cell: ({ cell }) => {
  104. const status: "running" | "destroying" = cell.value as any;
  105. return <Status status={status}>{status}</Status>;
  106. },
  107. },
  108. {
  109. Header: "Endpoint",
  110. accessor: "instance_endpoint",
  111. Cell: ({ row }) => {
  112. return (
  113. <>
  114. <CopyToClipboard as={Url} text={row.original.instance_endpoint}>
  115. <span>{row.original.instance_endpoint}</span>
  116. <i className="material-icons-outlined">content_copy</i>
  117. </CopyToClipboard>
  118. </>
  119. );
  120. },
  121. },
  122. {
  123. id: "connect_button",
  124. Cell: ({ row }: any) => {
  125. return (
  126. <>
  127. <ConnectButton
  128. onClick={() =>
  129. setCurrentModal("ConnectToDatabaseInstructionsModal", {
  130. endpoint: row.original.instance_endpoint,
  131. name: row.original.instance_name,
  132. })
  133. }
  134. >
  135. Connect
  136. </ConnectButton>
  137. </>
  138. );
  139. },
  140. width: 50,
  141. },
  142. ];
  143. if (isAuth("cluster", "", ["get", "delete"])) {
  144. columns.push({
  145. id: "delete_button",
  146. Cell: ({ row }: { row: Row<DatabaseObject> }) => {
  147. return (
  148. <>
  149. <DeleteButton
  150. onClick={() =>
  151. setCurrentOverlay({
  152. message: `Are you sure you want to delete ${row.original.instance_name}?`,
  153. onYes: () =>
  154. handleDeleteDatabase(
  155. row.original.project_id,
  156. row.original.infra_id
  157. ),
  158. onNo: () => setCurrentOverlay(null),
  159. })
  160. }
  161. >
  162. <i className="material-icons">delete</i>
  163. </DeleteButton>
  164. </>
  165. );
  166. },
  167. width: 50,
  168. });
  169. } else {
  170. columns = columns.filter((col) => col.id !== "delete_button");
  171. }
  172. return columns;
  173. }, [user]);
  174. const data = useMemo<Array<DatabaseObject>>(() => {
  175. return databases;
  176. }, [databases]);
  177. return (
  178. <DatabasesListWrapper>
  179. <ControlRow>
  180. <Button
  181. to={`/infrastructure/provision/RDS?origin=${encodeURIComponent(
  182. "/databases"
  183. )}`}
  184. >
  185. <i className="material-icons">add</i>
  186. Create database
  187. </Button>
  188. </ControlRow>
  189. <StyledTableWrapper>
  190. <Table columns={columns} data={data} isLoading={isLoading} />
  191. </StyledTableWrapper>
  192. </DatabasesListWrapper>
  193. );
  194. };
  195. export default DatabasesList;
  196. const Status = styled.div<{ status: "running" | "destroying" }>`
  197. padding: 5px 10px;
  198. margin-right: 12px;
  199. background: ${(props) => {
  200. if (props.status === "running") return "#38a88a";
  201. if (props.status === "destroying") return "#cc3d42";
  202. }};
  203. font-size: 13px;
  204. border-radius: 3px;
  205. display: flex;
  206. align-items: center;
  207. justify-content: center;
  208. max-height: 25px;
  209. max-width: 80px;
  210. text-transform: capitalize;
  211. font-weight: 400;
  212. user-select: none;
  213. `;
  214. const DeleteButton = styled.div`
  215. display: flex;
  216. visibility: ${(props: { invis?: boolean }) =>
  217. props.invis ? "hidden" : "visible"};
  218. align-items: center;
  219. justify-content: center;
  220. width: 30px;
  221. float: right;
  222. height: 30px;
  223. :hover {
  224. background: #ffffff11;
  225. border-radius: 20px;
  226. cursor: pointer;
  227. }
  228. > i {
  229. font-size: 20px;
  230. color: #ffffff44;
  231. border-radius: 20px;
  232. }
  233. `;
  234. const DatabasesListWrapper = styled.div`
  235. margin-top: 35px;
  236. `;
  237. const StyledTableWrapper = styled.div`
  238. padding: 14px;
  239. position: relative;
  240. border-radius: 8px;
  241. background: #26292e;
  242. border: 1px solid #494b4f;
  243. width: 100%;
  244. height: 100%;
  245. :not(:last-child) {
  246. margin-bottom: 25px;
  247. }
  248. `;
  249. const ControlRow = styled.div`
  250. display: flex;
  251. margin-left: auto;
  252. justify-content: space-between;
  253. align-items: center;
  254. margin-bottom: 35px;
  255. padding-left: 0px;
  256. `;
  257. const Url = styled.a`
  258. max-width: 300px;
  259. font-size: 13px;
  260. user-select: text;
  261. font-weight: 400;
  262. display: flex;
  263. align-items: center;
  264. > i {
  265. margin-left: 10px;
  266. font-size: 15px;
  267. }
  268. > span {
  269. overflow: hidden;
  270. white-space: nowrap;
  271. text-overflow: ellipsis;
  272. }
  273. :hover {
  274. cursor: pointer;
  275. }
  276. `;
  277. const Button = styled(Link)`
  278. display: flex;
  279. flex-direction: row;
  280. align-items: center;
  281. justify-content: space-between;
  282. font-size: 13px;
  283. cursor: pointer;
  284. font-family: "Work Sans", sans-serif;
  285. border-radius: 20px;
  286. color: white;
  287. height: 35px;
  288. padding: 0px 8px;
  289. padding-bottom: 1px;
  290. margin-right: 10px;
  291. font-weight: 500;
  292. padding-right: 15px;
  293. overflow: hidden;
  294. white-space: nowrap;
  295. text-overflow: ellipsis;
  296. box-shadow: 0 5px 8px 0px #00000010;
  297. cursor: ${(props: { disabled?: boolean }) =>
  298. props.disabled ? "not-allowed" : "pointer"};
  299. background: ${(props: { disabled?: boolean }) =>
  300. props.disabled ? "#aaaabbee" : "#616FEEcc"};
  301. :hover {
  302. background: ${(props: { disabled?: boolean }) =>
  303. props.disabled ? "" : "#505edddd"};
  304. }
  305. > i {
  306. color: white;
  307. width: 18px;
  308. height: 18px;
  309. font-weight: 600;
  310. font-size: 12px;
  311. border-radius: 20px;
  312. display: flex;
  313. align-items: center;
  314. margin-right: 5px;
  315. justify-content: center;
  316. }
  317. `;
  318. const ConnectButton = styled.button<{}>`
  319. height: 25px;
  320. font-size: 13px;
  321. font-weight: 500;
  322. font-family: "Work Sans", sans-serif;
  323. color: white;
  324. display: flex;
  325. align-items: center;
  326. padding: 6px 20px 7px 20px;
  327. text-align: left;
  328. border: 0;
  329. border-radius: 5px;
  330. background: #5561c0;
  331. box-shadow: 0 2px 5px 0 #00000030;
  332. cursor: pointer;
  333. user-select: none;
  334. :focus {
  335. outline: 0;
  336. }
  337. :hover {
  338. filter: brightness(120%);
  339. }
  340. `;