Merge pull request #94 from houhoucoop/issue-7382

feat: add confirmation pop-up for VM stop and pause actions
This commit is contained in:
Yiya Chen 2025-01-21 10:23:54 +08:00 committed by GitHub
commit b9fab8bb57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 225 additions and 16 deletions

View File

@ -0,0 +1,173 @@
<script>
import { mapState } from 'vuex';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { alternateLabel } from '@shell/utils/platform';
import AsyncButton from '@shell/components/AsyncButton';
import { Banner } from '@components/Banner';
import { Card } from '@components/Card';
import { escapeHtml } from '@shell/utils/string';
/**
* @name ConfirmExecutionDialog
* @description Dialog component to confirm the related resources before executing the action.
*/
export default {
name: 'ConfirmExecutionDialog',
emits: ['close'],
components: {
AsyncButton,
Banner,
Card,
},
props: {
/**
* @property resources to be deleted.
* @type {Resource[]} Array of the resource model's instance
*/
resources: {
type: Array,
required: true
}
},
data() {
return { errors: [] };
},
computed: {
...mapState('action-menu', ['modalData']),
warningMessageKey() {
return this.modalData.warningMessageKey;
},
names() {
return this.resources.map((obj) => obj.nameDisplay).slice(0, 5);
},
resourceNames() {
return this.names.reduce((res, name, i) => {
if (i >= 5) {
return res;
}
res += `<b>${ escapeHtml(name) }</b>`;
if (i === this.names.length - 1) {
res += this.plusMore;
} else {
res += i === this.resources.length - 2 ? ' and ' : ', ';
}
return res;
}, '');
},
plusMore() {
const remaining = this.resources.length - this.names.length;
return this.t('dialog.confirmExecution.andOthers', { count: remaining });
},
type() {
const types = new Set(this.resources.reduce((array, each) => {
array.push(each.type);
return array;
}, []));
if (types.size > 1) {
return this.t('generic.resource', { count: this.resources.length });
}
const schema = this.resources[0]?.schema;
if ( !schema ) {
return `resource${ this.resources.length === 1 ? '' : 's' }`;
}
return this.$store.getters['type-map/labelFor'](schema, this.resources.length);
},
protip() {
return this.t('dialog.confirmExecution.protip', { alternateLabel });
},
},
methods: {
escapeHtml,
close() {
this.errors = [];
this.$emit('close');
},
async apply(buttonDone) {
try {
for (const resource of this.resources) {
await resource.doActionGrowl(this.modalData.action, {});
}
buttonDone(true);
this.close();
} catch (e) {
this.errors = exceptionToErrorsArray(e);
buttonDone(false);
}
}
}
};
</script>
<template>
<Card :show-highlight-border="false">
<template #title>
<h4 class="text-default-text">
{{ t('dialog.confirmExecution.title') }}
</h4>
</template>
<template #body>
<div class="pl-10 pr-10">
<span
v-clean-html="t(warningMessageKey, { type, names: resourceNames }, true)"
></span>
<div class="text-info mt-20">
{{ protip }}
</div>
<Banner
v-for="(error, i) in errors"
:key="i"
/>
</div>
</template>
<template #actions>
<div class="actions">
<button
class="btn role-secondary"
@click="close"
>
{{ t('generic.cancel') }}
</button>
<AsyncButton
mode="apply"
class="btn bg-primary ml-10"
:disabled="applyDisabled"
@click="apply"
/>
</div>
</template>
</Card>
</template>
<style lang='scss' scoped>
.modal-container {
max-width: 400px;
}
.actions {
width: 100%;
text-align: right;
}
</style>

View File

@ -38,6 +38,21 @@ asyncButton:
success: Restarted
waiting: Restarting&hellip;
dialog:
confirmExecution:
title: Are you sure?
andOthers: |-
{count, plural,
=0 {}
=1 { and <b>one other </b>}
other { and <b>{count} other </b>}
}
protip: "Tip: Hold the {alternateLabel} key while clicking action to bypass this confirmation"
stop:
message: "Are you sure you want to continue stop the {type} {names}?"
pause:
message: "Are you sure you want to continue pause the {type} {names}?"
harvester:
productLabel: 'Harvester'
modal:

View File

@ -103,10 +103,12 @@ export default class VirtVm extends HarvesterResource {
return [
{
action: 'stopVM',
altAction: 'altStopVM',
enabled: !!this.actions?.stop,
icon: 'icon icon-close',
label: this.t('harvester.action.stop'),
bulkable: true
bulkable: true,
bulkAction: 'stopVM',
},
{
action: 'forceStop',
@ -117,6 +119,7 @@ export default class VirtVm extends HarvesterResource {
},
{
action: 'pauseVM',
altAction: 'altPauseVM',
enabled: !!this.actions?.pause,
icon: 'icon icon-pause',
label: this.t('harvester.action.pause')
@ -402,7 +405,16 @@ export default class VirtVm extends HarvesterResource {
return node?.id;
}
pauseVM() {
pauseVM(resources = this) {
this.$dispatch('promptModal', {
resources,
action: 'pause',
warningMessageKey: 'dialog.confirmExecution.pause.message',
component: 'ConfirmExecutionDialog'
});
}
altPauseVM() {
this.doActionGrowl('pause', {});
}
@ -417,7 +429,16 @@ export default class VirtVm extends HarvesterResource {
this.doActionGrowl('unpause', {});
}
stopVM() {
stopVM(resources = this) {
this.$dispatch('promptModal', {
resources,
action: 'stop',
warningMessageKey: 'dialog.confirmExecution.stop.message',
component: 'ConfirmExecutionDialog'
});
}
altStopVM() {
this.doActionGrowl('stop', {});
}