add BackupInfo view/route

This commit is contained in:
Axolotle 2020-09-25 18:40:09 +02:00
parent c559678dca
commit d839260b73
4 changed files with 280 additions and 1 deletions

View file

@ -65,7 +65,7 @@
"confirm_install_app_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk?", "confirm_install_app_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk?",
"confirm_migrations_skip": "Skipping migrations is not recommended. Are you sure you want to do that?", "confirm_migrations_skip": "Skipping migrations is not recommended. Are you sure you want to do that?",
"confirm_postinstall": "You are about to launch the post-installation process on the domain %s. It may take a few minutes, *do not interrupt the operation*.", "confirm_postinstall": "You are about to launch the post-installation process on the domain %s. It may take a few minutes, *do not interrupt the operation*.",
"confirm_restore": "Are you sure you want to restore %s?", "confirm_restore": "Are you sure you want to restore {name}?",
"confirm_service_restart": "Are you sure you want to restart {name}?", "confirm_service_restart": "Are you sure you want to restart {name}?",
"confirm_service_start": "Are you sure you want to start {name}?", "confirm_service_start": "Are you sure you want to start {name}?",
"confirm_service_stop": "Are you sure you want to stop {name}?", "confirm_service_stop": "Are you sure you want to stop {name}?",

View file

@ -303,6 +303,19 @@ const routes = [
{ name: 'backup-list', param: 'id' } { name: 'backup-list', param: 'id' }
] ]
} }
},
{
name: 'backup-info',
path: '/backup/:id/:name',
component: () => import(/* webpackChunkName: "views/backup" */ '@/views/backup/BackupInfo'),
props: true,
meta: {
breadcrumb: [
{ name: 'backup', trad: 'backup' },
{ name: 'backup-list', param: 'id' },
{ name: 'backup-info', param: 'name' }
]
}
} }
] ]

View file

