Author SHA1 Message Date
haylanandClaude Bot ea252ecf6c fix(admin): move dump modals into sw-page #content slot so delete works on 6.7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:54:26 +01:00
haylanandClaude Bot 8b13c5b3c8 fix(dump): pass --column-statistics=0 for MySQL 8 mysqldump on MariaDB
When the server is MariaDB but only MySQL 8's mysqldump is installed
(e.g. dockware/web), the dump aborted with "Unknown table
'COLUMN_STATISTICS' in information_schema (1109)".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:29:43 +01:00
haylanandClaude Bot fa464bc20d refactor(admin): lazy registration, Meteor components, Settings card for 6.7
- register the list page lazily and wrap it with wrapComponentConfig
- sw-button/sw-alert/sw-progress-bar -> mt-button/mt-banner/mt-progress-bar
- beforeDestroy -> beforeUnmount (Vue 3)
- split the combined `import type` that broke the Vite build
- Vite outputs to public/administration/assets/: update .gitignore and the CI asset check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:29:43 +01:00
haylanandClaude Bot 4abd77103e feat!: require Shopware 6.7, release as 0.1.0
- shopware/core ~6.7.0; 6.6 stays on the 0.0.x line
- drop the sw-settings navigation entry; the settingsItem card in
  Settings > System is the only entry, like core settings modules
- dev container on dockware/shopware:6.7.14.2

