Add Save As RKE Template cluster action

This commit is contained in:
Neil MacDougall 2021-05-28 12:07:33 +01:00
parent 1f0e95beff
commit f02e35c76c
8 changed files with 269 additions and 3 deletions

View File

@ -77,13 +77,15 @@ button,
.role-secondary {
background: transparent;
color: var(--primary) !important;
border: solid thin var(--primary);
border: solid 1px var(--primary);
line-height: $btn-height - 2px;
}
.role-tertiary {
background: var(--accent-btn);
border: solid thin var(--primary);
border: solid 1px var(--primary);
color: var(--primary);
line-height: $btn-height - 2px;
}
.role-link {

View File

@ -2455,6 +2455,12 @@ promptRemove:
promptRestore:
title: Restore Snapshot
promptSaveAsRKETemplate:
title: Create RKE Template from {cluster}
name: Cluster Template Name
description: Create a new RKE cluster template and initial revision from the current cluster configuration.
warning: This will modify the current cluster, setting up the cluster to use the newly created cluster template and revision.
rancherAlertingDrivers:
msTeams: Enable Microsoft Teams
sms: Enable SMS

106
components/PromptModal.vue Normal file
View File

@ -0,0 +1,106 @@
<script>
import { mapState, mapGetters } from 'vuex';
import { isArray } from '@/utils/array';
import AsyncButton from '@/components/AsyncButton';
import Card from '@/components/Card';
import Banner from '@/components/Banner';
import SaveAsRKETemplateDialog from '@/components/SaveAsRKETemplateDialog';
export default {
components: {
Card,
AsyncButton,
Banner,
// Need to include all of the dialogs here
// Would be nice if this could be done dynamically
SaveAsRKETemplateDialog,
},
data() {
return {
errors: [],
labels: {},
restoreMode: 'all',
moveTo: this.workspace,
opened: false,
allWorkspaces: [],
};
},
computed: {
...mapState('action-menu', ['showModal', 'modalData']),
...mapGetters({ t: 'i18n/t' }),
...mapGetters(['currentCluster']),
dynamic() {
return this.modalComponent;
},
resources() {
let resources = this.modalData?.resources;
if (!isArray(resources)) {
resources = [resources];
}
return resources || [];
},
component() {
return this.modalData?.component;
},
},
watch: {
showModal(show) {
if (show) {
this.opened = true;
this.$modal.show('promptModal');
} else {
this.opened = false;
this.$modal.hide('promptModal');
}
},
},
methods: {
close() {
if (!this.opened) {
return;
}
this.errors = [];
this.labels = {};
this.$store.commit('action-menu/togglePromptModal');
},
modalClosed() {
this.close();
}
}
};
</script>
<template>
<modal
class="promptModal-modal"
name="promptModal"
styles="background-color: var(--nav-bg); border-radius: var(--border-radius); max-height: 100vh;"
height="auto"
:scrollable="true"
@closed="modalClosed()"
>
<component v-if="opened && component" :is="component" @close="close()" :resources="resources"/>
</modal>
</template>
<style lang='scss'>
.promptModal-modal {
border-radius: var(--border-radius);
overflow: scroll;
max-height: 100vh;
& ::-webkit-scrollbar-corner {
background: rgba(0,0,0,0);
}
}
</style>

View File

@ -0,0 +1,101 @@
<script>
import AsyncButton from '@/components/AsyncButton';
import Card from '@/components/Card';
import Banner from '@/components/Banner';
import LabeledInput from '@/components/form/LabeledInput';
import { exceptionToErrorsArray } from '@/utils/error';
const DEFAULT_REVISION = 'v1';
export default {
components: {
Card,
AsyncButton,
Banner,
LabeledInput,
},
props: {
resources: {
type: Array,
required: true
}
},
data() {
return { errors: [], name: '' };
},
computed: {
cluster() {
if (this.resources?.length === 1) {
const c = this.resources[0];
return c;
}
return {};
},
},
methods: {
close() {
this.$emit('close');
},
async apply(buttonDone) {
try {
await this.$store.dispatch('rancher/request', {
url: `/v3/clusters/${ escape(this.cluster.name) }?action=saveAsTemplate`,
method: 'post',
data: {
clusterTemplateName: this.name,
clusterTemplateRevisionName: DEFAULT_REVISION
},
});
buttonDone(true);
this.close();
} catch (err) {
this.errors = exceptionToErrorsArray(err);
buttonDone(false);
}
}
}
};
</script>
<template>
<Card class="prompt-restore" :show-highlight-border="false">
<h4 slot="title" class="text-default-text" v-html="t('promptSaveAsRKETemplate.title', { cluster: cluster.displayName }, true)" />
<div slot="body" class="pl-10 pr-10">
<form>
<p class="pt-10 pb-10">{{ t('promptSaveAsRKETemplate.description') }}</P>
<Banner color="warning" label-key="promptSaveAsRKETemplate.warning" />
<LabeledInput
v-model="name"
:label="t('promptSaveAsRKETemplate.name')"
:required="true"
/>
</form>
</div>
<div slot="actions">
<button class="btn role-secondary mr-10" @click="close">
{{ t('generic.cancel') }}
</button>
<AsyncButton
mode="create"
:disabled="name.length <= 0"
@click="apply"
/>
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" />
</div>
</Card>
</template>
<style lang='scss' scoped>
.prompt-restore {
margin: 0;
}
</style>

View File

@ -8,6 +8,7 @@ import ActionMenu from '@/components/ActionMenu';
import WindowManager from '@/components/nav/WindowManager';
import PromptRemove from '@/components/PromptRemove';
import PromptRestore from '@/components/PromptRestore';
import PromptModal from '@/components/PromptModal';
import AssignTo from '@/components/AssignTo';
import Group from '@/components/nav/Group';
import Header from '@/components/nav/Header';
@ -32,6 +33,7 @@ export default {
PromptRemove,
PromptRestore,
AssignTo,
PromptModal,
Header,
ActionMenu,
Group,
@ -483,6 +485,7 @@ export default {
<PromptRemove />
<PromptRestore />
<AssignTo />
<PromptModal />
<button v-if="dev" v-shortkey.once="['shift','l']" class="hide" @shortkey="toggleNoneLocale()" />
<button v-if="dev" v-shortkey.once="['shift','t']" class="hide" @shortkey="toggleTheme()" />
<button v-shortkey.once="['f8']" class="hide" @shortkey="wheresMyDebugger()" />

View File

@ -6,6 +6,8 @@ import { ucFirst } from '@/utils/string';
export const DEFAULT_WORKSPACE = 'fleet-default';
const SAVE_AS_RKE_TEMPLATE_ACTION = '"saveAsTemplate';
export default {
details() {
const out = [
@ -45,6 +47,20 @@ export default {
enabled: this.$rootGetters['isRancher'],
});
const canSaveAsTemplate = this.isRke1 && this.mgmt.status.driver === 'rancherKubernetesEngine' && !this.mgmt.spec.clusterTemplateName;
if (canSaveAsTemplate) {
insertAt(out, 2, { divider: true });
insertAt(out, 3, {
action: 'saveAsRKETemplate',
label: 'Save as RKE Template',
icon: 'icon icon-folder',
bulkable: true,
enabled: this.$rootGetters['isRancher'],
});
}
return out;
},
@ -284,4 +300,13 @@ export default {
return proxyFor(this.$ctx, x);
});
},
saveAsRKETemplate() {
return (resources = this) => {
this.$dispatch('promptModal', {
resources,
component: 'SaveAsRKETemplateDialog'
});
};
}
};

View File

@ -399,6 +399,10 @@ export default {
commit('action-menu/toggleAssignTo', resources, { root: true });
},
promptModal({ commit, state }, data ) {
commit('action-menu/togglePromptModal', data, { root: true });
},
async resourceAction({ getters, dispatch }, {
resource, actionName, body, opt,
}) {

View File

@ -13,11 +13,13 @@ export const state = function() {
showPromptRestore: false,
showAssignTo: false,
showPromptUpdate: false,
showModal: false,
toMove: [],
toRemove: [],
toRestore: [],
toAssign: [],
toUpdate: [],
modalData: {},
};
};
@ -135,7 +137,24 @@ export const mutations = {
}
state.toUpdate = resources;
}
},
togglePromptModal(state, data) {
console.log('Toggle prompt modal');
console.log(data);
if (!data) {
// Clearing the resources also hides the prompt
state.showModal = false;
} else {
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> SHOW >>> ');
console.log(data.resources);
state.showModal = true;
}
state.modalData = data;
}
};
export const actions = {