UI: Upgrade to Vue3 library (#5151)

* add new vue 3 library & and 2.x (beta)

* edit config files, settings, utils, store,... corresponding to Vue 3

* edit layout and config to suit the new library

* fix header & side menu

* components, autogenview

* fix dashboard & compute

* compute: fix form errors

* storage: fix form & template by vue3

* networks: fix form & template by vue3

* image: fix form & template by vue3

* project: fix by vue3 library

* user: fix by vue3

* iam: fix by vue3

* fix zoneWizard & tooltip click event

* fix infra > physicalnetwork & visible modal

* fix infra by vue3 & antdv 2x

* fix offerings by vue3

* fix plugin by vue3

* fix form & action form

* update the ant-design latest version

* fix icon, style dark mode, menu

* fix unittest

* fix babel plugins not found

* add name,ref missing & callback i18n not found

* fix slot & info icon

* fix unit test

* fix tooltip label of form item

* fix lint errors

* using global app, globalProperties

* add focus directive & edit the position of ctrl+Enter

* upgrage Vue 3 version

* fix main UT

* fix build failed

* using `optionFilterProp="label"' & fix build fail

* fix UT with new code

* fix icons of undefined

* fix error run app

* fix selectbox options

* add vue version for clear storage

* fix template

* fix template of iprange form

* fix warning test UT

* fix conflit

* fix build failed

* fix error run app the first time after upgrade

* fix auto-complete & watch object/array

* fix error run application

* fix error build

* fix form, icon, template & locales

* fix conflit & form

* remove slot errors

* fix error build & test UT

* fix error template

* Add licenses for missing files

* add scroll to first errors

* add scroll to first errors

* fix select filter, tag event

* add shallowRef async component are missing

* fix css, upgrade vue-cropper version

* fix css

* fix vue 3 coding for new components

* Remove unused components

* fixes `this` not found in @/roles

* fix redirect after login again when session expired error

* fix openKeys menu & watch router

* fixes

* fix build failed

* fixes

* fixes ut

* fixes

* fixes eslint

* fixes

* fixes

* fixes css

* fix menu sidebar css

* fix some css icon, images

* fix build fail

* fixes

* fixes

* fixes

* fixes

* fix publicip resource

* fixes ut

* fixes

* fixes

* fixes layout mode

* fixes dropdown filter columns

* fixes dashboard & hidden setting for normal user

* fixes

* fixes layout

* fixes avatar

* fixes

* Add missing else

* Fix query in routable paths

Co-authored-by: davidjumani <dj.davidjumani1994@gmail.com>
This commit is contained in:
Hoang Nguyen 2022-03-09 19:47:09 +07:00 committed by GitHub
parent 7439581c77
commit d258da5524
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
297 changed files with 25717 additions and 24849 deletions

View File

@ -1,4 +1,7 @@
{
"plugins": [
["import", { "libraryName": "ant-design-vue", "libraryDirectory": "lib"}, "ant-design-vue"]
],
"env": {
"test": {
"plugins": ["require-context-hook"]

View File

@ -16,6 +16,7 @@
// under the License.
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
testURL: 'http://localhost/',
setupFiles: ['<rootDir>/tests/setup.js'],
moduleFileExtensions: [
@ -41,7 +42,7 @@ module.exports = {
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
transformIgnorePatterns: [
'<rootDir>/node_modules/(?!ant-design-vue|vue)'
'<rootDir>/node_modules/(?!ant-design-vue|vue|@babel/runtime|lodash-es|@ant-design)'
],
collectCoverage: true,
collectCoverageFrom: [

16330
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,8 @@
"@fortawesome/fontawesome-svg-core": "^1.2.34",
"@fortawesome/free-brands-svg-icons": "^5.15.2",
"@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/vue-fontawesome": "^2.0.2",
"ant-design-vue": "~1.7.3",
"@fortawesome/vue-fontawesome": "^3.0.0-4",
"ant-design-vue": "^2.2.3",
"antd-theme-webpack-plugin": "^1.3.9",
"axios": "^0.21.1",
"babel-plugin-require-context-hook": "^1.0.0",
@ -47,48 +47,52 @@
"js-cookie": "^2.2.1",
"lodash": "^4.17.15",
"md5": "^2.2.1",
"mitt": "^2.1.0",
"moment": "^2.26.0",
"npm-check-updates": "^6.0.1",
"nprogress": "^0.2.0",
"viser-vue": "^2.4.8",
"vue": "^2.6.12",
"vue": "^3.2.11",
"vue-clipboard2": "^0.3.1",
"vue-cropper": "0.5.6",
"vue-i18n": "^8.22.4",
"vue-cropper": "^1.0.2",
"vue-i18n": "^9.1.6",
"vue-loader": "^16.2.0",
"vue-ls": "^3.2.2",
"vue-router": "^3.5.1",
"vue-svg-component-runtime": "^1.0.1",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
"vue-router": "^4.0.0-0",
"vue-web-storage": "^6.1.0",
"vue3-clipboard": "^1.0.0",
"vuedraggable": "^4.0.3",
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@vue/cli": "^4.4.1",
"@vue/cli-plugin-babel": "^4.4.1",
"@vue/cli-plugin-eslint": "^4.4.1",
"@vue/cli-plugin-unit-jest": "^4.4.1",
"@vue/cli-service": "^4.4.1",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-unit-jest": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/test-utils": "^1.0.3",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^25.1.0",
"babel-plugin-import": "^1.13.0",
"eslint": "^6.8.0",
"eslint-plugin-html": "^6.0.2",
"@vue/test-utils": "^2.0.0-0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"babel-plugin-import": "^1.13.3",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.11.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^7.0.0",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"node-sass": "^4.13.1",
"node-sass": "^4.0.0",
"sass": "^1.35.1",
"sass-loader": "^8.0.2",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-cli-plugin-i18n": "^1.0.1",
"vue-svg-icon-loader": "^2.1.1",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.43.0"
"vue-jest": "^5.0.0-0",
"vue-svg-loader": "^0.17.0-beta.2",
"webpack": "^4.46.0"
},
"eslintConfig": {
"root": true,
@ -96,7 +100,7 @@
"node": true
},
"extends": [
"plugin:vue/strongly-recommended",
"plugin:vue/vue3-essential",
"@vue/standard"
],
"parserOptions": {
@ -149,7 +153,18 @@
"ignoreReadBeforeAssign": false
}
]
}
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"jest": true
}
}
]
},
"postcss": {
"plugins": {

View File

@ -19,6 +19,7 @@
"500": "assets/500.png"
},
"theme": {
"@layout-mode": "light",
"@logo-background-color": "#ffffff",
"@navigation-background-color": "#ffffff",
"@project-nav-background-color": "#001529",

View File

@ -860,6 +860,7 @@
"label.download.kubernetes.cluster.config": "Download Kubernetes cluster config",
"label.download.percent": "Download Percent",
"label.download.progress": "Download Progress",
"label.download.setting": "Download setting",
"label.download.state": "Download State",
"label.dpd": "Dead Peer Detection",
"label.drag.new.position": "Drag to new position",
@ -1958,7 +1959,6 @@
"label.save.and.continue": "Save and continue",
"label.save.changes": "Save changes",
"label.save.new.rule": "Save new Rule",
"label.save.setting": "Save setting",
"label.saving.processing": "Saving....",
"label.scale.up.policy": "SCALE UP POLICY",
"label.scale.vm": "Scale VM",
@ -2210,7 +2210,7 @@
"label.templatetype": "Template Type",
"label.tftp.dir": "TFTP Directory",
"label.tftpdir": "Tftp root directory",
"label.theme.alert": "Please save for the changes to take effect.",
"label.theme.alert": "The setting is only visible to the current browser. To apply the setting, please download the JSON file and replace its content in the `theme` section of the `config.json` file under the path: `/public/config.json`",
"label.theme.color": "Theme Color",
"label.theme.cyan": "Cyan",
"label.theme.dark": "Dark Style",

View File

@ -36,12 +36,7 @@ export default {
}
},
created () {
const userThemeSetting = this.$store.getters.themeSetting || {}
if (Object.keys(userThemeSetting).length === 0) {
window.less.modifyVars(this.$config.theme)
} else {
window.less.modifyVars(userThemeSetting)
}
window.less.modifyVars(this.$config.theme)
console.log('config and theme applied')
}
}

View File

@ -39,18 +39,6 @@
inkscape:window-y="26"
inkscape:window-maximized="0"
inkscape:current-layer="svg38" />
<defs
id="defs4">
<style
type="text/css"
id="style2">
<![CDATA[
.fil0 {fill:#424B53;fill-rule:nonzero}
.fil2 {fill:#969C98;fill-rule:nonzero}
.fil1 {fill:#BFD43F;fill-rule:nonzero}
]]>
</style>
</defs>
<g
id="g4573"
transform="translate(-686.25631,82.403259)"><polygon

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -13,8 +13,6 @@
ns2:viewOrigin="262 450"
ns2:rulerOrigin="0 0"
ns2:pageBounds="0 792 612 0"
width="56"
height="56.000069"
viewBox="0 0 55.999999 56.000069"
overflow="visible"
enable-background="new 0 0 87.041 108.445"

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -41,15 +41,6 @@
inkscape:current-layer="layer1" />
<defs
id="defs4">
<style
type="text/css"
id="style2">
<![CDATA[
.fil0 {fill:#424B53;fill-rule:nonzero}
.fil2 {fill:#969C98;fill-rule:nonzero}
.fil1 {fill:#BFD43F;fill-rule:nonzero}
]]>
</style>
</defs>
<g

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -20,7 +20,7 @@
<a-col>
<a-row :md="24" :lg="layout === 'horizontal' ? 12 : 24">
<a-checkbox
v-decorator="[checkBoxDecorator, {}]"
v-model:value="fields[checkBoxDecorator]"
:checked="checked"
@change="handleCheckChange">
{{ checkBoxLabel }}
@ -31,9 +31,7 @@
:label="inputLabel"
v-if="reversed !== checked">
<a-input
v-decorator="[inputDecorator, {
initialValue: defaultInputValue
}]"
v-model:value="fields[inputDecorator]"
@change="val => handleInputChangeTimed(val)" />
</a-form-item>
</a-row>
@ -91,11 +89,14 @@ export default {
return {
checked: false,
inputValue: '',
inputUpdateTimer: null
inputUpdateTimer: null,
fields: {}
}
},
created () {
this.checked = this.defaultCheckBoxValue
this.fields[this.checkBoxDecorator] = this.checked
this.fields[this.inputDecorator] = this.defaultInputValue
},
methods: {
handleCheckChange (e) {

View File

@ -18,7 +18,7 @@
<template>
<div style="width: 100%">
<a-row :gutter="6">
<a-col :md="24" :lg="layout === 'horizontal' ? 12 : 24">
<a-col :md="24" :lg="layout === 'horizontal' ? 10 : 24">
<a-checkbox
:checked="checked"
@change="handleCheckChange">
@ -27,16 +27,16 @@
</a-col>
<a-col :md="24" :lg="layout === 'horizontal' ? 12 : 24">
<a-form-item
v-if="reversed != checked"
v-if="reversed !== checked"
:label="selectLabel">
<a-select
v-model="selectedOption"
v-model:value="selectedOption"
showSearch
optionFilterProp="children"
optionFilterProp="label"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="val => { this.handleSelectChange(val) }">
@change="val => { handleSelectChange(val) }">
<a-select-option
v-for="(opt) in selectSource"
:key="opt.id"
@ -138,3 +138,9 @@ export default {
}
}
</script>
<style lang="less" scoped>
.ant-list-split .ant-list-item div {
width: 100%;
}
</style>

View File

@ -23,22 +23,36 @@
:autoAdjustOverflow="true"
:arrowPointAtCenter="true"
overlayClassName="header-notice-popover">
<template slot="content">
<template #content>
<a-spin :spinning="loading">
<a-list style="min-width: 200px; max-width: 300px">
<a-list-item>
<a-list-item-meta :title="$t('label.notifications')">
<a-avatar :style="{backgroundColor: '#6887d0', verticalAlign: 'middle'}" icon="notification" slot="avatar"/>
<a-button size="small" slot="description" @click="clearJobs">{{ $t('label.clear.list') }}</a-button>
<template #avatar>
<a-avatar :style="{ backgroundColor: '#6887d0', verticalAlign: 'middle' }">
<template #icon><notification-outlined /></template>
</a-avatar>
</template>
<template #description><a-button size="small" @click="clearJobs">{{ $t('label.clear.list') }}</a-button></template>
</a-list-item-meta>
</a-list-item>
<a-list-item v-for="(notice, index) in notices" :key="index">
<div slot="title"> {{ notice.path }} </div>
<template #title>{{ notice.path }} </template>
<a-list-item-meta :title="notice.title">
<a-avatar :style="notificationAvatar[notice.status].style" :icon="notificationAvatar[notice.status].icon" slot="avatar"/>
<span slot="description" v-if="getResourceName(notice.description, 'name') && notice.path"><router-link :to="{ path: notice.path}"> {{ getResourceName(notice.description, "name") + ' - ' }}</router-link></span>
<span slot="description" v-if="getResourceName(notice.description, 'name') && notice.path"> {{ getResourceName(notice.description, "msg") }}</span>
<span slot="description" v-else> {{ notice.description }} </span>
<template #avatar>
<a-avatar :style="notificationAvatar[notice.status].style">
<template #icon>
<render-icon :icon="notificationAvatar[notice.status].icon" />
</template>
</a-avatar>
</template>
<template #description>
<span v-if="getResourceName(notice.description, 'name') && notice.path">
<router-link :to="{ path: notice.path}"> {{ getResourceName(notice.description, "name") + ' - ' }}</router-link>
</span>
<span v-if="getResourceName(notice.description, 'name') && notice.path"> {{ getResourceName(notice.description, "msg") }}</span>
<span v-else>{{ notice.description }}</span>
</template>
</a-list-item-meta>
</a-list-item>
</a-list>
@ -46,7 +60,7 @@
</template>
<span @click="showNotifications" class="header-notice-opener">
<a-badge :count="notices.length">
<a-icon class="header-notice-icon" type="bell" />
<bell-outlined class="header-notice-icon" />
</a-badge>
</span>
</a-popover>
@ -54,9 +68,11 @@
<script>
import store from '@/store'
import RenderIcon from '@/utils/renderIcon'
export default {
name: 'HeaderNotice',
components: { RenderIcon },
data () {
return {
loading: false,
@ -64,9 +80,9 @@ export default {
notices: [],
poller: null,
notificationAvatar: {
done: { icon: 'check-circle', style: 'backgroundColor:#87d068' },
progress: { icon: 'loading', style: 'backgroundColor:#ffbf00' },
failed: { icon: 'close-circle', style: 'backgroundColor:#f56a00' }
done: { icon: 'check-circle-outlined', style: { backgroundColor: '#87d068' } },
progress: { icon: 'loading-outlined', style: { backgroundColor: '#ffbf00' } },
failed: { icon: 'close-circle-outlined', style: { backgroundColor: '#f56a00' } }
}
}
},

View File

@ -28,20 +28,22 @@
@focus="fetchData"
showSearch>
<a-tooltip placement="bottom" slot="suffixIcon">
<template slot="title">
<span>{{ $t('label.projects') }}</span>
</template>
<span style="font-size: 20px; color: #999; margin-top: -5px">
<a-icon v-if="!loading" type="project" />
<a-icon v-else type="loading" />
</span>
</a-tooltip>
<template #suffixIcon>
<a-tooltip placement="bottom">
<template #title>
<span>{{ $t('label.projects') }}</span>
</template>
<span class="custom-suffix-icon">
<ProjectOutlined v-if="!loading" />
<LoadingOutlined v-else />
</span>
</a-tooltip>
</template>
<a-select-option v-for="(project, index) in projects" :key="index" :label="project.displaytext || project.name">
<span>
<resource-icon v-if="project.icon && project.icon.base64image" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
<a-icon v-else style="margin-right: 5px" type="project" />
<project-outlined v-else style="margin-right: 5px" />
{{ project.displaytext || project.name }}
</span>
</a-select-option>
@ -108,7 +110,7 @@ export default {
}
},
filterProject (input, option) {
return option.componentOptions.propsData.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
}
}
@ -127,4 +129,12 @@ export default {
padding-right: 5px;
}
}
.custom-suffix-icon {
font-size: 20px;
position: absolute;
top: 0;
right: 1px;
margin-top: -3px;
}
</style>

View File

@ -23,22 +23,24 @@
:defaultValue="currentAccount"
:value="currentAccount"
showSearch
optionFilterProp="children"
optionFilterProp="label"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="changeAccount"
@focus="fetchData" >
<a-tooltip placement="bottom" slot="suffixIcon">
<template slot="title">
<span>{{ $t('label.domain') }}</span>
</template>
<span style="font-size: 20px; color: #999; margin-top: -5px">
<a-icon v-if="!loading" type="block" />
<a-icon v-else type="loading" />
</span>
</a-tooltip>
<template #suffixIcon>
<a-tooltip placement="bottom">
<template #title>
<span>{{ $t('label.domain') }}</span>
</template>
<span class="custom-suffix-icon">
<BlockOutlined v-if="!loading" />
<LoadingOutlined v-else />
</span>
</a-tooltip>
</template>
<a-select-option v-for="(account, index) in samlAccounts" :key="index">
{{ `${account.accountName} (${account.domainName})` }}
@ -126,4 +128,12 @@ export default {
padding-right: 5px;
}
}
.custom-suffix-icon {
font-size: 20px;
position: absolute;
top: 0;
right: 0;
margin-top: -8px;
}
</style>

View File

@ -18,36 +18,36 @@
<template>
<a-dropdown>
<span class="action ant-dropdown-link translation-menu">
<font-awesome-icon :icon="['fas', 'language']" size="lg" />
<TranslationOutlined />
</span>
<a-menu
slot="overlay"
:selectedKeys="[language]"
@click="onClick">
<a-menu-item key="en" value="enUS">English</a-menu-item>
<a-menu-item key="hi" value="hi">ि</a-menu-item>
<a-menu-item key="ja_JP" value="jpJP">日本語</a-menu-item>
<a-menu-item key="ko_KR" value="koKR">한국어</a-menu-item>
<a-menu-item key="zh_CN" value="zhCN">简体中文</a-menu-item>
<a-menu-item key="ar" value="arEG">Arabic</a-menu-item>
<a-menu-item key="ca" value="caES">Catalan</a-menu-item>
<a-menu-item key="de_DE" value="deDE">Deutsch</a-menu-item>
<a-menu-item key="es" value="esES">Español</a-menu-item>
<a-menu-item key="fr_FR" value="frFR">Français</a-menu-item>
<a-menu-item key="it_IT" value="itIT">Italiano</a-menu-item>
<a-menu-item key="hu" value="huHU">Magyar</a-menu-item>
<a-menu-item key="nl_NL" value="nlNL">Nederlands</a-menu-item>
<a-menu-item key="nb_NO" value="nbNO">Norsk</a-menu-item>
<a-menu-item key="pl" value="plPL">Polish</a-menu-item>
<a-menu-item key="pt_BR" value="ptBR">Português brasileiro</a-menu-item>
<a-menu-item key="ru_RU" value="ruRU">Русский</a-menu-item>
<a-menu-item key="el_GR" value="elGR">Ελληνικά</a-menu-item>
</a-menu>
<template #overlay>
<a-menu
:selectedKeys="[language]"
@click="onClick">
<a-menu-item key="en" value="enUS">English</a-menu-item>
<a-menu-item key="hi" value="hi">ि</a-menu-item>
<a-menu-item key="ja_JP" value="jpJP">日本語</a-menu-item>
<a-menu-item key="ko_KR" value="koKR">한국어</a-menu-item>
<a-menu-item key="zh_CN" value="zhCN">简体中文</a-menu-item>
<a-menu-item key="ar" value="arEG">Arabic</a-menu-item>
<a-menu-item key="ca" value="caES">Catalan</a-menu-item>
<a-menu-item key="de_DE" value="deDE">Deutsch</a-menu-item>
<a-menu-item key="es" value="esES">Español</a-menu-item>
<a-menu-item key="fr_FR" value="frFR">Français</a-menu-item>
<a-menu-item key="it_IT" value="itIT">Italiano</a-menu-item>
<a-menu-item key="hu" value="huHU">Magyar</a-menu-item>
<a-menu-item key="nl_NL" value="nlNL">Nederlands</a-menu-item>
<a-menu-item key="nb_NO" value="nbNO">Norsk</a-menu-item>
<a-menu-item key="pl" value="plPL">Polish</a-menu-item>
<a-menu-item key="pt_BR" value="ptBR">Português brasileiro</a-menu-item>
<a-menu-item key="ru_RU" value="ruRU">Русский</a-menu-item>
<a-menu-item key="el_GR" value="elGR">Ελληνικά</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
<script>
import Vue from 'vue'
import moment from 'moment'
import 'moment/locale/zh-cn'
import { loadLanguageAsync } from '@/locales'
@ -62,7 +62,7 @@ export default {
}
},
mounted () {
this.language = Vue.ls.get('LOCALE') || 'en'
this.language = this.$localStorage.get('LOCALE') || 'en'
this.setLocale(this.language)
},
methods: {
@ -75,11 +75,10 @@ export default {
this.setLocale(localeValue)
},
setLocale (localeValue) {
this.$locale = localeValue
this.$i18n.locale = localeValue
this.language = localeValue
moment.locale(localeValue)
Vue.ls.set('LOCALE', localeValue)
this.$localStorage.set('LOCALE', localeValue)
loadLanguageAsync(localeValue)
}
}

View File

@ -21,7 +21,7 @@
<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" />
<database-outlined />
{{ server.name || server.apiBase || 'Local-Server' }}
</label>
<a-dropdown>
@ -32,44 +32,46 @@
<a-avatar v-else-if="userInitials" class="user-menu-avatar avatar" size="small" :style="{ backgroundColor: '#1890ff', color: 'white' }">
{{ userInitials }}
</a-avatar>
<a-avatar v-else class="user-menu-avatar avatar" size="small" icon="user" :style="{ backgroundColor: '#1890ff', color: 'white' }" />
<a-avatar v-else class="user-menu-avatar avatar" size="small" :style="{ backgroundColor: '#1890ff', color: 'white' }">
<template #icon><user-outlined /></template>
</a-avatar>
<span>{{ nickname() }}</span>
</span>
<a-menu slot="overlay" class="user-menu-wrapper">
<a-menu-item class="user-menu-item" key="0">
<template #overlay>
<a-menu class="user-menu-wrapper">
<router-link :to="{ path: '/accountuser/' + $store.getters.userInfo.id }">
<a-icon class="user-menu-item-icon" type="user"/>
<span class="user-menu-item-name">{{ $t('label.profilename') }}</span>
<a-menu-item class="user-menu-item" key="0">
<UserOutlined class="user-menu-item-icon" />
<span class="user-menu-item-name">{{ $t('label.profilename') }}</span>
</a-menu-item>
</router-link>
</a-menu-item>
<a-menu-item class="user-menu-item" key="1">
<a @click="toggleUseBrowserTimezone">
<a-icon class="user-menu-item-icon" type="clock-circle"/>
<span class="user-menu-item-name" style="margin-right: 5px">{{ $t('label.use.local.timezone') }}</span>
<a-switch
:checked="$store.getters.usebrowsertimezone" />
<a-menu-item class="user-menu-item" key="1">
<ClockCircleOutlined class="user-menu-item-icon" />
<span class="user-menu-item-name" style="margin-right: 5px">{{ $t('label.use.local.timezone') }}</span>
<a-switch :checked="$store.getters.usebrowsertimezone" />
</a-menu-item>
</a>
</a-menu-item>
<a-menu-item class="user-menu-item" key="2" disabled>
<a :href="$config.docBase" target="_blank">
<a-icon class="user-menu-item-icon" type="question-circle-o"></a-icon>
<span class="user-menu-item-name">{{ $t('label.help') }}</span>
<a-menu-item class="user-menu-item" key="2">
<QuestionCircleOutlined class="user-menu-item-icon" />
<span class="user-menu-item-name">{{ $t('label.help') }}</span>
</a-menu-item>
</a>
</a-menu-item>
<a-menu-divider/>
<a-menu-item class="user-menu-item" key="3">
<a-menu-divider/>
<a href="javascript:;" @click="handleLogout">
<a-icon class="user-menu-item-icon" type="logout"/>
<span class="user-menu-item-name">{{ $t('label.logout') }}</span>
<a-menu-item class="user-menu-item" key="3">
<LogoutOutlined class="user-menu-item-icon" />
<span class="user-menu-item-name">{{ $t('label.logout') }}</span>
</a-menu-item>
</a>
</a-menu-item>
</a-menu>
</a-menu>
</template>
</a-dropdown>
</div>
</template>
<script>
import Vue from 'vue'
import { api } from '@/api'
import HeaderNotice from './HeaderNotice'
import TranslationMenu from './TranslationMenu'
@ -96,7 +98,7 @@ export default {
this.userInitials = (this.$store.getters.userInfo.firstname.toUpperCase().charAt(0) || '') +
(this.$store.getters.userInfo.lastname.toUpperCase().charAt(0) || '')
this.getIcon()
eventBus.$on('refresh-header', () => {
eventBus.on('refresh-header', () => {
this.getIcon()
})
this.$store.watch(
@ -113,7 +115,7 @@ export default {
},
computed: {
server () {
return Vue.ls.get(SERVER_MANAGER) || this.$config.servers[0]
return this.$localStorage.get(SERVER_MANAGER) || this.$config.servers[0]
}
},
methods: {

View File

@ -0,0 +1,171 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<a-menu
:mode="mode"
:theme="theme"
:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
@click="selectMenu"
@openChange="onOpenChange"
>
<template v-for="(item, index) in menuData" :key="index">
<a-sub-menu v-if="item.children && !item.hideChildrenInMenu" :key="item.path">
<template #title>
<span>
<render-icon
v-if="item.meta.icon && typeof (item.meta.icon) === 'string'"
:icon="item.meta.icon"
@click="() => { handleClickParentMenu(item) }" />
<span @click="() => { handleClickParentMenu(item) }">{{ $t(item.meta.title) }}</span>
</span>
</template>
<template v-for="children in item.children" :key="children.path">
<a-menu-item :key="children.path" v-if="!children.hidden">
<router-link :to="{ name: children.name, target: children.meta.target || null }">
<render-icon
v-if="children.meta.icon && typeof (children.meta.icon) === 'string'"
:icon="children.meta.icon" />
<render-icon v-else :svgIcon="children.meta.icon" />
<span>{{ $t(children.meta.title) }}</span>
</router-link>
</a-menu-item>
</template>
</a-sub-menu>
<a-menu-item v-else :key="item.path">
<router-link :to="{ name: item.name, target: item.meta.target || null }">
<render-icon
v-if="item.meta.icon && typeof (item.meta.icon) === 'string'"
:icon="item.meta.icon"
@click="() => { handleClickParentMenu(item) }" />
<span>{{ $t(item.meta.title) }}</span>
</router-link>
</a-menu-item>
</template>
</a-menu>
</template>
<script>
import RenderIcon from '@/utils/renderIcon'
export default {
name: 'SMenu',
components: { RenderIcon },
props: {
menu: {
type: Array,
required: true
},
theme: {
type: String,
required: false,
default: 'dark'
},
mode: {
type: String,
required: false,
default: 'inline'
},
collapsed: {
type: Boolean,
required: false,
default: false
}
},
data () {
return {
openKeys: [],
selectedKeys: [],
cachedOpenKeys: [],
cachedPath: null
}
},
computed: {
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
return keys
},
menuData () {
return this.menu.filter(item => !item.hidden)
}
},
created () {
this.updateMenu()
},
watch: {
collapsed (val) {
this.openKeys = val ? [] : this.cachedOpenKeys
},
'$route.fullPath': function () {
this.updateMenu()
}
},
methods: {
selectMenu (obj) {
this.selectedKeys = [obj.key]
},
onOpenChange (openKeys) {
const latestOpenKey = openKeys.find(key => this.openKeys.indexOf(key) === -1)
if (this.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
},
updateMenu () {
const routes = this.$route.matched.concat()
if (routes.length >= 4 && this.$route.meta.hidden) {
routes.pop()
this.selectedKeys = [routes[2].path]
} else {
this.selectedKeys = [routes.pop().path]
}
const openKeys = []
if (this.mode === 'inline') {
routes.forEach(item => {
openKeys.push(item.path)
})
}
this.cachedPath = this.selectedKeys[0]
this.cachedOpenKeys = openKeys
if (!this.collapsed) {
this.openKeys = openKeys
}
},
handleClickParentMenu (menuItem) {
if (this.cachedPath === menuItem.redirect) {
return
}
if (menuItem.redirect) {
this.cachedPath = menuItem.redirect
setTimeout(() => this.$router.push({ path: menuItem.path }))
}
}
}
}
</script>
<style>
.sider .ant-menu-vertical .ant-menu-item {
margin-right: 0;
}
</style>

View File

@ -19,12 +19,12 @@
<a-layout-sider
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
width="256px"
:collapsible="collapsible"
v-model="collapsed"
collapsible
v-model:collapsed="isCollapsed"
:trigger="null">
<logo />
<s-menu
:collapsed="collapsed"
:collapsed="isCollapsed"
:menu="menus"
:theme="theme"
:mode="mode"
@ -34,14 +34,13 @@
</template>
<script>
import ALayoutSider from 'ant-design-vue/es/layout/Sider'
import Logo from '../header/Logo'
import SMenu from './index'
import { mixin, mixinDevice } from '@/utils/mixin.js'
export default {
name: 'SideMenu',
components: { ALayoutSider, Logo, SMenu },
components: { Logo, SMenu },
mixins: [mixin, mixinDevice],
props: {
mode: {
@ -69,6 +68,11 @@ export default {
required: true
}
},
computed: {
isCollapsed () {
return this.collapsed
}
},
methods: {
onSelect (obj) {
this.$emit('menuSelect', obj)
@ -84,23 +88,23 @@ export default {
z-index: 10;
height: auto;
/deep/ .ant-layout-sider-children {
:deep(.ant-layout-sider-children) {
overflow-y: hidden;
&:hover {
overflow-y: auto;
}
}
/deep/ .ant-menu-vertical .ant-menu-item {
:deep(.ant-menu-vertical) .ant-menu-item {
margin-top: 0px;
margin-bottom: 0px;
}
/deep/ .ant-menu-inline .ant-menu-item:not(:last-child) {
:deep(.ant-menu-inline) .ant-menu-item:not(:last-child) {
margin-bottom: 0px;
}
/deep/ .ant-menu-inline .ant-menu-item {
:deep(.ant-menu-inline) .ant-menu-item {
margin-top: 0px;
}

View File

@ -15,5 +15,5 @@
// specific language governing permissions and limitations
// under the License.
import SMenu from './menu'
import SMenu from './SMenu.vue'
export default SMenu

View File

@ -1,207 +0,0 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'
const { Item, SubMenu } = Menu
export default {
name: 'SMenu',
props: {
menu: {
type: Array,
required: true
},
theme: {
type: String,
required: false,
default: 'dark'
},
mode: {
type: String,
required: false,
default: 'inline'
},
collapsed: {
type: Boolean,
required: false,
default: false
}
},
data () {
return {
openKeys: [],
selectedKeys: [],
cachedOpenKeys: [],
cachedPath: null
}
},
computed: {
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
return keys
}
},
created () {
this.updateMenu()
},
watch: {
collapsed (val) {
if (val) {
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
} else {
this.openKeys = this.cachedOpenKeys
}
},
$route: function () {
this.updateMenu()
}
},
methods: {
// select menu item
onOpenChange (openKeys) {
if (this.mode === 'horizontal') {
this.openKeys = openKeys
return
}
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
},
updateMenu () {
const routes = this.$route.matched.concat()
if (routes.length >= 4 && this.$route.meta.hidden) {
routes.pop()
this.selectedKeys = [routes[2].path]
} else {
this.selectedKeys = [routes.pop().path]
}
const openKeys = []
if (this.mode === 'inline') {
routes.forEach(item => {
openKeys.push(item.path)
})
}
this.cachedPath = this.selectedKeys[0]
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
},
// render
renderItem (menu) {
if (!menu.hidden) {
return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
}
return null
},
renderMenuItem (menu) {
const target = menu.meta.target || null
const props = {
to: { name: menu.name },
target: target
}
return (
<Item {...{ key: menu.path }}>
<router-link {...{ props }}>
{this.renderIcon(menu.meta.icon, menu)}
<span>{this.$t(menu.meta.title)}</span>
</router-link>
</Item>
)
},
renderSubMenu (menu) {
const itemArr = []
const on = {
click: () => {
this.handleClickParentMenu(menu)
}
}
if (!menu.hideChildrenInMenu) {
menu.children.forEach(item => itemArr.push(this.renderItem(item)))
}
return (
<SubMenu {...{ key: menu.path }}>
<span slot="title">
{this.renderIcon(menu.meta.icon, menu)}
<span {...{ on: on }}>{this.$t(menu.meta.title)}</span>
</span>
{itemArr}
</SubMenu>
)
},
renderIcon (icon, menuItem) {
if (icon === 'none' || icon === undefined) {
return null
}
const props = {}
const on = {
click: () => {
this.handleClickParentMenu(menuItem)
}
}
typeof (icon) === 'object' ? props.component = icon : props.type = icon
return (
<Icon {... { props, on } } />
)
},
handleClickParentMenu (menuItem) {
if (this.cachedPath === menuItem.redirect) {
return
}
if (menuItem.redirect) {
this.cachedPath = menuItem.redirect
setTimeout(() => this.$router.push({ path: menuItem.path }))
}
}
},
render () {
const { mode, theme, menu } = this
const props = {
mode: mode,
theme: theme,
openKeys: this.openKeys
}
const on = {
select: obj => {
this.selectedKeys = obj.selectedKeys
this.$emit('select', obj)
},
openChange: this.onOpenChange
}
const menuTree = menu.map(item => {
if (item.hidden) {
return null
}
return this.renderItem(item)
})
// {...{ props, on: on }}
return (
<Menu vModel={this.selectedKeys} {...{ props, on: on }}>
{menuTree}
</Menu>
)
}
}

View File

@ -150,11 +150,11 @@ export default {
}
},
watch: {
$route: function (newVal) {
this.activeKey = newVal.fullPath
if (this.fullPathList.indexOf(newVal.fullPath) < 0) {
this.fullPathList.push(newVal.fullPath)
this.pages.push(newVal)
'$route.fullPath': function (fullPath) {
this.activeKey = fullPath
if (this.fullPathList.indexOf(fullPath) < 0) {
this.fullPathList.push(fullPath)
this.pages.push(this.$route)
}
},
activeKey: function (newPathKey) {

View File

@ -24,7 +24,7 @@
CloudStack {{ $store.getters.features.cloudstackversion }}
<a-divider type="vertical" />
<a href="https://github.com/apache/cloudstack/issues/new" target="_blank">
<a-icon type="github"/>
<github-outlined />
{{ $t('label.report.bug') }}
</a>
</div>

View File

@ -18,16 +18,14 @@
<template>
<a-layout-header v-if="!headerBarFixed" :class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', theme ]" :style="{ padding: '0' }">
<div v-if="mode === 'sidemenu'" class="header">
<a-icon
v-if="device==='mobile'"
class="trigger"
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
@click="toggle"></a-icon>
<a-icon
v-else
class="trigger"
:type="collapsed ? 'menu-unfold' : 'menu-fold'"
@click="toggle"/>
<template v-if="device==='mobile'">
<menu-fold-outlined class="trigger" v-if="collapsed" @click="toggle" />
<menu-unfold-outlined class="trigger" v-else @click="toggle" />
</template>
<template v-else>
<menu-unfold-outlined class="trigger" v-if="collapsed" @click="toggle" />
<menu-fold-outlined class="trigger" v-else @click="toggle" />
</template>
<project-menu v-if="device !== 'mobile'" />
<saml-domain-switcher style="margin-left: 20px" />
<user-menu></user-menu>
@ -42,11 +40,10 @@
:menu="menus"
:theme="theme"
></s-menu>
<a-icon
v-else
class="trigger"
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
@click="toggle"></a-icon>
<div v-else>
<menu-fold-outlined class="trigger" v-if="collapsed" @click="toggle" />
<menu-unfold-outlined class="trigger" v-else @click="toggle" />
</div>
</div>
<project-menu v-if="device !== 'mobile'" />
<saml-domain-switcher style="margin-left: 20px" />
@ -58,7 +55,6 @@
</template>
<script>
import Breadcrumb from '@/components/widgets/Breadcrumb'
import Logo from '../header/Logo'
import SMenu from '../menu/'
import ProjectMenu from '../header/ProjectMenu'
@ -70,7 +66,6 @@ import { mixin } from '@/utils/mixin.js'
export default {
name: 'GlobalHeader',
components: {
Breadcrumb,
Logo,
SMenu,
ProjectMenu,

View File

@ -65,16 +65,17 @@
</a-drawer>
</template>
<template v-if="isDevelopmentMode || allowSettingTheme">
<drawer :visible="showSetting" placement="right">
<div slot="handler">
<a-button type="primary" size="large">
<a-icon :type="showSetting ? 'close' : 'setting'"/>
</a-button>
</div>
<setting slot="drawer" :visible="showSetting" />
</drawer>
</template>
<drawer :visible="showSetting" placement="right" v-if="isAdmin && (isDevelopmentMode || allowSettingTheme)">
<template #handler>
<a-button type="primary" size="large">
<close-outlined v-if="showSetting" />
<setting-outlined v-else />
</a-button>
</template>
<template #drawer>
<setting :visible="showSetting" />
</template>
</drawer>
</a-affix>
@ -118,6 +119,7 @@ import GlobalFooter from '@/components/page/GlobalFooter'
import { triggerWindowResizeEvent } from '@/utils/util'
import { mapState, mapActions } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin.js'
import { isAdmin } from '@/role'
import Drawer from '@/components/widgets/Drawer'
import Setting from '@/components/view/Setting.vue'
@ -143,6 +145,9 @@ export default {
...mapState({
mainMenu: state => state.permission.addRouters
}),
isAdmin () {
return isAdmin()
},
isDevelopmentMode () {
return process.env.NODE_ENV === 'development'
},
@ -190,7 +195,9 @@ export default {
this.collapsed = !this.sidebarOpened
},
mounted () {
if (this.$store.getters.darkMode) {
const layoutMode = this.$config.theme['@layout-mode'] || 'light'
this.$store.dispatch('SetDarkMode', (layoutMode === 'dark'))
if (layoutMode === 'dark') {
document.body.classList.add('dark-mode')
}
const userAgent = navigator.userAgent
@ -203,7 +210,7 @@ export default {
})
}
},
beforeDestroy () {
beforeUnmount () {
document.body.classList.remove('dark')
},
methods: {
@ -218,7 +225,7 @@ export default {
if (this.sidebarOpened) {
left = this.isDesktop() ? '256px' : '80px'
} else {
left = this.isMobile() && '0' || (this.fixSidebar && '80px' || '0')
left = this.isMobile() ? '0' : (this.fixSidebar ? '80px' : '0')
}
return left
},

View File

@ -24,7 +24,7 @@
v-if="item.name"
:to="{ path: item.path === '' ? '/' : item.path }"
>
<a-icon v-if="index == 0" :type="item.meta.icon" />
<render-icon v-if="index == 0" :icon="item.meta.icon" />
{{ item.meta.title }}
</router-link>
<span v-else-if="$route.params.id">{{ $route.params.id }}</span>
@ -51,12 +51,12 @@
</template>
<script>
import Breadcrumb from '@/components/widgets/Breadcrumb'
import RenderIcon from '@/utils/renderIcon'
export default {
name: 'PageHeader',
components: {
's-breadcrumb': Breadcrumb
RenderIcon
},
props: {
title: {
@ -101,7 +101,7 @@ export default {
}
},
watch: {
$route () {
'$route.fullPath' () {
this.getBreadcrumb()
}
}

View File

@ -19,21 +19,21 @@
<div :style="!$route.meta.pageHeader ? 'margin: -24px -24px 0px;' : null">
<!-- pageHeader , route meta hideHeader:true on hide -->
<page-header v-if="!$route.meta.pageHeader" :title="title" :logo="logo" :avatar="avatar">
<slot slot="action" name="action"></slot>
<slot slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && desc">
<template #action><slot name="action"></slot></template>
<template #content><slot name="headerContent"></slot></template>
<template #slot:content v-if="!this.$slots.headerContent && desc">
<p style="font-size: 14px;color: rgba(0,0,0,.65)">{{ desc }}</p>
<div class="link">
<template v-for="(link, index) in linkList">
<a :key="index" :href="link.href">
<a-icon :type="link.icon"/>
<template v-for="(link, index) in linkList" :key="index">
<a :href="link.href">
<render-icon v-if="index == 0" :icon="link.icon" />
<span>{{ link.title }}</span>
</a>
</template>
</div>
</div>
<slot slot="extra" name="extra"></slot>
<div slot="pageMenu">
</template>
<template #extra><slot name="extra"></slot></template>
<template #pageMenu>
<div class="page-menu-search" v-if="search">
<a-input-search style="width: 80%; max-width: 522px;" placeholder="请输入..." size="large" enterButton="搜索" />
</div>
@ -43,7 +43,7 @@
<a-tab-pane v-for="item in tabs.items" :tab="item.title" :key="item.key"></a-tab-pane>
</a-tabs>
</div>
</div>
</template>
</page-header>
<div class="content">
<div :class="['page-header-index-wide']">
@ -54,11 +54,14 @@
</template>
<script>
import RenderIcon from '@/utils/renderIcon'
import PageHeader from './PageHeader'
export default {
name: 'LayoutContent',
components: {
RenderIcon,
PageHeader
},
// ['desc', 'logo', 'title', 'avatar', 'linkList', 'extraImage']

View File

@ -17,7 +17,7 @@
<template>
<a-popover trigger="click" placement="bottomRight" :overlayStyle="{ width: '300px' }">
<template slot="content">
<template #content>
<a-spin :spinning="loadding">
<a-tabs>
<a-tab-pane v-for="(tab, k) in tabs" :tab="tab.title" :key="k">
@ -28,7 +28,7 @@
</template>
<span @click="fetchNotice" class="header-notice">
<a-badge count="12">
<a-icon style="font-size: 16px; padding: 4px" type="bell" />
<bell-outlined style="font-size: 16px; padding: 4px" />
</a-badge>
</span>
</a-popover>

View File

@ -18,7 +18,7 @@
<template>
<span class="row-action-button">
<a-tooltip arrowPointAtCenter placement="bottomRight" v-if="resource && resource.id && dataView">
<template slot="title">
<template #title>
{{ $t('label.view.console') }}
</template>
<console :resource="resource" :size="size" />
@ -28,7 +28,7 @@
:key="actionIndex"
arrowPointAtCenter
placement="bottomRight">
<template slot="title">
<template #title>
{{ $t(action.label) }}
</template>
<a-badge
@ -42,15 +42,16 @@
)"
:disabled="'disabled' in action ? action.disabled(resource, $store.getters) : false" >
<a-button
:type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
:shape="!dataView && action.icon === 'plus' ? 'round' : 'circle'"
:type="(['PlusOutlined', 'plus-outlined', 'DeleteOutlined', 'delete-outlined'].includes(action.icon) ? 'primary' : 'default')"
:shape="!dataView && ['PlusOutlined', 'plus-outlined'].includes(action.icon) ? 'round' : 'circle'"
:danger="['DeleteOutlined', 'delete-outlined'].includes(action.icon)"
style="margin-left: 5px"
:size="size"
@click="execAction(action)">
<span v-if="!dataView && action.icon === 'plus'">
<span v-if="!dataView && ['PlusOutlined', 'plus-outlined'].includes(action.icon)">
{{ $t(action.label) }}
</span>
<a-icon v-if="(typeof action.icon === 'string')" :type="action.icon" />
<render-icon v-if="(typeof action.icon === 'string')" :icon="action.icon" />
<font-awesome-icon v-else :icon="action.icon" />
</a-button>
</a-badge>
@ -61,15 +62,16 @@
(dataView && action.dataView && ('show' in action ? action.show(resource, $store.getters) : true))
)"
:disabled="'disabled' in action ? action.disabled(resource, $store.getters) : false"
:type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
:shape="!dataView && ['plus', 'user-add'].includes(action.icon) ? 'round' : 'circle'"
:type="(['PlusOutlined', 'plus-outlined', 'DeleteOutlined', 'delete-outlined'].includes(action.icon) ? 'primary' : 'default')"
:danger="['DeleteOutlined', 'delete-outlined'].includes(action.icon)"
:shape="!dataView && ['PlusOutlined', 'plus-outlined', 'UserAddOutlined', 'user-add-outlined'].includes(action.icon) ? 'round' : 'circle'"
style="margin-left: 5px"
:size="size"
@click="execAction(action)">
<span v-if="!dataView && ['plus', 'user-add'].includes(action.icon)">
<span v-if="!dataView && ['PlusOutlined', 'plus-outlined', 'UserAddOutlined', 'user-add-outlined'].includes(action.icon)">
{{ $t(action.label) }}
</span>
<a-icon v-if="(typeof action.icon === 'string')" :type="action.icon" />
<render-icon v-if="(typeof action.icon === 'string')" :icon="action.icon" />
<font-awesome-icon v-else :icon="action.icon" />
</a-button>
</a-tooltip>
@ -78,11 +80,13 @@
<script>
import { api } from '@/api'
import RenderIcon from '@/utils/renderIcon'
import Console from '@/components/widgets/Console'
export default {
name: 'ActionButton',
components: {
RenderIcon,
Console
},
data () {
@ -132,11 +136,14 @@ export default {
}
},
watch: {
resource (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
resource: {
deep: true,
handler (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.handleShowBadge()
}
this.handleShowBadge()
}
},
methods: {
@ -186,8 +193,8 @@ export default {
Promise.all(arrAsync).then(response => {
for (let j = 0; j < response.length; j++) {
this.$set(this.actionBadge, response[j].api, {})
this.$set(this.actionBadge[response[j].api], 'badgeNum', response[j].count)
this.actionBadge[response[j].api] = {}
this.actionBadge[response[j].api].badgeNum = response[j].count
}
}).catch(() => {})
}
@ -201,7 +208,7 @@ export default {
margin-left: 5px;
}
/deep/.button-action-badge .ant-badge-count {
:deep(.button-action-badge) .ant-badge-count {
right: 10px;
z-index: 8;
}

View File

@ -20,7 +20,7 @@
<div class="account-center-team" v-if="annotationType && 'listAnnotations' in $store.getters.apis">
<a-spin :spinning="loadingAnnotations">
<div class="title">
{{ $t('label.comments') }} ({{ this.itemCount }})
{{ $t('label.comments') }} ({{ itemCount }})
</div>
<a-divider :dashed="true" />
<a-list
@ -29,48 +29,47 @@
itemLayout="horizontal"
:pagination="false"
size="small" >
<a-list-item slot="renderItem" slot-scope="item">
<a-comment
class="comment"
:content="item.annotation"
:datetime="$toLocaleDate(item.created)"
:author="item.username" >
<a-avatar
slot="avatar"
icon="message" />
<a-popconfirm
:title="$t('label.make') + ' ' + (item.adminsonly ? $t('label.annotation.everyone') : $t('label.adminsonly')) + ' ?'"
v-if="['Admin'].includes($store.getters.userInfo.roletype)"
slot="actions"
key="visibility"
@confirm="updateVisibility(item)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')" >
<a-icon
type="eye"
:style="[{
color: item.adminsonly ? $config.theme['@primary-color'] : $config.theme['@disabled-color']
}]" />
<span>
{{ item.adminsonly ? $t('label.adminsonly') : $t('label.annotation.everyone') }}
</span>
</a-popconfirm>
</a-comment>
<a-popconfirm
:title="$t('label.remove.annotation')"
v-if="'removeAnnotation' in $store.getters.apis && isAdminOrAnnotationOwner(item)"
slot="actions"
key="visibility"
@confirm="deleteNote(item)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')" >
<a-icon
type="delete"
shape="circle"
theme="twoTone"
two-tone-color="#eb2f96" />
</a-popconfirm>
</a-list-item>
<template #renderItem="{ item }">
<a-list-item>
<a-comment
class="comment"
:content="item.annotation"
:datetime="$toLocaleDate(item.created)"
:author="item.username" >
<template #avatar>
<a-avatar><template #icon><message-outlined /></template></a-avatar>
</template>
<template #actions>
<a-popconfirm
:title="$t('label.make') + ' ' + (item.adminsonly ? $t('label.annotation.everyone') : $t('label.adminsonly')) + ' ?'"
v-if="['Admin'].includes($store.getters.userInfo.roletype)"
key="visibility"
@confirm="updateVisibility(item)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')" >
<eye-outlined
:style="[{
color: item.adminsonly ? $config.theme['@primary-color'] : $config.theme['@disabled-color']
}]" />
<span>
{{ item.adminsonly ? $t('label.adminsonly') : $t('label.annotation.everyone') }}
</span>
</a-popconfirm>
</template>
</a-comment>
<template #actions>
<a-popconfirm
:title="$t('label.remove.annotation')"
v-if="'removeAnnotation' in $store.getters.apis && isAdminOrAnnotationOwner(item)"
key="visibility"
@confirm="deleteNote(item)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')" >
<delete-two-tone shape="circle" two-tone-color="#eb2f96" />
</a-popconfirm>
</template>
</a-list-item>
</template>
</a-list>
<a-pagination
class="row-element"
@ -82,33 +81,37 @@
:pageSizeOptions="['10']"
@change="changePage"
showQuickJumper>
<template slot="buildOptionText" slot-scope="props">
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
<a-divider :dashed="true" />
<a-comment v-if="'addAnnotation' in $store.getters.apis">
<a-avatar
slot="avatar"
icon="edit"
@click="showNotesInput = true" />
<div slot="content" v-ctrl-enter="saveNote">
<a-textarea
rows="4"
@change="handleNoteChange"
:value="annotation"
:placeholder="$t('label.add.note')" />
<a-checkbox @change="toggleNoteVisibility" v-if="['Admin'].includes(this.$store.getters.userInfo.roletype)" style="margin-top: 10px">
{{ $t('label.adminsonly') }}
</a-checkbox>
<a-button
style="margin-top: 10px; float: right"
@click="saveNote"
type="primary" >
{{ $t('label.submit') }}
</a-button>
</div>
<template #avatar>
<a-avatar @click="showNotesInput = true">
<template #icon><edit-outlined /></template>
</a-avatar>
</template>
<template #content>
<div v-ctrl-enter="saveNote">
<a-textarea
rows="4"
@change="handleNoteChange"
v-model:value="annotation"
:placeholder="$t('label.add.note')" />
<a-checkbox @change="toggleNoteVisibility" v-if="['Admin'].includes($store.getters.userInfo.roletype)" style="margin-top: 10px">
{{ $t('label.adminsonly') }}
</a-checkbox>
<a-button
style="margin-top: 10px; float: right"
@click="saveNote"
type="primary"
ref="submit">
{{ $t('label.submit') }}
</a-button>
</div>
</template>
</a-comment>
</a-spin>
</div>
@ -145,12 +148,14 @@ export default {
}
},
watch: {
resource: function (newItem, oldItem) {
this.resource = newItem
this.resourceType = this.$route.meta.resourceType
this.annotationType = this.generateAnnotationType()
if (this.annotationType) {
this.getAnnotations()
resource: {
deep: true,
handler () {
this.resourceType = this.$route.meta.resourceType
this.annotationType = this.generateAnnotationType()
if (this.annotationType) {
this.getAnnotations()
}
}
}
},

View File

@ -17,6 +17,7 @@
<template>
<a-modal
v-if="showGroupActionModal"
:visible="showGroupActionModal"
:closable="true"
:maskClosable="false"
@ -26,22 +27,23 @@
style="top: 20px;overflow-y: auto"
centered
>
<span slot="title"> {{ $t(message.title) }}
<template #title>
{{ $t(message.title) }}
<a
v-if="message.docHelp || $route.meta.docHelp"
style="margin-left: 5px"
:href="$config.docBase + '/' + (message.docHelp || $route.meta.docHelp)"
target="_blank">
<a-icon type="question-circle-o"></a-icon>
<question-circle-outlined />
</a>
</span>
<template slot="footer">
</template>
<template #footer>
<a-button key="back" @click="handleCancel"> {{ $t('label.close') }} </a-button>
</template>
<a-card :bordered="false" style="background:#f1f1f1">
<div><a-icon type="check-circle-o" style="color: #52c41a; margin-right: 8px"/> {{ $t('label.success') + ': ' + succeededCount }}</div>
<div><a-icon type="close-circle-o" style="color: #f5222d; margin-right: 8px"/> {{ $t('state.failed') + ': ' + failedCount }}</div>
<div><a-icon type="sync-o" style="color: #1890ff; margin-right: 8px"/> {{ $t('state.inprogress') + ': ' + selectedItems.filter(item => item.status === 'InProgress').length || 0 }}</div>
<div><check-circle-outlined style="color: #52c41a; margin-right: 8px"/> {{ $t('label.success') + ': ' + succeededCount }}</div>
<div><close-circle-outlined style="color: #f5222d; margin-right: 8px"/> {{ $t('state.failed') + ': ' + failedCount }}</div>
<div><sync-outlined style="color: #1890ff; margin-right: 8px"/> {{ $t('state.inprogress') + ': ' + selectedItems.filter(item => item.status === 'InProgress').length || 0 }}</div>
</a-card>
<a-divider />
<div v-if="showGroupActionModal">
@ -50,33 +52,33 @@
size="middle"
:columns="selectedColumns"
:dataSource="tableChanged ? filteredItems : selectedItems"
:rowKey="(record, idx) => (this.$route.path.includes('/template') || this.$route.path.includes('/iso')) ? record.zoneid: record.id"
:rowKey="(record, idx) => ($route.path.includes('/template') || $route.path.includes('/iso')) ? record.zoneid: record.id"
:pagination="true"
@change="handleTableChange"
style="overflow-y: auto">
<div slot="status" slot-scope="text">
<template #status="{text}">
<status :text=" text ? text : $t('state.inprogress') " displayText></status>
</div>
<template slot="algorithm" slot-scope="record">
</template>
<template #algorithm="{record}">
{{ returnAlgorithmName(record.algorithm) }}
</template>
<template slot="privateport" slot-scope="record">
<template #privateport="{record}">
{{ record.privateport }} - {{ record.privateendport }}
</template>
<template slot="publicport" slot-scope="record">
<template #publicport="{record}">
{{ record.publicport }} - {{ record.publicendport }}
</template>
<template slot="protocol" slot-scope="record">
{{ record.protocol | capitalise }}
<template #protocol="{record}">
{{ capitalise(record.protocol) }}
</template>
<template slot="startport" slot-scope="record">
<template #startport="{record}">
{{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
</template>
<template slot="endport" slot-scope="record">
<template #endport="{record}">
{{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
</template>
<template slot="vm" slot-scope="record">
<div><a-icon type="desktop"/> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
<template #vm="{record}">
<div><desktop-outlined /> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
</template>
</a-table>
<br/>
@ -91,12 +93,6 @@ export default {
components: {
Status
},
filters: {
capitalise: val => {
if (val === 'all') return 'All'
return val.toUpperCase()
}
},
props: {
showGroupActionModal: {
type: Boolean,
@ -185,6 +181,10 @@ export default {
default :
return ''
}
},
capitalise (val) {
if (val === 'all') return 'All'
return val.toUpperCase()
}
}
}

View File

@ -31,51 +31,55 @@
:ok-button-props="{props: { type: 'default' } }"
:cancel-button-props="{props: { type: 'primary' } }"
centered>
<span slot="title">
<template #title>
{{ $t(message.title) }}
</span>
</template>
<span>
<a-alert
v-if="isDestructiveAction()"
type="error">
<a-icon slot="message" type="exclamation-circle" style="color: red; fontSize: 30px; display: inline-flex" />
<span style="padding-left: 5px" slot="message" v-html="`<b>${selectedRowKeys.length} ` + $t('label.items.selected') + `. </b>`" />
<span slot="message" v-html="$t(message.confirmMessage)" />
<template #message>
<exclamation-circle-outlined style="color: red; fontSize: 30px; display: inline-flex" />
<span style="padding-left: 5px" v-html="`<b>${selectedRowKeys.length} ` + $t('label.items.selected') + `. </b>`" />
<span v-html="$t(message.confirmMessage)" />
</template>
</a-alert>
<a-alert v-else type="warning">
<span v-if="selectedRowKeys.length > 0" slot="message" v-html="`<b>${selectedRowKeys.length} ` + $t('label.items.selected') + `. </b>`" />
<span slot="message" v-html="$t(message.confirmMessage)" />
<template #message>
<span v-if="selectedRowKeys.length > 0" v-html="`<b>${selectedRowKeys.length} ` + $t('label.items.selected') + `. </b>`" />
<span v-html="$t(message.confirmMessage)" />
</template>
</a-alert>
<a-divider />
<a-table
size="middle"
:columns="selectedColumns"
:dataSource="selectedItems"
:rowKey="(record, idx) => this.$route.path.includes('/iso/') ? record.zoneid : record.id"
:rowKey="(record, idx) => $route.path.includes('/iso/') ? record.zoneid : record.id"
:pagination="true"
style="overflow-y: auto">
<template slot="algorithm" slot-scope="record">
<template #algorithm="{record}">
{{ returnAlgorithmName(record.algorithm) }}
</template>
<template v-for="(column, index) in selectedColumns" :slot="column" slot-scope="text" >
<span :key="index"> {{ text }} ==== {{ column }} </span>
<template #column="{ text }">
<span v-for="(column, index) in selectedColumns" :key="index"> {{ text }} ==== {{ column }}</span>
</template>
<template slot="privateport" slot-scope="record">
<template #privateport="{record}">
{{ record.privateport }} - {{ record.privateendport }}
</template>
<template slot="publicport" slot-scope="record">
<template #publicport="{record}">
{{ record.publicport }} - {{ record.publicendport }}
</template>
<template slot="protocol" slot-scope="record">
{{ record.protocol | capitalise }}
<template #protocol="{record}">
{{ capitalise(record.protocol) }}
</template>
<template slot="vm" slot-scope="record">
<div><a-icon type="desktop"/> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
<template #vm="{record}">
<div><desktop-outlined /> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
</template>
<template slot="startport" slot-scope="record">
<template #startport="{record}">
{{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
</template>
<template slot="endport" slot-scope="record">
<template #endport="{record}">
{{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
</template>
</a-table>

View File

@ -30,7 +30,7 @@
<strong>{{ $t('label.account') }}</strong><br/>
<router-link :to="{ path: '/account/' + dedicatedAccountId }">{{ dedicatedAccountId }}</router-link>
</p>
<a-button style="margin-top: 10px; margin-bottom: 10px;" type="danger" @click="handleRelease">
<a-button style="margin-top: 10px; margin-bottom: 10px;" type="primary" danger @click="handleRelease">
{{ releaseButtonLabel }}
</a-button>
</div>
@ -79,9 +79,12 @@ export default {
}
},
watch: {
resource (newItem, oldItem) {
if (this.resource && this.resource.id && newItem && newItem.id !== oldItem.id) {
this.fetchData()
resource: {
deep: true,
handler (newItem, oldItem) {
if (this.resource && this.resource.id && newItem && newItem.id !== oldItem.id) {
this.fetchData()
}
}
}
},

View File

@ -24,12 +24,13 @@
<a-select
style="width: 100%"
showSearch
optionFilterProp="children"
optionFilterProp="label"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="handleChangeDomain"
v-model="domainId">
v-focus="true"
v-model:value="domainId">
<a-select-option v-for="(domain, index) in domainsList" :value="domain.id" :key="index">
{{ domain.path || domain.name || domain.description }}
</a-select-option>
@ -42,9 +43,9 @@
style="width: 100%"
@change="handleChangeAccount"
showSearch
optionFilterProp="children"
optionFilterProp="label"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="(account, index) in accountsList" :value="account.name" :key="index">
{{ account.name }}

View File

@ -17,20 +17,21 @@
<template>
<a-modal
v-model="dedicatedDomainModal"
v-ctrl-enter="handleDedicateForm"
:visible="dedicatedDomainModal"
:title="label"
:closable="true"
:maskClosable="false"
:footer="null"
@cancel="closeModal">
<DedicateDomain
@domainChange="id => domainId = id"
@accountChange="id => dedicatedAccount = id"
:error="domainError" />
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ this.$t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleDedicateForm">{{ this.$t('label.ok') }}</a-button>
<div v-ctrl-enter="handleDedicateForm">
<DedicateDomain
@domainChange="id => domainId = id"
@accountChange="id => dedicatedAccount = id"
:error="domainError" />
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleDedicateForm">{{ $t('label.ok') }}</a-button>
</div>
</div>
</a-modal>
</template>

View File

@ -26,9 +26,9 @@
<a-button
type="dashed"
style="width: 100%"
icon="plus"
:disabled="!('updateTemplate' in $store.getters.apis && 'updateVirtualMachine' in $store.getters.apis && isAdminOrOwner())"
@click="onShowAddDetail">
<template #icon><plus-outlined /></template>
{{ $t('label.add.setting') }}
</a-button>
</div>
@ -40,8 +40,8 @@
class="detail-input"
ref="keyElm"
:filterOption="filterOption"
:value="newKey"
:dataSource="Object.keys(detailOptions)"
v-model:value="newKey"
:options="detailKeys"
:placeholder="$t('label.name')"
@change="e => onAddInputChange(e, 'newKey')" />
<a-input
@ -52,12 +52,12 @@
<a-auto-complete
class="detail-input"
:filterOption="filterOption"
:value="newValue"
:dataSource="detailOptions[newKey]"
v-model:value="newValue"
:options="detailValues"
:placeholder="$t('label.value')"
@change="e => onAddInputChange(e, 'newValue')" />
<tooltip-button :tooltip="$t('label.add.setting')" icon="check" @click="addDetail" buttonClass="detail-button" />
<tooltip-button :tooltip="$t('label.cancel')" icon="close" @click="closeDetail" buttonClass="detail-button" />
<tooltip-button :tooltip="$t('label.add.setting')" icon="check-outlined" @onClick="addDetail" buttonClass="detail-button" />
<tooltip-button :tooltip="$t('label.cancel')" icon="close-outlined" @onClick="closeDetail" buttonClass="detail-button" />
</a-input-group>
<p v-if="error" style="color: red"> {{ $t(error) }} </p>
</div>
@ -65,48 +65,60 @@
<a-list size="large">
<a-list-item :key="index" v-for="(item, index) in details">
<a-list-item-meta>
<span slot="title">
<template #title>
{{ item.name }}
</span>
<span slot="description" style="word-break: break-all">
<span v-if="item.edit" style="display: flex">
</template>
<template #description>
<div v-if="item.edit" style="display: flex">
<a-auto-complete
style="width: 100%"
:value="item.value"
:dataSource="detailOptions[item.name]"
v-model:value="item.value"
:options="detailOptions[item.name]"
@change="val => handleInputChange(val, index)"
@pressEnter="e => updateDetail(index)" />
</span>
<span v-else>{{ item.value }}</span>
</span>
<tooltip-button
buttonClass="edit-button"
:tooltip="$t('label.cancel')"
@onClick="hideEditDetail(index)"
v-if="item.edit"
iconType="close-circle-two-tone"
iconTwoToneColor="#f5222d" />
<tooltip-button
buttonClass="edit-button"
:tooltip="$t('label.ok')"
@onClick="updateDetail(index)"
v-if="item.edit"
iconType="check-circle-two-tone"
iconTwoToneColor="#52c41a" />
</div>
<span v-else style="word-break: break-all">{{ item.value }}</span>
</template>
</a-list-item-meta>
<div
slot="actions"
v-if="!disableSettings && 'updateTemplate' in $store.getters.apis &&
'updateVirtualMachine' in $store.getters.apis && isAdminOrOwner() && allowEditOfDetail(item.name)">
<tooltip-button :tooltip="$t('label.cancel')" @click="hideEditDetail(index)" v-if="item.edit" iconType="close-circle" iconTwoToneColor="#f5222d" />
<tooltip-button :tooltip="$t('label.ok')" @click="updateDetail(index)" v-if="item.edit" iconType="check-circle" iconTwoToneColor="#52c41a" />
<tooltip-button
:tooltip="$t('label.edit')"
icon="edit"
:disabled="deployasistemplate === true"
v-if="!item.edit"
@click="showEditDetail(index)" />
</div>
<div
slot="actions"
v-if="!disableSettings && 'updateTemplate' in $store.getters.apis &&
'updateVirtualMachine' in $store.getters.apis && isAdminOrOwner() && allowEditOfDetail(item.name)">
<a-popconfirm
:title="`${$t('label.delete.setting')}?`"
@confirm="deleteDetail(index)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
placement="left"
>
<tooltip-button :tooltip="$t('label.delete')" :disabled="deployasistemplate === true" type="danger" icon="delete" />
</a-popconfirm>
</div>
<template #actions>
<div
v-if="!disableSettings && 'updateTemplate' in $store.getters.apis &&
'updateVirtualMachine' in $store.getters.apis && isAdminOrOwner() && allowEditOfDetail(item.name)">
<tooltip-button
:tooltip="$t('label.edit')"
icon="edit-outlined"
:disabled="deployasistemplate === true"
v-if="!item.edit"
@onClick="showEditDetail(index)" />
</div>
<div
v-if="!disableSettings && 'updateTemplate' in $store.getters.apis &&
'updateVirtualMachine' in $store.getters.apis && isAdminOrOwner() && allowEditOfDetail(item.name)">
<a-popconfirm
:title="`${$t('label.delete.setting')}?`"
@confirm="deleteDetail(index)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
placement="left"
>
<tooltip-button :tooltip="$t('label.delete')" :disabled="deployasistemplate === true" type="primary" :danger="true" icon="delete-outlined" />
</a-popconfirm>
</div>
</template>
</a-list-item>
</a-list>
</a-spin>
@ -140,8 +152,29 @@ export default {
}
},
watch: {
resource: function (newItem, oldItem) {
this.updateResource(newItem)
resource: {
deep: true,
handler (newItem) {
this.updateResource(newItem)
}
}
},
computed: {
detailKeys () {
return Object.keys(this.detailOptions).map(key => {
return { value: key }
})
},
detailValues () {
if (!this.newKey) {
return []
}
if (!Array.isArray(this.detailOptions[this.newKey])) {
return { value: this.detailOptions[this.newKey] }
}
return this.detailOptions[this.newKey].map(value => {
return { value: value }
})
}
},
created () {
@ -150,7 +183,7 @@ export default {
methods: {
filterOption (input, option) {
return (
option.componentOptions.children[0].text.toUpperCase().indexOf(input.toUpperCase()) >= 0
option.value.toUpperCase().indexOf(input.toUpperCase()) >= 0
)
},
updateResource (resource) {
@ -158,18 +191,17 @@ export default {
if (!resource) {
return
}
this.resource = resource
this.resourceType = this.$route.meta.resourceType
if (resource.details) {
this.details = Object.keys(this.resource.details).map(k => {
return { name: k, value: this.resource.details[k], edit: false }
this.details = Object.keys(resource.details).map(k => {
return { name: k, value: resource.details[k], edit: false }
})
}
api('listDetailOptions', { resourcetype: this.resourceType, resourceid: this.resource.id }).then(json => {
api('listDetailOptions', { resourcetype: this.resourceType, resourceid: resource.id }).then(json => {
this.detailOptions = json.listdetailoptionsresponse.detailoptions.details
})
this.disableSettings = (this.$route.meta.name === 'vm' && this.resource.state !== 'Stopped')
api('listTemplates', { templatefilter: 'all', id: this.resource.templateid }).then(json => {
this.disableSettings = (this.$route.meta.name === 'vm' && resource.state !== 'Stopped')
api('listTemplates', { templatefilter: 'all', id: resource.templateid }).then(json => {
this.deployasistemplate = json.listtemplatesresponse.template[0].deployasis
})
},
@ -184,16 +216,13 @@ export default {
showEditDetail (index) {
this.details[index].edit = true
this.details[index].originalValue = this.details[index].value
this.$set(this.details, index, this.details[index])
},
hideEditDetail (index) {
this.details[index].edit = false
this.details[index].value = this.details[index].originalValue
this.$set(this.details, index, this.details[index])
},
handleInputChange (val, index) {
this.details[index].value = val
this.$set(this.details, index, this.details[index])
},
onAddInputChange (val, obj) {
this.error = false

View File

@ -19,48 +19,50 @@
<a-list
size="small"
:dataSource="fetchDetails()">
<a-list-item slot="renderItem" slot-scope="item" v-if="item in resource">
<div>
<strong>{{ item === 'service' ? $t('label.supportedservices') : $t('label.' + String(item).toLowerCase()) }}</strong>
<br/>
<div v-if="Array.isArray(resource[item]) && item === 'service'">
<div v-for="(service, idx) in resource[item]" :key="idx">
{{ service.name }} : {{ service.provider[0].name }}
<template #renderItem="{item}">
<a-list-item v-if="item in dataResource">
<div>
<strong>{{ item === 'service' ? $t('label.supportedservices') : $t('label.' + String(item).toLowerCase()) }}</strong>
<br/>
<div v-if="Array.isArray(dataResource[item]) && item === 'service'">
<div v-for="(service, idx) in dataResource[item]" :key="idx">
{{ service.name }} : {{ service.provider[0].name }}
</div>
</div>
</div>
<div v-else-if="$route.meta.name === 'backup' && item === 'volumes'">
<div v-for="(volume, idx) in JSON.parse(resource[item])" :key="idx">
<router-link :to="{ path: '/volume/' + volume.uuid }">{{ volume.type }} - {{ volume.path }}</router-link> ({{ parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GB)
<div v-else-if="$route.meta.name === 'backup' && item === 'volumes'">
<div v-for="(volume, idx) in JSON.parse(dataResource[item])" :key="idx">
<router-link :to="{ path: '/volume/' + volume.uuid }">{{ volume.type }} - {{ volume.path }}</router-link> ({{ parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GB)
</div>
</div>
</div>
<div v-else-if="$route.meta.name === 'computeoffering' && item === 'rootdisksize'">
<div>
{{ resource.rootdisksize }} GB
<div v-else-if="$route.meta.name === 'computeoffering' && item === 'rootdisksize'">
<div>
{{ dataResource.rootdisksize }} GB
</div>
</div>
<div v-else-if="['name', 'type'].includes(item)">
<span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(dataResource[item])">{{ $t(dataResource[item].toLowerCase()) }}</span>
<span v-else>{{ dataResource[item] }}</span>
</div>
<div v-else-if="['created', 'sent', 'lastannotated'].includes(item)">
{{ $toLocaleDate(dataResource[item]) }}
</div>
<div v-else-if="$route.meta.name === 'guestnetwork' && item === 'egressdefaultpolicy'">
{{ dataResource[item]? $t('message.egress.rules.allow') : $t('message.egress.rules.deny') }}
</div>
<div v-else>{{ dataResource[item] }}</div>
</div>
<div v-else-if="['name', 'type'].includes(item)">
<span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(resource[item])">{{ $t(resource[item].toLowerCase()) }}</span>
<span v-else>{{ resource[item] }}</span>
</a-list-item>
<a-list-item v-else-if="item === 'ip6address' && ipV6Address && ipV6Address.length > 0">
<div>
<strong>{{ $t('label.' + String(item).toLowerCase()) }}</strong>
<br/>
<div>{{ ipV6Address }}</div>
</div>
<div v-else-if="['created', 'sent', 'lastannotated'].includes(item)">
{{ $toLocaleDate(resource[item]) }}
</div>
<div v-else-if="$route.meta.name === 'guestnetwork' && item === 'egressdefaultpolicy'">
{{ resource[item]? $t('message.egress.rules.allow') : $t('message.egress.rules.deny') }}
</div>
<div v-else>{{ resource[item] }}</div>
</div>
</a-list-item>
<a-list-item slot="renderItem" slot-scope="item" v-else-if="item === 'ip6address' && ipV6Address && ipV6Address.length > 0">
<div>
<strong>{{ $t('label.' + String(item).toLowerCase()) }}</strong>
<br/>
<div>{{ ipV6Address }}</div>
</div>
</a-list-item>
<HostInfo :resource="resource" v-if="$route.meta.name === 'host' && 'listHosts' in $store.getters.apis" />
<DedicateData :resource="resource" v-if="dedicatedSectionActive" />
<VmwareData :resource="resource" v-if="$route.meta.name === 'zone' && 'listVmwareDcs' in $store.getters.apis" />
</a-list-item>
</template>
<HostInfo :resource="dataResource" v-if="$route.meta.name === 'host' && 'listHosts' in $store.getters.apis" />
<DedicateData :resource="dataResource" v-if="dedicatedSectionActive" />
<VmwareData :resource="dataResource" v-if="$route.meta.name === 'zone' && 'listVmwareDcs' in $store.getters.apis" />
</a-list>
</template>
@ -90,7 +92,8 @@ export default {
return {
dedicatedRoutes: ['zone', 'pod', 'cluster', 'host'],
dedicatedSectionActive: false,
projectname: ''
projectname: '',
dataResource: {}
}
},
mounted () {
@ -98,22 +101,26 @@ export default {
},
computed: {
ipV6Address () {
if (this.resource.nic && this.resource.nic.length > 0) {
return this.resource.nic.filter(e => { return e.ip6address }).map(e => { return e.ip6address }).join(', ')
if (this.dataResource.nic && this.dataResource.nic.length > 0) {
return this.dataResource.nic.filter(e => { return e.ip6address }).map(e => { return e.ip6address }).join(', ')
}
return null
}
},
created () {
this.dataResource = this.resource
this.dedicatedSectionActive = this.dedicatedRoutes.includes(this.$route.meta.name)
},
watch: {
resource (newItem) {
this.resource = newItem
if ('account' in this.resource && this.resource.account.startsWith('PrjAcct-')) {
this.projectname = this.resource.account.substring(this.resource.account.indexOf('-') + 1, this.resource.account.lastIndexOf('-'))
this.resource.projectname = this.projectname
resource: {
deep: true,
handler () {
this.dataResource = this.resource
if ('account' in this.dataResource && this.dataResource.account.startsWith('PrjAcct-')) {
this.projectname = this.dataResource.account.substring(this.dataResource.account.indexOf('-') + 1, this.dataResource.account.lastIndexOf('-'))
this.dataResource.projectname = this.projectname
}
}
},
$route () {
@ -123,15 +130,15 @@ export default {
},
methods: {
fetchProjectAdmins () {
if (!this.resource.owner) {
if (!this.dataResource.owner) {
return false
}
var owners = this.resource.owner
var owners = this.dataResource.owner
var projectAdmins = []
for (var owner of owners) {
projectAdmins.push(Object.keys(owner).includes('user') ? owner.account + '(' + owner.user + ')' : owner.account)
}
this.resource.account = projectAdmins.join()
this.dataResource.account = projectAdmins.join()
},
fetchDetails () {
var details = this.$route.meta.details

View File

@ -1,131 +0,0 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<a-modal
:title="$t(currentAction.label)"
:visible="showForm"
:closable="true"
:confirmLoading="currentAction.loading"
style="top: 20px;"
@cancel="close"
centered
>
<a-spin :spinning="currentAction.loading">
<a-form
:form="form"
@submit="handleSubmit"
layout="vertical" >
<a-form-item
v-for="(field, fieldIndex) in currentAction.params"
:key="fieldIndex"
:label="$t(field.name)"
:v-bind="field.name"
v-if="field.name !== 'id'"
>
<span v-if="field.type==='boolean'">
<a-switch
v-decorator="[field.name, {
rules: [{ required: field.required, message: `${this.$t('message.error.required.input')}` }]
}]"
:placeholder="field.description"
/>
</span>
<span v-else-if="field.type==='uuid' || field.name==='account'">
<a-select
:loading="field.loading"
v-decorator="[field.name, {
rules: [{ required: field.required, message: `${this.$t('message.error.select')}` }]
}]"
:placeholder="field.description"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="(opt, optIndex) in field.opts" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</span>
<span v-else-if="field.type==='long'">
<a-input-number
v-decorator="[field.name, {
rules: [{ required: field.required, message: `${this.$t('message.validate.number')}` }]
}]"
:placeholder="field.description"
/>
</span>
<span v-else-if="field.name==='password'">
<a-input-password
v-decorator="[field.name, {
rules: [{ required: field.required, message: `${this.$t('message.error.required.input')}` }]
}]"
:placeholder="field.description"
/>
</span>
<span v-else>
<a-input
v-decorator="[field.name, {
rules: [{ required: field.required, message: `${this.$t('message.error.required.input')}` }]
}]"
:placeholder="field.description"
/>
</span>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import ChartCard from '@/components/widgets/ChartCard'
export default {
name: 'FormView',
components: {
ChartCard
},
props: {
currentAction: {
type: Object,
required: true
},
showForm: {
type: Boolean,
default: false
},
handleSubmit: {
type: Function,
default: () => {}
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
},
methods: {
close () {
this.currentAction.loading = false
this.showForm = false
}
}
}
</script>
<style scoped>
</style>

View File

@ -27,16 +27,16 @@
v-clipboard:copy="name" >
<upload-resource-icon v-if="'uploadResourceIcon' in $store.getters.apis" :visible="showUpload" :resource="resource" @handle-close="showUpload(false)"/>
<div class="ant-upload-preview" v-if="$showIcon()">
<a-icon type="camera" class="upload-icon"/>
<camera-outlined class="upload-icon"/>
</div>
<slot name="avatar">
<span v-if="(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon) && !['router', 'systemvm', 'volume'].includes($route.path.split('/')[1])">
<resource-icon :image="getImage(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon)" size="4x" style="margin-right: 5px"/>
</span>
<span v-else>
<os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="4x" @update-osname="(name) => resource.ostypename = name"/>
<a-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :type="$route.meta.icon"/>
<a-icon v-else style="font-size: 36px" :component="$route.meta.icon" />
<os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="4x" @update-osname="setResourceOsType"/>
<render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :icon="$route.meta.icon" />
<render-icon v-else style="font-size: 36px" :svgIcon="$route.meta.icon" />
</span>
</slot>
</div>
@ -82,7 +82,7 @@
{{ resource.version }}
</a-tag>
<a-tooltip placement="right" >
<template slot="title">
<template #title>
<span>{{ $t('label.view.console') }}</span>
</template>
<console style="margin-top: -5px;" :resource="resource" size="default" v-if="resource.id" />
@ -118,12 +118,11 @@
<tooltip-button
tooltipPlacement="right"
:tooltip="$t('label.copyid')"
style="margin-left: -5px"
icon="barcode"
icon="barcode-outlined"
type="dashed"
size="small"
@click="$message.success($t('label.copied.clipboard'))"
v-clipboard:copy="resource.id" />
:copyResource="resource.id"
@onClick="$message.success($t('label.copied.clipboard'))" />
<span style="margin-left: 10px;">{{ resource.id }}</span>
</div>
</div>
@ -140,7 +139,7 @@
<div class="resource-detail-item" v-if="('cpunumber' in resource && 'cpuspeed' in resource) || resource.cputotal">
<div class="resource-detail-item__label">{{ $t('label.cpu') }}</div>
<div class="resource-detail-item__details">
<a-icon type="appstore" />
<appstore-outlined />
<span v-if="resource.cputotal">{{ resource.cputotal }}</span>
<span v-else>{{ resource.cpunumber }} CPU x {{ parseFloat(resource.cpuspeed / 1000.0).toFixed(2) }} Ghz</span>
</div>
@ -168,7 +167,7 @@
<div class="resource-detail-item" v-if="'memory' in resource">
<div class="resource-detail-item__label">{{ $t('label.memory') }}</div>
<div class="resource-detail-item__details">
<a-icon type="bulb" />{{ resource.memory + ' ' + $t('label.mb.memory') }}
<bulb-outlined />{{ resource.memory + ' ' + $t('label.mb.memory') }}
</div>
<div>
<span v-if="resource.memorykbs && resource.memoryintfreekbs">
@ -185,7 +184,7 @@
<div class="resource-detail-item" v-else-if="resource.memorytotalgb">
<div class="resource-detail-item__label">{{ $t('label.memory') }}</div>
<div class="resource-detail-item__details">
<a-icon type="bulb" />{{ resource.memorytotalgb + ' ' + $t('label.memory') }}
<bulb-outlined />{{ resource.memorytotalgb + ' ' + $t('label.memory') }}
</div>
<div>
<span v-if="resource.memoryusedgb">
@ -213,7 +212,7 @@
<div style="display: flex; flex-direction: column; width: 100%;">
<div>
<a-icon type="bulb" />{{ resource.memorytotal + ' ' + $t('label.memory') }}
<bulb-outlined />{{ resource.memorytotal + ' ' + $t('label.memory') }}
</div>
<div>
<span
@ -241,7 +240,7 @@
<div class="resource-detail-item" v-if="resource.volumes || resource.sizegb">
<div class="resource-detail-item__label">{{ $t('label.disksize') }}</div>
<div class="resource-detail-item__details">
<a-icon type="hdd" />
<hdd-outlined />
<span style="width: 100%;" v-if="$route.meta.name === 'vm' && resource.volumes">{{ (resource.volumes.reduce((total, item) => total += item.size, 0) / (1024 * 1024 * 1024.0)).toFixed(2) }} GB Storage</span>
<span style="width: 100%;" v-else-if="resource.sizegb || resource.size">{{ resource.sizegb || (resource.size/1024.0) }}</span>
</div>
@ -255,7 +254,7 @@
<div class="resource-detail-item" v-else-if="resource.disksizetotalgb">
<div class="resource-detail-item__label">{{ $t('label.disksize') }}</div>
<div class="resource-detail-item__details">
<a-icon type="database" />{{ resource.disksizetotalgb }}
<database-outlined />{{ resource.disksizetotalgb }}
</div>
<div>
<span v-if="resource.disksizeusedgb">
@ -279,19 +278,18 @@
<div class="resource-detail-item" v-if="resource.nic || ('networkkbsread' in resource && 'networkkbswrite' in resource)">
<div class="resource-detail-item__label">{{ $t('label.network') }}</div>
<div class="resource-detail-item__details resource-detail-item__details--start">
<a-icon type="wifi" />
<wifi-outlined />
<div>
<div v-if="'networkkbsread' in resource && 'networkkbswrite' in resource">
<a-tag><a-icon type="arrow-down" />RX {{ toSize(resource.networkkbsread) }}</a-tag>
<a-tag><a-icon type="arrow-up" />TX {{ toSize(resource.networkkbswrite) }}</a-tag>
<a-tag><ArrowDownOutlined />RX {{ toSize(resource.networkkbsread) }}</a-tag>
<a-tag><ArrowUpOutlined />TX {{ toSize(resource.networkkbswrite) }}</a-tag>
</div>
<div v-else>{{ resource.nic.length }} NIC(s)</div>
<div
v-if="resource.nic"
v-for="(eth, index) in resource.nic"
:key="eth.id"
style="margin-left: -24px; margin-top: 5px;">
<a-icon type="api" />eth{{ index }} {{ eth.ipaddress }}
<api-outlined />eth{{ index }} {{ eth.ipaddress }}
<router-link v-if="!isStatic && eth.networkname && eth.networkid" :to="{ path: '/guestnetwork/' + eth.networkid }">({{ eth.networkname }})</router-link>
<a-tag v-if="eth.isdefault">
{{ $t('label.default') }}
@ -308,7 +306,7 @@
v-for="network in resource.networks"
:key="network.id"
style="margin-top: 5px;">
<a-icon type="api" />{{ network.name }}
<api-outlined />{{ network.name }}
<span v-if="resource.defaultnetworkid === network.id">
({{ $t('label.default') }})
</span>
@ -319,8 +317,7 @@
<div class="resource-detail-item" v-if="resource.ipaddress">
<div class="resource-detail-item__label">{{ $t('label.ip') }}</div>
<div class="resource-detail-item__details">
<a-icon
type="environment"
<environment-outlined
@click="$message.success(`${$t('label.copied.clipboard')} : ${ ipaddress }`)"
v-clipboard:copy="ipaddress" />
<router-link v-if="!isStatic && resource.ipaddressid" :to="{ path: '/publicip/' + resource.ipaddressid }">{{ ipaddress }}</router-link>
@ -330,8 +327,7 @@
<div class="resource-detail-item" v-if="ipV6Address && ipV6Address !== null">
<div class="resource-detail-item__label">{{ $t('label.ip6address') }}</div>
<div class="resource-detail-item__details">
<a-icon
type="environment"
<environment-outlined
@click="$message.success(`${$t('label.copied.clipboard')} : ${ ipV6Address }`)"
v-clipboard:copy="ipV6Address" />
{{ ipV6Address }}
@ -343,7 +339,7 @@
<span v-if="images.project">
<resource-icon :image="getImage(images.project)" size="1x" style="margin-right: 5px"/>
</span>
<a-icon v-else type="project" />
<project-outlined v-else />
<router-link v-if="!isStatic && resource.projectid" :to="{ path: '/project/' + resource.projectid }">{{ resource.project || resource.projectname || resource.projectid }}</router-link>
<router-link v-else :to="{ path: '/project', query: { name: resource.projectname }}">{{ resource.projectname }}</router-link>
</div>
@ -357,14 +353,14 @@
<div class="resource-detail-item" v-if="resource.groupid">
<div class="resource-detail-item__label">{{ $t('label.group') }}</div>
<div class="resource-detail-item__details">
<a-icon type="gold" />
<gold-outlined />
<router-link :to="{ path: '/vmgroup/' + resource.groupid }">{{ resource.group || resource.groupid }}</router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.keypairs">
<div class="resource-detail-item" v-if="resource.keypairs && resource.keypairs.length > 0">
<div class="resource-detail-item__label">{{ $t('label.keypairs') }}</div>
<div class="resource-detail-item__details">
<a-icon type="key" />
<key-outlined />
<li v-for="keypair in keypairs" :key="keypair">
<router-link :to="{ path: '/ssh/' + keypair }" style="margin-right: 5px">{{ keypair }}</router-link>
</li>
@ -373,7 +369,7 @@
<div class="resource-detail-item" v-if="resource.virtualmachineid">
<div class="resource-detail-item__label">{{ $t('label.vmname') }}</div>
<div class="resource-detail-item__details">
<a-icon type="desktop" />
<desktop-outlined />
<router-link :to="{ path: '/vm/' + resource.virtualmachineid }">{{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} </router-link>
<status class="status status--end" :text="resource.vmstate" v-if="resource.vmstate"/>
</div>
@ -381,28 +377,28 @@
<div class="resource-detail-item" v-if="resource.volumeid">
<div class="resource-detail-item__label">{{ $t('label.volume') }}</div>
<div class="resource-detail-item__details">
<a-icon type="hdd" />
<hdd-outlined />
<router-link :to="{ path: '/volume/' + resource.volumeid }">{{ resource.volumename || resource.volume || resource.volumeid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.associatednetworkid">
<div class="resource-detail-item__label">{{ $t('label.associatednetwork') }}</div>
<div class="resource-detail-item__details">
<a-icon type="wifi" />
<wifi-outlined />
<router-link :to="{ path: '/guestnetwork/' + resource.associatednetworkid }">{{ resource.associatednetworkname || resource.associatednetworkid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.sourceipaddressnetworkid">
<div class="resource-detail-item__label">{{ $t('label.network') }}</div>
<div class="resource-detail-item__details">
<a-icon type="wifi" />
<wifi-outlined />
<router-link :to="{ path: '/guestnetwork/' + resource.sourceipaddressnetworkid }">{{ resource.sourceipaddressnetworkname || resource.sourceipaddressnetworkid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.guestnetworkid">
<div class="resource-detail-item__label">{{ $t('label.guestnetwork') }}</div>
<div class="resource-detail-item__details">
<a-icon type="gateway" />
<gateway-outlined />
<router-link :to="{ path: '/guestnetwork/' + resource.guestnetworkid }">{{ resource.guestnetworkname || resource.guestnetworkid }} </router-link>
</div>
</div>
@ -412,7 +408,7 @@
<span v-if="images.vpc">
<resource-icon :image="getImage(images.vpc)" size="1x" style="margin-right: 5px"/>
</span>
<a-icon v-else type="deployment-unit" />
<deployment-unit-outlined v-else />
<router-link :to="{ path: '/vpc/' + resource.vpcid }">{{ resource.vpcname || resource.vpcid }}</router-link>
</div>
</div>
@ -423,14 +419,14 @@
<span v-if="images.acl">
<resource-icon :image="getImage(images.acl)" size="1x" style="margin-right: 5px"/>
</span>
<a-icon v-else type="deployment-unit" />
<deployment-unit-outlined v-else />
<router-link :to="{ path: '/acllist/' + resource.aclid }">{{ resource.aclname || resource.aclid }}</router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.affinitygroup && resource.affinitygroup.length > 0">
<div class="resource-detail-item__label">{{ $t('label.affinitygroup') }}</div>
<a-icon type="swap" />
<SwapOutlined />
<span
v-for="(group, index) in resource.affinitygroup"
:key="group.id"
@ -443,7 +439,7 @@
<div class="resource-detail-item__label">{{ resource.isoid ? $t('label.iso') : $t('label.templatename') }}</div>
<div class="resource-detail-item__details">
<resource-icon v-if="resource.icon" :image="getImage(resource.icon.base64image)" size="1x" style="margin-right: 5px"/>
<a-icon v-else type="picture" />
<PictureOutlined v-else />
<div v-if="resource.isoid">
<router-link :to="{ path: '/iso/' + resource.isoid }">{{ resource.isodisplaytext || resource.isoname || resource.isoid }} </router-link>
</div>
@ -455,47 +451,47 @@
<div class="resource-detail-item" v-if="resource.serviceofferingname && resource.serviceofferingid">
<div class="resource-detail-item__label">{{ $t('label.serviceofferingname') }}</div>
<div class="resource-detail-item__details">
<a-icon type="cloud" />
<cloud-outlined />
<router-link v-if="!isStatic && $route.meta.name === 'router'" :to="{ path: '/computeoffering/' + resource.serviceofferingid, query: { issystem: true } }">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
<router-link v-else-if="$router.resolve('/computeoffering/' + resource.serviceofferingid).route.name !== '404'" :to="{ path: '/computeoffering/' + resource.serviceofferingid }">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
<router-link v-else-if="$router.resolve('/computeoffering/' + resource.serviceofferingid).name !== '404'" :to="{ path: '/computeoffering/' + resource.serviceofferingid }">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
<span v-else>{{ resource.serviceofferingname || resource.serviceofferingid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.diskofferingname && resource.diskofferingid">
<div class="resource-detail-item__label">{{ $t('label.diskoffering') }}</div>
<div class="resource-detail-item__details">
<a-icon type="hdd" />
<router-link v-if="!isStatic && $router.resolve('/diskoffering/' + resource.diskofferingid).route.name !== '404'" :to="{ path: '/diskoffering/' + resource.diskofferingid }">{{ resource.diskofferingname || resource.diskofferingid }} </router-link>
<hdd-outlined />
<router-link v-if="!isStatic && $router.resolve('/diskoffering/' + resource.diskofferingid).name !== '404'" :to="{ path: '/diskoffering/' + resource.diskofferingid }">{{ resource.diskofferingname || resource.diskofferingid }} </router-link>
<span v-else>{{ resource.diskofferingname || resource.diskofferingid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.backupofferingid">
<div class="resource-detail-item__label">{{ $t('label.backupofferingid') }}</div>
<a-icon type="cloud-upload" />
<router-link v-if="!isStatic && $router.resolve('/backupoffering/' + resource.backupofferingid).route.name !== '404'" :to="{ path: '/backupoffering/' + resource.backupofferingid }">{{ resource.backupofferingname || resource.backupofferingid }} </router-link>
<cloud-upload-outlined />
<router-link v-if="!isStatic && $router.resolve('/backupoffering/' + resource.backupofferingid).name !== '404'" :to="{ path: '/backupoffering/' + resource.backupofferingid }">{{ resource.backupofferingname || resource.backupofferingid }} </router-link>
<span v-else>{{ resource.backupofferingname || resource.backupofferingid }}</span>
</div>
<div class="resource-detail-item" v-if="resource.networkofferingid">
<div class="resource-detail-item__label">{{ $t('label.networkofferingid') }}</div>
<div class="resource-detail-item__details">
<a-icon type="wifi" />
<router-link v-if="!isStatic && $router.resolve('/networkoffering/' + resource.networkofferingid).route.name !== '404'" :to="{ path: '/networkoffering/' + resource.networkofferingid }">{{ resource.networkofferingname || resource.networkofferingid }} </router-link>
<wifi-outlined />
<router-link v-if="!isStatic && $router.resolve('/networkoffering/' + resource.networkofferingid).name !== '404'" :to="{ path: '/networkoffering/' + resource.networkofferingid }">{{ resource.networkofferingname || resource.networkofferingid }} </router-link>
<span v-else>{{ resource.networkofferingname || resource.networkofferingid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.vpcofferingid">
<div class="resource-detail-item__label">{{ $t('label.vpcoffering') }}</div>
<div class="resource-detail-item__details">
<a-icon type="deployment-unit" />
<router-link v-if="!isStatic && $router.resolve('/vpcoffering/' + resource.vpcofferingid).route.name !== '404'" :to="{ path: '/vpcoffering/' + resource.vpcofferingid }">{{ resource.vpcofferingname || resource.vpcofferingid }} </router-link>
<DeploymentUnitOutlined />
<router-link v-if="!isStatic && $router.resolve('/vpcoffering/' + resource.vpcofferingid).name !== '404'" :to="{ path: '/vpcoffering/' + resource.vpcofferingid }">{{ resource.vpcofferingname || resource.vpcofferingid }} </router-link>
<span v-else>{{ resource.vpcofferingname || resource.vpcofferingid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.storageid">
<div class="resource-detail-item__label">{{ $t('label.storagepool') }}</div>
<div class="resource-detail-item__details">
<a-icon type="database" />
<router-link v-if="!isStatic && $router.resolve('/storagepool/' + resource.storageid).route.name !== '404'" :to="{ path: '/storagepool/' + resource.storageid }">{{ resource.storage || resource.storageid }} </router-link>
<database-outlined />
<router-link v-if="!isStatic && $router.resolve('/storagepool/' + resource.storageid).name !== '404'" :to="{ path: '/storagepool/' + resource.storageid }">{{ resource.storage || resource.storageid }} </router-link>
<span v-else>{{ resource.storage || resource.storageid }}</span>
<a-tag style="margin-left: 5px;" v-if="resource.storagetype">
{{ resource.storagetype }}
@ -505,24 +501,24 @@
<div class="resource-detail-item" v-if="resource.hostid">
<div class="resource-detail-item__label">{{ $t('label.hostname') }}</div>
<div class="resource-detail-item__details">
<a-icon type="desktop" />
<router-link v-if="!isStatic && $router.resolve('/host/' + resource.hostid).route.name !== '404'" :to="{ path: '/host/' + resource.hostid }">{{ resource.hostname || resource.hostid }} </router-link>
<desktop-outlined />
<router-link v-if="!isStatic && $router.resolve('/host/' + resource.hostid).name !== '404'" :to="{ path: '/host/' + resource.hostid }">{{ resource.hostname || resource.hostid }} </router-link>
<span v-else>{{ resource.hostname || resource.hostid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.clusterid">
<div class="resource-detail-item__label">{{ $t('label.clusterid') }}</div>
<div class="resource-detail-item__details">
<a-icon type="cluster" />
<router-link v-if="!isStatic && $router.resolve('/cluster/' + resource.clusterid).route.name !== '404'" :to="{ path: '/cluster/' + resource.clusterid }">{{ resource.clustername || resource.cluster || resource.clusterid }}</router-link>
<cluster-outlined />
<router-link v-if="!isStatic && $router.resolve('/cluster/' + resource.clusterid).name !== '404'" :to="{ path: '/cluster/' + resource.clusterid }">{{ resource.clustername || resource.cluster || resource.clusterid }}</router-link>
<span v-else>{{ resource.clustername || resource.cluster || resource.clusterid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.podid">
<div class="resource-detail-item__label">{{ $t('label.podid') }}</div>
<div class="resource-detail-item__details">
<a-icon type="appstore" />
<router-link v-if="!isStatic && $router.resolve('/pod/' + resource.podid).route.name !== '404'" :to="{ path: '/pod/' + resource.podid }">{{ resource.podname || resource.pod || resource.podid }}</router-link>
<appstore-outlined />
<router-link v-if="!isStatic && $router.resolve('/pod/' + resource.podid).name !== '404'" :to="{ path: '/pod/' + resource.podid }">{{ resource.podname || resource.pod || resource.podid }}</router-link>
<span v-else>{{ resource.podname || resource.pod || resource.podid }}</span>
</div>
</div>
@ -532,17 +528,17 @@
<span v-if="images.zone">
<resource-icon :image="getImage(images.zone)" size="1x" style="margin-right: 5px"/>
</span>
<a-icon v-else type="global" />
<router-link v-if="!isStatic && $router.resolve('/zone/' + resource.zoneid).route.name !== '404'" :to="{ path: '/zone/' + resource.zoneid }">{{ resource.zone || resource.zonename || resource.zoneid }}</router-link>
<global-outlined v-else />
<router-link v-if="!isStatic && $router.resolve('/zone/' + resource.zoneid).name !== '404'" :to="{ path: '/zone/' + resource.zoneid }">{{ resource.zone || resource.zonename || resource.zoneid }}</router-link>
<span v-else>{{ resource.zone || resource.zonename || resource.zoneid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.owner">
<div class="resource-detail-item__label">{{ $t('label.owners') }}</div>
<div class="resource-detail-item__details">
<a-icon type="user" />
<template v-for="(item,idx) in resource.owner">
<span style="margin-right:5px" :key="idx">
<user-outlined />
<template v-for="(item, idx) in resource.owner" :key="idx">
<span style="margin-right:5px">
<span v-if="$store.getters.userInfo.roletype !== 'User'">
<router-link v-if="!isStatic && 'user' in item" :to="{ path: '/accountuser', query: { username: item.user, domainid: resource.domainid }}">{{ item.account + '(' + item.user + ')' }}</router-link>
<router-link v-else :to="{ path: '/account', query: { name: item.account, domainid: resource.domainid } }">{{ item.account }}</router-link>
@ -558,7 +554,7 @@
<span v-if="images.account">
<resource-icon :image="getImage(images.account)" size="1x" style="margin-right: 5px"/>
</span>
<a-icon v-else type="user" />
<user-outlined v-else />
<router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/account', query: { name: resource.account, domainid: resource.domainid } }">{{ resource.account }}</router-link>
<span v-else>{{ resource.account }}</span>
</div>
@ -566,8 +562,8 @@
<div class="resource-detail-item" v-if="resource.roleid">
<div class="resource-detail-item__label">{{ $t('label.role') }}</div>
<div class="resource-detail-item__details">
<a-icon type="idcard" />
<router-link v-if="!isStatic && $router.resolve('/role/' + resource.roleid).route.name !== '404'" :to="{ path: '/role/' + resource.roleid }">{{ resource.rolename || resource.role || resource.roleid }}</router-link>
<idcard-outlined />
<router-link v-if="!isStatic && $router.resolve('/role/' + resource.roleid).name !== '404'" :to="{ path: '/role/' + resource.roleid }">{{ resource.rolename || resource.role || resource.roleid }}</router-link>
<span v-else>{{ resource.rolename || resource.role || resource.roleid }}</span>
</div>
</div>
@ -575,7 +571,7 @@
<div class="resource-detail-item__label">{{ $t('label.domain') }}</div>
<div class="resource-detail-item__details">
<resource-icon v-if="images.domain" :image="getImage(images.domain)" size="1x" style="margin-right: 5px"/>
<a-icon v-else type="block" />
<block-outlined v-else />
<router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + resource.domainid + '?tab=details' }">{{ resource.domain || resource.domainid }}</router-link>
<span v-else>{{ resource.domain || resource.domainid }}</span>
</div>
@ -583,21 +579,21 @@
<div class="resource-detail-item" v-if="resource.managementserverid">
<div class="resource-detail-item__label">{{ $t('label.management.servers') }}</div>
<div class="resource-detail-item__details">
<a-icon type="rocket" />
<router-link v-if="!isStatic && $router.resolve('/managementserver/' + resource.managementserverid).route.name !== '404'" :to="{ path: '/managementserver/' + resource.managementserverid }">{{ resource.managementserver || resource.managementserverid }}</router-link>
<rocket-outlined />
<router-link v-if="!isStatic && $router.resolve('/managementserver/' + resource.managementserverid).name !== '404'" :to="{ path: '/managementserver/' + resource.managementserverid }">{{ resource.managementserver || resource.managementserverid }}</router-link>
<span v-else>{{ resource.managementserver || resource.managementserverid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.created">
<div class="resource-detail-item__label">{{ $t('label.created') }}</div>
<div class="resource-detail-item__details">
<a-icon type="calendar" />{{ $toLocaleDate(resource.created) }}
<calendar-outlined />{{ $toLocaleDate(resource.created) }}
</div>
</div>
<div class="resource-detail-item" v-if="resource.lastupdated">
<div class="resource-detail-item__label">{{ $t('label.last.updated') }}</div>
<div class="resource-detail-item__details">
<a-icon type="calendar" />{{ $toLocaleDate(resource.lastupdated) }}
<calendar-outlined />{{ $toLocaleDate(resource.lastupdated) }}
</div>
</div>
</div>
@ -606,9 +602,12 @@
<a-divider/>
<div v-for="item in $route.meta.related" :key="item.path">
<router-link
v-if="$router.resolve('/' + item.name).route.name !== '404'"
:to="{ path: '/' + item.name + '?' + item.param + '=' + (item.value ? resource[item.value] : item.param === 'account' ? resource.name + '&domainid=' + resource.domainid : item.param === 'keypair' ? resource.name : resource.id) }">
<a-button style="margin-right: 10px" :icon="$router.resolve('/' + item.name).route.meta.icon" >
v-if="$router.resolve('/' + item.name).name !== '404'"
:to="{ name: item.name, query: getRouterQuery(item) }">
<a-button style="margin-right: 10px">
<template #icon>
<render-icon :icon="$router.resolve('/' + item.name).meta.icon" />
</template>
{{ $t('label.view') + ' ' + $t(item.title) }}
</a-button>
</router-link>
@ -618,34 +617,34 @@
<div class="account-center-tags" v-if="showKeys">
<a-divider/>
<div class="user-keys">
<a-icon type="key" />
<key-outlined />
<strong>
{{ $t('label.apikey') }}
<tooltip-button
tooltipPlacement="right"
:tooltip="$t('label.copy') + ' ' + $t('label.apikey')"
icon="copy"
icon="CopyOutlined"
type="dashed"
size="small"
@click="$message.success($t('label.copied.clipboard'))"
v-clipboard:copy="resource.apikey" />
@onClick="$message.success($t('label.copied.clipboard'))"
:copyResource="resource.apikey" />
</strong>
<div>
{{ resource.apikey.substring(0, 20) }}...
</div>
</div> <br/>
<div class="user-keys">
<a-icon type="lock" />
<lock-outlined />
<strong>
{{ $t('label.secretkey') }}
<tooltip-button
tooltipPlacement="right"
:tooltip="$t('label.copy') + ' ' + $t('label.secretkey')"
icon="copy"
icon="CopyOutlined"
type="dashed"
size="small"
@click="$message.success($t('label.copied.clipboard'))"
v-clipboard:copy="resource.secretkey" />
@onClick="$message.success($t('label.copied.clipboard'))"
:copyResource="resource.secretkey" />
</strong>
<div>
{{ resource.secretkey.substring(0, 20) }}...
@ -658,8 +657,8 @@
<a-spin :spinning="loadingTags">
<div class="title">{{ $t('label.tags') }}</div>
<div>
<template v-for="(tag, index) in tags">
<a-tag :key="index" :closable="isAdminOrOwner() && 'deleteTags' in $store.getters.apis" :afterClose="() => handleDeleteTag(tag)">
<template v-for="(tag, index) in tags" :key="index">
<a-tag :closable="isAdminOrOwner() && 'deleteTags' in $store.getters.apis" @close="() => handleDeleteTag(tag)">
{{ tag.key }} = {{ tag.value }}
</a-tag>
</template>
@ -678,8 +677,8 @@
placeholder="="
disabled />
<a-input :value="inputValue" @change="handleValueChange" style="width: 30%; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
<tooltip-button :tooltip="$t('label.ok')" icon="check" size="small" @click="handleInputConfirm" />
<tooltip-button :tooltip="$t('label.cancel')" icon="close" size="small" @click="inputVisible=false" />
<tooltip-button :tooltip="$t('label.ok')" icon="CheckOutlined" size="small" @onClick="handleInputConfirm" />
<tooltip-button :tooltip="$t('label.cancel')" icon="CloseOutlined" size="small" @onClick="inputVisible=false" />
</a-input-group>
</div>
<a-tag
@ -687,7 +686,7 @@
class="btn-add-tag"
style="borderStyle: dashed;"
v-else-if="isAdminOrOwner() && 'createTags' in $store.getters.apis">
<a-icon type="plus" /> {{ $t('label.new.tag') }}
<plus-outlined /> {{ $t('label.new.tag') }}
</a-tag>
</div>
</a-spin>
@ -697,8 +696,8 @@
</template>
<script>
import { api } from '@/api'
import RenderIcon from '@/utils/renderIcon'
import Console from '@/components/widgets/Console'
import OsLogo from '@/components/widgets/OsLogo'
import Status from '@/components/widgets/Status'
@ -715,7 +714,8 @@ export default {
Status,
TooltipButton,
UploadResourceIcon,
ResourceIcon
ResourceIcon,
RenderIcon
},
props: {
resource: {
@ -748,9 +748,7 @@ export default {
inputValue: '',
tags: [],
showKeys: false,
showNotesInput: false,
loadingTags: false,
loadingAnnotations: false,
showUpload: false,
images: {
zone: '',
@ -761,41 +759,46 @@ export default {
project: '',
vpc: '',
network: ''
}
},
newResource: {}
}
},
watch: {
$route: function () {
'$route.fullPath': function () {
this.getIcons()
},
resource: function (newItem, oldItem) {
this.resource = newItem
this.resourceType = this.$route.meta.resourceType
this.showKeys = false
this.setData()
resource: {
deep: true,
handler (newData, oldData) {
if (newData === oldData) return
this.newResource = newData
this.resourceType = this.$route.meta.resourceType
this.showKeys = false
this.setData()
if (this.tagsSupportingResourceTypes.includes(this.resourceType)) {
if ('tags' in this.resource) {
this.tags = this.resource.tags
} else if (this.resourceType) {
this.getTags()
if (this.tagsSupportingResourceTypes.includes(this.resourceType)) {
if ('tags' in this.resource) {
this.tags = this.resource.tags
} else if (this.resourceType) {
this.getTags()
}
}
if ('apikey' in this.resource) {
this.getUserKeys()
}
this.getIcons()
}
if ('apikey' in this.resource) {
this.getUserKeys()
}
this.getIcons()
},
async templateIcon () {
this.getIcons()
}
},
async created () {
created () {
this.setData()
eventBus.$on('handle-close', (showModal) => {
eventBus.on('handle-close', (showModal) => {
this.showUploadModal(showModal)
})
await this.getIcons()
this.getIcons()
},
computed: {
tagsSupportingResourceTypes () {
@ -834,9 +837,6 @@ export default {
return null
}
},
async mounted () {
this.getIcons()
},
methods: {
showUploadModal (show) {
if (show) {
@ -948,7 +948,8 @@ export default {
}
api('getUserKeys', { id: this.resource.id }).then(json => {
this.showKeys = true
this.resource.secretkey = json.getuserkeysresponse.userkeys.secretkey
this.newResource.secretkey = json.getuserkeysresponse.userkeys.secretkey
this.$emit('change-resource', this.newResource)
})
},
getTags () {
@ -976,7 +977,7 @@ export default {
isAdminOrOwner () {
return ['Admin'].includes(this.$store.getters.userInfo.roletype) ||
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
this.resource.project && this.resource.projectid === this.$store.getters.project.id
(this.resource.project && this.resource.projectid === this.$store.getters.project.id)
},
showInput () {
this.inputVisible = true
@ -1017,14 +1018,34 @@ export default {
}).finally(e => {
this.getTags()
})
},
setResourceOsType (name) {
this.newResource.ostypename = name
this.$emit('change-resource', this.newResource)
},
getRouterQuery (item) {
const query = {}
if (item.value) {
query[item.param] = this.resource[item.value]
} else {
if (item.param === 'account') {
query[item.param] = this.resource.name
query.domainid = this.resource.domainid
} else if (item.param === 'keypair') {
query[item.param] = this.resource.name
} else {
query[item.param] = this.resource.id
}
}
return query
}
}
}
</script>
<style lang="scss" scoped>
/deep/ .ant-card-body {
:deep(.ant-card-body) {
padding: 30px;
}
@ -1125,15 +1146,6 @@ export default {
width: 100%;
}
.status {
margin-top: -5px;
&--end {
margin-left: 5px;
}
}
.upload-icon {
position: absolute;
top: 70px;

View File

@ -25,22 +25,22 @@
:dataSource="nics"
:pagination="false"
:rowKey="record => record.InstanceID">
<template slot="displaytext" slot-scope="record">
<template #displaytext="{record}">
<span>{{ record.elementName + ' - ' + record.name }}
<a-tooltip :title="record.nicDescription" placement="top">
<a-icon type="info-circle" class="table-tooltip-icon" />
<info-circle-outlined class="table-tooltip-icon" />
</a-tooltip>
</span>
</template>
<div slot="size" slot-scope="record">
<template #size="{record}">
<span v-if="record.size">
{{ $bytesToHumanReadableSize(record.size) }}
</span>
</div>
<template slot="selectednetwork" slot-scope="record">
</template>
<template #selectednetwork="{record}">
<span>{{ record.selectednetworkname || '' }}</span>
</template>
<template slot="select" slot-scope="record">
<template #select="{record}">
<div style="display: flex; justify-content: flex-end;"><a-button @click="openNicNetworkSelector(record)">{{ record.selectednetworkid ? $t('label.change') : $t('label.select') }}</a-button></div>
</template>
</a-table>
@ -88,31 +88,21 @@ export default {
nicColumns: [
{
title: this.$t('label.nic'),
scopedSlots: { customRender: 'displaytext' }
slots: { customRender: 'displaytext' }
},
{
title: this.$t('label.network'),
scopedSlots: { customRender: 'selectednetwork' }
slots: { customRender: 'selectednetwork' }
},
{
title: '',
scopedSlots: { customRender: 'select' }
slots: { customRender: 'select' }
}
],
selectedNicForNetworkSelection: {}
}
},
methods: {
resetSelection () {
var nics = this.nics
this.nics = []
for (var nic of nics) {
nic.selectednetworkid = null
nic.selectednetworkname = ''
}
this.nics = nics
this.updateNicToNetworkSelection()
},
openNicNetworkSelector (nic) {
this.selectedNicForNetworkSelection = nic
},

View File

@ -26,15 +26,15 @@
:dataSource="volumes"
:pagination="false"
:rowKey="record => record.id">
<div slot="size" slot-scope="record">
<template #size="{ record }">
<span v-if="record.size">
{{ $bytesToHumanReadableSize(record.size) }}
</span>
</div>
<template slot="selectedstorage" slot-scope="record">
</template>
<template #selectedstorage="{ record }">
<span>{{ record.selectedstoragename || '' }}</span>
</template>
<template slot="select" slot-scope="record">
<template #select="{ record }">
<div style="display: flex; justify-content: flex-end;"><a-button @click="openVolumeStoragePoolSelector(record)">{{ record.selectedstorageid ? $t('label.change') : $t('label.select') }}</a-button></div>
</template>
</a-table>
@ -95,15 +95,15 @@ export default {
},
{
title: this.$t('label.size'),
scopedSlots: { customRender: 'size' }
slots: { customRender: 'size' }
},
{
title: this.$t('label.storage'),
scopedSlots: { customRender: 'selectedstorage' }
slots: { customRender: 'selectedstorage' }
},
{
title: '',
scopedSlots: { customRender: 'select' }
slots: { customRender: 'select' }
}
],
selectedVolumeForStoragePoolSelection: {},
@ -112,7 +112,6 @@ export default {
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
this.apiParams = {}
if (this.$route.meta.name === 'vm') {
this.apiConfig = this.$store.getters.apis.migrateVirtualMachineWithVolume || {}

View File

@ -21,9 +21,9 @@
v-if="showSearch"
style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
:placeholder="$t('label.search')"
v-model="filter"
v-model:value="filter"
@search="handleSearch"
autoFocus />
v-focus="true" />
<a-table
size="small"
@ -35,17 +35,18 @@
@change="handleTableChange"
@handle-search-filter="handleTableChange" >
<template v-for="(column, index) in Object.keys(routerlinks({}))" :slot="column" slot-scope="text, item" >
<span :key="index">
<router-link :set="routerlink = routerlinks(item)" :to="{ path: routerlink[column] }" >{{ text }}</router-link>
</span>
<template
v-for="(column, index) in Object.keys(routerlinks({}))"
:key="index"
#[column]="{ text, record }" >
<router-link :set="routerlink = routerlinks(record)" :to="{ path: routerlink[column] }" >{{ text }}</router-link>
</template>
<template slot="state" slot-scope="text">
<template #state="{text}">
<status :text="text ? text : ''" />{{ text }}
</template>
<template slot="status" slot-scope="text">
<template #status="{text}">
<status :text="text ? text : ''" />{{ text }}
</template>
@ -62,7 +63,7 @@
@change="handleTableChange"
@showSizeChange="handlePageSizeChange"
showSizeChanger>
<template slot="buildOptionText" slot-scope="props">
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
@ -127,17 +128,23 @@ export default {
}
},
watch: {
resource (newItem, oldItem) {
if (newItem !== oldItem) {
this.fetchData()
resource: {
deep: true,
handler (newItem, oldItem) {
if (newItem !== oldItem) {
this.fetchData()
}
}
},
items (newItem, oldItem) {
if (newItem) {
this.dataSource = newItem
items: {
deep: true,
handler (newItem) {
if (newItem) {
this.dataSource = newItem
}
}
},
'$i18n.locale' (to, from) {
'$i18n.global.locale' (to, from) {
if (to !== from) {
this.fetchData()
}
@ -192,7 +199,7 @@ export default {
columns.push({
dataIndex: col,
title: this.$t('label.' + col),
scopedSlots: { customRender: col }
slots: { customRender: col }
})
}
return columns

View File

@ -27,21 +27,31 @@
:rowClassName="getRowClassName"
style="overflow-y: auto"
>
<template slot="footer">
<template #filterDropdown>
<div style="padding: 8px" class="filter-dropdown">
<a-menu>
<a-menu-item v-for="(column, idx) in columnKeys" :key="idx" @click="updateSelectedColumns(column)">
<a-checkbox :id="idx.toString()" :checked="selectedColumns.includes(getColumnKey(column))"/>
{{ $t('label.' + String(getColumnKey(column)).toLowerCase()) }}
</a-menu-item>
</a-menu>
</div>
</template>
<template #footer>
<span v-if="hasSelected">
{{ `Selected ${selectedRowKeys.length} items` }}
</span>
</template>
<!--
<div slot="expandedRowRender" slot-scope="resource">
<info-card :resource="resource" style="margin-left: 0px; width: 50%">
<div slot="actions" style="padding-top: 12px">
<div #expandedRowRender="{ resource }">
<info-card :resource="resource style="margin-left: 0px; width: 50%">
<div #actions style="padding-top: 12px">
<a-tooltip
v-for="(action, actionIndex) in $route.meta.actions"
:key="actionIndex"
placement="bottom">
<template slot="title">
<template #title>
{{ $t(action.label) }}
</template>
<a-button
@ -60,12 +70,12 @@
</div>
-->
<span slot="name" slot-scope="text, record">
<span v-if="['vm'].includes($route.path.split('/')[1])">
<template #name="{text, record}">
<span v-if="['vm'].includes($route.path.split('/')[1])" style="margin-right: 5px">
<span v-if="record.icon && record.icon.base64image">
<resource-icon :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
<resource-icon :image="record.icon.base64image" size="1x"/>
</span>
<os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="lg" style="margin-right: 5px" />
<os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="lg" />
</span>
<span style="min-width: 120px" >
<QuickView
@ -75,22 +85,22 @@
:enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'name' "
@exec-action="$parent.execAction"/>
<span v-if="$route.path.startsWith('/project')" style="margin-right: 5px">
<tooltip-button type="dashed" size="small" icon="login" @click="changeProject(record)" />
<tooltip-button type="dashed" size="small" icon="LoginOutlined" @onClick="changeProject(record)" />
</span>
<span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])">
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
<os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="1x" style="margin-right: 5px" />
<a-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px; margin-right: 5px" :type="$route.meta.icon"/>
<a-icon v-else style="font-size: 16px; margin-right: 5px" :component="$route.meta.icon" />
<span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])" style="margin-right: 5px">
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="1x"/>
<os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="1x" />
<render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px;" :icon="$route.meta.icon"/>
<render-icon v-else style="font-size: 16px;" :svgIcon="$route.meta.icon" />
</span>
<span v-else>
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" style="margin-right: 5px" />
<span v-else :style="{ 'margin-right': record.ostypename ? '5px' : '0' }">
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" />
</span>
<span v-if="record.hasannotations">
<span v-if="record.id">
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
<router-link :to="{ path: $route.path + '/' + record.id + '?tab=comments' }"><a-icon style="padding-left: 10px" size="small" type="message" theme="filled"/></router-link>
<router-link :to="{ path: $route.path + '/' + record.id, query: { tab: 'comments' } }"><message-filled style="padding-left: 10px" size="small"/></router-link>
</span>
<router-link v-else :to="{ path: $route.path + '/' + record.name }" >{{ text }}</router-link>
</span>
@ -104,108 +114,134 @@
<router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ text }}</router-link>
</span>
</span>
</span>
<a slot="templatetype" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: $route.path + '/' + record.templatetype }">{{ text }}</router-link>
</a>
<template slot="type" slot-scope="text">
</template>
<template #templatetype="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: $route.path + '/' + record.templatetype }">{{ text }}</router-link>
</a>
</template>
<template #type="{ text }">
<span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(text)">{{ $t(text.toLowerCase()) }}</span>
<span v-else>{{ text }}</span>
</template>
<a slot="displayname" slot-scope="text, record" href="javascript:;">
<QuickView
style="margin-left: 5px"
:actions="actions"
:resource="record"
:enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'displayname' "
@exec-action="$parent.execAction"/>
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
</a>
<span slot="username" slot-scope="text, record" href="javascript:;">
<span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])">
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
<a-icon v-else style="font-size: 16px; margin-right: 5px" type="user" />
</span>
<router-link :to="{ path: $route.path + '/' + record.id }" v-if="['/accountuser', '/vpnuser'].includes($route.path)">{{ text }}</router-link>
<router-link :to="{ path: '/accountuser', query: { username: record.username, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</span>
<span slot="entityid" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: generateCommentsPath(record) }">{{ record.entityname }}</router-link>
</span>
<span slot="entitytype" slot-scope="text, record" href="javascript:;">
<template #displayname="{text, record}">
<a href="javascript:;">
<QuickView
style="margin-left: 5px"
:actions="actions"
:resource="record"
:enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'displayname' "
@exec-action="$parent.execAction"/>
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
</a>
</template>
<template #username="{text, record}">
<a href="javascript:;">
<span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])" style="margin-right: 5px">
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="1x"/>
<user-outlined v-else style="font-size: 16px;" />
</span>
<router-link :to="{ path: $route.path + '/' + record.id }" v-if="['/accountuser', '/vpnuser'].includes($route.path)">{{ text }}</router-link>
<router-link :to="{ path: '/accountuser', query: { username: record.username, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</a>
</template>
<template #entityid="{ record }" href="javascript:;">
<router-link :to="{ path: generateCommentsPath(record), query: { tab: 'comments' } }">{{ record.entityname }}</router-link>
</template>
<template #entitytype="{ record }" href="javascript:;">
{{ generateHumanReadableEntityType(record) }}
</span>
<span slot="adminsonly" v-if="['Admin'].includes($store.getters.userInfo.roletype)" slot-scope="text, record" href="javascript:;">
</template>
<template #adminsonly="{ record }" v-if="['Admin'].includes($store.getters.userInfo.roletype)" href="javascript:;">
<a-checkbox :checked="record.adminsonly" :value="record.id" v-if="record.userid === $store.getters.userInfo.id" @change="e => updateAdminsOnly(e)" />
<a-checkbox :checked="record.adminsonly" disabled v-else />
</span>
<span slot="ipaddress" slot-scope="text, record" href="javascript:;">
</template>
<template #ipaddress="{ text, record }" href="javascript:;">
<router-link v-if="['/publicip', '/privategw'].includes($route.path)" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
<span v-if="record.issourcenat">
&nbsp;
<a-tag>source-nat</a-tag>
</span>
</span>
<span slot="ip6address" slot-scope="text, record" href="javascript:;">
</template>
<template #ip6address="{ text, record }" href="javascript:;">
<span>{{ ipV6Address(text, record) }}</span>
</span>
<a slot="publicip" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
</a>
<span slot="traffictype" slot-scope="text" href="javascript:;">
</template>
<template #publicip="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
</a>
</template>
<template #traffictype="{ text }" href="javascript:;">
{{ text }}
</span>
<a slot="vmname" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
</a>
<a slot="virtualmachinename" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
</a>
<span slot="hypervisor" slot-scope="text, record">
</template>
<template #vmname="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
</a>
</template>
<template #virtualmachinename="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
</a>
</template>
<template #hypervisor="{ text, record }">
<span v-if="$route.name === 'hypervisorcapability'">
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
</span>
<span v-else>{{ text }}</span>
</span>
<template slot="state" slot-scope="text, record">
</template>
<template #state="{ text, record }">
<status v-if="$route.path.startsWith('/host')" :text="getHostState(record)" displayText />
<status v-else :text="text ? text : ''" displayText style="min-width: 80px" />
<status v-else :text="text ? text : ''" displayText :styles="{ 'min-width': '80px' }" />
</template>
<template slot="allocationstate" slot-scope="text">
<template #allocationstate="{ text }">
<status :text="text ? text : ''" displayText />
</template>
<template slot="resourcestate" slot-scope="text">
<template #resourcestate="{ text }">
<status :text="text ? text : ''" displayText />
</template>
<template slot="powerstate" slot-scope="text">
<template #powerstate="{ text }">
<status :text="text ? text : ''" displayText />
</template>
<template slot="agentstate" slot-scope="text">
<template #agentstate="{ text }">
<status :text="text ? text : ''" displayText />
</template>
<a slot="guestnetworkname" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/guestnetwork/' + record.guestnetworkid }">{{ text }}</router-link>
</a>
<a slot="associatednetworkname" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/guestnetwork/' + record.associatednetworkid }">{{ text }}</router-link>
</a>
<a slot="vpcname" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/vpc/' + record.vpcid }">{{ text }}</router-link>
</a>
<a slot="hostname" slot-scope="text, record" href="javascript:;">
<router-link v-if="record.hostid" :to="{ path: '/host/' + record.hostid }">{{ text }}</router-link>
<router-link v-else-if="record.hostname" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</a>
<a slot="storage" slot-scope="text, record" href="javascript:;">
<router-link v-if="record.storageid" :to="{ path: '/storagepool/' + record.storageid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</a>
<template #guestnetworkname="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/guestnetwork/' + record.guestnetworkid }">{{ text }}</router-link>
</a>
</template>
<template #associatednetworkname="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/guestnetwork/' + record.associatednetworkid }">{{ text }}</router-link>
</a>
</template>
<template #vpcname="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/vpc/' + record.vpcid }">{{ text }}</router-link>
</a>
</template>
<template #hostname="{ text, record }">
<a href="javascript:;">
<router-link v-if="record.hostid" :to="{ path: '/host/' + record.hostid }">{{ text }}</router-link>
<router-link v-else-if="record.hostname" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</a>
</template>
<template #storage="{ text, record }">
<a href="javascript:;">
<router-link v-if="record.storageid" :to="{ path: '/storagepool/' + record.storageid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</a>
</template>
<template v-for="(value, name) in thresholdMapping" :slot="name" slot-scope="text, record" href="javascript:;">
<span :key="name">
<template
v-for="(value, name) in thresholdMapping"
:key="name"
#[name]="{ text, record }"
href="javascript:;">
<span>
<span v-if="record[value.disable]" class="alert-disable-threshold">
{{ text }}
</span>
@ -218,20 +254,26 @@
</span>
</template>
<a slot="level" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/event/' + record.id }">{{ text }}</router-link>
</a>
<template #level="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/event/' + record.id }">{{ text }}</router-link>
</a>
</template>
<a slot="clustername" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/cluster/' + record.clusterid }">{{ text }}</router-link>
</a>
<a slot="podname" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/pod/' + record.podid }">{{ text }}</router-link>
</a>
<span slot="account" slot-scope="text, record">
<template #clustername="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/cluster/' + record.clusterid }">{{ text }}</router-link>
</a>
</template>
<template #podname="{ text, record }">
<a href="javascript:;">
<router-link :to="{ path: '/pod/' + record.podid }">{{ text }}</router-link>
</a>
</template>
<template #account="{ text, record }">
<template v-if="record.owner">
<template v-for="(item,idx) in record.owner">
<span style="margin-right:5px" :key="idx">
<template v-for="(item, idx) in record.owner" :key="idx">
<span style="margin-right:5px">
<span v-if="$store.getters.userInfo.roletype !== 'User'">
<router-link v-if="'user' in item" :to="{ path: '/accountuser', query: { username: item.user, domainid: record.domainid }}">{{ item.account + '(' + item.user + ')' }}</router-link>
<router-link v-else :to="{ path: '/account', query: { name: item.account, domainid: record.domainid, dataView: true } }">{{ item.account }}</router-link>
@ -248,87 +290,91 @@
<router-link :to="{ path: '/account', query: { name: record.account, domainid: record.domainid, dataView: true } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</template>
</span>
<span slot="domain" slot-scope="text, record" href="javascript:;">
<router-link v-if="record.domainid && !record.domainid.toString().includes(',') && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + record.domainid + '?tab=details' }">{{ text }}</router-link>
</template>
<template #domain="{ text, record }">
<router-link v-if="record.domainid && !record.domainid.toString().includes(',') && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</span>
<span slot="domainpath" slot-scope="text, record" href="javascript:;">
<router-link v-if="record.domainid && !record.domainid.includes(',') && $router.resolve('/domain/' + record.domainid).route.name !== '404'" :to="{ path: '/domain/' + record.domainid + '?tab=details' }">{{ text }}</router-link>
</template>
<template #domainpath="{ text, record }">
<router-link v-if="record.domainid && !record.domainid.includes(',') && $router.resolve('/domain/' + record.domainid).name !== '404'" :to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</span>
<a slot="zone" slot-scope="text, record" href="javascript:;">
<router-link v-if="record.zoneid && !record.zoneid.includes(',') && $router.resolve('/zone/' + record.zoneid).route.name !== '404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
</template>
<template #zone="{ text, record }">
<a href="javascript:;">
<router-link v-if="record.zoneid && !record.zoneid.includes(',') && $router.resolve('/zone/' + record.zoneid).name !== '404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</a>
</template>
<template #zonename="{ text, record }">
<router-link v-if="$router.resolve('/zone/' + record.zoneid).name !== '404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</a>
<span slot="zonename" slot-scope="text, record">
<router-link v-if="$router.resolve('/zone/' + record.zoneid).route.name !== '404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
</template>
<template #rolename="{ text, record }">
<router-link v-if="record.roleid && $router.resolve('/role/' + record.roleid).name !== '404'" :to="{ path: '/role/' + record.roleid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</span>
<span slot="rolename" slot-scope="text, record">
<router-link v-if="record.roleid && $router.resolve('/role/' + record.roleid).route.name !== '404'" :to="{ path: '/role/' + record.roleid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</span>
<a slot="readonly" slot-scope="text, record">
</template>
<template #readonly="{ record }">
<status :text="record.readonly ? 'ReadOnly' : 'ReadWrite'" displayText />
</a>
<span slot="requiresupgrade" slot-scope="text, record">
</template>
<template #requiresupgrade="{ record }">
<status :text="record.requiresupgrade ? 'warning' : ''" />
{{ record.requiresupgrade ? 'Yes' : 'No' }}
</span>
<span slot="autoscalingenabled" slot-scope="text, record">
</template>
<template #autoscalingenabled="{ record }">
<status :text="record.autoscalingenabled ? 'Enabled' : 'Disabled'" />
{{ record.autoscalingenabled ? 'Enabled' : 'Disabled' }}
</span>
<span slot="current" slot-scope="text, record">
</template>
<template #current="{record}">
<status :text="record.current ? record.current.toString() : 'false'" />
</span>
<span slot="created" slot-scope="text">
</template>
<template #created="{ text }">
{{ $toLocaleDate(text) }}
</span>
<span slot="sent" slot-scope="text">
</template>
<template #sent="{ text }">
{{ $toLocaleDate(text) }}
</span>
<div slot="order" slot-scope="text, record" class="shift-btns">
<a-tooltip placement="top">
<template slot="title">{{ $t('label.move.to.top') }}</template>
<a-button
shape="round"
@click="moveItemTop(record)"
class="shift-btn">
<a-icon type="double-left" class="shift-btn shift-btn--rotated" />
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<template slot="title">{{ $t('label.move.to.bottom') }}</template>
<a-button
shape="round"
@click="moveItemBottom(record)"
class="shift-btn">
<a-icon type="double-right" class="shift-btn shift-btn--rotated" />
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<template slot="title">{{ $t('label.move.up.row') }}</template>
<a-button shape="round" @click="moveItemUp(record)" class="shift-btn">
<a-icon type="caret-up" class="shift-btn" />
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<template slot="title">{{ $t('label.move.down.row') }}</template>
<a-button shape="round" @click="moveItemDown(record)" class="shift-btn">
<a-icon type="caret-down" class="shift-btn" />
</a-button>
</a-tooltip>
</div>
</template>
<template #order="{ text, record }">
<div class="shift-btns">
<a-tooltip :name="text" placement="top">
<template #title>{{ $t('label.move.to.top') }}</template>
<a-button
shape="round"
@click="moveItemTop(record)"
class="shift-btn">
<DoubleLeftOutlined class="shift-btn shift-btn--rotated" />
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<template #title>{{ $t('label.move.to.bottom') }}</template>
<a-button
shape="round"
@click="moveItemBottom(record)"
class="shift-btn">
<DoubleRightOutlined class="shift-btn shift-btn--rotated" />
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<template #title>{{ $t('label.move.up.row') }}</template>
<a-button shape="round" @click="moveItemUp(record)" class="shift-btn">
<CaretUpOutlined class="shift-btn" />
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<template #title>{{ $t('label.move.down.row') }}</template>
<a-button shape="round" @click="moveItemDown(record)" class="shift-btn">
<CaretDownOutlined class="shift-btn" />
</a-button>
</a-tooltip>
</div>
</template>
<template slot="value" slot-scope="text, record">
<template #value="{ text, record }">
<a-input
v-if="editableValueKey === record.key"
:autoFocus="true"
v-focus="true"
:defaultValue="record.value"
:disabled="!('updateConfiguration' in $store.getters.apis)"
v-model="editableValue"
v-model:value="editableValue"
@keydown.esc="editableValueKey = null"
@pressEnter="saveValue(record)">
</a-input>
@ -336,40 +382,40 @@
{{ text }}
</div>
</template>
<template slot="actions" slot-scope="text, record">
<template #actions="{ record }">
<tooltip-button
:tooltip="$t('label.edit')"
:disabled="!('updateConfiguration' in $store.getters.apis)"
v-if="editableValueKey !== record.key"
icon="edit"
@click="editValue(record)" />
icon="edit-outlined"
@onClick="editValue(record)" />
<tooltip-button
:tooltip="$t('label.cancel')"
@click="editableValueKey = null"
@onClick="editableValueKey = null"
v-if="editableValueKey === record.key"
iconType="close-circle"
iconType="CloseCircleTwoTone"
iconTwoToneColor="#f5222d" />
<tooltip-button
:tooltip="$t('label.ok')"
:disabled="!('updateConfiguration' in $store.getters.apis)"
@click="saveValue(record)"
@onClick="saveValue(record)"
v-if="editableValueKey === record.key"
iconType="check-circle"
iconType="CheckCircleTwoTone"
iconTwoToneColor="#52c41a" />
<tooltip-button
:tooltip="$t('label.reset.config.value')"
@click="resetConfig(record)"
@onClick="resetConfig(record)"
v-if="editableValueKey !== record.key"
icon="reload"
icon="reload-outlined"
:disabled="!('updateConfiguration' in $store.getters.apis)" />
</template>
<template slot="tariffActions" slot-scope="text, record">
<template #tariffActions="{ record }">
<tooltip-button
:tooltip="$t('label.edit')"
v-if="editableValueKey !== record.key"
:disabled="!('quotaTariffUpdate' in $store.getters.apis)"
icon="edit"
@click="editTariffValue(record)" />
icon="edit-outlined"
@onClick="editTariffValue(record)" />
<slot></slot>
</template>
</a-table>
@ -377,30 +423,36 @@
<script>
import { api } from '@/api'
import Console from '@/components/widgets/Console'
import OsLogo from '@/components/widgets/OsLogo'
import Status from '@/components/widgets/Status'
import InfoCard from '@/components/view/InfoCard'
import QuickView from '@/components/view/QuickView'
import TooltipButton from '@/components/widgets/TooltipButton'
import ResourceIcon from '@/components/view/ResourceIcon'
import RenderIcon from '@/utils/renderIcon'
export default {
name: 'ListView',
components: {
Console,
OsLogo,
Status,
InfoCard,
QuickView,
TooltipButton,
ResourceIcon
ResourceIcon,
RenderIcon
},
props: {
columns: {
type: Array,
required: true
},
columnKeys: {
type: Array,
default: () => []
},
selectedColumns: {
type: Array,
default: () => []
},
items: {
type: Array,
required: true
@ -660,7 +712,7 @@ export default {
return record.nic.filter(e => { return e.ip6address }).map(e => { return e.ip6address }).join(', ') || text
},
generateCommentsPath (record) {
return '/' + this.entityTypeToPath(record.entitytype) + '/' + record.entityid + '?tab=comments'
return '/' + this.entityTypeToPath(record.entitytype) + '/' + record.entityid
},
generateHumanReadableEntityType (record) {
switch (record.entitytype) {
@ -738,31 +790,40 @@ export default {
return 'Unsecure'
}
return host.state
},
getColumnKey (name) {
if (typeof name === 'object') {
name = Object.keys(name)[0]
}
return name
},
updateSelectedColumns (name) {
this.$emit('update-selected-columns', name)
}
}
}
</script>
<style scoped>
/deep/ .ant-table-thead {
<style>
:deep(.ant-table-thead) {
background-color: #f9f9f9;
}
/deep/ .ant-table-small > .ant-table-content > .ant-table-body {
:deep(.ant-table-small) > .ant-table-content > .ant-table-body {
margin: 0;
}
/deep/ .light-row {
background-color: #fff;
}
/deep/ .dark-row {
background-color: #f9f9f9;
}
/deep/ .ant-table-tbody>tr>td, .ant-table-thead>tr>th {
:deep(.ant-table-tbody)>tr>td, :deep(.ant-table-thead)>tr>th {
overflow-wrap: anywhere;
}
.filter-dropdown .ant-menu-vertical {
border: none;
}
.filter-dropdown .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background-color: transparent;
}
</style>
<style scoped lang="scss">

View File

@ -21,7 +21,7 @@
<a-input-search
class="top-spaced"
:placeholder="$t('label.search')"
v-model="searchQuery"
v-model:value="searchQuery"
style="margin-bottom: 10px;"
@search="fetchNetworks"
autoFocus />
@ -33,7 +33,7 @@
:dataSource="networks"
:pagination="false"
:rowKey="record => record.id">
<template slot="select" slot-scope="record">
<template #select="{record}">
<a-radio
@click="updateSelection(record)"
:checked="selectedNetwork != null && record.id === selectedNetwork.id">
@ -51,7 +51,7 @@
@change="handleChangePage"
@showSizeChange="handleChangePageSize"
showSizeChanger>
<template slot="buildOptionText" slot-scope="props">
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
@ -114,7 +114,7 @@ export default {
},
{
title: this.$t('label.select'),
scopedSlots: { customRender: 'select' }
slots: { customRender: 'select' }
}
]
}

View File

@ -17,7 +17,7 @@
<template>
<a-popover v-if="enabled && actionsExist" triggers="hover" placement="topLeft" v-model="visible">
<template slot="content">
<template #content>
<action-button
:size="size"
:actions="actions"
@ -25,7 +25,9 @@
:resource="resource"
@exec-action="execAction" />
</template>
<a-button shape="circle" size="small" icon="more" style="float: right; background-color: transparent; border-color: transparent"/>
<a-button shape="circle" size="small" style="float: right; background-color: transparent; border-color: transparent">
<template #icon><MoreOutlined /></template>
</a-button>
</a-popover>
</template>
@ -58,8 +60,11 @@ export default {
}
},
watch: {
resource () {
this.actionsExist = this.doActionsExist()
actions: {
deep: true,
handler () {
this.actionsExist = this.doActionsExist()
}
}
},
data () {

View File

@ -20,23 +20,25 @@
size="small"
:loading="loading"
:dataSource="usageList" >
<a-list-item slot="renderItem" slot-scope="item" class="list-item" v-if="!($route.meta.name === 'project' && item === 'project')">
<div class="list-item__container">
<strong>
{{ $t('label.' + item + 'limit') }}
</strong>
({{ resource[item + 'available'] === '-1' ? $t('label.unlimited') : resource[item + 'available'] }} {{ $t('label.available') }})
<div class="list-item__vals">
<div class="list-item__data">
{{ $t('label.used') }} / {{ $t('label.limit') }} : {{ resource[item + 'total'] }} / {{ resource[item + 'limit'] === '-1' ? $t('label.unlimited') : resource[item + 'limit'] }}
<template #renderItem="{ item }">
<a-list-item class="list-item" v-if="!($route.meta.name === 'project' && item === 'project')">
<div class="list-item__container">
<strong>
{{ $t('label.' + item + 'limit') }}
</strong>
({{ resource[item + 'available'] === '-1' ? $t('label.unlimited') : resource[item + 'available'] }} {{ $t('label.available') }})
<div class="list-item__vals">
<div class="list-item__data">
{{ $t('label.used') }} / {{ $t('label.limit') }} : {{ resource[item + 'total'] }} / {{ resource[item + 'limit'] === '-1' ? $t('label.unlimited') : resource[item + 'limit'] }}
</div>
<a-progress
status="normal"
:percent="parseFloat(getPercentUsed(resource[item + 'total'], resource[item + 'limit']))"
:format="p => resource[item + 'limit'] !== '-1' && resource[item + 'limit'] !== 'Unlimited' ? p.toFixed(2) + '%' : ''" />
</div>
<a-progress
status="normal"
:percent="parseFloat(getPercentUsed(resource[item + 'total'], resource[item + 'limit']))"
:format="p => resource[item + 'limit'] !== '-1' && resource[item + 'limit'] !== 'Unlimited' ? p.toFixed(2) + '%' : ''" />
</div>
</div>
</a-list-item>
</a-list-item>
</template>
</a-list>
</template>
@ -61,14 +63,6 @@ export default {
]
}
},
watch: {
resource (newData, oldData) {
if (!newData || !newData.id) {
return
}
this.resource = newData
}
},
methods: {
getPercentUsed (total, limit) {
return (limit === 'Unlimited') ? 0 : (total / limit) * 100

View File

@ -16,7 +16,7 @@
// under the License.
<template>
<img :src="getImg()" :height="getDimensions()" :width="getDimensions()" :style="{ marginTop: (getDimensions() === '56px' || ['deployVirtualMachine'].includes(this.$route.path.split('/')[2])) ? '' : '-5px' }"/>
<img :src="getImg()" :height="getDimensions()" :width="getDimensions()" :style="{ marginTop: (getDimensions() === 56 || ['deployVirtualMachine'].includes($route.path.split('/')[2])) ? '' : '-5px' }"/>
</template>
<script>
export default {
@ -45,13 +45,13 @@ export default {
getDimensions () {
switch (this.size) {
case '4x':
return '56px'
return 56
case '2x':
return '24px'
return 24
case '1x':
return '16px'
return 16
default:
return '16px'
return 16
}
}
}

View File

@ -18,26 +18,28 @@
<template>
<a-spin :spinning="formLoading">
<a-form
:form="form"
@submit="handleSubmit"
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
layout="vertical"
v-ctrl-enter="handleSubmit"
>
<a-form-item
v-for="(item, index) in dataResource"
:key="index"
v-if="item.resourcetypename !== 'project'"
:v-bind="item.resourcetypename"
:label="$t('label.max' + item.resourcetypename.replace('_', ''))">
<a-input-number
:disabled="!('updateResourceLimit' in $store.getters.apis)"
style="width: 100%;"
v-decorator="[item.resourcetype, {
initialValue: item.max
}]"
:autoFocus="index === 0"
/>
</a-form-item>
<div v-for="(item, index) in dataResource" :key="index">
<a-form-item
v-if="item.resourcetypename !== 'project'"
:v-bind="item.resourcetypename"
:label="$t('label.max' + (item.resourcetypename ? item.resourcetypename.replace('_', '') : ''))"
:name="item.resourcetype"
:ref="item.resourcetype">
<a-input-number
:disabled="!('updateResourceLimit' in $store.getters.apis)"
style="width: 100%;"
v-model:value="form[item.resourcetype]"
v-focus="index === 0"
/>
</a-form-item>
</div>
<div class="card-footer">
<a-button
:disabled="!('updateResourceLimit' in $store.getters.apis)"
@ -52,6 +54,7 @@
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
export default {
@ -72,19 +75,22 @@ export default {
dataResource: []
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
},
created () {
this.formRef = ref()
this.form = reactive({})
this.rules = reactive({})
this.dataResource = this.resource
this.fetchData()
},
watch: {
resource (newData, oldData) {
if (!newData || !newData.id) {
return
resource: {
deep: true,
handler (newData) {
if (!newData || !newData.id) {
return
}
this.fetchData()
}
this.resource = newData
this.fetchData()
}
},
methods: {
@ -102,10 +108,15 @@ export default {
},
async fetchData () {
const params = this.getParams()
const form = {}
try {
this.formLoading = true
this.dataResource = await this.listResourceLimits(params)
this.form.resetFields()
this.dataResource.forEach(item => {
form[item.resourcetype] = item.max || -1
})
this.form = form
this.formRef.value.resetFields()
this.formLoading = false
} catch (e) {
this.$notification.error({
@ -120,10 +131,8 @@ export default {
if (this.formLoading) return
this.form.validateFieldsAndScroll((err, values) => {
if (err) {
return
}
this.formRef.value.validate().then(() => {
const values = toRaw(this.form)
const arrAsync = []
const params = this.getParams()
for (const key in values) {
@ -147,6 +156,8 @@ export default {
}).finally(() => {
this.formLoading = false
})
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
},
listResourceLimits (params) {

View File

@ -17,38 +17,41 @@
<template>
<resource-layout>
<div slot="left">
<template #left>
<slot name="info-card">
<info-card :resource="resource" :loading="loading" />
</slot>
</div>
<a-spin :spinning="loading" slot="right">
</template>
<template #right>
<a-card
class="spin-content"
:loading="loading"
:bordered="true"
style="width:100%">
<component
v-if="tabs.length === 1"
:is="tabs[0].component"
:resource="resource"
:loading="loading"
:tab="tabs[0].name" />
<keep-alive v-if="tabs.length === 1">
<component
:is="tabs[0].component"
:resource="resource"
:loading="loading"
:tab="tabs[0].name" />
</keep-alive>
<a-tabs
v-else
style="width: 100%"
:animated="false"
:activeKey="activeTab || tabs[0].name"
@change="onTabChange" >
<a-tab-pane
v-for="tab in tabs"
:tab="$t('label.' + tab.name)"
:key="tab.name"
v-if="showTab(tab)">
<component :is="tab.component" :resource="resource" :loading="loading" :tab="activeTab" />
</a-tab-pane>
<template v-for="tab in tabs" :key="tab.name">
<a-tab-pane
:key="tab.name"
:tab="$t('label.' + tab.name)"
v-if="showTab(tab)">
<keep-alive><component :is="tab.component" :resource="resource" :loading="loading" :tab="activeTab" /></keep-alive>
</a-tab-pane>
</template>
</a-tabs>
</a-card>
</a-spin>
</template>
</resource-layout>
</template>
@ -97,33 +100,39 @@ export default {
}
},
watch: {
resource: function (newItem, oldItem) {
this.resource = newItem
if (newItem.id === oldItem.id) return
resource: {
deep: true,
handler (newItem, oldItem) {
if (newItem.id === oldItem.id) return
if (this.resource.associatednetworkid) {
api('listNetworks', { id: this.resource.associatednetworkid, listall: true }).then(response => {
if (response && response.listnetworksresponse && response.listnetworksresponse.network) {
this.networkService = response.listnetworksresponse.network[0]
} else {
this.networkService = {}
}
})
if (this.resource.associatednetworkid) {
api('listNetworks', { id: this.resource.associatednetworkid, listall: true }).then(response => {
if (response && response.listnetworksresponse && response.listnetworksresponse.network) {
this.networkService = response.listnetworksresponse.network[0]
} else {
this.networkService = {}
}
})
}
}
},
$route: function (newItem, oldItem) {
'$route.fullPath': function () {
this.setActiveTab()
}
},
mounted () {
created () {
const self = this
this.setActiveTab()
window.addEventListener('popstate', function () {
self.setActiveTab()
})
},
methods: {
onTabChange (key) {
this.activeTab = key
const query = Object.assign({}, this.$route.query)
query.tab = key
history.replaceState(
history.pushState(
{},
null,
'#' + this.$route.path + '?' + Object.keys(query).map(key => {

View File

@ -19,11 +19,11 @@
<span :style="styleSearch">
<span v-if="!searchFilters || searchFilters.length === 0" style="display: flex;">
<a-input-search
style="width: 100%; display: table-cell"
v-model:value="searchQuery"
:placeholder="$t('label.search')"
v-model="searchQuery"
allowClear
@search="onSearch" />
@search="onSearch"
/>
</span>
<span
@ -33,113 +33,117 @@
allowClear
class="input-search"
:placeholder="$t('label.search')"
v-model="searchQuery"
v-model:value="searchQuery"
@search="onSearch">
<a-popover
placement="bottomRight"
slot="addonBefore"
trigger="click"
v-model="visibleFilter">
<template slot="content" v-if="visibleFilter">
<a-form
style="min-width: 170px"
:form="form"
layout="vertical"
@submit="handleSubmit">
<a-form-item
v-for="(field, index) in fields"
:key="index"
:label="field.name==='keyword' ?
('listAnnotations' in $store.getters.apis ? $t('label.annotation') : $t('label.name')) :
(field.name==='entitytype' ? $t('label.entity.type') : $t('label.' + field.name))">
<a-select
allowClear
v-if="field.type==='list'"
v-decorator="[field.name, {
initialValue: fieldValues[field.name] || null
}]"
showSearch
:dropdownMatchSelectWidth="false"
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.propsData.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="field.loading">
<a-select-option
v-for="(opt, idx) in field.opts"
:key="idx"
:value="opt.id"
:label="$t(opt.name)">
<div>
<span v-if="(field.name.startsWith('zone'))">
<span v-if="opt.icon">
<resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<template #addonBefore>
<a-popover
placement="bottomRight"
trigger="click"
v-model:visible="visibleFilter">
<template #content v-if="visibleFilter">
<a-form
style="min-width: 170px"
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
v-ctrl-enter="handleSubmit">
<a-form-item
v-for="(field, index) in fields"
:key="index"
:label="field.name==='keyword' ?
('listAnnotations' in $store.getters.apis ? $t('label.annotation') : $t('label.name')) :
(field.name==='entitytype' ? $t('label.entity.type') : $t('label.' + field.name))">
<a-select
allowClear
v-if="field.type==='list'"
v-model:value="form[field.name]"
showSearch
:dropdownMatchSelectWidth="false"
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="field.loading">
<a-select-option
v-for="(opt, idx) in field.opts"
:key="idx"
:value="opt.id"
:label="$t(opt.name)">
<div>
<span v-if="(field.name.startsWith('zone'))">
<span v-if="opt.icon">
<resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
</span>
<global-outlined v-else style="margin-right: 5px" />
</span>
<a-icon v-else type="global" style="margin-right: 5px" />
</span>
<span v-if="(field.name.startsWith('domain'))">
<span v-if="opt.icon">
<resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<span v-if="(field.name.startsWith('domain'))">
<span v-if="opt.icon">
<resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
</span>
<block-outlined v-else style="margin-right: 5px" />
</span>
<a-icon v-else type="block" style="margin-right: 5px" />
</span>
{{ $t(opt.path || opt.name) }}
</div>
</a-select-option>
</a-select>
<a-input
v-else-if="field.type==='input'"
v-decorator="[field.name, {
initialValue: fieldValues[field.name] || null
}]" />
<div v-else-if="field.type==='tag'">
<div>
{{ $t(opt.path || opt.name) }}
</div>
</a-select-option>
</a-select>
<a-input
v-else-if="field.type==='input'"
v-model:value="form[field.name]" />
<div v-else-if="field.type==='tag'">
<a-input-group
type="text"
size="small"
compact>
<a-input ref="input" :value="inputKey" @change="e => inputKey = e.target.value" style="width: 50px; text-align: center" :placeholder="$t('label.key')" />
<a-input ref="input" v-model:value="inputKey" style="width: 50px; text-align: center" :placeholder="$t('label.key')" />
<a-input
class="tag-disabled-input"
style=" width: 20px; border-left: 0; pointer-events: none; text-align: center"
placeholder="="
disabled />
<a-input :value="inputValue" @change="handleValueChange" style="width: 50px; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
<tooltip-button :tooltip="$t('label.clear')" icon="close" size="small" @click="inputKey = inputValue = ''" />
<a-input v-model:value="inputValue" style="width: 50px; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
<tooltip-button :tooltip="$t('label.clear')" icon="close-outlined" size="small" @onClick="inputKey = inputValue = ''" />
</a-input-group>
</div>
</a-form-item>
<div class="filter-group-button">
<a-button
class="filter-group-button-clear"
type="default"
size="small"
@click="onClear">
<template #icon><stop-outlined /></template>
{{ $t('label.reset') }}
</a-button>
<a-button
class="filter-group-button-search"
type="primary"
size="small"
ref="submit"
html-type="submit">
<template #icon><search-outlined /></template>
{{ $t('label.search') }}
</a-button>
</div>
</a-form-item>
<div class="filter-group-button">
<a-button
class="filter-group-button-clear"
type="default"
size="small"
icon="stop"
@click="onClear">{{ $t('label.reset') }}</a-button>
<a-button
class="filter-group-button-search"
type="primary"
size="small"
icon="search"
html-type="submit"
@click="handleSubmit">{{ $t('label.search') }}</a-button>
</div>
</a-form>
</template>
<a-button
class="filter-button"
size="small"
@click="() => { searchQuery = null }">
<a-icon type="filter" :theme="isFiltered ? 'twoTone' : 'outlined'" />
</a-button>
</a-popover>
</a-form>
</template>
<a-button
class="filter-button"
size="small"
@click="() => { searchQuery = null }">
<filter-two-tone v-if="isFiltered" />
<filter-outlined v-else />
</a-button>
</a-popover>
</template>
</a-input-search>
</span>
</span>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import TooltipButton from '@/components/widgets/TooltipButton'
import ResourceIcon from '@/components/view/ResourceIcon'
@ -176,8 +180,10 @@ export default {
isFiltered: false
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
created () {
this.formRef = ref()
this.form = reactive({})
this.rules = reactive({})
},
watch: {
visibleFilter (newValue, oldValue) {
@ -222,6 +228,11 @@ export default {
}
},
methods: {
onVisibleForm () {
this.visibleFilter = !this.visibleFilter
if (!this.visibleFilter) return
this.initFormFieldData()
},
async initFormFieldData () {
const arrayField = []
this.fields = []
@ -331,7 +342,6 @@ export default {
this.fields[clusterIndex].opts = this.sortArray(cluster[0].data)
}
}
this.$forceUpdate()
}).finally(() => {
if (zoneIndex > -1) {
this.fields[zoneIndex].loading = false
@ -364,6 +374,9 @@ export default {
if (this.$route.meta.params) {
Object.assign(this.fieldValues, this.$route.meta.params)
}
this.fields.forEach(field => {
this.form[field.name] = this.fieldValues[field.name]
})
this.inputKey = this.fieldValues['tags[0].key'] || null
this.inputValue = this.fieldValues['tags[0].value'] || null
},
@ -506,11 +519,7 @@ export default {
this.$emit('search', { searchQuery: this.searchQuery })
},
onClear () {
this.searchFilters.map(item => {
const field = {}
field[item] = undefined
this.form.setFieldsValue(field)
})
this.formRef.value.resetFields()
this.isFiltered = false
this.inputKey = null
this.inputValue = null
@ -518,13 +527,10 @@ export default {
this.paramsFilter = {}
this.$emit('search', this.paramsFilter)
},
handleSubmit (e) {
e.preventDefault()
handleSubmit () {
this.paramsFilter = {}
this.form.validateFieldsAndScroll((err, values) => {
if (err) {
return
}
this.formRef.value.validate().then(() => {
const values = toRaw(this.form)
this.isFiltered = true
for (const key in values) {
const input = values[key]
@ -542,12 +548,6 @@ export default {
this.$emit('search', this.paramsFilter)
})
},
handleKeyChange (e) {
this.inputKey = e.target.value
},
handleValueChange (e) {
this.inputValue = e.target.value
},
changeFilter (filter) {
this.$emit('change-filter', filter)
}
@ -561,7 +561,7 @@ export default {
}
.filter-group {
/deep/.ant-input-group-addon {
:deep(.ant-input-group-addon) {
padding: 0 5px;
}
@ -587,7 +587,7 @@ export default {
}
}
/deep/.ant-input-group {
:deep(.ant-input-group) {
.ant-input-affix-wrapper {
width: calc(100% - 10px);
}

View File

@ -21,7 +21,7 @@
<a-radio-group
class="setting-group"
name="themeGroup"
v-model="layoutMode"
v-model:value="layoutMode"
@change="switchLayoutMode">
<setting-item
view-type="radio-group"
@ -58,7 +58,7 @@
<a-input
:disabled="layoutMode === 'dark'"
type="color"
v-model="navBgColorPick"
v-model:value="navBgColorPick"
@blur="(e) => updateSetting('@navigation-background-color', e.target.value)" />
</div>
</div>
@ -70,7 +70,7 @@
<a-input
:disabled="layoutMode === 'dark'"
type="color"
v-model="navBgColorPick"
v-model:value="navBgColorPick"
@blur="(e) => updateSetting('@navigation-text-color', e.target.value)" />
</div>
</div>
@ -89,7 +89,7 @@
<div class="color-picker" :style="{ backgroundColor: projectNavBgColorPick }">
<a-input
type="color"
v-model="projectNavBgColorPick"
v-model:value="projectNavBgColorPick"
@blur="(e) => updateSetting('@project-nav-background-color', e.target.value)" />
</div>
</div>
@ -100,7 +100,7 @@
<div class="color-picker" :style="{ backgroundColor: projectNavTextColorPick }">
<a-input
type="color"
v-model="projectNavTextColorPick"
v-model:value="projectNavTextColorPick"
@blur="(e) => updateSetting('@project-nav-text-color', e.target.value)" />
</div>
</div>
@ -113,12 +113,16 @@
<a-alert class="setting-action-alert" :message="$t('label.theme.alert')" type="warning" show-icon />
<a-button
class="setting-action-btn"
icon="copy"
@click="saveSetting">{{ $t('label.save.setting') }}</a-button>
@click="downloadSetting">
<template #icon><download-outlined /></template>
{{ $t('label.download.setting') }}
</a-button>
<a-button
class="setting-action-btn"
icon="undo"
@click="resetSetting">{{ $t('label.reset.to.default') }}</a-button>
@click="resetSetting">
<template #icon><undo-outlined /></template>
{{ $t('label.reset.to.default') }}
</a-button>
</div>
</div>
</template>
@ -139,12 +143,12 @@ export default {
},
data () {
return {
layoutMode: 'light',
colorPick: this.$store.getters.themeSetting['@primary-color'] || this.$config.theme['@primary-color'],
navBgColorPick: this.$store.getters.themeSetting['@navigation-background-color'] || this.$config.theme['@navigation-background-color'],
navTextColorPick: this.$store.getters.themeSetting['@navigation-text-color'] || this.$config.theme['@navigation-text-color'],
projectNavBgColorPick: this.$store.getters.themeSetting['@project-nav-background-color'] || this.$config.theme['@project-nav-background-color'],
projectNavTextColorPick: this.$store.getters.themeSetting['@project-nav-text-color'] || this.$config.theme['@project-nav-text-color'],
layoutMode: this.$config.theme['@layout-mode'] || 'light',
colorPick: this.$config.theme['@primary-color'],
navBgColorPick: this.$config.theme['@navigation-background-color'],
navTextColorPick: this.$config.theme['@navigation-text-color'],
projectNavBgColorPick: this.$config.theme['@project-nav-background-color'],
projectNavTextColorPick: this.$config.theme['@project-nav-text-color'],
uiSettings: {},
originalSetting: {}
}
@ -158,12 +162,12 @@ export default {
{
name: 'light',
type: 'image-checkbox',
component: () => import('@/assets/icons/light.svg?inline')
icon: 'light'
},
{
name: 'dark',
type: 'image-checkbox',
component: () => import('@/assets/icons/dark.svg?inline')
icon: 'dark'
}
]
return arrStyle
@ -225,14 +229,12 @@ export default {
methods: {
fetchData () {
this.originalSetting = Object.assign({}, this.$config.theme)
this.layoutMode = 'light'
if (this.$store.getters.darkMode) {
this.layoutMode = 'dark'
}
this.layoutMode = this.$config.theme['@layout-mode'] || 'light'
this.uiSettings = this.$config.theme
},
switchLayoutMode () {
this.$store.dispatch('SetDarkMode', (this.layoutMode === 'dark'))
this.updateSetting('@layout-mode', this.layoutMode)
},
switchColor (e) {
this.colorPick = e.target.value
@ -248,13 +250,8 @@ export default {
onClose () {
this.parentToggleSetting(false)
},
saveSetting () {
const loading = this.$message.loading(this.$t('label.save.setting'), 0)
this.$store.dispatch('SetThemeSetting', this.uiSettings)
setTimeout(() => {
loading()
this.$message.success(this.$t('label.success'))
}, 1000)
downloadSetting () {
this.downloadObjectAsJson(this.uiSettings)
},
resetSetting () {
this.layoutMode = 'light'
@ -264,78 +261,20 @@ export default {
this.projectNavBgColorPick = this.originalSetting['@project-nav-background-color']
this.projectNavTextColorPick = this.originalSetting['@project-nav-text-color']
this.$store.dispatch('SetThemeSetting', {})
this.switchLayoutMode()
this.$config.theme = this.originalSetting
window.less.modifyVars(this.$config.theme)
this.$message.success(this.$t('label.success'))
},
formatConfig (obj, dep) {
dep = dep || 1
const LN = '\n'
const TAB = ' '
let indent = ''
for (let i = 0; i < dep; i++) {
indent += TAB
}
let isArray = false
let arrayLastIsObj = false
let str = ''
let prefix = '{'
let subfix = '}'
if (Array.isArray(obj)) {
isArray = true
prefix = '['
subfix = ']'
str = obj.map((item, index) => {
let format = ''
if (typeof item === 'function') {
//
} else if (typeof item === 'object') {
arrayLastIsObj = true
format = `${LN}${indent}${this.formatConfig(item, dep + 1)},`
} else if ((typeof item === 'number' && !isNaN(item)) || typeof item === 'boolean') {
format = `${item},`
} else if (typeof item === 'string') {
format = `'${item}',`
}
if (index === obj.length - 1) {
format = format.substring(0, format.length - 1)
} else {
arrayLastIsObj = false
}
return format
}).join('')
} else if (typeof obj !== 'function' && typeof obj === 'object') {
str = Object.keys(obj).map((key, index, keys) => {
const val = obj[key]
let format = ''
if (typeof val === 'function') {
//
} else if (typeof val === 'object') {
format = `${LN}${indent}${key}: ${this.formatConfig(val, dep + 1)},`
} else if ((typeof val === 'number' && !isNaN(val)) || typeof val === 'boolean') {
format = `${LN}${indent}${key}: ${val},`
} else if (typeof val === 'string') {
format = `${LN}${indent}${key}: '${val}',`
}
if (index === keys.length - 1) {
format = format.substring(0, format.length - 1)
}
return format
}).join('')
}
const len = TAB.length
if (indent.length >= len) {
indent = indent.substring(0, indent.length - len)
}
if (!isArray || arrayLastIsObj) {
subfix = LN + indent + subfix
}
return `${prefix}${str}${subfix}`
downloadObjectAsJson (exportObj) {
const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(exportObj, null, 2))
const downloadAnchorNode = document.createElement('a')
downloadAnchorNode.setAttribute('href', dataStr)
downloadAnchorNode.setAttribute('download', 'theme.json')
document.body.appendChild(downloadAnchorNode)
downloadAnchorNode.click()
downloadAnchorNode.remove()
}
}
}
@ -394,8 +333,15 @@ export default {
padding: 0 24px;
&-alert {
display: flex;
flex-direction: row;
align-items: baseline;
margin: 20px 0 8px;
word-break: break-word;
position: relative;
:deep(.ant-alert-icon) {
}
}
&-btn {

View File

@ -27,15 +27,11 @@
<a-col v-for="(item) in items" :key="item.name" :style="colWidth">
<a-tooltip :title="$t(`label.theme.${item.name}`)" placement="top">
<div :class="['img-checkbox', item.disabled ? 'disabled' : '']" v-if="item.type==='image-checkbox'">
<component
:is="item.component"
:style="{
height: '56px',
width: '56px'
}"/>
<light v-if="item.icon==='light'" :style="{ height: '56px', width: '56px' }" />
<dark v-if="item.icon==='dark'" :style="{ height: '56px', width: '56px' }"/>
<div :class="['check-item', item.name === checked ? 'check-item-checked' : '']">
<a-radio :value="item.name" :disabled="item.disabled"></a-radio>
<a-icon :class="['check-icon', item.name]" type="check" />
<a-radio v-model:value="item.name" :disabled="item.disabled"></a-radio>
<check-outlined :class="['check-icon', item.name]" />
</div>
</div>
@ -43,8 +39,8 @@
<div
:class="['check-color', item.color === checked ? 'check-color-checked' : '']"
:style="{ backgroundColor: item.color }">
<a-radio :value="item.color"></a-radio>
<a-icon class="check-icon" type="check" />
<a-radio v-model:value="item.color"></a-radio>
<check-outlined class="check-icon" />
</div>
</div>
</a-tooltip>
@ -55,11 +51,16 @@
</template>
<script>
import AddNetscalerLoadBalancer from '@/views/infra/network/providers/AddNetscalerLoadBalancer.vue'
import light from '@/assets/icons/light.svg?inline'
import dark from '@/assets/icons/dark.svg?inline'
export default {
components: { AddNetscalerLoadBalancer },
name: 'SettingItem',
components: {
light,
dark
},
props: {
viewType: {
type: String,
@ -113,8 +114,8 @@ export default {
position: absolute;
top: 0;
width: 100%;
padding-top: 25px;
padding-left: 20px;
padding-top: 18px;
padding-left: 15px;
height: 100%;
font-size: 14px;
font-weight: bold;

View File

@ -20,23 +20,23 @@
<a-input-search
style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8;"
:placeholder="$t('label.search')"
v-model="filter"
v-model:value="filter"
@search="handleSearch" />
<a-list size="large" class="list" :loading="loading || tabLoading">
<a-list-item :key="index" v-for="(item, index) in items" class="item">
<a-list-item-meta>
<span slot="title" style="word-break: break-all">{{ item.name }}</span>
<span slot="description" style="word-break: break-all">{{ item.description }}</span>
<template #title style="word-break: break-all">{{ item.name }}</template>
<template #description style="word-break: break-all">{{ item.description }}</template>
</a-list-item-meta>
<div class="item__content">
<a-input
:autoFocus="editableValueKey === index"
v-focus="editableValueKey === index"
v-if="editableValueKey === index"
class="editable-value value"
:defaultValue="item.value"
v-model="editableValue"
v-model:value="editableValue"
@keydown.esc="editableValueKey = null"
@pressEnter="updateData(item)">
</a-input>
@ -45,32 +45,32 @@
</span>
</div>
<div slot="actions" class="action">
<template #actions class="action">
<tooltip-button
:tooltip="$t('label.edit')"
:disabled="!('updateConfiguration' in $store.getters.apis)"
v-if="editableValueKey !== index"
icon="edit"
@click="setEditableSetting(item, index)" />
icon="edit-outlined"
@onClick="setEditableSetting(item, index)" />
<tooltip-button
:tooltip="$t('label.cancel')"
@click="editableValueKey = null"
@onClick="editableValueKey = null"
v-if="editableValueKey === index"
iconType="close-circle"
iconType="CloseCircleTwoTone"
iconTwoToneColor="#f5222d" />
<tooltip-button
:tooltip="$t('label.ok')"
@click="updateData(item)"
@onClick="updateData(item)"
v-if="editableValueKey === index"
iconType="check-circle"
iconType="CheckCircleTwoTone"
iconTwoToneColor="#52c41a" />
<tooltip-button
:tooltip="$t('label.reset.config.value')"
@click="resetConfig(item)"
@onClick="resetConfig(item)"
v-if="editableValueKey !== index"
icon="reload"
icon="reload-outlined"
:disabled="!('updateConfiguration' in $store.getters.apis)" />
</div>
</template>
</a-list-item>
</a-list>
</div>
@ -131,10 +131,12 @@ export default {
this.fetchData()
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem.id) return
this.resource = newItem
this.fetchData()
resource: {
deep: true,
handler (newItem) {
if (!newItem.id) return
this.fetchData()
}
}
},
methods: {
@ -239,7 +241,7 @@ export default {
&__content {
width: 100%;
display: flex;
display: block;
word-break: break-all;
@media (min-width: 760px) {

View File

@ -20,7 +20,7 @@
<a-input-search
class="top-spaced"
:placeholder="$t('label.search')"
v-model="searchQuery"
v-model:value="searchQuery"
style="margin-bottom: 10px;"
@search="fetchStoragePools"
autoFocus />
@ -32,42 +32,38 @@
:dataSource="storagePools"
:pagination="false"
:rowKey="record => record.id">
<span slot="suitabilityCustomTitle">
<template #suitabilityCustomTitle>
{{ $t('label.suitability') }}
<a-tooltip :title="$t('message.volume.state.primary.storage.suitability')" placement="top">
<a-icon type="info-circle" class="table-tooltip-icon" />
<info-circle-outlined class="table-tooltip-icon" />
</a-tooltip>
</span>
<div slot="name" slot-scope="record">
</template>
<template #name="{ record }">
{{ record.name }}
<a-tooltip v-if="record.name === $t('label.auto.assign')" :title="$t('message.migrate.volume.pool.auto.assign')" placement="top">
<a-icon type="info-circle" class="table-tooltip-icon" />
<info-circle-outlined class="table-tooltip-icon" />
</a-tooltip>
</div>
<div slot="suitability" slot-scope="record">
<a-icon
</template>
<template #suitability="{ record }">
<check-circle-two-tone
class="host-item__suitability-icon"
type="check-circle"
theme="twoTone"
twoToneColor="#52c41a"
v-if="record.suitableformigration" />
<a-icon
<close-circle-two-tone
class="host-item__suitability-icon"
type="close-circle"
theme="twoTone"
twoToneColor="#f5222d"
v-else />
</div>
<div slot="disksizetotal" slot-scope="record">
</template>
<template #disksizetotal="{ record }">
<span v-if="record.disksizetotal">{{ $bytesToHumanReadableSize(record.disksizetotal) }}</span>
</div>
<div slot="disksizeused" slot-scope="record">
</template>
<template #disksizeused="{ record }">
<span v-if="record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizeused) }}</span>
</div>
<div slot="disksizefree" slot-scope="record">
</template>
<template #disksizefree="{ record }">
<span v-if="record.disksizetotal && record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizetotal * 1 - record.disksizeused * 1) }}</span>
</div>
<template slot="select" slot-scope="record">
</template>
<template #select="{ record }">
<a-tooltip placement="top" :title="record.state !== 'Up' ? $t('message.primary.storage.invalid.state') : ''">
<a-radio
:disabled="record.id !== -1 && record.state !== 'Up'"
@ -88,7 +84,7 @@
@change="handleChangePage"
@showSizeChange="handleChangePageSize"
showSizeChanger>
<template slot="buildOptionText" slot-scope="props">
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
@ -137,7 +133,7 @@ export default {
columns: [
{
title: this.$t('label.storageid'),
scopedSlots: { customRender: 'name' }
slots: { customRender: 'name' }
},
{
title: this.$t('label.clusterid'),
@ -149,27 +145,26 @@ export default {
},
{
title: this.$t('label.disksizetotal'),
scopedSlots: { customRender: 'disksizetotal' }
slots: { customRender: 'disksizetotal' }
},
{
title: this.$t('label.disksizeused'),
scopedSlots: { customRender: 'disksizeused' }
slots: { customRender: 'disksizeused' }
},
{
title: this.$t('label.disksizefree'),
scopedSlots: { customRender: 'disksizefree' }
slots: { customRender: 'disksizefree' }
},
{
title: this.$t('label.select'),
scopedSlots: { customRender: 'select' }
slots: { customRender: 'select' }
}
]
}
},
created () {
if (this.suitabilityEnabled) {
this.columns.splice(1, 0, { slots: { title: 'suitabilityCustomTitle' }, scopedSlots: { customRender: 'suitability' } }
)
this.columns.splice(1, 0, { title: 'suitabilityCustomTitle', slots: 'suitability' })
}
this.preselectStoragePool()
this.fetchStoragePools()

View File

@ -17,62 +17,66 @@
<template>
<resource-layout>
<a-spin :spinning="loading" slot="left">
<a-card :bordered="false">
<a-input-search
size="default"
:placeholder="$t('label.search')"
v-model="searchQuery"
@search="onSearch"
>
<a-icon slot="prefix" type="search" />
</a-input-search>
<a-spin :spinning="loadingSearch">
<a-tree
showLine
v-if="treeViewData.length > 0"
class="list-tree-view"
:treeData="treeViewData"
:loadData="onLoadData"
:expandAction="false"
:showIcon="true"
:selectedKeys="defaultSelected"
:checkStrictly="true"
@select="onSelect"
@expand="onExpand"
:expandedKeys="arrExpand">
<a-icon slot="parent" type="folder" />
<a-icon slot="leaf" type="block" />
</a-tree>
</a-spin>
</a-card>
</a-spin>
<a-spin :spinning="detailLoading" slot="right">
<a-card
class="spin-content"
:bordered="true"
style="width:100%">
<a-tabs
style="width: 100%"
:animated="false"
:defaultActiveKey="tabs[0].name"
@change="onTabChange" >
<a-tab-pane
v-for="tab in tabs"
:tab="$t('label.' + tab.name)"
:key="tab.name"
v-if="checkShowTabDetail(tab)">
<component
:is="tab.component"
:resource="resource"
:items="items"
:tab="tabActive"
:loading="loading"
:bordered="false" />
</a-tab-pane>
</a-tabs>
</a-card>
</a-spin>
<template #left>
<a-spin :spinning="loading">
<a-card :bordered="false">
<a-input-search
size="default"
:placeholder="$t('label.search')"
v-model:value="searchQuery"
@search="onSearch"
>
<template #prefix><search-outlined /></template>
</a-input-search>
<a-spin :spinning="loadingSearch">
<a-tree
showLine
v-if="treeViewData.length > 0"
class="list-tree-view"
:treeData="treeViewData"
:loadData="onLoadData"
:expandAction="false"
:showIcon="true"
:selectedKeys="defaultSelected"
:checkStrictly="true"
@select="onSelect"
@expand="onExpand"
:expandedKeys="arrExpand">
<template #parent><folder-outlined /></template>
<template #leaf><block-outlined /></template>
</a-tree>
</a-spin>
</a-card>
</a-spin>
</template>
<template #right>
<a-spin :spinning="detailLoading">
<a-card
class="spin-content"
:bordered="true"
style="width:100%">
<a-tabs
style="width: 100%"
:animated="false"
:defaultActiveKey="tabs[0].name"
@change="onTabChange" >
<template v-for="tab in tabs" :tab="$t('label.' + tab.name)" :key="tab.name">
<a-tab-pane :tab="$t('label.' + tab.name)" :key="tab.name" v-if="checkShowTabDetail(tab)">
<keep-alive>
<component
:is="tab.component"
:resource="resource"
:items="items"
:tab="tabActive"
:loading="loading"
:bordered="false" />
</keep-alive>
</a-tab-pane>
</template>
</a-tabs>
</a-card>
</a-spin>
</template>
</resource-layout>
</template>
@ -154,7 +158,7 @@ export default {
this.metaName = this.$route.meta.name
this.apiList = this.$route.meta.permission[0] ? this.$route.meta.permission[0] : ''
this.apiChildren = this.$route.meta.permission[1] ? this.$route.meta.permission[1] : ''
eventBus.$on('refresh-domain-icon', () => {
eventBus.on('refresh-domain-icon', () => {
this.getDetailResource(this.selectedTreeKey)
})
},
@ -173,14 +177,31 @@ export default {
}
}
},
treeSelected () {
if (Object.keys(this.treeSelected).length === 0) {
return
}
treeSelected: {
deep: true,
handler () {
if (Object.keys(this.treeSelected).length === 0) {
return
}
if (Object.keys(this.resource).length > 0) {
this.selectedTreeKey = this.resource.key
this.$emit('change-resource', this.resource)
if (Object.keys(this.resource).length > 0) {
this.selectedTreeKey = this.resource.key
this.$emit('change-resource', this.resource)
// set default expand
if (this.defaultSelected.length > 1) {
const arrSelected = this.defaultSelected
this.defaultSelected = []
this.defaultSelected.push(arrSelected[0])
}
return
}
this.resource = this.treeSelected
this.resource = this.createResourceData(this.resource)
this.selectedTreeKey = this.treeSelected.key
this.defaultSelected.push(this.selectedTreeKey)
// set default expand
if (this.defaultSelected.length > 1) {
@ -188,33 +209,22 @@ export default {
this.defaultSelected = []
this.defaultSelected.push(arrSelected[0])
}
return
}
this.resource = this.treeSelected
this.resource = this.createResourceData(this.resource)
this.selectedTreeKey = this.treeSelected.key
this.defaultSelected.push(this.selectedTreeKey)
// set default expand
if (this.defaultSelected.length > 1) {
const arrSelected = this.defaultSelected
this.defaultSelected = []
this.defaultSelected.push(arrSelected[0])
}
},
treeVerticalData () {
if (!this.treeStore.isExpand) {
return
}
if (this.treeStore.expands && this.treeStore.expands.length > 0) {
for (const expandKey of this.treeStore.expands) {
if (this.arrExpand.includes(expandKey)) {
continue
treeVerticalData: {
deep: true,
handler () {
if (!this.treeStore.isExpand) {
return
}
if (this.treeStore.expands && this.treeStore.expands.length > 0) {
for (const expandKey of this.treeStore.expands) {
if (this.arrExpand.includes(expandKey)) {
continue
}
const keyVisible = this.treeVerticalData.findIndex(item => item.key === expandKey)
if (keyVisible > -1) this.arrExpand.push(expandKey)
}
const keyVisible = this.treeVerticalData.findIndex(item => item.key === expandKey)
if (keyVisible > -1) this.arrExpand.push(expandKey)
}
}
}
@ -256,7 +266,7 @@ export default {
if (item.id === dataGenerate[i].id) {
// replace all value of tree data
Object.keys(dataGenerate[i]).forEach((value, idx) => {
this.$set(this.treeVerticalData[index], value, dataGenerate[i][value])
this.treeVerticalData[index][value] = dataGenerate[i][value]
})
}
})
@ -307,18 +317,20 @@ export default {
this.defaultSelected = []
this.defaultSelected.push(this.selectedTreeKey)
this.treeStore.expands = this.arrExpand
this.treeStore.selected = this.selectedTreeKey
const treeStore = this.treeStore
treeStore.expands = this.arrExpand
treeStore.selected = this.selectedTreeKey
this.$emit('change-tree-store', this.treeStore)
this.getDetailResource(this.selectedTreeKey)
},
onExpand (treeExpand) {
const treeStore = this.treeStore
this.arrExpand = treeExpand
this.treeStore.isExpand = true
this.treeStore.expands = this.arrExpand
this.treeStore.selected = this.selectedTreeKey
this.$emit('change-tree-store', this.treeStore)
treeStore.isExpand = true
treeStore.expands = this.arrExpand
treeStore.selected = this.selectedTreeKey
this.$emit('change-tree-store', treeStore)
},
onSearch (value) {
if (this.searchQuery === '' && this.oldSearchQuery === '') {
@ -492,17 +504,17 @@ export default {
Object.keys(resource).forEach((value, idx) => {
if (resource[value] === 'Unlimited') {
this.$set(resource, value, '-1')
resource.value = '-1'
}
})
this.$set(resource, 'title', resource.name)
this.$set(resource, 'key', resource.id)
resource.title = resource.name
resource.key = resource.id
resource.slots = {
icon: 'parent'
}
if (!resource.haschild) {
this.$set(resource, 'isLeaf', true)
resource.isLeaf = true
resource.slots = {
icon: 'leaf'
}
@ -562,7 +574,7 @@ export default {
.list-tree-view {
overflow-y: hidden;
}
/deep/.ant-tree.ant-tree-directory {
:deep(.ant-tree).ant-tree-directory {
li.ant-tree-treenode-selected {
span.ant-tree-switcher {
color: rgba(0, 0, 0, 0.65);
@ -588,20 +600,20 @@ export default {
}
}
/deep/.ant-tree li span.ant-tree-switcher.ant-tree-switcher-noop {
:deep(.ant-tree) li span.ant-tree-switcher.ant-tree-switcher-noop {
display: none
}
:deep(.ant-tree-node-content-wrapper-open) > span:first-child,
:deep(.ant-tree-node-content-wrapper-close) > span:first-child {
display: none;
}
/deep/.ant-tree-node-content-wrapper-open > span:first-child,
/deep/.ant-tree-node-content-wrapper-close > span:first-child {
display: none;
}
/deep/.ant-tree-icon__customize {
:deep(.ant-tree-icon__customize) {
padding-right: 5px;
}
/deep/.ant-tree li .ant-tree-node-content-wrapper {
:deep(.ant-tree) li .ant-tree-node-content-wrapper {
padding-left: 0;
margin-left: 3px;
}

View File

@ -18,6 +18,7 @@
<template>
<div>
<a-modal
v-if="visible"
:title="$t('label.upload.resource.icon')"
:visible="visible"
:maskClosable="true"
@ -56,41 +57,56 @@
<a-row>
<a-col :xs="2" :md="2">
<a-upload name="file" :beforeUpload="beforeUpload" :showUploadList="false">
<a-button><a-icon type="upload" />{{ $t('label.choose.resource.icon') }} </a-button>
<a-button><upload-outlined />{{ $t('label.choose.resource.icon') }} </a-button>
</a-upload>
</a-col>
<a-col :xs="{span: 2, offset: 4}" :md="1">
<a-button icon="plus" @click="changeScale(5)"/>
<a-button @click="changeScale(5)">
<template #icon><plus-outlined /></template>
</a-button>
</a-col>
<a-col :xs="{span: 1, offset: 0}" :md="2">
<a-button icon="minus" @click="changeScale(-5)"/>
<a-button @click="changeScale(-5)">
<template #icon><minus-outlined /></template>
</a-button>
</a-col>
<a-col :lg="{span: 1, offset: 0}" :md="2">
<a-button icon="undo" @click="rotateLeft"/>
<a-button @click="rotateLeft">
<template #icon><undo-outlined /></template>
</a-button>
</a-col>
<a-col :lg="{span: 1, offset: 0}" :md="2">
<a-button icon="redo" @click="rotateRight"/>
<a-button @click="rotateRight">
<template #icon><redo-outlined /></template>
</a-button>
</a-col>
<a-col :xs="{span: 1, offset: 3}" :md="1">
<a-button type="primary" @click="uploadIcon('blob')"> {{ $t('label.upload') }} </a-button>
</a-col>
<a-col :xs="{span: 2, offset: 5}" :md="2">
<a-button v-if="resource.icon && resource.icon.resourcetype.toLowerCase() === $getResourceType().toLowerCase()" type="danger" @click="deleteIcon('blob')"> {{ $t('label.delete') }} </a-button>
<a-button
v-if="resource.icon && resource.icon.resourcetype.toLowerCase() === $getResourceType().toLowerCase()"
type="primary"
danger
@click="deleteIcon('blob')"> {{ $t('label.delete') }} </a-button>
</a-col>
</a-row>
</a-modal>
<a-modal
v-if="showAlert"
:visible="showAlert"
:footer="null"
style="top: 20px;"
centered
width="auto"
:maskClosable="false">
<span slot="title">
<template #title>
{{ $t('label.warning') }}
</span>
</template>
<a-alert type="warning">
<span slot="message" v-html="$t('message.warn.filetype')" />
<template #message>
<span v-html="$t('message.warn.filetype')" />
</template>
</a-alert>
<a-divider style="margin-top: 0;"></a-divider>
<div :span="24" class="action-button">
@ -119,13 +135,17 @@ export default {
}
},
watch: {
resource: function (oldVal, newVal) {
if (oldVal === newVal) return
this.defaultImage = this.resource?.icon?.base64image || ''
resource: {
deep: true,
handler () {
this.defaultImage = this.resource?.icon?.base64image || ''
}
},
preview: function (data) {
this.realTime(data)
return this.previews
preview: {
deep: true,
handler () {
this.realTime(this.preview)
}
}
},
data () {
@ -151,7 +171,7 @@ export default {
handleClose () {
this.options.img = ''
this.previews = {}
eventBus.$emit('handle-close')
eventBus.emit('handle-close')
},
realTime (data) {
if (data && data.url) {
@ -247,12 +267,12 @@ export default {
})
}).finally(() => {
this.handleClose()
eventBus.$emit('refresh-icon')
eventBus.emit('refresh-icon')
if (['user', 'account'].includes(resourceType.toLowerCase())) {
eventBus.$emit('refresh-header')
eventBus.emit('refresh-header')
}
if (['domain'].includes(this.$route.path.split('/')[1])) {
eventBus.$emit('refresh-domain-icon')
eventBus.emit('refresh-domain-icon')
}
})
},
@ -277,12 +297,12 @@ export default {
})
}).finally(() => {
this.handleClose()
eventBus.$emit('refresh-icon')
eventBus.emit('refresh-icon')
if (['user', 'account'].includes(resourceType.toLowerCase())) {
eventBus.$emit('refresh-header')
eventBus.emit('refresh-header')
}
if (['domain'].includes(this.$route.path.split('/')[1])) {
eventBus.$emit('refresh-domain-icon')
eventBus.emit('refresh-domain-icon')
}
})
}

View File

@ -50,9 +50,12 @@ export default {
}
},
watch: {
resource (newItem, oldItem) {
if (this.resource && this.resource.id && newItem && newItem.id !== oldItem.id) {
this.fetchData()
resource: {
deep: true,
handler (newItem, oldItem) {
if (this.resource && this.resource.id && newItem && newItem.id !== oldItem.id) {
this.fetchData()
}
}
}
},
@ -62,14 +65,12 @@ export default {
methods: {
fetchData () {
if (!this.resource.id) return
this.$set(this.resource, 'vmwaredc', null)
api('listVmwareDcs', {
zoneid: this.resource.id
}).then(response => {
if (response.listvmwaredcsresponse.VMwareDC && response.listvmwaredcsresponse.VMwareDC.length > 0) {
this.vmwaredc = response.listvmwaredcsresponse.VMwareDC[0]
}
this.$set(this.resource, 'vmwaredc', this.vmwaredc)
}).catch(error => {
this.$notifyError(error)
})

View File

@ -22,8 +22,8 @@
v-if="item && item.name"
:to="{ path: item.path === '' ? '/' : item.path }"
>
<a-icon v-if="index == 0" :type="item.meta.icon" style="font-size: 16px" @click="resetToMainView" />
{{ $t(item.meta.title) }}
<render-icon v-if="index == 0" :icon="item.meta.icon" style="font-size: 16px" @click="resetToMainView" />
<span v-if="item.meta.title">{{ $t(item.meta.title) }}</span>
</router-link>
<span v-else-if="$route.params.id">
<label
@ -40,9 +40,9 @@
<span v-else>
{{ $t(item.meta.title) }}
</span>
<span v-if="index === (breadList.length - 1)" style="margin-left: 5px">
<span v-if="index === (breadList.length - 1)" style="margin-left: 8px">
<a-tooltip placement="bottom">
<template slot="title">
<template #title>
{{ $t('label.open.documentation') }}
</template>
<a
@ -50,7 +50,7 @@
style="margin-right: 12px"
:href="$config.docBase + '/' + $route.meta.docHelp"
target="_blank">
<a-icon type="question-circle-o"></a-icon>
<QuestionCircleOutlined />
</a>
</a-tooltip>
<slot name="end">
@ -61,9 +61,11 @@
</template>
<script>
import RenderIcon from '@/utils/renderIcon'
export default {
name: 'Breadcrumb',
components: { RenderIcon },
props: {
resource: {
type: Object,
@ -82,7 +84,7 @@ export default {
this.getBreadcrumb()
},
watch: {
$route () {
'$route.fullPath' () {
this.getBreadcrumb()
}
},
@ -90,8 +92,9 @@ export default {
getBreadcrumb () {
this.name = this.$route.name
this.breadList = []
this.$route.matched.forEach((item) => {
if (item && item.parent && item.parent.name !== 'index' && !item.path.endsWith(':id')) {
this.$route.matched.forEach((item, idx) => {
const parent = this.$route.matched[idx - 1]
if (item && parent && parent.name !== 'index' && !item.path.endsWith(':id')) {
this.breadList.pop()
}
this.breadList.push(item)

View File

@ -21,13 +21,12 @@
: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" />
<code-outlined />
</a-button>
</a>
</template>
<script>
import Vue from 'vue'
import { SERVER_MANAGER } from '@/store/mutation-types'
export default {
@ -47,7 +46,7 @@ export default {
if (!this.$config.multipleServer) {
return this.$config.apiBase.replace('/api', '')
}
const serverStorage = Vue.ls.get(SERVER_MANAGER)
const serverStorage = this.$localStorage.get(SERVER_MANAGER)
const apiBase = serverStorage.apiBase.replace('/api', '')
if (!serverStorage.apiHost || serverStorage.apiHost === '/') {
return [location.origin, apiBase].join('')

View File

@ -144,7 +144,7 @@ export default {
border-radius: 0 5px 5px 0;
}
button {
:deep(button) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
padding-left: 10px;
@ -159,7 +159,7 @@ export default {
border-radius: 5px 0 0 5px;
}
button {
:deep(button) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
padding-left: 12px;

View File

@ -17,7 +17,7 @@
<template>
<a-tooltip placement="bottom">
<template slot="title">
<template #title>
{{ name }}
</template>
<font-awesome-icon
@ -27,6 +27,8 @@
v-if="logo !== 'debian'" />
<debian-icon
v-else-if="logo === 'debian'"
:width="size === '4x' ? 56 : 16"
:height="size === '4x' ? 56 : 16"
:style="{
height: size === '4x' ? '56px' : '16px',
width: size === '4x' ? '56px' : '16px',
@ -74,8 +76,7 @@ export default {
}
},
watch: {
osId: function (newItem, oldItem) {
this.osId = newItem
osId: function () {
this.fetchData()
}
},

View File

@ -16,9 +16,9 @@
// under the License.
<template>
<a-tooltip placement="bottom" :title="$t(getTooltip(text))">
<a-tooltip placement="bottom" :title="getTooltip(text)">
<a-badge
style="display: inline-flex"
:style="getStyle()"
:title="text"
:color="getStatusColor(text)"
:status="getBadgeStatus(text)"
@ -38,6 +38,10 @@ export default {
displayText: {
type: Boolean,
default: false
},
styles: {
type: Object,
default: () => {}
}
},
methods: {
@ -158,34 +162,50 @@ export default {
},
getTooltip (state) {
if (!(state && this.displayText)) {
return
return ''
}
if (this.$route.path === '/vmsnapshot' || this.$route.path.includes('/vmsnapshot/')) {
return 'message.vmsnapshot.state.' + state.toLowerCase()
return this.$t('message.vmsnapshot.state.' + state.toLowerCase())
}
if (this.$route.path === '/vm' || this.$route.path.includes('/vm/')) {
return 'message.vm.state.' + state.toLowerCase()
return this.$t('message.vm.state.' + state.toLowerCase())
}
if (this.$route.path === '/volume' || this.$route.path.includes('/volume/')) {
return 'message.volume.state.' + state.toLowerCase()
return this.$t('message.volume.state.' + state.toLowerCase())
}
if (this.$route.path === '/guestnetwork' || this.$route.path.includes('/guestnetwork/')) {
return 'message.guestnetwork.state.' + state.toLowerCase()
return this.$t('message.guestnetwork.state.' + state.toLowerCase())
}
if (this.$route.path === '/publicip' || this.$route.path.includes('/publicip/')) {
return 'message.publicip.state.' + state.toLowerCase()
return this.$t('message.publicip.state.' + state.toLowerCase())
}
// Nothing for snapshots, vpcs, gateways, vnpnconn, vpnuser, kubectl, event, project, account, infra. They're all self explanatory
return state
return this.$t(state)
},
getStyle () {
let styles = { display: 'inline-flex' }
if (this.styles && typeof this.styles === 'object') {
styles = Object.assign({}, styles, this.styles)
}
return styles
}
}
}
</script>
<style scoped>
/deep/ .ant-badge-status-dot {
<style scoped lang="less">
:deep(.ant-badge-status-dot) {
width: 12px;
height: 12px;
margin-top: 5px;
}
.status {
margin-top: -5px;
&--end {
margin-left: 5px;
}
}
</style>

View File

@ -17,31 +17,50 @@
<template>
<a-tooltip arrowPointAtCenter :placement="tooltipPlacement">
<template slot="title" v-if="tooltip">
<template #title v-if="tooltip">
{{ tooltip }}
</template>
<a-button
style="margin-left: -5px"
v-if="copyResource"
shape="circle"
:size="size"
:type="type"
:danger="danger"
:disabled="disabled"
:class="buttonClass"
:loading="loading"
@click="handleClicked()"
v-clipboard:copy="copyResource" >
<template #icon v-if="icon"><render-icon :icon="icon" /></template>
<template v-if="iconType && iconTwoToneColor">
<render-icon :icon="iconType" :props="{ theme: 'twoTone', twoToneColor: iconTwoToneColor }" />
</template>
</a-button>
<a-button
v-else
shape="circle"
:size="size"
:type="type"
:danger="danger"
:disabled="disabled"
:icon="icon"
:class="buttonClass"
:loading="loading"
@click="handleClicked()" >
<a-icon
v-if="iconType && iconTwoToneColor"
:type="iconType"
theme="twoTone"
:twoToneColor="iconTwoToneColor" />
<template #icon v-if="icon"><render-icon :icon="icon" /></template>
<template v-if="iconType && iconTwoToneColor">
<render-icon :icon="iconType" :props="{ theme: 'twoTone', twoToneColor: iconTwoToneColor }" />
</template>
</a-button>
</a-tooltip>
</template>
<script>
import RenderIcon from '@/utils/renderIcon'
export default {
name: 'TooltipButton',
components: { RenderIcon },
props: {
tooltip: {
type: String,
@ -82,15 +101,19 @@ export default {
loading: {
type: Boolean,
default: false
}
},
data () {
return {
},
copyResource: {
type: String,
default: ''
},
danger: {
type: Boolean,
default: false
}
},
methods: {
handleClicked () {
this.$emit('click')
this.$emit('onClick')
}
}
}

View File

@ -19,7 +19,7 @@
<span>
{{ title }}
<a-tooltip v-if="tooltip" :title="tooltip" :placement="tooltipPlacement">
<a-icon type="info-circle" class="tooltip-icon" />
<info-circle-outlined class="tooltip-icon" />
</a-tooltip>
</span>
</template>

View File

@ -15,5 +15,5 @@
// specific language governing permissions and limitations
// under the License.
import Vue from 'vue'
export default new Vue()
import mitt from 'mitt'
export default mitt()

View File

@ -16,10 +16,12 @@
// under the License.
// eslint-disable-next-line
import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
import { UserLayout, BasicLayout, RouteView } from '@/layouts'
import AutogenView from '@/views/AutogenView.vue'
import IFramePlugin from '@/views/plugins/IFramePlugin.vue'
import Vue from 'vue'
import { shallowRef, defineAsyncComponent } from 'vue'
import { vueProps } from '@/vue-app'
import compute from '@/config/section/compute'
import storage from '@/config/section/storage'
@ -46,11 +48,11 @@ function generateRouterMap (section) {
meta: {
title: section.title,
icon: section.icon,
docHelp: Vue.prototype.$applyDocHelpMappings(section.docHelp),
docHelp: vueProps.$applyDocHelpMappings(section.docHelp),
searchFilters: section.searchFilters,
related: section.related
},
component: RouteView
component: shallowRef(RouteView)
}
if (section.children && section.children.length > 0) {
@ -61,7 +63,7 @@ function generateRouterMap (section) {
if ('show' in child && !child.show()) {
continue
}
var component = child.component ? child.component : AutogenView
var component = child.component ? child.component : shallowRef(AutogenView)
var route = {
name: child.name,
path: '/' + child.name,
@ -70,7 +72,7 @@ function generateRouterMap (section) {
title: child.title,
name: child.name,
icon: child.icon,
docHelp: Vue.prototype.$applyDocHelpMappings(child.docHelp),
docHelp: vueProps.$applyDocHelpMappings(child.docHelp),
permission: child.permission,
resourceType: child.resourceType,
filters: child.filters,
@ -92,7 +94,7 @@ function generateRouterMap (section) {
title: child.title,
name: child.name,
icon: child.icon,
docHelp: Vue.prototype.$applyDocHelpMappings(child.docHelp),
docHelp: vueProps.$applyDocHelpMappings(child.docHelp),
permission: child.permission,
resourceType: child.resourceType,
params: child.params ? child.params : {},
@ -128,7 +130,7 @@ function generateRouterMap (section) {
map.children.push(route)
}
} else {
map.component = section.component ? section.component : AutogenView
map.component = section.component ? section.component : shallowRef(AutogenView)
map.hideChildrenInMenu = true
map.meta.name = section.name
@ -147,7 +149,7 @@ function generateRouterMap (section) {
title: section.title,
name: section.name,
icon: section.icon,
docHelp: Vue.prototype.$applyDocHelpMappings(section.docHelp),
docHelp: vueProps.$applyDocHelpMappings(section.docHelp),
hidden: section.hidden,
permission: section.permission,
resourceType: section.resourceType,
@ -158,7 +160,7 @@ function generateRouterMap (section) {
tabs: section.tabs,
actions: section.actions ? section.actions : []
},
component: section.component ? section.component : AutogenView
component: section.component ? section.component : shallowRef(AutogenView)
}]
}
@ -189,8 +191,8 @@ export function asyncRouterMap () {
const routerMap = [{
path: '/',
name: 'index',
component: BasicLayout,
meta: { icon: 'home' },
component: shallowRef(BasicLayout),
meta: { icon: 'HomeOutlined' },
redirect: '/dashboard',
children: [
{
@ -198,16 +200,16 @@ export function asyncRouterMap () {
name: 'dashboard',
meta: {
title: 'label.dashboard',
icon: 'dashboard',
icon: 'DashboardOutlined',
tabs: [
{
name: 'dashboard',
component: () => import('@/views/dashboard/UsageDashboardChart')
component: shallowRef(defineAsyncComponent(() => import('@/views/dashboard/UsageDashboardChart')))
},
{
name: 'accounts',
show: (record, route, user) => { return record.account === user.account || ['Admin', 'DomainAdmin'].includes(user.roletype) },
component: () => import('@/views/project/AccountsTab')
component: shallowRef(defineAsyncComponent(() => import('@/views/project/AccountsTab')))
},
{
name: 'limits',
@ -215,7 +217,7 @@ export function asyncRouterMap () {
projectid: 'id'
},
show: (record, route, user) => { return ['Admin'].includes(user.roletype) },
component: () => import('@/components/view/ResourceLimitTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceLimitTab.vue')))
}
]
},
@ -242,7 +244,7 @@ export function asyncRouterMap () {
{
path: '/exception',
name: 'exception',
component: RouteView,
component: shallowRef(RouteView),
hidden: true,
redirect: '/exception/404',
meta: { title: 'Exception', icon: 'warning' },
@ -273,10 +275,10 @@ export function asyncRouterMap () {
]
},
{
path: '*', redirect: '/exception/404', hidden: true
path: '/:catchAll(.*)', redirect: '/exception/404', hidden: true
}]
const plugins = Vue.prototype.$config.plugins
const plugins = vueProps.$config.plugins
if (plugins && plugins.length > 0) {
plugins.map(plugin => {
routerMap[0].children.push({

View File

@ -15,12 +15,13 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'account',
title: 'label.accounts',
icon: 'team',
icon: 'team-outlined',
docHelp: 'adminguide/accounts.html',
permission: ['listAccounts'],
columns: ['name', 'state', 'rolename', 'roletype', 'domainpath'],
@ -33,39 +34,39 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'resources',
component: () => import('@/components/view/ResourceCountUsage.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceCountUsage.vue')))
},
{
name: 'limits',
show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) },
component: () => import('@/components/view/ResourceLimitTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceLimitTab.vue')))
},
{
name: 'certificate',
component: () => import('@/views/iam/SSLCertificateTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/SSLCertificateTab.vue')))
},
{
name: 'settings',
component: () => import('@/components/view/SettingsTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue'))),
show: () => { return 'listConfigurations' in store.getters.apis }
}
],
actions: [
{
api: 'createAccount',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.account',
listView: true,
popup: true,
component: () => import('@/views/iam/AddAccount.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/AddAccount.vue')))
},
{
api: 'ldapCreateAccount',
icon: 'user-add',
icon: 'user-add-outlined',
label: 'label.add.ldap.account',
docHelp: 'adminguide/accounts.html#using-an-ldap-server-for-user-authentication',
listView: true,
@ -73,11 +74,11 @@ export default {
show: (record, store) => {
return store.isLdapEnabled
},
component: () => import('@/views/iam/AddLdapAccount.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/AddLdapAccount.vue')))
},
{
api: 'updateAccount',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.action.edit.account',
dataView: true,
args: ['newname', 'account', 'domainid', 'networkdomain'],
@ -92,7 +93,7 @@ export default {
},
{
api: 'updateResourceCount',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.action.update.resource.count',
message: 'message.update.resource.count',
dataView: true,
@ -109,7 +110,7 @@ export default {
},
{
api: 'enableAccount',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.action.enable.account',
message: 'message.enable.account',
dataView: true,
@ -125,7 +126,7 @@ export default {
},
{
api: 'disableAccount',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.action.disable.account',
message: 'message.disable.account',
dataView: true,
@ -146,7 +147,7 @@ export default {
},
{
api: 'disableAccount',
icon: 'lock',
icon: 'LockOutlined',
label: 'label.action.lock.account',
message: 'message.lock.account',
dataView: true,
@ -167,7 +168,7 @@ export default {
},
{
api: 'uploadSslCert',
icon: 'safety-certificate',
icon: 'SafetyCertificateOutlined',
label: 'label.add.certificate',
dataView: true,
args: ['name', 'certificate', 'privatekey', 'certchain', 'password', 'account', 'domainid'],
@ -184,7 +185,7 @@ export default {
},
{
api: 'deleteAccount',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.account',
message: 'message.delete.account',
dataView: true,

View File

@ -15,18 +15,19 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import kubernetes from '@/assets/icons/kubernetes.svg?inline'
import store from '@/store'
export default {
name: 'compute',
title: 'label.compute',
icon: 'cloud',
icon: 'cloud-outlined',
children: [
{
name: 'vm',
title: 'label.instances',
icon: 'desktop',
icon: 'desktop-outlined',
docHelp: 'adminguide/virtual_machines.html',
permission: ['listVirtualMachinesMetrics'],
resourceType: 'UserVm',
@ -68,12 +69,12 @@ export default {
searchFilters: ['name', 'zoneid', 'domainid', 'account', 'tags'],
details: ['displayname', 'name', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename', 'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'boottype', 'bootmode', 'account', 'domain', 'zonename'],
tabs: [{
component: () => import('@/views/compute/InstanceTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/InstanceTab.vue')))
}],
actions: [
{
api: 'deployVirtualMachine',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.vm.add',
docHelp: 'adminguide/virtual_machines.html#creating-vms',
listView: true,
@ -81,16 +82,16 @@ export default {
},
{
api: 'updateVirtualMachine',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.action.edit.instance',
docHelp: 'adminguide/virtual_machines.html#changing-the-vm-name-os-or-group',
dataView: true,
popup: true,
component: () => import('@/views/compute/EditVM.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/EditVM.vue')))
},
{
api: 'startVirtualMachine',
icon: 'caret-right',
icon: 'caret-right-outlined',
label: 'label.action.start.instance',
message: 'message.action.start.instance',
docHelp: 'adminguide/virtual_machines.html#stopping-and-starting-vms',
@ -99,11 +100,11 @@ export default {
popup: true,
groupMap: (selection) => { return selection.map(x => { return { id: x } }) },
show: (record) => { return ['Stopped'].includes(record.state) },
component: () => import('@/views/compute/StartVirtualMachine.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/StartVirtualMachine.vue')))
},
{
api: 'stopVirtualMachine',
icon: 'poweroff',
icon: 'poweroff-outlined',
label: 'label.action.stop.instance',
message: 'message.action.stop.instance',
docHelp: 'adminguide/virtual_machines.html#stopping-and-starting-vms',
@ -115,7 +116,7 @@ export default {
},
{
api: 'rebootVirtualMachine',
icon: 'reload',
icon: 'reload-outlined',
label: 'label.action.reboot.instance',
message: 'message.action.reboot.instance',
docHelp: 'adminguide/virtual_machines.html#stopping-and-starting-vms',
@ -137,7 +138,7 @@ export default {
},
{
api: 'restoreVirtualMachine',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.reinstall.vm',
message: 'message.reinstall.vm',
dataView: true,
@ -162,7 +163,7 @@ export default {
},
{
api: 'createVMSnapshot',
icon: 'camera',
icon: 'camera-outlined',
label: 'label.action.vmsnapshot.create',
docHelp: 'adminguide/virtual_machines.html#virtual-machine-snapshots',
dataView: true,
@ -189,11 +190,11 @@ export default {
return ((['Running'].includes(record.state) && record.hypervisor !== 'LXC') ||
(['Stopped'].includes(record.state) && !['KVM', 'LXC'].includes(record.hypervisor)))
},
component: () => import('@/views/compute/CreateSnapshotWizard.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/CreateSnapshotWizard.vue')))
},
{
api: 'assignVirtualMachineToBackupOffering',
icon: 'folder-add',
icon: 'folder-add-outlined',
label: 'label.backup.offering.assign',
message: 'label.backup.offering.assign',
docHelp: 'adminguide/virtual_machines.html#backup-offerings',
@ -208,7 +209,7 @@ export default {
},
{
api: 'createBackup',
icon: 'cloud-upload',
icon: 'cloud-upload-outlined',
label: 'label.create.backup',
message: 'message.backup.create',
docHelp: 'adminguide/virtual_machines.html#creating-vm-backups',
@ -223,13 +224,13 @@ export default {
},
{
api: 'createBackupSchedule',
icon: 'schedule',
icon: 'schedule-outlined',
label: 'Configure Backup Schedule',
docHelp: 'adminguide/virtual_machines.html#creating-vm-backups',
dataView: true,
popup: true,
show: (record) => { return record.backupofferingid },
component: () => import('@/views/compute/BackupScheduleWizard.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/BackupScheduleWizard.vue'))),
mapping: {
virtualmachineid: {
value: (record, params) => { return record.id }
@ -241,7 +242,7 @@ export default {
},
{
api: 'removeVirtualMachineFromBackupOffering',
icon: 'scissor',
icon: 'scissor-outlined',
label: 'label.backup.offering.remove',
message: 'label.backup.offering.remove',
docHelp: 'adminguide/virtual_machines.html#restoring-vm-backups',
@ -256,17 +257,17 @@ export default {
},
{
api: 'attachIso',
icon: 'paper-clip',
icon: 'paper-clip-outlined',
label: 'label.action.attach.iso',
docHelp: 'adminguide/templates.html#attaching-an-iso-to-a-vm',
dataView: true,
popup: true,
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && !record.isoid },
component: () => import('@/views/compute/AttachIso.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/AttachIso.vue')))
},
{
api: 'detachIso',
icon: 'link',
icon: 'link-outlined',
label: 'label.action.detach.iso',
message: 'message.detach.iso.confirm',
dataView: true,
@ -286,50 +287,50 @@ export default {
},
{
api: 'updateVMAffinityGroup',
icon: 'swap',
icon: 'swap-outlined',
label: 'label.change.affinity',
docHelp: 'adminguide/virtual_machines.html#change-affinity-group-for-an-existing-vm',
dataView: true,
args: ['affinitygroupids'],
show: (record) => { return ['Stopped'].includes(record.state) },
component: () => import('@/views/compute/ChangeAffinity'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ChangeAffinity'))),
popup: true
},
{
api: 'scaleVirtualMachine',
icon: 'arrows-alt',
icon: 'arrows-alt-outlined',
label: 'label.scale.vm',
docHelp: 'adminguide/virtual_machines.html#how-to-dynamically-scale-cpu-and-ram',
dataView: true,
show: (record) => { return ['Stopped'].includes(record.state) || (['Running'].includes(record.state) && record.hypervisor !== 'LXC') },
disabled: (record) => { return record.state === 'Running' && !record.isdynamicallyscalable },
popup: true,
component: () => import('@/views/compute/ScaleVM.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ScaleVM.vue')))
},
{
api: 'migrateVirtualMachine',
icon: 'drag',
icon: 'drag-outlined',
label: 'label.migrate.instance.to.host',
docHelp: 'adminguide/virtual_machines.html#moving-vms-between-hosts-manual-live-migration',
dataView: true,
show: (record, store) => { return ['Running'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
popup: true,
component: () => import('@/views/compute/MigrateWizard')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateWizard.vue')))
},
{
api: 'migrateVirtualMachine',
icon: 'drag',
icon: 'drag-outlined',
label: 'label.migrate.instance.to.ps',
message: 'message.migrate.instance.to.ps',
docHelp: 'adminguide/virtual_machines.html#moving-vms-between-hosts-manual-live-migration',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
component: () => import('@/views/compute/MigrateVMStorage'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateVMStorage'))),
popup: true
},
{
api: 'resetPasswordForVirtualMachine',
icon: 'key',
icon: 'key-outlined',
label: 'label.action.reset.password',
message: 'message.action.instance.reset.password',
dataView: true,
@ -338,27 +339,27 @@ export default {
},
{
api: 'resetSSHKeyForVirtualMachine',
icon: 'lock',
icon: 'lock-outlined',
label: 'label.reset.ssh.key.pair',
message: 'message.desc.reset.ssh.key.pair',
docHelp: 'adminguide/virtual_machines.html#resetting-ssh-keys',
dataView: true,
show: (record) => { return ['Stopped'].includes(record.state) },
popup: true,
component: () => import('@/views/compute/ResetSshKeyPair')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ResetSshKeyPair')))
},
{
api: 'assignVirtualMachine',
icon: 'user-add',
icon: 'user-add-outlined',
label: 'label.assign.instance.another',
dataView: true,
component: () => import('@/views/compute/AssignInstance'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/AssignInstance'))),
popup: true,
show: (record) => { return ['Stopped'].includes(record.state) }
},
{
api: 'recoverVirtualMachine',
icon: 'medicine-box',
icon: 'medicine-box-outlined',
label: 'label.recover.vm',
message: 'message.recover.vm',
dataView: true,
@ -366,7 +367,7 @@ export default {
},
{
api: 'unmanageVirtualMachine',
icon: 'disconnect',
icon: 'disconnect-outlined',
label: 'label.action.unmanage.virtualmachine',
message: 'message.action.unmanage.virtualmachine',
dataView: true,
@ -374,7 +375,7 @@ export default {
},
{
api: 'expungeVirtualMachine',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.expunge.instance',
message: (record) => { return record.backupofferingid ? 'message.action.expunge.instance.with.backups' : 'message.action.expunge.instance' },
docHelp: 'adminguide/virtual_machines.html#deleting-vms',
@ -383,7 +384,7 @@ export default {
},
{
api: 'destroyVirtualMachine',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.destroy.instance',
message: 'message.action.destroy.instance',
docHelp: 'adminguide/virtual_machines.html#deleting-vms',
@ -396,14 +397,14 @@ export default {
popup: true,
groupMap: (selection, values) => { return selection.map(x => { return { id: x, expunge: values.expunge } }) },
show: (record) => { return ['Running', 'Stopped', 'Error'].includes(record.state) },
component: () => import('@/views/compute/DestroyVM.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/DestroyVM.vue')))
}
]
},
{
name: 'kubernetes',
title: 'label.kubernetes',
icon: kubernetes,
icon: shallowRef(kubernetes),
docHelp: 'plugins/cloudstack-kubernetes-service.html',
permission: ['listKubernetesClusters'],
columns: (store) => {
@ -420,22 +421,22 @@ export default {
details: ['name', 'description', 'zonename', 'kubernetesversionname', 'autoscalingenabled', 'minsize', 'maxsize', 'size', 'controlnodes', 'cpunumber', 'memory', 'keypair', 'associatednetworkname', 'account', 'domain', 'zonename'],
tabs: [{
name: 'k8s',
component: () => import('@/views/compute/KubernetesServiceTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/KubernetesServiceTab.vue')))
}],
resourceType: 'KubernetesCluster',
actions: [
{
api: 'createKubernetesCluster',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.kubernetes.cluster.create',
docHelp: 'plugins/cloudstack-kubernetes-service.html#creating-a-new-kubernetes-cluster',
listView: true,
popup: true,
component: () => import('@/views/compute/CreateKubernetesCluster.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/CreateKubernetesCluster.vue')))
},
{
api: 'startKubernetesCluster',
icon: 'caret-right',
icon: 'caret-right-outlined',
label: 'label.kubernetes.cluster.start',
message: 'message.kubernetes.cluster.start',
docHelp: 'plugins/cloudstack-kubernetes-service.html#starting-a-stopped-kubernetes-cluster',
@ -447,7 +448,7 @@ export default {
},
{
api: 'stopKubernetesCluster',
icon: 'poweroff',
icon: 'poweroff-outlined',
label: 'label.kubernetes.cluster.stop',
message: 'message.kubernetes.cluster.stop',
docHelp: 'plugins/cloudstack-kubernetes-service.html#stopping-kubernetes-cluster',
@ -459,29 +460,29 @@ export default {
},
{
api: 'scaleKubernetesCluster',
icon: 'swap',
icon: 'swap-outlined',
label: 'label.kubernetes.cluster.scale',
message: 'message.kubernetes.cluster.scale',
docHelp: 'plugins/cloudstack-kubernetes-service.html#scaling-kubernetes-cluster',
dataView: true,
show: (record) => { return ['Created', 'Running'].includes(record.state) },
popup: true,
component: () => import('@/views/compute/ScaleKubernetesCluster.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ScaleKubernetesCluster.vue')))
},
{
api: 'upgradeKubernetesCluster',
icon: 'plus-circle',
icon: 'plus-circle-outlined',
label: 'label.kubernetes.cluster.upgrade',
message: 'message.kubernetes.cluster.upgrade',
docHelp: 'plugins/cloudstack-kubernetes-service.html#upgrading-kubernetes-cluster',
dataView: true,
show: (record) => { return ['Created', 'Running'].includes(record.state) },
popup: true,
component: () => import('@/views/compute/UpgradeKubernetesCluster.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/UpgradeKubernetesCluster.vue')))
},
{
api: 'deleteKubernetesCluster',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.kubernetes.cluster.delete',
message: 'message.kubernetes.cluster.delete',
docHelp: 'plugins/cloudstack-kubernetes-service.html#deleting-kubernetes-cluster',
@ -496,7 +497,7 @@ export default {
{
name: 'vmgroup',
title: 'label.instance.groups',
icon: 'gold',
icon: 'gold-outlined',
docHelp: 'adminguide/virtual_machines.html#changing-the-vm-name-os-or-group',
resourceType: 'VMInstanceGroup',
permission: ['listInstanceGroups'],
@ -510,31 +511,31 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
actions: [
{
api: 'createInstanceGroup',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.new.instance.group',
listView: true,
args: ['name']
},
{
api: 'updateInstanceGroup',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.update.instance.group',
dataView: true,
args: ['name']
},
{
api: 'deleteInstanceGroup',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.instance.group',
message: 'message.action.delete.instance.group',
dataView: true,
@ -547,7 +548,7 @@ export default {
{
name: 'ssh',
title: 'label.ssh.key.pairs',
icon: 'key',
icon: 'key-outlined',
docHelp: 'adminguide/virtual_machines.html#using-ssh-keys-for-authentication',
permission: ['listSSHKeyPairs'],
columns: () => {
@ -567,26 +568,26 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
actions: [
{
api: 'createSSHKeyPair',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.create.ssh.key.pair',
docHelp: 'adminguide/virtual_machines.html#creating-the-ssh-keypair',
listView: true,
popup: true,
component: () => import('@/views/compute/CreateSSHKeyPair.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/CreateSSHKeyPair.vue')))
},
{
api: 'deleteSSHKeyPair',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.remove.ssh.key.pair',
message: 'message.please.confirm.remove.ssh.key.pair',
dataView: true,
@ -618,7 +619,7 @@ export default {
{
name: 'affinitygroup',
title: 'label.affinity.groups',
icon: 'swap',
icon: 'swap-outlined',
docHelp: 'adminguide/virtual_machines.html#affinity-groups',
permission: ['listAffinityGroups'],
columns: () => {
@ -637,7 +638,7 @@ export default {
actions: [
{
api: 'createAffinityGroup',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.affinity.group',
docHelp: 'adminguide/virtual_machines.html#creating-a-new-affinity-group',
listView: true,
@ -650,7 +651,7 @@ export default {
},
{
api: 'deleteAffinityGroup',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.affinity.group',
docHelp: 'adminguide/virtual_machines.html#delete-an-affinity-group',
message: 'message.delete.affinity.group',

View File

@ -18,27 +18,27 @@
export default {
name: 'config',
title: 'label.configuration',
icon: 'setting',
icon: 'setting-outlined',
permission: ['listConfigurations', 'listInfrastructure'],
children: [
{
name: 'globalsetting',
title: 'label.global.settings',
icon: 'setting',
icon: 'setting-outlined',
permission: ['listConfigurations'],
columns: ['name', 'description', 'category', 'value', 'actions']
},
{
name: 'ldapsetting',
title: 'label.ldap.configuration',
icon: 'team',
icon: 'team-outlined',
permission: ['listLdapConfigurations'],
columns: ['hostname', 'port', 'domainid'],
details: ['hostname', 'port', 'domainid'],
actions: [
{
api: 'addLdapConfiguration',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.configure.ldap',
listView: true,
args: [
@ -47,7 +47,7 @@ export default {
},
{
api: 'deleteLdapConfiguration',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.remove.ldap',
message: 'message.remove.ldap',
dataView: true,
@ -69,14 +69,14 @@ export default {
{
name: 'hypervisorcapability',
title: 'label.hypervisor.capabilities',
icon: 'database',
icon: 'database-outlined',
permission: ['listHypervisorCapabilities'],
columns: ['hypervisor', 'hypervisorversion', 'maxguestslimit', 'maxhostspercluster'],
details: ['hypervisor', 'hypervisorversion', 'maxguestslimit', 'maxdatavolumeslimit', 'maxhostspercluster', 'securitygroupenabled', 'storagemotionenabled'],
actions: [
{
api: 'updateHypervisorCapabilities',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['maxguestslimit']

View File

@ -15,18 +15,19 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'domain',
title: 'label.domains',
icon: 'block',
icon: 'BlockOutlined',
docHelp: 'adminguide/accounts.html#domains',
permission: ['listDomains', 'listDomainChildren'],
resourceType: 'Domain',
columns: ['name', 'state', 'path', 'parentdomainname', 'level'],
details: ['name', 'id', 'path', 'parentdomainname', 'level', 'networkdomain', 'created'],
component: () => import('@/views/iam/DomainView.vue'),
component: shallowRef(() => import('@/views/iam/DomainView.vue')),
related: [{
name: 'account',
title: 'label.accounts',
@ -35,37 +36,37 @@ export default {
tabs: [
{
name: 'domain',
component: () => import('@/components/view/InfoCard.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/components/view/InfoCard.vue'))),
show: (record, route) => { return route.path === '/domain' }
},
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'resources',
show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) },
component: () => import('@/components/view/ResourceCountUsage.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceCountUsage.vue')))
},
{
name: 'limits',
show: (record, route, user) => { return ['Admin'].includes(user.roletype) || (['DomainAdmin'].includes(user.roletype) && record.id !== user.domainid) },
component: () => import('@/components/view/ResourceLimitTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceLimitTab.vue')))
},
{
name: 'settings',
component: () => import('@/components/view/SettingsTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue'))),
show: () => { return 'listConfigurations' in store.getters.apis }
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
treeView: true,
actions: [
{
api: 'createDomain',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.domain',
listView: true,
dataView: false,
@ -78,7 +79,7 @@ export default {
},
{
api: 'updateDomain',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.action.edit.domain',
listView: true,
dataView: true,
@ -96,7 +97,7 @@ export default {
},
{
api: 'updateResourceCount',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.action.update.resource.count',
message: 'message.update.resource.count.domain',
listView: true,
@ -110,7 +111,7 @@ export default {
},
{
api: 'linkDomainToLdap',
icon: 'link',
icon: 'LinkOutlined',
label: 'label.link.domain.to.ldap',
docHelp: 'adminguide/accounts.html#using-an-ldap-server-for-user-authentication',
listView: true,
@ -130,7 +131,7 @@ export default {
},
{
api: 'deleteDomain',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.domain',
listView: true,
dataView: true,

View File

@ -18,7 +18,7 @@
export default {
name: 'event',
title: 'label.events',
icon: 'schedule',
icon: 'ScheduleOutlined',
docHelp: 'adminguide/events.html',
permission: ['listEvents'],
columns: ['level', 'type', 'state', 'description', 'username', 'account', 'domain', 'created'],
@ -32,7 +32,7 @@ export default {
actions: [
{
api: 'archiveEvents',
icon: 'book',
icon: 'book-outlined',
label: 'label.archive.events',
message: 'message.confirm.archive.selected.events',
docHelp: 'adminguide/events.html#deleting-and-archiving-events-and-alerts',
@ -49,7 +49,7 @@ export default {
},
{
api: 'deleteEvents',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.events',
message: 'message.confirm.remove.selected.events',
docHelp: 'adminguide/events.html#deleting-and-archiving-events-and-alerts',

View File

@ -15,19 +15,20 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import kubernetes from '@/assets/icons/kubernetes.svg?inline'
import store from '@/store'
export default {
name: 'image',
title: 'label.images',
icon: 'picture',
icon: 'picture-outlined',
docHelp: 'adminguide/templates.html',
children: [
{
name: 'template',
title: 'label.templates',
icon: 'save',
icon: 'save-outlined',
docHelp: 'adminguide/templates.html',
permission: ['listTemplates'],
params: { templatefilter: 'self', showunique: 'true' },
@ -60,40 +61,40 @@ export default {
}],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'zones',
component: () => import('@/views/image/TemplateZones.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/TemplateZones.vue')))
}, {
name: 'settings',
component: () => import('@/components/view/DetailSettings')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailSettings')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'registerTemplate',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.action.register.template',
docHelp: 'adminguide/templates.html#uploading-templates-from-a-remote-http-server',
listView: true,
popup: true,
component: () => import('@/views/image/RegisterOrUploadTemplate.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/RegisterOrUploadTemplate.vue')))
},
{
api: 'registerTemplate',
icon: 'cloud-upload',
icon: 'cloud-upload-outlined',
label: 'label.upload.template.from.local',
docHelp: 'adminguide/templates.html#uploading-templates-and-isos-from-a-local-computer',
listView: true,
popup: true,
component: () => import('@/views/image/RegisterOrUploadTemplate.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/RegisterOrUploadTemplate.vue')))
},
{
api: 'updateTemplate',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
show: (record, store) => {
@ -103,11 +104,11 @@ export default {
record.isready
},
popup: true,
component: () => import('@/views/image/UpdateTemplate.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/UpdateTemplate.vue')))
},
{
api: 'updateTemplatePermissions',
icon: 'share-alt',
icon: 'share-alt-outlined',
label: 'label.action.template.share',
dataView: true,
args: (record, store) => {
@ -127,7 +128,7 @@ export default {
},
{
api: 'extractTemplate',
icon: 'cloud-download',
icon: 'cloud-download-outlined',
label: 'label.action.download.template',
message: 'message.action.download.template',
docHelp: 'adminguide/templates.html#exporting-templates',
@ -153,7 +154,7 @@ export default {
},
{
api: 'updateTemplatePermissions',
icon: 'reconciliation',
icon: 'reconciliation-outlined',
label: 'label.action.template.permission',
docHelp: 'adminguide/templates.html#sharing-templates-with-other-accounts-projects',
dataView: true,
@ -165,14 +166,14 @@ export default {
record.templatetype !== 'SYSTEM' &&
record.isready
},
component: () => import('@/views/image/UpdateTemplateIsoPermissions')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/UpdateTemplateIsoPermissions')))
}
]
},
{
name: 'iso',
title: 'label.isos',
icon: 'usb',
icon: 'usb-outlined',
docHelp: 'adminguide/templates.html#working-with-isos',
permission: ['listIsos'],
params: { isofilter: 'self', showunique: 'true' },
@ -197,37 +198,37 @@ export default {
}],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'zones',
component: () => import('@/views/image/IsoZones.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/IsoZones.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'registerIso',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.action.register.iso',
docHelp: 'adminguide/templates.html#id10',
listView: true,
popup: true,
component: () => import('@/views/image/RegisterOrUploadIso.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/RegisterOrUploadIso.vue')))
},
{
api: 'registerIso',
icon: 'cloud-upload',
icon: 'cloud-upload-outlined',
label: 'label.upload.iso.from.local',
docHelp: 'adminguide/templates.html#id10',
listView: true,
popup: true,
component: () => import('@/views/image/RegisterOrUploadIso.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/RegisterOrUploadIso.vue')))
},
{
api: 'updateIso',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.action.edit.iso',
dataView: true,
show: (record, store) => {
@ -241,7 +242,7 @@ export default {
},
{
api: 'updateIsoPermissions',
icon: 'share-alt',
icon: 'share-alt-outlined',
label: 'label.action.iso.share',
dataView: true,
args: (record, store) => {
@ -261,7 +262,7 @@ export default {
},
{
api: 'extractIso',
icon: 'cloud-download',
icon: 'cloud-download-outlined',
label: 'label.action.download.iso',
message: 'message.action.download.iso',
docHelp: 'adminguide/templates.html#exporting-templates',
@ -286,7 +287,7 @@ export default {
},
{
api: 'updateIsoPermissions',
icon: 'reconciliation',
icon: 'reconciliation-outlined',
label: 'label.action.iso.permission',
docHelp: 'adminguide/templates.html#sharing-templates-with-other-accounts-projects',
dataView: true,
@ -299,14 +300,14 @@ export default {
!(record.account === 'system' && record.domainid === 1) &&
record.isready
},
component: () => import('@/views/image/UpdateTemplateIsoPermissions')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/UpdateTemplateIsoPermissions')))
}
]
},
{
name: 'kubernetesiso',
title: 'label.kubernetes.isos',
icon: kubernetes,
icon: shallowRef(kubernetes),
docHelp: 'plugins/cloudstack-kubernetes-service.html#kubernetes-supported-versions',
permission: ['listKubernetesSupportedVersions'],
columns: ['name', 'state', 'semanticversion', 'isostate', 'mincpunumber', 'minmemory', 'zonename'],
@ -314,23 +315,23 @@ export default {
actions: [
{
api: 'addKubernetesSupportedVersion',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.kubernetes.version.add',
listView: true,
popup: true,
component: () => import('@/views/image/AddKubernetesSupportedVersion.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/AddKubernetesSupportedVersion.vue')))
},
{
api: 'updateKubernetesSupportedVersion',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.kubernetes.version.update',
dataView: true,
popup: true,
component: () => import('@/views/image/UpdateKubernetesSupportedVersion.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/image/UpdateKubernetesSupportedVersion.vue')))
},
{
api: 'deleteKubernetesSupportedVersion',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.kubernetes.version.delete',
message: 'message.kubernetes.version.delete',
dataView: true

View File

@ -30,13 +30,13 @@ import ilbvms from '@/config/section/infra/ilbvms'
export default {
name: 'infra',
title: 'label.infrastructure',
icon: 'bank',
icon: 'BankOutlined',
permission: ['listInfrastructure'],
children: [
{
name: 'infrasummary',
title: 'label.summary',
icon: 'read',
icon: 'ReadOutlined',
permission: ['listInfrastructure'],
component: () => import('@/views/infra/InfraSummary.vue')
},
@ -54,7 +54,7 @@ export default {
{
name: 'cpusocket',
title: 'label.cpu.sockets',
icon: 'inbox',
icon: 'InboxOutlined',
docHelp: 'adminguide/management.html#reporting-cpu-sockets',
permission: ['listHosts'],
component: () => import('@/views/infra/CpuSockets.vue')
@ -62,14 +62,14 @@ export default {
{
name: 'managementserver',
title: 'label.management.servers',
icon: 'rocket',
icon: 'RocketOutlined',
permission: ['listManagementServers'],
columns: ['name', 'state', 'version']
},
{
name: 'alert',
title: 'label.alerts',
icon: 'flag',
icon: 'FlagOutlined',
docHelp: 'adminguide/management.html#administrator-alerts',
permission: ['listAlerts'],
columns: ['name', 'description', 'type', 'sent'],
@ -77,7 +77,7 @@ export default {
actions: [
{
api: 'archiveAlerts',
icon: 'book',
icon: 'book-outlined',
label: 'label.archive.alerts',
message: 'message.confirm.archive.selected.alerts',
docHelp: 'adminguide/events.html#deleting-and-archiving-events-and-alerts',
@ -93,7 +93,7 @@ export default {
},
{
api: 'deleteAlerts',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.alerts',
message: 'message.confirm.remove.selected.alerts',
docHelp: 'adminguide/events.html#deleting-and-archiving-events-and-alerts',

View File

@ -15,12 +15,13 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'cluster',
title: 'label.clusters',
icon: 'cluster',
icon: 'cluster-outlined',
permission: ['listClustersMetrics'],
columns: () => {
const fields = ['name', 'state', 'allocationstate', 'clustertype', 'hypervisortype', 'hosts']
@ -41,37 +42,37 @@ export default {
resourceType: 'Cluster',
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'resources',
component: () => import('@/views/infra/Resources.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/Resources.vue')))
}, {
name: 'settings',
component: () => import('@/components/view/SettingsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue')))
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'addCluster',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.cluster',
docHelp: 'adminguide/installguide/configuration.html#adding-a-cluster',
listView: true,
popup: true,
component: () => import('@/views/infra/ClusterAdd.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/ClusterAdd.vue')))
},
{
api: 'updateCluster',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['clustername']
},
{
api: 'updateCluster',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.action.enable.cluster',
message: 'message.action.enable.cluster',
docHelp: 'adminguide/installguide/hosts.html#disabling-and-enabling-zones-pods-and-clusters',
@ -81,7 +82,7 @@ export default {
},
{
api: 'updateCluster',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.action.disable.cluster',
message: 'message.action.disable.cluster',
docHelp: 'adminguide/installguide/hosts.html#disabling-and-enabling-zones-pods-and-clusters',
@ -91,7 +92,7 @@ export default {
},
{
api: 'updateCluster',
icon: 'plus-square',
icon: 'plus-square-outlined',
label: 'label.action.manage.cluster',
message: 'message.action.manage.cluster',
dataView: true,
@ -100,7 +101,7 @@ export default {
},
{
api: 'updateCluster',
icon: 'minus-square',
icon: 'minus-square-outlined',
label: 'label.action.unmanage.cluster',
message: 'message.action.unmanage.cluster',
dataView: true,
@ -109,7 +110,7 @@ export default {
},
{
api: 'enableOutOfBandManagementForCluster',
icon: 'plus-circle',
icon: 'plus-circle-outlined',
label: 'label.outofbandmanagement.enable',
message: 'label.outofbandmanagement.enable',
dataView: true,
@ -125,7 +126,7 @@ export default {
},
{
api: 'disableOutOfBandManagementForCluster',
icon: 'minus-circle',
icon: 'minus-circle-outlined',
label: 'label.outofbandmanagement.disable',
message: 'label.outofbandmanagement.disable',
dataView: true,
@ -141,7 +142,7 @@ export default {
},
{
api: 'enableHAForCluster',
icon: 'eye',
icon: 'eye-outlined',
label: 'label.ha.enable',
message: 'label.ha.enable',
dataView: true,
@ -157,7 +158,7 @@ export default {
},
{
api: 'disableHAForCluster',
icon: 'eye-invisible',
icon: 'eye-invisible-outlined',
label: 'label.ha.disable',
message: 'label.ha.disable',
dataView: true,
@ -173,7 +174,7 @@ export default {
},
{
api: 'startRollingMaintenance',
icon: 'setting',
icon: 'setting-outlined',
label: 'label.start.rolling.maintenance',
message: 'label.start.rolling.maintenance',
dataView: true,
@ -186,7 +187,7 @@ export default {
},
{
api: 'deleteCluster',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.cluster',
message: 'message.action.delete.cluster',
dataView: true

View File

@ -15,12 +15,13 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'host',
title: 'label.hosts',
icon: 'desktop',
icon: 'desktop-outlined',
permission: ['listHostsMetrics'],
resourceType: 'Host',
params: { type: 'routing' },
@ -37,10 +38,10 @@ export default {
details: ['name', 'id', 'resourcestate', 'ipaddress', 'hypervisor', 'type', 'clustername', 'podname', 'zonename', 'disconnected', 'created'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
related: [{
name: 'vm',
@ -50,16 +51,16 @@ export default {
actions: [
{
api: 'addHost',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.host',
docHelp: 'adminguide/installguide/configuration.html#adding-a-host',
listView: true,
popup: true,
component: () => import('@/views/infra/HostAdd.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/HostAdd.vue')))
},
{
api: 'updateHost',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['name', 'hosttags', 'oscategoryid'],
@ -71,7 +72,7 @@ export default {
},
{
api: 'provisionCertificate',
icon: 'safety-certificate',
icon: 'safety-certificate-outlined',
label: 'label.action.secure.host',
message: 'message.action.secure.host',
dataView: true,
@ -85,7 +86,7 @@ export default {
},
{
api: 'reconnectHost',
icon: 'forward',
icon: 'forward-outlined',
label: 'label.action.force.reconnect',
message: 'message.confirm.action.force.reconnect',
dataView: true,
@ -93,7 +94,7 @@ export default {
},
{
api: 'updateHost',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.disable.host',
message: 'message.confirm.disable.host',
dataView: true,
@ -102,7 +103,7 @@ export default {
},
{
api: 'updateHost',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.enable.host',
message: 'message.confirm.enable.host',
dataView: true,
@ -111,7 +112,7 @@ export default {
},
{
api: 'prepareHostForMaintenance',
icon: 'plus-square',
icon: 'plus-square-outlined',
label: 'label.action.enable.maintenance.mode',
message: 'message.action.host.enable.maintenance.mode',
docHelp: 'adminguide/hosts.html#maintaining-hypervisors-on-hosts',
@ -120,7 +121,7 @@ export default {
},
{
api: 'cancelHostMaintenance',
icon: 'minus-square',
icon: 'minus-square-outlined',
label: 'label.action.cancel.maintenance.mode',
message: 'message.action.cancel.maintenance.mode',
docHelp: 'adminguide/hosts.html#maintaining-hypervisors-on-hosts',
@ -129,7 +130,7 @@ export default {
},
{
api: 'configureOutOfBandManagement',
icon: 'setting',
icon: 'setting-outlined',
label: 'label.outofbandmanagement.configure',
message: 'label.outofbandmanagement.configure',
docHelp: 'adminguide/hosts.html#out-of-band-management',
@ -146,7 +147,7 @@ export default {
},
{
api: 'enableOutOfBandManagementForHost',
icon: 'plus-circle',
icon: 'plus-circle-outlined',
label: 'label.outofbandmanagement.enable',
message: 'label.outofbandmanagement.enable',
docHelp: 'adminguide/hosts.html#out-of-band-management',
@ -163,7 +164,7 @@ export default {
},
{
api: 'disableOutOfBandManagementForHost',
icon: 'minus-circle',
icon: 'minus-circle-outlined',
label: 'label.outofbandmanagement.disable',
message: 'label.outofbandmanagement.disable',
docHelp: 'adminguide/hosts.html#out-of-band-management',
@ -180,7 +181,7 @@ export default {
},
{
api: 'issueOutOfBandManagementPowerAction',
icon: 'login',
icon: 'login-outlined',
label: 'label.outofbandmanagement.action.issue',
message: 'label.outofbandmanagement.action.issue',
docHelp: 'adminguide/hosts.html#out-of-band-management',
@ -200,7 +201,7 @@ export default {
},
{
api: 'changeOutOfBandManagementPassword',
icon: 'key',
icon: 'key-outlined',
label: 'label.outofbandmanagement.changepassword',
message: 'label.outofbandmanagement.changepassword',
docHelp: 'adminguide/hosts.html#out-of-band-management',
@ -217,7 +218,7 @@ export default {
},
{
api: 'configureHAForHost',
icon: 'tool',
icon: 'tool-outlined',
label: 'label.ha.configure',
message: 'label.ha.configure',
docHelp: 'adminguide/reliability.html#ha-for-hosts',
@ -235,7 +236,7 @@ export default {
},
{
api: 'enableHAForHost',
icon: 'eye',
icon: 'eye-outlined',
label: 'label.ha.enable',
message: 'label.ha.enable',
docHelp: 'adminguide/reliability.html#ha-for-hosts',
@ -252,7 +253,7 @@ export default {
},
{
api: 'disableHAForHost',
icon: 'eye-invisible',
icon: 'eye-invisible-outlined',
label: 'label.ha.disable',
message: 'label.ha.disable',
docHelp: 'adminguide/reliability.html#ha-for-hosts',
@ -270,7 +271,7 @@ export default {
},
{
api: 'startRollingMaintenance',
icon: 'setting',
icon: 'setting-outlined',
label: 'label.start.rolling.maintenance',
message: 'label.start.rolling.maintenance',
docHelp: 'adminguide/hosts.html#kvm-rolling-maintenance',
@ -287,7 +288,7 @@ export default {
},
{
api: 'deleteHost',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.remove.host',
docHelp: 'adminguide/hosts.html#removing-hosts',
dataView: true,

View File

@ -15,10 +15,11 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'ilbvm',
title: 'label.internal.lb',
icon: 'share-alt',
icon: 'share-alt-outlined',
permission: ['listInternalLoadBalancerVMs'],
params: { projectid: '-1' },
columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'version', 'hostname', 'account', 'zonename', 'requiresupgrade'],
@ -26,7 +27,7 @@ export default {
actions: [
{
api: 'startInternalLoadBalancerVM',
icon: 'caret-right',
icon: 'caret-right-outlined',
label: 'label.action.start.router',
message: 'message.confirm.start.lb.vm',
dataView: true,
@ -37,7 +38,7 @@ export default {
},
{
api: 'stopInternalLoadBalancerVM',
icon: 'poweroff',
icon: 'poweroff-outlined',
label: 'label.action.stop.router',
dataView: true,
args: ['forced'],
@ -48,11 +49,11 @@ export default {
},
{
api: 'migrateSystemVm',
icon: 'drag',
icon: 'drag-outlined',
label: 'label.action.migrate.router',
dataView: true,
show: (record, store) => { return record.state === 'Running' && ['Admin'].includes(store.userInfo.roletype) },
component: () => import('@/views/compute/MigrateWizard'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateWizard'))),
popup: true
},
{
@ -61,7 +62,7 @@ export default {
label: 'label.action.migrate.systemvm.to.ps',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['VMware'].includes(record.hypervisor) },
component: () => import('@/views/compute/MigrateVMStorage'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateVMStorage'))),
popup: true
}
]

View File

@ -15,10 +15,11 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'nsp',
title: 'label.network.service.providers',
icon: 'compass',
icon: 'compass-outlined',
docHelp: 'adminguide/networking.html#network-service-providers',
hidden: true,
permission: ['listNetworkServiceProviders'],
@ -26,12 +27,12 @@ export default {
details: ['name', 'state', 'servicelist', 'canenableindividualservice', 'physicalnetworkid'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}],
actions: [
{
api: 'updateNetworkServiceProvider',
icon: 'stop',
icon: 'stop-outlined',
label: 'label.disable.provider',
message: 'message.confirm.disable.provider',
dataView: true,
@ -45,7 +46,7 @@ export default {
},
{
api: 'updateNetworkServiceProvider',
icon: 'right-circle',
icon: 'right-circle-outlined',
label: 'label.enable.provider',
message: 'message.confirm.enable.provider',
dataView: true,

View File

@ -15,27 +15,28 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'physicalnetwork',
title: 'label.physical.network',
docHelp: 'adminguide/networking_and_traffic.html#basic-zone-physical-network-configuration',
icon: 'api',
icon: 'api-outlined',
hidden: true,
permission: ['listPhysicalNetworks'],
columns: ['name', 'state', 'isolationmethods', 'vlan', 'broadcastdomainrange', 'zonename', 'tags'],
details: ['name', 'state', 'isolationmethods', 'vlan', 'broadcastdomainrange', 'zonename', 'tags'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'traffic.types',
component: () => import('@/views/infra/network/TrafficTypesTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/network/TrafficTypesTab.vue')))
}, {
name: 'network.service.providers',
component: () => import('@/views/infra/network/ServiceProvidersTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/network/ServiceProvidersTab.vue')))
}, {
name: 'dedicated.vlan.vni.ranges',
component: () => import('@/views/infra/network/DedicatedVLANTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/network/DedicatedVLANTab.vue')))
}],
related: [{
name: 'guestnetwork',
@ -45,7 +46,7 @@ export default {
actions: [
{
api: 'createPhysicalNetwork',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.physical.network',
listView: true,
args: ['name', 'zoneid', 'isolationmethods', 'vlan', 'tags', 'networkspeed', 'broadcastdomainrange'],
@ -57,7 +58,7 @@ export default {
},
{
api: 'updatePhysicalNetwork',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.action.enable.physical.network',
dataView: true,
args: ['state'],
@ -70,7 +71,7 @@ export default {
},
{
api: 'updatePhysicalNetwork',
icon: 'stop',
icon: 'stop-outlined',
label: 'label.action.disable.physical.network',
dataView: true,
args: ['state'],
@ -83,14 +84,14 @@ export default {
},
{
api: 'updatePhysicalNetwork',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.update.physical.network',
dataView: true,
args: ['vlan', 'tags']
},
{
api: 'addTrafficType',
icon: 'plus-square',
icon: 'plus-square-outlined',
label: 'label.add.traffic.type',
dataView: true,
args: ['traffictype', 'physicalnetworkid', 'isolationmethod'],
@ -108,15 +109,15 @@ export default {
},
{
api: 'updateTrafficType',
icon: 'branches',
icon: 'branches-outlined',
label: 'label.update.traffic.label',
dataView: true,
popup: true,
component: () => import('@/views/infra/network/EditTrafficLabel.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/network/EditTrafficLabel.vue')))
},
{
api: 'deletePhysicalNetwork',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.physical.network',
message: 'message.action.delete.physical.network',
dataView: true

View File

@ -15,10 +15,11 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'pod',
title: 'label.pods',
icon: 'appstore',
icon: 'appstore-outlined',
permission: ['listPods'],
columns: ['name', 'allocationstate', 'gateway', 'netmask', 'zonename'],
details: ['name', 'id', 'allocationstate', 'netmask', 'gateway', 'zonename'],
@ -34,34 +35,34 @@ export default {
resourceType: 'Pod',
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'resources',
component: () => import('@/views/infra/Resources.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/Resources.vue')))
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'createPod',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.pod',
docHelp: 'installguide/configuration.html#adding-a-pod',
listView: true,
popup: true,
component: () => import('@/views/infra/PodAdd.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/PodAdd.vue')))
},
{
api: 'updatePod',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['name', 'netmask', 'gateway']
},
{
api: 'updatePod',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.action.enable.pod',
message: 'message.action.enable.pod',
docHelp: 'adminguide/hosts.html#disabling-and-enabling-zones-pods-and-clusters',
@ -76,7 +77,7 @@ export default {
},
{
api: 'updatePod',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.action.disable.pod',
message: 'message.action.disable.pod',
docHelp: 'adminguide/hosts.html#disabling-and-enabling-zones-pods-and-clusters',
@ -91,7 +92,7 @@ export default {
},
{
api: 'startRollingMaintenance',
icon: 'setting',
icon: 'setting-outlined',
label: 'label.start.rolling.maintenance',
message: 'label.start.rolling.maintenance',
dataView: true,
@ -104,7 +105,7 @@ export default {
},
{
api: 'deletePod',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.pod',
message: 'message.action.delete.pod',
dataView: true

View File

@ -15,12 +15,13 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'storagepool',
title: 'label.primary.storage',
icon: 'database',
icon: 'database-outlined',
docHelp: 'adminguide/storage.html#primary-storage',
permission: ['listStoragePoolsMetrics'],
columns: () => {
@ -42,34 +43,34 @@ export default {
resourceType: 'PrimaryStorage',
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'settings',
component: () => import('@/components/view/SettingsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue')))
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'createStoragePool',
icon: 'plus',
icon: 'plus-outlined',
docHelp: 'installguide/configuration.html#add-primary-storage',
label: 'label.add.primary.storage',
listView: true,
popup: true,
component: () => import('@/views/infra/AddPrimaryStorage.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/AddPrimaryStorage.vue')))
},
{
api: 'updateStoragePool',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['name', 'tags', 'capacitybytes', 'capacityiops']
},
{
api: 'updateStoragePool',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.disable.storage',
message: 'message.confirm.disable.storage',
dataView: true,
@ -78,7 +79,7 @@ export default {
},
{
api: 'updateStoragePool',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.enable.storage',
message: 'message.confirm.enable.storage',
dataView: true,
@ -87,7 +88,7 @@ export default {
},
{
api: 'syncStoragePool',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.sync.storage',
message: 'message.confirm.sync.storage',
dataView: true,
@ -95,7 +96,7 @@ export default {
},
{
api: 'enableStorageMaintenance',
icon: 'plus-square',
icon: 'plus-square-outlined',
label: 'label.action.enable.maintenance.mode',
message: 'message.action.primarystorage.enable.maintenance.mode',
dataView: true,
@ -103,7 +104,7 @@ export default {
},
{
api: 'cancelStorageMaintenance',
icon: 'minus-square',
icon: 'minus-square-outlined',
label: 'label.action.cancel.maintenance.mode',
message: 'message.action.cancel.maintenance.mode',
dataView: true,
@ -111,7 +112,7 @@ export default {
},
{
api: 'deleteStoragePool',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.primary.storage',
dataView: true,
args: ['forced'],

View File

@ -15,10 +15,12 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'router',
title: 'label.virtual.routers',
icon: 'fork',
icon: 'fork-outlined',
docHelp: 'adminguide/systemvm.html#virtual-router',
permission: ['listRouters'],
params: { projectid: '-1' },
@ -28,17 +30,17 @@ export default {
resourceType: 'VirtualRouter',
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'nics',
component: () => import('@/views/network/NicsTable.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/NicsTable.vue')))
}, {
name: 'router.health.checks',
show: (record, route, user) => { return ['Running'].includes(record.state) && ['Admin'].includes(user.roletype) },
component: () => import('@views/infra/routers/RouterHealthCheck.vue')
component: shallowRef(defineAsyncComponent(() => import('@views/infra/routers/RouterHealthCheck.vue')))
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
related: [{
name: 'vm',
@ -49,7 +51,7 @@ export default {
actions: [
{
api: 'startRouter',
icon: 'caret-right',
icon: 'caret-right-outlined',
label: 'label.action.start.router',
message: 'message.action.start.router',
dataView: true,
@ -60,7 +62,7 @@ export default {
},
{
api: 'stopRouter',
icon: 'poweroff',
icon: 'poweroff-outlined',
label: 'label.action.stop.router',
message: 'message.action.stop.router',
dataView: true,
@ -72,7 +74,7 @@ export default {
},
{
api: 'rebootRouter',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.action.reboot.router',
message: 'message.action.reboot.router',
dataView: true,
@ -84,7 +86,7 @@ export default {
},
{
api: 'scaleSystemVm',
icon: 'arrows-alt',
icon: 'arrows-alt-outlined',
label: 'label.change.service.offering',
message: 'message.confirm.scale.up.router.vm',
dataView: true,
@ -105,7 +107,7 @@ export default {
},
{
api: 'upgradeRouterTemplate',
icon: 'fullscreen',
icon: 'fullscreen-outlined',
label: 'label.upgrade.router.newer.template',
message: 'message.confirm.upgrade.router.newer.template',
docHelp: 'adminguide/systemvm.html#upgrading-virtual-routers',
@ -115,12 +117,12 @@ export default {
},
{
api: 'migrateSystemVm',
icon: 'drag',
icon: 'drag-outlined',
label: 'label.action.migrate.router',
message: 'message.migrate.router.confirm',
dataView: true,
show: (record, store) => { return record.state === 'Running' && ['Admin'].includes(store.userInfo.roletype) },
component: () => import('@/views/compute/MigrateWizard'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateWizard'))),
popup: true
},
{
@ -129,12 +131,12 @@ export default {
label: 'label.action.migrate.systemvm.to.ps',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['VMware'].includes(record.hypervisor) },
component: () => import('@/views/compute/MigrateVMStorage'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateVMStorage'))),
popup: true
},
{
api: 'runDiagnostics',
icon: 'reconciliation',
icon: 'reconciliation-outlined',
label: 'label.action.run.diagnostics',
dataView: true,
show: (record, store) => { return ['Running'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
@ -151,7 +153,7 @@ export default {
},
{
api: 'getDiagnosticsData',
icon: 'download',
icon: 'download-outlined',
label: 'label.action.get.diagnostics',
dataView: true,
show: (record, store) => { return ['Running'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
@ -165,7 +167,7 @@ export default {
},
{
api: 'destroyRouter',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.destroy.router',
message: 'message.confirm.destroy.router',
dataView: true,

View File

@ -14,12 +14,14 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'imagestore',
title: 'label.secondary.storage',
icon: 'picture',
icon: 'picture-outlined',
docHelp: 'adminguide/storage.html#secondary-storage',
permission: ['listImageStores'],
columns: () => {
@ -42,35 +44,35 @@ export default {
resourceType: 'SecondaryStorage',
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'settings',
component: () => import('@/components/view/SettingsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue')))
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'addImageStore',
icon: 'plus',
icon: 'plus-outlined',
docHelp: 'installguide/configuration.html#add-secondary-storage',
label: 'label.add.secondary.storage',
listView: true,
popup: true,
component: () => import('@/views/infra/AddSecondaryStorage.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/AddSecondaryStorage.vue')))
},
{
api: 'migrateSecondaryStorageData',
icon: 'drag',
icon: 'drag-outlined',
label: 'label.migrate.data.from.image.store',
listView: true,
popup: true,
component: () => import('@/views/infra/MigrateData.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/MigrateData.vue')))
},
{
api: 'updateImageStore',
icon: 'stop',
icon: 'stop-outlined',
label: 'label.action.image.store.read.only',
message: 'message.action.secondary.storage.read.only',
dataView: true,
@ -79,7 +81,7 @@ export default {
},
{
api: 'updateImageStore',
icon: 'check-circle',
icon: 'check-circle-outlined',
label: 'label.action.image.store.read.write',
message: 'message.action.secondary.storage.read.write',
dataView: true,
@ -88,7 +90,7 @@ export default {
},
{
api: 'deleteImageStore',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.secondary.storage',
message: 'message.action.delete.secondary.storage',
dataView: true,

View File

@ -15,10 +15,12 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'systemvm',
title: 'label.system.vms',
icon: 'thunderbolt',
icon: 'thunderbolt-outlined',
docHelp: 'adminguide/systemvm.html',
permission: ['listSystemVms'],
columns: ['name', 'state', 'agentstate', 'systemvmtype', 'publicip', 'privateip', 'linklocalip', 'hostname', 'zonename'],
@ -27,17 +29,17 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
actions: [
{
api: 'startSystemVm',
icon: 'caret-right',
icon: 'caret-right-outlined',
label: 'label.action.start.systemvm',
message: 'message.action.start.systemvm',
dataView: true,
@ -48,7 +50,7 @@ export default {
},
{
api: 'stopSystemVm',
icon: 'poweroff',
icon: 'poweroff-outlined',
label: 'label.action.stop.systemvm',
message: 'message.action.stop.systemvm',
dataView: true,
@ -60,7 +62,7 @@ export default {
},
{
api: 'rebootSystemVm',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.action.reboot.systemvm',
message: 'message.action.reboot.systemvm',
dataView: true,
@ -72,7 +74,7 @@ export default {
},
{
api: 'scaleSystemVm',
icon: 'arrows-alt',
icon: 'arrows-alt-outlined',
label: 'label.change.service.offering',
message: 'message.confirm.scale.up.system.vm',
dataView: true,
@ -87,12 +89,12 @@ export default {
},
{
api: 'migrateSystemVm',
icon: 'drag',
icon: 'drag-outlined',
label: 'label.action.migrate.systemvm',
message: 'message.migrate.systemvm.confirm',
dataView: true,
show: (record, store) => { return record.state === 'Running' && ['Admin'].includes(store.userInfo.roletype) },
component: () => import('@/views/compute/MigrateWizard'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateWizard'))),
popup: true
},
{
@ -101,12 +103,12 @@ export default {
label: 'label.action.migrate.systemvm.to.ps',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['VMware'].includes(record.hypervisor) },
component: () => import('@/views/compute/MigrateVMStorage'),
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateVMStorage'))),
popup: true
},
{
api: 'runDiagnostics',
icon: 'reconciliation',
icon: 'reconciliation-outlined',
label: 'label.action.run.diagnostics',
dataView: true,
show: (record) => { return record.state === 'Running' },
@ -123,7 +125,7 @@ export default {
},
{
api: 'getDiagnosticsData',
icon: 'download',
icon: 'download-outlined',
label: 'label.action.get.diagnostics',
dataView: true,
show: (record) => { return record.state === 'Running' },
@ -137,7 +139,7 @@ export default {
},
{
api: 'destroySystemVm',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.destroy.systemvm',
message: 'message.action.destroy.systemvm',
dataView: true,

View File

@ -15,12 +15,13 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'zone',
title: 'label.zones',
icon: 'global',
icon: 'global-outlined',
permission: ['listZonesMetrics'],
columns: () => {
const fields = ['name', 'allocationstate', 'networktype', 'clusters']
@ -56,36 +57,36 @@ export default {
resourceType: 'Zone',
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'physical.network',
component: () => import('@/views/infra/zone/PhysicalNetworksTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/zone/PhysicalNetworksTab.vue')))
}, {
name: 'system.vms',
component: () => import('@/views/infra/zone/SystemVmsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/zone/SystemVmsTab.vue')))
}, {
name: 'resources',
component: () => import('@/views/infra/Resources.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/Resources.vue')))
}, {
name: 'settings',
component: () => import('@/components/view/SettingsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue')))
}, {
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'createZone',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.zone',
docHelp: 'installguide/configuration.html#adding-a-zone',
listView: true,
popup: true,
component: () => import('@/views/infra/zone/ZoneWizard.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/zone/ZoneWizard.vue')))
},
{
api: 'updateZone',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.action.edit.zone',
dataView: true,
args: ['name', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'internaldns1', 'internaldns2', 'guestcidraddress', 'domain', 'localstorageenabled'],
@ -93,7 +94,7 @@ export default {
},
{
api: 'updateZone',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.action.edit.zone',
dataView: true,
args: ['name', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'internaldns1', 'internaldns2', 'domain', 'localstorageenabled'],
@ -101,7 +102,7 @@ export default {
},
{
api: 'updateZone',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.action.disable.zone',
message: 'message.action.disable.zone',
docHelp: 'adminguide/hosts.html#disabling-and-enabling-zones-pods-and-clusters',
@ -111,7 +112,7 @@ export default {
},
{
api: 'updateZone',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.action.enable.zone',
message: 'message.action.enable.zone',
docHelp: 'adminguide/hosts.html#disabling-and-enabling-zones-pods-and-clusters',
@ -121,7 +122,7 @@ export default {
},
{
api: 'enableOutOfBandManagementForZone',
icon: 'plus-circle',
icon: 'plus-circle-outlined',
label: 'label.outofbandmanagement.enable',
message: 'label.outofbandmanagement.enable',
dataView: true,
@ -137,7 +138,7 @@ export default {
},
{
api: 'disableOutOfBandManagementForZone',
icon: 'minus-circle',
icon: 'minus-circle-outlined',
label: 'label.outofbandmanagement.disable',
message: 'label.outofbandmanagement.disable',
dataView: true,
@ -153,7 +154,7 @@ export default {
},
{
api: 'enableHAForZone',
icon: 'eye',
icon: 'eye-outlined',
label: 'label.ha.enable',
message: 'label.ha.enable',
dataView: true,
@ -169,7 +170,7 @@ export default {
},
{
api: 'disableHAForZone',
icon: 'eye-invisible',
icon: 'eye-invisible-outlined',
label: 'label.ha.disable',
message: 'label.ha.disable',
dataView: true,
@ -185,7 +186,7 @@ export default {
},
{
api: 'addVmwareDc',
icon: 'block',
icon: 'block-outlined',
label: 'label.add.vmware.datacenter',
dataView: true,
show: record => !record.vmwaredc,
@ -198,7 +199,7 @@ export default {
},
{
api: 'updateVmwareDc',
icon: 'block',
icon: 'block-outlined',
label: 'label.update.vmware.datacenter',
message: 'message.restart.mgmt.server',
additionalMessage: 'message.restart.mgmt.server',
@ -213,7 +214,7 @@ export default {
},
{
api: 'removeVmwareDc',
icon: 'minus-square',
icon: 'minus-square-outlined',
label: 'label.remove.vmware.datacenter',
message: 'message.confirm.remove.vmware.datacenter',
dataView: true,
@ -227,7 +228,7 @@ export default {
},
{
api: 'startRollingMaintenance',
icon: 'setting',
icon: 'setting-outlined',
label: 'label.start.rolling.maintenance',
message: 'label.start.rolling.maintenance',
dataView: true,
@ -240,7 +241,7 @@ export default {
},
{
api: 'deleteZone',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.zone',
message: 'message.action.delete.zone',
dataView: true

View File

@ -15,19 +15,20 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
import { isAdmin } from '@/role'
export default {
name: 'network',
title: 'label.network',
icon: 'wifi',
icon: 'wifi-outlined',
docHelp: 'adminguide/networking_and_traffic.html#advanced-zone-physical-network-configuration',
children: [
{
name: 'guestnetwork',
title: 'label.guest.networks',
icon: 'apartment',
icon: 'apartment-outlined',
permission: ['listNetworks'],
resourceType: 'Network',
columns: () => {
@ -53,49 +54,49 @@ export default {
}],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'egress.rules',
component: () => import('@/views/network/EgressRulesTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/EgressRulesTab.vue'))),
show: (record) => { return record.type === 'Isolated' && !('vpcid' in record) && 'listEgressFirewallRules' in store.getters.apis }
}, {
name: 'public.ip.addresses',
component: () => import('@/views/network/IpAddressesTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/IpAddressesTab.vue'))),
show: (record) => { return (record.type === 'Isolated' || record.type === 'Shared') && !('vpcid' in record) && 'listPublicIpAddresses' in store.getters.apis }
}, {
name: 'virtual.routers',
component: () => import('@/views/network/RoutersTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/RoutersTab.vue'))),
show: (record) => { return (record.type === 'Isolated' || record.type === 'Shared') && 'listRouters' in store.getters.apis }
}, {
name: 'guest.ip.range',
component: () => import('@/views/network/GuestIpRanges.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/GuestIpRanges.vue'))),
show: (record) => { return 'listVlanIpRanges' in store.getters.apis && (record.type === 'Shared' || (record.service && record.service.filter(x => x.name === 'SourceNat').count === 0)) }
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'createNetwork',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.network',
docHelp: 'adminguide/networking_and_traffic.html#configure-guest-traffic-in-an-advanced-zone',
listView: true,
popup: true,
component: () => import('@/views/network/CreateNetwork.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/CreateNetwork.vue')))
},
{
api: 'updateNetwork',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.update.network',
dataView: true,
popup: true,
component: () => import('@/views/network/UpdateNetwork.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/UpdateNetwork.vue')))
},
{
api: 'restartNetwork',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.restart.network',
message: 'message.restart.network',
dataView: true,
@ -107,7 +108,7 @@ export default {
},
{
api: 'replaceNetworkACLList',
icon: 'swap',
icon: 'swap-outlined',
label: 'label.replace.acl.list',
message: 'message.confirm.replace.acl.new.one',
docHelp: 'adminguide/networking_and_traffic.html#configuring-network-access-control-list',
@ -126,7 +127,7 @@ export default {
},
{
api: 'deleteNetwork',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.network',
message: 'message.action.delete.network',
dataView: true,
@ -139,7 +140,7 @@ export default {
{
name: 'vpc',
title: 'label.vpc',
icon: 'deployment-unit',
icon: 'deployment-unit-outlined',
docHelp: 'adminguide/networking_and_traffic.html#configuring-a-virtual-private-cloud',
permission: ['listVPCs'],
resourceType: 'Vpc',
@ -161,28 +162,28 @@ export default {
}],
tabs: [{
name: 'vpc',
component: () => import('@/views/network/VpcTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/VpcTab.vue')))
}],
actions: [
{
api: 'createVPC',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.vpc',
docHelp: 'adminguide/networking_and_traffic.html#adding-a-virtual-private-cloud',
listView: true,
popup: true,
component: () => import('@/views/network/CreateVpc.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/CreateVpc.vue')))
},
{
api: 'updateVPC',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['name', 'displaytext']
},
{
api: 'restartVPC',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.restart.vpc',
message: (record) => { return record.redundantvpcrouter ? 'message.restart.vpc' : 'message.restart.vpc.remark' },
dataView: true,
@ -199,7 +200,7 @@ export default {
},
{
api: 'deleteVPC',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.remove.vpc',
message: 'message.remove.vpc',
dataView: true,
@ -212,7 +213,7 @@ export default {
{
name: 'securitygroups',
title: 'label.security.groups',
icon: 'fire',
icon: 'fire-outlined',
docHelp: 'adminguide/networking_and_traffic.html#security-groups',
permission: ['listSecurityGroups'],
resourceType: 'SecurityGroup',
@ -220,13 +221,13 @@ export default {
details: ['name', 'id', 'description', 'account', 'domain'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'ingress.rule',
component: () => import('@/views/network/IngressEgressRuleConfigure.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/IngressEgressRuleConfigure.vue')))
}, {
name: 'egress.rule',
component: () => import('@/views/network/IngressEgressRuleConfigure.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/IngressEgressRuleConfigure.vue')))
}],
show: () => {
if (!store.getters.zones || store.getters.zones.length === 0) {
@ -241,7 +242,7 @@ export default {
actions: [
{
api: 'createSecurityGroup',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.security.group',
docHelp: 'adminguide/networking_and_traffic.html#adding-a-security-group',
listView: true,
@ -249,7 +250,7 @@ export default {
},
{
api: 'updateSecurityGroup',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['name'],
@ -257,7 +258,7 @@ export default {
},
{
api: 'deleteSecurityGroup',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.security.group',
message: 'message.action.delete.security.group',
dataView: true,
@ -268,19 +269,19 @@ export default {
{
name: 'publicip',
title: 'label.public.ip.addresses',
icon: 'environment',
icon: 'environment-outlined',
docHelp: 'adminguide/networking_and_traffic.html#reserving-public-ip-addresses-and-vlans-for-accounts',
permission: ['listPublicIpAddresses'],
resourceType: 'PublicIpAddress',
columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'],
details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename'],
component: () => import('@/views/network/PublicIpResource.vue'),
component: shallowRef(() => import('@/views/network/PublicIpResource.vue')),
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'firewall',
component: () => import('@/views/network/FirewallRules.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/FirewallRules.vue'))),
networkServiceFilter: networkService => networkService.filter(x => x.name === 'Firewall').length > 0,
groupAction: true,
popup: true,
@ -288,35 +289,35 @@ export default {
},
{
name: 'portforwarding',
component: () => import('@/views/network/PortForwarding.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/PortForwarding.vue'))),
networkServiceFilter: networkService => networkService.filter(x => x.name === 'PortForwarding').length > 0
}, {
name: 'loadbalancing',
component: () => import('@/views/network/LoadBalancing.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/LoadBalancing.vue'))),
networkServiceFilter: networkService => networkService.filter(x => x.name === 'Lb').length > 0
}, {
name: 'vpn',
component: () => import('@/views/network/VpnDetails.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/VpnDetails.vue'))),
show: (record) => { return record.issourcenat }
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}],
actions: [
{
api: 'enableStaticNat',
icon: 'plus-circle',
icon: 'plus-circle-outlined',
label: 'label.action.enable.static.nat',
docHelp: 'adminguide/networking_and_traffic.html#enabling-or-disabling-static-nat',
dataView: true,
show: (record) => { return !record.virtualmachineid && !record.issourcenat },
popup: true,
component: () => import('@/views/network/EnableStaticNat.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/EnableStaticNat.vue')))
},
{
api: 'disableStaticNat',
icon: 'minus-circle',
icon: 'minus-circle-outlined',
label: 'label.action.disable.static.nat',
message: 'message.action.disable.static.nat',
docHelp: 'adminguide/networking_and_traffic.html#enabling-or-disabling-static-nat',
@ -331,7 +332,7 @@ export default {
},
{
api: 'disassociateIpAddress',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.release.ip',
message: 'message.action.release.ip',
docHelp: 'adminguide/networking_and_traffic.html#releasing-an-ip-address-alloted-to-a-vpc',
@ -346,23 +347,23 @@ export default {
{
name: 'privategw',
title: 'label.private.gateway',
icon: 'gateway',
icon: 'gateway-outlined',
hidden: true,
permission: ['listPrivateGateways'],
columns: ['ipaddress', 'state', 'gateway', 'netmask', 'account'],
details: ['ipaddress', 'gateway', 'netmask', 'vlan', 'sourcenatsupported', 'aclname', 'account', 'domain', 'zone'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'static.routes',
component: () => import('@/views/network/StaticRoutesTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/StaticRoutesTab.vue'))),
show: () => true
}],
actions: [
{
api: 'createPrivateGateway',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.private.gateway',
docHelp: 'adminguide/networking_and_traffic.html#adding-a-private-gateway-to-a-vpc',
listView: true,
@ -381,7 +382,7 @@ export default {
},
{
api: 'replaceNetworkACLList',
icon: 'swap',
icon: 'swap-outlined',
label: 'label.replace.acl.list',
message: 'message.confirm.replace.acl.new.one',
docHelp: 'adminguide/networking_and_traffic.html#acl-on-private-gateway',
@ -399,7 +400,7 @@ export default {
},
{
api: 'deletePrivateGateway',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.gateway',
message: 'message.delete.gateway',
dataView: true
@ -409,7 +410,7 @@ export default {
{
name: 's2svpn',
title: 'label.site.to.site.vpn',
icon: 'lock',
icon: 'lock-outlined',
hidden: true,
permission: ['listVpnGateways'],
columns: ['publicip', 'account', 'domain'],
@ -417,7 +418,7 @@ export default {
actions: [
{
api: 'createVpnGateway',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.vpn.gateway',
docHelp: 'adminguide/networking_and_traffic.html#creating-a-vpn-gateway-for-the-vpc',
listView: true,
@ -425,7 +426,7 @@ export default {
},
{
api: 'deleteVpnGateway',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.vpn.gateway',
message: 'message.delete.vpn.gateway',
docHelp: 'adminguide/networking_and_traffic.html#restarting-and-removing-a-vpn-connection',
@ -437,7 +438,7 @@ export default {
name: 's2svpnconn',
title: 'label.site.to.site.vpn.connections',
docHelp: 'adminguide/networking_and_traffic.html#setting-up-a-site-to-site-vpn-connection',
icon: 'sync',
icon: 'sync-outlined',
hidden: true,
permission: ['listVpnConnections'],
columns: ['publicip', 'state', 'gateway', 'ipsecpsk', 'ikepolicy', 'esppolicy'],
@ -445,7 +446,7 @@ export default {
actions: [
{
api: 'createVpnConnection',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.create.vpn.connection',
docHelp: 'adminguide/networking_and_traffic.html#creating-a-vpn-connection',
listView: true,
@ -461,7 +462,7 @@ export default {
},
{
api: 'resetVpnConnection',
icon: 'reload',
icon: 'reload-outlined',
label: 'label.reset.vpn.connection',
message: 'message.reset.vpn.connection',
docHelp: 'adminguide/networking_and_traffic.html#restarting-and-removing-a-vpn-connection',
@ -469,7 +470,7 @@ export default {
},
{
api: 'deleteVpnConnection',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.vpn.connection',
message: 'message.delete.vpn.connection',
docHelp: 'adminguide/networking_and_traffic.html#restarting-and-removing-a-vpn-connection',
@ -480,7 +481,7 @@ export default {
{
name: 'acllist',
title: 'label.network.acl.lists',
icon: 'bars',
icon: 'bars-outlined',
docHelp: 'adminguide/networking_and_traffic.html#configuring-network-access-control-list',
hidden: true,
permission: ['listNetworkACLLists'],
@ -488,16 +489,16 @@ export default {
details: ['name', 'description', 'id'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'acl.list.rules',
component: () => import('@/views/network/AclListRulesTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/AclListRulesTab.vue'))),
show: () => true
}],
actions: [
{
api: 'createNetworkACLList',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.acl.list',
docHelp: 'adminguide/networking_and_traffic.html#creating-acl-lists',
listView: true,
@ -505,14 +506,14 @@ export default {
},
{
api: 'updateNetworkACLList',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit.acl.list',
dataView: true,
args: ['name', 'description']
},
{
api: 'deleteNetworkACLList',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.acl.list',
message: 'message.confirm.delete.acl.list',
dataView: true
@ -523,23 +524,23 @@ export default {
name: 'ilb',
title: 'label.internal.lb',
docHelp: 'adminguide/networking_and_traffic.html#load-balancing-across-tiers',
icon: 'share-alt',
icon: 'share-alt-outlined',
hidden: true,
permission: ['listLoadBalancers'],
columns: ['name', 'sourceipaddress', 'loadbalancerrule', 'algorithm', 'account', 'domain'],
details: ['name', 'sourceipaddress', 'loadbalancerrule', 'algorithm', 'account', 'domain'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'loadbalancerinstance',
component: () => import('@/views/network/InternalLBAssignedVmTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/network/InternalLBAssignedVmTab.vue'))),
show: () => true
}],
actions: [
{
api: 'createLoadBalancer',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.internal.lb',
docHelp: 'adminguide/networking_and_traffic.html#creating-an-internal-lb-rule',
listView: true,
@ -563,15 +564,15 @@ export default {
},
{
api: 'assignToLoadBalancerRule',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.assign.vms',
dataView: true,
popup: true,
component: () => import('@/views/network/InternalLBAssignVmForm.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/InternalLBAssignVmForm.vue')))
},
{
api: 'deleteLoadBalancer',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.internal.lb',
message: 'message.confirm.delete.internal.lb',
dataView: true
@ -581,7 +582,7 @@ export default {
{
name: 'vpnuser',
title: 'label.vpn.users',
icon: 'user',
icon: 'user-alt-outlined',
permission: ['listVpnUsers'],
hidden: true,
columns: ['username', 'state', 'account', 'domain'],
@ -589,7 +590,7 @@ export default {
actions: [
{
api: 'addVpnUser',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.vpn.user',
listView: true,
args: (record, store) => {
@ -602,7 +603,7 @@ export default {
},
{
api: 'removeVpnUser',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.vpn.user',
message: 'message.action.delete.vpn.user',
dataView: true,
@ -634,7 +635,7 @@ export default {
{
name: 'vpncustomergateway',
title: 'label.vpncustomergatewayid',
icon: 'lock',
icon: 'lock-outlined',
permission: ['listVpnCustomerGateways'],
columns: ['name', 'gateway', 'cidrlist', 'ipsecpsk', 'account'],
details: ['name', 'id', 'gateway', 'cidrlist', 'ipsecpsk', 'ikepolicy', 'ikelifetime', 'ikeversion', 'esppolicy', 'esplifetime', 'dpd', 'splitconnections', 'forceencap', 'account', 'domain'],
@ -643,26 +644,26 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
actions: [
{
api: 'createVpnCustomerGateway',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.vpn.customer.gateway',
docHelp: 'adminguide/networking_and_traffic.html#creating-and-updating-a-vpn-customer-gateway',
listView: true,
popup: true,
component: () => import('@/views/network/CreateVpnCustomerGateway.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/network/CreateVpnCustomerGateway.vue')))
},
{
api: 'updateVpnCustomerGateway',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
docHelp: 'adminguide/networking_and_traffic.html#updating-and-removing-a-vpn-customer-gateway',
dataView: true,
@ -675,7 +676,7 @@ export default {
},
{
api: 'deleteVpnCustomerGateway',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.vpn.customer.gateway',
message: 'message.delete.vpn.customer.gateway',
docHelp: 'adminguide/networking_and_traffic.html#updating-and-removing-a-vpn-customer-gateway',

View File

@ -14,19 +14,20 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'offering',
title: 'label.menu.service.offerings',
icon: 'shopping',
icon: 'shopping-outlined',
permission: ['listServiceOfferings', 'listDiskOfferings', 'listDomains'],
children: [
{
name: 'computeoffering',
title: 'label.compute.offerings',
docHelp: 'adminguide/service_offerings.html#compute-and-disk-service-offerings',
icon: 'cloud',
icon: 'cloud-outlined',
permission: ['listServiceOfferings', 'listDomains'],
params: { isrecursive: 'true' },
columns: ['name', 'displaytext', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'],
@ -46,11 +47,11 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
related: [{
@ -60,30 +61,30 @@ export default {
}],
actions: [{
api: 'createServiceOffering',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.compute.offering',
docHelp: 'adminguide/service_offerings.html#creating-a-new-compute-offering',
listView: true,
popup: true,
component: () => import('@/views/offering/AddComputeOffering.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/AddComputeOffering.vue')))
}, {
api: 'updateServiceOffering',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
dataView: true,
args: ['name', 'displaytext', 'storagetags', 'hosttags']
}, {
api: 'updateServiceOffering',
icon: 'lock',
icon: 'lock-outlined',
label: 'label.action.update.offering.access',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
dataView: true,
popup: true,
component: () => import('@/views/offering/UpdateOfferingAccess.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
}, {
api: 'deleteServiceOffering',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.service.offering',
message: 'message.action.delete.service.offering',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
@ -96,7 +97,7 @@ export default {
{
name: 'systemoffering',
title: 'label.system.offerings',
icon: 'setting',
icon: 'setting-outlined',
docHelp: 'adminguide/service_offerings.html#system-service-offerings',
permission: ['listServiceOfferings', 'listInfrastructure'],
params: { issystem: 'true', isrecursive: 'true' },
@ -104,16 +105,16 @@ export default {
details: ['name', 'id', 'displaytext', 'systemvmtype', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness'],
actions: [{
api: 'createServiceOffering',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.system.service.offering',
docHelp: 'adminguide/service_offerings.html#creating-a-new-system-service-offering',
listView: true,
params: { issystem: 'true' },
popup: true,
component: () => import('@/views/offering/AddComputeOffering.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/AddComputeOffering.vue')))
}, {
api: 'updateServiceOffering',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
params: { issystem: 'true' },
@ -121,7 +122,7 @@ export default {
args: ['name', 'displaytext']
}, {
api: 'deleteServiceOffering',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.system.service.offering',
message: 'message.action.delete.system.service.offering',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
@ -135,7 +136,7 @@ export default {
{
name: 'diskoffering',
title: 'label.disk.offerings',
icon: 'hdd',
icon: 'hdd-outlined',
docHelp: 'adminguide/service_offerings.html#compute-and-disk-service-offerings',
permission: ['listDiskOfferings', 'listDomains'],
params: { isrecursive: 'true' },
@ -152,11 +153,11 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
related: [{
@ -166,30 +167,30 @@ export default {
}],
actions: [{
api: 'createDiskOffering',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.disk.offering',
docHelp: 'adminguide/service_offerings.html#creating-a-new-disk-offering',
listView: true,
popup: true,
component: () => import('@/views/offering/AddDiskOffering.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/AddDiskOffering.vue')))
}, {
api: 'updateDiskOffering',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
dataView: true,
args: ['name', 'displaytext', 'tags']
}, {
api: 'updateDiskOffering',
icon: 'lock',
icon: 'lock-outlined',
label: 'label.action.update.offering.access',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
dataView: true,
popup: true,
component: () => import('@/views/offering/UpdateOfferingAccess.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
}, {
api: 'deleteDiskOffering',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.disk.offering',
message: 'message.action.delete.disk.offering',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
@ -202,7 +203,7 @@ export default {
{
name: 'backupoffering',
title: 'label.backup.offerings',
icon: 'cloud-upload',
icon: 'cloud-upload-outlined',
docHelp: 'adminguide/virtual_machines.html#backup-offerings',
permission: ['listBackupOfferings', 'listInfrastructure'],
columns: ['name', 'description', 'zonename'],
@ -214,12 +215,12 @@ export default {
}],
actions: [{
api: 'importBackupOffering',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.import.backup.offering',
docHelp: 'adminguide/virtual_machines.html#importing-backup-offerings',
listView: true,
popup: true,
component: () => import('@/views/offering/ImportBackupOffering.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/ImportBackupOffering.vue')))
}, {
api: 'updateBackupOffering',
icon: 'edit',
@ -230,7 +231,7 @@ export default {
args: ['name', 'description']
}, {
api: 'deleteBackupOffering',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.backup.offering',
message: 'message.action.delete.backup.offering',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
@ -243,7 +244,7 @@ export default {
{
name: 'networkoffering',
title: 'label.network.offerings',
icon: 'wifi',
icon: 'wifi-outlined',
docHelp: 'adminguide/networking.html#network-offerings',
permission: ['listNetworkOfferings', 'listInfrastructure'],
params: { isrecursive: 'true' },
@ -253,24 +254,24 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
actions: [{
api: 'createNetworkOffering',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.network.offering',
docHelp: 'adminguide/networking.html#creating-a-new-network-offering',
listView: true,
popup: true,
component: () => import('@/views/offering/AddNetworkOffering.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/AddNetworkOffering.vue')))
}, {
api: 'updateNetworkOffering',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
dataView: true,
@ -282,7 +283,7 @@ export default {
}
}, {
api: 'updateNetworkOffering',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.enable.network.offering',
message: 'message.confirm.enable.network.offering',
dataView: true,
@ -298,7 +299,7 @@ export default {
groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Enabled' } }) }
}, {
api: 'updateNetworkOffering',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.disable.network.offering',
message: 'message.confirm.disable.network.offering',
dataView: true,
@ -314,15 +315,15 @@ export default {
groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Disabled' } }) }
}, {
api: 'updateNetworkOffering',
icon: 'lock',
icon: 'lock-outlined',
label: 'label.action.update.offering.access',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
dataView: true,
popup: true,
component: () => import('@/views/offering/UpdateOfferingAccess.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
}, {
api: 'deleteNetworkOffering',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.remove.network.offering',
message: 'message.confirm.remove.network.offering',
docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
@ -335,7 +336,7 @@ export default {
{
name: 'vpcoffering',
title: 'label.vpc.offerings',
icon: 'deployment-unit',
icon: 'deployment-unit-outlined',
docHelp: 'plugins/nuage-plugin.html?#vpc-offerings',
permission: ['listVPCOfferings', 'listInfrastructure'],
params: { isrecursive: 'true' },
@ -349,21 +350,21 @@ export default {
}],
actions: [{
api: 'createVPCOffering',
icon: 'plus',
icon: 'plus-outlined',
docHelp: 'plugins/nuage-plugin.html?#optional-create-and-enable-vpc-offering',
label: 'label.add.vpc.offering',
listView: true,
popup: true,
component: () => import('@/views/offering/AddVpcOffering.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/AddVpcOffering.vue')))
}, {
api: 'updateVPCOffering',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['name', 'displaytext']
}, {
api: 'updateVPCOffering',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.enable.vpc.offering',
message: 'message.confirm.enable.vpc.offering',
dataView: true,
@ -379,7 +380,7 @@ export default {
groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Enabled' } }) }
}, {
api: 'updateVPCOffering',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.disable.vpc.offering',
message: 'message.confirm.disable.vpc.offering',
dataView: true,
@ -395,14 +396,14 @@ export default {
groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Disabled' } }) }
}, {
api: 'updateVPCOffering',
icon: 'lock',
icon: 'lock-outlined',
label: 'label.action.update.offering.access',
dataView: true,
popup: true,
component: () => import('@/views/offering/UpdateOfferingAccess.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
}, {
api: 'deleteVPCOffering',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.remove.vpc.offering',
message: 'message.confirm.remove.vpc.offering',
dataView: true,

View File

@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import cloudian from '@/assets/icons/cloudian.svg?inline'
export default {
@ -23,5 +24,5 @@ export default {
docHelp: 'plugins/cloudian-connector.html',
icon: cloudian,
permission: ['cloudianSsoLogin'],
component: () => import('@/views/plugins/CloudianPlugin.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/plugins/CloudianPlugin.vue')))
}

View File

@ -15,39 +15,40 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'quota',
title: 'label.quota',
icon: 'pie-chart',
icon: 'pie-chart-outlined',
docHelp: 'plugins/quota.html',
permission: ['quotaSummary'],
children: [
{
name: 'quotasummary',
title: 'label.summary',
icon: 'bars',
icon: 'bars-outlined',
permission: ['quotaSummary'],
columns: ['account', 'domain', 'state', 'currency', 'balance', 'quota'],
details: ['account', 'domain', 'state', 'currency', 'balance', 'quota', 'startdate', 'enddate'],
component: () => import('@/views/plugins/quota/QuotaSummary.vue'),
component: shallowRef(() => import('@/views/plugins/quota/QuotaSummary.vue')),
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'quota.statement.quota',
component: () => import('@/views/plugins/quota/QuotaUsage.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/plugins/quota/QuotaUsage.vue')))
},
{
name: 'quota.statement.balance',
component: () => import('@/views/plugins/quota/QuotaBalance.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/plugins/quota/QuotaBalance.vue')))
}
],
actions: [
{
api: 'quotaCredits',
icon: 'plus',
icon: 'plus-outlined',
docHelp: 'plugins/quota.html#quota-credits',
label: 'label.quota.add.credits',
dataView: true,
@ -66,23 +67,23 @@ export default {
{
name: 'quotatariff',
title: 'label.quota.tariff',
icon: 'credit-card',
icon: 'credit-card-outlined',
docHelp: 'plugins/quota.html#quota-tariff',
permission: ['quotaTariffList'],
columns: ['usageName', 'description', 'usageUnit', 'tariffValue', 'tariffActions'],
details: ['usageName', 'description', 'usageUnit', 'tariffValue'],
component: () => import('@/views/plugins/quota/QuotaTariff.vue')
component: shallowRef(() => import('@/views/plugins/quota/QuotaTariff.vue'))
},
{
name: 'quotaemailtemplate',
title: 'label.templatetype',
icon: 'mail',
icon: 'mail-outlined',
permission: ['quotaEmailTemplateList'],
columns: ['templatetype', 'templatesubject', 'templatebody'],
details: ['templatetype', 'templatesubject', 'templatebody'],
tabs: [{
name: 'details',
component: () => import('@/views/plugins/quota/EmailTemplateDetails.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/plugins/quota/EmailTemplateDetails.vue')))
}]
}
]

View File

@ -14,12 +14,14 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'project',
title: 'label.projects',
icon: 'project',
icon: 'project-outlined',
docHelp: 'adminguide/projects.html',
permission: ['listProjects'],
resourceType: 'Project',
@ -29,16 +31,16 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/views/project/ProjectDetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/project/ProjectDetailsTab.vue')))
},
{
name: 'accounts',
component: () => import('@/views/project/AccountsTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/project/AccountsTab.vue'))),
show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) || record.isCurrentUserProjectAdmin }
},
{
name: 'project.roles',
component: () => import('@/views/project/iam/ProjectRoleTab.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/project/iam/ProjectRoleTab.vue'))),
show: (record, route, user) => {
return (['Admin', 'DomainAdmin'].includes(user.roletype) || record.isCurrentUserProjectAdmin) &&
'listProjectRoles' in store.getters.apis
@ -46,18 +48,18 @@ export default {
},
{
name: 'resources',
component: () => import('@/components/view/ResourceCountUsage.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceCountUsage.vue')))
},
{
name: 'limits',
show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) },
component: () => import('@/components/view/ResourceLimitTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceLimitTab.vue')))
}
],
actions: [
{
api: 'createProject',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.new.project',
docHelp: 'adminguide/projects.html#creating-a-new-project',
listView: true,
@ -65,17 +67,17 @@ export default {
},
{
api: 'updateProjectInvitation',
icon: 'key',
icon: 'key-outlined',
label: 'label.enter.token',
docHelp: 'adminguide/projects.html#accepting-a-membership-invitation',
listView: true,
popup: true,
show: (record, store) => { return store.features.projectinviterequired },
component: () => import('@/views/project/InvitationTokenTemplate.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/project/InvitationTokenTemplate.vue')))
},
{
api: 'listProjectInvitations',
icon: 'team',
icon: 'team-outlined',
label: 'label.project.invitation',
docHelp: 'adminguide/projects.html#accepting-a-membership-invitation',
listView: true,
@ -86,11 +88,11 @@ export default {
state: 'Pending'
},
show: (record, store) => { return store.features.projectinviterequired },
component: () => import('@/views/project/InvitationsTemplate.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/project/InvitationsTemplate.vue')))
},
{
api: 'updateProject',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit.project.details',
dataView: true,
args: ['displaytext'],
@ -100,7 +102,7 @@ export default {
},
{
api: 'activateProject',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.activate.project',
message: 'message.activate.project',
dataView: true,
@ -113,7 +115,7 @@ export default {
},
{
api: 'suspendProject',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.suspend.project',
message: 'message.suspend.project',
docHelp: 'adminguide/projects.html#sending-project-membership-invitations',
@ -128,7 +130,7 @@ export default {
},
{
api: 'addAccountToProject',
icon: 'user-add',
icon: 'user-add-outlined',
label: 'label.action.project.add.account',
docHelp: 'adminguide/projects.html#adding-project-members-from-the-ui',
dataView: true,
@ -136,11 +138,11 @@ export default {
show: (record, store) => {
return (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) || record.isCurrentUserProjectAdmin
},
component: () => import('@/views/project/AddAccountOrUserToProject.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/project/AddAccountOrUserToProject.vue')))
},
{
api: 'deleteProject',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.project',
message: 'message.delete.project',
docHelp: 'adminguide/projects.html#suspending-or-deleting-a-project',

View File

@ -15,41 +15,42 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'role',
title: 'label.roles',
icon: 'idcard',
icon: 'idcard-outlined',
docHelp: 'adminguide/accounts.html#roles',
permission: ['listRoles', 'listRolePermissions'],
columns: ['name', 'type', 'description'],
details: ['name', 'id', 'type', 'description'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
}, {
name: 'rules',
component: () => import('@/views/iam/RolePermissionTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/RolePermissionTab.vue')))
}],
actions: [
{
api: 'createRole',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.role',
listView: true,
popup: true,
component: () => import('@/views/iam/CreateRole.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/CreateRole.vue')))
},
{
api: 'importRole',
icon: 'cloud-upload',
icon: 'cloud-upload-outlined',
label: 'label.import.role',
listView: true,
popup: true,
component: () => import('@/views/iam/ImportRole.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/ImportRole.vue')))
},
{
api: 'updateRole',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit.role',
dataView: true,
args: ['name', 'description', 'type'],
@ -61,7 +62,7 @@ export default {
},
{
api: 'deleteRole',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.role',
message: 'label.delete.role',
dataView: true

View File

@ -15,17 +15,18 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
export default {
name: 'storage',
title: 'label.storage',
icon: 'database',
icon: 'database-outlined',
children: [
{
name: 'volume',
title: 'label.volumes',
icon: 'hdd',
icon: 'hdd-outlined',
docHelp: 'adminguide/storage.html#working-with-volumes',
permission: ['listVolumesMetrics'],
resourceType: 'Volume',
@ -65,54 +66,54 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
searchFilters: ['name', 'zoneid', 'domainid', 'account', 'state', 'tags'],
actions: [
{
api: 'createVolume',
icon: 'plus',
icon: 'plus-outlined',
docHelp: 'adminguide/storage.html#creating-a-new-volume',
label: 'label.action.create.volume',
listView: true,
popup: true,
component: () => import('@/views/storage/CreateVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateVolume.vue')))
},
{
api: 'createVolume',
icon: 'cloud-upload',
icon: 'cloud-upload-outlined',
docHelp: 'adminguide/storage.html#uploading-an-existing-volume-to-a-virtual-machine',
label: 'label.upload.volume.from.local',
listView: true,
popup: true,
component: () => import('@/views/storage/UploadLocalVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/UploadLocalVolume.vue')))
},
{
api: 'uploadVolume',
icon: 'link',
icon: 'link-outlined',
docHelp: 'adminguide/storage.html#uploading-an-existing-volume-to-a-virtual-machine',
label: 'label.upload.volume.from.url',
listView: true,
popup: true,
component: () => import('@/views/storage/UploadVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/UploadVolume.vue')))
},
{
api: 'attachVolume',
icon: 'paper-clip',
icon: 'paper-clip-outlined',
label: 'label.action.attach.disk',
dataView: true,
show: (record) => { return record.type !== 'ROOT' && ['Allocated', 'Ready', 'Uploaded'].includes(record.state) && !('virtualmachineid' in record) },
popup: true,
component: () => import('@/views/storage/AttachVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/AttachVolume.vue')))
},
{
api: 'detachVolume',
icon: 'link',
icon: 'link-outlined',
label: 'label.action.detach.disk',
message: 'message.detach.disk',
dataView: true,
@ -123,7 +124,7 @@ export default {
},
{
api: 'updateVolume',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
args: ['name'],
@ -138,7 +139,7 @@ export default {
},
{
api: 'createSnapshot',
icon: 'camera',
icon: 'camera-outlined',
docHelp: 'adminguide/storage.html#working-with-volume-snapshots',
label: 'label.action.take.snapshot',
dataView: true,
@ -148,11 +149,11 @@ export default {
record.hypervisor === 'KVM' && record.vmstate !== 'Running')
},
popup: true,
component: () => import('@/views/storage/TakeSnapshot.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/TakeSnapshot.vue')))
},
{
api: 'createSnapshotPolicy',
icon: 'clock-circle',
icon: 'clock-circle-outlined',
docHelp: 'adminguide/storage.html#working-with-volume-snapshots',
label: 'label.action.recurring.snapshot',
dataView: true,
@ -162,7 +163,7 @@ export default {
record.hypervisor === 'KVM' && record.vmstate !== 'Running')
},
popup: true,
component: () => import('@/views/storage/RecurringSnapshotVolume.vue'),
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/RecurringSnapshotVolume.vue'))),
mapping: {
volumeid: {
value: (record) => { return record.id }
@ -174,39 +175,39 @@ export default {
},
{
api: 'resizeVolume',
icon: 'fullscreen',
icon: 'fullscreen-outlined',
docHelp: 'adminguide/storage.html#resizing-volumes',
label: 'label.action.resize.volume',
dataView: true,
popup: true,
show: (record) => { return ['Allocated', 'Ready'].includes(record.state) },
component: () => import('@/views/storage/ResizeVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/ResizeVolume.vue')))
},
{
api: 'migrateVolume',
icon: 'drag',
icon: 'drag-outlined',
docHelp: 'adminguide/storage.html#id2',
label: 'label.migrate.volume',
args: ['volumeid', 'storageid', 'livemigrate'],
dataView: true,
show: (record, store) => { return record.state === 'Ready' && ['Admin'].includes(store.userInfo.roletype) },
popup: true,
component: () => import('@/views/storage/MigrateVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/MigrateVolume.vue')))
},
{
api: 'changeOfferingForVolume',
icon: 'swap',
icon: 'swap-outlined',
docHelp: 'adminguide/storage.html#id2',
label: 'label.change.offering.for.volume',
args: ['id', 'diskofferingid', 'size', 'miniops', 'maxiops', 'automigrate'],
dataView: true,
show: (record, store) => { return ['Allocated', 'Ready'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
popup: true,
component: () => import('@/views/storage/ChangeOfferingForVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/ChangeOfferingForVolume.vue')))
},
{
api: 'extractVolume',
icon: 'cloud-download',
icon: 'cloud-download-outlined',
label: 'label.action.download.volume',
message: 'message.download.volume.confirm',
dataView: true,
@ -224,7 +225,7 @@ export default {
},
{
api: 'createTemplate',
icon: 'picture',
icon: 'picture-outlined',
label: 'label.action.create.template.from.volume',
dataView: true,
show: (record) => {
@ -241,7 +242,7 @@ export default {
},
{
api: 'recoverVolume',
icon: 'medicine-box',
icon: 'medicine-box-outlined',
label: 'label.action.recover.volume',
message: 'message.action.recover.volume',
dataView: true,
@ -251,7 +252,7 @@ export default {
},
{
api: 'deleteVolume',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.volume',
message: 'message.action.delete.volume',
dataView: true,
@ -266,7 +267,7 @@ export default {
},
{
api: 'destroyVolume',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.destroy.volume',
message: 'message.action.destroy.volume',
dataView: true,
@ -284,7 +285,7 @@ export default {
{
name: 'snapshot',
title: 'label.snapshots',
icon: 'build',
icon: 'build-outlined',
docHelp: 'adminguide/storage.html#working-with-volume-snapshots',
permission: ['listSnapshots'],
resourceType: 'Snapshot',
@ -300,18 +301,18 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
searchFilters: ['name', 'domainid', 'account', 'tags'],
actions: [
{
api: 'createTemplate',
icon: 'picture',
icon: 'picture-outlined',
label: 'label.create.template',
dataView: true,
show: (record) => { return record.state === 'BackedUp' },
@ -324,7 +325,7 @@ export default {
},
{
api: 'createVolume',
icon: 'hdd',
icon: 'hdd-outlined',
label: 'label.action.create.volume',
dataView: true,
show: (record) => { return record.state === 'BackedUp' },
@ -343,7 +344,7 @@ export default {
},
{
api: 'revertSnapshot',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.action.revert.snapshot',
message: 'message.action.revert.snapshot',
dataView: true,
@ -351,7 +352,7 @@ export default {
},
{
api: 'deleteSnapshot',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.snapshot',
message: 'message.action.delete.snapshot',
dataView: true,
@ -365,7 +366,7 @@ export default {
{
name: 'vmsnapshot',
title: 'label.vm.snapshots',
icon: 'camera',
icon: 'camera-outlined',
docHelp: 'adminguide/storage.html#working-with-volume-snapshots',
permission: ['listVMSnapshot'],
resourceType: 'VMSnapshot',
@ -382,27 +383,27 @@ export default {
tabs: [
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'comments',
component: () => import('@/components/view/AnnotationsTab.vue')
component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
}
],
actions: [
{
api: 'createSnapshotFromVMSnapshot',
icon: 'camera',
icon: 'camera-outlined',
label: 'label.action.create.snapshot.from.vmsnapshot',
message: 'message.action.create.snapshot.from.vmsnapshot',
dataView: true,
popup: true,
show: (record) => { return (record.state === 'Ready' && record.hypervisor === 'KVM') },
component: () => import('@/views/storage/CreateSnapshotFromVMSnapshot.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateSnapshotFromVMSnapshot.vue')))
},
{
api: 'revertToVMSnapshot',
icon: 'sync',
icon: 'sync-outlined',
label: 'label.action.vmsnapshot.revert',
message: 'label.action.vmsnapshot.revert',
dataView: true,
@ -416,7 +417,7 @@ export default {
},
{
api: 'deleteVMSnapshot',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.vmsnapshot.delete',
message: 'message.action.vmsnapshot.delete',
dataView: true,
@ -436,14 +437,14 @@ export default {
{
name: 'backup',
title: 'label.backup',
icon: 'cloud-upload',
icon: 'cloud-upload-outlined',
permission: ['listBackups'],
columns: [{ name: (record) => { return record.virtualmachinename } }, 'virtualmachinename', 'status', 'type', 'created', 'account', 'zone'],
details: ['virtualmachinename', 'id', 'type', 'externalid', 'size', 'virtualsize', 'volumes', 'backupofferingname', 'zone', 'account', 'domain', 'created'],
actions: [
{
api: 'restoreBackup',
icon: 'sync',
icon: 'sync-outlined',
docHelp: 'adminguide/virtual_machines.html#restoring-vm-backups',
label: 'label.backup.restore',
message: 'message.backup.restore',
@ -452,17 +453,17 @@ export default {
},
{
api: 'restoreVolumeFromBackupAndAttachToVM',
icon: 'paper-clip',
icon: 'paper-clip-outlined',
label: 'label.backup.attach.restore',
message: 'message.backup.attach.restore',
dataView: true,
show: (record) => { return record.state !== 'Destroyed' },
popup: true,
component: () => import('@/views/storage/RestoreAttachBackupVolume.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/RestoreAttachBackupVolume.vue')))
},
{
api: 'removeVirtualMachineFromBackupOffering',
icon: 'scissor',
icon: 'scissor-outlined',
label: 'label.backup.offering.remove',
message: 'message.backup.offering.remove',
dataView: true,
@ -479,7 +480,7 @@ export default {
},
{
api: 'deleteBackup',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.delete.backup',
message: 'message.delete.backup',
dataView: true,

View File

@ -19,12 +19,12 @@ import store from '@/store'
export default {
name: 'tools',
title: 'label.tools',
icon: 'tool',
icon: 'tool-outlined',
children: [
{
name: 'comment',
title: 'label.comments',
icon: 'message',
icon: 'message-outlined',
docHelp: 'adminguide/events.html',
permission: ['listAnnotations'],
columns: () => {
@ -43,7 +43,7 @@ export default {
actions: [
{
api: 'removeAnnotation',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.remove.annotation',
message: 'message.remove.annotation',
dataView: false,
@ -63,7 +63,7 @@ export default {
{
name: 'manageinstances',
title: 'label.action.import.export.instances',
icon: 'interaction',
icon: 'interaction-outlined',
docHelp: 'adminguide/virtual_machines.html#importing-and-unmanaging-virtual-machine',
resourceType: 'UserVm',
permission: ['listInfrastructure', 'listUnmanagedInstances'],

View File

@ -15,10 +15,12 @@
// specific language governing permissions and limitations
// under the License.
import { shallowRef, defineAsyncComponent } from 'vue'
export default {
name: 'accountuser',
title: 'label.users',
icon: 'user',
icon: 'user-outlined',
docHelp: 'adminguide/accounts.html#users',
hidden: true,
permission: ['listUsers'],
@ -27,38 +29,38 @@ export default {
actions: [
{
api: 'createUser',
icon: 'plus',
icon: 'plus-outlined',
label: 'label.add.user',
listView: true,
popup: true,
component: () => import('@/views/iam/AddUser.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/AddUser.vue')))
},
{
api: 'updateUser',
icon: 'edit',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
popup: true,
component: () => import('@/views/iam/EditUser.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/EditUser.vue')))
},
{
api: 'updateUser',
icon: 'key',
icon: 'key-outlined',
label: 'label.action.change.password',
dataView: true,
popup: true,
component: () => import('@/views/iam/ChangeUserPassword.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/ChangeUserPassword.vue')))
},
{
api: 'registerUserKeys',
icon: 'file-protect',
icon: 'file-protect-outlined',
label: 'label.action.generate.keys',
message: 'message.generate.keys',
dataView: true
},
{
api: 'enableUser',
icon: 'play-circle',
icon: 'play-circle-outlined',
label: 'label.action.enable.user',
message: 'message.enable.user',
dataView: true,
@ -70,7 +72,7 @@ export default {
},
{
api: 'disableUser',
icon: 'pause-circle',
icon: 'pause-circle-outlined',
label: 'label.action.disable.user',
message: 'message.disable.user',
dataView: true,
@ -82,18 +84,18 @@ export default {
},
{
api: 'authorizeSamlSso',
icon: 'form',
icon: 'form-outlined',
label: 'Configure SAML SSO Authorization',
dataView: true,
popup: true,
show: (record, store) => {
return ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)
},
component: () => import('@/views/iam/ConfigureSamlSsoAuth.vue')
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/ConfigureSamlSsoAuth.vue')))
},
{
api: 'deleteUser',
icon: 'delete',
icon: 'delete-outlined',
label: 'label.action.delete.user',
message: 'message.delete.user',
dataView: true,

View File

@ -26,9 +26,14 @@ export default {
invertedMode: true,
multiTab: false, // enable to have tab/route history stuff
// vue-ls options
// storageOptions: {
// namespace: 'primate__', // key prefix
// name: 'ls', // name variable Vue.[ls] or this.[$ls],
// storage: 'local' // storage name session, local, memory
// },
// vue-ls options
storageOptions: {
namespace: 'primate__', // key prefix
name: 'ls', // name variable Vue.[ls] or this.[$ls],
storage: 'local' // storage name session, local, memory
prefix: 'primate__', // key prefix
drivers: ['local'] // storage name session, local, memory
}
}

View File

@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
import Vue from 'vue'
import { vueProps } from '@/vue-app'
import config from '@/config/settings'
import store from '@/store/'
import {
@ -31,21 +31,31 @@ import {
DEFAULT_FIXED_SIDEMENU,
DEFAULT_CONTENT_WIDTH_TYPE,
DEFAULT_MULTI_TAB,
HEADER_NOTICES
HEADER_NOTICES,
VUE_VERSION
} from '@/store/mutation-types'
export default function Initializer () {
store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))
store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))
store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))
store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
store.commit('TOGGLE_INVERTED', Vue.ls.get(DEFAULT_COLOR_INVERTED, config.invertedMode))
store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))
store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab))
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
store.commit('SET_PROJECT', Vue.ls.get(CURRENT_PROJECT))
store.commit('SET_HEADER_NOTICES', Vue.ls.get(HEADER_NOTICES) || [])
export default {
install: (app) => {
let vueVersion = vueProps.$localStorage.get(VUE_VERSION)
if (vueVersion !== app.version) {
vueVersion = app.version
vueProps.$localStorage.clear()
}
store.commit('SET_VUE_VERSION', vueVersion)
store.commit('SET_SIDEBAR_TYPE', vueProps.$localStorage.get(SIDEBAR_TYPE, true))
store.commit('TOGGLE_THEME', vueProps.$localStorage.get(DEFAULT_THEME, config.navTheme))
store.commit('TOGGLE_LAYOUT_MODE', vueProps.$localStorage.get(DEFAULT_LAYOUT_MODE, config.layout))
store.commit('TOGGLE_FIXED_HEADER', vueProps.$localStorage.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
store.commit('TOGGLE_FIXED_SIDERBAR', vueProps.$localStorage.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
store.commit('TOGGLE_CONTENT_WIDTH', vueProps.$localStorage.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
store.commit('TOGGLE_FIXED_HEADER_HIDDEN', vueProps.$localStorage.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
store.commit('TOGGLE_INVERTED', vueProps.$localStorage.get(DEFAULT_COLOR_INVERTED, config.invertedMode))
store.commit('TOGGLE_COLOR', vueProps.$localStorage.get(DEFAULT_COLOR, config.primaryColor))
store.commit('TOGGLE_MULTI_TAB', vueProps.$localStorage.get(DEFAULT_MULTI_TAB, config.multiTab))
store.commit('SET_TOKEN', vueProps.$localStorage.get(ACCESS_TOKEN))
store.commit('SET_PROJECT', vueProps.$localStorage.get(CURRENT_PROJECT))
store.commit('SET_HEADER_NOTICES', vueProps.$localStorage.get(HEADER_NOTICES) || [])
}
}

View File

@ -15,8 +15,6 @@
// specific language governing permissions and limitations
// under the License.
import Vue from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
@ -25,9 +23,13 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
// import { far } from '@fortawesome/free-regular-svg-icons'
import { faCentos, faUbuntu, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava } from '@fortawesome/free-brands-svg-icons'
import { faLanguage, faCompactDisc, faCameraRetro } from '@fortawesome/free-solid-svg-icons'
import { faCompactDisc, faCameraRetro } from '@fortawesome/free-solid-svg-icons'
library.add(faCentos, faUbuntu, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava)
library.add(faLanguage, faCompactDisc, faCameraRetro)
library.add(faCompactDisc, faCameraRetro)
Vue.component('font-awesome-icon', FontAwesomeIcon)
export default {
install: (app) => {
app.component('font-awesome-icon', FontAwesomeIcon)
}
}

View File

@ -15,8 +15,6 @@
// specific language governing permissions and limitations
// under the License.
/* eslint-disable */
import Vue from 'vue'
import {
ConfigProvider,
Layout,
@ -34,7 +32,6 @@ import {
Modal,
Table,
Tabs,
Icon,
Badge,
Popover,
Dropdown,
@ -57,7 +54,6 @@ import {
Popconfirm,
Descriptions,
message,
notification,
Affix,
Timeline,
Pagination,
@ -68,61 +64,66 @@ import {
AutoComplete,
Collapse
} from 'ant-design-vue'
import VueClipboard from 'vue3-clipboard'
import VueCropper from 'vue-cropper'
Vue.use(ConfigProvider)
Vue.use(Layout)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Button)
Vue.use(Switch)
Vue.use(Radio)
Vue.use(Checkbox)
Vue.use(Select)
Vue.use(Card)
Vue.use(Form)
Vue.use(Row)
Vue.use(Col)
Vue.use(Modal)
Vue.use(Table)
Vue.use(Tabs)
Vue.use(Icon)
Vue.use(Badge)
Vue.use(Popover)
Vue.use(Dropdown)
Vue.use(Descriptions)
Vue.use(List)
Vue.use(Avatar)
Vue.use(Breadcrumb)
Vue.use(Steps)
Vue.use(Spin)
Vue.use(Menu)
Vue.use(Drawer)
Vue.use(Tooltip)
Vue.use(Alert)
Vue.use(Tag)
Vue.use(Divider)
Vue.use(DatePicker)
Vue.use(TimePicker)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Skeleton)
Vue.use(Popconfirm)
Vue.use(notification)
Vue.use(Affix)
Vue.use(Timeline)
Vue.use(Pagination)
Vue.use(Comment)
Vue.use(Tree)
Vue.use(Calendar)
Vue.use(Slider)
Vue.use(AutoComplete)
Vue.use(Collapse)
Vue.use(VueCropper)
export default {
install: (app) => {
app.config.globalProperties.$confirm = Modal.confirm
app.config.globalProperties.$message = message
app.config.globalProperties.$info = Modal.info
app.config.globalProperties.$success = Modal.success
app.config.globalProperties.$error = Modal.error
app.config.globalProperties.$warning = Modal.warning
Vue.prototype.$confirm = Modal.confirm
Vue.prototype.$message = message
Vue.prototype.$info = Modal.info
Vue.prototype.$success = Modal.success
Vue.prototype.$error = Modal.error
Vue.prototype.$warning = Modal.warning
app.use(VueClipboard, { autoSetContainer: true })
app.use(VueCropper)
app.use(ConfigProvider)
app.use(Layout)
app.use(Input)
app.use(InputNumber)
app.use(Button)
app.use(Switch)
app.use(Radio)
app.use(Checkbox)
app.use(Select)
app.use(Card)
app.use(Form)
app.use(Row)
app.use(Col)
app.use(Modal)
app.use(Table)
app.use(Tabs)
app.use(Badge)
app.use(Popover)
app.use(Dropdown)
app.use(List)
app.use(Avatar)
app.use(Breadcrumb)
app.use(Steps)
app.use(Spin)
app.use(Menu)
app.use(Drawer)
app.use(Tooltip)
app.use(Alert)
app.use(Tag)
app.use(Divider)
app.use(DatePicker)
app.use(TimePicker)
app.use(Upload)
app.use(Progress)
app.use(Skeleton)
app.use(Popconfirm)
// app.use(Notification)
app.use(Affix)
app.use(Timeline)
app.use(Pagination)
app.use(Comment)
app.use(Tree)
app.use(Calendar)
app.use(Slider)
app.use(AutoComplete)
app.use(Collapse)
app.use(Descriptions)
}
}

View File

@ -0,0 +1,294 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import {
ApartmentOutlined,
ApiOutlined,
AppstoreOutlined,
ArrowDownOutlined,
ArrowUpOutlined,
ArrowsAltOutlined,
AuditOutlined,
BankOutlined,
BarcodeOutlined,
BarsOutlined,
BellOutlined,
BlockOutlined,
BranchesOutlined,
BookOutlined,
BuildOutlined,
BulbOutlined,
BugOutlined,
CalendarOutlined,
CameraOutlined,
CaretDownOutlined,
CaretRightOutlined,
CaretUpOutlined,
CheckCircleOutlined,
CheckCircleTwoTone,
CheckOutlined,
CheckSquareOutlined,
ClockCircleOutlined,
CloseCircleOutlined,
CloseCircleTwoTone,
CloseOutlined,
CloudDownloadOutlined,
CloudOutlined,
CloudUploadOutlined,
ClusterOutlined,
CodeOutlined,
CompassOutlined,
CopyOutlined,
CreditCardOutlined,
DashboardOutlined,
DatabaseOutlined,
DeleteOutlined,
DeleteTwoTone,
DeploymentUnitOutlined,
DesktopOutlined,
DisconnectOutlined,
DoubleLeftOutlined,
DoubleRightOutlined,
DownOutlined,
DownloadOutlined,
DragOutlined,
EditOutlined,
EnvironmentOutlined,
ExclamationCircleOutlined,
EyeInvisibleOutlined,
EyeOutlined,
FileProtectOutlined,
FilterOutlined,
FilterTwoTone,
FireOutlined,
FlagOutlined,
FolderAddOutlined,
FolderOutlined,
ForkOutlined,
FormOutlined,
ForwardOutlined,
FullscreenOutlined,
GatewayOutlined,
GithubOutlined,
GlobalOutlined,
GoldOutlined,
HddOutlined,
HomeOutlined,
IdcardOutlined,
ImportOutlined,
InboxOutlined,
InfoCircleOutlined,
InteractionOutlined,
KeyOutlined,
LinkOutlined,
LoadingOutlined,
LockOutlined,
LoginOutlined,
LogoutOutlined,
MailOutlined,
MedicineBoxOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
MessageFilled,
MessageOutlined,
MinusCircleOutlined,
MinusOutlined,
MinusSquareOutlined,
MoreOutlined,
NotificationOutlined,
NumberOutlined,
PaperClipOutlined,
PauseCircleOutlined,
PictureOutlined,
PieChartOutlined,
PlayCircleOutlined,
PlusCircleOutlined,
PlusOutlined,
PlusSquareOutlined,
PoweroffOutlined,
ProjectOutlined,
QuestionCircleOutlined,
ReadOutlined,
ReconciliationOutlined,
RedoOutlined,
ReloadOutlined,
RightCircleOutlined,
RocketOutlined,
SafetyCertificateOutlined,
SafetyOutlined,
SaveOutlined,
ScheduleOutlined,
ScissorOutlined,
SearchOutlined,
SettingOutlined,
ShareAltOutlined,
ShoppingOutlined,
StopOutlined,
SwapOutlined,
SyncOutlined,
TagOutlined,
TeamOutlined,
ThunderboltOutlined,
ToolOutlined,
TranslationOutlined,
UndoOutlined,
UsbOutlined,
UserAddOutlined,
UserOutlined,
UploadOutlined,
WifiOutlined
} from '@ant-design/icons-vue'
export default {
install: (app) => {
app.component('ApartmentOutlined', ApartmentOutlined)
app.component('ApiOutlined', ApiOutlined)
app.component('AppstoreOutlined', AppstoreOutlined)
app.component('ArrowDownOutlined', ArrowDownOutlined)
app.component('ArrowUpOutlined', ArrowUpOutlined)
app.component('ArrowsAltOutlined', ArrowsAltOutlined)
app.component('AuditOutlined', AuditOutlined)
app.component('BankOutlined', BankOutlined)
app.component('BarcodeOutlined', BarcodeOutlined)
app.component('BarsOutlined', BarsOutlined)
app.component('BellOutlined', BellOutlined)
app.component('BlockOutlined', BlockOutlined)
app.component('BranchesOutlined', BranchesOutlined)
app.component('BookOutlined', BookOutlined)
app.component('BuildOutlined', BuildOutlined)
app.component('BulbOutlined', BulbOutlined)
app.component('BugOutlined', BugOutlined)
app.component('CalendarOutlined', CalendarOutlined)
app.component('CameraOutlined', CameraOutlined)
app.component('CaretDownOutlined', CaretDownOutlined)
app.component('CaretRightOutlined', CaretRightOutlined)
app.component('CaretUpOutlined', CaretUpOutlined)
app.component('CheckCircleOutlined', CheckCircleOutlined)
app.component('CheckCircleTwoTone', CheckCircleTwoTone)
app.component('CheckOutlined', CheckOutlined)
app.component('CheckSquareOutlined', CheckSquareOutlined)
app.component('ClockCircleOutlined', ClockCircleOutlined)
app.component('CloseCircleOutlined', CloseCircleOutlined)
app.component('CloseCircleTwoTone', CloseCircleTwoTone)
app.component('CloseOutlined', CloseOutlined)
app.component('CloudDownloadOutlined', CloudDownloadOutlined)
app.component('CloudOutlined', CloudOutlined)
app.component('CloudUploadOutlined', CloudUploadOutlined)
app.component('ClusterOutlined', ClusterOutlined)
app.component('CodeOutlined', CodeOutlined)
app.component('CompassOutlined', CompassOutlined)
app.component('CopyOutlined', CopyOutlined)
app.component('CreditCardOutlined', CreditCardOutlined)
app.component('DashboardOutlined', DashboardOutlined)
app.component('DatabaseOutlined', DatabaseOutlined)
app.component('DeleteOutlined', DeleteOutlined)
app.component('DeleteTwoTone', DeleteTwoTone)
app.component('DeploymentUnitOutlined', DeploymentUnitOutlined)
app.component('DesktopOutlined', DesktopOutlined)
app.component('DisconnectOutlined', DisconnectOutlined)
app.component('DoubleLeftOutlined', DoubleLeftOutlined)
app.component('DoubleRightOutlined', DoubleRightOutlined)
app.component('DownOutlined', DownOutlined)
app.component('DownloadOutlined', DownloadOutlined)
app.component('DragOutlined', DragOutlined)
app.component('EditOutlined', EditOutlined)
app.component('EnvironmentOutlined', EnvironmentOutlined)
app.component('ExclamationCircleOutlined', ExclamationCircleOutlined)
app.component('EyeInvisibleOutlined', EyeInvisibleOutlined)
app.component('EyeOutlined', EyeOutlined)
app.component('FileProtectOutlined', FileProtectOutlined)
app.component('FilterOutlined', FilterOutlined)
app.component('FilterTwoTone', FilterTwoTone)
app.component('FireOutlined', FireOutlined)
app.component('FlagOutlined', FlagOutlined)
app.component('FolderAddOutlined', FolderAddOutlined)
app.component('FolderOutlined', FolderOutlined)
app.component('ForkOutlined', ForkOutlined)
app.component('FormOutlined', FormOutlined)
app.component('ForwardOutlined', ForwardOutlined)
app.component('FullscreenOutlined', FullscreenOutlined)
app.component('GatewayOutlined', GatewayOutlined)
app.component('GithubOutlined', GithubOutlined)
app.component('GlobalOutlined', GlobalOutlined)
app.component('GoldOutlined', GoldOutlined)
app.component('HddOutlined', HddOutlined)
app.component('HomeOutlined', HomeOutlined)
app.component('IdcardOutlined', IdcardOutlined)
app.component('ImportOutlined', ImportOutlined)
app.component('InboxOutlined', InboxOutlined)
app.component('InfoCircleOutlined', InfoCircleOutlined)
app.component('InteractionOutlined', InteractionOutlined)
app.component('KeyOutlined', KeyOutlined)
app.component('LinkOutlined', LinkOutlined)
app.component('LoadingOutlined', LoadingOutlined)
app.component('LockOutlined', LockOutlined)
app.component('LoginOutlined', LoginOutlined)
app.component('LogoutOutlined', LogoutOutlined)
app.component('MailOutlined', MailOutlined)
app.component('MedicineBoxOutlined', MedicineBoxOutlined)
app.component('MenuFoldOutlined', MenuFoldOutlined)
app.component('MenuUnfoldOutlined', MenuUnfoldOutlined)
app.component('MessageFilled', MessageFilled)
app.component('MessageOutlined', MessageOutlined)
app.component('MinusCircleOutlined', MinusCircleOutlined)
app.component('MinusOutlined', MinusOutlined)
app.component('MinusSquareOutlined', MinusSquareOutlined)
app.component('MoreOutlined', MoreOutlined)
app.component('NotificationOutlined', NotificationOutlined)
app.component('NumberOutlined', NumberOutlined)
app.component('PaperClipOutlined', PaperClipOutlined)
app.component('PauseCircleOutlined', PauseCircleOutlined)
app.component('PictureOutlined', PictureOutlined)
app.component('PieChartOutlined', PieChartOutlined)
app.component('PlayCircleOutlined', PlayCircleOutlined)
app.component('PlusCircleOutlined', PlusCircleOutlined)
app.component('PlusOutlined', PlusOutlined)
app.component('PlusSquareOutlined', PlusSquareOutlined)
app.component('PoweroffOutlined', PoweroffOutlined)
app.component('ProjectOutlined', ProjectOutlined)
app.component('QuestionCircleOutlined', QuestionCircleOutlined)
app.component('ReadOutlined', ReadOutlined)
app.component('ReconciliationOutlined', ReconciliationOutlined)
app.component('RedoOutlined', RedoOutlined)
app.component('ReloadOutlined', ReloadOutlined)
app.component('RightCircleOutlined', RightCircleOutlined)
app.component('RocketOutlined', RocketOutlined)
app.component('SafetyCertificateOutlined', SafetyCertificateOutlined)
app.component('SafetyOutlined', SafetyOutlined)
app.component('SaveOutlined', SaveOutlined)
app.component('ScheduleOutlined', ScheduleOutlined)
app.component('ScissorOutlined', ScissorOutlined)
app.component('SearchOutlined', SearchOutlined)
app.component('SettingOutlined', SettingOutlined)
app.component('ShareAltOutlined', ShareAltOutlined)
app.component('ShoppingOutlined', ShoppingOutlined)
app.component('StopOutlined', StopOutlined)
app.component('SwapOutlined', SwapOutlined)
app.component('SyncOutlined', SyncOutlined)
app.component('TagOutlined', TagOutlined)
app.component('TeamOutlined', TeamOutlined)
app.component('ThunderboltOutlined', ThunderboltOutlined)
app.component('ToolOutlined', ToolOutlined)
app.component('TranslationOutlined', TranslationOutlined)
app.component('UndoOutlined', UndoOutlined)
app.component('UsbOutlined', UsbOutlined)
app.component('UserAddOutlined', UserAddOutlined)
app.component('UserOutlined', UserOutlined)
app.component('UploadOutlined', UploadOutlined)
app.component('WifiOutlined', WifiOutlined)
}
}

View File

@ -15,36 +15,31 @@
// specific language governing permissions and limitations
// under the License.
import Vue from 'vue'
import VueStorage from 'vue-ls'
import { vueApp } from '@/vue-app'
import StoragePlugin from 'vue-web-storage'
import config from '@/config/settings'
// base library'
// import Viser from 'viser-vue'
import VueCropper from 'vue-cropper'
import '@/core/lazy_lib/components_use'
import componentsUse from '@/core/lazy_lib/components_use'
import iconsUse from '@/core/lazy_lib/icons_use'
import 'ant-design-vue/dist/antd.min.css'
import 'vue-cropper/dist/index.css'
import '@/style/vars.less'
// ext library
import VueClipboard from 'vue-clipboard2'
import PermissionHelper from '@/utils/helper/permission'
// customisation
import Spin from 'ant-design-vue/es/spin/Spin'
import { Spin } from 'ant-design-vue'
VueClipboard.config.autoSetContainer = true
// Vue.use(Viser)
Vue.use(VueStorage, config.storageOptions)
Vue.use(VueClipboard)
Vue.use(PermissionHelper)
Vue.use(VueCropper)
vueApp.use(StoragePlugin, config.storageOptions)
vueApp.use(PermissionHelper)
vueApp.use(componentsUse)
vueApp.use(iconsUse)
Spin.setDefaultIndicator({
indicator: (h) => {
return <a-icon type="loading" style="font-size: 30px" spin />
indicator: () => {
return <loading-outlined style="font-size: 30px" spin />
}
})

View File

@ -1,50 +0,0 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import Vue from 'vue'
import VueStorage from 'vue-ls'
import config from '@/config/settings'
// base library
import Antd from 'ant-design-vue'
import Viser from 'viser-vue'
import VueCropper from 'vue-cropper'
import 'ant-design-vue/dist/antd.less'
import '@/style/vars.less'
// ext library
import VueClipboard from 'vue-clipboard2'
import PermissionHelper from '@/utils/helper/permission'
// import '@/components/use'
// customisation
import Spin from 'ant-design-vue/es/spin/Spin'
VueClipboard.config.autoSetContainer = true
Vue.use(Antd)
Vue.use(Viser)
Vue.use(VueStorage, config.storageOptions)
Vue.use(VueClipboard)
Vue.use(PermissionHelper)
Vue.use(VueCropper)
Spin.setDefaultIndicator({
indicator: (h) => {
return <a-icon type="loading" style="font-size: 30px" spin />
}
})

Some files were not shown because too many files have changed in this diff Show More