Skip to content

Commit

Permalink
Add VM schedule pages
Browse files Browse the repository at this point in the history
- add VM schedule list/edit/details pages
- add VM schedule column in vmbackup/vmsnapshot list pages
- add action menu for VM
- show volume backup error message in backup/snapshot detail volume tab

Signed-off-by: andy.lee <andy.lee@suse.com>
  • Loading branch information
a110605 committed Aug 14, 2024
1 parent 3c11f43 commit 9c9a821
Show file tree
Hide file tree
Showing 30 changed files with 1,547 additions and 83 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@
"browser-env": "3.3.0",
"cookie": "0.5.0",
"cookie-universal-nuxt": "2.1.4",
"cron-validator": "1.2.0",
"cronstrue": "1.95.0",
"cron-validator": "1.3.1",
"cronstrue": "2.50.0",
"cross-env": "6.0.3",
"d3": "7.3.0",
"d3-selection": "1.4.1",
Expand Down
119 changes: 119 additions & 0 deletions pkg/harvester/components/FilterVMSchedule.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<script>
import { RadioGroup } from '@components/Form/Radio';
export default {
name: 'HarvesterFilterVMSchedule',
components: { RadioGroup },
props: {
rows: {
type: Array,
required: true,
},
},
data() {
return { selected: '' };
},
computed: {
scheduleOptions() {
const options = this.rows.map(r => r.sourceSchedule).filter(r => r);
return Array.from(new Set(options));
},
},
methods: {
onSelect(selected) {
this.selected = selected;
this.filterRows();
},
remove() {
this.selected = '';
this.filterRows();
},
filterRows() {
if (!this.selected) {
this.$emit('change-rows', this.rows);
return;
}
const filteredRows = this.rows.filter(row => row.sourceSchedule === this.selected);
this.$emit('change-rows', filteredRows, this.selected);
}
},
watch: {
rows: {
deep: true,
immediate: false,
handler() {
this.filterRows();
}
}
}
};
</script>

<template>
<div class="vm-schedule-filter">
<template>
<span v-if="selected" class="banner-item bg-warning">
{{ t('harvester.tableHeaders.vmSchedule') }}{{ selected ? ` = ${selected}`: '' }}<i class="icon icon-close ml-5" @click="remove" />
</span>
</template>

<v-popover
trigger="click"
placement="bottom-end"
>
<slot name="header">
<button ref="actionDropDown" class="btn bg-primary mr-10">
<slot name="title">
{{ t('harvester.fields.filterSchedule') }}
</slot>
</button>
</slot>
<template slot="popover">
<div class="filter-popup">
<RadioGroup
v-model="selected"
class="mr-10 ml-10"
name="model"
:options="scheduleOptions"
:labels="scheduleOptions"
@input="onSelect"
/>
</div>
</template>
</v-popover>
</div>
</template>

