This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch 4.18
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.18 by this push:
new b2e83271f8b ui: Admin, account and project dashboard improvements
(#8193)
b2e83271f8b is described below
commit b2e83271f8b71a25890043179511115df8402c8d
Author: Rohit Yadav <[email protected]>
AuthorDate: Wed Nov 8 14:38:05 2023 +0530
ui: Admin, account and project dashboard improvements (#8193)
Signed-off-by: Rohit Yadav <[email protected]>
---
ui/public/locales/en.json | 1 +
ui/src/components/widgets/ChartCard.vue | 1 -
ui/src/components/widgets/Drawer.vue | 2 +-
ui/src/config/router.js | 23 +-
ui/src/core/lazy_lib/components_use.js | 4 +-
ui/src/style/dark-mode.less | 4 +-
ui/src/utils/device.js | 6 +-
ui/src/views/dashboard/CapacityDashboard.vue | 532 ++++++++++++++++++++-----
ui/src/views/dashboard/UsageDashboard.vue | 569 +++++++++++++++++++++------
9 files changed, 882 insertions(+), 260 deletions(-)
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 55f07357de9..d1f0d84e755 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -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",
diff --git a/ui/src/components/widgets/ChartCard.vue
b/ui/src/components/widgets/ChartCard.vue
index 8d5e413c9db..a41cd69a3f1 100644
--- a/ui/src/components/widgets/ChartCard.vue
+++ b/ui/src/components/widgets/ChartCard.vue
@@ -93,7 +93,6 @@ export default {
}
&-footer {
- border-top: 1px solid #e8e8e8;
padding-top: 9px;
margin-top: 8px;
}
diff --git a/ui/src/components/widgets/Drawer.vue
b/ui/src/components/widgets/Drawer.vue
index 38636522dbd..0a1c535e5f8 100644
--- a/ui/src/components/widgets/Drawer.vue
+++ b/ui/src/components/widgets/Drawer.vue
@@ -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{
diff --git a/ui/src/config/router.js b/ui/src/config/router.js
index e7e8e642877..502a0246edf 100644
--- a/ui/src/config/router.js
+++ b/ui/src/config/router.js
@@ -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')
},
diff --git a/ui/src/core/lazy_lib/components_use.js
b/ui/src/core/lazy_lib/components_use.js
index 10790d61bc0..3c61c02c08f 100644
--- a/ui/src/core/lazy_lib/components_use.js
+++ b/ui/src/core/lazy_lib/components_use.js
@@ -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)
}
}
diff --git a/ui/src/style/dark-mode.less b/ui/src/style/dark-mode.less
index 1b338ea9ae6..4d8ae25104f 100644
--- a/ui/src/style/dark-mode.less
+++ b/ui/src/style/dark-mode.less
@@ -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;
}
@@ -959,4 +959,4 @@
.button-clear-notification {
background-color: @dark-secondary-bgColor;
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/utils/device.js b/ui/src/utils/device.js
index ce6deab7b39..86c7b1e7d84 100644
--- a/ui/src/utils/device.js
+++ b/ui/src/utils/device.js
@@ -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)
}
diff --git a/ui/src/views/dashboard/CapacityDashboard.vue
b/ui/src/views/dashboard/CapacityDashboard.vue
index 2d7b31ed467..a174e60b709 100644
--- a/ui/src/views/dashboard/CapacityDashboard.vue
+++ b/ui/src/views/dashboard/CapacityDashboard.vue
@@ -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,91 +41,283 @@
<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>
+ <router-link :to="{ path: '/zone/' + zoneSelected.id }" v-else>
+ <h3>
+ <global-outlined />
+ {{ $t('label.zone') }}
+ </h3>
+ </router-link>
+ </div>
+ </template>
+ <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>
- <template #footer>
- <div class="center">{{ displayData(stat.name, stat.capacityused)
}} / {{ displayData(stat.name, stat.capacitytotal) }}</div>
- </template>
- </chart-card>
- </a-col>
- </a-row>
+ </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">
- <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>
+ <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>
+ <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>
- <template #footer>
- <div class="capacity-dashboard-footer">
- <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: '#666' }"><small><router-link :to="{
path: '/event/' + event.id }">{{ event.type }}</router-link></small></span><br/>
- <resource-label :resourceType="event.resourcetype"
:resourceId="event.resourceid" :resourceName="event.resourcename" />
- <span :style="{ color: '#aaa' }">({{ event.username }}) {{
event.description }}</span>
- </a-timeline-item>
- </a-timeline>
+ </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>
+ <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>
+ <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) {
+ 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('listPods', { zoneid: zone.id }).then(json => {
+ this.loading = false
+ 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 = {
- zoneid: zone.id,
- fetchlatest: latest
+ page: 1,
+ pagesize: 8,
+ listall: true
}
this.loading = true
- api('listCapacity', params).then(json => {
- this.stats = []
+ api('listAlerts', params).then(json => {
+ this.alerts = []
this.loading = false
- if (json && json.listcapacityresponse &&
json.listcapacityresponse.capacity) {
- this.stats = json.listcapacityresponse.capacity
+ 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>
diff --git a/ui/src/views/dashboard/UsageDashboard.vue
b/ui/src/views/dashboard/UsageDashboard.vue
index a6983300cb9..9f7d9883dab 100644
--- a/ui/src/views/dashboard/UsageDashboard.vue
+++ b/ui/src/views/dashboard/UsageDashboard.vue
@@ -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>
- </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-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-button size="small" type="text">
+ <more-outlined />
+ </a-button>
+ </a-dropdown>
+ </span>
+ </h3>
+ </div>
+ </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 :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') }}
+ <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-button>
+ </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>
- <template #footer>
- <div class="usage-dashboard-chart-footer">
- <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: '#666' }"><small><router-link :to="{
path: '/event/' + event.id }">{{ event.type }}</router-link></small></span><br/>
- <resource-label :resourceType="event.resourcetype"
:resourceId="event.resourceid" :resourceName="event.resourcename" />
- <span :style="{ color: '#aaa' }">({{ event.username }}) {{
event.description }}</span>
- </a-timeline-item>
- </a-timeline>
+ </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>
+ <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>
+ <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()
+ }
+ 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-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
+ },
+ 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-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
- }
- 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' })
+ },
+ updateData () {
+ this.data = {
+ running: 0,
+ stopped: 0,
+ instances: 0,
+ kubernetes: 0,
+ volumes: 0,
+ snapshots: 0,
+ networks: 0,
+ vpcs: 0,
+ ips: 0,
+ templates: 0
+ }
+ 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 }).then(json => {
- var count = 0
- if (json && json.listvolumesresponse) {
- count = json.listvolumesresponse.count
- }
- 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('listVolumes', { listall: true, page: 1, pagesize: 1 }).then(json =>
{
+ this.loading = false
+ this.data.volumes = json?.listvolumesresponse?.count
})
- 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('listSnapshots', { listall: true, page: 1, pagesize: 1 }).then(json
=> {
+ this.loading = false
+ this.data.snapshots = json?.listsnapshotsresponse?.count
})
- 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' })
+ 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
})
- this.listEvents()
},
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%;