This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch 4.15
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.15 by this push:
     new 1182051  UI: Add multiple management server support (#4885)
1182051 is described below

commit 1182051961faf2db13d9e4d7e01b5d3ba019136d
Author: Hoang Nguyen <[email protected]>
AuthorDate: Wed Aug 11 18:22:38 2021 +0700

    UI: Add multiple management server support (#4885)
    
    * add multiple management server support
    
    * display the server on the user menu
    
    * remove primary color in server icon
    
    * using `/client` from apiBase
    
    * add a setting that allows users to customize whether to use multiple 
servers or not
    
    * set default hidden the multiple server config
---
 ui/public/config.json                 |  8 ++++++
 ui/src/components/header/UserMenu.vue | 17 +++++++++++
 ui/src/components/widgets/Console.vue | 18 +++++++++++-
 ui/src/main.js                        |  7 ++++-
 ui/src/permission.js                  | 16 ++++++++++-
 ui/src/store/getters.js               |  1 +
 ui/src/store/modules/app.js           | 13 +++++++--
 ui/src/store/mutation-types.js        |  1 +
 ui/src/views/auth/Login.vue           | 54 ++++++++++++++++++++++++++++++++++-
 9 files changed, 129 insertions(+), 6 deletions(-)

diff --git a/ui/public/config.json b/ui/public/config.json
index 4c9be76..6c13408 100644
--- a/ui/public/config.json
+++ b/ui/public/config.json
@@ -1,5 +1,12 @@
 {
   "apiBase": "/client/api",
+  "servers": [
+    {
+      "name": "Local-Server",
+      "apiHost": "",
+      "apiBase": "/client/api"
+    }
+  ],
   "docBase": "http://docs.cloudstack.apache.org/en/latest";,
   "appTitle": "CloudStack",
   "footer": "Licensed under the <a href='http://www.apache.org/licenses/' 
target='_blank'>Apache License</a>, Version 2.0.",
@@ -48,5 +55,6 @@
   },
   "plugins": [],
   "basicZoneEnabled": true,
+  "multipleServer": false,
   "docHelpMappings": {}
 }
diff --git a/ui/src/components/header/UserMenu.vue 
b/ui/src/components/header/UserMenu.vue
index 33335c6..33f4bc3 100644
--- a/ui/src/components/header/UserMenu.vue
+++ b/ui/src/components/header/UserMenu.vue
@@ -20,6 +20,10 @@
 
     <translation-menu class="action"/>
     <header-notice class="action"/>
+    <label class="user-menu-server-info action" v-if="$config.multipleServer">
+      <a-icon slot="prefix" type="database" />
+      {{ server.name || server.apiBase || 'Local-Server' }}
+    </label>
     <a-dropdown>
       <span class="user-menu-dropdown action">
         <a-avatar class="user-menu-avatar avatar" size="small" 
:src="avatar()"/>
@@ -59,9 +63,11 @@
 </template>
 
 <script>
+import Vue from 'vue'
 import HeaderNotice from './HeaderNotice'
 import TranslationMenu from './TranslationMenu'
 import { mapActions, mapGetters } from 'vuex'
+import { SERVER_MANAGER } from '@/store/mutation-types'
 
 export default {
   name: 'UserMenu',
@@ -69,6 +75,11 @@ export default {
     TranslationMenu,
     HeaderNotice
   },
+  computed: {
+    server () {
+      return Vue.ls.get(SERVER_MANAGER) || this.$config.servers[0]
+    }
+  },
   methods: {
     ...mapActions(['Logout']),
     ...mapGetters(['nickname', 'avatar']),
@@ -108,5 +119,11 @@ export default {
     min-width: 12px;
     margin-right: 8px;
   }
+
+  &-server-info {
+    .anticon {
+      margin-right: 5px;
+    }
+  }
 }
 </style>
diff --git a/ui/src/components/widgets/Console.vue 
b/ui/src/components/widgets/Console.vue
index 3064d8f..0c65600 100644
--- a/ui/src/components/widgets/Console.vue
+++ b/ui/src/components/widgets/Console.vue
@@ -18,7 +18,7 @@
 <template>
   <a
     v-if="['vm', 'systemvm', 'router', 'ilbvm'].includes($route.meta.name) && 
'updateVirtualMachine' in $store.getters.apis"
-    :href="'/client/console?cmd=access&vm=' + resource.id"
+    :href="server + '/console?cmd=access&vm=' + resource.id"
     target="_blank">
     <a-button style="margin-left: 5px" shape="circle" type="dashed" 
:size="size" :disabled="['Stopped', 'Error', 
'Destroyed'].includes(resource.state)" >
       <a-icon type="code" />
@@ -27,6 +27,9 @@
 </template>
 
 <script>
+import Vue from 'vue'
+import { SERVER_MANAGER } from '@/store/mutation-types'
+
 export default {
   name: 'Console',
   props: {
@@ -38,6 +41,19 @@ export default {
       type: String,
       default: 'small'
     }
+  },
+  computed: {
+    server () {
+      if (!this.$config.multipleServer) {
+        return this.$config.apiBase.replace('/api', '')
+      }
+      const serverStorage = Vue.ls.get(SERVER_MANAGER)
+      const apiBase = serverStorage.apiBase.replace('/api', '')
+      if (!serverStorage.apiHost || serverStorage.apiHost === '/') {
+        return [location.origin, apiBase].join('')
+      }
+      return [serverStorage.apiHost, apiBase].join('')
+    }
   }
 }
 </script>
diff --git a/ui/src/main.js b/ui/src/main.js
index 9fbae3e..dce669d 100644
--- a/ui/src/main.js
+++ b/ui/src/main.js
@@ -37,7 +37,12 @@ Vue.use(toLocaleDatePlugin)
 
 fetch('config.json').then(response => response.json()).then(config => {
   Vue.prototype.$config = config
-  Vue.axios.defaults.baseURL = config.apiBase
+  let basUrl = config.apiBase
+  if (config.multipleServer) {
+    basUrl = (config.servers[0].apiHost || '') + config.servers[0].apiBase
+  }
+
+  Vue.axios.defaults.baseURL = basUrl
 
   loadLanguageAsync().then(() => {
     new Vue({
diff --git a/ui/src/permission.js b/ui/src/permission.js
index 8e97511..1b6c468 100644
--- a/ui/src/permission.js
+++ b/ui/src/permission.js
@@ -26,7 +26,7 @@ import 'nprogress/nprogress.css' // progress bar style
 import message from 'ant-design-vue/es/message'
 import notification from 'ant-design-vue/es/notification'
 import { setDocumentTitle } from '@/utils/domUtil'
-import { ACCESS_TOKEN, APIS } from '@/store/mutation-types'
+import { ACCESS_TOKEN, APIS, SERVER_MANAGER } from '@/store/mutation-types'
 
 NProgress.configure({ showSpinner: false }) // NProgress Configuration
 
@@ -39,6 +39,20 @@ router.beforeEach((to, from, next) => {
     const title = i18n.t(to.meta.title) + ' - ' + 
Vue.prototype.$config.appTitle
     setDocumentTitle(title)
   }
+
+  if (Vue.prototype.$config.multipleServer) {
+    const servers = Vue.prototype.$config.servers
+    const serverStorage = Vue.ls.get(SERVER_MANAGER)
+    let apiFullPath = ''
+    if (serverStorage) {
+      apiFullPath = (serverStorage.apiHost || '') + serverStorage.apiBase
+    }
+    const serverFilter = servers.filter(ser => (ser.apiHost || '') + 
ser.apiBase === apiFullPath)
+    const server = serverFilter[0] || servers[0]
+    Vue.axios.defaults.baseURL = (server.apiHost || '') + server.apiBase
+    store.dispatch('SetServer', server)
+  }
+
   const validLogin = Vue.ls.get(ACCESS_TOKEN) || Cookies.get('userid') || 
Cookies.get('userid', { path: '/client' })
   if (validLogin) {
     if (to.path === '/user/login') {
diff --git a/ui/src/store/getters.js b/ui/src/store/getters.js
index ed9ab40..f694362 100644
--- a/ui/src/store/getters.js
+++ b/ui/src/store/getters.js
@@ -36,6 +36,7 @@ const getters = {
   zones: state => state.user.zones,
   timezoneoffset: state => state.user.timezoneoffset,
   usebrowsertimezone: state => state.user.usebrowsertimezone,
+  server: state => state.app.server,
   domainStore: state => state.user.domainStore
 }
 
diff --git a/ui/src/store/modules/app.js b/ui/src/store/modules/app.js
index a03fc63..e512c42 100644
--- a/ui/src/store/modules/app.js
+++ b/ui/src/store/modules/app.js
@@ -27,7 +27,8 @@ import {
   DEFAULT_FIXED_HEADER_HIDDEN,
   DEFAULT_CONTENT_WIDTH_TYPE,
   DEFAULT_MULTI_TAB,
-  USE_BROWSER_TIMEZONE
+  USE_BROWSER_TIMEZONE,
+  SERVER_MANAGER
 } from '@/store/mutation-types'
 
 const app = {
@@ -44,7 +45,8 @@ const app = {
     color: null,
     inverted: true,
     multiTab: true,
-    metrics: false
+    metrics: false,
+    server: ''
   },
   mutations: {
     SET_SIDEBAR_TYPE: (state, type) => {
@@ -100,6 +102,10 @@ const app = {
     SET_USE_BROWSER_TIMEZONE: (state, bool) => {
       Vue.ls.set(USE_BROWSER_TIMEZONE, bool)
       state.usebrowsertimezone = bool
+    },
+    SET_SERVER: (state, server) => {
+      Vue.ls.set(SERVER_MANAGER, server)
+      state.server = server
     }
   },
   actions: {
@@ -147,6 +153,9 @@ const app = {
     },
     SetUseBrowserTimezone ({ commit }, bool) {
       commit('SET_USE_BROWSER_TIMEZONE', bool)
+    },
+    SetServer ({ commit }, server) {
+      commit('SET_SERVER', server)
     }
   }
 }
diff --git a/ui/src/store/mutation-types.js b/ui/src/store/mutation-types.js
index 948f938..7adc857 100644
--- a/ui/src/store/mutation-types.js
+++ b/ui/src/store/mutation-types.js
@@ -32,6 +32,7 @@ export const ZONES = 'ZONES'
 export const HEADER_NOTICES = 'HEADER_NOTICES'
 export const TIMEZONE_OFFSET = 'TIMEZONE_OFFSET'
 export const USE_BROWSER_TIMEZONE = 'USE_BROWSER_TIMEZONE'
+export const SERVER_MANAGER = 'SERVER_MANAGER'
 export const DOMAIN_STORE = 'DOMAIN_STORE'
 
 export const CONTENT_WIDTH_TYPE = {
diff --git a/ui/src/views/auth/Login.vue b/ui/src/views/auth/Login.vue
index 42c7137..db5ff67 100644
--- a/ui/src/views/auth/Login.vue
+++ b/ui/src/views/auth/Login.vue
@@ -35,6 +35,23 @@
           <a-icon type="safety" />
           {{ $t('label.login.portal') }}
         </span>
+        <a-form-item v-if="$config.multipleServer">
+          <a-select
+            size="large"
+            :placeholder="$t('server')"
+            v-decorator="[
+              'server',
+              {
+                initialValue: (server.apiHost || '') + server.apiBase
+              }
+            ]"
+            @change="onChangeServer">
+            <a-select-option v-for="item in $config.servers" 
:key="(item.apiHost || '') + item.apiBase">
+              <a-icon slot="prefix" type="database" :style="{ color: 
'rgba(0,0,0,.25)' }"></a-icon>
+              {{ item.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
         <a-form-item>
           <a-input
             size="large"
@@ -85,6 +102,23 @@
           <a-icon type="audit" />
           {{ $t('label.login.single.signon') }}
         </span>
+        <a-form-item v-if="$config.multipleServer">
+          <a-select
+            size="large"
+            :placeholder="$t('server')"
+            v-decorator="[
+              'server',
+              {
+                initialValue: (server.apiHost || '') + server.apiBase
+              }
+            ]"
+            @change="onChangeServer">
+            <a-select-option v-for="item in $config.servers" 
:key="(item.apiHost || '') + item.apiBase">
+              <a-icon slot="prefix" type="database" :style="{ color: 
'rgba(0,0,0,.25)' }"></a-icon>
+              {{ item.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
         <a-form-item>
           <a-select v-decorator="['idp', { initialValue: selectedIdp } ]">
             <a-select-option v-for="(idp, idx) in idps" :key="idx" 
:value="idp.id">
@@ -110,8 +144,11 @@
 </template>
 
 <script>
+import Vue from 'vue'
 import { api } from '@/api'
+import store from '@/store'
 import { mapActions } from 'vuex'
+import { SERVER_MANAGER } from '@/store/mutation-types'
 import TranslationMenu from '@/components/header/TranslationMenu'
 
 export default {
@@ -130,10 +167,15 @@ export default {
         time: 60,
         loginBtn: false,
         loginType: 0
-      }
+      },
+      server: ''
     }
   },
   created () {
+    if (this.$config.multipleServer) {
+      this.server = Vue.ls.get(SERVER_MANAGER) || this.$config.servers[0]
+    }
+
     this.fetchData()
   },
   methods: {
@@ -176,6 +218,11 @@ export default {
 
       validateFields(validateFieldsKey, { force: true }, (err, values) => {
         if (!err) {
+          if (this.$config.multipleServer) {
+            this.axios.defaults.baseURL = (this.server.apiHost || '') + 
this.server.apiBase
+            store.dispatch('SetServer', this.server)
+          }
+
           if (customActiveKey === 'cs') {
             const loginParams = { ...values }
             delete loginParams.username
@@ -216,6 +263,11 @@ export default {
       } else {
         this.$message.error(this.$t('message.login.failed'))
       }
+    },
+    onChangeServer (server) {
+      const servers = this.$config.servers || []
+      const serverFilter = servers.filter(ser => (ser.apiHost || '') + 
ser.apiBase === server)
+      this.server = serverFilter[0] || {}
     }
   }
 }

Reply via email to