index.jsx 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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. // @flow
  15. import React from 'react'
  16. import styled from 'styled-components'
  17. import { observer } from 'mobx-react'
  18. import MainTemplate from '../../templates/MainTemplate'
  19. import Navigation from '../../organisms/Navigation'
  20. import FilterList from '../../organisms/FilterList'
  21. import PageHeader from '../../organisms/PageHeader'
  22. import EndpointListItem from '../../molecules/EndpointListItem'
  23. import AlertModal from '../../organisms/AlertModal'
  24. import Modal from '../../molecules/Modal'
  25. import ChooseProvider from '../../organisms/ChooseProvider'
  26. import Endpoint from '../../organisms/Endpoint'
  27. import type { Endpoint as EndpointType } from '../../../types/Endpoint'
  28. import endpointImage from './images/endpoint-large.svg'
  29. import projectStore from '../../../stores/ProjectStore'
  30. import endpointStore from '../../../stores/EndpointStore'
  31. import migrationStore from '../../../stores/MigrationStore'
  32. import replicaStore from '../../../stores/ReplicaStore'
  33. import providerStore from '../../../stores/ProviderStore'
  34. import LabelDictionary from '../../../utils/LabelDictionary'
  35. import { requestPollTimeout } from '../../../config.js'
  36. const Wrapper = styled.div``
  37. const BulkActions = [
  38. { label: 'Delete', value: 'delete' },
  39. ]
  40. type State = {
  41. showDeleteEndpointsConfirmation: boolean,
  42. confirmationItems: ?EndpointType[],
  43. showChooseProviderModal: boolean,
  44. showEndpointModal: boolean,
  45. providerType: ?string,
  46. showEndpointsInUseModal: boolean,
  47. modalIsOpen: boolean,
  48. }
  49. @observer
  50. class EndpointsPage extends React.Component<{}, State> {
  51. pollTimeout: TimeoutID
  52. stopPolling: boolean
  53. constructor() {
  54. super()
  55. this.state = {
  56. showDeleteEndpointsConfirmation: false,
  57. confirmationItems: null,
  58. showChooseProviderModal: false,
  59. showEndpointModal: false,
  60. providerType: null,
  61. showEndpointsInUseModal: false,
  62. modalIsOpen: false,
  63. }
  64. }
  65. componentDidMount() {
  66. document.title = 'Coriolis Endpoints'
  67. projectStore.getProjects()
  68. this.stopPolling = false
  69. this.pollData()
  70. }
  71. componentWillUnmount() {
  72. clearTimeout(this.pollTimeout)
  73. this.stopPolling = true
  74. }
  75. getFilterItems() {
  76. let types = [{ label: 'All', value: 'all' }]
  77. endpointStore.endpoints.forEach(endpoint => {
  78. if (!types.find(t => t.value === endpoint.type)) {
  79. types.push({ label: LabelDictionary.get(endpoint.type), value: endpoint.type })
  80. }
  81. })
  82. return types
  83. }
  84. getEndpointUsage(endpoint: EndpointType) {
  85. let replicasCount = replicaStore.replicas.filter(
  86. r => r.origin_endpoint_id === endpoint.id || r.destination_endpoint_id === endpoint.id).length
  87. let migrationsCount = migrationStore.migrations.filter(
  88. r => r.origin_endpoint_id === endpoint.id || r.destination_endpoint_id === endpoint.id).length
  89. return { migrationsCount, replicasCount }
  90. }
  91. handleProjectChange() {
  92. endpointStore.getEndpoints({ showLoading: true })
  93. migrationStore.getMigrations()
  94. replicaStore.getReplicas()
  95. }
  96. handleReloadButtonClick() {
  97. projectStore.getProjects()
  98. endpointStore.getEndpoints({ showLoading: true })
  99. migrationStore.getMigrations()
  100. replicaStore.getReplicas()
  101. }
  102. handleItemClick(item: EndpointType) {
  103. window.location.href = `/#/endpoint/${item.id}`
  104. }
  105. handleActionChange(items: EndpointType[], action: string) {
  106. if (action === 'delete') {
  107. let endpointsInUse = items.filter(endpoint => {
  108. const endpointUsage = this.getEndpointUsage(endpoint)
  109. return endpointUsage.migrationsCount > 0 || endpointUsage.replicasCount > 0
  110. })
  111. if (endpointsInUse.length > 0) {
  112. this.setState({ showEndpointsInUseModal: true })
  113. } else {
  114. this.setState({
  115. showDeleteEndpointsConfirmation: true,
  116. confirmationItems: items,
  117. })
  118. }
  119. }
  120. }
  121. handleCloseDeleteEndpointsConfirmation() {
  122. this.setState({
  123. showDeleteEndpointsConfirmation: false,
  124. confirmationItems: null,
  125. })
  126. }
  127. handleDeleteEndpointsConfirmation() {
  128. if (this.state.confirmationItems) {
  129. this.state.confirmationItems.forEach(endpoint => {
  130. endpointStore.delete(endpoint)
  131. })
  132. }
  133. this.handleCloseDeleteEndpointsConfirmation()
  134. }
  135. handleEmptyListButtonClick() {
  136. providerStore.loadProviders()
  137. this.setState({ showChooseProviderModal: true })
  138. }
  139. handleCloseChooseProviderModal() {
  140. this.setState({ showChooseProviderModal: false })
  141. }
  142. handleProviderClick(providerType: string) {
  143. this.setState({
  144. showChooseProviderModal: false,
  145. showEndpointModal: true,
  146. providerType,
  147. })
  148. }
  149. handleCloseEndpointModal() {
  150. this.setState({ showEndpointModal: false })
  151. }
  152. handleModalOpen() {
  153. this.setState({ modalIsOpen: true })
  154. }
  155. handleModalClose() {
  156. this.setState({ modalIsOpen: false }, () => {
  157. this.pollData()
  158. })
  159. }
  160. pollData() {
  161. if (this.state.modalIsOpen || this.stopPolling) {
  162. return
  163. }
  164. Promise.all([endpointStore.getEndpoints(), migrationStore.getMigrations(), replicaStore.getReplicas()]).then(() => {
  165. this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
  166. })
  167. }
  168. itemFilterFunction(item: any, filterItem?: ?string, filterText?: string) {
  169. let endpoint: EndpointType = item
  170. if ((filterItem !== 'all' && (endpoint.type !== filterItem)) ||
  171. (endpoint.name.toLowerCase().indexOf(filterText || '') === -1 &&
  172. // $FlowIssue
  173. endpoint.description.toLowerCase().indexOf(filterText) === -1)
  174. ) {
  175. return false
  176. }
  177. return true
  178. }
  179. render() {
  180. let items: any = endpointStore.endpoints
  181. return (
  182. <Wrapper>
  183. <MainTemplate
  184. navigationComponent={<Navigation currentPage="endpoints" />}
  185. listComponent={
  186. <FilterList
  187. filterItems={this.getFilterItems()}
  188. selectionLabel="endpoint"
  189. loading={endpointStore.loading}
  190. items={items}
  191. onItemClick={item => {
  192. let anyItem: any = item
  193. let endpoint: EndpointType = anyItem
  194. this.handleItemClick(endpoint)
  195. }}
  196. onReloadButtonClick={() => { this.handleReloadButtonClick() }}
  197. actions={BulkActions}
  198. onActionChange={(items, action) => {
  199. let anyItems: any = items
  200. let endpoints: EndpointType[] = anyItems
  201. this.handleActionChange(endpoints, action)
  202. }}
  203. itemFilterFunction={(...args) => this.itemFilterFunction(...args)}
  204. renderItemComponent={options =>
  205. // $FlowIssue
  206. (<EndpointListItem
  207. {...options}
  208. getUsage={endpoint => this.getEndpointUsage(endpoint)}
  209. />)
  210. }
  211. emptyListImage={endpointImage}
  212. emptyListMessage="You don’t have any Cloud Endpoints in this project."
  213. emptyListExtraMessage="A Cloud Endpoint is used for the source or target of a Replica/Migration."
  214. emptyListButtonLabel="Add Endpoint"
  215. onEmptyListButtonClick={() => { this.handleEmptyListButtonClick() }}
  216. />
  217. }
  218. headerComponent={
  219. <PageHeader
  220. title="Coriolis Endpoints"
  221. onProjectChange={() => { this.handleProjectChange() }}
  222. onModalOpen={() => { this.handleModalOpen() }}
  223. onModalClose={() => { this.handleModalClose() }}
  224. />
  225. }
  226. />
  227. <AlertModal
  228. isOpen={this.state.showDeleteEndpointsConfirmation}
  229. title="Delete Endpoints?"
  230. message="Are you sure you want to delete the selected endpoints?"
  231. extraMessage="Deleting a Coriolis Cloud Endpoint is permanent!"
  232. onConfirmation={() => { this.handleDeleteEndpointsConfirmation() }}
  233. onRequestClose={() => { this.handleCloseDeleteEndpointsConfirmation() }}
  234. />
  235. <Modal
  236. isOpen={this.state.showChooseProviderModal}
  237. title="New Cloud Endpoint"
  238. onRequestClose={() => { this.handleCloseChooseProviderModal() }}
  239. >
  240. <ChooseProvider
  241. onCancelClick={() => { this.handleCloseChooseProviderModal() }}
  242. providers={providerStore.providers}
  243. loading={providerStore.providersLoading}
  244. onProviderClick={providerName => { this.handleProviderClick(providerName) }}
  245. />
  246. </Modal>
  247. <Modal
  248. isOpen={this.state.showEndpointModal}
  249. title="New Cloud Endpoint"
  250. onRequestClose={() => { this.handleCloseEndpointModal() }}
  251. >
  252. <Endpoint
  253. deleteOnCancel
  254. type={this.state.providerType}
  255. onCancelClick={() => { this.handleCloseEndpointModal() }}
  256. />
  257. </Modal>
  258. <AlertModal
  259. type="error"
  260. isOpen={this.state.showEndpointsInUseModal}
  261. title="Endpoints are in use"
  262. message="Some of the selected endpoints can't be deleted because they are in use by replicas or migrations."
  263. extraMessage="You must first delete the replicas or migrations which use these endpoints."
  264. onRequestClose={() => { this.setState({ showEndpointsInUseModal: false }) }}
  265. />
  266. </Wrapper>
  267. )
  268. }
  269. }
  270. export default EndpointsPage