mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-02 20:02:29 +01:00
ui: Admin, account and project dashboard improvements (#8193)
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
e65c9ffe70
commit
b2e83271f8
@ -2148,6 +2148,7 @@
|
||||
"label.volumetotal": "Volume",
|
||||
"label.volumetype": "Volume Type",
|
||||
"label.vpc": "VPC",
|
||||
"label.vpcs": "VPCs",
|
||||
"label.vpc.id": "VPC ID",
|
||||
"label.vpc.offerings": "VPC offerings",
|
||||
"label.vpc.virtual.router": "VPC virtual router",
|
||||
|
||||
@ -93,7 +93,6 @@ export default {
|
||||
}
|
||||
|
||||
&-footer {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding-top: 9px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ export default {
|
||||
text-align: center;
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
top: calc(50% - 45px);
|
||||
top: calc(100% - 45px);
|
||||
z-index: 100;
|
||||
|
||||
&.left{
|
||||
|
||||
@ -20,7 +20,7 @@ import { UserLayout, BasicLayout, RouteView } from '@/layouts'
|
||||
import AutogenView from '@/views/AutogenView.vue'
|
||||
import IFramePlugin from '@/views/plugins/IFramePlugin.vue'
|
||||
|
||||
import { shallowRef, defineAsyncComponent } from 'vue'
|
||||
import { shallowRef } from 'vue'
|
||||
import { vueProps } from '@/vue-app'
|
||||
|
||||
import compute from '@/config/section/compute'
|
||||
@ -201,26 +201,7 @@ export function asyncRouterMap () {
|
||||
name: 'dashboard',
|
||||
meta: {
|
||||
title: 'label.dashboard',
|
||||
icon: 'DashboardOutlined',
|
||||
tabs: [
|
||||
{
|
||||
name: 'dashboard',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/dashboard/UsageDashboardChart')))
|
||||
},
|
||||
{
|
||||
name: 'accounts',
|
||||
show: (record, route, user) => { return record.account === user.account || ['Admin', 'DomainAdmin'].includes(user.roletype) },
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/project/AccountsTab')))
|
||||
},
|
||||
{
|
||||
name: 'limits',
|
||||
params: {
|
||||
projectid: 'id'
|
||||
},
|
||||
show: (record, route, user) => { return ['Admin'].includes(user.roletype) },
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/components/view/ResourceLimitTab.vue')))
|
||||
}
|
||||
]
|
||||
icon: 'DashboardOutlined'
|
||||
},
|
||||
component: () => import('@/views/dashboard/Dashboard')
|
||||
},
|
||||
|
||||
@ -63,7 +63,8 @@ import {
|
||||
Slider,
|
||||
AutoComplete,
|
||||
Collapse,
|
||||
Space
|
||||
Space,
|
||||
Statistic
|
||||
} from 'ant-design-vue'
|
||||
import VueClipboard from 'vue3-clipboard'
|
||||
import VueCropper from 'vue-cropper'
|
||||
@ -127,5 +128,6 @@ export default {
|
||||
app.use(Collapse)
|
||||
app.use(Descriptions)
|
||||
app.use(Space)
|
||||
app.use(Statistic)
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
.dark-mode {
|
||||
background: @dark-bgColor;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1, h2, h3, h4, h5, h6, .ant-statistic-title, .ant-statistic-content {
|
||||
color: @dark-text-color-3;
|
||||
}
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ export const deviceEnquire = function (callback) {
|
||||
}
|
||||
|
||||
enquireJs
|
||||
.register('screen and (max-width: 800px)', matchMobile)
|
||||
.register('screen and (min-width: 800px) and (max-width: 1366px)', matchTablet)
|
||||
.register('screen and (min-width: 1367px)', matchDesktop)
|
||||
.register('screen and (max-width: 765px)', matchMobile)
|
||||
.register('screen and (min-width: 766px) and (max-width: 1279px)', matchTablet)
|
||||
.register('screen and (min-width: 1280px)', matchDesktop)
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
// under the License.
|
||||
|
||||
<template>
|
||||
<a-row class="capacity-dashboard" :gutter="12">
|
||||
<a-col :xl="18">
|
||||
<a-row class="capacity-dashboard" :gutter="[12,12]">
|
||||
<a-col :span="24">
|
||||
<div class="capacity-dashboard-wrapper">
|
||||
<div class="capacity-dashboard-select">
|
||||
<a-select
|
||||
@ -41,90 +41,282 @@
|
||||
<div class="capacity-dashboard-button">
|
||||
<a-button
|
||||
shape="round"
|
||||
@click="() => { listCapacity(zoneSelected, true); listEvents() }">
|
||||
@click="() => { updateData(zoneSelected); listAlerts(); listEvents(); }">
|
||||
<reload-outlined/>
|
||||
{{ $t('label.fetch.latest') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:xs="12"
|
||||
:sm="8"
|
||||
:md="6"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
v-for="stat in stats"
|
||||
:key="stat.type">
|
||||
<chart-card :loading="loading">
|
||||
<router-link :to="{ path: '/zone/' + zoneSelected.id }">
|
||||
<div class="capacity-dashboard-chart-card-inner">
|
||||
<h3>{{ $t(ts[stat.name]) }}</h3>
|
||||
<a-progress
|
||||
type="dashboard"
|
||||
:status="getStatus(parseFloat(stat.percentused))"
|
||||
:percent="parseFloat(stat.percentused)"
|
||||
:format="percent => `${parseFloat(stat.percentused).toFixed(2)}%`"
|
||||
:strokeColor="getStrokeColour(parseFloat(stat.percentused))"
|
||||
:width="100" />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<router-link :to="{ path: '/infrasummary' }" v-if="!zoneSelected.id">
|
||||
<h3>
|
||||
<bank-outlined />
|
||||
{{ $t('label.infrastructure') }}
|
||||
</h3>
|
||||
</router-link>
|
||||
<template #footer>
|
||||
<div class="center">{{ displayData(stat.name, stat.capacityused) }} / {{ displayData(stat.name, stat.capacitytotal) }}</div>
|
||||
<router-link :to="{ path: '/zone/' + zoneSelected.id }" v-else>
|
||||
<h3>
|
||||
<global-outlined />
|
||||
{{ $t('label.zone') }}
|
||||
</h3>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</chart-card>
|
||||
<a-divider style="margin: 0px 0px; border-width: 0px"/>
|
||||
<a-row :gutter="[12, 12]">
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/pod', query: { zoneid: zoneSelected.id } }">
|
||||
<a-statistic
|
||||
:title="$t('label.pods')"
|
||||
:value="data.pods"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<appstore-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/cluster', query: { zoneid: zoneSelected.id } }">
|
||||
<a-statistic
|
||||
:title="$t('label.clusters')"
|
||||
:value="data.clusters"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<cluster-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/host', query: { zoneid: zoneSelected.id } }">
|
||||
<a-statistic
|
||||
:title="$t('label.hosts')"
|
||||
:value="data.totalHosts"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<database-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/host', query: { zoneid: zoneSelected.id, state: 'alert' } }">
|
||||
<a-statistic
|
||||
:title="$t('label.host.alerts')"
|
||||
:value="data.alertHosts"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<database-outlined/>
|
||||
<a-badge v-if="data.alertHosts > 0" count="!" style="margin-left: -5px" />
|
||||
<a-badge v-else count="✓" style="margin-left: -5px" :number-style="{ backgroundColor: '#52c41a' }" />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/storagepool', query: { zoneid: zoneSelected.id } }">
|
||||
<a-statistic
|
||||
:title="$t('label.primary.storage')"
|
||||
:value="data.pools"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<hdd-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/systemvm', query: { zoneid: zoneSelected.id } }">
|
||||
<a-statistic
|
||||
:title="$t('label.system.vms')"
|
||||
:value="data.systemvms"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<thunderbolt-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/router', query: { zoneid: zoneSelected.id } }">
|
||||
<a-statistic
|
||||
:title="$t('label.virtual.routers')"
|
||||
:value="data.routers"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<fork-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/vm', query: { zoneid: zoneSelected.id, projectid: '-1' } }">
|
||||
<a-statistic
|
||||
:title="$t('label.instances')"
|
||||
:value="data.instances"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<cloud-server-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :xl="6" class="dashboard-event">
|
||||
<chart-card :loading="loading">
|
||||
<div style="text-align: center">
|
||||
<a-tooltip placement="bottom" class="capacity-dashboard-button-wrapper">
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
{{ $t('label.view') + ' ' + $t('label.host.alerts') }}
|
||||
</template>
|
||||
<a-button type="primary" danger shape="circle">
|
||||
<router-link :to="{ name: 'host', query: {'state': 'Alert'} }">
|
||||
<desktop-outlined class="capacity-dashboard-button-icon" />
|
||||
</router-link>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="capacity-dashboard-button-wrapper">
|
||||
<template #title>
|
||||
{{ $t('label.view') + ' ' + $t('label.alerts') }}
|
||||
</template>
|
||||
<a-button shape="circle">
|
||||
<router-link :to="{ name: 'alert' }">
|
||||
<flag-outlined class="capacity-dashboard-button-icon" />
|
||||
</router-link>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="capacity-dashboard-button-wrapper">
|
||||
<template #title>
|
||||
{{ $t('label.view') + ' ' + $t('label.events') }}
|
||||
</template>
|
||||
<a-button shape="circle">
|
||||
<router-link :to="{ name: 'event' }">
|
||||
<schedule-outlined class="capacity-dashboard-button-icon" />
|
||||
</router-link>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<div class="center">
|
||||
<h3><cloud-outlined /> {{ $t('label.compute') }}</h3>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="capacity-dashboard-footer">
|
||||
</template>
|
||||
<div>
|
||||
<div v-for="ctype in ['MEMORY', 'CPU', 'CPU_CORE', 'GPU']" :key="ctype" >
|
||||
<div v-if="statsMap[ctype]">
|
||||
<div>
|
||||
<strong>{{ $t(ts[ctype]) }}</strong>
|
||||
</div>
|
||||
<a-progress
|
||||
status="active"
|
||||
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) : 0"
|
||||
:format="p => statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) + '%' : '0%'"
|
||||
stroke-color="#52c41a"
|
||||
size="small"
|
||||
style="width:95%; float: left"
|
||||
/>
|
||||
<br/>
|
||||
<div style="text-align: center">
|
||||
{{ displayData(ctype, statsMap[ctype]?.capacityused) }} {{ $t('label.allocated') }} | {{ displayData(ctype, statsMap[ctype]?.capacitytotal) }} {{ $t('label.total') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<h3><hdd-outlined /> {{ $t('label.storage') }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<div v-for="ctype in ['STORAGE', 'STORAGE_ALLOCATED', 'LOCAL_STORAGE', 'SECONDARY_STORAGE']" :key="ctype" >
|
||||
<div v-if="statsMap[ctype]">
|
||||
<div>
|
||||
<strong>{{ $t(ts[ctype]) }}</strong>
|
||||
</div>
|
||||
<a-progress
|
||||
status="active"
|
||||
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) : 0"
|
||||
:format="p => statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) + '%' : '0%'"
|
||||
stroke-color="#52c41a"
|
||||
size="small"
|
||||
style="width:95%; float: left"
|
||||
/>
|
||||
<br/>
|
||||
<div style="text-align: center">
|
||||
{{ displayData(ctype, statsMap[ctype]?.capacityused) }} <span v-if="ctype !== 'STORAGE'">{{ $t('label.allocated') }}</span><span v-else>{{ $t('label.used') }}</span> | {{ displayData(ctype, statsMap[ctype]?.capacitytotal) }} {{ $t('label.total') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<h3><apartment-outlined /> {{ $t('label.network') }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<div v-for="ctype in ['VLAN', 'VIRTUAL_NETWORK_PUBLIC_IP', 'VIRTUAL_NETWORK_IPV6_SUBNET', 'DIRECT_ATTACHED_PUBLIC_IP', 'PRIVATE_IP']" :key="ctype" >
|
||||
<div v-if="statsMap[ctype]">
|
||||
<div>
|
||||
<strong>{{ $t(ts[ctype]) }}</strong>
|
||||
</div>
|
||||
<a-progress
|
||||
status="active"
|
||||
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) : 0"
|
||||
:format="p => statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) + '%' : '0%'"
|
||||
stroke-color="#52c41a"
|
||||
size="small"
|
||||
style="width:95%; float: left"
|
||||
/>
|
||||
<br/>
|
||||
<div style="text-align: center">
|
||||
{{ displayData(ctype, statsMap[ctype]?.capacityused) }} {{ $t('label.allocated') }} | {{ displayData(ctype, statsMap[ctype]?.capacitytotal) }} {{ $t('label.total') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<router-link :to="{ path: '/alert' }">
|
||||
<a-card :loading="loading" :bordered="false" class="dashboard-card dashboard-event">
|
||||
<div class="center" style="margin-top: -8px">
|
||||
<h3>
|
||||
<flag-outlined />
|
||||
{{ $t('label.alerts') }}
|
||||
</h3>
|
||||
</div>
|
||||
<a-divider style="margin: 6px 0px; border-width: 0px"/>
|
||||
<a-timeline>
|
||||
<a-timeline-item
|
||||
v-for="alert in alerts"
|
||||
:key="alert.id"
|
||||
color="red">
|
||||
<span :style="{ color: '#999' }"><small>{{ $toLocaleDate(alert.sent) }}</small></span>
|
||||
<span :style="{ color: '#666' }"><small><router-link :to="{ path: '/alert/' + alert.id }">{{ alert.name }}</router-link></small></span><br/>
|
||||
<span :style="{ color: '#aaa' }">{{ alert.description }}</span>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
<router-link :to="{ path: '/alert' }">
|
||||
<a-button>
|
||||
{{ $t('label.view') }} {{ $t('label.alerts') }}
|
||||
</a-button>
|
||||
</router-link>
|
||||
</a-card>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<router-link :to="{ path: '/event' }">
|
||||
<a-card :loading="loading" :bordered="false" class="dashboard-card dashboard-event">
|
||||
<div class="center" style="margin-top: -8px">
|
||||
<h3>
|
||||
<schedule-outlined />
|
||||
{{ $t('label.events') }}
|
||||
</h3>
|
||||
</div>
|
||||
<a-divider style="margin: 6px 0px; border-width: 0px"/>
|
||||
<a-timeline>
|
||||
<a-timeline-item
|
||||
v-for="event in events"
|
||||
:key="event.id"
|
||||
:color="getEventColour(event)">
|
||||
<span :style="{ color: '#999' }"><small>{{ $toLocaleDate(event.created) }}</small></span><br/>
|
||||
<span :style="{ color: '#999' }"><small>{{ $toLocaleDate(event.created) }}</small></span>
|
||||
<span :style="{ color: '#666' }"><small><router-link :to="{ path: '/event/' + event.id }">{{ event.type }}</router-link></small></span><br/>
|
||||
<span>
|
||||
<resource-label :resourceType="event.resourcetype" :resourceId="event.resourceid" :resourceName="event.resourcename" />
|
||||
</span>
|
||||
<span :style="{ color: '#aaa' }">({{ event.username }}) {{ event.description }}</span>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</div>
|
||||
</template>
|
||||
</chart-card>
|
||||
<router-link :to="{ path: '/event' }">
|
||||
<a-button>
|
||||
{{ $t('label.view') }} {{ $t('label.events') }}
|
||||
</a-button>
|
||||
</router-link>
|
||||
</a-card>
|
||||
</router-link>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
@ -135,21 +327,35 @@ import { api } from '@/api'
|
||||
import ChartCard from '@/components/widgets/ChartCard'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import ResourceLabel from '@/components/widgets/ResourceLabel'
|
||||
import Status from '@/components/widgets/Status'
|
||||
|
||||
export default {
|
||||
name: 'CapacityDashboard',
|
||||
components: {
|
||||
ChartCard,
|
||||
ResourceIcon,
|
||||
ResourceLabel
|
||||
ResourceLabel,
|
||||
Status
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
tabKey: 'alerts',
|
||||
alerts: [],
|
||||
events: [],
|
||||
zones: [],
|
||||
zoneSelected: {},
|
||||
stats: [],
|
||||
statsMap: {},
|
||||
data: {
|
||||
pods: 0,
|
||||
clusters: 0,
|
||||
totalHosts: 0,
|
||||
alertHosts: 0,
|
||||
pools: 0,
|
||||
instances: 0,
|
||||
systemvms: 0,
|
||||
routers: 0
|
||||
},
|
||||
ts: {
|
||||
CPU: 'label.cpu',
|
||||
CPU_CORE: 'label.cpunumber',
|
||||
@ -159,8 +365,8 @@ export default {
|
||||
MEMORY: 'label.memory',
|
||||
PRIVATE_IP: 'label.management.ips',
|
||||
SECONDARY_STORAGE: 'label.secondary.storage',
|
||||
STORAGE: 'label.storage',
|
||||
STORAGE_ALLOCATED: 'label.primary.storage',
|
||||
STORAGE: 'label.primary.storage.used',
|
||||
STORAGE_ALLOCATED: 'label.primary.storage.allocated',
|
||||
VIRTUAL_NETWORK_PUBLIC_IP: 'label.public.ips',
|
||||
VLAN: 'label.vlan',
|
||||
VIRTUAL_NETWORK_IPV6_SUBNET: 'label.ipv6.subnets'
|
||||
@ -196,13 +402,10 @@ export default {
|
||||
}
|
||||
return 'normal'
|
||||
},
|
||||
getStrokeColour (value) {
|
||||
if (value >= 80) {
|
||||
return this.$config.theme['@graph-exception-color'] || 'red'
|
||||
}
|
||||
return this.$config.theme['@graph-normal-color'] || 'primary'
|
||||
},
|
||||
displayData (dataType, value) {
|
||||
if (!value) {
|
||||
value = 0
|
||||
}
|
||||
switch (dataType) {
|
||||
case 'CPU':
|
||||
value = parseFloat(value / 1000.0, 10).toFixed(2) + ' GHz'
|
||||
@ -214,9 +417,9 @@ export default {
|
||||
case 'LOCAL_STORAGE':
|
||||
value = parseFloat(value / (1024 * 1024 * 1024.0), 10).toFixed(2)
|
||||
if (value >= 1024.0) {
|
||||
value = parseFloat(value / 1024.0).toFixed(2) + ' TB'
|
||||
value = parseFloat(value / 1024.0).toFixed(2) + ' TiB'
|
||||
} else {
|
||||
value = value + ' GB'
|
||||
value = value + ' GiB'
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -224,26 +427,134 @@ export default {
|
||||
},
|
||||
fetchData () {
|
||||
this.listZones()
|
||||
this.listAlerts()
|
||||
this.listEvents()
|
||||
},
|
||||
listCapacity (zone, latest = false) {
|
||||
const params = {
|
||||
zoneid: zone.id,
|
||||
fetchlatest: latest
|
||||
listCapacity (zone, latest = false, additive = false) {
|
||||
this.loading = true
|
||||
api('listCapacity', { zoneid: zone.id, fetchlatest: latest }).then(json => {
|
||||
this.loading = false
|
||||
let stats = []
|
||||
if (json && json.listcapacityresponse && json.listcapacityresponse.capacity) {
|
||||
stats = json.listcapacityresponse.capacity
|
||||
}
|
||||
for (const stat of stats) {
|
||||
if (additive) {
|
||||
for (const [key, value] of Object.entries(stat)) {
|
||||
if (stat.name in this.statsMap) {
|
||||
if (key in this.statsMap[stat.name]) {
|
||||
this.statsMap[stat.name][key] += value
|
||||
} else {
|
||||
this.statsMap[stat.name][key] = value
|
||||
}
|
||||
} else {
|
||||
this.statsMap[stat.name] = { key: value }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.statsMap[stat.name] = stat
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateData (zone) {
|
||||
if (!zone.id) {
|
||||
this.statsMap = {}
|
||||
for (const zone of this.zones.slice(1)) {
|
||||
this.listCapacity(zone, true, true)
|
||||
}
|
||||
} else {
|
||||
this.statsMap = {}
|
||||
this.listCapacity(this.zoneSelected, true)
|
||||
}
|
||||
|
||||
this.data = {
|
||||
pods: 0,
|
||||
clusters: 0,
|
||||
totalHosts: 0,
|
||||
alertHosts: 0,
|
||||
pools: 0,
|
||||
instances: 0,
|
||||
systemvms: 0,
|
||||
routers: 0
|
||||
}
|
||||
this.loading = true
|
||||
api('listCapacity', params).then(json => {
|
||||
this.stats = []
|
||||
api('listPods', { zoneid: zone.id }).then(json => {
|
||||
this.loading = false
|
||||
if (json && json.listcapacityresponse && json.listcapacityresponse.capacity) {
|
||||
this.stats = json.listcapacityresponse.capacity
|
||||
this.data.pods = json?.listpodsresponse?.count
|
||||
if (!this.data.pods) {
|
||||
this.data.pods = 0
|
||||
}
|
||||
})
|
||||
api('listClusters', { zoneid: zone.id }).then(json => {
|
||||
this.loading = false
|
||||
this.data.clusters = json?.listclustersresponse?.count
|
||||
if (!this.data.clusters) {
|
||||
this.data.clusters = 0
|
||||
}
|
||||
})
|
||||
api('listHosts', { zoneid: zone.id, listall: true, details: 'min', type: 'routing', page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.totalHosts = json?.listhostsresponse?.count
|
||||
if (!this.data.totalHosts) {
|
||||
this.data.totalHosts = 0
|
||||
}
|
||||
})
|
||||
api('listHosts', { zoneid: zone.id, listall: true, details: 'min', type: 'routing', state: 'alert', page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.alertHosts = json?.listhostsresponse?.count
|
||||
if (!this.data.alertHosts) {
|
||||
this.data.alertHosts = 0
|
||||
}
|
||||
})
|
||||
api('listStoragePools', { zoneid: zone.id }).then(json => {
|
||||
this.loading = false
|
||||
this.data.pools = json?.liststoragepoolsresponse?.count
|
||||
if (!this.data.pools) {
|
||||
this.data.pools = 0
|
||||
}
|
||||
})
|
||||
api('listSystemVms', { zoneid: zone.id }).then(json => {
|
||||
this.loading = false
|
||||
this.data.systemvms = json?.listsystemvmsresponse?.count
|
||||
if (!this.data.systemvms) {
|
||||
this.data.systemvms = 0
|
||||
}
|
||||
})
|
||||
api('listRouters', { zoneid: zone.id, listall: true }).then(json => {
|
||||
this.loading = false
|
||||
this.data.routers = json?.listroutersresponse?.count
|
||||
if (!this.data.routers) {
|
||||
this.data.routers = 0
|
||||
}
|
||||
})
|
||||
api('listVirtualMachines', { zoneid: zone.id, listall: true, projectid: '-1', details: 'min', page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.instances = json?.listvirtualmachinesresponse?.count
|
||||
if (!this.data.instances) {
|
||||
this.data.instances = 0
|
||||
}
|
||||
})
|
||||
},
|
||||
listAlerts () {
|
||||
const params = {
|
||||
page: 1,
|
||||
pagesize: 8,
|
||||
listall: true
|
||||
}
|
||||
this.loading = true
|
||||
api('listAlerts', params).then(json => {
|
||||
this.alerts = []
|
||||
this.loading = false
|
||||
if (json && json.listalertsresponse && json.listalertsresponse.alert) {
|
||||
this.alerts = json.listalertsresponse.alert
|
||||
}
|
||||
})
|
||||
},
|
||||
listEvents () {
|
||||
const params = {
|
||||
page: 1,
|
||||
pagesize: 6,
|
||||
pagesize: 8,
|
||||
listall: true
|
||||
}
|
||||
this.loading = true
|
||||
@ -269,18 +580,19 @@ export default {
|
||||
if (json && json.listzonesresponse && json.listzonesresponse.zone) {
|
||||
this.zones = json.listzonesresponse.zone
|
||||
if (this.zones.length > 0) {
|
||||
this.zones.splice(0, 0, { name: this.$t('label.all.zone') })
|
||||
this.zoneSelected = this.zones[0]
|
||||
this.listCapacity(this.zones[0])
|
||||
this.updateData(this.zones[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
changeZone (index) {
|
||||
this.zoneSelected = this.zones[index]
|
||||
this.listCapacity(this.zoneSelected)
|
||||
this.updateData(this.zoneSelected)
|
||||
},
|
||||
filterZone (input, option) {
|
||||
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,7 +602,6 @@ export default {
|
||||
.capacity-dashboard {
|
||||
&-wrapper {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&-chart-card-inner {
|
||||
@ -313,7 +624,7 @@ export default {
|
||||
|
||||
&-button {
|
||||
width: auto;
|
||||
padding-left: 12px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&-button-icon {
|
||||
@ -321,21 +632,28 @@ export default {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
&-footer {
|
||||
&-title {
|
||||
padding-top: 12px;
|
||||
padding-left: 3px;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
width: 100%;
|
||||
min-height: 370px;
|
||||
}
|
||||
|
||||
.dashboard-event {
|
||||
width: 100%;
|
||||
overflow-x:hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 370px;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-event {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -16,82 +16,303 @@
|
||||
// under the License.
|
||||
|
||||
<template>
|
||||
<a-row class="usage-dashboard" :gutter="12">
|
||||
<a-col :xl="16" style="padding-left: 0; padding-right: 0;">
|
||||
<a-row>
|
||||
<a-card style="width: 100%">
|
||||
<a-tabs
|
||||
v-if="showProject"
|
||||
:animated="false"
|
||||
@change="onTabChange">
|
||||
<template v-for="tab in $route.meta.tabs" :key="tab.name">
|
||||
<a-tab-pane
|
||||
v-if="'show' in tab ? tab.show(project, $route, $store.getters.userInfo) : true"
|
||||
:tab="$t('label.' + tab.name)"
|
||||
:key="tab.name">
|
||||
<keep-alive>
|
||||
<component
|
||||
:is="tab.component"
|
||||
:resource="project"
|
||||
:loading="loading"
|
||||
:bordered="false"
|
||||
:stats="stats" />
|
||||
</keep-alive>
|
||||
</a-tab-pane>
|
||||
<a-row class="capacity-dashboard" :gutter="[12,12]">
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<h3>
|
||||
<dashboard-outlined /> {{ $t('label.resources') }}
|
||||
<span style="float: right" v-if="showProject">
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<router-link :to="{ path: '/project/' + project.id }">
|
||||
<project-outlined/>
|
||||
{{ $t('label.view') }} {{ $t('label.project') }}
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="showProject && ['Admin'].includes($store.getters.userInfo.roletype)">
|
||||
<router-link :to="{ path: '/project/' + project.id, query: { tab: 'limits.configure' } }">
|
||||
<setting-outlined/>
|
||||
{{ $t('label.configure') }} {{ $t('label.project') }} {{ $t('label.limits') }}
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-tabs>
|
||||
<a-row :gutter="24" v-else>
|
||||
<a-col
|
||||
class="usage-dashboard-chart-tile"
|
||||
:xs="12"
|
||||
:md="8"
|
||||
v-for="stat in stats"
|
||||
:key="stat.type">
|
||||
<a-card
|
||||
class="usage-dashboard-chart-card"
|
||||
:bordered="false"
|
||||
:loading="loading"
|
||||
:style="stat.bgcolor ? { 'background': stat.bgcolor } : {}">
|
||||
<router-link v-if="stat.path" :to="{ path: stat.path, query: stat.query }">
|
||||
<div
|
||||
class="usage-dashboard-chart-card-inner">
|
||||
<h3>{{ stat.name }}</h3>
|
||||
<h2>
|
||||
<render-icon :icon="stat.icon" />
|
||||
{{ stat.count == undefined ? 0 : stat.count }}
|
||||
</h2>
|
||||
</div>
|
||||
</router-link>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :xl="8">
|
||||
<chart-card :loading="loading" >
|
||||
<div class="usage-dashboard-chart-card-inner">
|
||||
<a-button>
|
||||
<router-link :to="{ name: 'event' }">
|
||||
{{ $t('label.view') + ' ' + $t('label.events') }}
|
||||
</router-link>
|
||||
<a-button size="small" type="text">
|
||||
<more-outlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="usage-dashboard-chart-footer">
|
||||
</template>
|
||||
<a-divider style="margin: 6px 0px; border-width: 0px"/>
|
||||
<a-row :gutter="[10, 10]">
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/vm' }">
|
||||
<a-statistic
|
||||
:title="$t('label.instances')"
|
||||
:value="data.instances"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<cloud-server-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/kubernetes' }">
|
||||
<a-statistic
|
||||
:title="$t('label.kubernetes.cluster')"
|
||||
:value="data.kubernetes"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<cluster-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/volume' }">
|
||||
<a-statistic
|
||||
:title="$t('label.volumes')"
|
||||
:value="data.volumes"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<hdd-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/snapshot' }">
|
||||
<a-statistic
|
||||
:title="$t('label.snapshots')"
|
||||
:value="data.snapshots"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<build-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/guestnetwork' }">
|
||||
<a-statistic
|
||||
:title="$t('label.guest.networks')"
|
||||
:value="data.networks"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<apartment-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/vpc' }">
|
||||
<a-statistic
|
||||
:title="$t('label.vpcs')"
|
||||
:value="data.vpcs"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<deployment-unit-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/publicip' }">
|
||||
<a-statistic
|
||||
:title="$t('label.public.ips')"
|
||||
:value="data.ips"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<environment-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/template', query: { templatefilter: 'self', filter: 'self' } }">
|
||||
<a-statistic
|
||||
:title="$t('label.templates')"
|
||||
:value="data.templates"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<picture-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<h3>
|
||||
<cloud-outlined /> {{ $t('label.compute') }}
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<a-divider style="margin: 6px 0px; border-width: 0px"/>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/vm', query: { state: 'running', filter: 'running' } }">
|
||||
<a-statistic
|
||||
:title="$t('label.running') + ' ' + $t('label.instances')"
|
||||
:value="data.running"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<status class="status" text="Running"/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<router-link :to="{ path: '/vm', query: { state: 'stopped', filter: 'stopped' } }">
|
||||
<a-statistic
|
||||
:title="$t('label.stopped') + ' ' + $t('label.instances')"
|
||||
:value="data.stopped"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<status class="status" text="Stopped"/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider style="margin: 1px 0px; border-width: 0px;"/>
|
||||
<div
|
||||
v-for="usageType in ['vm', 'cpu', 'memory', 'project']"
|
||||
:key="usageType">
|
||||
<div v-if="usageType + 'total' in entity">
|
||||
<div>
|
||||
<strong>
|
||||
{{ $t(getLabel(usageType)) }}
|
||||
</strong>
|
||||
<span style="float: right">
|
||||
{{ getValue(usageType, entity[usageType + 'total']) }} {{ $t('label.used') }}
|
||||
</span>
|
||||
</div>
|
||||
<a-progress
|
||||
status="active"
|
||||
:percent="parseFloat(getPercentUsed(entity[usageType + 'total'], entity[usageType + 'limit']))"
|
||||
:format="p => resource[item + 'limit'] !== '-1' && resource[item + 'limit'] !== 'Unlimited' ? p.toFixed(0) + '%' : ''"
|
||||
stroke-color="#52c41a"
|
||||
size="small"
|
||||
/>
|
||||
<br/>
|
||||
<div style="text-align: center">
|
||||
{{ entity[usageType + 'available'] === 'Unlimited' ? $t('label.unlimited') : getValue(usageType, entity[usageType + 'available']) }} {{ $t('label.available') }}
|
||||
{{ entity[usageType + 'limit'] === 'Unlimited' ? '' : (' | ' + getValue(usageType, entity[usageType + 'limit']) + ' ' + $t('label.limit')) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<h3><hdd-outlined /> {{ $t('label.storage') }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
<a-divider style="margin: 6px 0px; border-width: 0px"/>
|
||||
<div
|
||||
v-for="usageType in ['volume', 'snapshot', 'template', 'primarystorage', 'secondarystorage']"
|
||||
:key="usageType">
|
||||
<div>
|
||||
<div>
|
||||
<strong>
|
||||
{{ $t(getLabel(usageType)) }}
|
||||
</strong>
|
||||
<span style="float: right">
|
||||
{{ getValue(usageType, entity[usageType + 'total']) }} {{ $t('label.used') }}
|
||||
</span>
|
||||
</div>
|
||||
<a-progress
|
||||
status="active"
|
||||
:percent="parseFloat(getPercentUsed(entity[usageType + 'total'], entity[usageType + 'limit']))"
|
||||
:format="p => resource[item + 'limit'] !== '-1' && resource[item + 'limit'] !== 'Unlimited' ? p.toFixed(0) + '%' : ''"
|
||||
stroke-color="#52c41a"
|
||||
size="small"
|
||||
/>
|
||||
<br/>
|
||||
<div style="text-align: center">
|
||||
{{ entity[usageType + 'available'] === 'Unlimited' ? $t('label.unlimited') : getValue(usageType, entity[usageType + 'available']) }} {{ $t('label.available') }}
|
||||
{{ entity[usageType + 'limit'] === 'Unlimited' ? '' : (' | ' + getValue(usageType, entity[usageType + 'limit']) + ' ' + $t('label.limit')) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }" class="dashboard-card">
|
||||
<chart-card :loading="loading" class="dashboard-card">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<h3><apartment-outlined /> {{ $t('label.network') }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
<a-divider style="margin: 6px 0px; border-width: 0px"/>
|
||||
<div
|
||||
v-for="usageType in ['ip', 'network', 'vpc']"
|
||||
:key="usageType">
|
||||
<div>
|
||||
<div>
|
||||
<strong>
|
||||
{{ $t(getLabel(usageType)) }}
|
||||
</strong>
|
||||
<span style="float: right">
|
||||
{{ getValue(usageType, entity[usageType + 'total']) }} {{ $t('label.used') }}
|
||||
</span>
|
||||
</div>
|
||||
<a-progress
|
||||
status="active"
|
||||
:percent="parseFloat(getPercentUsed(entity[usageType + 'total'], entity[usageType + 'limit']))"
|
||||
:format="p => resource[item + 'limit'] !== '-1' && resource[item + 'limit'] !== 'Unlimited' ? p.toFixed(0) + '%' : ''"
|
||||
stroke-color="#52c41a"
|
||||
size="small"
|
||||
/>
|
||||
<br/>
|
||||
<div style="text-align: center">
|
||||
{{ entity[usageType + 'available'] === 'Unlimited' ? $t('label.unlimited') : getValue(usageType, entity[usageType + 'available']) }} {{ $t('label.available') }}
|
||||
{{ entity[usageType + 'limit'] === 'Unlimited' ? '' : (' | ' + getValue(usageType, entity[usageType + 'limit']) + ' ' + $t('label.limit')) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
|
||||
<chart-card :loading="loading" class="dashboard-card dashboard-event">
|
||||
<template #title>
|
||||
<div class="center">
|
||||
<h3><schedule-outlined /> {{ $t('label.events') }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
<a-divider style="margin: 6px 0px; border-width: 0px"/>
|
||||
<a-timeline>
|
||||
<a-timeline-item
|
||||
v-for="event in events"
|
||||
:key="event.id"
|
||||
:color="getEventColour(event)">
|
||||
<span :style="{ color: '#999' }"><small>{{ $toLocaleDate(event.created) }}</small></span><br/>
|
||||
<span :style="{ color: '#999' }"><small>{{ $toLocaleDate(event.created) }}</small></span>
|
||||
<span :style="{ color: '#666' }"><small><router-link :to="{ path: '/event/' + event.id }">{{ event.type }}</router-link></small></span><br/>
|
||||
<span>
|
||||
<resource-label :resourceType="event.resourcetype" :resourceId="event.resourceid" :resourceName="event.resourcename" />
|
||||
</span>
|
||||
<span :style="{ color: '#aaa' }">({{ event.username }}) {{ event.description }}</span>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</div>
|
||||
</template>
|
||||
<router-link :to="{ path: '/event' }">
|
||||
<a-button>
|
||||
{{ $t('label.view') }} {{ $t('label.events') }}
|
||||
</a-button>
|
||||
</router-link>
|
||||
</chart-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@ -104,13 +325,15 @@ import store from '@/store'
|
||||
import ChartCard from '@/components/widgets/ChartCard'
|
||||
import UsageDashboardChart from '@/views/dashboard/UsageDashboardChart'
|
||||
import ResourceLabel from '@/components/widgets/ResourceLabel'
|
||||
import Status from '@/components/widgets/Status'
|
||||
|
||||
export default {
|
||||
name: 'UsageDashboard',
|
||||
components: {
|
||||
ChartCard,
|
||||
UsageDashboardChart,
|
||||
ResourceLabel
|
||||
ResourceLabel,
|
||||
Status
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
@ -129,9 +352,29 @@ export default {
|
||||
loading: false,
|
||||
showAction: false,
|
||||
showAddAccount: false,
|
||||
project: {},
|
||||
account: {},
|
||||
events: [],
|
||||
stats: [],
|
||||
project: {}
|
||||
data: {
|
||||
running: 0,
|
||||
stopped: 0,
|
||||
instances: 0,
|
||||
kubernetes: 0,
|
||||
volumes: 0,
|
||||
snapshots: 0,
|
||||
networks: 0,
|
||||
vpcs: 0,
|
||||
ips: 0,
|
||||
templates: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
entity: function () {
|
||||
if (this.showProject) {
|
||||
return this.project
|
||||
}
|
||||
return this.account
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@ -158,6 +401,9 @@ export default {
|
||||
deep: true,
|
||||
handler (newData, oldData) {
|
||||
this.project = newData
|
||||
if (newData.id) {
|
||||
this.fetchData()
|
||||
}
|
||||
}
|
||||
},
|
||||
'$i18n.global.locale' (to, from) {
|
||||
@ -168,61 +414,95 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.stats = [{}, {}, {}, {}, {}, {}]
|
||||
api('listVirtualMachines', { state: 'Running', listall: true }).then(json => {
|
||||
var count = 0
|
||||
if (json && json.listvirtualmachinesresponse) {
|
||||
count = json.listvirtualmachinesresponse.count
|
||||
if (store.getters.project.id) {
|
||||
this.listProject()
|
||||
} else {
|
||||
this.listAccount()
|
||||
}
|
||||
var tileColor = this.$config.theme['@dashboard-tile-runningvms-bg'] || '#dfe9cc'
|
||||
this.stats.splice(0, 1, { name: this.$t('label.running.vms'), count: count, icon: 'desktop-outlined', bgcolor: tileColor, path: '/vm', query: { state: 'running', filter: 'running' } })
|
||||
})
|
||||
api('listVirtualMachines', { state: 'Stopped', listall: true }).then(json => {
|
||||
var count = 0
|
||||
if (json && json.listvirtualmachinesresponse) {
|
||||
count = json.listvirtualmachinesresponse.count
|
||||
this.updateData()
|
||||
},
|
||||
listAccount () {
|
||||
this.loading = true
|
||||
api('listAccounts', { id: this.$store.getters.userInfo.accountid }).then(json => {
|
||||
this.loading = false
|
||||
if (json && json.listaccountsresponse && json.listaccountsresponse.account) {
|
||||
this.account = json.listaccountsresponse.account[0]
|
||||
}
|
||||
var tileColor = this.$config.theme['@dashboard-tile-stoppedvms-bg'] || '#edcbce'
|
||||
this.stats.splice(1, 1, { name: this.$t('label.stopped.vms'), count: count, icon: 'poweroff-outlined', bgcolor: tileColor, path: '/vm', query: { state: 'stopped', filter: 'stopped' } })
|
||||
})
|
||||
api('listVirtualMachines', { listall: true }).then(json => {
|
||||
var count = 0
|
||||
if (json && json.listvirtualmachinesresponse) {
|
||||
count = json.listvirtualmachinesresponse.count
|
||||
},
|
||||
listProject () {
|
||||
this.loading = true
|
||||
api('listProjects', { id: store.getters.project.id }).then(json => {
|
||||
this.loading = false
|
||||
if (json && json.listprojectsresponse && json.listprojectsresponse.project) {
|
||||
this.project = json.listprojectsresponse.project[0]
|
||||
}
|
||||
var tileColor = this.$config.theme['@dashboard-tile-totalvms-bg'] || '#ffffff'
|
||||
this.stats.splice(2, 1, { name: this.$t('label.total.vms'), count: count, icon: 'number-outlined', bgcolor: tileColor, path: '/vm' })
|
||||
})
|
||||
api('listVolumes', { listall: true }).then(json => {
|
||||
var count = 0
|
||||
if (json && json.listvolumesresponse) {
|
||||
count = json.listvolumesresponse.count
|
||||
},
|
||||
updateData () {
|
||||
this.data = {
|
||||
running: 0,
|
||||
stopped: 0,
|
||||
instances: 0,
|
||||
kubernetes: 0,
|
||||
volumes: 0,
|
||||
snapshots: 0,
|
||||
networks: 0,
|
||||
vpcs: 0,
|
||||
ips: 0,
|
||||
templates: 0
|
||||
}
|
||||
var tileColor = this.$config.theme['@dashboard-tile-totalvolumes-bg'] || '#ffffff'
|
||||
this.stats.splice(3, 1, { name: this.$t('label.total.volume'), count: count, icon: 'database-outlined', bgcolor: tileColor, path: '/volume' })
|
||||
})
|
||||
api('listNetworks', { listall: true }).then(json => {
|
||||
var count = 0
|
||||
if (json && json.listnetworksresponse) {
|
||||
count = json.listnetworksresponse.count
|
||||
}
|
||||
var tileColor = this.$config.theme['@dashboard-tile-totalnetworks-bg'] || '#ffffff'
|
||||
this.stats.splice(4, 1, { name: this.$t('label.total.network'), count: count, icon: 'apartment-outlined', bgcolor: tileColor, path: '/guestnetwork' })
|
||||
})
|
||||
api('listPublicIpAddresses', { listall: true }).then(json => {
|
||||
var count = 0
|
||||
if (json && json.listpublicipaddressesresponse) {
|
||||
count = json.listpublicipaddressesresponse.count
|
||||
}
|
||||
var tileColor = this.$config.theme['@dashboard-tile-totalips-bg'] || '#ffffff'
|
||||
this.stats.splice(5, 1, { name: this.$t('label.public.ip.addresses'), count: count, icon: 'environment-outlined', bgcolor: tileColor, path: '/publicip' })
|
||||
})
|
||||
this.listInstances()
|
||||
this.listEvents()
|
||||
this.loading = true
|
||||
api('listKubernetesClusters', { listall: true, page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.kubernetes = json?.listkubernetesclustersresponse?.count
|
||||
})
|
||||
api('listVolumes', { listall: true, page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.volumes = json?.listvolumesresponse?.count
|
||||
})
|
||||
api('listSnapshots', { listall: true, page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.snapshots = json?.listsnapshotsresponse?.count
|
||||
})
|
||||
api('listNetworks', { listall: true, page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.networks = json?.listnetworksresponse?.count
|
||||
})
|
||||
api('listVPCs', { listall: true, page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.vpcs = json?.listvpcsresponse?.count
|
||||
})
|
||||
api('listPublicIpAddresses', { listall: true, page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.ips = json?.listpublicipaddressesresponse?.count
|
||||
})
|
||||
api('listTemplates', { templatefilter: 'self', listall: true, page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.templates = json?.listtemplatesresponse?.count
|
||||
})
|
||||
},
|
||||
listInstances (zone) {
|
||||
this.loading = true
|
||||
api('listVirtualMachines', { listall: true, details: 'min', page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.instances = json?.listvirtualmachinesresponse?.count
|
||||
})
|
||||
api('listVirtualMachines', { listall: true, details: 'min', state: 'running', page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.running = json?.listvirtualmachinesresponse?.count
|
||||
})
|
||||
api('listVirtualMachines', { listall: true, details: 'min', state: 'stopped', page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.stopped = json?.listvirtualmachinesresponse?.count
|
||||
})
|
||||
},
|
||||
listEvents () {
|
||||
const params = {
|
||||
page: 1,
|
||||
pagesize: 6,
|
||||
pagesize: 8,
|
||||
listall: true
|
||||
}
|
||||
this.loading = true
|
||||
@ -234,6 +514,37 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
getLabel (usageType) {
|
||||
switch (usageType) {
|
||||
case 'vm':
|
||||
return 'label.instances'
|
||||
case 'cpu':
|
||||
return 'label.cpunumber'
|
||||
case 'memory':
|
||||
return 'label.memory'
|
||||
case 'primarystorage':
|
||||
return 'label.primary.storage'
|
||||
case 'secondarystorage':
|
||||
return 'label.secondary.storage'
|
||||
case 'ip':
|
||||
return 'label.public.ips'
|
||||
}
|
||||
return 'label.' + usageType + 's'
|
||||
},
|
||||
getValue (usageType, value) {
|
||||
switch (usageType) {
|
||||
case 'memory':
|
||||
return parseFloat(value / 1024.0).toFixed(2) + ' GiB'
|
||||
case 'primarystorage':
|
||||
return parseFloat(value).toFixed(2) + ' GiB'
|
||||
case 'secondarystorage':
|
||||
return parseFloat(value).toFixed(2) + ' GiB'
|
||||
}
|
||||
return value
|
||||
},
|
||||
getPercentUsed (total, limit) {
|
||||
return (limit === 'Unlimited') ? 0 : (total / limit) * 100
|
||||
},
|
||||
getEventColour (event) {
|
||||
if (event.level === 'ERROR') {
|
||||
return 'red'
|
||||
@ -242,13 +553,6 @@ export default {
|
||||
return 'green'
|
||||
}
|
||||
return 'blue'
|
||||
},
|
||||
onTabChange (key) {
|
||||
this.showAddAccount = false
|
||||
|
||||
if (key !== 'Dashboard') {
|
||||
this.showAddAccount = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,6 +580,23 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
width: 100%;
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.dashboard-event {
|
||||
width: 100%;
|
||||
overflow-x:hidden;
|
||||
overflow-y: scroll;
|
||||
max-height: 420px;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.ant-col-xl-8 {
|
||||
width: 100%;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user