Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add confirmation pop-up for VM stop and pause actions (backport #94) #102

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
173 changes: 173 additions & 0 deletions pkg/harvester/dialog/ConfirmExecutionDialog.vue
Original file line number Diff line number Diff line change
@@ -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>
25 changes: 20 additions & 5 deletions pkg/harvester/l10n/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -861,14 +876,14 @@ harvester:
doc: Read the <a href="{url}" target="_blank">documentation</a> before starting the upgrade process. Ensure that you complete procedures that are relevant to your environment and the version you are upgrading to.
tip: Unmet system requirements and incorrectly performed procedures may cause complete upgrade failure and other issues that require manual workarounds.
moreNotes: For more details about the release notes, please visit -

schedule:
label: Virtual Machine Schedules
createTitle: Create Schedule
createButtonText: Create Schedule
scheduleType: Virtual Machine Schedule Type
cron: Cron Schedule
detail:
detail:
namespace: Namespace
sourceVM: Source Virtual Machine
tabs:
Expand All @@ -878,12 +893,12 @@ harvester:
message:
noSetting:
suffix: before creating a backup schedule
retain:
retain:
label: Retain
count: Count
tooltip: Number of up-to-date VM backups to retain. Maximum to 250, minimum to 2.
tooltip: Number of up-to-date VM backups to retain. Maximum to 250, minimum to 2.
maxFailure:
label: Max Failure
label: Max Failure
count: Count
tooltip: Max number of consecutive failed backups that could be tolerated. If reach this threshold, Harvester controller will suspend the schedule job. This value should less than retain count
virtualMachine:
Expand Down
43 changes: 32 additions & 11 deletions pkg/harvester/models/kubevirt.io.virtualmachine.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,13 @@ export default class VirtVm extends HarvesterResource {

return [
{
action: 'stopVM',
enabled: !!this.actions?.stop,
icon: 'icon icon-close',
label: this.t('harvester.action.stop'),
bulkable: true
action: 'stopVM',
altAction: 'altStopVM',
enabled: !!this.actions?.stop,
icon: 'icon icon-close',
label: this.t('harvester.action.stop'),
bulkable: true,
bulkAction: 'stopVM',
},
{
action: 'forceStop',
Expand All @@ -116,10 +118,11 @@ export default class VirtVm extends HarvesterResource {
bulkable: true
},
{
action: 'pauseVM',
enabled: !!this.actions?.pause,
icon: 'icon icon-pause',
label: this.t('harvester.action.pause')
action: 'pauseVM',
altAction: 'altPauseVM',
enabled: !!this.actions?.pause,
icon: 'icon icon-pause',
label: this.t('harvester.action.pause')
},
{
action: 'unpauseVM',
Expand Down Expand Up @@ -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', {});
}

Expand All @@ -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', {});
}

Expand Down
Loading