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 DumpApiService from './service/dump-api.service';
|
||||||
import './module/swag-example';
|
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