@ -0,0 +1,265 @@
<template>
<div class="backup-info" v-if="isReady">
<!-- BACKUP INFO -->
<b-card>
<template v-slot:header>
<h2><icon iname="info-circle" /> {{ $t('infos') }}</h2>
</template>
<dl>
<dt v-t="'id'" />
<dd>{{ name }}</dd>
<hr>
<dt v-t="'created_at'" />
<dd>{{ createdAt | readableDate }}</dd>
<hr>
<dt v-t="'size'" />
<dd>{{ size | humanSize }}</dd>
<hr>
<dt v-t="'path'" />
<dd>{{ path }}</dd>
<hr>
</dl>
</b-card>
<!-- BACKUP CONTENT -->
<b-card no-body>
<b-card-header class="d-flex align-items-md-center flex-column flex-md-row">
<div>
<h2><icon iname="archive" /> {{ $t('backup_content') }}</h2>
</div>
<div class="ml-md-auto mt-2 mt-md-0">
<b-button
size="sm" variant="outline-secondary"
v-t="'select_all'"
@click="toggleSelected()"
/>
<b-button
size="sm" variant="outline-secondary" class="ml-2"
v-t="'select_none'"
@click="toggleSelected(false)"
/>
</div>
</b-card-header>
<b-form-checkbox-group
v-if="hasItems" v-model="selected"
id="backup-select" name="backup-select" size="lg"
aria-describedby="backup-restore-feedback"
>
<b-list-group flush>
<!-- SYSTEM PARTS -->
<b-list-group-item
v-for="(item, partName) in systemParts" :key="partName"
class="d-flex justify-content-between align-items-center pr-0"
>
<div class="mr-2">
<h5>{{ item.name }} <small v-if="item.size">({{ item.size | humanSize }})</small></h5>
<p class="mb-0">{{ item.description }}</p>
</div>
<b-form-checkbox :value="partName" :aria-label="$t('check')" />
</b-list-group-item>
<!-- APPS -->
<b-list-group-item
v-for="(item, appName) in apps" :key="appName"
class="d-flex justify-content-between align-items-center pr-0"
>
<div class="mr-2">
<h5>{{ item.name }} <small>{{ appName }} ({{ item.size | humanSize }})</small></h5>
<p class="mb-0">{{ $t('version') }} {{ item.version }}</p>
</div>
<b-form-checkbox :value="appName" :aria-label="$t('check')" />
</b-list-group-item>
</b-list-group>
<b-form-invalid-feedback id="backup-restore-feedback" :state="isValid">
<b-alert variant="danger" show class="mb-0">
{{ error }}
</b-alert>
</b-form-invalid-feedback>
</b-form-checkbox-group>
<b-alert
v-else
variant="warning" class="mb-0" show
>
<icon iname="exclamation-triangle" /> {{ $t('archive_empty') }}
</b-alert>
<!-- SUBMIT -->
<template v-if="hasItems" v-slot:footer>
<div class="d-flex justify-content-end">
<b-button
v-b-modal.confirm-restore-backup form="backup-restore" variant="success"
v-t="'restore'" :disabled="selected.length === 0"
/>
</div>
</template>
</b-card>
<!-- RESTORE BACKUP MODAL -->
<b-modal
id="confirm-restore-backup" centered
body-bg-variant="danger" body-text-variant="light"
@ok="restoreBackup" hide-header
>
{{ $t('confirm_restore', { name }) }}
</b-modal>
<!-- DELETE ARCHIVE -->
<b-card>
<template v-slot:header>
<h2><icon iname="wrench" /> {{ $t('operations') }}</h2>
</template>
<b-form-group label-cols="auto" :label="$t('backup_archive_delete')" label-for="delete-backup">
<b-button
variant="danger" id="delete-backup" v-b-modal.confirm-delete-backup
>
<icon iname="trash-o" /> {{ $t('delete') }}
</b-button>
</b-form-group>
</b-card>
<!-- DELETE BACKUP MODAL -->
<b-modal
id="confirm-delete-backup" centered
body-bg-variant="danger" body-text-variant="light"
@ok="deleteBackup" hide-header
>
{{ $t('confirm_delete', { name }) }}
</b-modal>
</div>
</template>
<script>
import api from '@/helpers/api'
import { readableDate } from '@/filters/date'
import { humanSize } from '@/filters/size'
export default {
name: 'BackupInfo',
props: {
id: {
type: String,
required: true
},
name: {
type: String,
required: true
}
},
data () {
return {
isReady: false,
restore: false,
selected: [],
error: '',
isValid: null,
// api data
createdAt: undefined,
size: undefined,
path: undefined,
apps: undefined,
systemParts: undefined
}
},
filters: {
readableDate,
humanSize
},
methods: {
fetchData () {
api.get(`backup/archives/${this.name}?with_details`).then((data) => {
this.createdAt = data.created_at
this.size = data.size
this.path = data.path
this.hasItems = Object.keys(data.system).length !== 0 || Object.keys(data.apps).length !== 0
this.systemParts = this.formatHooks(data.system)
this.apps = data.apps
this.toggleSelected()
this.isReady = true
})
},
toggleSelected (select = true) {
if (select) {
this.selected = [
...Object.keys(this.apps),
...Object.keys(this.systemParts)
]
} else {
this.selected = []
}
},
restoreBackup () {
const data = {
apps: [],
system: [],
force: ''
}
for (const item of this.selected) {
if (item in this.systemParts) {
data.system = [...data.system, ...this.systemParts[item].value]
} else {
data.apps.push(item)
}
}
api.post('backup/restore/' + this.name, data).then(response => {
// FIXME display ws messages
this.isValid = null
}).catch(err => {
// FIXME some errors may be sent by the websocket (yunohost api error for exemple)
this.error = err.message
this.isValid = false
})
},
deleteBackup () {
api.delete('backup/archives/' + this.name).then(() => {
this.$router.push({ name: 'backup-list', params: { id: this.id } })
})
},
formatHooks (hooks) {
const data = {}
Object.entries(hooks).forEach(([hook, { size }]) => {
const groupId = hook.startsWith('conf_') ? 'adminjs_group_configuration' : hook
if (groupId in data) {
data[groupId].value.push(hook)
data[groupId].description += ', ' + this.$i18n.t('hook_' + hook)
data[groupId].size += size
} else {
data[groupId] = {
name: this.$i18n.t('hook_' + groupId),
value: [hook],
description: this.$i18n.t(groupId === hook ? `hook_${hook}_desc` : 'hook_' + hook),
size
}
}
})
return data
}
},
created () {
this.fetchData()
}
}
</script>

View file

@ -61,6 +61,7 @@ export default {
methods: { methods: {
fetchData () { fetchData () {
api.get('backup/archives?with_info').then(({ archives }) => { api.get('backup/archives?with_info').then(({ archives }) => {
// FIXME use archives = null if no archives
this.archives = Object.entries(archives).map(([name, data]) => { this.archives = Object.entries(archives).map(([name, data]) => {
data.name = name data.name = name
return data return data