Skip to content

Commit

Permalink
Move UI configuration file to server side
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
Sergiu Miclea committed Mar 12, 2019
1 parent da25c8a commit 89fcdad
Show file tree
Hide file tree
Showing 57 changed files with 341 additions and 271 deletions.
60 changes: 60 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down
13 changes: 12 additions & 1 deletion server/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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}`)
})
26 changes: 21 additions & 5 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand Down Expand Up @@ -101,7 +117,7 @@ class App extends React.Component<{}> {
<Route component={NotFoundPage} />
</Switch>
<Notifications />
</Wrapper>
</Wrapper >
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/molecules/LoginOptions/LoginOptions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions src/components/molecules/NewItemDropdown/NewItemDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ 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'
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;
Expand Down Expand Up @@ -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',
Expand All @@ -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 = (
Expand Down
2 changes: 1 addition & 1 deletion src/components/molecules/ScheduleItem/ScheduleItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
6 changes: 4 additions & 2 deletions src/components/molecules/UserDropdown/UserDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}`
}

Expand Down
13 changes: 3 additions & 10 deletions src/components/molecules/WizardBreadcrumbs/WizardBreadcrumbs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>
Expand Down
27 changes: 4 additions & 23 deletions src/components/molecules/WizardBreadcrumbs/test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})
Loading

0 comments on commit 89fcdad

Please sign in to comment.