Skip to content
7 changes: 6 additions & 1 deletion ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"label.action.create.snapshot.from.vmsnapshot": "Create snapshot from VM snapshot",
"label.action.create.template.from.volume": "Create template from volume",
"label.action.create.volume": "Create volume",
"label.action.create.volume.add": "Create and Add Volume",
"label.action.delete.account": "Delete account",
"label.action.delete.backup.offering": "Delete backup offering",
"label.action.delete.cluster": "Delete cluster",
Expand Down Expand Up @@ -1135,7 +1136,8 @@
"label.license.agreements": "License agreements",
"label.limit": "Limit",
"label.limitcpuuse": "CPU cap",
"label.limits": "Configure limits",
"label.limits": "Limits",
"label.limits.configure": "Configure limits",
"label.link.domain.to.ldap": "Link domain to LDAP",
"label.linklocalip": "Link-local/Control IP address",
"label.linux": "Linux",
Expand Down Expand Up @@ -1785,6 +1787,7 @@
"label.snapshotlimit": "Snapshot limits",
"label.snapshotmemory": "Snapshot memory",
"label.snapshots": "Snapshots",
"label.snapshottype": "Snapshot Type",
"label.sockettimeout": "Socket timeout",
"label.softwareversion": "Software version",
"label.source.based": "SourceBased",
Expand Down Expand Up @@ -2140,6 +2143,7 @@
"label.volumename": "Volume name",
"label.volumes": "Volumes",
"label.volumetotal": "Volume",
"label.volumetype": "Volume Type",
"label.vpc": "VPC",
"label.vpc.id": "VPC ID",
"label.vpc.offerings": "VPC offerings",
Expand Down Expand Up @@ -2347,6 +2351,7 @@
"message.attach.volume": "Please fill in the following data to attach a new volume. If you are attaching a disk volume to a Windows based virtual machine, you will need to reboot the instance to see the attached disk.",
"message.attach.volume.failed": "Failed to attach volume.",
"message.attach.volume.progress": "Attaching volume",
"message.attach.volume.success": "Successfully attached the volume to the instance",
"message.authorization.failed": "Session expired, authorization verification failed.",
"message.autoscale.loadbalancer.update": "The load balancer rule can be updated only when autoscale VM group is DISABLED.",
"message.autoscale.policies.update": "The scale up/down policies can be updated only when autoscale VM group is DISABLED.",
Expand Down
8 changes: 7 additions & 1 deletion ui/src/components/header/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<a-dropdown>
<span class="user-menu-dropdown action">
<span v-if="image">
<resource-icon :image="image" size="2x" style="margin-right: 5px"/>
<resource-icon :image="image" size="4x" style="margin-right: 5px; margin-top: -3px"/>
</span>
<a-avatar v-else-if="userInitials" class="user-menu-avatar avatar" size="small" :style="{ backgroundColor: '#1890ff', color: 'white' }">
{{ userInitials }}
Expand All @@ -45,6 +45,12 @@
<span class="user-menu-item-name">{{ $t('label.profilename') }}</span>
</a-menu-item>
</router-link>
<router-link :to="{ path: '/account/' + $store.getters.userInfo.accountid, query: { tab: 'limits' } }">
<a-menu-item class="user-menu-item" key="0">
<ControlOutlined class="user-menu-item-icon" />
<span class="user-menu-item-name">{{ $t('label.limits') }}</span>
</a-menu-item>
</router-link>
<a @click="toggleUseBrowserTimezone">
<a-menu-item class="user-menu-item" key="1">
<ClockCircleOutlined class="user-menu-item-icon" />
Expand Down
2 changes: 0 additions & 2 deletions ui/src/components/menu/SideMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,12 @@ export default {

.ant-menu-light {
border-right-color: transparent;
padding: 14px 0;
}
}

