Explorar el Código

Move UI configuration file to server side

This allows updating the configuration while the UI app is running.
With a simple browser page refresh the new configiguration will be used.

The new configuration file location is `./config.js`.
Sergiu Miclea hace 7 años
padre
commit
89fcdad852
Se han modificado 57 ficheros con 341 adiciones y 271 borrados
  1. 60 0
      config.js
  2. 1 0
      package.json
  3. 1 3
      private/cypress/integration/6 - users and projects/1 - Create a project.js
  4. 0 2
      private/cypress/integration/6 - users and projects/2 - Add a new user as a member.js
  5. 0 2
      private/cypress/integration/6 - users and projects/3 - Add existing user as a member.js
  6. 0 2
      private/cypress/integration/6 - users and projects/4 - Create a user.js
  7. 0 2
      private/cypress/integration/6 - users and projects/5 - Edit and delete user.js
  8. 0 2
      private/cypress/integration/6 - users and projects/6 - Edit and delete project.js
  9. 12 1
      server/main.js
  10. 21 5
      src/components/App.jsx
  11. 2 2
      src/components/molecules/LoginOptions/LoginOptions.jsx
  12. 7 3
      src/components/molecules/NewItemDropdown/NewItemDropdown.jsx
  13. 1 1
      src/components/molecules/ScheduleItem/ScheduleItem.jsx
  14. 4 2
      src/components/molecules/UserDropdown/UserDropdown.jsx
  15. 3 10
      src/components/molecules/WizardBreadcrumbs/WizardBreadcrumbs.jsx
  16. 4 23
      src/components/molecules/WizardBreadcrumbs/test.jsx
  17. 2 2
      src/components/organisms/EditReplica/EditReplica.jsx
  18. 3 2
      src/components/organisms/LoginForm/LoginForm.jsx
  19. 10 6
      src/components/organisms/Navigation/Navigation.jsx
  20. 1 1
      src/components/organisms/ReplicaExecutionOptions/ReplicaExecutionOptions.jsx
  21. 1 1
      src/components/organisms/ReplicaExecutionOptions/test.jsx
  22. 1 1
      src/components/organisms/Schedule/Schedule.jsx
  23. 1 1
      src/components/organisms/WizardOptions/WizardOptions.jsx
  24. 6 7
      src/components/organisms/WizardPageContent/WizardPageContent.jsx
  25. 3 4
      src/components/pages/AssessmentsPage/AssessmentsPage.jsx
  26. 2 2
      src/components/pages/EndpointsPage/EndpointsPage.jsx
  27. 2 0
      src/components/pages/LoginPage/LoginPage.jsx
  28. 2 2
      src/components/pages/MigrationDetailsPage/MigrationDetailsPage.jsx
  29. 2 2
      src/components/pages/MigrationsPage/MigrationsPage.jsx
  30. 2 2
      src/components/pages/ProjectsPage/ProjectsPage.jsx
  31. 2 2
      src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.jsx
  32. 2 2
      src/components/pages/ReplicasPage/ReplicasPage.jsx
  33. 2 2
      src/components/pages/UsersPage/UsersPage.jsx
  34. 13 4
      src/components/pages/WizardPage/WizardPage.jsx
  35. 0 141
      src/config.js
  36. 99 0
      src/constants.js
  37. 1 1
      src/index.js
  38. 1 1
      src/plugins/endpoint/default/OptionsSchemaPlugin.js
  39. 2 2
      src/plugins/endpoint/openstack/ContentPlugin.jsx
  40. 1 1
      src/sources/AssessmentSource.js
  41. 5 2
      src/sources/EndpointSource.js
  42. 1 1
      src/sources/InstanceSource.js
  43. 1 1
      src/sources/MigrationSource.js
  44. 1 1
      src/sources/NetworkSource.js
  45. 1 1
      src/sources/NotificationSource.js
  46. 1 1
      src/sources/ProjectSource.js
  47. 1 1
      src/sources/ProviderSource.js
  48. 1 1
      src/sources/ReplicaSource.js
  49. 1 1
      src/sources/ScheduleSource.js
  50. 3 2
      src/sources/UserSource.js
  51. 1 1
      src/sources/WizardSource.js
  52. 3 3
      src/stores/InstanceStore.js
  53. 5 3
      src/stores/ProviderStore.js
  54. 3 3
      src/stores/WizardStore.js
  55. 14 0
      src/types/Config.js
  56. 17 0
      src/utils/Config.js
  57. 5 0
      yarn.lock

+ 60 - 0
config.js

@@ -0,0 +1,60 @@
+// @flow
+
+import type { Config } from './src/types/Config'
+
+const conf: Config = {
+
+  // The list of pages which will not appear in the navigation menu
+  // Remove or comment to enable them
+  disabledPages: [
+    'planning',
+    'users',
+    'projects',
+  ],
+
+  // Whether to show the user domain name input when logging in
+  showUserDomainInput: false,
+
+  // The default user domain name used for logging in
+  defaultUserDomain: 'default',
+
+  // Shows the 'Use Current User/Project/Domain for Authentification' switch
+  // when creating a new openstack endpoint
+  showOpenstackCurrentUserSwitch: false,
+
+  // Whether to use Barbican secrets when creating a new endpoint
+  useBarbicanSecrets: true,
+
+  // The timeout between polling requests
+  requestPollTimeout: 5000,
+
+  // The list of providers which offer storage listing
+  storageProviders: ['openstack', 'azure'],
+
+  // The list of providers which offer source options
+  sourceOptionsProviders: ['aws'],
+
+  // - Specifies the `limit` for each provider when listing all its VMs for pagination.
+  // - If the provider is not in this list, the 'default' value will be used.
+  // - If the `default` value is lower than the number of instances that fit into a page, the latter number will be used.
+  // - `Infinity` value means no `limit` will be used, i.e. all VMs will be listed.
+  instancesListBackgroundLoading: { default: 10, ovm: Infinity },
+
+  // A list of providers for which `destination-options` API call(s) will be made
+  // If the item is just a string with the provider name, only one API call will be made
+  // If the item has `envRequiredFields`, an additional API call will be made once the specified fields are filled
+  providersWithExtraOptions: [
+    'openstack',
+    'oracle_vm',
+    {
+      name: 'azure',
+      envRequiredFields: ['location', 'resource_group'],
+    },
+    {
+      name: 'oci',
+      envRequiredFields: ['compartment', 'availability_domain'],
+    },
+  ],
+}
+
+export const config = conf

+ 1 - 0
package.json

@@ -98,6 +98,7 @@
     "react-notification-system": "^0.2.15",
     "react-router-dom": "^4.2.2",
     "react-tooltip": "^3.4.0",
+    "require-without-cache": "^0.0.6",
     "rimraf": "^2.6.2",
     "styled-components": "2.2.0",
     "styled-tools": "^0.2.2",

+ 1 - 3
private/cypress/integration/6 - users and projects/1 - Create a project.js

@@ -12,9 +12,7 @@ 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/>.
 */
 
