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] || {}
}
}
}