Просмотр исходного кода

Add ability to set services URLs using MOD_JSON

Using the UI mod JSON file (set using MOD_JSON env. variable), services
URLs can be modified.

You can use "{BASE_URL}" as a part of the service URL and it will be set
at runtime to the value of CORIOLIS_URL env. variable.

You can check [`ui-mod-sample.json`](ui-mod-sample.json) for an example
on how to modify the services URLs. The example uses the default values
used when no modding is applied.

When modding, you can add just the services URLs which you'd like to
change (there's no need to add them all, if there are URLs which use the
default value).
Sergiu Miclea 6 лет назад
Родитель
Сommit
257c052529

+ 9 - 0
config.js

@@ -97,6 +97,15 @@ const conf: Config = {
 
   // The number of items per page applicable to main lists: replicas, migrations, endpoints, users etc.
   mainListItemsPerPage: 20,
+
+  servicesUrls: {
+    keystone: '{BASE_URL}/identity',
+    barbican: '{BASE_URL}/barbican',
+    coriolis: '{BASE_URL}/coriolis',
+    coriolisLogs: '{BASE_URL}/logs',
+    coriolisLogStreamBaseUrl: '{BASE_URL}',
+    coriolisLicensing: '{BASE_URL}/licensing',
+  },
 }
 
 export const config = conf

+ 30 - 2
server/api/ConfigApi.js

@@ -18,7 +18,28 @@ import path from 'path'
 import fs from 'fs'
 import requireWithoutCache from 'require-without-cache'
 
