| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874 |
- /*
- Copyright (C) 2019 Cloudbase Solutions SRL
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- import React from 'react'
- import { observer } from 'mobx-react'
- import styled from 'styled-components'
- import providerStore, { getFieldChangeOptions } from '@src/stores/ProviderStore'
- import replicaStore from '@src/stores/ReplicaStore'
- import migrationStore from '@src/stores/MigrationStore'
- import endpointStore from '@src/stores/EndpointStore'
- import { OptionsSchemaPlugin } from '@src/plugins'
- import Button from '@src/components/ui/Button'
- import StatusImage from '@src/components/ui/StatusComponents/StatusImage'
- import Modal from '@src/components/ui/Modal'
- import Panel from '@src/components/ui/Panel'
- import WizardNetworks, { WizardNetworksChangeObject } from '@src/components/modules/WizardModule/WizardNetworks'
- import WizardOptions, { findInvalidFields, INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from '@src/components/modules/WizardModule/WizardOptions'
- import WizardStorage from '@src/components/modules/WizardModule/WizardStorage'
- import type {
- UpdateData, TransferItemDetails, MigrationItemDetails,
- } from '@src/@types/MainItem'
- import {
- Endpoint, EndpointUtils, StorageBackend, StorageMap,
- } from '@src/@types/Endpoint'
- import type { Field } from '@src/@types/Field'
- import type {
- Instance, InstanceScript,
- } from '@src/@types/Instance'
- import {
- Network, NetworkMap, NetworkUtils, SecurityGroup,
- } from '@src/@types/Network'
- import { providerTypes, migrationFields } from '@src/constants'
- import configLoader from '@src/utils/Config'
- import LoadingButton from '@src/components/ui/LoadingButton'
- import minionPoolStore from '@src/stores/MinionPoolStore'
- import WizardScripts from '@src/components/modules/WizardModule/WizardScripts'
- import networkStore from '@src/stores/NetworkStore'
- import { ThemeProps } from '@src/components/Theme'
- const PanelContent = styled.div<any>`
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- flex-grow: 1;
- min-height: 0;
- `
- const LoadingWrapper = styled.div<any>`
- display: flex;
- flex-direction: column;
- align-items: center;
- margin: 32px 0;
- `
- const LoadingText = styled.div<any>`
- font-size: 18px;
- margin-top: 32px;
- `
- const ErrorWrapper = styled.div<any>`
- display: flex;
- flex-direction: column;
- height: 100%;
- align-items: center;
- justify-content: center;
- padding: 32px;
- `
- const ErrorMessage = styled.div<any>`
- margin-top: 16px;
- text-align: center;
- `
- const Buttons = styled.div<any>`
- padding: 32px;
- display: flex;
- flex-shrink: 0;
- justify-content: space-between;
- `
- type Props = {
- type?: 'replica' | 'migration',
- isOpen: boolean,
- onRequestClose: () => void,
- onUpdateComplete: (redirectTo: string) => void,
- replica: TransferItemDetails,
- destinationEndpoint: Endpoint,
- sourceEndpoint: Endpoint,
- instancesDetails: Instance[],
- instancesDetailsLoading: boolean,
- networks: Network[],
- networksLoading: boolean,
- onReloadClick: () => void,
- }
- type State = {
- selectedPanel: string | null,
- destinationData: any,
- sourceData: any,
- updateDisabled: boolean,
- updating: boolean,
- selectedNetworks: NetworkMap[],
- defaultStorage: { value: string | null, busType?: string | null } | undefined,
- storageMap: StorageMap[],
- sourceFailed: boolean,
- destinationFailedMessage: string | null,
- uploadedScripts: InstanceScript[],
- removedScripts: InstanceScript[],
- }
- @observer
- class TransferItemModal extends React.Component<Props, State> {
- state: State = {
- selectedPanel: 'source_options',
- destinationData: {},
- sourceData: {},
- updateDisabled: false,
- updating: false,
- selectedNetworks: [],
- defaultStorage: undefined,
- storageMap: [],
- uploadedScripts: [],
- sourceFailed: false,
- destinationFailedMessage: null,
- removedScripts: [],
- }
- scrollableRef: HTMLElement | null | undefined
- UNSAFE_componentWillMount() {
- this.loadData(true)
- }
- getStorageMap(storageBackends: StorageBackend[]): StorageMap[] {
- const storageMap: StorageMap[] = []
- const currentStorage = this.props.replica.storage_mappings
- const buildStorageMap = (type: 'backend' | 'disk', mapping: any): StorageMap => {
- const busTypeInfo = EndpointUtils.getBusTypeStorageId(storageBackends, mapping.destination)
- const backend = storageBackends.find(b => b.name === busTypeInfo.id)
- const newStorageMap: StorageMap = {
- type,
- source: { storage_backend_identifier: mapping.source, id: mapping.disk_id },
- target: { name: busTypeInfo.id!, id: backend ? backend.id : busTypeInfo.id },
- }
- if (busTypeInfo.busType) {
- newStorageMap.targetBusType = busTypeInfo.busType
- }
- return newStorageMap
- }
- const backendMappings = currentStorage?.backend_mappings || []
- backendMappings.forEach(mapping => {
- storageMap.push(buildStorageMap('backend', mapping))
- })
- const diskMappings = currentStorage?.disk_mappings || []
- diskMappings.forEach(mapping => {
- storageMap.push(buildStorageMap('disk', mapping))
- })
- this.state.storageMap.forEach(mapping => {
- const fieldName = mapping.type === 'backend' ? 'storage_backend_identifier' : 'id'
- const existingMapping = storageMap.find(m => m.type === mapping.type
- && m.source[fieldName] === String(mapping.source[fieldName]))
- if (existingMapping) {
- existingMapping.target = mapping.target
- if (mapping.targetBusType !== undefined) {
- existingMapping.targetBusType = mapping.targetBusType
- }
- } else {
- storageMap.push(mapping)
- }
- })
- return storageMap
- }
- getSelectedNetworks(): NetworkMap[] {
- const selectedNetworks: NetworkMap[] = []
- const networkMap: any = this.props.replica.network_map
- if (networkMap) {
- Object.keys(networkMap).forEach(sourceNetworkName => {
- // if the network mapping was updated, just use the new mapping instead of the old one
- const updatedMapping = this.state.selectedNetworks.find(m => m.sourceNic.network_name === sourceNetworkName)
- if (updatedMapping) {
- selectedNetworks.push(updatedMapping)
- return
- }
- // add extra information to the current network mapping
- const destNetObj: any = networkMap[sourceNetworkName]
- const portKeyInfo = NetworkUtils.getPortKeyNetworkId(this.props.networks, destNetObj)
- const destNetId = String(typeof destNetObj === 'string' || !destNetObj
- || !destNetObj.id ? portKeyInfo.id : destNetObj.id)
- const network = this.props.networks.find(n => n.name === destNetId || n.id === destNetId) || null
- const mapping: NetworkMap = {
- sourceNic: {
- id: '', network_name: sourceNetworkName, mac_address: '', network_id: '',
- },
- targetNetwork: network,
- }
- if (destNetObj.security_groups) {
- const destSecGroupsInfo = network?.security_groups || []
- const secInfo = destNetObj.security_groups.map((s: SecurityGroup) => {
- const foundSecGroupInfo = destSecGroupsInfo
- .find((si: any) => (si.id ? si.id === s : si === s))
- return foundSecGroupInfo || { id: s, name: s }
- })
- mapping.targetSecurityGroups = secInfo
- }
- if (portKeyInfo.portKey) {
- mapping.targetPortKey = portKeyInfo.portKey
- }
- selectedNetworks.push(mapping)
- })
- }
- // add any new networks mappings that were not in the original network mappings
- this.state.selectedNetworks.forEach(mapping => {
- if (!selectedNetworks.find(m => m.sourceNic.network_name === mapping.sourceNic.network_name)) {
- selectedNetworks.push(mapping)
- }
- })
- return selectedNetworks
- }
- getDefaultStorage(): { value: string | null, busType?: string | null } {
- if (this.state.defaultStorage) {
- return this.state.defaultStorage
- }
- const buildDefaultStorage = (defaultValue: string | null | undefined) => {
- const busTypeInfo = EndpointUtils.getBusTypeStorageId(endpointStore.storageBackends, defaultValue || null)
- const defaultStorage: { value: string | null, busType?: string | null } = {
- value: busTypeInfo.id,
- }
- if (busTypeInfo.busType) {
- defaultStorage.busType = busTypeInfo.busType
- }
- return defaultStorage
- }
- if (this.props.replica.storage_mappings?.default) {
- return buildDefaultStorage(this.props.replica.storage_mappings.default)
- }
- if (endpointStore.storageConfigDefault) {
- return buildDefaultStorage(endpointStore.storageConfigDefault)
- }
- return { value: null }
- }
- getFieldValue(opts: { type: 'source' | 'destination', fieldName: string, defaultValue: any, parentFieldName?: string }) {
- const {
- type, fieldName, defaultValue, parentFieldName,
- } = opts
- const currentData = type === 'source' ? this.state.sourceData : this.state.destinationData
- const replicaMinionMappings = this.props.replica[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]
- if (parentFieldName) {
- if (currentData[parentFieldName]
- && currentData[parentFieldName][fieldName] !== undefined) {
- return currentData[parentFieldName][fieldName]
- }
- if (parentFieldName === INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS
- && replicaMinionMappings
- && replicaMinionMappings[fieldName] !== undefined
- ) {
- return replicaMinionMappings[fieldName]
- }
- }
- if (currentData[fieldName] !== undefined) {
- return currentData[fieldName]
- }
- if (fieldName === 'title') {
- let title = this.props.instancesDetails?.[0]?.name
- if (this.props.instancesDetails && this.props.instancesDetails.length > 1) {
- title += ` (+${this.props.instancesDetails.length - 1} more)`
- }
- return title
- }
- if (fieldName === 'minion_pool_id') {
- return type === 'source' ? this.props.replica.origin_minion_pool_id : this.props.replica.destination_minion_pool_id
- }
- const replicaData: any = type === 'source' ? this.props.replica.source_environment
- : this.props.replica.destination_environment
- if (parentFieldName) {
- if (replicaData[parentFieldName]?.[fieldName] !== undefined) {
- return replicaData[parentFieldName][fieldName]
- }
- }
- if (replicaData[fieldName] !== undefined) {
- return replicaData[fieldName]
- }
- const endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
- const plugin = OptionsSchemaPlugin.for(endpoint.type)
- const osMapping = /^(windows|linux)/.exec(fieldName)
- if (osMapping) {
- const osData = replicaData[`${plugin.migrationImageMapFieldName}/${osMapping[0]}`]
- return osData
- }
- const anyData = this.props.replica as any
- if (migrationFields.find(f => f.name === fieldName) && anyData[fieldName]) {
- return anyData[fieldName]
- }
- if (fieldName === 'skip_os_morphing' && this.props.type === 'migration') {
- return migrationStore.getDefaultSkipOsMorphing(anyData)
- }
- return defaultValue
- }
- async loadData(useCache: boolean) {
- minionPoolStore.loadMinionPools()
- await providerStore.loadProviders()
- const loadAllOptions = async (type: 'source' | 'destination') => {
- const endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
- try {
- await this.loadOptions(endpoint, type, useCache)
- this.loadExtraOptions({ type, useCache })
- } catch (err) {
- if (type === 'source') {
- this.setState(prevState => {
- let selectedPanel = prevState.selectedPanel
- if (selectedPanel === 'source_options') {
- selectedPanel = 'dest_options'
- }
- return { sourceFailed: true, selectedPanel }
- })
- }
- }
- }
- loadAllOptions('source')
- loadAllOptions('destination')
- }
- async loadOptions(endpoint: Endpoint, optionsType: 'source' | 'destination', useCache: boolean) {
- try {
- await providerStore.loadOptionsSchema({
- providerName: endpoint.type,
- optionsType,
- useCache,
- })
- } catch (err) {
- if (optionsType === 'destination' || this.props.type === 'migration') {
- const destinationFailedMessage = this.props.type === 'replica'
- ? 'An error has occurred during the loading of the Replica\'s options for editing. There could be connection issues with the destination platform. Please retry the operation.'
- : 'An error has occurred during loading of the source or destination platforms\' environment options for editing of the Migration\'s parameters. You may still recreate the Migration with the same parameters as the original one by clicking "Create".'
- this.setState({ destinationFailedMessage })
- }
- throw err
- }
- await providerStore.getOptionsValues({
- optionsType,
- endpointId: endpoint.id,
- providerName: endpoint.type,
- useCache,
- })
- }
- loadExtraOptions(opts: {
- field?: Field,
- type: 'source' | 'destination',
- useCache?: boolean,
- parentFieldName?: string
- }) {
- const {
- field, type, useCache, parentFieldName,
- } = opts
- const endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
- const env = type === 'source' ? this.props.replica.source_environment : this.props.replica.destination_environment
- const stateEnv = type === 'source' ? this.state.sourceData : this.state.destinationData
- const envData = getFieldChangeOptions({
- providerName: endpoint.type,
- schema: type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema,
- data: {
- ...env,
- ...stateEnv,
- },
- field: field || null,
- type,
- parentFieldName,
- })
- if (!envData) {
- return
- }
- providerStore.getOptionsValues({
- optionsType: type,
- endpointId: endpoint.id,
- providerName: endpoint.type,
- useCache,
- envData,
- })
- if (type === 'destination') {
- networkStore.loadNetworks(endpoint.id, envData, { cache: true })
- if (this.hasStorageMap()) {
- endpointStore.loadStorage(this.props.destinationEndpoint.id, envData, { cache: true })
- }
- }
- }
- hasStorageMap(): boolean {
- return providerStore.providers?.[this.props.destinationEndpoint.type]
- ? !!providerStore.providers[this.props.destinationEndpoint.type].types.find(t => t === providerTypes.STORAGE)
- : false
- }
- isUpdateDisabled() {
- const isDestFailed = this.props.type === 'replica' && this.state.destinationFailedMessage
- return this.state.updateDisabled || isDestFailed
- }
- isLoadingDestOptions() {
- return providerStore.destinationSchemaLoading
- || providerStore.destinationOptionsPrimaryLoading
- }
- isLoadingSourceOptions() {
- return providerStore.sourceSchemaLoading
- || providerStore.sourceOptionsPrimaryLoading
- }
- isLoadingNetwork() {
- return this.props.instancesDetailsLoading
- }
- isLoadingStorage() {
- return this.props.instancesDetailsLoading || endpointStore.storageLoading
- }
- isLoading() {
- return this.isLoadingSourceOptions()
- || this.isLoadingDestOptions() || this.isLoadingNetwork()
- || this.isLoadingStorage()
- }
- validateOptions(type: 'source' | 'destination') {
- const env = type === 'source' ? this.props.replica.source_environment : this.props.replica.destination_environment
- const data = type === 'source' ? this.state.sourceData : this.state.destinationData
- const schema = type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema
- const invalidFields = findInvalidFields({
- ...env,
- ...data,
- }, schema)
- this.setState({ updateDisabled: invalidFields.length > 0 })
- }
- handlePanelChange(panel: string) {
- this.setState({ selectedPanel: panel })
- }
- handleReload() {
- this.props.onReloadClick()
- this.loadData(false)
- }
- handleFieldChange(opts: { type: 'source' | 'destination', field: Field, value: any, parentFieldName?: string }) {
- const {
- type, field, value, parentFieldName,
- } = opts
- const data = type === 'source' ? { ...this.state.sourceData } : { ...this.state.destinationData }
- if (field.type === 'array') {
- const replicaData: any = type === 'source' ? this.props.replica.source_environment
- : this.props.replica.destination_environment
- const currentValues: string[] = data[field.name] || []
- const oldValues: string[] = replicaData[field.name] || []
- let values: string[] = currentValues
- if (!currentValues.length) {
- values = [...oldValues]
- }
- if (values.find(v => v === value)) {
- data[field.name] = values.filter(v => v !== value)
- } else {
- data[field.name] = [...values, value]
- }
- } else if (field.groupName) {
- if (!data[field.groupName]) {
- data[field.groupName] = {}
- }
- data[field.groupName][field.name] = value
- } else if (parentFieldName) {
- data[parentFieldName] = data[parentFieldName] || {}
- data[parentFieldName][field.name] = value
- } else {
- data[field.name] = value
- }
- if (field.subFields) {
- field.subFields.forEach(subField => {
- const subFieldKeys = Object.keys(data).filter(k => k.indexOf(subField.name) > -1)
- subFieldKeys.forEach(k => {
- delete data[k]
- })
- })
- }
- const handleStateUpdate = () => {
- if (field.type !== 'string' || field.enum) {
- this.loadExtraOptions({ field, type, parentFieldName })
- }
- this.validateOptions(type)
- }
- if (type === 'source') {
- this.setState({ sourceData: data }, () => { handleStateUpdate() })
- } else {
- this.setState({ destinationData: data }, () => { handleStateUpdate() })
- }
- }
- async handleUpdateClick() {
- this.setState({ updating: true })
- const updateData: UpdateData = {
- source: this.state.sourceData,
- destination: this.state.destinationData,
- network: this.state.selectedNetworks.length > 0 ? this.getSelectedNetworks() : [],
- storage: this.state.storageMap,
- uploadedScripts: this.state.uploadedScripts,
- removedScripts: this.state.removedScripts,
- }
- if (this.props.type === 'replica') {
- try {
- await replicaStore.update({
- replica: this.props.replica as any,
- sourceEndpoint: this.props.sourceEndpoint,
- destinationEndpoint: this.props.destinationEndpoint,
- updateData,
- defaultStorage: this.getDefaultStorage(),
- storageConfigDefault: endpointStore.storageConfigDefault,
- })
- this.props.onRequestClose()
- this.props.onUpdateComplete(`/replicas/${this.props.replica.id}/executions`)
- } catch (err) {
- this.setState({ updating: false })
- }
- } else {
- try {
- const defaultStorage = EndpointUtils.getBusTypeStorageId(endpointStore.storageBackends, this.props.replica.storage_mappings?.default || null)
- const replicaDefaultStorage: { value: string | null, busType?: string | null } = {
- value: defaultStorage.id,
- busType: defaultStorage.busType,
- }
- const migration: MigrationItemDetails = await migrationStore.recreate({
- migration: this.props.replica as any,
- sourceEndpoint: this.props.sourceEndpoint,
- destEndpoint: this.props.destinationEndpoint,
- updateData,
- defaultStorage: replicaDefaultStorage,
- updatedDefaultStorage: this.state.defaultStorage,
- replicationCount: this.props.replica.replication_count,
- })
- migrationStore.clearDetails()
- this.props.onRequestClose()
- this.props.onUpdateComplete(`/migrations/${migration.id}/tasks`)
- } catch (err) {
- this.setState({ updating: false })
- }
- }
- }
- handleNetworkChange(changeObject: WizardNetworksChangeObject) {
- const networkMap = this.state.selectedNetworks
- .filter(n => n.sourceNic.network_name !== changeObject.nic.network_name)
- this.setState({
- selectedNetworks: [...networkMap, {
- sourceNic: changeObject.nic,
- targetNetwork: changeObject.network,
- targetSecurityGroups: changeObject.securityGroups,
- targetPortKey: changeObject.portKey,
- }],
- })
- }
- handleCancelScript(global: 'windows' | 'linux' | null, instanceName: string | null) {
- this.setState(prevState => ({
- uploadedScripts: prevState.uploadedScripts
- .filter(s => (global ? s.global !== global : s.instanceId !== instanceName)),
- }))
- }
- handleScriptUpload(script: InstanceScript) {
- this.setState(prevState => ({
- uploadedScripts: [
- ...prevState.uploadedScripts,
- script,
- ],
- }))
- }
- handleScriptDataRemove(script: InstanceScript) {
- this.setState(prevState => ({
- removedScripts: [
- ...prevState.removedScripts,
- script,
- ],
- }))
- }
- handleStorageChange(mapping: StorageMap) {
- this.setState(prevState => {
- const diskFieldName = mapping.type === 'backend' ? 'storage_backend_identifier' : 'id'
- const storageMap = prevState.storageMap
- .filter(n => n.type !== mapping.type || n.source[diskFieldName] !== mapping.source[diskFieldName])
- storageMap.push(mapping)
- return { storageMap }
- })
- }
- renderDestinationFailedMessage() {
- return (
- <ErrorWrapper>
- <StatusImage status="ERROR" />
- <ErrorMessage>{this.state.destinationFailedMessage}</ErrorMessage>
- </ErrorWrapper>
- )
- }
- renderOptions(type: 'source' | 'destination') {
- const loading = type === 'source' ? (providerStore.sourceSchemaLoading || providerStore.sourceOptionsPrimaryLoading)
- : (providerStore.destinationSchemaLoading || providerStore.destinationOptionsPrimaryLoading)
- if (this.state.destinationFailedMessage) {
- return this.renderDestinationFailedMessage()
- }
- if (loading) {
- return this.renderLoading(`Loading ${type === 'source' ? 'source' : 'target'} options ...`)
- }
- const optionsLoading = type === 'source' ? providerStore.sourceOptionsSecondaryLoading
- : providerStore.destinationOptionsSecondaryLoading
- const schema = type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema
- const fields = this.props.type === 'replica' ? schema.filter(f => !f.readOnly) : schema
- const extraOptionsConfig = configLoader.config.extraOptionsApiCalls.find(o => {
- const provider = type === 'source' ? this.props.sourceEndpoint.type : this.props.destinationEndpoint.type
- return o.name === provider && o.types.find(t => t === type)
- })
- let optionsLoadingSkipFields: string[] = []
- if (extraOptionsConfig) {
- optionsLoadingSkipFields = extraOptionsConfig.requiredFields
- }
- const endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
- let dictionaryKey = ''
- if (endpoint) {
- dictionaryKey = `${endpoint.type}-${type}`
- }
- const minionPools = minionPoolStore.minionPools
- .filter(m => m.platform === type && m.endpoint_id === endpoint.id)
- return (
- <WizardOptions
- minionPools={minionPools}
- wizardType={`${this.props.type || 'replica'}-${type}-options-edit`}
- getFieldValue={(f, d, pf) => this.getFieldValue({
- type, fieldName: f, defaultValue: d, parentFieldName: pf,
- })}
- fields={fields}
- selectedInstances={type === 'destination' ? this.props.instancesDetails : null}
- hasStorageMap={type === 'source' ? false : this.hasStorageMap()}
- storageBackends={endpointStore.storageBackends}
- onChange={(f, v, fp) => {
- this.handleFieldChange({
- type, field: f, value: v, parentFieldName: fp,
- })
- }}
- oneColumnStyle={{
- marginTop: '-16px', display: 'flex', flexDirection: 'column', width: '100%', alignItems: 'center',
- }}
- fieldWidth={ThemeProps.inputSizes.large.width}
- onScrollableRef={ref => { this.scrollableRef = ref }}
- availableHeight={384}
- useAdvancedOptions
- layout="modal"
- isSource={type === 'source'}
- optionsLoading={optionsLoading}
- optionsLoadingSkipFields={[...optionsLoadingSkipFields, 'description', 'execute_now',
- 'execute_now_options', ...migrationFields.map(f => f.name)]}
- dictionaryKey={dictionaryKey}
- />
- )
- }
- renderStorageMapping() {
- if (this.props.instancesDetailsLoading) {
- return this.renderLoading('Loading instances details ...')
- }
- return (
- <WizardStorage
- loading={endpointStore.storageLoading}
- defaultStorage={this.getDefaultStorage()}
- onDefaultStorageChange={(value, busType) => { this.setState({ defaultStorage: { value, busType } }) }}
- defaultStorageLayout="modal"
- storageBackends={endpointStore.storageBackends}
- instancesDetails={this.props.instancesDetails}
- storageMap={this.getStorageMap(endpointStore.storageBackends)}
- onChange={mapping => { this.handleStorageChange(mapping) }}
- style={{ padding: '32px 32px 0 32px', width: 'calc(100% - 64px)' }}
- titleWidth={160}
- onScrollableRef={ref => { this.scrollableRef = ref }}
- />
- )
- }
- renderNetworkMapping() {
- return (
- <WizardNetworks
- instancesDetails={this.props.instancesDetails}
- loadingInstancesDetails={this.props.instancesDetailsLoading}
- networks={this.props.networks}
- loading={this.props.networksLoading}
- onChange={change => {
- this.handleNetworkChange(change)
- }}
- selectedNetworks={this.getSelectedNetworks()}
- style={{ padding: '32px 32px 0 32px', width: 'calc(100% - 64px)' }}
- titleWidth={160}
- />
- )
- }
- renderUserScripts() {
- return (
- <WizardScripts
- instances={this.props.instancesDetails}
- loadingInstances={this.props.instancesDetailsLoading}
- onScriptUpload={s => { this.handleScriptUpload(s) }}
- onScriptDataRemove={s => { this.handleScriptDataRemove(s) }}
- onCancelScript={(g, i) => { this.handleCancelScript(g, i) }}
- uploadedScripts={this.state.uploadedScripts}
- removedScripts={this.state.removedScripts}
- userScriptData={this.props.replica?.user_scripts}
- scrollableRef={(r: HTMLElement) => { this.scrollableRef = r }}
- style={{ padding: '32px 32px 0 32px', width: 'calc(100% - 64px)' }}
- />
- )
- }
- renderContent() {
- let content = null
- switch (this.state.selectedPanel) {
- case 'source_options':
- content = this.renderOptions('source')
- break
- case 'dest_options':
- content = this.renderOptions('destination')
- break
- case 'network_mapping':
- content = this.renderNetworkMapping()
- break
- case 'user_scripts':
- content = this.renderUserScripts()
- break
- case 'storage_mapping':
- content = this.renderStorageMapping()
- break
- default:
- content = null
- }
- return (
- <PanelContent>
- {content}
- <Buttons>
- <Button
- large
- onClick={this.props.onRequestClose}
- secondary
- >Close
- </Button>
- {this.isLoading() ? (
- <LoadingButton large>Loading ...</LoadingButton>
- ) : this.state.updating ? (
- <LoadingButton large>Updating ...</LoadingButton>
- ) : (
- <Button
- large
- onClick={() => { this.handleUpdateClick() }}
- disabled={this.isUpdateDisabled()}
- >
- {this.props.type === 'replica' ? 'Update' : 'Create'}
- </Button>
- )}
- </Buttons>
- </PanelContent>
- )
- }
- renderLoading(message: string) {
- const loadingMessage = message || 'Loading ...'
- return (
- <LoadingWrapper>
- <StatusImage loading />
- <LoadingText>{loadingMessage}</LoadingText>
- </LoadingWrapper>
- )
- }
- render() {
- const navigationItems: Panel['props']['navigationItems'] = [
- {
- value: 'source_options',
- label: 'Source Options',
- disabled: this.state.sourceFailed,
- title: this.state.sourceFailed ? 'There are source platform errors, source options can\'t be updated' : '',
- loading: this.isLoadingSourceOptions(),
- },
- {
- value: 'dest_options',
- label: 'Target Options',
- loading: this.isLoadingDestOptions(),
- },
- {
- value: 'network_mapping',
- label: 'Network Mapping',
- loading: this.isLoadingNetwork(),
- },
- {
- value: 'user_scripts',
- label: 'User Scripts',
- loading: this.props.instancesDetailsLoading,
- },
- ]
- if (this.hasStorageMap()) {
- navigationItems.push({
- value: 'storage_mapping',
- label: 'Storage Mapping',
- loading: this.isLoadingStorage(),
- })
- }
- return (
- <Modal
- isOpen={this.props.isOpen}
- title={`${this.props.type === 'replica' ? 'Edit Replica' : 'Recreate Migration'}`}
- onRequestClose={this.props.onRequestClose}
- contentStyle={{ width: '800px' }}
- onScrollableRef={() => this.scrollableRef}
- fixedHeight={512}
- >
- <Panel
- navigationItems={navigationItems}
- content={this.renderContent()}
- onChange={navItem => { this.handlePanelChange(navItem.value) }}
- selectedValue={this.state.selectedPanel}
- onReloadClick={() => { this.handleReload() }}
- reloadLabel={this.props.type === 'replica' ? 'Reload All Replica Options' : 'Reload All Migration Options'}
- />
- </Modal>
- )
- }
- }
- export default TransferItemModal
|