2
0

EndpointDetailsContent.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. Copyright (C) 2017 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 * as React from 'react'
  15. import { Link } from 'react-router-dom'
  16. import { observer } from 'mobx-react'
  17. import styled from 'styled-components'
  18. import EndpointLogos from '../EndpointLogos/EndpointLogos'
  19. import PasswordValue from '../../../ui/PasswordValue/PasswordValue'
  20. import Button from '../../../ui/Button/Button'
  21. import CopyValue from '../../../ui/CopyValue/CopyValue'
  22. import CopyMultilineValue from '../../../ui/CopyMultilineValue/CopyMultilineValue'
  23. import StatusImage from '../../../ui/StatusComponents/StatusImage/StatusImage'
  24. import type { Endpoint } from '../../../../@types/Endpoint'
  25. import { ThemePalette, ThemeProps } from '../../../Theme'
  26. import DateUtils from '../../../../utils/DateUtils'
  27. import LabelDictionary from '../../../../utils/LabelDictionary'
  28. import configLoader from '../../../../utils/Config'
  29. import { Region } from '../../../../@types/Region'
  30. import {
  31. getTransferItemTitle, MigrationItem, ReplicaItem, TransferItem,
  32. } from '../../../../@types/MainItem'
  33. import { Field as FieldType } from '../../../../@types/Field'
  34. import DomUtils from '../../../../utils/DomUtils'
  35. const Wrapper = styled.div<any>`
  36. ${ThemeProps.exactWidth(ThemeProps.contentWidth)}
  37. margin: 0 auto;
  38. padding-left: 126px;
  39. `
  40. const Info = styled.div<any>`
  41. display: flex;
  42. flex-wrap: wrap;
  43. margin-top: 32px;
  44. margin-left: -32px;
  45. `
  46. const Field = styled.div<any>`
  47. ${ThemeProps.exactWidth('calc(50% - 32px)')}
  48. margin-bottom: 32px;
  49. margin-left: 32px;
  50. `
  51. const Label = styled.div<any>`
  52. font-size: 10px;
  53. font-weight: ${ThemeProps.fontWeights.medium};
  54. color: ${ThemePalette.grayscale[3]};
  55. text-transform: uppercase;
  56. margin-bottom: 3px;
  57. `
  58. const Value = styled.div<any>``
  59. const Buttons = styled.div<any>`
  60. display: flex;
  61. justify-content: space-between;
  62. margin-top: 64px;
  63. `
  64. const MainButtons = styled.div<any>``
  65. const DeleteButton = styled.div<any>``
  66. const LoadingWrapper = styled.div<any>`
  67. display: flex;
  68. justify-content: center;
  69. width: 100%;
  70. margin: 32px 0 64px 0;
  71. `
  72. const LinkStyled = styled(Link)`
  73. color: ${ThemePalette.primary};
  74. text-decoration: none;
  75. cursor: pointer;
  76. `
  77. const TransferItems = styled.div`
  78. max-height: 200px;
  79. overflow: auto;
  80. `
  81. const TransferItemWrapper = styled.div`
  82. margin-bottom: 4px;
  83. `
  84. const DownloadLink = styled.div`
  85. display: inline-block;
  86. color: ${ThemePalette.primary};
  87. cursor: pointer;
  88. :hover {
  89. text-decoration: underline;
  90. }
  91. `
  92. type Props = {
  93. item: Endpoint | null,
  94. regions: Region[],
  95. connectionInfo: Endpoint['connection_info'] | null,
  96. loading: boolean,
  97. usage: { migrations: MigrationItem[], replicas: ReplicaItem[] },
  98. connectionInfoSchema: FieldType[],
  99. onDeleteClick: () => void,
  100. onValidateClick: () => void,
  101. }
  102. @observer
  103. class EndpointDetailsContent extends React.Component<Props> {
  104. renderedKeys!: { [prop: string]: boolean }
  105. renderDownloadValue(value: string, fieldName: string) {
  106. const endpoint = this.props.item
  107. if (!endpoint) {
  108. return null
  109. }
  110. return (
  111. <DownloadLink onClick={() => {
  112. DomUtils.download(value, fieldName)
  113. }}
  114. >Download
  115. </DownloadLink>
  116. )
  117. }
  118. renderConnectionInfoLoading() {
  119. if (!this.props.loading) {
  120. return null
  121. }
  122. return (
  123. <LoadingWrapper>
  124. <StatusImage loading data-test-id="edContent-connLoading" />
  125. </LoadingWrapper>
  126. )
  127. }
  128. renderConnectionInfo(connectionInfo: any): React.ReactNode {
  129. if (!connectionInfo) {
  130. return null
  131. }
  132. return Object.keys(connectionInfo).map(key => {
  133. let value = connectionInfo[key]
  134. if (key === 'secret_ref') {
  135. return null
  136. }
  137. if (typeof connectionInfo[key] === 'object') {
  138. return this.renderConnectionInfo(connectionInfo[key])
  139. }
  140. if (this.renderedKeys[key]) {
  141. return null
  142. }
  143. this.renderedKeys[key] = true
  144. if (value === true) {
  145. value = 'Yes'
  146. } else if (value === false) {
  147. value = 'No'
  148. } else if (!value) {
  149. value = '-'
  150. }
  151. let valueElement = null
  152. const schemaField = this.props.connectionInfoSchema.find(f => f.name === key)
  153. if (configLoader.config.passwordFields.find(fn => fn === key) || key.indexOf('password') > -1) {
  154. valueElement = <PasswordValue value={value} data-test-id="edContent-connPassword" />
  155. } else if (schemaField?.useFile) {
  156. valueElement = this.renderDownloadValue(value, key)
  157. } else {
  158. valueElement = this.renderValue(value, `connValue-${key}`)
  159. }
  160. return (
  161. <Field key={key}>
  162. <Label>{LabelDictionary.get(key)}</Label>
  163. {valueElement}
  164. </Field>
  165. )
  166. })
  167. }
  168. renderButtons() {
  169. return (
  170. <Buttons>
  171. <MainButtons>
  172. <Button onClick={this.props.onValidateClick} data-test-id="edContent-validateButton">Validate Endpoint</Button>
  173. </MainButtons>
  174. <DeleteButton>
  175. <Button hollow alert onClick={this.props.onDeleteClick} data-test-id="edContent-deleteButton">Delete Endpoint</Button>
  176. </DeleteButton>
  177. </Buttons>
  178. )
  179. }
  180. renderValue(value: string, dataTestId?: string) {
  181. return <CopyValue data-test-id={dataTestId ? `edContent-${dataTestId}` : undefined} value={value} maxWidth="90%" />
  182. }
  183. renderRegions() {
  184. return (
  185. <span>
  186. {this.props.item?.mapped_regions.map(regionId => this.props.regions.find(r => r.id === regionId)?.name).join(', ') || '-'}
  187. </span>
  188. )
  189. }
  190. renderUsage(items: TransferItem[]) {
  191. return (
  192. <TransferItems>
  193. {items.map(item => (
  194. <TransferItemWrapper>
  195. <LinkStyled
  196. key={item.id}
  197. to={`/${item.type}s/${item.id}`}
  198. >
  199. {getTransferItemTitle(item)}
  200. </LinkStyled>
  201. </TransferItemWrapper>
  202. ))}
  203. </TransferItems>
  204. )
  205. }
  206. render() {
  207. this.renderedKeys = {}
  208. const {
  209. // eslint-disable-next-line @typescript-eslint/naming-convention
  210. type, name, description, created_at, id,
  211. } = this.props.item || {}
  212. const usage: TransferItem[] = this.props.usage.replicas
  213. .concat(this.props.usage.migrations as any[])
  214. return (
  215. <Wrapper>
  216. <EndpointLogos endpoint={type} />
  217. <Info>
  218. <Field>
  219. <Label>Id</Label>
  220. {this.renderValue(id || '')}
  221. </Field>
  222. <Field>
  223. <Label>Name</Label>
  224. {this.renderValue(name || '', 'name')}
  225. </Field>
  226. <Field>
  227. <Label>Type</Label>
  228. {this.renderValue(this.props.item ? configLoader.config.providerNames[this.props.item.type] : '', 'type')}
  229. </Field>
  230. <Field>
  231. <Label>Coriolis Regions</Label>
  232. {this.renderRegions()}
  233. </Field>
  234. <Field>
  235. <Label>Description</Label>
  236. {description ? <CopyMultilineValue data-test-id="edContent-description" value={description} /> : <Value>-</Value>}
  237. </Field>
  238. <Field>
  239. <Label>Created</Label>
  240. {this.renderValue(DateUtils.getLocalTime(created_at).format('DD/MM/YYYY HH:mm'), 'created')}
  241. </Field>
  242. <Field>
  243. <Label>Used in replicas/migrations ({usage.length})</Label>
  244. {usage.length > 0 ? this.renderUsage(usage) : <Value>-</Value>}
  245. </Field>
  246. {!this.props.connectionInfo ? this.renderConnectionInfoLoading() : null}
  247. {this.renderConnectionInfo(this.props.connectionInfo)}
  248. </Info>
  249. {this.renderButtons()}
  250. </Wrapper>
  251. )
  252. }
  253. }
  254. export default EndpointDetailsContent