<style lang="scss" scoped>
.vm-schedule-filter {
display: inline-block;
.banner-item {
display: inline-block;
font-size: 16px;
margin-right: 10px;
padding: 6px;
border-radius: 2px;
i {
cursor: pointer;
vertical-align: middle;
}
}
}
.filter-popup {
width: max-content;
}
</style>
14 changes: 14 additions & 0 deletions pkg/harvester/config/harvester.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ export function init($plugin, store) {

basicType(
[
HCI.SCHEDULE_VM_BACKUP,
HCI.BACKUP,
HCI.SNAPSHOT,
HCI.VM_SNAPSHOT,
Expand Down Expand Up @@ -464,6 +465,19 @@ export function init($plugin, store) {
exact: false
});

configureType(HCI.SCHEDULE_VM_BACKUP, { showListMasthead: false, showConfigView: false });
virtualType({
labelKey: 'harvester.schedule.label',
name: HCI.SCHEDULE_VM_BACKUP,
namespaced: true,
weight: 201,
route: {
name: `${ PRODUCT_NAME }-c-cluster-resource`,
params: { resource: HCI.SCHEDULE_VM_BACKUP }
},
exact: false
});

configureType(HCI.BACKUP, { showListMasthead: false, showConfigView: false });
virtualType({
labelKey: 'harvester.backup.label',
Expand Down
36 changes: 36 additions & 0 deletions pkg/harvester/config/table-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,39 @@ export const SNAPSHOT_TARGET_VOLUME = {
sort: 'spec.source.persistentVolumeClaimName',
formatter: 'SnapshotTargetVolume',
};

// The column of cron expression volume on VM schedules list page
export const VM_SCHEDULE_CRON = {
name: 'CronExpression',
labelKey: 'harvester.tableHeaders.cronExpression',
value: 'spec.cron',
align: 'center',
sort: 'spec.cron',
};

// The column of retain on VM schedules list page
export const VM_SCHEDULE_RETAIN = {
name: 'Retain',
labelKey: 'harvester.tableHeaders.retain',
value: 'spec.retain',
sort: 'spec.retain',
align: 'center',
};

// The column of maxFailure on VM schedules list page
export const VM_SCHEDULE_MAX_FAILURE = {
name: 'MaxFailure',
labelKey: 'harvester.tableHeaders.maxFailure',
value: 'spec.maxFailure',
sort: 'spec.maxFailure',
align: 'center',
};

// The column of type on VM schedules list page
export const VM_SCHEDULE_TYPE = {
name: 'Type',
labelKey: 'harvester.tableHeaders.scheduleType',
value: 'spec.vmbackup.type',
sort: 'spec.vmbackup.type',
align: 'center',
};
129 changes: 129 additions & 0 deletions pkg/harvester/detail/harvesterhci.io.schedulevmbackup/BackupList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<script>
import ResourceTable from '@shell/components/ResourceTable';
import { STATE, NAME, AGE } from '@shell/config/table-headers';
import { allSettled } from '../../utils/promise';
import { BACKUP_TYPE } from '../../config/types';
import { HCI } from '../../types';
export default {
name: 'BackupList',
components: { ResourceTable },
props: {
id: {
type: String,
required: true,
},
},
async fetch() {
const inStore = this.$store.getters['currentProduct'].inStore;
const hash = await allSettled({ backups: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.BACKUP }) });
this.rows = hash.backups;
},
data() {
const inStore = this.$store.getters['currentProduct'].inStore;
const schema = this.$store.getters[`${ inStore }/schemaFor`](HCI.BACKUP);
return {
rows: [],
schema
};
},
computed: {
headers() {
const cols = [
STATE,
{
...NAME,
width: 400
},
{
name: 'targetVM',
labelKey: 'tableHeaders.targetVm',
value: 'attachVM',
align: 'left',
sort: 'attachVM',
formatter: 'AttachVMWithName'
},
{
name: 'backupTarget',
labelKey: 'tableHeaders.backupTarget',
value: 'backupTarget',
sort: 'backupTarget',
align: 'left',
formatter: 'HarvesterBackupTargetValidation'
},
{
name: 'readyToUse',
labelKey: 'tableHeaders.readyToUse',
value: 'status.readyToUse',
sort: 'status.readyToUse',
align: 'center',
formatter: 'Checked',
},
];
if (this.hasBackupProgresses) {
cols.push({
name: 'backupProgress',
labelKey: 'tableHeaders.progress',
value: 'backupProgress',
sort: 'backupProgress',
align: 'left',
formatter: 'HarvesterBackupProgressBar',
});
}
cols.push(AGE);
return cols;
},
hasBackupProgresses() {
return !!this.rows.find(R => R.status?.progress !== undefined);
},
filteredRows() {
let r = this.rows.filter(row => row.spec?.type === BACKUP_TYPE.BACKUP);
if (this.id) {
r = r.filter(backup => backup.metadata.annotations?.['harvesterhci.io/svmbackupId'] === this.id);
}
return r;
},
},
};
</script>
<template>
<ResourceTable
v-bind="$attrs"
:headers="headers"
:groupable="false"
:rows="filteredRows"
:schema="schema"
key-field="_key"
default-sort-by="age"
v-on="$listeners"
>
<template #col:name="{row}">
<td>
<span>
<n-link
v-if="row?.status?.source"
:to="row.detailLocation"
>
{{ row.nameDisplay }}
</n-link>
<span v-else>
{{ row.nameDisplay }}
</span>
</span>
</td>
</template>
</ResourceTable>
</template>
Loading

0 comments on commit 9c9a821

Please sign in to comment.