BREAKING CHANGE: Shopware 6.6 is no longer supported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:49:56 +01:00
14 changed files with 126 additions and 80 deletions

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@ composer.phar
# Frontend
*.vite/
**/public/administration/js/
**/public/administration/assets/
**/public/static/js/
# Environment
+17
View File
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.1.0] - 2026-11-02
### Changed
- Requires Shopware 6.7 (`shopware/core ~6.7.0`); 6.6 stays on the 0.0.x line.
- Administration: the list page is registered lazily, uses Meteor components (`mt-button`, `mt-banner`, `mt-progress-bar`) and stops polling on `beforeUnmount` (Vue 3).
### Fixed
- The admin build failed on a combined `import type` statement, so the Settings card never appeared.
- Delete did nothing and the create progress modal never showed: both modals sat outside `sw-page`'s named slots, which Vue 3 drops. They now live in `#content`.
- Creating a dump failed on MariaDB when only MySQL 8's `mysqldump` was installed (`Unknown table 'COLUMN_STATISTICS'`); it now passes `--column-statistics=0` in that case.
### Removed
- The Settings sidebar navigation entry; the plugin is reached through its card in Settings → System, like core settings modules.
## [0.0.6] - 2026-10-04
### Changed
+1 -1
View File
@@ -67,7 +67,7 @@ vendor/bin/phpunit tests/SomeTest.php # single file
## Dependencies
- PHP `^8.2`, `shopware/core` `~6.6.0` (from `composer.json`); `phpunit/phpunit` `^10.0` as the only
- PHP `^8.2`, `shopware/core` `~6.7.0` (from `composer.json`); `phpunit/phpunit` `^10.0` as the only
dev dependency.
- PSR-4 autoload: `AeonDumpManager\` → `src/`, `AeonDumpManager\Tests\` → `tests/`.
+2 -2
View File
@@ -3,7 +3,7 @@
"description": "Export database dumps for Shopware 6 environments.",
"type": "shopware-platform-plugin",
"license": "MIT",
"version": "0.0.6",
"version": "0.1.0",
"keywords": [
"shopware",
"shopware6",
@@ -26,7 +26,7 @@
},
"require": {
"php": "^8.2",
"shopware/core": "~6.6.0"
"shopware/core": "~6.7.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
+1 -1
View File
@@ -1,6 +1,6 @@
services:
shopware:
image: dockware/shopware:6.6.10.12
image: dockware/shopware:6.7.14.2
container_name: shopware
ports:
- "80:80" # Shop / Admin / Adminer / Mailcatcher (all served via path on this port)
-3
View File
@@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}
+1 -1
View File
@@ -13,5 +13,5 @@ check() { # <label> <entry-glob-prefix> <expected path in zip>
fi
}
check admin src/Resources/app/administration/src "Resources/public/administration/js/"
check admin src/Resources/app/administration/src "Resources/public/administration/assets/"
check storefront src/Resources/app/storefront/src "Resources/app/storefront/dist/"
@@ -1,8 +1,10 @@
import './acl';
import './page/aeon-dump-manager-list';
import deDE from './snippet/de-DE.json';
import enGB from './snippet/en-GB.json';
// Lazy (async) registration, as core does: the page chunk loads on first visit.
Shopware.Component.register('aeon-dump-manager-list', () => import('./page/aeon-dump-manager-list'));
Shopware.Module.register('aeon-dump-manager', {
type: 'plugin',
name: 'DumpManager',
@@ -27,27 +29,10 @@ Shopware.Module.register('aeon-dump-manager', {
},
},
// Per https://developer.shopware.com/docs/v6.6/guides/plugins/plugins/administration/routing-navigation/add-menu-entry.html —
// plugins can't register a first-level main-menu entry, so this nests
// under the core 'sw-settings' navigation node. Clicking Settings in the
// main sidebar shows this as a sub-item, landing at #/sw/settings/.
navigation: [{
id: 'aeon-dump-manager-list',
label: 'aeon-dump-manager.general.mainMenuItemGeneral',
color: '#ff3d58',
path: 'aeon.dump.manager.list',
icon: 'regular-database',
parent: 'sw-settings',
position: 100,
privilege: 'aeon_dump_manager.viewer',
}],
// Also shown as a card on the Settings overview page's "System" group,
// next to Plugins/Cache — complements the navigation entry above.
settingsItem: {
group: 'system',
settingsItem: [{
group: 'plugins',
to: 'aeon.dump.manager.list',
icon: 'regular-database',
privilege: 'aeon_dump_manager.viewer',
},
}],
});
@@ -1,7 +1,7 @@
{% block aeon_dump_manager_list %}
<sw-page class="aeon-dump-manager-list">
<template #smart-bar-actions>
<sw-button
<mt-button
v-if="acl.can('aeon_dump_manager.creator')"
variant="primary"
:is-loading="pendingJob !== null"
@@ -9,7 +9,7 @@
@click="onCreate"
>
{{ $tc('aeon-dump-manager.list.buttonCreate') }}
</sw-button>
</mt-button>
</template>
<template #content>
@@ -75,50 +75,51 @@
</template>
</sw-data-grid>
</div>
<!-- sw-page has no default slot (Vue 3 drops unslotted content): modals must live in #content. -->
{% block aeon_dump_manager_list_progress_modal %}
<sw-modal
v-if="pendingJob !== null"
:title="$tc('aeon-dump-manager.list.progressTitle')"
variant="small"
class="aeon-dump-manager-list__progress-modal"
@modal-close="onCloseProgressModal"
>
<div class="aeon-dump-manager-list__progress-wrapper">
<template v-if="pendingJob.status === 'done'">
<mt-banner variant="positive">
{{ $tc('aeon-dump-manager.list.createSuccess') }}
</mt-banner>
</template>
<template v-else-if="pendingJob.status === 'failed'">
<mt-banner variant="critical">
{{ $tc('aeon-dump-manager.list.createFailed', 0, { error: pendingJob.errorMessage || '' }) }}
</mt-banner>
</template>
<template v-else>
<mt-progress-bar
:model-value="pendingJob.percent || 0"
:max-value="100"
/>
<span class="aeon-dump-manager-list__progress-text">
{{ $tc('aeon-dump-manager.list.progressText') }}
</span>
</template>
</div>
</sw-modal>
{% endblock %}
<sw-confirm-modal
v-if="deleteFilenames !== null"
:title="$tc('aeon-dump-manager.list.confirmDeleteTitle')"
:text="deleteFilenames.length > 1
? $tc('aeon-dump-manager.list.confirmDeleteTextMultiple', 0, { count: deleteFilenames.length })
: $tc('aeon-dump-manager.list.confirmDeleteText', 0, { filename: deleteFilenames[0] })"
type="delete"
@confirm="onConfirmDelete"
@cancel="onCancelDelete"
@close="onCancelDelete"
/>
</template>
{% block aeon_dump_manager_list_progress_modal %}
<sw-modal
v-if="pendingJob !== null"
:title="$tc('aeon-dump-manager.list.progressTitle')"
variant="small"
class="aeon-dump-manager-list__progress-modal"
@modal-close="onCloseProgressModal"
>
<div class="aeon-dump-manager-list__progress-wrapper">
<template v-if="pendingJob.status === 'done'">
<sw-alert variant="success">
{{ $tc('aeon-dump-manager.list.createSuccess') }}
</sw-alert>
</template>
<template v-else-if="pendingJob.status === 'failed'">
<sw-alert variant="error">
{{ $tc('aeon-dump-manager.list.createFailed', 0, { error: pendingJob.errorMessage || '' }) }}
</sw-alert>
</template>
<template v-else>
<sw-progress-bar
:value="pendingJob.percent || 0"
:max-value="100"
/>
<span class="aeon-dump-manager-list__progress-text">
{{ $tc('aeon-dump-manager.list.progressText') }}
</span>
</template>
</div>
</sw-modal>
{% endblock %}
<sw-confirm-modal
v-if="deleteFilenames !== null"
:title="$tc('aeon-dump-manager.list.confirmDeleteTitle')"
:text="deleteFilenames.length > 1
? $tc('aeon-dump-manager.list.confirmDeleteTextMultiple', 0, { count: deleteFilenames.length })
: $tc('aeon-dump-manager.list.confirmDeleteText', 0, { filename: deleteFilenames[0] })"
type="delete"
@confirm="onConfirmDelete"
@cancel="onCancelDelete"
@close="onCancelDelete"
/>
</sw-page>
{% endblock %}
@@ -1,7 +1,8 @@
import template from './aeon-dump-manager-list.html.twig';
import type DumpApiService, { DumpFile, DumpJob } from '../../../../service/dump-api.service';
import type DumpApiService from '../../../../service/dump-api.service';
import type { DumpFile, DumpJob } from '../../../../service/dump-api.service';
const { Component, Mixin } = Shopware;
const { Mixin } = Shopware;
interface PendingJob {
status: string;
@@ -45,7 +46,7 @@ interface ComponentInstance extends ComponentData {
listFilters: Record<string, unknown>[];
}
Component.register('aeon-dump-manager-list', {
export default Shopware.Component.wrapComponentConfig({
template,
mixins: [
@@ -147,7 +148,7 @@ Component.register('aeon-dump-manager-list', {
},
},
beforeDestroy(this: ComponentInstance) {
beforeUnmount(this: ComponentInstance) {
this.stopPolling();
},
+14
View File
@@ -6,6 +6,7 @@ use AeonDumpManager\Service\Exception\DumpBinaryNotFoundException;
use Doctrine\DBAL\Connection;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
/**
* Picks mariadb-dump/mysqldump depending on what the configured DB connection
@@ -38,6 +39,19 @@ class DumpBinaryLocator
throw DumpBinaryNotFoundException::create($binaryNames);
}
/**
* @return list<string> client-specific flags for the located binary
*/
public function extraArguments(string $binary): array
{
$version = new Process([$binary, '--version']);
$version->run();
return MySqlVariant::needsColumnStatisticsOff($version->getOutput(), $this->isMariaDb())
? ['--column-statistics=0']
: [];
}
private function isMariaDb(): bool
{
// Connection::getServerVersion() is private in the DBAL version this
+1
View File
@@ -173,6 +173,7 @@ class DumpService
$binary,
'--single-transaction',
'--quick',
...$this->dumpBinaryLocator->extraArguments($binary),
'-h', (string) ($params['host'] ?? '127.0.0.1'),
'-P', (string) ($params['port'] ?? 3306),
'-u', (string) ($params['user'] ?? 'root'),
+14
View File
@@ -14,4 +14,18 @@ class MySqlVariant
{
return stripos($serverVersion, 'mariadb') !== false;
}
/**
* MySQL 8+ mysqldump queries information_schema.COLUMN_STATISTICS, which
* MariaDB lacks (error 1109); --column-statistics=0 skips it. MariaDB's own
* client (version string contains "MariaDB") and MySQL < 8 don't know the flag.
*/
public static function needsColumnStatisticsOff(string $clientVersion, bool $serverIsMariaDb): bool
{
return $serverIsMariaDb
&& !self::isMariaDb($clientVersion)
// MySQL < 8 prints "Ver 10.13 Distrib 5.7.x": the Distrib number is the real version.
&& (preg_match('/Distrib (\d+)\./', $clientVersion, $m) === 1 || preg_match('/\bVer (\d+)\./', $clientVersion, $m) === 1)
&& (int) $m[1] >= 8;
}
}
+16
View File
@@ -21,4 +21,20 @@ class MySqlVariantTest extends TestCase
yield 'mariadb without legacy prefix' => ['10.11.2-MariaDB', true];
yield 'mariadb lowercase' => ['10.11.2-mariadb', true];
}
#[DataProvider('columnStatisticsProvider')]
public function testNeedsColumnStatisticsOff(string $clientVersion, bool $serverIsMariaDb, bool $expected): void
{
self::assertSame($expected, MySqlVariant::needsColumnStatisticsOff($clientVersion, $serverIsMariaDb));
}
public static function columnStatisticsProvider(): iterable
{
$mysql8 = 'mysqldump Ver 8.0.46-0ubuntu0.24.04.4 for Linux on x86_64 ((Ubuntu))';
yield 'mysql 8 client, mariadb server' => [$mysql8, true, true];
yield 'mysql 8 client, mysql server' => [$mysql8, false, false];
yield 'mysql 5.7 client, mariadb server' => ['mysqldump Ver 10.13 Distrib 5.7.44, for Linux (x86_64)', true, false];
yield 'mariadb client, mariadb server' => ['mariadb-dump from 11.4.2-MariaDB, client 10.19 for debian-linux-gnu (x86_64)', true, false];
yield 'mariadb legacy mysqldump name' => ['mysqldump Ver 10.19 Distrib 10.11.6-MariaDB, for debian-linux-gnu (x86_64)', true, false];
}
}