-import type { Config } from '../../src/types/Config'
+import type { Config, Services } from '../../src/types/Config'
+
+const getBaseUrl = () => {
+  let BASE_URL = process.env.CORIOLIS_URL || ''
+  return BASE_URL.trim().replace(/\/$/, '')
+}
+
+const setDefaultServicesUrls = (config: Config): Config => {
+  Object.keys(config.servicesUrls).forEach(key => {
+    config.servicesUrls[key] = config.servicesUrls[key].replace('{BASE_URL}', getBaseUrl())
+  })
+  return config
+}
+
+const modServicesUrls = (config: Config, servicesMod: Services): Services => {
+  let services: Services = { ...config.servicesUrls }
+  Object.keys(services).forEach(key => {
+    services[key] = (servicesMod[key] ? servicesMod[key] : services[key])
+      .replace('{BASE_URL}', getBaseUrl())
+  })
+  return services
+}
 
 export default (router: express$Router) => {
   // $FlowIgnore
@@ -27,13 +48,20 @@ export default (router: express$Router) => {
     let config: Config = requireWithoutCache(configPath, require).config
     let modJsonPath: ?string = process.env.MOD_JSON
     if (!modJsonPath) {
+      setDefaultServicesUrls(config)
       res.send(config)
       return
     }
     try {
       let jsonContent = fs.readFileSync(modJsonPath)
       let configMod = JSON.parse(jsonContent).config
-      Object.keys(configMod).forEach(key => { config[key] = configMod[key] })
+      Object.keys(configMod).forEach(key => {
+        if (key === 'servicesUrls') {
+          config[key] = modServicesUrls(config, configMod[key])
+        } else {
+          config[key] = configMod[key]
+        }
+      })
       res.send(config)
     } catch (err) {
       console.error(err)

+ 0 - 4
server/main.js

@@ -25,16 +25,12 @@ const app = express()
 
 const PORT = process.env.PORT || 3000
 const isDev = process.argv.find(a => a === '--dev')
-const CORIOLIS_URL = process.env.CORIOLIS_URL || '/'
-let CORIOLIS_LICENSING_BASE_URL = process.env.CORIOLIS_LICENSING_BASE_URL || ''
 
 // Write file to disk with process env variables, so that the client code can read
 if (!fs.existsSync('./dist')) {
   fs.mkdirSync('./dist')
 }
 fs.writeFileSync('./dist/env.js', `window.env = {
-  CORIOLIS_URL: '${CORIOLIS_URL}',
-  CORIOLIS_LICENSING_BASE_URL: '${(CORIOLIS_LICENSING_BASE_URL)}',
   ENV: '${isDev ? 'development' : 'production'}',
 }
 `)

+ 1 - 1
src/components/App.jsx

@@ -91,8 +91,8 @@ class App extends React.Component<{}, State> {
   async componentWillMount() {
     let startTime = new Date().getTime()
     observe(userStore, 'loggedUser', () => { this.refreshState(startTime) })
-    userStore.tokenLogin()
     await configLoader.load()
+    userStore.tokenLogin()
     this.setState({ isConfigReady: true })
   }
 

+ 0 - 16
src/constants.js

@@ -14,22 +14,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 // @flow
 
-export const coriolisUrl = (window.env && window.env.CORIOLIS_URL) || '/'
-
-let licenceUrl = (window.env && window.env.CORIOLIS_LICENSING_BASE_URL) || '/licensing'
-
-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`,
-  licence: licenceUrl,
-}
-
 export type NavigationMenuType = { label: string, value: string, hidden?: boolean, requiresAdmin?: boolean }
 export const navigationMenu: NavigationMenuType[] = [
   { label: 'Dashboard', value: 'dashboard' },

+ 2 - 2
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 '../constants'
+import configLoader from '../utils/Config'
 import notificationStore from '../stores/NotificationStore'
 
 class AssessmentSourceUtils {
@@ -62,7 +62,7 @@ class AssessmentSource {
     }
 
     return Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/${type}s`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/${type}s`,
       method: 'POST',
       data: payload,
     }).then(response => {

+ 16 - 18
src/sources/EndpointSource.js

@@ -22,8 +22,6 @@ import { SchemaParser } from './Schemas'
 import ObjectUtils from '../utils/ObjectUtils'
 import type { Endpoint, Validation, Storage } from '../types/Endpoint'
 
-import { servicesUrl } from '../constants'
-
 import configLoader from '../utils/Config'
 
 let getBarbicanPayload = data => {
@@ -42,7 +40,7 @@ let getBarbicanPayload = data => {
 class EndpointSource {
   async getEndpoints(skipLog?: boolean): Promise<Endpoint[]> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints`,
       skipLog,
     })
     let connections = []
@@ -58,7 +56,7 @@ class EndpointSource {
 
   async delete(endpoint: Endpoint): Promise<string> {
     await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpoint.id}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpoint.id}`,
       method: 'DELETE',
     })
     if (endpoint.connection_info && endpoint.connection_info.secret_ref) {
@@ -66,7 +64,7 @@ class EndpointSource {
       // $FlowIssue
       let uuid = endpoint.connection_info.secret_ref.substr(uuidIndex + 1)
       await Api.send({
-        url: `${servicesUrl.barbican}/v1/secrets/${uuid}`,
+        url: `${configLoader.config.servicesUrls.barbican}/v1/secrets/${uuid}`,
         method: 'DELETE',
       })
       return endpoint.id
@@ -83,7 +81,7 @@ class EndpointSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.barbican}/v1/secrets/${uuid || 'undefined'}/payload`,
+      url: `${configLoader.config.servicesUrls.barbican}/v1/secrets/${uuid || 'undefined'}/payload`,
       responseType: 'text',
       headers: { Accept: 'text/plain' },
     })