&.dark {
.ant-menu-dark {
border-right-color: transparent;
padding: 14px 0;
}
}
}
Expand Down
72 changes: 51 additions & 21 deletions ui/src/components/view/InfoCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
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() && !$route.path.includes('zones')">
<camera-outlined class="upload-icon"/>
<edit-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])">
Expand Down Expand Up @@ -119,14 +119,14 @@
<div class="resource-detail-item__label">{{ $t('label.id') }}</div>
<div class="resource-detail-item__details">
<tooltip-button
tooltipPlacement="right"
tooltipPlacement="top"
:tooltip="$t('label.copyid')"
icon="barcode-outlined"
type="dashed"
size="small"
:copyResource="resource.id"
@onClick="$message.success($t('label.copied.clipboard'))" />
<span style="margin-left: 10px;">{{ resource.id }}</span>
<span style="margin-left: 10px;"><copy-label :label="resource.id" /></span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.ostypename && resource.ostypeid">
Expand All @@ -139,6 +139,29 @@
<span style="margin-left: 8px">{{ resource.ostypename }}</span>
</div>
</div>
<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">
<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 }">
<copy-label :label="ipaddress" />
</router-link>
<span v-else>
<span v-if="ipaddress.includes(',')">
<span
v-for="(value, index) in ipaddress.split(',')"
:key="index">
<copy-label :label="value" /><br/>
</span>
</span>
<span v-else>
<copy-label :label="ipaddress" />
</span>
</span>
</div>
</div>
<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">
Expand Down Expand Up @@ -292,11 +315,19 @@
v-for="(eth, index) in resource.nic"
:key="eth.id"
style="margin-left: -24px; margin-top: 5px;">
<api-outlined /><strong>eth{{ index }}</strong> {{ eth.ip6address ? eth.ipaddress + ', ' + eth.ip6address : eth.ipaddress }}
<router-link v-if="!isStatic && eth.networkname && eth.networkid" :to="{ path: '/guestnetwork/' + eth.networkid }">({{ eth.networkname }})</router-link>
<api-outlined />
<strong>eth{{ index }}</strong>&nbsp;
<copy-label :label="eth.ip6address ? eth.ipaddress + ', ' + eth.ip6address : eth.ipaddress" />&nbsp;
<a-tag v-if="eth.isdefault">
{{ $t('label.default') }}
</a-tag >
</a-tag ><br/>
<span v-if="!isStatic && eth.networkname && eth.networkid">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<apartment-outlined/>
<router-link :to="{ path: '/guestnetwork/' + eth.networkid }">
{{ eth.networkname }}
</router-link>
</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -324,16 +355,6 @@
<span>{{ resource.loadbalancer.name }} ( {{ resource.loadbalancer.publicip }}:{{ resource.loadbalancer.publicport }})</span>
</div>
</div>
<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">
<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>
<span v-else>{{ ipaddress }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.projectid || resource.projectname">
<div class="resource-detail-item__label">{{ $t('label.project') }}</div>
<div class="resource-detail-item__details">
Expand Down Expand Up @@ -368,10 +389,15 @@
<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">
<key-outlined />
<li v-for="keypair in keypairs" :key="keypair">
<router-link :to="{ path: '/ssh/' + keypair }" style="margin-right: 5px">{{ keypair }}</router-link>
</li>
<div>
<div
v-for="keypair in keypairs"
:key="keypair"
style="margin-top: 5px;">
<key-outlined />
<router-link :to="{ path: '/ssh/' + keypair }" style="margin-right: 5px">{{ keypair }}</router-link>
</div>
</div>
</div>
</div>
<div class="resource-detail-item" v-if="resource.resourcetype && resource.resourceid && routeFromResourceType">
Expand Down Expand Up @@ -420,7 +446,8 @@
<div class="resource-detail-item__label">{{ $t('label.publicip') }}</div>
<div class="resource-detail-item__details">
<gateway-outlined />
<router-link :to="{ path: '/publicip/' + resource.publicipid }">{{ resource.publicip }} </router-link>
<router-link v-if="resource.publicipid" :to="{ path: '/publicip/' + resource.publicipid }">{{ resource.publicip }} </router-link>
<copy-label :label="resource.publicip"/>
</div>
</div>
<div class="resource-detail-item" v-if="resource.vpcid">
Expand Down Expand Up @@ -554,6 +581,7 @@
</span>
<global-outlined v-else />
<router-link v-if="!isStatic && $router.resolve('/zone/' + resource.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + resource.zoneid }">{{ resource.zone || resource.zonename || resource.zoneid }}</router-link>
<router-link v-else-if="$router.resolve('/zones/' + resource.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zones/' + resource.zoneid }">{{ resource.zone || resource.zonename || resource.zoneid }}</router-link>
<span v-else>{{ resource.zone || resource.zonename || resource.zoneid }}</span>
</div>
</div>
Expand Down Expand Up @@ -733,6 +761,7 @@ import { createPathBasedOnVmType } from '@/utils/plugins'
import Console from '@/components/widgets/Console'
import OsLogo from '@/components/widgets/OsLogo'
import Status from '@/components/widgets/Status'
import CopyLabel from '@/components/widgets/CopyLabel'
import TooltipButton from '@/components/widgets/TooltipButton'
import UploadResourceIcon from '@/components/view/UploadResourceIcon'
import eventBus from '@/config/eventBus'
Expand All @@ -745,6 +774,7 @@ export default {
Console,
OsLogo,
Status,
CopyLabel,
TooltipButton,
UploadResourceIcon,
ResourceIcon,
Expand Down
36 changes: 30 additions & 6 deletions ui/src/components/view/ListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@
<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"/>
<resource-icon :image="record.icon.base64image" size="2x"/>
</span>
<os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="lg" />
<os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="2x" />
</span>
<span style="min-width: 120px" >
<QuickView
Expand All @@ -88,8 +88,8 @@
<tooltip-button type="dashed" size="small" icon="LoginOutlined" @onClick="changeProject(record)" />
</span>
<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" />
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
<os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="2x" />
<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>
Expand Down Expand Up @@ -143,7 +143,7 @@
</template>
<template #username="{text, record}">
<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"/>
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
<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>
Expand All @@ -162,7 +162,9 @@
</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-else>
<copy-label :label="text" />
</span>
<span v-if="record.issourcenat">
&nbsp;
<a-tag>source-nat</a-tag>
Expand All @@ -188,6 +190,25 @@
<template #virtualmachinename="{ text, record }">
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
</template>
<template #volumename="{ text, record }">
<router-link :to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link>
</template>
<template #size="{ text }">
<span v-if="text">
{{ parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) }} GiB
</span>
</template>
<template #physicalsize="{ text }">
<span v-if="text">
{{ parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) }} GiB
</span>
</template>
<template #physicalnetworkname="{ text, record }">
<router-link :to="{ path: '/physicalnetwork/' + record.physicalnetworkid }">{{ text }}</router-link>
</template>
<template #serviceofferingname="{ text, record }">
<router-link :to="{ path: '/computeoffering/' + record.serviceofferingid }">{{ text }}</router-link>
</template>
<template #hypervisor="{ text, record }">
<span v-if="$route.name === 'hypervisorcapability'">
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
Expand Down Expand Up @@ -303,6 +324,7 @@
</template>
<template #zonename="{ text, record }">
<router-link v-if="$router.resolve('/zone/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
<router-link v-else-if="$router.resolve('/zones/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zones/' + record.zoneid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</template>
<template #rolename="{ text, record }">
Expand Down Expand Up @@ -431,6 +453,7 @@ import { api } from '@/api'
import OsLogo from '@/components/widgets/OsLogo'
import Status from '@/components/widgets/Status'
import QuickView from '@/components/view/QuickView'
import CopyLabel from '@/components/widgets/CopyLabel'
import TooltipButton from '@/components/widgets/TooltipButton'
import ResourceIcon from '@/components/view/ResourceIcon'
import ResourceLabel from '@/components/widgets/ResourceLabel'
Expand All @@ -442,6 +465,7 @@ export default {
OsLogo,
Status,
QuickView,
CopyLabel,
TooltipButton,
ResourceIcon,
ResourceLabel
Expand Down
51 changes: 51 additions & 0 deletions ui/src/components/widgets/CopyLabel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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>
<span @click="$message.success($t('label.copied.clipboard') + ': ' + label)">
<a-tooltip :title="tooltip ? tooltip : $t('label.copy')" :placement="tooltipPlacement">
<a href="javascript:;" v-clipboard:copy="label">{{ label }}</a>
</a-tooltip>
</span>
</template>

<script>

export default {
name: 'CopyLabel',
props: {
label: {
type: String,
default: ''
},
tooltip: {
type: String,
default: ''
},
tooltipPlacement: {
type: String,
default: 'top'
}
}
}
</script>

<style scoped lang="scss">
.tooltip-icon {
color: rgba(0,0,0,.45);
}
</style>
4 changes: 2 additions & 2 deletions ui/src/config/section/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export default {
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'resources',
name: 'limits',
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceCountUsage.vue')))
},
{
name: 'limits',
name: 'limits.configure',
show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) },
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceLimitTab.vue')))
},
Expand Down
Loading