feat(dump): add settings-page admin UI module
This commit is contained in:
Regular → Executable
+8
-2
@@ -1,2 +1,8 @@
|
||||
// Import admin module
|
||||
import './module/swag-example';
|
||||
import DumpApiService from './service/dump-api.service';
|
||||
import './module/aeon-dump-manager';
|
||||
|
||||
Shopware.Application.addServiceProvider('aeonDumpManagerApiService', (container) => {
|
||||
const initContainer = Shopware.Application.getContainer('init');
|
||||
|
||||
return new DumpApiService(initContainer.httpClient, container.loginService);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
Shopware.Service('privileges').addPrivilegeMappingEntry({
|
||||
category: 'permissions',
|
||||
parent: null,
|
||||
key: 'aeon_dump_manager',
|
||||
roles: {
|
||||
viewer: {
|
||||
privileges: ['aeon_dump_manager.viewer'],
|
||||
dependencies: [],
|
||||
},
|
||||
creator: {
|
||||
privileges: ['aeon_dump_manager.creator'],
|
||||
dependencies: ['aeon_dump_manager.viewer'],
|
||||
},
|
||||
deleter: {
|
||||
privileges: ['aeon_dump_manager.deleter'],
|
||||
dependencies: ['aeon_dump_manager.viewer'],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import './acl';
|
||||
import './page/aeon-dump-manager-list';
|
||||
import deDE from './snippet/de-DE.json';
|
||||
import enGB from './snippet/en-GB.json';
|
||||
|
||||
Shopware.Module.register('aeon-dump-manager', {
|
||||
type: 'plugin',
|
||||
name: 'DumpManager',
|
||||
title: 'aeon-dump-manager.general.mainMenuItemGeneral',
|
||||
description: 'aeon-dump-manager.general.descriptionTextModule',
|
||||
color: '#ff3d58',
|
||||
icon: 'default-object-storage',
|
||||
|
||||
snippets: {
|
||||
'de-DE': deDE,
|
||||
'en-GB': enGB,
|
||||
},
|
||||
|
||||
routes: {
|
||||
list: {
|
||||
component: 'aeon-dump-manager-list',
|
||||
path: 'list',
|
||||
meta: {
|
||||
parentPath: 'sw.settings.index.system',
|
||||
privilege: 'aeon_dump_manager.viewer',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Shown as a card on the Settings page's "System" group, next to
|
||||
// Plugins/Cache — matches the locked ACL/menu-naming decision. Plugins
|
||||
// cannot register a first-level main-menu entry.
|
||||
settingsItem: {
|
||||
group: 'system',
|
||||
to: 'aeon.dump.manager.list',
|
||||
icon: 'default-object-storage',
|
||||
privilege: 'aeon_dump_manager.viewer',
|
||||
},
|
||||
});
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{% block aeon_dump_manager_list %}
|
||||
<sw-page class="aeon-dump-manager-list">
|
||||
<template #smart-bar-actions>
|
||||
<sw-button
|
||||
v-if="acl.can('aeon_dump_manager.creator')"
|
||||
variant="primary"
|
||||
:disabled="pendingJob !== null"
|
||||
@click="onCreate"
|
||||
>
|
||||
{{ $tc('aeon-dump-manager.list.buttonCreate') }}
|
||||
</sw-button>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<sw-card-view>
|
||||
<sw-card>
|
||||
<sw-data-grid
|
||||
:data-source="dumps"
|
||||
:columns="columns"
|
||||
:is-loading="isLoading"
|
||||
:show-selection="false"
|
||||
identifier="filename"
|
||||
>
|
||||
<template #before-item-list>
|
||||
<tr v-if="pendingJob !== null" class="aeon-dump-manager-list__pending-row">
|
||||
<td colspan="3">
|
||||
<span>{{ $tc('aeon-dump-manager.list.creating') }}</span>
|
||||
<sw-progress-bar :max-value="100" :value="pendingJob.percent || 0" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<template #column-datetime="{ item }">
|
||||
{{ formatDatetime(item.datetime) }}
|
||||
</template>
|
||||
|
||||
<template #column-sizeBytes="{ item }">
|
||||
{{ formatSize(item.sizeBytes) }}
|
||||
</template>
|
||||
|
||||
<template #actions="{ item }">
|
||||
<sw-context-menu-item
|
||||
v-if="acl.can('aeon_dump_manager.deleter')"
|
||||
variant="danger"
|
||||
@click="onRequestDelete(item.filename)"
|
||||
>
|
||||
{{ $tc('aeon-dump-manager.list.buttonDelete') }}
|
||||
</sw-context-menu-item>
|
||||
</template>
|
||||
|
||||
<template #empty-state>
|
||||
{{ $tc('aeon-dump-manager.list.empty') }}
|
||||
</template>
|
||||
</sw-data-grid>
|
||||
</sw-card>
|
||||
</sw-card-view>
|
||||
</template>
|
||||
|
||||
<sw-confirm-modal
|
||||
v-if="deleteFilename !== null"
|
||||
:title="$tc('aeon-dump-manager.list.confirmDeleteTitle')"
|
||||
:text="$tc('aeon-dump-manager.list.confirmDeleteText', 0, { filename: deleteFilename })"
|
||||
@confirm="onConfirmDelete"
|
||||
@cancel="onCancelDelete"
|
||||
@close="onCancelDelete"
|
||||
/>
|
||||
</sw-page>
|
||||
{% endblock %}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
import template from './aeon-dump-manager-list.html.twig';
|
||||
|
||||
const { Component } = Shopware;
|
||||
|
||||
Component.register('aeon-dump-manager-list', {
|
||||
template,
|
||||
|
||||
inject: ['aeonDumpManagerApiService', 'acl'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
dumps: [],
|
||||
isLoading: false,
|
||||
pendingJob: null,
|
||||
deleteFilename: null,
|
||||
pollTimer: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
columns() {
|
||||
return [
|
||||
{
|
||||
property: 'datetime',
|
||||
label: this.$tc('aeon-dump-manager.list.columnDatetime'),
|
||||
allowResize: true,
|
||||
},
|
||||
{
|
||||
property: 'filename',
|
||||
label: this.$tc('aeon-dump-manager.list.columnFilename'),
|
||||
allowResize: true,
|
||||
},
|
||||
{
|
||||
property: 'sizeBytes',
|
||||
label: this.$tc('aeon-dump-manager.list.columnSize'),
|
||||
align: 'right',
|
||||
allowResize: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadList();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.stopPolling();
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadList() {
|
||||
this.isLoading = true;
|
||||
|
||||
return this.aeonDumpManagerApiService.getList()
|
||||
.then((response) => {
|
||||
this.dumps = response.dumps;
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
formatDatetime(value) {
|
||||
return new Date(value).toLocaleString();
|
||||
},
|
||||
|
||||
formatSize(bytes) {
|
||||
return Shopware.Utils.format.fileSize(bytes);
|
||||
},
|
||||
|
||||
onCreate() {
|
||||
this.pendingJob = { percent: 0, status: 'pending' };
|
||||
|
||||
this.aeonDumpManagerApiService.create()
|
||||
.then((response) => {
|
||||
this.pollJobStatus(response.jobId);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.pendingJob = null;
|
||||
this.createNotificationError({
|
||||
message: this.$tc('aeon-dump-manager.list.createFailed', 0, { error: error.message }),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
pollJobStatus(jobId) {
|
||||
this.stopPolling();
|
||||
|
||||
this.pollTimer = window.setInterval(() => {
|
||||
this.aeonDumpManagerApiService.getJobStatus(jobId).then((job) => {
|
||||
if (job.status === 'done') {
|
||||
this.stopPolling();
|
||||
this.pendingJob = null;
|
||||
this.createNotificationSuccess({ message: this.$tc('aeon-dump-manager.list.createSuccess') });
|
||||
this.loadList();
|
||||
return;
|
||||
}
|
||||
|
||||
if (job.status === 'failed') {
|
||||
this.stopPolling();
|
||||
this.pendingJob = null;
|
||||
this.createNotificationError({
|
||||
message: this.$tc('aeon-dump-manager.list.createFailed', 0, { error: job.errorMessage }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingJob = job;
|
||||
});
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
stopPolling() {
|
||||
if (this.pollTimer !== null) {
|
||||
window.clearInterval(this.pollTimer);
|
||||
this.pollTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
onRequestDelete(filename) {
|
||||
this.deleteFilename = filename;
|
||||
},
|
||||
|
||||
onCancelDelete() {
|
||||
this.deleteFilename = null;
|
||||
},
|
||||
|
||||
onConfirmDelete() {
|
||||
const filename = this.deleteFilename;
|
||||
this.deleteFilename = null;
|
||||
|
||||
return this.aeonDumpManagerApiService.remove(filename)
|
||||
.then(() => this.loadList());
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"aeon-dump-manager": {
|
||||
"general": {
|
||||
"mainMenuItemGeneral": "Dump-Verwaltung",
|
||||
"descriptionTextModule": "SQL-Datenbank-Dumps erstellen, auflisten und löschen"
|
||||
},
|
||||
"list": {
|
||||
"columnFilename": "Name",
|
||||
"columnDatetime": "Erstellt am",
|
||||
"columnSize": "Größe",
|
||||
"buttonCreate": "Dump erstellen",
|
||||
"buttonDelete": "Löschen",
|
||||
"confirmDeleteTitle": "Dump löschen?",
|
||||
"confirmDeleteText": "Möchten Sie \"{filename}\" wirklich löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"empty": "Keine Dumps gefunden.",
|
||||
"creating": "Dump wird erstellt…",
|
||||
"createFailed": "Dump konnte nicht erstellt werden: {error}",
|
||||
"createSuccess": "Dump erstellt."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"aeon-dump-manager": {
|
||||
"general": {
|
||||
"mainMenuItemGeneral": "Dump manager",
|
||||
"descriptionTextModule": "Create, list and delete SQL database dumps"
|
||||
},
|
||||
"list": {
|
||||
"columnFilename": "Name",
|
||||
"columnDatetime": "Created at",
|
||||
"columnSize": "Size",
|
||||
"buttonCreate": "Create dump",
|
||||
"buttonDelete": "Delete",
|
||||
"confirmDeleteTitle": "Delete dump?",
|
||||
"confirmDeleteText": "Are you sure you want to delete \"{filename}\"? This cannot be undone.",
|
||||
"empty": "No dumps found.",
|
||||
"creating": "Creating dump…",
|
||||
"createFailed": "Could not create dump: {error}",
|
||||
"createSuccess": "Dump created."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const { ApiService } = Shopware.Classes;
|
||||
|
||||
export default class DumpApiService extends ApiService {
|
||||
constructor(httpClient, loginService, apiEndpoint = 'aeon-dump-manager') {
|
||||
super(httpClient, loginService, apiEndpoint);
|
||||
}
|
||||
|
||||
getList() {
|
||||
return this.httpClient
|
||||
.get('/_action/aeon-dump-manager/dumps', { headers: this.getBasicHeaders() })
|
||||
.then((response) => ApiService.handleResponse(response));
|
||||
}
|
||||
|
||||
create() {
|
||||
return this.httpClient
|
||||
.post('/_action/aeon-dump-manager/dumps', {}, { headers: this.getBasicHeaders() })
|
||||
.then((response) => ApiService.handleResponse(response));
|
||||
}
|
||||
|
||||
remove(filename) {
|
||||
return this.httpClient
|
||||
.delete(`/_action/aeon-dump-manager/dumps/${encodeURIComponent(filename)}`, { headers: this.getBasicHeaders() })
|
||||
.then((response) => ApiService.handleResponse(response));
|
||||
}
|
||||
|
||||
getJobStatus(jobId) {
|
||||
return this.httpClient
|
||||
.get(`/_action/aeon-dump-manager/dumps/jobs/${encodeURIComponent(jobId)}`, { headers: this.getBasicHeaders() })
|
||||
.then((response) => ApiService.handleResponse(response));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user