diff --git a/@xen-orchestra/web/src/components/site/dashboard/Backups.vue b/@xen-orchestra/web/src/components/site/dashboard/Backups.vue index be1add3ebef..8b58ad50ba7 100644 --- a/@xen-orchestra/web/src/components/site/dashboard/Backups.vue +++ b/@xen-orchestra/web/src/components/site/dashboard/Backups.vue @@ -2,8 +2,13 @@ {{ $t('backups') }} - - + + @@ -12,12 +17,14 @@ import { useDashboardStore } from '@/stores/xo-rest-api/dashboard.store' import CardTitle from '@core/components/card/CardTitle.vue' import CardNumbers from '@core/components/CardNumbers.vue' +import Divider from '@core/components/divider/Divider.vue' import DonutChartWithLegend, { type DonutChartWithLegendProps, } from '@core/components/donut-chart-with-legend/DonutChartWithLegend.vue' import LoadingHero from '@core/components/state-hero/LoadingHero.vue' +import NoDataHero from '@core/components/state-hero/NoDataHero.vue' import UiCard from '@core/components/UiCard.vue' -import { faCircleInfo, faServer } from '@fortawesome/free-solid-svg-icons' +import { faCircleInfo } from '@fortawesome/free-solid-svg-icons' import { computed } from 'vue' import { useI18n } from 'vue-i18n' @@ -25,13 +32,13 @@ const { record, isReady } = useDashboardStore().subscribe() const { t } = useI18n() -const title = computed(() => ({ +const jobsTitle = computed(() => ({ label: t('backups.jobs'), iconTooltip: t('backups.jobs.based-on-last-three'), icon: faCircleInfo, })) -const segments = computed(() => [ +const jobsSegments = computed(() => [ { label: t('backups.jobs.running-good'), value: record.value?.backups?.jobs.successful ?? 0, @@ -53,4 +60,28 @@ const segments = computed(() => [ color: 'disabled', }, ]) + +const vmsProtectionTitle = computed(() => ({ + label: t('backups.vms-protection'), + iconTooltip: t('backups.vms-protection.tooltip'), + icon: faCircleInfo, +})) + +const vmsProtectionSegments = computed(() => [ + { + label: t('backups.vms-protection.protected'), + value: record.value?.backups?.vmsProtection.protected ?? 0, + color: 'success', + }, + { + label: t('backups.vms-protection.unprotected'), + value: record.value?.backups?.vmsProtection.unprotected ?? 0, + color: 'warning', + }, + { + label: t('backups.vms-protection.no-job'), + value: record.value?.backups?.vmsProtection.notInJob ?? 0, + color: 'danger', + }, +]) diff --git a/@xen-orchestra/web/src/locales/en.json b/@xen-orchestra/web/src/locales/en.json index 1fde8d70511..09ca0a31652 100644 --- a/@xen-orchestra/web/src/locales/en.json +++ b/@xen-orchestra/web/src/locales/en.json @@ -12,6 +12,12 @@ "backups.jobs.looks-like-issue": "Looks like there is an issue", "backups.jobs.running-good": "Running good", + "backups.vms-protection": "VMs protection", + "backups.vms-protection.no-job": "In no job", + "backups.vms-protection.protected": "In at least 1 job & protected", + "backups.vms-protection.tooltip": "A VM is protected if it's in a backup job, with an enabled schedule, and the last run succeeded", + "backups.vms-protection.unprotected": "In at least 1 job but unprotected", + "end-of-life": "End of life", "eol": "EOL", "for-backup": "For backup", diff --git a/@xen-orchestra/web/src/locales/fr.json b/@xen-orchestra/web/src/locales/fr.json index 0fcd10d4d88..f46573ea675 100644 --- a/@xen-orchestra/web/src/locales/fr.json +++ b/@xen-orchestra/web/src/locales/fr.json @@ -12,6 +12,12 @@ "backups.jobs.looks-like-issue": "Il semble qu'il y ait un problème", "backups.jobs.running-good": "Tout est ok", + "backups.vms-protection": "Protection des VMs", + "backups.vms-protection.no-job": "Dans aucun job", + "backups.vms-protection.protected": "Dans au moins 1 job et protégé", + "backups.vms-protection.tooltip": "Une VM est protégée si elle se trouve dans un job, avec une planification activée, et si la dernière exécution a réussi", + "backups.vms-protection.unprotected": "Dans au moins 1 job mais sans protection", + "end-of-life": "Fin de vie", "eol": "EOL", "for-backup": "Pour la sauvegarde", diff --git a/@xen-orchestra/web/src/types/xo/dashboard.type.ts b/@xen-orchestra/web/src/types/xo/dashboard.type.ts index 33387df7751..b0c6c692bd4 100644 --- a/@xen-orchestra/web/src/types/xo/dashboard.type.ts +++ b/@xen-orchestra/web/src/types/xo/dashboard.type.ts @@ -47,5 +47,10 @@ export type XoDashboard = { total: number } issues: BackupIssue[] + vmsProtection: { + protected: number + unprotected: number + notInJob: number + } } } diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index f5443be736b..078d6ac50fd 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -12,7 +12,9 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” - [Hosts] Display a warning for hosts whose TLS key is too short to update to XCP-ng 8.3 (PR [#7995](https://github.com/vatesfr/xen-orchestra/pull/7995)) +- **XO 6**: - [Dashboard] Display S3 backup repository data (PR [#8006](https://github.com/vatesfr/xen-orchestra/pull/8006)) + - [Dashboard] Display VMs protection data (PR [#8007](https://github.com/vatesfr/xen-orchestra/pull/8007)) - **xo-cli** - `rest get --output $file` now displays progress information during download - `rest post` and `rest put` now accept `--input $file` to upload a file and display progress information diff --git a/packages/xo-server/src/api/mirror-backup.mjs b/packages/xo-server/src/api/mirror-backup.mjs index 802f25ac522..d8bd341cf5d 100644 --- a/packages/xo-server/src/api/mirror-backup.mjs +++ b/packages/xo-server/src/api/mirror-backup.mjs @@ -113,10 +113,6 @@ editJob.params = { remotes: { type: 'object', }, - schedules: { - type: 'object', - optional: true, - }, filter: MIRROR_BACKUP_FILTER, settings: SCHEMA_SETTINGS, } diff --git a/packages/xo-web/src/xo-app/backup/new/mirror/index.js b/packages/xo-web/src/xo-app/backup/new/mirror/index.js index 951b0f22047..111c9b482fa 100644 --- a/packages/xo-web/src/xo-app/backup/new/mirror/index.js +++ b/packages/xo-web/src/xo-app/backup/new/mirror/index.js @@ -189,7 +189,6 @@ const NewMirrorBackup = decorate([ } const settings = { ...state.settings } - const schedules = { ...state.schedules } await Promise.all([ ...map(props.schedules, ({ id }) => { const schedule = state.schedules[id] @@ -214,16 +213,16 @@ const NewMirrorBackup = decorate([ enabled: schedule.enabled, }) settings[newSchedule.id] = settings[schedule.id] - schedules[newSchedule.id] = newSchedule delete settings[schedule.id] - delete schedules[schedule.id] } }), ]) + const { schedules, ...jobProps } = normalize({ ...state, settings, isIncremental: state.isIncremental }) + await editMirrorBackupJob({ id: props.job.id, - ...normalize({ ...state, settings, schedules, isIncremental: state.isIncremental }), + ...jobProps, }) }, resetMirrorBackup: () => (_, props) => getInitialState(props),