mirror of https://github.com/rancher/dashboard.git
Merge pull request #1243 from mantis-toboggan-md/backup-restore-op-deployment
Backup restore op deployment
This commit is contained in:
commit
d99ec480d0
|
|
@ -69,7 +69,7 @@ $selected: rgba($primary, .5);
|
|||
--header-btn-bg : #317DB0;
|
||||
--header-btn-text : white;
|
||||
--header-height : 50px;
|
||||
--nav-width : 230px;
|
||||
--nav-width : 250px;
|
||||
--nav-bg : #{$lighter};
|
||||
--nav-active : #{rgba($primary, 0.3)};
|
||||
--footer-bg : transparent;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ nav:
|
|||
|
||||
product:
|
||||
apps: Apps & Marketplace
|
||||
backup: Backup Restore Operator
|
||||
backup: Backup & Restore Operator
|
||||
cis: CIS Benchmark
|
||||
ecm: Cluster Manager
|
||||
explorer: Cluster Explorer
|
||||
|
|
@ -137,7 +137,20 @@ asyncButton:
|
|||
|
||||
backupRestoreOperator:
|
||||
backupFilename: Backup Filename
|
||||
prune:
|
||||
deployment:
|
||||
rancherNamespace: Rancher ResourceSet Namespace
|
||||
storage:
|
||||
tip: Configure a storage location where all backups are saved by default. You will have the option to override this with each backup, but will be limited to using an S3-compatible object store.
|
||||
storageClass: Storage Class
|
||||
persistentVolume: Persistent Volume
|
||||
label: Default Storage Location
|
||||
options:
|
||||
s3: Use an S3-compatible object store
|
||||
defaultStorageClass: 'Use the default storage class ({name})'
|
||||
pickSC: Use an existing storage class
|
||||
pickPV: Use an existing persistent volume
|
||||
size: Size
|
||||
prune:
|
||||
label: Prune
|
||||
tip: Delete all resources that match the ResourceSet used, but are not present in the backup. (Recommended)
|
||||
encryption: Encryption
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
<script>
|
||||
import LabeledInput from '@/components/form/LabeledInput';
|
||||
import Checkbox from '@/components/form/Checkbox';
|
||||
import FileSelector from '@/components/form/FileSelector';
|
||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||
import { mapGetters } from 'vuex';
|
||||
export default {
|
||||
components: {
|
||||
LabeledInput,
|
||||
Checkbox,
|
||||
FileSelector,
|
||||
LabeledSelect,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
|
||||
secrets: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
credentialSecret: {
|
||||
get() {
|
||||
const { credentialSecretName, credentialSecretNamespace } = this.value;
|
||||
|
||||
return { metadata: { name: credentialSecretName, namespace: credentialSecretNamespace } };
|
||||
},
|
||||
|
||||
set(neu) {
|
||||
const { name, namespace } = neu.metadata;
|
||||
|
||||
this.$set(this.value, 'credentialSecretName', name);
|
||||
this.$set(this.value, 'credentialSecretNamespace', namespace);
|
||||
}
|
||||
},
|
||||
...mapGetters({ t: 'i18n/t' })
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model="credentialSecret"
|
||||
:get-option-label="opt=>opt.metadata.name || ''"
|
||||
:mode="mode"
|
||||
:options="secrets"
|
||||
:label="t('backupRestoreOperator.s3.credentialSecretName')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="value.bucketName" :mode="mode" :label="t('backupRestoreOperator.s3.bucketName')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="value.region" :mode="mode" :label="t('backupRestoreOperator.s3.region')" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="value.folder" :mode="mode" :label="t('backupRestoreOperator.s3.folder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="value.endpoint" :mode="mode" :label="t('backupRestoreOperator.s3.endpoint')" />
|
||||
<Checkbox v-model="value.insecureTLSSkipVerify" class="mt-10" :mode="mode" :label="t('backupRestoreOperator.s3.insecureTLSSkipVerify')" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="value.endpointCA" :mode="mode" type="multiline" :label="t('backupRestoreOperator.s3.endpointCA')" />
|
||||
<FileSelector v-if="mode!=='view'" class="btn btn-sm role-primary mt-5" :mode="mode" :label="t('generic.readFromFile')" @selected="e=>$set(s3, 'endpointCA', e)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
<script>
|
||||
import Tab from '@/components/Tabbed/Tab';
|
||||
import S3 from '@/chart/backup-restore-operator/S3';
|
||||
import RadioGroup from '@/components/form/RadioGroup';
|
||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||
import LabeledInput from '@/components/form/LabeledInput';
|
||||
import Banner from '@/components/Banner';
|
||||
import { get } from '@/utils/object';
|
||||
import { allHash } from '@/utils/promise';
|
||||
import { STORAGE_CLASS, SECRET, PV } from '@/config/types';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { STORAGE } from '@/config/labels-annotations';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Tab,
|
||||
RadioGroup,
|
||||
S3,
|
||||
LabeledInput,
|
||||
LabeledSelect,
|
||||
Banner
|
||||
},
|
||||
|
||||
hasTabs: true,
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
}
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const hash = await allHash({
|
||||
storageClasses: this.$store.dispatch('cluster/findAll', { type: STORAGE_CLASS }),
|
||||
persistentVolumes: this.$store.dispatch('cluster/findAll', { type: PV }),
|
||||
secrets: this.$store.dispatch('cluster/findAll', { type: SECRET }),
|
||||
});
|
||||
|
||||
this.secrets = hash.secrets;
|
||||
this.storageClasses = hash.storageClasses;
|
||||
this.persistentVolumes = hash.persistentVolumes;
|
||||
},
|
||||
|
||||
data() {
|
||||
const storageSource = this.mode === 'create' ? 's3' : this.getStorageSource(this.value);
|
||||
|
||||
return {
|
||||
storageSource, secrets: [], storageClasses: [], persistentVolumes: []
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
defaultStorageClass() {
|
||||
return this.storageClasses.filter(sc => sc.metadata.annotations[STORAGE.DEFAULT_STORAGE_CLASS])[0];
|
||||
},
|
||||
|
||||
storageClassNames() {
|
||||
return this.storageClasses.reduce((total, each) => {
|
||||
total.push(each.id);
|
||||
|
||||
return total;
|
||||
}, []);
|
||||
},
|
||||
|
||||
unboundPVs() {
|
||||
return this.persistentVolumes.reduce((total, each) => {
|
||||
if (each?.status?.phase !== 'bound') {
|
||||
total.push(each.id);
|
||||
}
|
||||
|
||||
return total;
|
||||
}, []);
|
||||
},
|
||||
|
||||
radioOptions() {
|
||||
const options = ['s3', 'pickSC', 'pickPV'];
|
||||
const labels = [
|
||||
this.t('backupRestoreOperator.deployment.storage.options.s3'),
|
||||
this.t('backupRestoreOperator.deployment.storage.options.pickSC'),
|
||||
this.t('backupRestoreOperator.deployment.storage.options.pickPV'),
|
||||
];
|
||||
|
||||
if (this.defaultStorageClass) {
|
||||
options.splice(1, 0, 'defaultSC');
|
||||
labels.splice(1, 0, this.t('backupRestoreOperator.deployment.storage.options.defaultStorageClass', { name: this.defaultStorageClass.name }));
|
||||
}
|
||||
|
||||
return { options, labels };
|
||||
},
|
||||
|
||||
...mapGetters({ t: 'i18n/t' })
|
||||
|
||||
},
|
||||
|
||||
watch: {
|
||||
storageSource(neu) {
|
||||
switch (neu) {
|
||||
case 'defaultSC':
|
||||
this.value.persistence.enabled = true;
|
||||
this.value.s3.enabled = false;
|
||||
this.value.persistence.storageClass = this.defaultStorageClass.id;
|
||||
break;
|
||||
case 'pickSC':
|
||||
this.value.persistence.enabled = true;
|
||||
delete this.value.persistence.volumeName;
|
||||
this.value.s3.enabled = false;
|
||||
break;
|
||||
case 'pickPV':
|
||||
this.value.persistence.enabled = true;
|
||||
this.value.persistence.storageClass = '-';
|
||||
this.value.s3.enabled = false;
|
||||
break;
|
||||
case 's3':
|
||||
this.value.persistence.enabled = false;
|
||||
this.value.s3.enabled = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getStorageSource() {
|
||||
if (get(this.value, 's3.enabled')) {
|
||||
return 's3';
|
||||
} if (get(this.value, 'persistence.enabled')) {
|
||||
if (this.value.persistence.storageClass) {
|
||||
if (this.value.persistence.storageClass === this.defaultSC.metadata.name) {
|
||||
return 'defaultSC';
|
||||
}
|
||||
|
||||
return 'pickSC';
|
||||
}
|
||||
if (this.value.persistence.volumeName) {
|
||||
return 'pickPV';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Tab label="Chart Options" name="chartOptions">
|
||||
<Banner color="info" :label="t('backupRestoreOperator.deployment.storage.tip')" />
|
||||
<RadioGroup
|
||||
v-model="storageSource"
|
||||
name="storageSource"
|
||||
:label="t('backupRestoreOperator.deployment.storage.label')"
|
||||
class="mb-10"
|
||||
:options="radioOptions.options"
|
||||
:labels="radioOptions.labels"
|
||||
/>
|
||||
<S3 v-if="storageSource==='s3'" :value="value.s3" :secrets="secrets" :mode="mode" />
|
||||
<template v-else>
|
||||
<div class="row">
|
||||
<div v-if="storageSource === 'pickSC'" class="col span-6">
|
||||
<LabeledSelect
|
||||
:key="storageSource"
|
||||
v-model="value.persistence.storageClass"
|
||||
:label="t('backupRestoreOperator.deployment.storage.storageClass')"
|
||||
:mode="mode"
|
||||
:options="storageClassNames"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="storageSource === 'pickPV'" class="col span-6">
|
||||
<LabeledSelect
|
||||
:key="storageSource"
|
||||
:value="value.persistence.volumeName"
|
||||
:label="t('backupRestoreOperator.deployment.storage.persistentVolume')"
|
||||
:mode="mode"
|
||||
:options="unboundPVs"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="value.persistence.size" :mode="mode" :label="t('backupRestoreOperator.deployment.size')" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Tab>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
::v-deep .radio-group.label>SPAN {
|
||||
font-size: 1em;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -200,7 +200,7 @@ export default {
|
|||
:options="options"
|
||||
:placeholder="placeholder"
|
||||
:reduce="x => reduce(x)"
|
||||
:value="value ? value : ' '"
|
||||
:value="value ? value : ''"
|
||||
@input="e=>$emit('input', e)"
|
||||
@search:blur="onBlur"
|
||||
@search:focus="onFocus"
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="label" class="radio-group label" :class="{'view':isView}">
|
||||
<div v-if="label" class="radio-group label">
|
||||
<span class="text-label">
|
||||
{{ label }}
|
||||
</span>
|
||||
|
|
@ -154,7 +154,7 @@ export default {
|
|||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.radio-group.label.view{
|
||||
color: var(--input-label);
|
||||
.radio-group.label{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ export const RIO = { STACK: 'rio.cattle.io/stack' };
|
|||
|
||||
export const CERTMANAGER = { ISSUER: 'cert-manager.io/issuer-name' };
|
||||
|
||||
export const STORAGE = { DEFAULT_STORAGE_CLASS: 'storageclass.kubernetes.io/is-default-class' };
|
||||
|
||||
export const NODE_ROLES = {
|
||||
CONTROL_PLANE: 'node-role.kubernetes.io/controlplane',
|
||||
WORKER: 'node-role.kubernetes.io/worker',
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import CruResource from '@/components/CruResource';
|
|||
import createEditView from '@/mixins/create-edit-view';
|
||||
import LabeledInput from '@/components/form/LabeledInput';
|
||||
import UnitInput from '@/components/form/UnitInput';
|
||||
import Checkbox from '@/components/form/Checkbox';
|
||||
import FileSelector from '@/components/form/FileSelector';
|
||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||
import Banner from '@/components/Banner';
|
||||
import RadioGroup from '@/components/form/RadioGroup';
|
||||
import NameNsDescription from '@/components/form/NameNsDescription';
|
||||
import Loading from '@/components/Loading';
|
||||
import S3 from '@/chart/backup-restore-operator/S3';
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
import { SECRET, BACKUP_RESTORE, CATALOG } from '@/config/types';
|
||||
import { allHash } from '@/utils/promise';
|
||||
|
|
@ -22,13 +22,12 @@ export default {
|
|||
CruResource,
|
||||
UnitInput,
|
||||
LabeledInput,
|
||||
Checkbox,
|
||||
FileSelector,
|
||||
LabeledSelect,
|
||||
RadioGroup,
|
||||
NameNsDescription,
|
||||
Banner,
|
||||
Loading
|
||||
Loading,
|
||||
S3
|
||||
},
|
||||
mixins: [createEditView],
|
||||
|
||||
|
|
@ -77,20 +76,6 @@ export default {
|
|||
return BRORelease ? BRORelease.spec.namespace : '';
|
||||
},
|
||||
|
||||
credentialSecret: {
|
||||
get() {
|
||||
const { credentialSecretName, credentialSecretNamespace } = this.s3;
|
||||
|
||||
return { metadata: { name: credentialSecretName, namespace: credentialSecretNamespace } };
|
||||
},
|
||||
set(neu) {
|
||||
const { name, namespace } = neu.metadata;
|
||||
|
||||
this.$set(this.s3, 'credentialSecretName', name);
|
||||
this.$set(this.s3, 'credentialSecretNamespace', namespace);
|
||||
}
|
||||
},
|
||||
|
||||
encryptionSecretNames() {
|
||||
return this.allSecrets.filter(secret => !!secret.data['encryption-provider-config.yaml'] && secret.metadata.namespace === this.chartNamespace).map(secret => secret.metadata.name);
|
||||
},
|
||||
|
|
@ -137,7 +122,7 @@ export default {
|
|||
if (neu === 'useDefault') {
|
||||
delete this.value.spec.storageLocation;
|
||||
} else {
|
||||
this.$set(this.value.spec, 'storageLocation', this.s3);
|
||||
this.$set(this.value.spec, 'storageLocation', { s3: this.s3 });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -224,32 +209,7 @@ export default {
|
|||
</div>
|
||||
|
||||
<template v-if="storageSource !== 'useDefault'">
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect v-model="credentialSecret" :get-option-label="opt=>opt.metadata.name || ''" :mode="mode" :options="allSecrets" :label="t('backupRestoreOperator.s3.credentialSecretName')" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.bucketName" :mode="mode" :label="t('backupRestoreOperator.s3.bucketName')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.region" :mode="mode" :label="t('backupRestoreOperator.s3.region')" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.folder" :mode="mode" :label="t('backupRestoreOperator.s3.folder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.endpoint" :mode="mode" :label="t('backupRestoreOperator.s3.endpoint')" />
|
||||
<Checkbox v-model="s3.insecureTLSSkipVerify" class="mt-10" :mode="mode" :label="t('backupRestoreOperator.s3.insecureTLSSkipVerify')" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.endpointCA" :mode="mode" type="multiline" :label="t('backupRestoreOperator.s3.endpointCA')" />
|
||||
<FileSelector v-if="mode!=='view'" class="btn btn-sm role-primary mt-5" :mode="mode" :label="t('generic.readFromFile')" @selected="e=>$set(s3, 'endpointCA', e)" />
|
||||
</div>
|
||||
</div>
|
||||
<S3 :value="s3" :secrets="allSecrets" :mode="mode" />
|
||||
</template>
|
||||
</template>
|
||||
<Banner v-else color="error">
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import createEditView from '@/mixins/create-edit-view';
|
|||
import LabeledInput from '@/components/form/LabeledInput';
|
||||
import UnitInput from '@/components/form/UnitInput';
|
||||
import Checkbox from '@/components/form/Checkbox';
|
||||
import FileSelector from '@/components/form/FileSelector';
|
||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||
import Loading from '@/components/Loading';
|
||||
import RadioGroup from '@/components/form/RadioGroup';
|
||||
import S3 from '@/chart/backup-restore-operator/S3';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { SECRET, BACKUP_RESTORE, CATALOG } from '@/config/types';
|
||||
import { allHash } from '@/utils/promise';
|
||||
|
|
@ -19,7 +19,7 @@ export default {
|
|||
UnitInput,
|
||||
LabeledInput,
|
||||
Checkbox,
|
||||
FileSelector,
|
||||
S3,
|
||||
LabeledSelect,
|
||||
Loading,
|
||||
RadioGroup
|
||||
|
|
@ -79,21 +79,6 @@ export default {
|
|||
return BRORelease ? BRORelease.spec.namespace : '';
|
||||
},
|
||||
|
||||
credentialSecret: {
|
||||
get() {
|
||||
const { credentialSecretName, credentialSecretNamespace } = this.s3;
|
||||
|
||||
return { metadata: { name: credentialSecretName, namespace: credentialSecretNamespace } };
|
||||
},
|
||||
|
||||
set(neu) {
|
||||
const { name, namespace } = neu.metadata;
|
||||
|
||||
this.$set(this.s3, 'credentialSecretName', name);
|
||||
this.$set(this.s3, 'credentialSecretNamespace', namespace);
|
||||
}
|
||||
},
|
||||
|
||||
encryptionSecretNames() {
|
||||
return this.allSecrets.filter(secret => !!secret.data['encryption-provider-config.yaml'] && secret.metadata.namespace === this.chartNamespace).map(secret => secret.metadata.name);
|
||||
},
|
||||
|
|
@ -177,38 +162,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
<template v-if="storageSource === 'configureS3'">
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model="credentialSecret"
|
||||
:get-option-label="opt=>opt.metadata.name || ''"
|
||||
:mode="mode"
|
||||
:options="allSecrets"
|
||||
:label="t('backupRestoreOperator.s3.credentialSecretName')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.bucketName" :mode="mode" :label="t('backupRestoreOperator.s3.bucketName')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.region" :mode="mode" :label="t('backupRestoreOperator.s3.region')" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.folder" :mode="mode" :label="t('backupRestoreOperator.s3.folder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-10">
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.endpoint" :mode="mode" :label="t('backupRestoreOperator.s3.endpoint')" />
|
||||
<Checkbox v-model="s3.insecureTLSSkipVerify" class="mt-10" :mode="mode" :label="t('backupRestoreOperator.s3.insecureTLSSkipVerify')" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput v-model="s3.endpointCA" :mode="mode" type="multiline" :label="t('backupRestoreOperator.s3.endpointCA')" />
|
||||
<FileSelector v-if="mode!=='view'" class="btn btn-sm role-primary mt-5" :mode="mode" :label="t('generic.readFromFile')" @selected="e=>$set(s3, 'endpointCA', e)" />
|
||||
</div>
|
||||
</div>
|
||||
<S3 v-model="s3" :mode="mode" :secrets="allSecrets" />
|
||||
</template>
|
||||
<div v-else-if="storageSource==='useBackup'" class="row mb-10">
|
||||
<div class="col span-6">
|
||||
|
|
|
|||
|
|
@ -507,7 +507,6 @@ export default {
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await this.repo.doAction((isUpgrade ? 'upgrade' : 'install'), input);
|
||||
|
||||
this.operation = await this.$store.dispatch('cluster/find', {
|
||||
|
|
|
|||
Loading…
Reference in New Issue