| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- /*
- Copyright (C) 2017 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 {
- observable, action, computed, runInAction,
- } from 'mobx'
- import ProviderSource from '../sources/ProviderSource'
- import apiCaller from '../utils/ApiCaller'
- import configLoader from '../utils/Config'
- import { providerTypes } from '../constants'
- import { OptionsSchemaPlugin } from '../plugins/endpoint'
- import type { OptionValues } from '../@types/Endpoint'
- import type { Field } from '../@types/Field'
- import type { Providers, ProviderTypes } from '../@types/Providers'
- import regionStore from './RegionStore'
- export const getFieldChangeOptions = (config: {
- providerName: string | null,
- schema: Field[],
- data: any,
- field: Field | null,
- type: 'source' | 'destination',
- }) => {
- const {
- providerName, schema, data, field, type,
- } = config
- const providerWithEnvOptions = configLoader.config.extraOptionsApiCalls
- .find(p => p.name === providerName && p.types.find(t => t === type))
- if (!providerName || !providerWithEnvOptions) {
- return null
- }
- const requiredFields = providerWithEnvOptions.requiredFields
- const requiredValues = providerWithEnvOptions.requiredValues
- const relistFields = providerWithEnvOptions.relistFields
- const findFieldInSchema = (name: string) => schema.find(f => f.name === name)
- const filterValidField = (fn: string) => {
- const schemaField = findFieldInSchema(fn)
- if (data) {
- // This is for 'list_all_networks' field, which requires options calls after each value change
- // @TODO: refactor to use `relistFields` option
- if (schemaField && schemaField.type === 'boolean') {
- return true
- }
- if (data[fn] === null) {
- return false
- }
- const defaultValue = data[fn] === undefined && schemaField && schemaField.default
- const requiredValue = requiredValues?.find(f => f.field === fn)
- if (defaultValue != null) {
- if (requiredValue) {
- return Boolean(requiredValue.values.find(v => v === defaultValue))
- }
- return true
- }
- if (requiredValue) {
- return Boolean(requiredValue.values.find(v => v === data[fn]))
- }
- return data[fn]
- }
- return false
- }
- const requiredValidFields = requiredFields.filter(filterValidField)
- const relistValidFields = relistFields?.filter(filterValidField)
- const relistField = relistFields?.find(fn => fn === field?.name)
- const isCurrentFieldValid = field ? (
- requiredValidFields.find(fn => fn === field.name)
- || relistField
- ) : true
- if (requiredValidFields.length !== requiredFields.length || !isCurrentFieldValid) {
- return null
- }
- const envData: any = {}
- const setEnvDataValue = (fn: string) => {
- envData[fn] = data ? data[fn] : null
- if (envData[fn] == null) {
- const schemaField = findFieldInSchema(fn)
- if (schemaField && schemaField.default) {
- envData[fn] = schemaField.default
- }
- }
- }
- requiredValidFields.forEach(fn => {
- setEnvDataValue(fn)
- })
- relistValidFields?.forEach(fn => {
- setEnvDataValue(fn)
- })
- return envData
- }
- class ProviderStore {
- @observable connectionInfoSchema: Field[] = []
- @observable connectionSchemaLoading: boolean = false
- @observable providers: Providers | null = null
- @observable providersLoading: boolean = false
- @observable destinationSchema: Field[] = []
- @observable destinationSchemaLoading: boolean = false
- @observable destinationOptions: OptionValues[] = []
- // Set to true while loading the options call for the first set of options
- @observable destinationOptionsPrimaryLoading: boolean = false
- // Set to true while loading the options call with a set of values in the 'env' parameter
- @observable destinationOptionsSecondaryLoading: boolean = false
- @observable sourceOptions: OptionValues[] = []
- // Set to true while loading the options call for the first set of options
- @observable sourceOptionsPrimaryLoading: boolean = false
- // Set to true while loading the options call with a set of values in the 'env' parameter
- @observable sourceOptionsSecondaryLoading: boolean = false
- @observable sourceSchema: Field[] = []
- @observable sourceSchemaLoading: boolean = false
- lastDestinationSchemaType: 'replica' | 'migration' = 'replica'
- @computed
- get providerNames(): ProviderTypes[] {
- const sortPriority = configLoader.config.providerSortPriority
- const array: any[] = Object.keys(this.providers || {}).sort((a, b) => {
- if (sortPriority[a] && sortPriority[b]) {
- return (sortPriority[a] - sortPriority[b]) || a.localeCompare(b)
- }
- if (sortPriority[a]) {
- return -1
- }
- if (sortPriority[b]) {
- return 1
- }
- return a.localeCompare(b)
- })
- return array
- }
- private async setRegions(regionsField: Field | undefined) {
- if (!regionsField) {
- return
- }
- await regionStore.getRegions()
- regionsField.enum = [...regionStore.regions]
- }
- @action async getConnectionInfoSchema(providerName: ProviderTypes): Promise<void> {
- this.connectionSchemaLoading = true
- try {
- const fields: Field[] = await ProviderSource.getConnectionInfoSchema(providerName)
- await this.setRegions(fields.find(f => f.name === 'mapped_regions'))
- runInAction(() => { this.connectionInfoSchema = fields })
- } finally {
- runInAction(() => { this.connectionSchemaLoading = false })
- }
- }
- @action clearConnectionInfoSchema() {
- this.connectionInfoSchema = []
- }
- @action async loadProviders(): Promise<void> {
- if (this.providers || this.providersLoading) {
- return
- }
- this.providersLoading = true
- try {
- const providers: Providers = await ProviderSource.loadProviders()
- runInAction(() => { this.providers = providers })
- } finally {
- runInAction(() => { this.providersLoading = false })
- }
- }
- loadOptionsSchemaLastReqId: string = ''
- loadOptionsSchemaLastDirection: 'source' | 'destination' | '' = ''
- @action async loadOptionsSchema(options: {
- providerName: ProviderTypes,
- optionsType: 'source' | 'destination',
- useCache?: boolean,
- quietError?: boolean,
- }): Promise<Field[]> {
- const {
- providerName, optionsType, useCache, quietError,
- } = options
- if (optionsType === 'source') {
- this.sourceSchemaLoading = true
- } else {
- this.destinationSchemaLoading = true
- }
- const reqId = providerName
- this.loadOptionsSchemaLastReqId = reqId
- this.loadOptionsSchemaLastDirection = optionsType
- const isValid = () => {
- const isSameRequest = this.loadOptionsSchemaLastReqId === reqId
- const isSameDirection = this.loadOptionsSchemaLastDirection === optionsType
- if (!isSameDirection) {
- return true
- }
- return isSameRequest
- }
- try {
- const fields: Field[] = await ProviderSource.loadOptionsSchema(providerName, optionsType, useCache, quietError)
- this.loadOptionsSchemaSuccess(fields, optionsType, isValid())
- return fields
- } finally {
- this.loadOptionsSchemaDone(optionsType, isValid())
- }
- }
- @action loadOptionsSchemaSuccess(
- fields: Field[],
- optionsType: 'source' | 'destination',
- isValid: boolean,
- ) {
- if (!isValid) {
- return
- }
- if (optionsType === 'source') {
- this.sourceSchema = fields
- } else {
- this.destinationSchema = fields
- }
- }
- @action loadOptionsSchemaDone(optionsType: 'source' | 'destination', isValid: boolean) {
- if (!isValid) {
- return
- }
- if (optionsType === 'source') {
- this.sourceSchemaLoading = false
- } else {
- this.destinationSchemaLoading = false
- }
- }
- getOptionsValuesLastReqId: string = ''
- getOptionsValuesLastDirection: 'source' | 'destination' | '' = ''
- async getOptionsValues(config: {
- optionsType: 'source' | 'destination',
- endpointId: string,
- providerName: ProviderTypes,
- envData?: { [prop: string]: any } | null,
- useCache?: boolean,
- quietError?: boolean,
- allowMultiple?: boolean,
- }): Promise<OptionValues[]> {
- const {
- providerName, optionsType, endpointId, envData, useCache, quietError, allowMultiple,
- } = config
- const providerType = optionsType === 'source' ? providerTypes.SOURCE_OPTIONS : providerTypes.DESTINATION_OPTIONS
- await this.loadProviders()
- if (!this.providers) {
- return []
- }
- const providerWithExtraOptions = this.providers[providerName]
- .types.find(t => t === providerType)
- if (!providerWithExtraOptions) {
- return []
- }
- let canceled = false
- if (!allowMultiple) {
- apiCaller.cancelRequests(endpointId)
- }
- this.getOptionsValuesStart(optionsType, !envData)
- const reqId = `${endpointId}-${providerType}`
- this.getOptionsValuesLastReqId = reqId
- this.getOptionsValuesLastDirection = optionsType
- const isValid = () => {
- const isSameRequest = this.getOptionsValuesLastReqId === reqId
- const isSameDirection = this.getOptionsValuesLastDirection === optionsType
- if (!isSameDirection) {
- return true
- }
- return isSameRequest
- }
- try {
- const options = await ProviderSource
- .getOptionsValues(optionsType, endpointId, envData, useCache, quietError)
- this.getOptionsValuesSuccess(
- optionsType,
- providerName,
- options,
- isValid(),
- )
- return options
- } catch (err) {
- console.error(err)
- canceled = err ? err.canceled : false
- if (canceled) {
- return optionsType === 'source' ? [...this.sourceOptions] : [...this.destinationOptions]
- }
- throw err
- } finally {
- if (!canceled) {
- this.getOptionsValuesDone(
- optionsType,
- !envData,
- isValid(),
- )
- }
- }
- }
- @action getOptionsValuesStart(optionsType: 'source' | 'destination', isPrimary: boolean) {
- if (optionsType === 'source') {
- if (isPrimary) {
- this.sourceOptions = []
- this.sourceOptionsPrimaryLoading = true
- this.sourceOptionsSecondaryLoading = false
- } else {
- this.sourceOptionsPrimaryLoading = false
- this.sourceOptionsSecondaryLoading = true
- }
- } else if (isPrimary) {
- this.destinationOptions = []
- this.destinationOptionsPrimaryLoading = true
- this.destinationOptionsSecondaryLoading = false
- } else {
- this.destinationOptionsPrimaryLoading = false
- this.destinationOptionsSecondaryLoading = true
- }
- }
- @action getOptionsValuesDone(
- optionsType: 'source' | 'destination',
- isPrimary: boolean,
- isValid: boolean,
- ) {
- if (!isValid) {
- return
- }
- if (optionsType === 'source') {
- if (isPrimary) {
- this.sourceOptionsPrimaryLoading = false
- } else {
- this.sourceOptionsSecondaryLoading = false
- }
- } else if (isPrimary) {
- this.destinationOptionsPrimaryLoading = false
- } else {
- this.destinationOptionsSecondaryLoading = false
- }
- }
- @action getOptionsValuesSuccess(
- optionsType: 'source' | 'destination',
- provider: ProviderTypes,
- options: OptionValues[],
- isValid: boolean,
- ) {
- if (!isValid) {
- return
- }
- const schema = optionsType === 'source' ? this.sourceSchema : this.destinationSchema
- schema.forEach(field => {
- const parser = OptionsSchemaPlugin.for(provider)
- parser.fillFieldValues(field, options)
- })
- if (optionsType === 'source') {
- this.sourceSchema = [...schema]
- this.sourceOptions = options
- } else {
- this.destinationSchema = [...schema]
- this.destinationOptions = options
- }
- }
- }
- export default new ProviderStore()
|