MinionPoolMachines.tsx 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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 * as React from 'react'
  15. import styled, { createGlobalStyle, css } from 'styled-components'
  16. import moment from 'moment'
  17. import { Collapse } from 'react-collapse'
  18. import { Link } from 'react-router-dom'
  19. import { MinionMachine, MinionPool } from '@src/@types/MinionPool'
  20. import DropdownLink from '@src/components/ui/Dropdowns/DropdownLink'
  21. import { ItemReplicaBadge } from '@src/components/ui/Dropdowns/NotificationDropdown'
  22. import { ThemePalette, ThemeProps } from '@src/components/Theme'
  23. import Arrow from '@src/components/ui/Arrow'
  24. import StatusPill from '@src/components/ui/StatusComponents/StatusPill'
  25. import { MigrationItem, ReplicaItem, TransferItem } from '@src/@types/MainItem'
  26. import networkImage from './images/network.svg'
  27. const GlobalStyle = createGlobalStyle`
  28. .ReactCollapse--collapse {
  29. transition: height 0.4s ease-in-out;
  30. }
  31. `
  32. const Wrapper = styled.div``
  33. const NoMachines = styled.div`
  34. text-align: center;
  35. `
  36. const Header = styled.div`
  37. display: flex;
  38. align-items: center;
  39. margin-bottom: 32px;
  40. margin-left: 20px;
  41. `
  42. const ArrowStyled = styled(Arrow)`
  43. position: absolute;
  44. left: -24px;
  45. `
  46. const Row = styled.div<any>`
  47. position: relative;
  48. padding: 8px 0;
  49. border-top: 1px solid white;
  50. transition: all ${ThemeProps.animations.swift};
  51. &:last-child {
  52. border-bottom: 0;
  53. border-bottom-left-radius: ${ThemeProps.borderRadius};
  54. border-bottom-right-radius: ${ThemeProps.borderRadius};
  55. }
  56. &:hover {
  57. background: ${ThemePalette.grayscale[0]};
  58. ${ArrowStyled} {
  59. opacity: 1;
  60. }
  61. }
  62. cursor: pointer;
  63. `
  64. const RowHeader = styled.div<any>`
  65. display: flex;
  66. align-items: center;
  67. padding: 0 16px;
  68. `
  69. const RowHeaderColumn = styled.div<any>`
  70. display: flex;
  71. align-items: center;
  72. ${ThemeProps.exactWidth('50%')}
  73. `
  74. const HeaderName = styled.div<any>`
  75. overflow: hidden;
  76. text-overflow: ellipsis;
  77. ${props => ThemeProps.exactWidth(`calc(100% - ${props.source ? 120 : 8}px)`)}
  78. `
  79. const HeaderIcon = styled.div<any>`
  80. min-width: 16px;
  81. min-height: 16px;
  82. background: url('${networkImage}') center no-repeat;
  83. margin-right: 16px;
  84. `
  85. const HeaderFilter = styled.div``
  86. const HeaderText = styled.div`
  87. margin-left: 16px;
  88. `
  89. const RowBody = styled.div<any>`
  90. display: flex;
  91. color: ${ThemePalette.grayscale[5]};
  92. padding: 0 16px;
  93. margin-top: 4px;
  94. `
  95. const RowBodyColumn = styled.div<any>`
  96. margin-top: 8px;
  97. &:first-child {
  98. ${ThemeProps.exactWidth('calc(50% - 70px)')}
  99. margin-right: 88px;
  100. }
  101. &:last-child {
  102. ${ThemeProps.exactWidth('calc(50% - 16px)')}
  103. }
  104. `
  105. const RowBodyColumnValue = styled.div<any>`
  106. overflow-wrap: break-word;
  107. `
  108. const MachinesWrapper = styled.div``
  109. const MachineWrapper = styled.div`
  110. background: ${ThemePalette.grayscale[1]};
  111. border-radius: ${ThemeProps.borderRadius};
  112. `
  113. const MachineTitle = styled.div`
  114. padding: 16px;
  115. border-bottom: 1px solid #7F8795;
  116. font-size: 16px;
  117. `
  118. const MachineBody = styled.div`
  119. padding: 16px;
  120. `
  121. const MachineRow = styled.div<{ secondary?: boolean }>`
  122. display: flex;
  123. margin-bottom: 8px;
  124. align-items: center;
  125. ${props => (props.secondary ? css`
  126. color: ${ThemePalette.grayscale[5]};
  127. margin-bottom: 4px;
  128. ` : '')}
  129. `
  130. const ValueLink = styled(Link)`
  131. display: flex;
  132. color: ${ThemePalette.primary};
  133. text-decoration: none;
  134. cursor: pointer;
  135. `
  136. type FilterType = 'all' | 'allocated' | 'not-allocated'
  137. type Props = {
  138. item?: MinionPool | null,
  139. replicas: ReplicaItem[]
  140. migrations: MigrationItem[]
  141. }
  142. type State = {
  143. filterStatus: FilterType
  144. openedRows: string[]
  145. }
  146. class MinionPoolMachines extends React.Component<Props, State> {
  147. state = {
  148. filterStatus: 'all' as FilterType,
  149. openedRows: [],
  150. }
  151. get machines() {
  152. return this.props.item?.minion_machines || []
  153. }
  154. get filteredMachines() {
  155. switch (this.state.filterStatus) {
  156. case 'all':
  157. return this.machines
  158. case 'allocated':
  159. return this.machines.filter(m => m.allocation_status === 'ALLOCATED' || m.allocation_status === 'AVAILABLE')
  160. default:
  161. return this.machines.filter(m => m.allocation_status !== 'ALLOCATED' && m.allocation_status !== 'AVAILABLE')
  162. }
  163. }
  164. handleRowClick(id: string) {
  165. if (this.state.openedRows.find(i => i === id)) {
  166. this.setState(prevState => ({
  167. openedRows: prevState.openedRows.filter(i => i !== id),
  168. }))
  169. } else {
  170. this.setState(prevState => ({
  171. openedRows: [...prevState.openedRows, id],
  172. }))
  173. }
  174. }
  175. renderNoMachines() {
  176. return (
  177. <NoMachines>There are no Minion Machines allocated to this Minion Pool</NoMachines>
  178. )
  179. }
  180. renderHeader() {
  181. const plural = this.machines.length === 1 ? '' : 's'
  182. return (
  183. <Header>
  184. <HeaderFilter>
  185. <DropdownLink
  186. items={[
  187. { label: 'All', value: 'all' },
  188. { label: 'Allocated', value: 'allocated' },
  189. { label: 'Not Allocated', value: 'not-allocated' },
  190. ]}
  191. selectedItem={this.state.filterStatus}
  192. onChange={item => {
  193. this.setState({
  194. filterStatus: item.value as FilterType,
  195. })
  196. }}
  197. />
  198. </HeaderFilter>
  199. <HeaderText>
  200. {this.machines.length} minion machine{plural}, {this.machines.filter(m => m.allocation_status === 'ALLOCATED' || m.allocation_status === 'AVAILABLE').length} allocated
  201. </HeaderText>
  202. </Header>
  203. )
  204. }
  205. renderConnectionInfo(machine: MinionMachine) {
  206. const isOpened: boolean = Boolean(this.state.openedRows.find(i => i === machine.id))
  207. return (
  208. <Row onClick={() => { this.handleRowClick(machine.id) }}>
  209. <ArrowStyled
  210. primary
  211. orientation={isOpened ? 'up' : 'down'}
  212. opacity={isOpened ? 1 : 0}
  213. thick
  214. />
  215. <RowHeader>
  216. <RowHeaderColumn>
  217. <HeaderIcon />
  218. <HeaderName>Connection Info</HeaderName>
  219. </RowHeaderColumn>
  220. </RowHeader>
  221. <Collapse isOpened={isOpened}>
  222. <RowBody>
  223. <RowBodyColumn>
  224. {Object.keys(machine.connection_info).map(prop => (
  225. <RowBodyColumnValue key={prop}>
  226. {prop}: {machine.connection_info[prop]}
  227. </RowBodyColumnValue>
  228. ))}
  229. </RowBodyColumn>
  230. </RowBody>
  231. </Collapse>
  232. </Row>
  233. )
  234. }
  235. renderMachines() {
  236. if (this.filteredMachines.length === 0) {
  237. return (
  238. <NoMachines>No Minion Machines found</NoMachines>
  239. )
  240. }
  241. return (
  242. <MachinesWrapper>
  243. {this.filteredMachines.map(machine => {
  244. const findTransferItem = (transferItems: TransferItem[]) => transferItems
  245. .find(i => i.id === machine.allocated_action)
  246. const allocatedAction = machine.allocated_action ? (
  247. findTransferItem(this.props.replicas) || findTransferItem(this.props.migrations)
  248. ) : null
  249. return (
  250. <MachineWrapper key={machine.id}>
  251. <MachineTitle>ID: {machine.id}</MachineTitle>
  252. <MachineBody>
  253. <MachineRow>
  254. Allocation Status: <StatusPill style={{ marginLeft: '8px' }} status={machine.allocation_status} />
  255. </MachineRow>
  256. <MachineRow style={{ marginBottom: '16px' }}>
  257. <span style={{ width: '114px' }}>Power Status:</span> <StatusPill style={{ marginLeft: '8px' }} status={machine.power_status} />
  258. </MachineRow>
  259. <MachineRow secondary>Created At: {moment(machine.created_at).format('YYYY-MM-DD HH:mm:ss')}</MachineRow>
  260. {machine.updated_at ? <MachineRow secondary>Updated At: {moment(machine.updated_at).format('YYYY-MM-DD HH:mm:ss')}</MachineRow> : null}
  261. {machine.last_used_at ? <MachineRow secondary>Last Used At: {moment(machine.last_used_at).format('YYYY-MM-DD HH:mm:ss')}</MachineRow> : null}
  262. {machine.allocated_action ? (
  263. <MachineRow secondary>
  264. Allocated Action:
  265. {allocatedAction ? (
  266. <>
  267. <ItemReplicaBadge style={{ margin: '0px 4px 0 5px' }}>{allocatedAction.type === 'replica' ? 'RE' : 'MI'}</ItemReplicaBadge>
  268. <ValueLink
  269. to={`/${allocatedAction.type}s/${allocatedAction.id}`}
  270. >
  271. {allocatedAction.instances[0]}
  272. </ValueLink>
  273. </>
  274. ) : <span>&nbsp;{machine.allocated_action}</span>}
  275. </MachineRow>
  276. ) : null}
  277. </MachineBody>
  278. {machine.connection_info ? this.renderConnectionInfo(machine) : null}
  279. </MachineWrapper>
  280. )
  281. })}
  282. <GlobalStyle />
  283. </MachinesWrapper>
  284. )
  285. }
  286. render() {
  287. return (
  288. <Wrapper>
  289. {this.props.item?.minion_machines.length ? this.renderHeader() : this.renderNoMachines()}
  290. {this.props.item?.minion_machines.length ? this.renderMachines() : null}
  291. </Wrapper>
  292. )
  293. }
  294. }
  295. export default MinionPoolMachines