ReplicaMigrationOptions.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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 from 'react'
  15. import { observer } from 'mobx-react'
  16. import styled from 'styled-components'
  17. import Button from '../../atoms/Button'
  18. import FieldInput from '../../molecules/FieldInput'
  19. import ToggleButtonBar from '../../atoms/ToggleButtonBar'
  20. import WizardScripts from '../WizardScripts'
  21. import LabelDictionary from '../../../utils/LabelDictionary'
  22. import KeyboardManager from '../../../utils/KeyboardManager'
  23. import StyleProps from '../../styleUtils/StyleProps'
  24. import replicaMigrationImage from './images/replica-migration.svg'
  25. import replicaMigrationFields from './replicaMigrationFields'
  26. import type { Field } from '../../../@types/Field'
  27. import type { Instance, InstanceScript } from '../../../@types/Instance'
  28. const Wrapper = styled.div<any>`
  29. display: flex;
  30. flex-direction: column;
  31. align-items: center;
  32. padding: 0 32px 32px 32px;
  33. min-height: 0;
  34. `
  35. const Image = styled.div<any>`
  36. ${StyleProps.exactWidth('288px')}
  37. ${StyleProps.exactHeight('96px')}
  38. background: url('${replicaMigrationImage}') center no-repeat;
  39. margin: 80px 0;
  40. `
  41. const OptionsBody = styled.div<any>`
  42. display: flex;
  43. flex-direction: column;
  44. `
  45. const ScriptsBody = styled.div<any>`
  46. display: flex;
  47. flex-direction: column;
  48. width: 100%;
  49. overflow: auto;
  50. min-height: 0;
  51. margin-bottom: 32px;
  52. `
  53. const Form = styled.div<any>`
  54. display: flex;
  55. flex-wrap: wrap;
  56. margin-left: -64px;
  57. justify-content: space-between;
  58. margin: 0 auto 46px auto;
  59. `
  60. const Buttons = styled.div<any>`
  61. display: flex;
  62. justify-content: space-between;
  63. width: 100%;
  64. `
  65. const FieldInputStyled = styled(FieldInput)`
  66. width: 224px;
  67. justify-content: space-between;
  68. margin-bottom: 32px;
  69. `
  70. type Props = {
  71. instances: Instance[],
  72. loadingInstances: boolean,
  73. defaultSkipOsMorphing?: boolean | null,
  74. onCancelClick: () => void,
  75. onMigrateClick: (fields: Field[], uploadedScripts: InstanceScript[]) => void,
  76. onResizeUpdate?: (scrollableRef: HTMLElement, scrollOffset?: number) => void,
  77. }
  78. type State = {
  79. fields: Field[],
  80. selectedBarButton: string,
  81. uploadedScripts: InstanceScript[],
  82. }
  83. @observer
  84. class ReplicaMigrationOptions extends React.Component<Props, State> {
  85. state: State = {
  86. fields: [],
  87. selectedBarButton: 'options',
  88. uploadedScripts: [],
  89. }
  90. scrollableRef!: HTMLElement
  91. UNSAFE_componentWillMount() {
  92. this.setState({
  93. fields: replicaMigrationFields.map(f => (f.name === 'skip_os_morphing' ? (
  94. { ...f, value: this.props.defaultSkipOsMorphing || null }
  95. ) : f)),
  96. })
  97. }
  98. componentDidMount() {
  99. KeyboardManager.onEnter('migration-options', () => { this.migrate() }, 2)
  100. }
  101. componentDidUpdate(_: Props, prevState: State) {
  102. if (prevState.selectedBarButton !== this.state.selectedBarButton) {
  103. if (this.props.onResizeUpdate) {
  104. this.props.onResizeUpdate(this.scrollableRef, 0)
  105. }
  106. }
  107. }
  108. componentWillUnmount() {
  109. KeyboardManager.removeKeyDown('migration-options')
  110. }
  111. migrate() {
  112. this.props.onMigrateClick(this.state.fields, this.state.uploadedScripts)
  113. }
  114. handleValueChange(field: Field, value: boolean) {
  115. this.setState(prevState => {
  116. const fields = prevState.fields.map(f => {
  117. const newField = { ...f }
  118. if (f.name === field.name) {
  119. newField.value = value
  120. }
  121. return newField
  122. })
  123. return { fields }
  124. })
  125. }
  126. handleCanceScript(global: string | null, instanceName: string | null) {
  127. this.setState(prevState => ({
  128. uploadedScripts: prevState.uploadedScripts
  129. .filter(s => (global ? s.global !== global : s.instanceName !== instanceName)),
  130. }))
  131. }
  132. handleScriptUpload(script: InstanceScript) {
  133. this.setState(prevState => ({
  134. uploadedScripts: [
  135. ...prevState.uploadedScripts,
  136. script,
  137. ],
  138. }))
  139. }
  140. renderOptions() {
  141. return (
  142. <Form>
  143. {this.state.fields.map(field => (
  144. <FieldInputStyled
  145. width={224}
  146. key={field.name}
  147. name={field.name}
  148. type={field.type}
  149. value={field.value || field.default}
  150. minimum={field.minimum}
  151. maximum={field.maximum}
  152. layout="page"
  153. label={LabelDictionary.get(field.name)}
  154. onChange={value => this.handleValueChange(field, value)}
  155. description={LabelDictionary.getDescription(field.name)}
  156. />
  157. ))}
  158. </Form>
  159. )
  160. }
  161. renderScripts() {
  162. return (
  163. <WizardScripts
  164. instances={this.props.instances}
  165. loadingInstances={this.props.loadingInstances}
  166. onScriptUpload={s => { this.handleScriptUpload(s) }}
  167. onCancelScript={(g, i) => { this.handleCanceScript(g, i) }}
  168. uploadedScripts={this.state.uploadedScripts}
  169. scrollableRef={(r: HTMLElement) => { this.scrollableRef = r }}
  170. layout="modal"
  171. />
  172. )
  173. }
  174. renderBody() {
  175. const Body = this.state.selectedBarButton === 'options' ? OptionsBody : ScriptsBody
  176. return (
  177. <Body>
  178. <ToggleButtonBar
  179. items={[{ label: 'Options', value: 'options' }, { label: 'User Scripts', value: 'script' }]}
  180. selectedValue={this.state.selectedBarButton}
  181. onChange={item => { this.setState({ selectedBarButton: item.value }) }}
  182. style={{ marginBottom: '32px' }}
  183. />
  184. {this.state.selectedBarButton === 'options' ? this.renderOptions() : this.renderScripts()}
  185. </Body>
  186. )
  187. }
  188. render() {
  189. return (
  190. <Wrapper>
  191. <Image />
  192. {this.renderBody()}
  193. <Buttons>
  194. <Button secondary onClick={this.props.onCancelClick} data-test-id="rmOptions-cancelButton">Cancel</Button>
  195. <Button onClick={() => { this.migrate() }} data-test-id="rmOptions-execButton">Migrate</Button>
  196. </Buttons>
  197. </Wrapper>
  198. )
  199. }
  200. }
  201. export default ReplicaMigrationOptions