ExpandedStack.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import Loading from "components/Loading";
  2. import Placeholder from "components/Placeholder";
  3. import TabSelector from "components/TabSelector";
  4. import TitleSection from "components/TitleSection";
  5. import React, { useContext, useState } from "react";
  6. import backArrow from "assets/back_arrow.png";
  7. import { useParams, useRouteMatch } from "react-router";
  8. import api from "shared/api";
  9. import { Context } from "shared/Context";
  10. import { useRouting } from "shared/routing";
  11. import { readableDate } from "shared/string_utils";
  12. import styled from "styled-components";
  13. import ChartList from "../../chart/ChartList";
  14. import Status from "../components/Status";
  15. import {
  16. Action,
  17. Br,
  18. InfoWrapper,
  19. LastDeployed,
  20. NamespaceTag,
  21. SepDot,
  22. Text,
  23. } from "../components/styles";
  24. import { getStackStatus, getStackStatusMessage } from "../shared";
  25. import { FullStackRevision, Stack, StackRevision } from "../types";
  26. import EnvGroups from "./components/EnvGroups";
  27. import RevisionList from "./_RevisionList";
  28. import SourceConfig from "./_SourceConfig";
  29. import { NavLink } from "react-router-dom";
  30. import Settings from "./components/Settings";
  31. import { ExpandedStackStore } from "./Store";
  32. import DynamicLink from "components/DynamicLink";
  33. const ExpandedStack = () => {
  34. const { namespace } = useParams<{
  35. namespace: string;
  36. stack_id: string;
  37. }>();
  38. const { stack, refreshStack } = useContext(ExpandedStackStore);
  39. const { pushFiltered } = useRouting();
  40. const { currentProject, currentCluster, setCurrentError } = useContext(
  41. Context
  42. );
  43. const { url } = useRouteMatch();
  44. const [isDeleting, setIsDeleting] = useState(false);
  45. const [currentTab, setCurrentTab] = useState("apps");
  46. const [currentRevision, setCurrentRevision] = useState<FullStackRevision>(
  47. () => stack.latest_revision
  48. );
  49. const handleDelete = () => {
  50. setIsDeleting(true);
  51. api
  52. .deleteStack(
  53. "<token>",
  54. {},
  55. {
  56. namespace,
  57. project_id: currentProject.id,
  58. cluster_id: currentCluster.id,
  59. stack_id: stack.id,
  60. }
  61. )
  62. .then(() => {
  63. pushFiltered("/stacks", []);
  64. })
  65. .catch((err) => {
  66. setCurrentError(err);
  67. setIsDeleting(false);
  68. });
  69. };
  70. if (stack === null) {
  71. return null;
  72. }
  73. if (isDeleting) {
  74. return (
  75. <Placeholder height="400px">
  76. <div>
  77. <h1>Deleting Stack</h1>
  78. <p>This may take some time...</p>
  79. <Loading />
  80. </div>
  81. </Placeholder>
  82. );
  83. }
  84. return (
  85. <div>
  86. <StackTitleWrapper>
  87. <BackButton to="/stacks">
  88. <BackButtonImg src={backArrow} />
  89. </BackButton>
  90. <TitleSection materialIconClass="material-icons-outlined" icon={"lan"}>
  91. {stack.name}
  92. </TitleSection>
  93. <NamespaceTag.Wrapper>
  94. Namespace
  95. <NamespaceTag.Tag>{stack.namespace}</NamespaceTag.Tag>
  96. </NamespaceTag.Wrapper>
  97. </StackTitleWrapper>
  98. {/* Stack error message */}
  99. {currentRevision &&
  100. currentRevision?.reason &&
  101. currentRevision?.message?.length > 0 ? (
  102. <StackErrorMessageStyles.Wrapper>
  103. <i className="material-icons">history</i>
  104. <StackErrorMessageStyles.Text color="#aaaabb">
  105. {currentRevision?.status === "failed" ? "Error: " : ""}
  106. {currentRevision?.message}
  107. </StackErrorMessageStyles.Text>
  108. </StackErrorMessageStyles.Wrapper>
  109. ) : null}
  110. <Break />
  111. <InfoWrapper>
  112. <LastDeployed>
  113. <Status
  114. status={getStackStatus(stack)}
  115. message={getStackStatusMessage(stack)}
  116. />
  117. <SepDot>•</SepDot>
  118. Last updated {readableDate(stack.updated_at)}
  119. </LastDeployed>
  120. </InfoWrapper>
  121. <RevisionList
  122. revisions={stack.revisions}
  123. currentRevision={currentRevision}
  124. latestRevision={stack.latest_revision}
  125. stackId={stack.id}
  126. stackNamespace={namespace}
  127. onRevisionClick={(revision) => setCurrentRevision(revision)}
  128. onRollback={() => refreshStack()}
  129. ></RevisionList>
  130. <Br />
  131. <TabSelector
  132. currentTab={currentTab}
  133. options={[
  134. {
  135. label: "Apps",
  136. value: "apps",
  137. component: (
  138. <>
  139. <Gap></Gap>
  140. <Action.Row>
  141. <Action.Button to={`${url}/new-app-resource`}>
  142. <i className="material-icons">add</i>
  143. Create App Resource
  144. </Action.Button>
  145. </Action.Row>
  146. {currentRevision.id !== stack.latest_revision.id ? (
  147. <ChartListWrapper>
  148. <Placeholder>
  149. Not available when previewing revisions
  150. </Placeholder>
  151. </ChartListWrapper>
  152. ) : (
  153. <ChartListWrapper>
  154. <ChartList
  155. currentCluster={currentCluster}
  156. currentView="stacks"
  157. namespace={namespace}
  158. sortType="Alphabetical"
  159. appFilters={
  160. stack?.latest_revision?.resources?.map(
  161. (res) => res.name
  162. ) || []
  163. }
  164. closeChartRedirectUrl={`${window.location.pathname}${window.location.search}`}
  165. />
  166. </ChartListWrapper>
  167. )}
  168. </>
  169. ),
  170. },
  171. {
  172. label: "Source Config",
  173. value: "source_config",
  174. component: (
  175. <>
  176. <SourceConfig
  177. namespace={namespace}
  178. revision={currentRevision}
  179. readOnly={stack.latest_revision.id !== currentRevision.id}
  180. onSourceConfigUpdate={() => refreshStack()}
  181. ></SourceConfig>
  182. </>
  183. ),
  184. },
  185. {
  186. label: "Env Groups",
  187. value: "env_groups",
  188. component: (
  189. <>
  190. <Gap></Gap>
  191. <Action.Row>
  192. <Action.Button to={`${url}/new-env-group`}>
  193. <i className="material-icons">add</i>
  194. Create Env Group
  195. </Action.Button>
  196. </Action.Row>
  197. <EnvGroups stack={stack} />
  198. </>
  199. ),
  200. },
  201. {
  202. label: "Settings",
  203. value: "settings",
  204. component: (
  205. <>
  206. <Gap></Gap>
  207. <Settings stackName={stack.name} onDelete={handleDelete} />
  208. </>
  209. ),
  210. },
  211. ]}
  212. setCurrentTab={(tab) => {
  213. setCurrentTab(tab);
  214. }}
  215. ></TabSelector>
  216. <PaddingBottom />
  217. </div>
  218. );
  219. };
  220. export default ExpandedStack;
  221. const PaddingBottom = styled.div`
  222. width: 100%;
  223. height: 150px;
  224. `;
  225. const Break = styled.div`
  226. width: 100%;
  227. height: 20px;
  228. `;
  229. const BackButton = styled(NavLink)`
  230. position: absolute;
  231. top: 0px;
  232. right: 0px;
  233. display: flex;
  234. width: 36px;
  235. cursor: pointer;
  236. height: 36px;
  237. align-items: center;
  238. justify-content: center;
  239. border: 1px solid #ffffff55;
  240. border-radius: 100px;
  241. background: #ffffff11;
  242. :hover {
  243. background: #ffffff22;
  244. > img {
  245. opacity: 1;
  246. }
  247. }
  248. `;
  249. const BackButtonImg = styled.img`
  250. width: 16px;
  251. opacity: 0.75;
  252. `;
  253. const ChartListWrapper = styled.div`
  254. width: 100%;
  255. margin: auto;
  256. padding-bottom: 125px;
  257. `;
  258. const Gap = styled.div`
  259. width: 100%;
  260. background: none;
  261. height: 30px;
  262. `;
  263. const StackErrorMessageStyles = {
  264. Text: styled(Text)`
  265. font-size: 13px;
  266. `,
  267. Wrapper: styled.div`
  268. display: flex;
  269. align-items: center;
  270. margin-top: 5px;
  271. > i {
  272. color: #ffffff44;
  273. margin-right: 8px;
  274. font-size: 20px;
  275. }
  276. `,
  277. Title: styled(Text)`
  278. font-size: 16px;
  279. font-weight: bold;
  280. `,
  281. };
  282. const StackTitleWrapper = styled.div`
  283. width: 100%;
  284. display: flex;
  285. position: relative;
  286. align-items: center;
  287. // Hotfix to make sure the title section and the namespace tag are aligned
  288. ${NamespaceTag.Wrapper} {
  289. margin-left: 17px;
  290. margin-bottom: 13px;
  291. }
  292. `;