-// @flow
-
-import { navigationMenu } from '../../../../src/config'
+import { navigationMenu } from '../../../../src/constants'
 
 const isEnabled: () => boolean = () => {
   let usersEnabled = navigationMenu.find(i => i.value === 'users' && i.disabled === false)

+ 0 - 2
private/cypress/integration/6 - users and projects/2 - Add a new user as a member.js

@@ -12,8 +12,6 @@ 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/>.
 */
 
-// @flow
-
 import { navigationMenu } from '../../../../src/config'
 
 const isEnabled: () => boolean = () => {

+ 0 - 2
private/cypress/integration/6 - users and projects/3 - Add existing user as a member.js

@@ -12,8 +12,6 @@ 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/>.
 */
 
-// @flow
-
 import { navigationMenu } from '../../../../src/config'
 
 const isEnabled: () => boolean = () => {

+ 0 - 2
private/cypress/integration/6 - users and projects/4 - Create a user.js

@@ -12,8 +12,6 @@ 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/>.
 */
 
-// @flow
-
 import { navigationMenu } from '../../../../src/config'
 
 const isEnabled: () => boolean = () => {

+ 0 - 2
private/cypress/integration/6 - users and projects/5 - Edit and delete user.js

@@ -12,8 +12,6 @@ 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/>.
 */
 
-// @flow
-
 import { navigationMenu } from '../../../../src/config'
 
 const isEnabled: () => boolean = () => {

+ 0 - 2
private/cypress/integration/6 - users and projects/6 - Edit and delete project.js

@@ -12,8 +12,6 @@ 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/>.
 */
 
-// @flow
-
 import { navigationMenu } from '../../../../src/config'
 
 const isEnabled: () => boolean = () => {

+ 12 - 1
server/main.js

@@ -12,9 +12,12 @@ 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/>.
 */
 
+// @flow
+
 import express from 'express'
 import fs from 'fs'
 import path from 'path'
+import requireWithoutCache from 'require-without-cache'
 
 import packageJson from '../package.json'
 
@@ -41,22 +44,30 @@ app.use(express.static('dist'))
 
 require('./proxy')(app)
 
+// $FlowIgnore
 app.get('/version', (req, res) => { res.send({ version: packageJson.version }) })
 
+// $FlowIgnore
+app.get('/config', (req, res) => {
+  res.send(requireWithoutCache('../config.js', require).config)
+})
+
 if (isDev) {
+  // $FlowIgnore
   app.use((req, res) => {
     res.redirect(`${req.baseUrl}/#${req.url}`)
   })
 } else {
+  // $FlowIgnore
   app.get('*/env.js', (req, res) => {
     res.sendFile(path.resolve(__dirname, '../dist', 'env.js'))
   })
+  // $FlowIgnore
   app.get('*', (req, res) => {
     res.sendFile(path.resolve(__dirname, '../dist', 'index.html'))
   })
 }
 
-
 app.listen(PORT, () => {
   console.log(`Express server is up on port ${PORT}`)
 })

+ 21 - 5
src/components/App.jsx

@@ -37,9 +37,10 @@ import UserDetailsPage from './pages/UserDetailsPage'
 import ProjectsPage from './pages/ProjectsPage'
 import ProjectDetailsPage from './pages/ProjectDetailsPage'
 
-import { navigationMenu } from '../config'
+import { navigationMenu } from '../constants'
 import Palette from './styleUtils/Palette'
 import StyleProps from './styleUtils/StyleProps'
+import configLoader from '../utils/Config'
 
 injectGlobal`
   ${Fonts}
@@ -63,16 +64,31 @@ const Wrapper = styled.div`
   }
 `
 
-class App extends React.Component<{}> {
+type State = {
+  isConfigReady: boolean,
+}
+
+class App extends React.Component<{}, State> {
+  state = {
+    isConfigReady: false,
+  }
+
   componentWillMount() {
     userStore.tokenLogin()
+    configLoader.load().then(() => {
+      this.setState({ isConfigReady: true })
+    })
   }
 
   render() {
+    if (!this.state.isConfigReady) {
+      return null
+    }
+
     let renderOptionalPage = (name: string, component: any, path?: string, exact?: boolean) => {
       const isAdmin = userStore.loggedUser ? userStore.loggedUser.isAdmin : true
-      // $FlowIgnore
-      if (navigationMenu.find(m => m.value === name && !m.disabled && (!m.requiresAdmin || isAdmin))) {
+      let isDisabled = configLoader.config.disabledPages.find(p => p === name)
+      if (navigationMenu.find(m => m.value === name && !isDisabled && (!m.requiresAdmin || isAdmin))) {
         return <Route path={`${path || `/${name}`}`} component={component} exact={exact} />
       }
       return null
@@ -101,7 +117,7 @@ class App extends React.Component<{}> {
           <Route component={NotFoundPage} />
         </Switch>
         <Notifications />
-      </Wrapper>
+      </Wrapper >
     )
   }
 }

+ 2 - 2
src/components/molecules/LoginOptions/LoginOptions.jsx

@@ -19,7 +19,7 @@ import styled, { css } from 'styled-components'
 
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
-import { loginButtons } from '../../../config'
+import { loginButtons } from '../../../constants'
 import googleLogo from './images/google-logo.svg'
 import microsoftLogo from './images/microsoft-logo.svg'
 import facebookLogo from './images/facebook-logo.svg'
@@ -95,7 +95,7 @@ const Logo = styled.div`
   ${props => buttonStyle(props.id, true)}
 `
 type Props = {
-  buttons?: {name: string, id: string}[]
+  buttons?: { name: string, id: string }[]
 }
 const LoginOptions = (props: Props) => {
   const buttons = props.buttons || loginButtons

+ 7 - 3
src/components/molecules/NewItemDropdown/NewItemDropdown.jsx

@@ -25,6 +25,7 @@ import DropdownButton from '../../atoms/DropdownButton'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import userStore from '../../../stores/UserStore'
+import configLoader from '../../../utils/Config'
 
 import migrationImage from './images/migration.svg'
 import replicaImage from './images/replica.svg'
@@ -32,7 +33,7 @@ import endpointImage from './images/endpoint.svg'
 import userImage from './images/user.svg'
 import projectImage from './images/project.svg'
 
-import { navigationMenu } from '../../../config'
+import { navigationMenu } from '../../../constants'
 
 const Wrapper = styled.div`
   position: relative;
@@ -176,6 +177,7 @@ class NewItemDropdown extends React.Component<Props, State> {
     }
 
     const isAdmin = userStore.loggedUser ? userStore.loggedUser.isAdmin : false
+    const disabledPages = configLoader.config ? configLoader.config.disabledPages : []
     let items: ItemType[] = [{
       title: 'Migration',
       href: '/wizard/migration',
@@ -196,13 +198,15 @@ class NewItemDropdown extends React.Component<Props, State> {
       value: 'user',
       description: 'Create a new Coriolis user',
       icon: { user: true },
-      disabled: Boolean(navigationMenu.find(i => i.value === 'users' && (i.disabled || (i.requiresAdmin && !isAdmin)))),
+      disabled: Boolean(navigationMenu.find(i => i.value === 'users'
+        && (disabledPages.find(p => p === 'users') || (i.requiresAdmin && !isAdmin)))),
     }, {
       title: 'Project',
       value: 'project',
       description: 'Create a new Coriolis project',
       icon: { project: true },
-      disabled: Boolean(navigationMenu.find(i => i.value === 'projects' && (i.disabled || (i.requiresAdmin && !isAdmin)))),
+      disabled: Boolean(navigationMenu.find(i => i.value === 'projects'
+        && (disabledPages.find(p => p === 'users') || (i.requiresAdmin && !isAdmin)))),
     }]
 
     let list = (

+ 1 - 1
src/components/molecules/ScheduleItem/ScheduleItem.jsx

@@ -25,7 +25,7 @@ import DatetimePicker from '../../molecules/DatetimePicker'
 import Button from '../../atoms/Button'
 import type { Schedule } from '../../../types/Schedule'
 
-import { executionOptions } from '../../../config'
+import { executionOptions } from '../../../constants'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import DateUtils from '../../../utils/DateUtils'

+ 4 - 2
src/components/molecules/UserDropdown/UserDropdown.jsx

@@ -22,11 +22,12 @@ import autobind from 'autobind-decorator'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
-import { navigationMenu } from '../../../config'
+import { navigationMenu } from '../../../constants'
 import type { User } from '../../../types/User'
 
 import userImage from './images/user.svg'
 import userWhiteImage from './images/user-white.svg'
+import configLoader from '../../../utils/Config'
 
 const Wrapper = styled.div`
   position: relative;
@@ -161,7 +162,8 @@ class UserDropdown extends React.Component<Props, State> {
 
     let href: ?string
     let isAdmin = this.props.user.isAdmin
-    if (isAdmin && navigationMenu.find(m => m.value === 'users' && !m.disabled && (!m.requiresAdmin || isAdmin))) {
+    if (isAdmin && navigationMenu.find(m => m.value === 'users'
+      && !configLoader.config.disabledPages.find(p => p === 'users') && (!m.requiresAdmin || isAdmin))) {
       href = `/user/${this.props.user.id}`
     }
 

+ 3 - 10
src/components/molecules/WizardBreadcrumbs/WizardBreadcrumbs.jsx

@@ -20,8 +20,8 @@ import styled from 'styled-components'
 
 import Arrow from '../../atoms/Arrow'
 
-import { wizardConfig } from '../../../config'
 import Palette from '../../styleUtils/Palette'
+import type { WizardPage } from '../../../types/WizardData'
 
 const Wrapper = styled.div`
   display: flex;
@@ -43,21 +43,14 @@ const Name = styled.div`
 
 type Props = {
   selected: { id: string },
-  wizardType: 'migration' | 'replica',
-  destinationProvider: ?string,
-  sourceProvider: ?string,
+  pages: WizardPage[],
 }
 @observer
 class WizardBreadcrumbs extends React.Component<Props> {
   render() {
-    let pages = wizardConfig.pages
-      .filter(p => !p.excludeFrom || p.excludeFrom !== this.props.wizardType)
-      .filter(p => !p.targetFilter || (this.props.destinationProvider && p.targetFilter(this.props.destinationProvider)))
-      .filter(p => !p.sourceFilter || (this.props.sourceProvider && p.sourceFilter(this.props.sourceProvider)))
-
     return (
       <Wrapper>
-        {pages.map(page => {
+        {this.props.pages.map(page => {
           return (
             <Breadcrumb key={page.id}>
               <Name selected={this.props.selected.id === page.id} data-test-id={`wBreadCrumbs-name-${page.id}`}>{page.breadcrumb}</Name>

+ 4 - 23
src/components/molecules/WizardBreadcrumbs/test.jsx

@@ -18,35 +18,16 @@ import React from 'react'
 import { shallow } from 'enzyme'
 import WizardBreadcrumbs from '.'
 import TW from '../../../utils/TestWrapper'
-import { wizardConfig } from '../../../config'
+import { wizardPages } from '../../../constants'
 
 const wrap = props => new TW(
-  shallow(<WizardBreadcrumbs destinationProvider="oci" sourceProvider="vmware_vsphere" {...props} />),
+  shallow(<WizardBreadcrumbs pages={wizardPages} destinationProvider="oci" sourceProvider="vmware_vsphere" {...props} />),
   'wBreadCrumbs'
 )
 
 describe('WizardBreadcrumbs Component', () => {
-  it('renders correct number of crumbs for replica', () => {
-    let wrapper = wrap({ selected: wizardConfig.pages[2], wizardType: 'replica' })
-    let pages = wizardConfig.pages.filter(p => !p.excludeFrom || p.excludeFrom !== 'replica')
-    expect(wrapper.find('name-', true).length).toBe(pages.length - 2)
-  })
-
-  it('renders correct number of crumbs for migration', () => {
-    let wrapper = wrap({ selected: wizardConfig.pages[2], wizardType: 'migration' })
-    let pages = wizardConfig.pages.filter(p => !p.excludeFrom || p.excludeFrom !== 'migration')
-    expect(wrapper.find('name-', true).length).toBe(pages.length - 2)
-  })
-
   it('has correct page selected', () => {
-    let pages = wizardConfig.pages.filter(p => !p.excludeFrom || p.excludeFrom !== 'migration')
-    let wrapper = wrap({ selected: pages[1], wizardType: 'migration' })
-    expect(wrapper.findText(`name-${pages[1].id}`)).toBe(pages[1].breadcrumb)
-  })
-
-  it('renders correct number of crumbs for Openstack', () => {
-    let wrapper = wrap({ selected: wizardConfig.pages[2], wizardType: 'migration', destinationProvider: 'openstack' })
-    let pages = wizardConfig.pages.filter(p => !p.excludeFrom || p.excludeFrom !== 'migration')
-    expect(wrapper.find('name-', true).length).toBe(pages.length - 1)
+    let wrapper = wrap({ selected: wizardPages[3] })
+    expect(wrapper.findText(`name-${wizardPages[3].id}`)).toBe(wizardPages[3].breadcrumb)
   })
 })

+ 2 - 2
src/components/organisms/EditReplica/EditReplica.jsx

@@ -39,8 +39,8 @@ import type { Field } from '../../../types/Field'
 import type { Instance, Nic, Disk } from '../../../types/Instance'
 import type { Network, NetworkMap } from '../../../types/Network'
 
-import { storageProviders } from '../../../config'
 import StyleProps from '../../styleUtils/StyleProps'
+import configLoader from '../../../utils/Config'
 
 const PanelContent = styled.div`
   padding: 32px;
@@ -117,7 +117,7 @@ class EditReplica extends React.Component<Props, State> {
       return false
     }
 
-    return Boolean(storageProviders.find(p => p === this.props.destinationEndpoint.type))
+    return Boolean(configLoader.config.storageProviders.find(p => p === this.props.destinationEndpoint.type))
   }
 
   isUpdateDisabled() {

+ 3 - 2
src/components/organisms/LoginForm/LoginForm.jsx

@@ -27,7 +27,7 @@ import StyleProps from '../../styleUtils/StyleProps'
 
 import errorIcon from './images/error.svg'
 
-import { loginButtons, showUserDomainInput } from '../../../config'
+import { loginButtons } from '../../../constants'
 import notificationStore from '../../../stores/NotificationStore'
 
 const Form = styled.form`
@@ -85,6 +85,7 @@ const LoginErrorText = styled.div`
 
 type Props = {
   className: string,
+  showUserDomainInput: boolean,
   loading: boolean,
   loginFailedResponse: { status: string, message?: string },
   domain: string,
@@ -182,7 +183,7 @@ class LoginForm extends React.Component<Props, State> {
         <LoginOptions />
         {loginSeparator}
         <FormFields>
-          {showUserDomainInput ? (
+          {this.props.showUserDomainInput ? (
             <LoginFormField
               label="Domain Name"
               value={this.props.domain}

+ 10 - 6
src/components/organisms/Navigation/Navigation.jsx

@@ -22,9 +22,9 @@ import autobind from 'autobind-decorator'
 
 import Logo from '../../atoms/Logo'
 import userStore from '../../../stores/UserStore'
+import configLoader from '../../../utils/Config'
 
-import { navigationMenu } from '../../../config'
-
+import { navigationMenu } from '../../../constants'
 import backgroundImage from './images/star-bg.jpg'
 import cbsImage from './images/cbsl-logo.svg'
 import cbsImageSmall from './images/cbsl-logo-small.svg'
@@ -237,6 +237,12 @@ class Navigation extends React.Component<Props> {
 
   isCollapsed: boolean = false
 
+  get filteredMenu() {
+    const isAdmin = userStore.loggedUser ? userStore.loggedUser.isAdmin : false
+    const isDisabled = (page: string) => configLoader.config ? configLoader.config.disabledPages.find(p => p === page) : false
+    return navigationMenu.filter(i => !isDisabled(i.value) && (!i.requiresAdmin || isAdmin))
+  }
+
   componentDidMount() {
     if (this.props.collapsed) {
       return
@@ -321,11 +327,10 @@ class Navigation extends React.Component<Props> {
   }
 
   renderMenu() {
-    const isAdmin = userStore.loggedUser ? userStore.loggedUser.isAdmin : false
     return (
       <Menu innerRef={ref => { this.menu = ref }} collapsed={this.props.collapsed}>
         {
-          navigationMenu.filter(i => i.disabled ? !i.disabled : i.requiresAdmin ? isAdmin : true).map(item => {
+          this.filteredMenu.map(item => {
             return (
               <MenuItem
                 key={item.value}
@@ -340,11 +345,10 @@ class Navigation extends React.Component<Props> {
   }
 
   renderSmallMenu() {
-    const isAdmin = userStore.loggedUser ? userStore.loggedUser.isAdmin : false
     return (
       <SmallMenu innerRef={ref => { this.smallMenu = ref }} collapsed={this.props.collapsed}>
         {
-          navigationMenu.filter(i => i.disabled ? !i.disabled : i.requiresAdmin ? isAdmin : true).map(item => {
+          this.filteredMenu.map(item => {
             let menuImage
             let bullet
             switch (item.value) {

+ 1 - 1
src/components/organisms/ReplicaExecutionOptions/ReplicaExecutionOptions.jsx

@@ -23,7 +23,7 @@ import WizardOptionsField from '../../molecules/WizardOptionsField'
 
 import LabelDictionary from '../../../utils/LabelDictionary'
 import KeyboardManager from '../../../utils/KeyboardManager'
-import { executionOptions } from '../../../config'
+import { executionOptions } from '../../../constants'
 import type { Field } from '../../../types/Field'
 
 import executionImage from './images/execution.svg'

+ 1 - 1
src/components/organisms/ReplicaExecutionOptions/test.jsx

@@ -20,7 +20,7 @@ import sinon from 'sinon'
 import TW from '../../../utils/TestWrapper'
 import ReplicaExecutionOptions from '.'
 
-import { executionOptions } from '../../../config'
+import { executionOptions } from '../../../constants'
 
 const wrap = props => new TW(shallow(<ReplicaExecutionOptions {...props} />), 'reOptions')
 

+ 1 - 1
src/components/organisms/Schedule/Schedule.jsx

@@ -32,7 +32,7 @@ import Palette from '../../styleUtils/Palette'
 import DateUtils from '../../../utils/DateUtils'
 import type { Schedule as ScheduleType } from '../../../types/Schedule'
 import type { Field } from '../../../types/Field'
-import { executionOptions } from '../../../config'
+import { executionOptions } from '../../../constants'
 
 import scheduleImage from './images/schedule.svg'
 

+ 1 - 1
src/components/organisms/WizardOptions/WizardOptions.jsx

@@ -28,7 +28,7 @@ import type { Field } from '../../../types/Field'
 import type { Instance } from '../../../types/Instance'
 import type { StorageBackend } from '../../../types/Endpoint'
 
-import { executionOptions } from '../../../config'
+import { executionOptions } from '../../../constants'
 
 const Wrapper = styled.div`
   display: flex;

+ 6 - 7
src/components/organisms/WizardPageContent/WizardPageContent.jsx

@@ -32,8 +32,8 @@ import WizardSummary from '../WizardSummary'
 
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
-import { providerTypes, wizardConfig } from '../../../config'
-import type { WizardData } from '../../../types/WizardData'
+import { providerTypes, wizardPages } from '../../../constants'
+import type { WizardData, WizardPage } from '../../../types/WizardData'
 import type { Endpoint, StorageBackend, StorageMap } from '../../../types/Endpoint'
 import type { Instance, Nic, Disk } from '../../../types/Instance'
 import type { Field } from '../../../types/Field'
@@ -141,6 +141,7 @@ type Props = {
   schedules: ScheduleType[],
   storageMap: StorageMap[],
   hasStorageMap: boolean,
+  pages: WizardPage[],
   onTypeChange: (isReplicaChecked: ?boolean) => void,
   onBackClick: () => void,
   onNextClick: () => void,
@@ -423,8 +424,8 @@ class WizardPageContent extends React.Component<Props, State> {
   renderNavigationActions() {
     let sourceEndpoint = this.props.wizardData.source && this.props.wizardData.source.type
     let targetEndpoint = this.props.wizardData.target && this.props.wizardData.target.type
-    let currentPageIndex = wizardConfig.pages.findIndex(p => p.id === this.props.page.id)
-    let isLastPage = currentPageIndex === wizardConfig.pages.length - 1
+    let currentPageIndex = wizardPages.findIndex(p => p.id === this.props.page.id)
+    let isLastPage = currentPageIndex === wizardPages.length - 1
 
     return (
       <Navigation>
@@ -460,9 +461,7 @@ class WizardPageContent extends React.Component<Props, State> {
           {this.renderNavigationActions()}
           <WizardBreadcrumbs
             selected={this.props.page}
-            wizardType={this.props.type}
-            destinationProvider={this.props.wizardData.target ? this.props.wizardData.target.type : null}
-            sourceProvider={this.props.wizardData.source ? this.props.wizardData.source.type : null}
+            pages={this.props.pages}
           />
         </Footer>
       </Wrapper>

+ 3 - 4
src/components/pages/AssessmentsPage/AssessmentsPage.jsx

@@ -32,8 +32,7 @@ import assessmentStore from '../../../stores/AssessmentStore'
 import endpointStore from '../../../stores/EndpointStore'
 import projectStore from '../../../stores/ProjectStore'
 import userStore from '../../../stores/UserStore'
-
-import { requestPollTimeout } from '../../../config'
+import configLoader from '../../../utils/Config'
 
 const Wrapper = styled.div``
 
@@ -178,7 +177,7 @@ class AssessmentsPage extends React.Component<Props, State> {
     let selectedResourceGroup = assessmentStore.selectedResourceGroup
 
     if (!connectionInfo || !connectionInfo.subscription_id || !selectedResourceGroup || !selectedResourceGroup.name) {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
       return
     }
 
@@ -189,7 +188,7 @@ class AssessmentsPage extends React.Component<Props, State> {
       userStore.loggedUser ? userStore.loggedUser.project.id : '',
       { backgroundLoading: true }
     ).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 2 - 2
src/components/pages/EndpointsPage/EndpointsPage.jsx

@@ -38,7 +38,7 @@ import migrationStore from '../../../stores/MigrationStore'
 import replicaStore from '../../../stores/ReplicaStore'
 import providerStore from '../../../stores/ProviderStore'
 import LabelDictionary from '../../../utils/LabelDictionary'
-import { requestPollTimeout } from '../../../config.js'
+import configLoader from '../../../utils/Config'
 import EndpointDuplicateOptions from '../../organisms/EndpointDuplicateOptions'
 
 const Wrapper = styled.div``
@@ -228,7 +228,7 @@ class EndpointsPage extends React.Component<{ history: any }, State> {
     }
 
     Promise.all([endpointStore.getEndpoints({ showLoading }), migrationStore.getMigrations(), replicaStore.getReplicas()]).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 2 - 0
src/components/pages/LoginPage/LoginPage.jsx

@@ -24,6 +24,7 @@ import LoginForm from '../../organisms/LoginForm'
 
 import StyleProps from '../../styleUtils/StyleProps'
 import userStore from '../../../stores/UserStore'
+import configStore from '../../../utils/Config'
 
 import backgroundImage from './images/star-bg.jpg'
 import cbsImage from './images/cbsl-logo.svg'
@@ -134,6 +135,7 @@ class LoginPage extends React.Component<Props, State> {
                 onFormSubmit={data => this.handleFormSubmit(data)}
                 loading={userStore.loading}
                 loginFailedResponse={userStore.loginFailedResponse}
+                showUserDomainInput={configStore.config.showUserDomainInput}
               />
             </Top>
             <Footer>

+ 2 - 2
src/components/pages/MigrationDetailsPage/MigrationDetailsPage.jsx

@@ -31,7 +31,7 @@ import endpointStore from '../../../stores/EndpointStore'
 import notificationStore from '../../../stores/NotificationStore'
 import networkStore from '../../../stores/NetworkStore'
 import instanceStore from '../../../stores/InstanceStore'
-import { requestPollTimeout } from '../../../config'
+import configLoader from '../../../utils/Config'
 
 import migrationImage from './images/migration.svg'
 import Palette from '../../styleUtils/Palette'
@@ -156,7 +156,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
       return
     }
     migrationStore.getMigration(this.props.match.params.id, false).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 2 - 2
src/components/pages/MigrationsPage/MigrationsPage.jsx

@@ -33,7 +33,7 @@ import projectStore from '../../../stores/ProjectStore'
 import migrationStore from '../../../stores/MigrationStore'
 import endpointStore from '../../../stores/EndpointStore'
 import notificationStore from '../../../stores/NotificationStore'
-import { requestPollTimeout } from '../../../config'
+import configLoader from '../../../utils/Config'
 
 const Wrapper = styled.div``
 
@@ -203,7 +203,7 @@ class MigrationsPage extends React.Component<{ history: any }, State> {
     }
 
     Promise.all([migrationStore.getMigrations(), endpointStore.getEndpoints()]).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 2 - 2
src/components/pages/ProjectsPage/ProjectsPage.jsx

@@ -28,7 +28,7 @@ import type { Project } from '../../../types/Project'
 
 import projectStore from '../../../stores/ProjectStore'
 import userStore from '../../../stores/UserStore'
-import { requestPollTimeout } from '../../../config.js'
+import configLoader from '../../../utils/Config'
 
 const Wrapper = styled.div``
 
@@ -92,7 +92,7 @@ class ProjectsPage extends React.Component<{ history: any }, State> {
     }
 
     Promise.all([projectStore.getProjects(showLoading), projectStore.getRoleAssignments()]).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 2 - 2
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.jsx

@@ -40,7 +40,7 @@ import endpointStore from '../../../stores/EndpointStore'
 import scheduleStore from '../../../stores/ScheduleStore'
 import instanceStore from '../../../stores/InstanceStore'
 import networkStore from '../../../stores/NetworkStore'
-import { requestPollTimeout } from '../../../config'
+import configLoader from '../../../utils/Config'
 
 import replicaImage from './images/replica.svg'
 import Palette from '../../styleUtils/Palette'
@@ -281,7 +281,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     }
 
     replicaStore.getReplicaExecutions(this.props.match.params.id, showLoading).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData(false) }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData(false) }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 2 - 2
src/components/pages/ReplicasPage/ReplicasPage.jsx

@@ -33,7 +33,7 @@ import projectStore from '../../../stores/ProjectStore'
 import replicaStore from '../../../stores/ReplicaStore'
 import endpointStore from '../../../stores/EndpointStore'
 import notificationStore from '../../../stores/NotificationStore'
-import { requestPollTimeout } from '../../../config'
+import configLoader from '../../../utils/Config'
 
 const Wrapper = styled.div``
 
@@ -164,7 +164,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
     }
 
     Promise.all([replicaStore.getReplicas(), endpointStore.getEndpoints()]).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 2 - 2
src/components/pages/UsersPage/UsersPage.jsx

@@ -28,7 +28,7 @@ import type { User } from '../../../types/User'
 
 import projectStore from '../../../stores/ProjectStore'
 import userStore from '../../../stores/UserStore'
-import { requestPollTimeout } from '../../../config.js'
+import configLoader from '../../../utils/Config'
 
 const Wrapper = styled.div``
 
@@ -88,7 +88,7 @@ class UsersPage extends React.Component<{ history: any }, State> {
     }
 
     userStore.getAllUsers(showLoading).then(() => {
-      this.pollTimeout = setTimeout(() => { this.pollData() }, requestPollTimeout)
+      this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 

+ 13 - 4
src/components/pages/WizardPage/WizardPage.jsx

@@ -35,7 +35,8 @@ import notificationStore from '../../../stores/NotificationStore'
 import scheduleStore from '../../../stores/ScheduleStore'
 import replicaStore from '../../../stores/ReplicaStore'
 import KeyboardManager from '../../../utils/KeyboardManager'
-import { wizardConfig, executionOptions } from '../../../config'
+import { wizardPages, executionOptions } from '../../../constants'
+import configLoader from '../../../utils/Config'
 
 import type { MainItem } from '../../../types/MainItem'
 import type { Endpoint as EndpointType, StorageBackend } from '../../../types/Endpoint'
@@ -80,10 +81,17 @@ class WizardPage extends React.Component<Props, State> {
   }
 
   get pages() {
-    return wizardConfig.pages
+    let sourceProvider = wizardStore.data.source ? wizardStore.data.source.type : ''
+    let destProvider = wizardStore.data.target ? wizardStore.data.target.type : ''
+    let pages = wizardPages
+    let sourceOptionsProviders = configLoader.config.sourceOptionsProviders
+    let storageOptionsProviders = configLoader.config.storageProviders
+    return pages
       .filter(p => !p.excludeFrom || p.excludeFrom !== this.state.type)
-      .filter(p => !p.targetFilter || (wizardStore.data.target && p.targetFilter(wizardStore.data.target.type)))
-      .filter(p => !p.sourceFilter || (wizardStore.data.source && p.sourceFilter(wizardStore.data.source.type)))
+      .filter(p => p.id !== 'storage'
+        || storageOptionsProviders.find(p => p === destProvider))
+      .filter(p => p.id !== 'source-options'
+        || sourceOptionsProviders.find(p => p === sourceProvider))
   }
 
   componentWillMount() {
@@ -491,6 +499,7 @@ class WizardPage extends React.Component<Props, State> {
             onUserItemClick={item => { this.handleUserItemClick(item) }}
           />}
           pageContentComponent={<WizardPageContent
+            pages={this.pages}
             page={wizardStore.currentPage}
             providerStore={providerStore}
             instanceStore={instanceStore}

+ 0 - 141
src/config.js

@@ -1,141 +0,0 @@
-/*
-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/>.
-*/
-
-// @flow
-
-export const coriolisUrl = (window.env && window.env.CORIOLIS_URL) || '/'
-
-export const servicesUrl = {
-  identity: `${coriolisUrl}identity/auth/tokens`,
-  projects: `${coriolisUrl}identity/auth/projects`,
-  users: `${coriolisUrl}identity/users`,
-  endpoints: `${coriolisUrl}coriolis/endpoints`,
-  coriolis: `${coriolisUrl}coriolis`,
-  migrations: `${coriolisUrl}coriolis/migrations`,
-  barbican: `${coriolisUrl}barbican`,
-  openId: `${coriolisUrl}identity/OS-FEDERATION/identity_providers/google/protocols/openid/auth`,
-}
-
-export const showUserDomainInput = false
-export const defaultUserDomain = 'default'
-
-// Whether to use Barbican secrets when creating a new endpoint
-export const useSecret = true
-
-// Shows the 'Use Current User/Project/Domain for Authentification' switch
-// when creating a new openstack endpoint
-export const showOpenstackCurrentUserSwitch = false
-
-
-export const navigationMenu: any[] = [
-  { label: 'Replicas', value: 'replicas' },
-  { label: 'Migrations', value: 'migrations' },
-  { label: 'Cloud Endpoints', value: 'endpoints' },
-
-  // Optional pages
-  { label: 'Planning', value: 'planning', disabled: true },
-
-  // User management pages
-  { label: 'Projects', value: 'projects', disabled: true, requiresAdmin: true },
-  { label: 'Users', value: 'users', disabled: true, requiresAdmin: true },
-]
-
-export const requestPollTimeout = 5000
-
-// https://github.com/cloudbase/coriolis/blob/master/coriolis/constants.py
-// PROVIDER_TYPE_IMPORT = 1 // migration target schema
-// PROVIDER_TYPE_EXPORT = 2 // migration source schema
-// PROVIDER_TYPE_REPLICA_IMPORT = 4 // replica target schema
-// PROVIDER_TYPE_REPLICA_EXPORT = 8 // replica source schema
-export const providerTypes = {
-  TARGET_MIGRATION: 1,
-  SOURCE_MIGRATION: 2,
-  TARGET_REPLICA: 4,
-  SOURCE_REPLICA: 8,
-  CONNECTION: 16,
-}
-
-export const loginButtons = [
-  // {
-  //   name: 'Google',
-  //   id: 'google',
-  //   url: '',
-  // },
-]
-
-export const env = {
-  name: process.env.NODE_ENV || 'development',
-  isDev: process.env.NODE_ENV !== 'production',
-  isBrowser: typeof window !== 'undefined',
-}
-
-export const executionOptions = [
-  {
-    name: 'shutdown_instances',
-    type: 'strict-boolean',
-    defaultValue: false,
-  },
-]
-
-export const storageProviders = ['openstack', 'azure']
-export const sourceOptionsProviders = ['aws']
-
-export const wizardConfig = {
-  pages: [
-    { id: 'type', title: 'New', breadcrumb: 'Type' },
-    { id: 'source', title: 'Select your source cloud', breadcrumb: 'Source Cloud' },
-    {
-      id: 'source-options',
-      title: 'Source options',
-      breadcrumb: 'Source Options',
-      sourceFilter: (p: string) => sourceOptionsProviders.find(s => s === p),
-    },
-    { id: 'vms', title: 'Select instances', breadcrumb: 'Select VMs' },
-    { id: 'target', title: 'Select your target cloud', breadcrumb: 'Target Cloud' },
-    { id: 'dest-options', title: 'Target options', breadcrumb: 'Target Options' },
-    { id: 'networks', title: 'Networks', breadcrumb: 'Networks' },
-    {
-      id: 'storage',
-      title: 'Storage Mapping',
-      breadcrumb: 'Storage',
-      targetFilter: (p: string) => storageProviders.find(s => s === p),
-    },
-    { id: 'schedule', title: 'Schedule', breadcrumb: 'Schedule', excludeFrom: 'migration' },
-    { id: 'summary', title: 'Summary', breadcrumb: 'Summary' },
-  ],
-}
-
-// - Specifies the `limit` for each provider when listing all its VMs for pagination.
-// - If the provider is not in this list, the 'default' value will be used.
-// - If the `default` value is lower than the number of instances that fit into a page, the latter number will be used.
-// - `Infinity` value means no `limit` will be used, i.e. all VMs will be listed.
-export const instancesListBackgroundLoading = { default: 10, ovm: Infinity }
-
-// A list of providers for which `destination-options` API call(s) will be made in the Wizard
-// If the item is just a string with the provider name, only one API call will be made
-// If the item has `envRequiredFields`, an additional API call will be made once the specified fields are filled
-export const providersWithExtraOptions = [
-  'openstack',
-  'oracle_vm',
-  {
-    name: 'azure',
-    envRequiredFields: ['location', 'resource_group'],
-  },
-  {
-    name: 'oci',
-    envRequiredFields: ['compartment', 'availability_domain'],
-  },
-]
-
-export const basename = process.env.PUBLIC_PATH

+ 99 - 0
src/constants.js

@@ -0,0 +1,99 @@
+/*
+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/>.
+*/
+
+// @flow
+
+export const coriolisUrl = (window.env && window.env.CORIOLIS_URL) || '/'
+
+export const servicesUrl = {
+  identity: `${coriolisUrl}identity/auth/tokens`,
+  projects: `${coriolisUrl}identity/auth/projects`,
+  users: `${coriolisUrl}identity/users`,
+  endpoints: `${coriolisUrl}coriolis/endpoints`,
+  coriolis: `${coriolisUrl}coriolis`,
+  migrations: `${coriolisUrl}coriolis/migrations`,
+  barbican: `${coriolisUrl}barbican`,
+  openId: `${coriolisUrl}identity/OS-FEDERATION/identity_providers/google/protocols/openid/auth`,
+}
+
+export const navigationMenu = [
+  { label: 'Replicas', value: 'replicas' },
+  { label: 'Migrations', value: 'migrations' },
+  { label: 'Cloud Endpoints', value: 'endpoints' },
+
+  // Optional pages
+  { label: 'Planning', value: 'planning' },
+
+  // User management pages
+  { label: 'Projects', value: 'projects', requiresAdmin: true },
+  { label: 'Users', value: 'users', requiresAdmin: true },
+]
+
+// https://github.com/cloudbase/coriolis/blob/master/coriolis/constants.py
+// PROVIDER_TYPE_IMPORT = 1 // migration target schema
+// PROVIDER_TYPE_EXPORT = 2 // migration source schema
+// PROVIDER_TYPE_REPLICA_IMPORT = 4 // replica target schema
+// PROVIDER_TYPE_REPLICA_EXPORT = 8 // replica source schema
+export const providerTypes = {
+  TARGET_MIGRATION: 1,
+  SOURCE_MIGRATION: 2,
+  TARGET_REPLICA: 4,
+  SOURCE_REPLICA: 8,
+  CONNECTION: 16,
+}
+
+export const loginButtons = [
+  // {
+  //   name: 'Google',
+  //   id: 'google',
+  //   url: '',
+  // },
+]
+
+export const env = {
+  name: process.env.NODE_ENV || 'development',
+  isDev: process.env.NODE_ENV !== 'production',
+  isBrowser: typeof window !== 'undefined',
+}
+
+export const executionOptions = [
+  {
+    name: 'shutdown_instances',
+    type: 'strict-boolean',
+    defaultValue: false,
+  },
+]
+
+export const wizardPages = [
+  { id: 'type', title: 'New', breadcrumb: 'Type' },
+  { id: 'source', title: 'Select your source cloud', breadcrumb: 'Source Cloud' },
+  {
+    id: 'source-options',
+    title: 'Source options',
+    breadcrumb: 'Source Options',
+  },
+  { id: 'vms', title: 'Select instances', breadcrumb: 'Select VMs' },
+  { id: 'target', title: 'Select your target cloud', breadcrumb: 'Target Cloud' },
+  { id: 'dest-options', title: 'Target options', breadcrumb: 'Target Options' },
+  { id: 'networks', title: 'Networks', breadcrumb: 'Networks' },
+  {
+    id: 'storage',
+    title: 'Storage Mapping',
+    breadcrumb: 'Storage',
+  },
+  { id: 'schedule', title: 'Schedule', breadcrumb: 'Schedule', excludeFrom: 'migration' },
+  { id: 'summary', title: 'Summary', breadcrumb: 'Summary' },
+]
+
+export const basename = process.env.PUBLIC_PATH

+ 1 - 1
src/index.js

@@ -19,7 +19,7 @@ import React from 'react'
 import { render } from 'react-dom'
 import { BrowserRouter, HashRouter } from 'react-router-dom'
 
-import { basename } from './config'
+import { basename } from './constants'
 import App from './components/App.jsx'
 
 const renderApp = () => React.createElement(

+ 1 - 1
src/plugins/endpoint/default/OptionsSchemaPlugin.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import type { Field } from '../../../types/Field'
 import type { DestinationOption, StorageMap } from '../../../types/Endpoint'
 import type { NetworkMap } from '../../../types/Network'
-import { executionOptions } from '../../../config'
+import { executionOptions } from '../../../constants'
 
 const migrationImageOsTypes = ['windows', 'linux']
 

+ 2 - 2
src/plugins/endpoint/openstack/ContentPlugin.jsx

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from 'react'
 import styled from 'styled-components'
 
-import { showOpenstackCurrentUserSwitch } from '../../../config'
+import configLoader from '../../../utils/Config'
 
 import ToggleButtonBar from '../../../components/atoms/ToggleButtonBar'
 import type { Field } from '../../../types/Field'
@@ -128,7 +128,7 @@ class ContentPlugin extends React.Component<Props, State> {
       extraAdvancedFields = extraAdvancedFields.concat(['user_domain', 'project_domain'])
     }
     let ignoreFields = ['user_domain_id', 'project_domain_id', 'user_domain_name', 'project_domain_name']
-    if (!showOpenstackCurrentUserSwitch) {
+    if (!configLoader.config.showOpenstackCurrentUserSwitch) {
       ignoreFields.push('openstack_use_current_user')
     }
 

+ 1 - 1
src/sources/AssessmentSource.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import type { MigrationInfo } from '../types/Assessment'
 import type { MainItem } from '../types/MainItem'
 import Api from '../utils/ApiCaller'
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 import notificationStore from '../stores/NotificationStore'
 
 class AssessmentSourceUtils {

+ 5 - 2
src/sources/EndpointSource.js

@@ -21,7 +21,9 @@ import { SchemaParser } from './Schemas'
 import ObjectUtils from '../utils/ObjectUtils'
 import type { Endpoint, Validation, Storage } from '../types/Endpoint'
 
-import { servicesUrl, useSecret } from '../config'
+import { servicesUrl } from '../constants'
+
+import configLoader from '../utils/Config'
 
 let getBarbicanPayload = data => {
   return {
@@ -175,7 +177,8 @@ class EdnpointSource {
     let parsedEndpoint: any = skipSchemaParser ? { ...endpoint } : SchemaParser.fieldsToPayload(endpoint)
     let newEndpoint: any = {}
     let connectionInfo = {}
-    if (useSecret && parsedEndpoint.connectionInfo && Object.keys(parsedEndpoint.connectionInfo).length > 0) {
+    if (configLoader.config.useBarbicanSecrets
+      && parsedEndpoint.connectionInfo && Object.keys(parsedEndpoint.connectionInfo).length > 0) {
       return Api.send({
         url: `${servicesUrl.barbican}/v1/secrets`,
         method: 'POST',

+ 1 - 1
src/sources/InstanceSource.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import Api from '../utils/ApiCaller'
 import type { Instance } from '../types/Instance'
 
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 
 class InstanceSource {
   static loadInstancesChunk(

+ 1 - 1
src/sources/MigrationSource.js

@@ -24,7 +24,7 @@ import type { Field } from '../types/Field'
 import type { NetworkMap } from '../types/Network'
 import type { Endpoint, StorageMap } from '../types/Endpoint'
 
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 
 class MigrationSourceUtils {
   static sortTaskUpdates(migration) {

+ 1 - 1
src/sources/NetworkSource.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import Api from '../utils/ApiCaller'
 import type { Network } from '../types/Network'
 
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 
 class NetworkSource {
   static loadNetworks(enpointId: string, environment: ?{ [string]: mixed }, options?: {

+ 1 - 1
src/sources/NotificationSource.js

@@ -16,7 +16,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import moment from 'moment'
 
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 import Api from '../utils/ApiCaller'
 import type { NotificationItemData, NotificationItem } from '../types/NotificationItem'
 

+ 1 - 1
src/sources/ProjectSource.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import Api from '../utils/ApiCaller'
 
 import UserSource from './UserSource'
-import { servicesUrl, coriolisUrl } from '../config'
+import { servicesUrl, coriolisUrl } from '../constants'
 import type { Project, Role, RoleAssignment } from '../types/Project'
 import type { User } from '../types/User'
 

+ 1 - 1
src/sources/ProviderSource.js

@@ -15,7 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 // @flow
 
 import Api from '../utils/ApiCaller'
-import { servicesUrl, providerTypes } from '../config'
+import { servicesUrl, providerTypes } from '../constants'
 import { SchemaParser } from './Schemas'
 import type { Field } from '../types/Field'
 import type { Providers } from '../types/Providers'

+ 1 - 1
src/sources/ReplicaSource.js

@@ -19,7 +19,7 @@ import moment from 'moment'
 import Api from '../utils/ApiCaller'
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
 
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 import type { MainItem, UpdateData } from '../types/MainItem'
 import type { Execution } from '../types/Execution'
 import type { Endpoint } from '../types/Endpoint'

+ 1 - 1
src/sources/ScheduleSource.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import moment from 'moment'
 
 import Api from '../utils/ApiCaller'
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 import DateUtils from '../utils/DateUtils'
 import type { Schedule } from '../types/Schedule'
 

+ 3 - 2
src/sources/UserSource.js

@@ -17,7 +17,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import cookie from 'js-cookie'
 
 import Api from '../utils/ApiCaller'
-import { servicesUrl, coriolisUrl, defaultUserDomain } from '../config'
+import { servicesUrl, coriolisUrl } from '../constants'
+import configLoader from '../utils/Config'
 import type { Credentials, User } from '../types/User'
 import type { Role, Project, RoleAssignment } from '../types/Project'
 
@@ -40,7 +41,7 @@ class UserSource {
   }
 
   static getDomainName(): string {
-    return localStorage.getItem('userDomainName') || defaultUserDomain
+    return localStorage.getItem('userDomainName') || configLoader.config.defaultUserDomain
   }
 
   static login(userData: Credentials): Promise<User> {

+ 1 - 1
src/sources/WizardSource.js

@@ -18,7 +18,7 @@ import Api from '../utils/ApiCaller'
 import notificationStore from '../stores/NotificationStore'
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
 
-import { servicesUrl } from '../config'
+import { servicesUrl } from '../constants'
 import type { WizardData } from '../types/WizardData'
 import type { StorageMap } from '../types/Endpoint'
 import type { MainItem } from '../types/MainItem'

+ 3 - 3
src/stores/InstanceStore.js

@@ -20,7 +20,7 @@ import type { Instance } from '../types/Instance'
 import type { Endpoint } from '../types/Endpoint'
 import InstanceSource from '../sources/InstanceSource'
 import ApiCaller from '../utils/ApiCaller'
-import { instancesListBackgroundLoading as chunkSize } from '../config'
+import configLoader from '../utils/Config'
 
 class InstanceLocalStorage {
   static saveInstancesToLocalStorage(endpointId: string, instances: Instance[]) {
@@ -128,7 +128,7 @@ class InstanceStore {
     }
     this.backgroundChunksLoading = true
     this.lastEndpointId = endpoint.id
-
+    let chunkSize = configLoader.config.instancesListBackgroundLoading
     let chunkCount = Math.max(chunkSize[endpoint.type] || chunkSize.default, vmsPerPage)
 
     let loadNextChunk = (lastEndpointId?: string) => {
@@ -203,7 +203,7 @@ class InstanceStore {
 
     this.searching = true
     this.searchChunksLoading = true
-
+    let chunkSize = configLoader.config.instancesListBackgroundLoading
     let chunkCount = Math.max(chunkSize[endpoint.type] || chunkSize.default, this.instancesPerPage)
 
     let loadNextChunk = (lastEndpointId?: string) => {

+ 5 - 3
src/stores/ProviderStore.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import { observable, action } from 'mobx'
 
 import ProviderSource from '../sources/ProviderSource'
-import { providersWithExtraOptions } from '../config.js'
+import configLoader from '../utils/Config'
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
 import type { DestinationOption } from '../types/Endpoint'
 import type { Field } from '../types/Field'
@@ -30,7 +30,8 @@ export const getFieldChangeDestOptions = (options: {
   field: ?Field,
 }) => {
   let { provider, destSchema, data, field } = options
-  let providerWithExtraOptions = providersWithExtraOptions.find(p => typeof p !== 'string' && p.name === provider)
+  let providerWithExtraOptions = configLoader.config.providersWithExtraOptions
+    .find(p => typeof p !== 'string' && p.name === provider)
   if (!provider || !providerWithExtraOptions || typeof providerWithExtraOptions === 'string' || !providerWithExtraOptions.envRequiredFields) {
     return null
   }
@@ -135,7 +136,8 @@ class ProviderStore {
   cache: { key: string, data: DestinationOption[] }[] = []
 
   @action getDestinationOptions(endpointId: string, provider: string, envData?: { [string]: mixed }, useCache?: boolean): Promise<DestinationOption[]> {
-    let providerWithExtraOptions = providersWithExtraOptions.find(p => typeof p === 'string' ? p === provider : p.name === provider)
+    let providerWithExtraOptions = configLoader.config.providersWithExtraOptions
+      .find(p => typeof p === 'string' ? p === provider : p.name === provider)
     if (!providerWithExtraOptions) {
       return Promise.resolve([])
     }

+ 3 - 3
src/stores/WizardStore.js

@@ -23,7 +23,7 @@ import type { Field } from '../types/Field'
 import type { NetworkMap } from '../types/Network'
 import type { StorageMap } from '../types/Endpoint'
 import type { Schedule } from '../types/Schedule'
-import { wizardConfig } from '../config'
+import { wizardPages } from '../constants'
 import Source from '../sources/WizardSource'
 
 const updateOptions = (oldOptions: ?{ [string]: mixed }, data: { field: Field, value: any }) => {
@@ -45,7 +45,7 @@ class WizardStore {
   @observable data: WizardData = {}
   @observable schedules: Schedule[] = []
   @observable storageMap: StorageMap[] = []
-  @observable currentPage: WizardPage = wizardConfig.pages[0]
+  @observable currentPage: WizardPage = wizardPages[0]
   @observable createdItem: ?MainItem = null
   @observable creatingItem: boolean = false
   @observable createdItems: ?MainItem[] = null
@@ -72,7 +72,7 @@ class WizardStore {
 
   @action clearData() {
     this.data = {}
-    this.currentPage = wizardConfig.pages[0]
+    this.currentPage = wizardPages[0]
   }
 
   @action setCurrentPage(page: WizardPage) {

+ 14 - 0
src/types/Config.js

@@ -0,0 +1,14 @@
+// @flow
+
+export type Config = {
+  disabledPages: string[],
+  showUserDomainInput: boolean,
+  defaultUserDomain: string,
+  showOpenstackCurrentUserSwitch: boolean,
+  useBarbicanSecrets: boolean,
+  requestPollTimeout: number,
+  storageProviders: string[],
+  sourceOptionsProviders: string[],
+  instancesListBackgroundLoading: { default: number, [string]: number },
+  providersWithExtraOptions: Array<string | { name: string, envRequiredFields: string[] }>,
+}

+ 17 - 0
src/utils/Config.js

@@ -0,0 +1,17 @@
+// @flow
+
+import apiCaller from './ApiCaller'
+
+import type { Config } from '../types/Config'
+
+class ConfigLoader {
+  config: Config
+
+  load() {
+    return apiCaller.get('/config').then(res => {
+      this.config = res.data
+    })
+  }
+}
+
+export default new ConfigLoader()

+ 5 - 0
yarn.lock

@@ -7315,6 +7315,11 @@ require-uncached@^1.0.3:
     caller-path "^0.1.0"
     resolve-from "^1.0.0"
 
+require-without-cache@^0.0.6:
+  version "0.0.6"
+  resolved "https://registry.yarnpkg.com/require-without-cache/-/require-without-cache-0.0.6.tgz#b76566d78f8c5efa58b978266b9cfbeb96e037e2"
+  integrity sha1-t2Vm14+MXvpYuXgma5z765bgN+I=
+
 resolve-from@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"