WizardVms.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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 React, { Component, PropTypes } from 'react';
  15. import withStyles from 'isomorphic-style-loader/lib/withStyles';
  16. import SearchBox from '../SearchBox';
  17. import s from './WizardVms.scss';
  18. import { itemsPerPage } from '../../config';
  19. import ConnectionsActions from '../../actions/ConnectionsActions';
  20. import LoadingIcon from '../LoadingIcon';
  21. const title = 'Select instances to migrate';
  22. const vmStatesConst = ["All", "RUNNING", "PAUSED", "STOPPED"]
  23. const searchTimeout = 1000;
  24. class WizardVms extends Component {
  25. static propTypes = {
  26. data: PropTypes.object,
  27. setWizardState: PropTypes.func
  28. }
  29. static contextTypes = {
  30. onSetTitle: PropTypes.func.isRequired,
  31. };
  32. constructor(props) {
  33. super(props)
  34. let valid = false
  35. if (this.props.data.instances) {
  36. this.props.data.instances.forEach((vm) => {
  37. if (vm.selected) {
  38. valid = true
  39. }
  40. })
  41. }
  42. this.retryLoadingInstances = this.retryLoadingInstances.bind(this)
  43. this.state = {
  44. valid,
  45. queryText: '',
  46. page: 0,
  47. filterStatus: 'All',
  48. filteredData: this.props.data.instances ? this.props.data.instances.slice(0, itemsPerPage) : [],
  49. nextStep: "WizardOptions"
  50. }
  51. }
  52. componentWillMount() {
  53. this.context.onSetTitle(title);
  54. this.props.setWizardState(this.state)
  55. }
  56. componentWillReceiveProps(newProps) {
  57. this.processProps(newProps)
  58. if (newProps.data.selectedInstances) {
  59. let valid = newProps.data.selectedInstances.length > 0
  60. if (this.props.data.valid != valid) {
  61. this.props.setWizardState({ valid: valid })
  62. }
  63. }
  64. }
  65. processProps(props) {
  66. if (props.data.instances) {
  67. this.setState({ filteredData: props.data.instances.slice(
  68. this.state.page * itemsPerPage, this.state.page * itemsPerPage + itemsPerPage) })
  69. }
  70. }
  71. checkVm(e, item) {
  72. let instances = this.props.data.instances
  73. instances.forEach((vm) => {
  74. if (vm == item) {
  75. vm.selected = !vm.selected
  76. let selectedInstances = this.props.data.selectedInstances
  77. let index = -1
  78. selectedInstances.forEach((instance, key) => {
  79. if (instance.name == item.name) {
  80. index = key
  81. }
  82. })
  83. if (index == -1) {
  84. selectedInstances.push(item)
  85. } else {
  86. selectedInstances.splice(index, 1)
  87. }
  88. this.props.setWizardState({ selectedInstances: selectedInstances })
  89. }
  90. })
  91. this.props.setWizardState({ instances: instances })
  92. }
  93. searchVm(queryText) {
  94. let queryResult = []
  95. if (this.props.data.instances) {
  96. this.props.data.instances.forEach((vm) => {
  97. if (
  98. (this.state.filterStatus === "All" || this.state.filterStatus === vm.status)
  99. ) {
  100. queryResult.push(vm)
  101. }
  102. }, this)
  103. }
  104. if (this.state.queryText != queryText) {
  105. if (this.timeout != null) {
  106. clearTimeout(this.timeout)
  107. }
  108. this.timeout = setTimeout(() => {
  109. this.setState({ page: 0, filteredData: null, queryText: queryText }, () => {
  110. this.props.setWizardState({ instances: null })
  111. ConnectionsActions.loadInstances(
  112. { id: this.props.data.sourceCloud.credential.id },
  113. this.state.page,
  114. queryText,
  115. false
  116. )
  117. })
  118. }, searchTimeout)
  119. } else {
  120. this.setState({
  121. filteredData: queryResult
  122. })
  123. }
  124. }
  125. instancesSelected() {
  126. let count = this.props.data.selectedInstances.length;
  127. return count;
  128. }
  129. filterStatus(e, status) {
  130. this.setState({ filterStatus: status }, () => {
  131. this.searchVm({ target: { value: this.state.queryText } })
  132. })
  133. }
  134. toTitleCase(str) {
  135. return str.replace(/\w\S*/g, (txt) => { // eslint-disable-line arrow-body-style
  136. return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  137. });
  138. }
  139. isSelected(item) {
  140. let selected = false;
  141. this.props.data.selectedInstances.forEach(instance => {
  142. if (instance.name == item.name) {
  143. selected = true
  144. }
  145. })
  146. return selected
  147. }
  148. nextPage() {
  149. if (this.state.filteredData && this.state.filteredData.length == itemsPerPage) {
  150. this.setState({ page: this.state.page + 1 }, () => {
  151. ConnectionsActions.loadInstances(
  152. { id: this.props.data.sourceCloud.credential.id },
  153. this.state.page,
  154. this.state.queryText
  155. )
  156. this.processProps({ data: { instances: this.props.data.instances } })
  157. })
  158. }
  159. }
  160. previousPage() {
  161. if (this.state.page > 0) {
  162. this.setState({ page: this.state.page + -1 }, () => {
  163. ConnectionsActions.loadInstances(
  164. { id: this.props.data.sourceCloud.credential.id },
  165. this.state.page,
  166. this.state.queryText
  167. )
  168. this.processProps({ data: { instances: this.props.data.instances } })
  169. })
  170. }
  171. }
  172. retryLoadingInstances() {
  173. ConnectionsActions.loadInstances(
  174. { id: this.props.data.sourceCloud.credential.id },
  175. this.state.page,
  176. this.state.queryText,
  177. false
  178. )
  179. }
  180. renderSearch() {
  181. let _this = this
  182. switch (this.props.data.instancesLoadState) {
  183. case "success":
  184. if (this.state.filteredData && this.state.filteredData.length) {
  185. let instances = this.state.filteredData.map((item, index) =>
  186. <div className="item" key={ "vm_" + index } onClick={ (e) => _this.checkVm(e, item) }>
  187. <div className="checkbox-container">
  188. <input
  189. id={"vm_check_" + index}
  190. type="checkbox"
  191. checked={this.isSelected(item)}
  192. onChange={(e) => _this.checkVm(e, item)}
  193. className="checkbox-normal"
  194. />
  195. <label htmlFor={ "vm_check_" + index }></label>
  196. </div>
  197. <span className="cell cell-icon">
  198. <div className="icon vm"></div>
  199. <span className="details">
  200. {item.instance_name}
  201. </span>
  202. </span>
  203. <span className="cell">{item.num_cpu} vCPU | {item.memory_mb} MB RAM
  204. {item.flavor_name && (" | " + item.flavor_name)}</span>
  205. </div>
  206. )
  207. return instances
  208. } else {
  209. return <div className="no-results">Your search returned no results</div>
  210. }
  211. case "error":
  212. return (<div className="no-results">
  213. An error occurred while searching for instances <br />
  214. <button onClick={this.retryLoadingInstances}>Retry</button>
  215. </div>)
  216. case "loading":
  217. return <LoadingIcon padding={64} text="Loading instances.." />
  218. default:
  219. return <LoadingIcon padding={64} text="Loading instances.." />
  220. }
  221. }
  222. render() {
  223. let _this = this
  224. let vmStates = vmStatesConst.map(
  225. (state, index) =>
  226. <a
  227. className={_this.state.filterStatus == state || (_this.state.filterStatus == null && state == "All") ?
  228. "selected" : ""}
  229. onClick={(e) => _this.filterStatus(e, state)} key={"status_" + index}
  230. >{_this.toTitleCase(state)}</a>
  231. )
  232. return (
  233. <div className={s.root}>
  234. <div className={s.container}>
  235. <div className={s.topFilters}>
  236. <SearchBox
  237. placeholder="Search VMs"
  238. value={this.state.queryText}
  239. onChange={(e) => this.searchVm(e)}
  240. className={"searchBox" + (!(this.state.filteredData && this.state.filteredData.length) ? " hidden" : " ")}
  241. />
  242. <div className="category-filter hidden">
  243. {vmStates}
  244. </div>
  245. </div>
  246. <div className="items-list instances">
  247. {this.renderSearch()}
  248. </div>
  249. <div className={s.selectionCount +
  250. (!(this.state.filteredData && this.state.filteredData.length) ? " hidden" : " ")}
  251. >
  252. {this.instancesSelected()} instances selected
  253. </div>
  254. <div className={s.pagination +
  255. (!(this.state.filteredData && this.state.filteredData.length) ? " hidden" : " ")}
  256. >
  257. <span
  258. className={(this.state.page == 0 ? "disabled " : "") + s.prev}
  259. onClick={(e) => this.previousPage(e)}
  260. ></span>
  261. <span className={s.currentPage}>{this.state.page + 1}</span>
  262. <span
  263. className={(this.state.filteredData && this.state.filteredData.length == itemsPerPage ?
  264. " " : "disabled ") + s.next}
  265. onClick={(e) => this.nextPage(e)}
  266. ></span>
  267. </div>
  268. </div>
  269. </div>
  270. );
  271. }
  272. }
  273. export default withStyles(WizardVms, s);