@@ -98,13 +96,13 @@ class EndpointSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.barbican}/v1/secrets/${uuid}`,
+      url: `${configLoader.config.servicesUrls.barbican}/v1/secrets/${uuid}`,
       headers: { Accept: 'application/json' },
     })
 
     if (response.data.status === 'ACTIVE') {
       let payload = await Api.send({
-        url: `${servicesUrl.barbican}/v1/secrets/${uuid}/payload`,
+        url: `${configLoader.config.servicesUrls.barbican}/v1/secrets/${uuid}/payload`,
         headers: { Accept: 'text/plain' },
       })
       return payload
@@ -122,7 +120,7 @@ class EndpointSource {
         return { ...endpoint }
       }
       let response = await Api.send({
-        url: `${servicesUrl.barbican}/v1/secrets/${uuid}/payload`,
+        url: `${configLoader.config.servicesUrls.barbican}/v1/secrets/${uuid}/payload`,
         responseType: 'text',
         headers: { Accept: 'text/plain' },
       })
@@ -133,7 +131,7 @@ class EndpointSource {
 
   async validate(endpoint: Endpoint): Promise<Validation> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpoint.id}/actions`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpoint.id}/actions`,
       method: 'POST',
       data: { 'validate-connection': null },
     })
@@ -152,12 +150,12 @@ class EndpointSource {
       let connectionInfo = {}
 
       await Api.send({
-        url: `${servicesUrl.barbican}/v1/secrets/${uuid}`,
+        url: `${configLoader.config.servicesUrls.barbican}/v1/secrets/${uuid}`,
         method: 'DELETE',
       })
 
       let response = await Api.send({
-        url: `${servicesUrl.barbican}/v1/secrets`,
+        url: `${configLoader.config.servicesUrls.barbican}/v1/secrets`,
         method: 'POST',
         data: getBarbicanPayload(ObjectUtils.skipField(parsedEndpoint.connection_info, 'secret_ref')),
       })
@@ -171,7 +169,7 @@ class EndpointSource {
         },
       }
       let putResponse = await Api.send({
-        url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpoint.id}`,
+        url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpoint.id}`,
         method: 'PUT',
         data: newPayload,
       })
@@ -195,7 +193,7 @@ class EndpointSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpoint.id}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpoint.id}`,
       method: 'PUT',
       data: { endpoint: parsedEndpoint },
     })
