dashboard/components/dialog/RollbackWorkloadDialog.vue

215 lines
6.3 KiB
Vue

<script>
import AsyncButton from '@/components/AsyncButton';
import day from 'dayjs';
import Card from '@/components/Card';
import { exceptionToErrorsArray } from '@/utils/error';
import LabeledSelect from '@/components/form/LabeledSelect';
import Banner from '@/components/Banner';
import { WORKLOAD_TYPES } from '@/config/types';
import { diffFrom } from '@/utils/time';
import { mapGetters } from 'vuex';
export default {
components: {
Card,
AsyncButton,
LabeledSelect,
Banner
},
props: {
resources: {
type: Array,
required: true
}
},
data() {
return {
errors: [],
selectedRevision: null,
currentRevision: null,
revisions: [],
};
},
computed: {
...mapGetters({ t: 'i18n/t' }),
workload() {
return this.resources[0];
},
workloadName() {
return this.workload.metadata.name;
},
workloadNamespace() {
return this.workload.metadata.namespace;
},
currentRevisionNumber() {
return this.workload.metadata.annotations['deployment.kubernetes.io/revision'];
},
rollbackRequestBody() {
if (!this.selectedRevision) {
return null;
}
// Build the request body in the same format that kubectl
// uses to call the Kubernetes API to roll back a workload.
// To see an example request body, run:
// kubectl rollout undo deployment/[deployment name] --to-revision=[revision number] -v=8
const body = [
{
op: 'replace',
path: '/spec/template',
value: {
metadata: {
creationTimestamp: null,
labels: { 'workload.user.cattle.io/workloadselector': this.selectedRevision.spec.template.metadata.labels['workload.user.cattle.io/workloadselector'] }
},
spec: this.selectedRevision.spec.template.spec
}
}, {
op: 'replace',
path: '/metadata/annotations',
value: { 'deployment.kubernetes.io/revision': this.selectedRevision.metadata.annotations['deployment.kubernetes.io/revision'] }
}
];
return body;
}
},
fetch() {
// Fetch revisions of the current workload
this.$store.dispatch('cluster/findAll', { type: WORKLOAD_TYPES.REPLICA_SET })
.then(( response ) => {
const allReplicaSets = response;
const hasRelationshipWithCurrentWorkload = ( replicaSet ) => {
const relationshipsOfReplicaSet = replicaSet.metadata.relationships;
const revisionsOfCurrentWorkload = relationshipsOfReplicaSet.filter(( relationship ) => {
const isRevisionOfCurrentWorkload = relationship.fromId && relationship.fromId === `${ this.workloadNamespace }/${ this.workloadName }`;
return isRevisionOfCurrentWorkload;
});
return revisionsOfCurrentWorkload.length > 0;
};
const workloadRevisions = allReplicaSets.filter(( replicaSet ) => {
return hasRelationshipWithCurrentWorkload( replicaSet );
});
const revisionOptions = workloadRevisions.map( (revision ) => {
const isCurrentRevision = this.getRevisionNumber(revision) === this.currentRevisionNumber;
if (isCurrentRevision) {
this.currentRevision = revision;
}
return this.buildRevisionOption( revision );
});
this.revisions = revisionOptions;
})
.catch(( err ) => {
this.errors = exceptionToErrorsArray(err);
});
},
methods: {
close() {
this.$emit('close');
},
async save() {
try {
await this.workload.rollBackWorkload(this.workload, this.rollbackRequestBody);
this.close();
} catch (err) {
this.errors = exceptionToErrorsArray(err);
}
},
getRevisionNumber( revision ) {
return revision.metadata.annotations['deployment.kubernetes.io/revision'];
},
buildRevisionOption( revision ) {
const revisionNumber = this.getRevisionNumber(revision);
const isCurrentRevision = revisionNumber === this.currentRevisionNumber;
const now = day();
const createdDate = day(revision.metadata.creationTimestamp);
const revisionAge = diffFrom(createdDate, now, this.t);
const units = this.t(revisionAge.unitsKey, { count: revisionAge.label });
const currentLabel = this.t('promptRollback.currentLabel');
const optionLabel = this.t('promptRollback.revisionOption', {
revisionNumber,
revisionAge: revisionAge.label,
units,
currentLabel: isCurrentRevision ? currentLabel : ''
});
return {
label: optionLabel,
value: revision,
disabled: isCurrentRevision
};
},
getOptionLabel(option) {
return option.label;
},
}
};
</script>
<template>
<Card
class="prompt-rollback"
:show-highlight-border="false"
>
<h4 slot="title" class="text-default-text">
{{ t('promptRollback.modalTitle', { workloadName }, true) }}
</h4>
<div slot="body" class="pl-10 pr-10">
<Banner v-if="revisions.length === 1" color="info" :label="t('promptRollback.singleRevisionBanner')" />
<form>
<LabeledSelect
v-model="selectedRevision"
class="provider"
:label="t('promptRollback.dropdownTitle')"
:placeholder="t('promptRollback.placeholder')"
:options="revisions"
:get-option-label="getOptionLabel"
/>
</form>
<Banner v-for="(error, i) in errors" :key="i" class="" color="error" :label="error" />
</div>
<div slot="actions" class="buttons right-align">
<button class="btn role-secondary mr-10" @click="close">
{{ t('generic.cancel') }}
</button>
<AsyncButton
:action-label="t('asyncButton.rollback.action')"
:disabled="!selectedRevision"
get-option-label="getOptionLabel"
:right-align="true"
@click="save"
/>
</div>
</Card>
</template>
<style lang='scss' scoped>
.prompt-rollback {
margin: 0;
}
.right-align {
margin-right: 0;
margin-left: auto;
}
.bottom {
flex-direction: column;
flex: 1;
.banner {
margin-top: 0
}
.buttons {
display: flex;
justify-content: flex-end;
width: 100%;
}
}
</style>