From 92f8605dd02cba6a4ec8a1d70b1909c3a3ad2e99 Mon Sep 17 00:00:00 2001 From: Haylan Date: Mon, 14 Sep 2026 22:00:26 +0200 Subject: [PATCH] feat(dump): add settings-page admin UI module --- src/Resources/app/administration/src/main.js | 10 +- .../src/module/aeon-dump-manager/acl/index.js | 19 +++ .../src/module/aeon-dump-manager/index.js | 39 +++++ .../aeon-dump-manager-list.html.twig | 68 +++++++++ .../page/aeon-dump-manager-list/index.js | 137 ++++++++++++++++++ .../aeon-dump-manager/snippet/de-DE.json | 21 +++ .../aeon-dump-manager/snippet/en-GB.json | 21 +++ .../src/service/dump-api.service.js | 31 ++++ 8 files changed, 344 insertions(+), 2 deletions(-) mode change 100644 => 100755 src/Resources/app/administration/src/main.js create mode 100644 src/Resources/app/administration/src/module/aeon-dump-manager/acl/index.js create mode 100644 src/Resources/app/administration/src/module/aeon-dump-manager/index.js create mode 100644 src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/aeon-dump-manager-list.html.twig create mode 100644 src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/index.js create mode 100644 src/Resources/app/administration/src/module/aeon-dump-manager/snippet/de-DE.json create mode 100644 src/Resources/app/administration/src/module/aeon-dump-manager/snippet/en-GB.json create mode 100644 src/Resources/app/administration/src/service/dump-api.service.js diff --git a/src/Resources/app/administration/src/main.js b/src/Resources/app/administration/src/main.js old mode 100644 new mode 100755 index 14673d3..682cf0e --- a/src/Resources/app/administration/src/main.js +++ b/src/Resources/app/administration/src/main.js @@ -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); +}); diff --git a/src/Resources/app/administration/src/module/aeon-dump-manager/acl/index.js b/src/Resources/app/administration/src/module/aeon-dump-manager/acl/index.js new file mode 100644 index 0000000..ef97f2d --- /dev/null +++ b/src/Resources/app/administration/src/module/aeon-dump-manager/acl/index.js @@ -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'], + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/aeon-dump-manager/index.js b/src/Resources/app/administration/src/module/aeon-dump-manager/index.js new file mode 100644 index 0000000..8e7ae07 --- /dev/null +++ b/src/Resources/app/administration/src/module/aeon-dump-manager/index.js @@ -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', + }, +}); diff --git a/src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/aeon-dump-manager-list.html.twig b/src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/aeon-dump-manager-list.html.twig new file mode 100644 index 0000000..056dba2 --- /dev/null +++ b/src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/aeon-dump-manager-list.html.twig @@ -0,0 +1,68 @@ +{% block aeon_dump_manager_list %} + + + + + + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/index.js b/src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/index.js new file mode 100644 index 0000000..c51f8be --- /dev/null +++ b/src/Resources/app/administration/src/module/aeon-dump-manager/page/aeon-dump-manager-list/index.js @@ -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()); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/aeon-dump-manager/snippet/de-DE.json b/src/Resources/app/administration/src/module/aeon-dump-manager/snippet/de-DE.json new file mode 100644 index 0000000..1413412 --- /dev/null +++ b/src/Resources/app/administration/src/module/aeon-dump-manager/snippet/de-DE.json @@ -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." + } + } +} diff --git a/src/Resources/app/administration/src/module/aeon-dump-manager/snippet/en-GB.json b/src/Resources/app/administration/src/module/aeon-dump-manager/snippet/en-GB.json new file mode 100644 index 0000000..b75e6d1 --- /dev/null +++ b/src/Resources/app/administration/src/module/aeon-dump-manager/snippet/en-GB.json @@ -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." + } + } +} diff --git a/src/Resources/app/administration/src/service/dump-api.service.js b/src/Resources/app/administration/src/service/dump-api.service.js new file mode 100644 index 0000000..58f5cdf --- /dev/null +++ b/src/Resources/app/administration/src/service/dump-api.service.js @@ -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)); + } +}