@@ -209,7 +207,7 @@ class EndpointSource {
     if (configLoader.config.useBarbicanSecrets
       && parsedEndpoint.connection_info && Object.keys(parsedEndpoint.connection_info).length > 0) {
       let response = await Api.send({
-        url: `${servicesUrl.barbican}/v1/secrets`,
+        url: `${configLoader.config.servicesUrls.barbican}/v1/secrets`,
         method: 'POST',
         data: getBarbicanPayload(ObjectUtils.skipField(parsedEndpoint.connection_info, 'secret_ref')),
       })
@@ -224,7 +222,7 @@ class EndpointSource {
         },
       }
       let postResponse = await Api.send({
-        url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints`,
+        url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints`,
         method: 'POST',
         data: newPayload,
       })
@@ -249,7 +247,7 @@ class EndpointSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints`,
       method: 'POST',
       data: {
         endpoint: {
@@ -263,7 +261,7 @@ class EndpointSource {
 
   async loadStorage(endpointId: string, data: any): Promise<Storage> {
     let env = btoa(JSON.stringify(data))
-    let response = await Api.get(`${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpointId}/storage?env=${env}`)
+    let response = await Api.get(`${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/storage?env=${env}`)
     return response.data.storage
   }
 }

+ 4 - 4
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 '../constants'
+import configLoader from '../utils/Config'
 
 import { InstanceInfoPlugin } from '../plugins/endpoint'
 
@@ -31,7 +31,7 @@ class InstanceSource {
     env?: any,
     cache?: boolean
   ): Promise<Instance[]> {
-    let url = `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances`
+    let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances`
     let queryParams: { [string]: string | number } = {}
 
     if (chunkSize !== Infinity) {
@@ -70,7 +70,7 @@ class InstanceSource {
 
   async loadInstances(endpointId: string, cache?: ?boolean): Promise<Instance[]> {
     Api.cancelRequests(endpointId)
-    let url = `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances`
+    let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances`
     let response = await Api.send({ url, cancelId: endpointId, cache })
     return response.data.instances
   }
@@ -85,7 +85,7 @@ class InstanceSource {
     cache?: ?boolean,
   }): Promise<{ instance: Instance, reqId: number }> {
     let { endpointId, instanceName, targetProvider, reqId, quietError, env, cache } = opts
-    let url = `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances/${btoa(instanceName)}`
+    let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances/${btoa(instanceName)}`
     if (env) {
       url += `?env=${btoa(JSON.stringify(env))}`
     }

+ 3 - 3
src/sources/LincenceSource.js

@@ -15,13 +15,13 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 // @flow
 import Api from '../utils/ApiCaller'
 
-import { servicesUrl } from '../constants'
+import configLoader from '../utils/Config'
 
 import type { Licence } from '../types/Licence'
 
 class LicenceSource {
   async loadLicenceInfo(skipLog?: ?boolean): Promise<Licence> {
-    let url = `${servicesUrl.licence}/licence-status`
+    let url = `${configLoader.config.servicesUrls.coriolisLicensing}/licence-status`
     let response = await Api.send({ url, quietError: true, skipLog })
     let root = response.data.licence_status
     return ({
@@ -36,7 +36,7 @@ class LicenceSource {
   }
 
   async addLicence(licence: string) {
-    let url = `${servicesUrl.licence}/licences`
+    let url = `${configLoader.config.servicesUrls.coriolisLicensing}/licences`
     await Api.send({
       url,
       method: 'POST',

+ 7 - 7
src/sources/MigrationSource.js

@@ -26,7 +26,7 @@ import type { Field } from '../types/Field'
 import type { NetworkMap } from '../types/Network'
 import type { Endpoint, StorageMap } from '../types/Endpoint'
 
-import { servicesUrl } from '../constants'
+import configLoader from '../utils/Config'
 
 class MigrationSourceUtils {
   static sortTaskUpdates(updates) {
@@ -54,7 +54,7 @@ class MigrationSourceUtils {
 class MigrationSource {
   async getMigrations(skipLog?: boolean): Promise<MainItem[]> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations/detail`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/detail`,
       skipLog,
     })
     let migrations = response.data.migrations
@@ -64,7 +64,7 @@ class MigrationSource {
 
   async getMigration(migrationId: string, skipLog?: boolean): Promise<MainItem> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations/${migrationId}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}`,
       skipLog,
     })
     let migration = response.data.migration
@@ -136,7 +136,7 @@ class MigrationSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
       method: 'POST',
       data: payload,
     })
@@ -149,7 +149,7 @@ class MigrationSource {
       data.cancel = { force: true }
     }
     await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations/${migrationId}/actions`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}/actions`,
       method: 'POST',
       data,
     })
@@ -158,7 +158,7 @@ class MigrationSource {
 
   async delete(migrationId: string): Promise<string> {
     await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations/${migrationId}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}`,
       method: 'DELETE',
     })
     return migrationId
@@ -179,7 +179,7 @@ class MigrationSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
       method: 'POST',
       data: payload,
     })

+ 2 - 2
src/sources/NetworkSource.js

@@ -17,14 +17,14 @@ 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 '../constants'
+import configLoader from '../utils/Config'
 
 class NetworkSource {
   async loadNetworks(enpointId: string, environment: ?{ [string]: mixed }, options?: {
     quietError?: boolean,
     cache?: boolean,
   }): Promise<Network[]> {
-    let url = `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${enpointId}/networks`
+    let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${enpointId}/networks`
     if (environment) {
       url = `${url}?env=${btoa(JSON.stringify(environment))}`
     }

+ 3 - 3
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 '../constants'
+import configLoader from '../utils/Config'
 import Api from '../utils/ApiCaller'
 import type { NotificationItemData, NotificationItem } from '../types/NotificationItem'
 
@@ -115,8 +115,8 @@ class DataUtils {
 class NotificationSource {
   async loadData(): Promise<NotificationItemData[]> {
     let [migrationsResponse, replicasResponse] = await Promise.all([
-      Api.send({ url: `${servicesUrl.coriolis}/${Api.projectId}/migrations`, skipLog: true, quietError: true }),
-      Api.send({ url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/detail`, skipLog: true, quietError: true }),
+      Api.send({ url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`, skipLog: true, quietError: true }),
+      Api.send({ url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/detail`, skipLog: true, quietError: true }),
     ])
 
     let migrations = migrationsResponse.data.migrations

+ 9 - 9
src/sources/ProjectSource.js

@@ -17,14 +17,14 @@ 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 '../constants'
+import configLoader from '../utils/Config'
 import type { Project, Role, RoleAssignment } from '../types/Project'
 import type { User } from '../types/User'
 
 class ProjectsSource {
   async getProjects(skipLog?: boolean): Promise<Project[]> {
     let response = await Api.send({
-      url: servicesUrl.projects,
+      url: `${configLoader.config.servicesUrls.keystone}/auth/projects`,
       skipLog,
     })
     if (response.data.projects) {
@@ -36,13 +36,13 @@ class ProjectsSource {
   }
 
   async getProjectDetails(projectId: string): Promise<Project> {
-    let response = await Api.get(`${coriolisUrl}identity/projects/${projectId}`)
+    let response = await Api.get(`${configLoader.config.servicesUrls.keystone}/projects/${projectId}`)
     return response.data.project
   }
 
   async getRoleAssignments(skipLog?: boolean): Promise<RoleAssignment[]> {
     let response = await Api.send({
-      url: `${coriolisUrl}identity/role_assignments?include_names`,
+      url: `${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`,
       skipLog,
     })
     let assignments: RoleAssignment[] = response.data.role_assignments
@@ -67,7 +67,7 @@ class ProjectsSource {
   async removeUser(projectId: string, userId: string, roleIds: string[]): Promise<void> {
     await Promise.all(roleIds.map(async id => {
       await Api.send({
-        url: `${coriolisUrl}identity/projects/${projectId}/users/${userId}/roles/${id}`,
+        url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}/users/${userId}/roles/${id}`,
         method: 'DELETE',
       })
     }))
@@ -75,7 +75,7 @@ class ProjectsSource {
 
   async assignUser(projectId: string, userId: string, roleId: string): Promise<void> {
     await Api.send({
-      url: `${coriolisUrl}identity/projects/${projectId}/users/${userId}/roles/${roleId}`,
+      url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}/users/${userId}/roles/${roleId}`,
       method: 'PUT',
     })
   }
@@ -98,7 +98,7 @@ class ProjectsSource {
     }
 
     let response = await Api.send({
-      url: `${coriolisUrl}identity/projects/${projectId}`,
+      url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}`,
       method: 'PATCH',
       data,
     })
@@ -107,7 +107,7 @@ class ProjectsSource {
 
   async delete(projectId: string): Promise<void> {
     await Api.send({
-      url: `${coriolisUrl}identity/projects/${projectId}`,
+      url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}`,
       method: 'DELETE',
     })
   }
@@ -123,7 +123,7 @@ class ProjectsSource {
       data.project.description = project.description
     }
     let response = await Api.send({
-      url: `${coriolisUrl}identity/projects/`,
+      url: `${configLoader.config.servicesUrls.keystone}/projects/`,
       method: 'POST',
       data,
     })

+ 6 - 5
src/sources/ProviderSource.js

@@ -15,7 +15,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 // @flow
 
 import Api from '../utils/ApiCaller'
-import { servicesUrl, providerTypes } from '../constants'
+import { providerTypes } from '../constants'
+import configLoader from '../utils/Config'
 import { SchemaParser } from './Schemas'
 import type { Field } from '../types/Field'
 import type { Providers } from '../types/Providers'
@@ -23,14 +24,14 @@ import type { OptionValues } from '../types/Endpoint'
 
 class ProviderSource {
   async getConnectionInfoSchema(providerName: string): Promise<Field[]> {
-    let response = await Api.get(`${servicesUrl.coriolis}/${Api.projectId}/providers/${providerName}/schemas/${providerTypes.CONNECTION}`)
+    let response = await Api.get(`${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/providers/${providerName}/schemas/${providerTypes.CONNECTION}`)
     let schema = response.data.schemas.connection_info_schema
     schema = SchemaParser.connectionSchemaToFields(providerName, schema)
     return schema
   }
 
   async loadProviders(): Promise<Providers> {
-    let response = await Api.get(`${servicesUrl.coriolis}/${Api.projectId}/providers`)
+    let response = await Api.get(`${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/providers`)
     return response.data.providers
   }
 
@@ -39,7 +40,7 @@ class ProviderSource {
 
     try {
       let response = await Api.send({
-        url: `${servicesUrl.coriolis}/${Api.projectId}/providers/${providerName}/schemas/${schemaTypeInt}`,
+        url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/providers/${providerName}/schemas/${schemaTypeInt}`,
         cache: useCache,
         quietError,
       })
@@ -65,7 +66,7 @@ class ProviderSource {
     let fieldName = optionsType === 'source' ? 'source_options' : 'destination_options'
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpointId}/${callName}${envString}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/${callName}${envString}`,
       cache,
       cancelId: endpointId,
       quietError,

+ 10 - 10
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 '../constants'
+import configLoader from '../utils/Config'
 import type { MainItem, UpdateData } from '../types/MainItem'
 import type { Execution } from '../types/Execution'
 import type { Endpoint } from '../types/Endpoint'
@@ -121,7 +121,7 @@ class ReplicaSourceUtils {
 class ReplicaSource {
   async getReplicas(skipLog?: boolean): Promise<MainItem[]> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/detail`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/detail`,
       skipLog,
     })
     let replicas = response.data.replicas
@@ -132,7 +132,7 @@ class ReplicaSource {
 
   async getReplicaExecutions(replicaId: string, skipLog?: boolean): Promise<Execution[]> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/detail`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/detail`,
       skipLog,
     })
     let executions = response.data.executions
@@ -143,7 +143,7 @@ class ReplicaSource {
 
   async getReplica(replicaId: string, skipLog?: boolean): Promise<MainItem> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}`,
       skipLog,
     })
     let replica = response.data.replica
@@ -160,7 +160,7 @@ class ReplicaSource {
       })
     }
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/executions`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/executions`,
       method: 'POST',
       data: payload,
     })
@@ -175,7 +175,7 @@ class ReplicaSource {
       data.cancel = { force: true }
     }
     await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/${executionId}/actions`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/${executionId}/actions`,
       method: 'POST',
       data,
     })
@@ -184,7 +184,7 @@ class ReplicaSource {
 
   async deleteExecution(replicaId: string, executionId: string): Promise<string> {
     await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/${executionId}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/${executionId}`,
       method: 'DELETE',
     })
     return replicaId
@@ -192,7 +192,7 @@ class ReplicaSource {
 
   async delete(replicaId: string): Promise<string> {
     await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}`,
       method: 'DELETE',
     })
     return replicaId
@@ -200,7 +200,7 @@ class ReplicaSource {
 
   async deleteDisks(replicaId: string): Promise<Execution> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/actions`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/actions`,
       method: 'POST',
       data: { 'delete-disks': null },
     })
@@ -228,7 +228,7 @@ class ReplicaSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replica.id}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replica.id}`,
       method: 'PUT',
       data: payload,
     })

+ 6 - 6
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 '../constants'
+import configLoader from '../utils/Config'
 import DateUtils from '../utils/DateUtils'
 import type { Schedule } from '../types/Schedule'
 
@@ -44,7 +44,7 @@ class ScheduleSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
       method: 'POST',
       data: payload,
     })
@@ -61,7 +61,7 @@ class ScheduleSource {
 
   async getSchedules(replicaId: string, opts?: { skipLog?: boolean }): Promise<Schedule[]> {
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
       skipLog: opts && opts.skipLog,
     })
     let schedules = [...response.data.schedules]
@@ -87,7 +87,7 @@ class ScheduleSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
       method: 'POST',
       data: payload,
     })
@@ -96,7 +96,7 @@ class ScheduleSource {
 
   async removeSchedule(replicaId: string, scheduleId: string): Promise<void> {
     await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules/${scheduleId}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules/${scheduleId}`,
       method: 'DELETE',
     })
   }
@@ -132,7 +132,7 @@ class ScheduleSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules/${scheduleId}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules/${scheduleId}`,
       method: 'PUT',
       data: payload,
     })

+ 13 - 14
src/sources/UserSource.js

@@ -17,7 +17,6 @@ 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 } from '../constants'
 import configLoader from '../utils/Config'
 import type { Credentials, User } from '../types/User'
 import type { Role, Project, RoleAssignment } from '../types/Project'
@@ -65,7 +64,7 @@ class UserSource {
     Api.setDefaultHeader('X-Auth-Token', null)
 
     let response = await Api.send({
-      url: servicesUrl.identity,
+      url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
       method: 'POST',
       data: auth,
     })
@@ -99,7 +98,7 @@ class UserSource {
 
     try {
       let response = await Api.send({
-        url: servicesUrl.identity,
+        url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
         method: 'POST',
         data: auth,
       })
@@ -128,7 +127,7 @@ class UserSource {
 
     try {
       let response = await Api.send({
-        url: servicesUrl.identity,
+        url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
         headers: { 'X-Subject-Token': token },
       })
       let data = UserModel.parseUserData(response.data)
@@ -160,7 +159,7 @@ class UserSource {
 
     try {
       await Api.send({
-        url: servicesUrl.identity,
+        url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
         method: 'DELETE',
         headers: { 'X-Subject-Token': token || '' },
       })
@@ -172,12 +171,12 @@ class UserSource {
   }
 
   async getUserInfo(userId: string): Promise<User> {
-    let response = await Api.get(`${servicesUrl.users}/${userId}`)
+    let response = await Api.get(`${configLoader.config.servicesUrls.keystone}/users/${userId}`)
     return response.data.user
   }
 
   async getAllUsers(skipLog?: boolean): Promise<User[]> {
-    let response = await Api.send({ url: `${servicesUrl.users}`, skipLog })
+    let response = await Api.send({ url: `${configLoader.config.servicesUrls.keystone}/users`, skipLog })
     let users: User[] = response.data.users
     await utils.waitFor(() => Boolean(configLoader.config))
     users = users.filter(u => !configLoader.config.hiddenUsers.find(hu => hu === u.name))
@@ -209,7 +208,7 @@ class UserSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.users}/${userId}`,
+      url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
       method: 'PATCH',
       data,
     })
@@ -251,7 +250,7 @@ class UserSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.users}`,
+      url: `${configLoader.config.servicesUrls.keystone}/users`,
       method: 'POST',
       data,
     })
@@ -272,7 +271,7 @@ class UserSource {
 
   async delete(userId: string): Promise<void> {
     await Api.send({
-      url: `${coriolisUrl}identity/users/${userId}`,
+      url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
       method: 'DELETE',
     })
   }
@@ -284,7 +283,7 @@ class UserSource {
 
   async assignUserToProjectWithRole(userId: string, projectId: string, roleId: string): Promise<void> {
     await Api.send({
-      url: `${coriolisUrl}identity/projects/${projectId}/users/${userId}/roles/${roleId}`,
+      url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}/users/${userId}/roles/${roleId}`,
       method: 'PUT',
     })
   }
@@ -304,14 +303,14 @@ class UserSource {
   }
 
   async getRoles(): Promise<Role[]> {
-    let response = await Api.get(`${coriolisUrl}identity/roles`)
+    let response = await Api.get(`${configLoader.config.servicesUrls.keystone}/roles`)
     let roles: Role[] = response.data.roles
     roles.sort((r1, r2) => r1.name.localeCompare(r2.name))
     return roles
   }
 
   async getProjects(userId: string): Promise<Project[]> {
-    let response = await Api.get(`${coriolisUrl}identity/role_assignments?include_names`)
+    let response = await Api.get(`${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`)
     let assignments: RoleAssignment[] = response.data.role_assignments
     let projects: $Shape<Project>[] = assignments
       .filter(a => a.user.id === userId)
@@ -324,7 +323,7 @@ class UserSource {
 
   async isAdmin(userId: string): Promise<boolean> {
     let response = await Api.send({
-      url: `${coriolisUrl}identity/role_assignments?include_names`,
+      url: `${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`,
       quietError: true,
     })
 

+ 2 - 2
src/sources/WizardSource.js

@@ -18,8 +18,8 @@ import Api from '../utils/ApiCaller'
 import notificationStore from '../stores/NotificationStore'
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
 
-import { servicesUrl } from '../constants'
 import DomUtils from '../utils/DomUtils'
+import configLoader from '../utils/Config'
 
 import type { WizardData } from '../types/WizardData'
 import type { StorageMap } from '../types/Endpoint'
@@ -64,7 +64,7 @@ class WizardSource {
     }
 
     let response = await Api.send({
-      url: `${servicesUrl.coriolis}/${Api.projectId}/${type}s`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/${type}s`,
       method: 'POST',
       data: payload,
     })

+ 8 - 10
src/stores/LogStore.js

@@ -21,7 +21,7 @@ import { saveAs } from 'file-saver'
 
 import type { Log } from '../types/Log'
 
-import { coriolisUrl, servicesUrl } from '../constants'
+import configLoader from '../utils/Config'
 
 import notificationStore from '../stores/NotificationStore'
 
@@ -29,8 +29,6 @@ import apiCaller from '../utils/ApiCaller'
 import DateUtils from '../utils/DateUtils'
 import DomUtils from '../utils/DomUtils'
 
-const BASE_URL = `${coriolisUrl}logs`
-
 const MAX_STREAM_LINES = 200
 class LogStore {
   @observable logs: Log[] = []
@@ -43,7 +41,7 @@ class LogStore {
       this.loading = true
     }
     try {
-      let response = await apiCaller.send({ url: BASE_URL })
+      let response = await apiCaller.send({ url: configLoader.config.servicesUrls.coriolisLogs })
       runInAction(() => {
         this.logs = response.data.logs
         this.loading = false
@@ -57,7 +55,7 @@ class LogStore {
 
   @action download(logName: string, startDate: ?Date, endDate: ?Date) {
     let token = cookie.get('token') || 'null'
-    let url = `${BASE_URL}/${logName}?auth_type=keystone&auth_token=${token}`
+    let url = `${configLoader.config.servicesUrls.coriolisLogs}/${logName}?auth_type=keystone&auth_token=${token}`
     if (startDate) {
       url += `&start_date=${DateUtils.toUnix(startDate)}`
     }
@@ -70,7 +68,7 @@ class LogStore {
 
   @action async downloadDiagnostics() {
     this.generatingDiagnostics = true
-    let baseUrl = `${servicesUrl.coriolis}/${apiCaller.projectId}`
+    let baseUrl = `${configLoader.config.servicesUrls.coriolis}/${apiCaller.projectId}`
     let [diagnosticsResp, replicasResp, migrationsResp] = await Promise.all([
       apiCaller.send({ url: `${baseUrl}/diagnostics` }),
       apiCaller.send({ url: `${baseUrl}/replicas/detail?show_deleted=true` }),
@@ -93,13 +91,13 @@ class LogStore {
     let { logName, severityLevel } = options
     let token = cookie.get('token') || 'null'
     let wsUrl
-    if (coriolisUrl === '/') {
-      wsUrl = `wss://${window.location.host}/`
+    if (configLoader.config.servicesUrls.coriolisLogStreamBaseUrl === '') {
+      wsUrl = `wss://${window.location.host}`
     } else {
-      wsUrl = coriolisUrl.replace('https', 'wss')
+      wsUrl = configLoader.config.servicesUrls.coriolisLogStreamBaseUrl.replace('https', 'wss')
     }
 
-    let url = `${wsUrl}log-stream?auth_type=keystone`
+    let url = `${wsUrl}/log-stream?auth_type=keystone`
     url += `&auth_token=${token}&severity=${severityLevel}`
 
     if (logName !== 'All Logs') {

+ 10 - 0
src/types/Config.js

@@ -12,6 +12,15 @@ type ExtraOption = {
   }[]
 }
 
+export type Services = {
+  keystone: string,
+  barbican: string,
+  coriolis: string,
+  coriolisLogs: string,
+  coriolisLogStreamBaseUrl: string,
+  coriolisLicensing: string,
+}
+
 export type Config = {
   disabledPages: string[],
   showUserDomainInput: boolean,
@@ -25,4 +34,5 @@ export type Config = {
   hiddenUsers: string[],
   passwordFields: string[],
   mainListItemsPerPage: number,
+  servicesUrls: Services,
 }

+ 9 - 1
ui-mod-sample.json

@@ -5,7 +5,15 @@
       "planning",
       "users",
       "projects"
-    ]
+    ],
+    "servicesUrls": {
+      "keystone": "{BASE_URL}/identity",
+      "barbican": "{BASE_URL}/barbican",
+      "coriolis": "{BASE_URL}/coriolis",
+      "coriolisLogs": "{BASE_URL}/logs",
+      "coriolisLogStreamBaseUrl": "{BASE_URL}",
+      "coriolisLicensing": "{BASE_URL}/licensing"
+    }
   },
   "providers": {
     "openstack": {