MinionPoolMainDetails.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. /*
  2. Copyright (C) 2020 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import { observer } from "mobx-react";
  15. import * as React from "react";
  16. import { Link } from "react-router-dom";
  17. import styled, { css } from "styled-components";
  18. import fieldHelper from "@src/@types/Field";
  19. import { DeploymentItem, ReplicaItem, TransferItem } from "@src/@types/MainItem";
  20. import { MinionPool } from "@src/@types/MinionPool";
  21. import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
  22. import { ThemePalette, ThemeProps } from "@src/components/Theme";
  23. import CopyMultilineValue from "@src/components/ui/CopyMultilineValue";
  24. import CopyValue from "@src/components/ui/CopyValue";
  25. import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
  26. import { OptionsSchemaPlugin } from "@src/plugins";
  27. import DateUtils from "@src/utils/DateUtils";
  28. import LabelDictionary from "@src/utils/LabelDictionary";
  29. import type { Endpoint } from "@src/@types/Endpoint";
  30. import type { Field as FieldType } from "@src/@types/Field";
  31. const Wrapper = styled.div<any>`
  32. display: flex;
  33. flex-direction: column;
  34. padding-bottom: 48px;
  35. `;
  36. const ColumnsLayout = styled.div<any>`
  37. display: flex;
  38. `;
  39. const Column = styled.div<any>`
  40. ${props => ThemeProps.exactWidth(props.width)}
  41. `;
  42. const Row = styled.div<any>`
  43. margin-bottom: 32px;
  44. &:last-child {
  45. margin-bottom: 16px;
  46. }
  47. `;
  48. const Field = styled.div<any>`
  49. display: flex;
  50. flex-direction: column;
  51. `;
  52. const Label = styled.div<any>`
  53. font-size: 10px;
  54. color: ${ThemePalette.grayscale[3]};
  55. font-weight: ${ThemeProps.fontWeights.medium};
  56. text-transform: uppercase;
  57. display: flex;
  58. align-items: center;
  59. `;
  60. const StatusIconStub = styled.div<any>`
  61. ${ThemeProps.exactSize("16px")}
  62. `;
  63. const Value = styled.div<any>`
  64. display: ${props =>
  65. props.flex ? "flex" : props.block ? "block" : "inline-table"};
  66. margin-top: 3px;
  67. ${props => (props.capitalize ? "text-transform: capitalize;" : "")}
  68. `;
  69. const ValueLink = styled(Link)`
  70. display: flex;
  71. margin-top: 3px;
  72. color: ${ThemePalette.primary};
  73. text-decoration: none;
  74. cursor: pointer;
  75. `;
  76. const PropertiesTable = styled.div<any>``;
  77. const PropertyRow = styled.div<any>`
  78. display: flex;
  79. justify-content: space-between;
  80. margin-bottom: 4px;
  81. `;
  82. const PropertyText = css``;
  83. const PropertyName = styled.div<any>`
  84. ${PropertyText}
  85. overflow: hidden;
  86. text-overflow: ellipsis;
  87. max-width: 50%;
  88. `;
  89. const PropertyValue = styled.div<any>`
  90. ${PropertyText}
  91. color: ${ThemePalette.grayscale[4]};
  92. text-align: right;
  93. overflow: hidden;
  94. text-overflow: ellipsis;
  95. max-width: calc(50% + 16px);
  96. margin-right: -16px;
  97. `;
  98. type Props = {
  99. item?: MinionPool | null;
  100. replicas: ReplicaItem[];
  101. deployments: DeploymentItem[];
  102. schema: FieldType[];
  103. schemaLoading: boolean;
  104. endpoints: Endpoint[];
  105. bottomControls: React.ReactNode;
  106. };
  107. @observer
  108. class MinionPoolMainDetails extends React.Component<Props> {
  109. getEndpoint(): Endpoint | undefined {
  110. const endpoint = this.props.endpoints.find(
  111. e => e.id === this.props.item?.endpoint_id
  112. );
  113. return endpoint;
  114. }
  115. renderLastExecutionTime() {
  116. return this.props.item?.updated_at
  117. ? this.renderValue(
  118. DateUtils.getLocalDate(this.props.item.updated_at).toFormat(
  119. "yyyy-LL-dd HH:mm:ss"
  120. )
  121. )
  122. : "-";
  123. }
  124. renderValue(value: string, capitalize?: boolean) {
  125. return <CopyValue value={value} maxWidth="90%" capitalize={capitalize} />;
  126. }
  127. renderEndpointLink(): React.ReactNode {
  128. const endpointIsMissing = (
  129. <Value flex>
  130. <StatusIcon style={{ marginRight: "8px" }} status="ERROR" />
  131. Endpoint is missing
  132. </Value>
  133. );
  134. const endpoint = this.getEndpoint();
  135. if (endpoint) {
  136. return (
  137. <ValueLink to={`/endpoints/${endpoint.id}`}>{endpoint.name}</ValueLink>
  138. );
  139. }
  140. return endpointIsMissing;
  141. }
  142. renderPropertiesTable(propertyNames: string[]) {
  143. const endpoint = this.getEndpoint();
  144. const getValue = (name: string, value: any) => {
  145. if (
  146. value.join &&
  147. value.length &&
  148. value[0].destination &&
  149. value[0].source
  150. ) {
  151. return value
  152. .map(
  153. (v: { source: any; destination: any }) =>
  154. `${v.source}=${v.destination}`
  155. )
  156. .join(", ");
  157. }
  158. const schema = this.props.schema;
  159. return fieldHelper.getValueAlias({
  160. name,
  161. value,
  162. fields: schema,
  163. targetProvider: endpoint && endpoint.type,
  164. });
  165. };
  166. let properties: any[] = [];
  167. const plugin = endpoint && OptionsSchemaPlugin.for(endpoint.type);
  168. const deploymentImageMapFieldName =
  169. plugin && plugin.deploymentImageMapFieldName;
  170. let dictionaryKey = "";
  171. if (endpoint) {
  172. dictionaryKey = `${endpoint.type}-minion-pool`;
  173. }
  174. const environment = this.props.item?.environment_options;
  175. propertyNames.forEach(pn => {
  176. const value = environment ? environment[pn] : "";
  177. const label = LabelDictionary.get(pn, dictionaryKey);
  178. if (value && value.join) {
  179. value.forEach((v: any, i: number) => {
  180. const useLabel = i === 0 ? label : "";
  181. properties.push({ label: useLabel, value: v });
  182. });
  183. } else if (value && typeof value === "object") {
  184. properties = properties.concat(
  185. Object.keys(value).map(p => {
  186. if (p === "disk_mappings") {
  187. return null;
  188. }
  189. let fieldName = pn;
  190. if (
  191. deploymentImageMapFieldName &&
  192. fieldName === deploymentImageMapFieldName
  193. ) {
  194. fieldName = p;
  195. }
  196. return {
  197. label: `${label} - ${LabelDictionary.get(p)}`,
  198. value: getValue(fieldName, value[p]),
  199. };
  200. })
  201. );
  202. } else {
  203. properties.push({ label, value: getValue(pn, value) });
  204. }
  205. });
  206. return (
  207. <PropertiesTable>
  208. {properties
  209. .filter(Boolean)
  210. .filter(p => p.value != null && p.value !== "")
  211. .map(prop => (
  212. <PropertyRow key={prop.label}>
  213. <PropertyName>{prop.label}</PropertyName>
  214. <PropertyValue>
  215. <CopyValue value={prop.value} />
  216. </PropertyValue>
  217. </PropertyRow>
  218. ))}
  219. </PropertiesTable>
  220. );
  221. }
  222. renderUsage(items: TransferItem[]) {
  223. return items.map(item => {
  224. const actionHref =
  225. item.type === "replica"
  226. ? "transfers" : "deployments"
  227. return (<div key={item.id}>
  228. <ValueLink to={`/${actionHref}/${item.id}`}>
  229. {item.instances[0]}
  230. </ValueLink>
  231. </div>);
  232. });
  233. }
  234. renderTable() {
  235. const endpoint = this.getEndpoint();
  236. const lastUpdated = this.renderLastExecutionTime();
  237. const getPropertyNames = () => {
  238. const env = this.props.item?.environment_options;
  239. return env
  240. ? Object.keys(env).filter(
  241. k =>
  242. k !== "network_map" &&
  243. (k !== "storage_mappings" ||
  244. (env[k] != null &&
  245. typeof env[k] === "object" &&
  246. Object.keys(env[k]).length > 0))
  247. )
  248. : [];
  249. };
  250. const usage: TransferItem[] = this.props.replicas.concat(
  251. this.props.deployments as any[]
  252. );
  253. return (
  254. <ColumnsLayout>
  255. <Column width="42.5%">
  256. <Row>
  257. <Field>
  258. <Label>Endpoint</Label>
  259. {this.renderEndpointLink()}
  260. </Field>
  261. </Row>
  262. <Row>
  263. <EndpointLogos endpoint={(endpoint ? endpoint.type : "") as any} />
  264. </Row>
  265. <Row>
  266. <Field>
  267. <Label>Id</Label>
  268. {this.renderValue(this.props.item?.id || "-")}
  269. </Field>
  270. </Row>
  271. <Row>
  272. <Field>
  273. <Label>Pool Platform</Label>
  274. {this.renderValue(this.props.item?.platform || "-", true)}
  275. </Field>
  276. </Row>
  277. <Row>
  278. <Field>
  279. <Label>Pool OS Type</Label>
  280. {this.renderValue(this.props.item?.os_type || "-", true)}
  281. </Field>
  282. </Row>
  283. <Row>
  284. <Field>
  285. <Label>Created</Label>
  286. {this.props.item?.created_at ? (
  287. this.renderValue(
  288. DateUtils.getLocalDate(this.props.item.created_at).toFormat(
  289. "yyyy-LL-dd HH:mm:ss"
  290. )
  291. )
  292. ) : (
  293. <Value>-</Value>
  294. )}
  295. </Field>
  296. </Row>
  297. {this.props.item?.notes ? (
  298. <Row>
  299. <Field>
  300. <Label>Notes</Label>
  301. <CopyMultilineValue value={this.props.item.notes} />
  302. </Field>
  303. </Row>
  304. ) : null}
  305. {lastUpdated ? (
  306. <Row>
  307. <Field>
  308. <Label>Last Updated</Label>
  309. <Value>{lastUpdated}</Value>
  310. </Field>
  311. </Row>
  312. ) : null}
  313. <Row>
  314. <Field>
  315. <Label>Used in Transfers ({usage.length})</Label>
  316. {usage.length > 0 ? this.renderUsage(usage) : <Value>-</Value>}
  317. </Field>
  318. </Row>
  319. </Column>
  320. <Column width="9.5%" />
  321. <Column width="48%" style={{ flexGrow: 1 }}>
  322. {getPropertyNames().length > 0 ? (
  323. <Row>
  324. <Field>
  325. <Label>
  326. Environment options{" "}
  327. {this.props.schemaLoading ? (
  328. <StatusIcon
  329. status="RUNNING"
  330. style={{ marginLeft: "8px" }}
  331. />
  332. ) : (
  333. <StatusIconStub />
  334. )}
  335. </Label>
  336. <Value block>
  337. {this.renderPropertiesTable(getPropertyNames())}
  338. </Value>
  339. </Field>
  340. </Row>
  341. ) : null}
  342. <Row>
  343. <Field>
  344. <Label>Minimum Minions</Label>
  345. <Value>{this.props.item?.minimum_minions || "1"}</Value>
  346. </Field>
  347. </Row>
  348. <Row>
  349. <Field>
  350. <Label>Maximum Minions</Label>
  351. <Value>{this.props.item?.maximum_minions || "1"}</Value>
  352. </Field>
  353. </Row>
  354. <Row>
  355. <Field>
  356. <Label>Minion Max Idle Time (s)</Label>
  357. <Value>{this.props.item?.minion_max_idle_time || "-"}</Value>
  358. </Field>
  359. </Row>
  360. <Row>
  361. <Field>
  362. <Label>Minion Retention Strategy</Label>
  363. <Value>
  364. {this.props.item?.minion_retention_strategy || "delete"}
  365. </Value>
  366. </Field>
  367. </Row>
  368. </Column>
  369. </ColumnsLayout>
  370. );
  371. }
  372. renderBottomControls() {
  373. return this.props.bottomControls;
  374. }
  375. render() {
  376. return (
  377. <Wrapper>
  378. {this.renderTable()}
  379. {this.renderBottomControls()}
  380. </Wrapper>
  381. );
  382. }
  383. }
  384. export default MinionPoolMainDetails;