Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
301bbc6
ui: support bulk action for various resources
Pearl1594 May 10, 2021
62e6e30
ui: support bulk action for various resources
Pearl1594 May 10, 2021
e2aa664
Bulk actions support - progress review
Pearl1594 May 21, 2021
ebe34c4
Merge branch 'master' of https://github.com/apache/cloudstack into ui…
Pearl1594 May 21, 2021
e9aadc8
Extract common code + suppress error notification with bulk actions
Pearl1594 May 24, 2021
5ad6a03
Merge branch 'ui-bulk-action' of github.com:shapeblue/cloudstack into…
Pearl1594 May 31, 2021
773dd17
cleanup + suppress notification
Pearl1594 May 31, 2021
2685ebd
add progress view
Pearl1594 May 31, 2021
957e008
Add routes to notification + add async jobs + refactor progress view
Pearl1594 Jun 2, 2021
92c6b51
Merge branch 'ui-bulk-action' of github.com:shapeblue/cloudstack into…
Pearl1594 Jun 3, 2021
18d66e3
minor tweaks
Pearl1594 Jun 3, 2021
22cb236
fix group action for vpn users
Pearl1594 Jun 4, 2021
15e52d4
Refactor code
Pearl1594 Jun 7, 2021
0449520
Unique row key
Pearl1594 Jun 8, 2021
4d487c7
remove redundant cols
Pearl1594 Jun 8, 2021
47929bc
address comments
Pearl1594 Jun 9, 2021
00748af
Merge branch 'ui-bulk-action' of github.com:shapeblue/cloudstack into…
Pearl1594 Jun 10, 2021
ed15af0
Added the following:
Pearl1594 Jun 11, 2021
07ec1c2
Merge branch 'main' of https://github.com/apache/cloudstack into ui-b…
Pearl1594 Jun 24, 2021
4d17fcf
Add dynamism to column filtering
Pearl1594 Jun 24, 2021
d66f64d
Merge branch 'main' of https://github.com/apache/cloudstack into ui-b…
Pearl1594 Jul 6, 2021
d30d493
Merge branch 'main' of https://github.com/apache/cloudstack into ui-b…
Pearl1594 Jul 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@
"label.action.attach.disk.processing": "Attaching Disk....",
"label.action.attach.iso": "Attach ISO",
"label.action.attach.iso.processing": "Attaching ISO....",
"label.action.bulk.delete.egress.firewall.rules": "Bulk delete egress firewall rules",
"label.action.bulk.delete.firewall.rules": "Bulk delete firewall rules",
"label.action.bulk.delete.load.balancer.rules": "Bulk delete load balancer rules",
"label.action.bulk.delete.templates": "Bulk delete templates",
"label.action.bulk.delete.isos": "Bulk delete ISOs",
"label.action.bulk.delete.portforward.rules": "Bulk delete Port Forward rules",
"label.action.bulk.release.public.ip.address": "Bulk release Public IP Addresses",
"label.action.cancel.maintenance.mode": "Cancel Maintenance Mode",
"label.action.cancel.maintenance.mode.processing": "Cancelling Maintenance Mode....",
"label.action.change.password": "Change Password",
Expand Down Expand Up @@ -99,6 +106,7 @@
"label.action.delete.disk.offering.processing": "Deleting Disk Offering....",
"label.action.delete.domain": "Delete Domain",
"label.action.delete.domain.processing": "Deleting Domain....",
"label.action.delete.egress.firewall": "Delete egress firewall rule",
"label.action.delete.firewall": "Delete firewall rule",
"label.action.delete.firewall.processing": "Deleting Firewall....",
"label.action.delete.ingress.rule": "Delete Ingress Rule",
Expand Down Expand Up @@ -586,6 +594,13 @@
"label.confirmdeclineinvitation": "Are you sure you want to decline this project invitation?",
"label.confirmpassword": "Confirm Password",
"label.confirmpassword.description": "Please type the same password again",
"label.confirm.delete.egress.firewall.rules": "Please confirm you wish to delete the selected egress firewall rules",
"label.confirm.delete.firewall.rules": "Please confirm you wish to delete the selected firewall rules",
"label.confirm.delete.loadbalancer.rules": "Please confirm you wish to delete the selected load balancing rules",
"label.confirm.delete.portforward.rules": "Please confirm you wish to delete the selected port-forward rules",
"label.confirm.delete.templates": "Please confirm you wish to delete the selected templates",
"label.confirm.delete.isos": "Please confirm you wish to delete the selected isos",
"label.confirm.release.public.ip.addresses": "Please confirm you wish to release the selected public IP addresses",
"label.congratulations": "Congratulations!",
"label.connectiontimeout": "Connection Timeout",
"label.conservemode": "Conserve mode",
Expand Down Expand Up @@ -693,6 +708,7 @@
"label.delete.opendaylight.device": "Delete OpenDaylight Controller",
"label.delete.pa": "Delete Palo Alto",
"label.delete.portable.ip.range": "Delete Portable IP Range",
"label.delete.portforward.rules": "Delete Port Forward Rules",
"label.delete.project": "Delete project",
"label.delete.project.role": "Delete Project Role",
"label.delete.role": "Delete Role",
Expand Down Expand Up @@ -902,6 +918,7 @@
"label.filterby": "Filter by",
"label.fingerprint": "FingerPrint",
"label.firewall": "Firewall",
"label.firewallrule": "Firewall Rule",
"label.firstname": "First Name",
"label.firstname.lower": "firstname",
"label.fix.errors": "Fix errors",
Expand Down Expand Up @@ -1169,6 +1186,7 @@
"label.isvolatile": "Volatile",
"label.item.listing": "Item listing",
"label.items": "items",
"label.items.selected": "item(s) selected",
"label.japanese.keyboard": "Japanese keyboard",
"label.keep": "Keep",
"label.keep.colon": "Keep:",
Expand Down Expand Up @@ -1520,6 +1538,7 @@
"label.opendaylight.controllerdetail": "OpenDaylight Controller Details",
"label.opendaylight.controllers": "OpenDaylight Controllers",
"label.operation": "Operation",
"label.operation.status": "Operation Status",
"label.optional": "Optional",
"label.order": "Order",
"label.oscategoryid": "OS Preference",
Expand Down Expand Up @@ -1605,6 +1624,7 @@
"label.portable.ip.ranges": "Portable IP Ranges",
"label.portableipaddress": "Portable IPs",
"label.portforwarding": "Port Forwarding",
"label.portforwarding.rule": "Port Forwarding Rule",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
Expand Down Expand Up @@ -2399,6 +2419,7 @@
"message.action.delete.external.firewall": "Please confirm that you would like to remove this external firewall. Warning: If you are planning to add back the same external firewall, you must reset usage data on the device.",
"message.action.delete.external.load.balancer": "Please confirm that you would like to remove this external load balancer. Warning: If you are planning to add back the same external load balancer, you must reset usage data on the device.",
"message.action.delete.ingress.rule": "Please confirm that you want to delete this ingress rule.",
"message.action.delete.instance.group": "Please confirm that you want to delete the instance group",
"message.action.delete.iso": "Please confirm that you want to delete this ISO.",
"message.action.delete.iso.for.all.zones": "The ISO is used by all zones. Please confirm that you want to delete it from all zones.",
"message.action.delete.network": "Please confirm that you want to delete this network.",
Expand Down Expand Up @@ -3323,6 +3344,8 @@
"state.error": "Error",
"state.expired": "Expired",
"state.expunging": "Expunging",
"state.failed": "Failed",
"state.inprogress": "In Progress",
"state.migrating": "Migrating",
"state.pending": "Pending",
"state.readonly": "Read-Only",
Expand Down
31 changes: 23 additions & 8 deletions ui/src/components/header/HeaderNotice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@
</a-list-item-meta>
</a-list-item>
<a-list-item v-for="(job, index) in jobs" :key="index">
<a-list-item-meta :title="job.title" :description="job.description">
<a-avatar :style="notificationAvatar[job.status].style" :icon="notificationAvatar[job.status].icon" slot="avatar"/>
<a-list-item-meta :title="job.title">
<a-avatar :style="notificationAvatar[job.status].style" :icon="notificationAvatar[job.status].icon" slot="avatar"/><br/>
<span v-if="getResourceName(job.description, 'name') && job.path" slot="description"><router-link :to="{ path: job.path}"> {{ getResourceName(job.description, "name") + ' - ' }}</router-link></span>
<span v-if="getResourceName(job.description, 'name') && job.path" slot="description"> {{ getResourceName(job.description, "msg") }}</span>
<span v-else slot="description"> {{ job.description }} </span>
</a-list-item-meta>
</a-list-item>
</a-list>
Expand Down Expand Up @@ -80,6 +83,16 @@ export default {
this.pollJobs()
}, 4000)
},
getResourceName (description, data) {
if (description) {
if (data === 'name') {
const name = description.match(/\(([^)]+)\)/)
return name ? name[1] : null
}
const msg = description.substring(description.indexOf(')') + 1)
return msg
}
},
async pollJobs () {
var hasUpdated = false
for (var i in this.jobs) {
Expand All @@ -102,12 +115,14 @@ export default {
if (result.jobresult.errortext !== null) {
this.jobs[i].description = '(' + this.jobs[i].description + ') ' + result.jobresult.errortext
}
this.$notification.error({
message: this.jobs[i].title,
description: this.jobs[i].description,
key: this.jobs[i].jobid,
duration: 0
})
if (!this.jobs[i].bulkAction) {
this.$notification.error({
message: this.jobs[i].title,
description: this.jobs[i].description,
key: this.jobs[i].jobid,
duration: 0
})
}
}
}).catch(function (e) {
console.log(this.$t('error.fetching.async.job.result') + e)
Expand Down
191 changes: 191 additions & 0 deletions ui/src/components/view/BulkActionProgress.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

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

export default {
name: 'BulkActionProgress',
components: {
Status
},
filters: {
capitalise: val => {
if (val === 'all') return 'All'
return val.toUpperCase()
}
},
props: {
showGroupActionModal: {
type: Boolean,
default: false
},
selectedItems: {
type: Array,
default: () => []
},
selectedColumns: {
type: Array,
default: () => []
},
message: {
type: Object,
default: () => {}
}
},
created () {
this.filteredItems = this.selectedItems
},
data () {
return {
appliedFilterStatus: {},
filteredItems: [],
filterItemsTimer: null,
tableChanged: false
}
},
inject: ['parentFetchData'],
watch: {
succeededCount (count) {
if (count > 0) {
this.filterItemsDelayed()
}
},
failedCount (count) {
if (count > 0) {
this.filterItemsDelayed()
}
}
},
computed: {
succeededCount () {
return this.selectedItems.filter(item => item.status === 'success').length || 0
},
failedCount () {
return this.selectedItems.filter(item => item.status === 'failed').length || 0
}
},
methods: {
handleTableChange (pagination, filters, sorter) {
this.filteredItems = this.selectedItems
this.appliedFilterStatus = filters.status
this.filterItems()
this.tableChanged = true
},
filterItems () {
if (this.appliedFilterStatus && this.appliedFilterStatus.length > 0) {
this.filteredItems = this.selectedItems.filter(item => {
if (this.appliedFilterStatus.includes(item.status)) {
return item
}
})
}
},
filterItemsDelayed () {
clearTimeout(this.filterItemsTimer)
this.filterItemsTimer = setTimeout(() => {
this.filterItems()
}, 50)
},
handleCancel () {
this.filteredItems = []
this.tableChanged = false
this.$emit('handle-cancel')
},
returnAlgorithmName (name) {
switch (name) {
case 'leastconn':
return 'Least connections'
case 'roundrobin' :
return 'Round-robin'
case 'source':
return 'Source'
default :
return ''
}
}
}
}
</script>
Loading