Bläddra i källkod

Merge pull request #519 from smiclea/services-modding

Add ability to set services URLs using MOD_JSON
Nashwan Azhari 6 år sedan
förälder
incheckning
1d030b060f

+ 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
@@ -141,7 +141,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,
     })
@@ -154,7 +154,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,
     })
@@ -163,7 +163,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
@@ -184,7 +184,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": {