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
This commit is contained in:
Hoang Nguyen 2021-08-11 18:22:38 +07:00 committed by GitHub
parent 1ccb42017f
commit 1182051961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 129 additions and 6 deletions

View File

@ -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": {}
}

View File

@ -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>

View File

@ -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>

View File

@ -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({

View File

@ -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') {

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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 = {

View File

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