diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..21f6b782 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,16 @@ +# FroshTools - Claude Code Guidelines + +## Datenbank-Aenderungen + +**WICHTIG: Bei jeder Aenderung die die Datenbank betrifft, muss der Benutzer explizit darauf hingewiesen werden, BEVOR die Aenderung durchgefuehrt wird.** + +Dazu gehoeren: +- Neue oder geaenderte Migrations (`src/Migration/`) +- Schema-Aenderungen (CREATE TABLE, ALTER TABLE, DROP TABLE, etc.) +- Daten-Manipulationen (INSERT, UPDATE, DELETE) +- Aenderungen an bestehenden Tabellen oder Spalten +- Aenderungen an Indizes oder Foreign Keys + +Reine Lese-Operationen (SELECT, SHOW STATUS, information_schema Queries) sind davon ausgenommen und muessen nicht extra angekuendigt werden. + +Der Benutzer muss die Datenbank-Aenderung ausdruecklich bestaetigen, bevor sie implementiert wird. diff --git a/src/Components/CacheStatisticsService.php b/src/Components/CacheStatisticsService.php new file mode 100644 index 00000000..5ed0115d --- /dev/null +++ b/src/Components/CacheStatisticsService.php @@ -0,0 +1,164 @@ + 0) { + $lastRestart = (new \DateTimeImmutable('@' . $stats['last_restart_time']))->format('c'); + } + + return [ + 'enabled' => (bool) ($status['opcache_enabled'] ?? false), + 'hitRate' => $total > 0 ? round($hits / $total * 100, 2) : 0.0, + 'hits' => $hits, + 'misses' => $misses, + 'usedMemory' => (int) ($memory['used_memory'] ?? 0), + 'freeMemory' => (int) ($memory['free_memory'] ?? 0), + 'wastedMemory' => (int) ($memory['wasted_memory'] ?? 0), + 'wastedPercentage' => round((float) ($memory['current_wasted_percentage'] ?? 0), 2), + 'cachedScripts' => (int) ($stats['num_cached_scripts'] ?? 0), + 'maxCachedScripts' => $maxScripts, + 'internedStringsUsedMemory' => (int) ($interned['used_memory'] ?? 0), + 'internedStringsFreeMemory' => (int) ($interned['free_memory'] ?? 0), + 'lastRestart' => $lastRestart, + ]; + } + + /** + * @return array{enabled: bool, hitRate: float, hits: int, misses: int, usedMemory: int, availableMemory: int, entries: int, fragmentation: float}|null + */ + public function getApcuStatistics(): ?array + { + if (!\function_exists('apcu_cache_info') || !\function_exists('apcu_sma_info')) { + return null; + } + + $cacheInfo = apcu_cache_info(true); + $smaInfo = apcu_sma_info(); + + if ($cacheInfo === false || $smaInfo === false) { + return null; + } + + $hits = (int) ($cacheInfo['num_hits'] ?? 0); + $misses = (int) ($cacheInfo['num_misses'] ?? 0); + $total = $hits + $misses; + + $availableMemory = (int) ($smaInfo['avail_mem'] ?? 0); + $totalSegSize = (int) ($smaInfo['num_seg'] ?? 1) * (int) ($smaInfo['seg_size'] ?? 0); + $usedMemory = $totalSegSize - $availableMemory; + + $fragmentation = 0.0; + if ($totalSegSize > 0) { + $fragmentation = round(($smaInfo['num_seg'] > 0 ? (1 - $availableMemory / $totalSegSize) : 0) * 100, 2); + } + + return [ + 'enabled' => true, + 'hitRate' => $total > 0 ? round($hits / $total * 100, 2) : 0.0, + 'hits' => $hits, + 'misses' => $misses, + 'usedMemory' => max(0, $usedMemory), + 'availableMemory' => $availableMemory, + 'entries' => (int) ($cacheInfo['num_entries'] ?? 0), + 'fragmentation' => $fragmentation, + ]; + } + + /** + * @return list + */ + public function getRedisStatistics(): array + { + $result = []; + $seenConnections = []; + + foreach ($this->cacheRegistry->all() as $name => $adapter) { + try { + $redis = $adapter->getRedisOrFail(); + } catch (\RuntimeException) { + continue; + } + + $connectionId = $redis->getHost() . ':' . $redis->getPort(); + if (\in_array($connectionId, $seenConnections, true)) { + continue; + } + $seenConnections[] = $connectionId; + + try { + $info = $redis->info(); + } catch (\RedisException) { + continue; + } + + $hits = (int) ($info['keyspace_hits'] ?? 0); + $misses = (int) ($info['keyspace_misses'] ?? 0); + $total = $hits + $misses; + + $totalKeys = 0; + foreach ($info as $key => $value) { + if (\str_starts_with($key, 'db') && \is_string($value) && preg_match('/keys=(\d+)/', $value, $matches)) { + $totalKeys += (int) $matches[1]; + } + } + + $result[] = [ + 'name' => $name, + 'version' => (string) ($info['redis_version'] ?? 'unknown'), + 'uptime' => (int) ($info['uptime_in_seconds'] ?? 0), + 'hits' => $hits, + 'misses' => $misses, + 'hitRate' => $total > 0 ? round($hits / $total * 100, 2) : 0.0, + 'usedMemory' => (int) ($info['used_memory'] ?? 0), + 'peakMemory' => (int) ($info['used_memory_peak'] ?? 0), + 'maxMemory' => (int) ($info['maxmemory'] ?? 0), + 'evictedKeys' => (int) ($info['evicted_keys'] ?? 0), + 'expiredKeys' => (int) ($info['expired_keys'] ?? 0), + 'totalKeys' => $totalKeys, + 'connectedClients' => (int) ($info['connected_clients'] ?? 0), + 'opsPerSec' => (int) ($info['instantaneous_ops_per_sec'] ?? 0), + ]; + } + + return $result; + } +} diff --git a/src/Components/DatabaseStatisticsService.php b/src/Components/DatabaseStatisticsService.php new file mode 100644 index 00000000..91ac95b7 --- /dev/null +++ b/src/Components/DatabaseStatisticsService.php @@ -0,0 +1,159 @@ +connection->fetchOne('SELECT VERSION()'); + + $statusVars = $this->fetchGlobalStatusMap([ + 'Uptime', + 'Threads_connected', + 'Questions', + 'Slow_queries', + 'Queries', + ]); + + $uptime = (int) ($statusVars['Uptime'] ?? 0); + $questions = (int) ($statusVars['Questions'] ?? 0); + + return [ + 'version' => \is_string($version) ? $version : 'unknown', + 'uptime' => $uptime, + 'threads' => (int) ($statusVars['Threads_connected'] ?? 0), + 'questions' => $questions, + 'slowQueries' => (int) ($statusVars['Slow_queries'] ?? 0), + 'queriesPerSecond' => $uptime > 0 ? round($questions / $uptime, 2) : 0.0, + ]; + } + + /** + * @return list + */ + public function getTableStatistics(): array + { + $rows = $this->connection->fetchAllAssociative( + 'SELECT TABLE_NAME, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH + FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() + ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC' + ); + + $result = []; + + foreach ($rows as $row) { + $dataSize = (int) ($row['DATA_LENGTH'] ?? 0); + $indexSize = (int) ($row['INDEX_LENGTH'] ?? 0); + + $result[] = [ + 'name' => (string) ($row['TABLE_NAME'] ?? ''), + 'engine' => $row['ENGINE'] !== null ? (string) $row['ENGINE'] : null, + 'rows' => (int) ($row['TABLE_ROWS'] ?? 0), + 'dataSize' => $dataSize, + 'indexSize' => $indexSize, + 'totalSize' => $dataSize + $indexSize, + ]; + } + + return $result; + } + + /** + * @return array{bufferPoolSize: int, bufferPoolUsed: int, bufferPoolHitRate: float, threadsConnected: int, threadsRunning: int, slowQueries: int, tmpDiskTables: int, tmpTables: int} + */ + public function getGlobalStatus(): array + { + $statusVars = $this->fetchGlobalStatusMap([ + 'Innodb_buffer_pool_read_requests', + 'Innodb_buffer_pool_reads', + 'Innodb_buffer_pool_pages_total', + 'Innodb_buffer_pool_pages_free', + 'Threads_connected', + 'Threads_running', + 'Slow_queries', + 'Created_tmp_disk_tables', + 'Created_tmp_tables', + ]); + + $variables = $this->fetchGlobalVariableMap([ + 'innodb_buffer_pool_size', + ]); + + $bufferPoolSize = (int) ($variables['innodb_buffer_pool_size'] ?? 0); + $pagesTotal = (int) ($statusVars['Innodb_buffer_pool_pages_total'] ?? 0); + $pagesFree = (int) ($statusVars['Innodb_buffer_pool_pages_free'] ?? 0); + $bufferPoolUsed = $pagesTotal > 0 && $bufferPoolSize > 0 + ? (int) (($pagesTotal - $pagesFree) / $pagesTotal * $bufferPoolSize) + : 0; + + $readRequests = (int) ($statusVars['Innodb_buffer_pool_read_requests'] ?? 0); + $diskReads = (int) ($statusVars['Innodb_buffer_pool_reads'] ?? 0); + $hitRate = $readRequests > 0 + ? round(($readRequests - $diskReads) / $readRequests * 100, 2) + : 0.0; + + return [ + 'bufferPoolSize' => $bufferPoolSize, + 'bufferPoolUsed' => $bufferPoolUsed, + 'bufferPoolHitRate' => $hitRate, + 'threadsConnected' => (int) ($statusVars['Threads_connected'] ?? 0), + 'threadsRunning' => (int) ($statusVars['Threads_running'] ?? 0), + 'slowQueries' => (int) ($statusVars['Slow_queries'] ?? 0), + 'tmpDiskTables' => (int) ($statusVars['Created_tmp_disk_tables'] ?? 0), + 'tmpTables' => (int) ($statusVars['Created_tmp_tables'] ?? 0), + ]; + } + + /** + * @param list $names + * @return array + */ + private function fetchGlobalStatusMap(array $names): array + { + $rows = $this->connection->fetchAllAssociative( + 'SHOW GLOBAL STATUS WHERE Variable_name IN (?)', + [$names], + [\Doctrine\DBAL\ArrayParameterType::STRING] + ); + + $map = []; + foreach ($rows as $row) { + $map[(string) $row['Variable_name']] = (string) $row['Value']; + } + + return $map; + } + + /** + * @param list $names + * @return array + */ + private function fetchGlobalVariableMap(array $names): array + { + $rows = $this->connection->fetchAllAssociative( + 'SHOW GLOBAL VARIABLES WHERE Variable_name IN (?)', + [$names], + [\Doctrine\DBAL\ArrayParameterType::STRING] + ); + + $map = []; + foreach ($rows as $row) { + $map[(string) $row['Variable_name']] = (string) $row['Value']; + } + + return $map; + } +} diff --git a/src/Controller/StatisticsController.php b/src/Controller/StatisticsController.php new file mode 100644 index 00000000..44d90820 --- /dev/null +++ b/src/Controller/StatisticsController.php @@ -0,0 +1,41 @@ + ['api'], '_acl' => ['frosh_tools:read']])] +class StatisticsController extends AbstractController +{ + public function __construct( + private readonly CacheStatisticsService $cacheStatisticsService, + private readonly DatabaseStatisticsService $databaseStatisticsService, + ) { + } + + #[Route(path: '/cache', name: 'api.frosh.tools.statistics.cache', methods: ['GET'])] + public function cacheStatistics(): JsonResponse + { + return new JsonResponse([ + 'opcache' => $this->cacheStatisticsService->getOpcacheStatistics(), + 'apcu' => $this->cacheStatisticsService->getApcuStatistics(), + 'redis' => $this->cacheStatisticsService->getRedisStatistics(), + ]); + } + + #[Route(path: '/database', name: 'api.frosh.tools.statistics.database', methods: ['GET'])] + public function databaseStatistics(): JsonResponse + { + return new JsonResponse([ + 'server' => $this->databaseStatisticsService->getServerInfo(), + 'tables' => $this->databaseStatisticsService->getTableStatistics(), + 'globalStatus' => $this->databaseStatisticsService->getGlobalStatus(), + ]); + } +} diff --git a/src/Resources/app/administration/src/api/frosh-tools.js b/src/Resources/app/administration/src/api/frosh-tools.js index 1f1767bd..b1a5e935 100644 --- a/src/Resources/app/administration/src/api/frosh-tools.js +++ b/src/Resources/app/administration/src/api/frosh-tools.js @@ -304,6 +304,28 @@ class FroshTools extends ApiService { return ApiService.handleResponse(response); }); } + + getCacheStatistics() { + const apiRoute = `${this.getApiBasePath()}/statistics/cache`; + return this.httpClient + .get(apiRoute, { + headers: this.getBasicHeaders(), + }) + .then((response) => { + return ApiService.handleResponse(response); + }); + } + + getDatabaseStatistics() { + const apiRoute = `${this.getApiBasePath()}/statistics/database`; + return this.httpClient + .get(apiRoute, { + headers: this.getBasicHeaders(), + }) + .then((response) => { + return ApiService.handleResponse(response); + }); + } } export default FroshTools; diff --git a/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/index.js b/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/index.js new file mode 100644 index 00000000..241c4837 --- /dev/null +++ b/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/index.js @@ -0,0 +1,166 @@ +import template from './template.twig'; +import './style.scss'; + +const { Component } = Shopware; + +Component.register('frosh-tools-tab-statistics', { + template, + + inject: ['froshToolsService'], + + data() { + return { + cacheStats: null, + dbStats: null, + isLoadingCache: true, + isLoadingDb: true, + numberFormatter: null, + percentFormatter: null, + }; + }, + + created() { + const language = + Shopware.Application.getContainer( + 'factory' + ).locale.getLastKnownLocale(); + this.numberFormatter = new Intl.NumberFormat(language, { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }); + this.percentFormatter = new Intl.NumberFormat(language, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + + this.loadData(); + }, + + computed: { + isLoading() { + return this.isLoadingCache || this.isLoadingDb; + }, + + tableColumns() { + return [ + { + property: 'name', + label: this.$t('frosh-tools.tabs.statistics.tableName'), + rawData: true, + allowResize: true, + }, + { + property: 'engine', + label: this.$t('frosh-tools.tabs.statistics.engine'), + rawData: true, + allowResize: true, + }, + { + property: 'rows', + label: this.$t('frosh-tools.tabs.statistics.rows'), + rawData: true, + align: 'right', + allowResize: true, + }, + { + property: 'dataSize', + label: this.$t('frosh-tools.tabs.statistics.dataSize'), + rawData: true, + align: 'right', + allowResize: true, + }, + { + property: 'indexSize', + label: this.$t('frosh-tools.tabs.statistics.indexSize'), + rawData: true, + align: 'right', + allowResize: true, + }, + { + property: 'totalSize', + label: this.$t('frosh-tools.tabs.statistics.totalSize'), + rawData: true, + align: 'right', + allowResize: true, + }, + ]; + }, + }, + + methods: { + loadData() { + this.loadCacheStats(); + this.loadDbStats(); + }, + + async loadCacheStats() { + this.isLoadingCache = true; + try { + this.cacheStats = await this.froshToolsService.getCacheStatistics(); + } catch { + this.cacheStats = null; + } + this.isLoadingCache = false; + }, + + async loadDbStats() { + this.isLoadingDb = true; + try { + this.dbStats = await this.froshToolsService.getDatabaseStatistics(); + } catch { + this.dbStats = null; + } + this.isLoadingDb = false; + }, + + formatSize(bytes) { + if (bytes >= 1024 * 1024 * 1024) { + return this.percentFormatter.format(bytes / (1024 * 1024 * 1024)) + ' GiB'; + } + + if (bytes >= 1024 * 1024) { + return this.percentFormatter.format(bytes / (1024 * 1024)) + ' MiB'; + } + + if (bytes >= 1024) { + return this.percentFormatter.format(bytes / 1024) + ' KiB'; + } + + return this.numberFormatter.format(bytes) + ' B'; + }, + + formatNumber(number) { + return this.numberFormatter.format(number); + }, + + formatPercent(value) { + return this.percentFormatter.format(value) + ' %'; + }, + + formatDecimal(value) { + return this.percentFormatter.format(value); + }, + + formatUptime(seconds) { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (days > 0) { + return `${days}d ${hours}h ${minutes}m`; + } + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + + return `${minutes}m`; + }, + + hitRateVariant(rate) { + if (rate >= 95) return 'success'; + if (rate >= 80) return 'warning'; + return 'danger'; + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/style.scss b/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/style.scss new file mode 100644 index 00000000..0b13190d --- /dev/null +++ b/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/style.scss @@ -0,0 +1,42 @@ +.frosh-tools-tab-statistics { + &__metrics { + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0; + } + + .frosh-tools-stat { + padding: 12px 10px; + + &__label { + font-size: 12px; + text-align: center; + margin-bottom: 6px; + } + + &__value { + font-size: 16px; + line-height: 1.3; + text-align: center; + word-break: break-word; + } + } + } +} + +.frosh-tools-stat { + &__value { + &--success { + color: #37d046; + } + + &--warning { + color: #ffab22; + } + + &--danger { + color: #de294c; + } + } +} diff --git a/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/template.twig b/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/template.twig new file mode 100644 index 00000000..6e7be0c6 --- /dev/null +++ b/src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-statistics/template.twig @@ -0,0 +1,399 @@ + + + + + + + + + OPcache: {{ $t('frosh-tools.tabs.statistics.notAvailable') }} + + + + + + +
+
+ {{ $t('frosh-tools.tabs.statistics.hitRate') }} +
+
+ {{ formatPercent(redis.hitRate) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.hits') }} / {{ $t('frosh-tools.tabs.statistics.misses') }} +
+
+ {{ formatNumber(redis.hits) }} / {{ formatNumber(redis.misses) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.usedMemory') }} / {{ $t('frosh-tools.tabs.statistics.maxMemory') }} +
+
+ {{ formatSize(redis.usedMemory) }} + +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.peakMemory') }} +
+
+ {{ formatSize(redis.peakMemory) }} +
+
+
+ +
+
+ {{ $t('frosh-tools.tabs.statistics.totalKeys') }} +
+
+ {{ formatNumber(redis.totalKeys) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.evictedKeys') }} / {{ $t('frosh-tools.tabs.statistics.expiredKeys') }} +
+
+ {{ formatNumber(redis.evictedKeys) }} / {{ formatNumber(redis.expiredKeys) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.opsPerSec') }} / {{ $t('frosh-tools.tabs.statistics.connectedClients') }} +
+
+ {{ formatNumber(redis.opsPerSec) }} / {{ formatNumber(redis.connectedClients) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.version') }} / {{ $t('frosh-tools.tabs.statistics.uptime') }} +
+
+ {{ redis.version }} / {{ formatUptime(redis.uptime) }} +
+
+
+
+ + + + +
+
+ {{ $t('frosh-tools.tabs.statistics.hitRate') }} +
+
+ {{ formatPercent(cacheStats.apcu.hitRate) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.hits') }} / {{ $t('frosh-tools.tabs.statistics.misses') }} +
+
+ {{ formatNumber(cacheStats.apcu.hits) }} / {{ formatNumber(cacheStats.apcu.misses) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.usedMemory') }} / {{ $t('frosh-tools.tabs.statistics.availableMemory') }} +
+
+ {{ formatSize(cacheStats.apcu.usedMemory) }} / {{ formatSize(cacheStats.apcu.availableMemory) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.entries') }} / {{ $t('frosh-tools.tabs.statistics.fragmentation') }} +
+
+ {{ formatNumber(cacheStats.apcu.entries) }} / {{ formatPercent(cacheStats.apcu.fragmentation) }} +
+
+
+
+ + + + +
+
+ {{ $t('frosh-tools.tabs.statistics.version') }} +
+
+ {{ dbStats.server.version }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.uptime') }} +
+
+ {{ formatUptime(dbStats.server.uptime) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.queriesPerSecond') }} +
+
+ {{ formatDecimal(dbStats.server.queriesPerSecond) }} +
+
+
+ +
+
+ {{ $t('frosh-tools.tabs.statistics.threads') }} +
+
+ {{ formatNumber(dbStats.server.threads) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.slowQueries') }} +
+
+ {{ formatNumber(dbStats.server.slowQueries) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.totalQueries') }} +
+
+ {{ formatNumber(dbStats.server.questions) }} +
+
+
+
+ + + + +
+
+ {{ $t('frosh-tools.tabs.statistics.bufferPoolHitRate') }} +
+
+ {{ formatPercent(dbStats.globalStatus.bufferPoolHitRate) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.usedMemory') }} / {{ $t('frosh-tools.tabs.statistics.totalMemory') }} +
+
+ {{ formatSize(dbStats.globalStatus.bufferPoolUsed) }} / {{ formatSize(dbStats.globalStatus.bufferPoolSize) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.threadsConnected') }} / {{ $t('frosh-tools.tabs.statistics.threadsRunning') }} +
+
+ {{ formatNumber(dbStats.globalStatus.threadsConnected) }} / {{ formatNumber(dbStats.globalStatus.threadsRunning) }} +
+
+
+
+ {{ $t('frosh-tools.tabs.statistics.tmpDiskTables') }} / {{ $t('frosh-tools.tabs.statistics.tmpTables') }} +
+
+ {{ formatNumber(dbStats.globalStatus.tmpDiskTables) }} / {{ formatNumber(dbStats.globalStatus.tmpTables) }} +
+
+
+
+ + + + + +
diff --git a/src/Resources/app/administration/src/module/frosh-tools/index.js b/src/Resources/app/administration/src/module/frosh-tools/index.js index f18b1e53..61673214 100644 --- a/src/Resources/app/administration/src/module/frosh-tools/index.js +++ b/src/Resources/app/administration/src/module/frosh-tools/index.js @@ -8,9 +8,13 @@ import './component/frosh-tools-tab-logs'; import './component/frosh-tools-tab-state-machines'; import './component/frosh-tools-tab-files'; import './component/frosh-tools-tab-fastly'; +import './component/frosh-tools-tab-statistics'; import './page/index'; import './acl'; +import enGB from './snippet/en-GB.json'; +import deDE from './snippet/de-DE.json'; + Shopware.Module.register('frosh-tools', { type: 'plugin', name: 'frosh-tools.title', @@ -20,6 +24,11 @@ Shopware.Module.register('frosh-tools', { icon: 'regular-cog', + snippets: { + 'de-DE': deDE, + 'en-GB': enGB, + }, + routes: { index: { component: 'frosh-tools-index', @@ -105,6 +114,14 @@ Shopware.Module.register('frosh-tools', { parentPath: 'sw.settings.index.plugins', }, }, + statistics: { + component: 'frosh-tools-tab-statistics', + path: 'statistics', + meta: { + privilege: 'frosh_tools:read', + parentPath: 'sw.settings.index.plugins', + }, + }, }, }, }, diff --git a/src/Resources/app/administration/src/module/frosh-tools/page/index/template.twig b/src/Resources/app/administration/src/module/frosh-tools/page/index/template.twig index b4f11731..e7eeb893 100644 --- a/src/Resources/app/administration/src/module/frosh-tools/page/index/template.twig +++ b/src/Resources/app/administration/src/module/frosh-tools/page/index/template.twig @@ -40,6 +40,9 @@ > {{ $t('frosh-tools.tabs.fastly.title') }} + + {{ $t('frosh-tools.tabs.statistics.title') }} + diff --git a/src/Resources/app/administration/src/module/frosh-tools/snippet/de-DE.json b/src/Resources/app/administration/src/module/frosh-tools/snippet/de-DE.json index 7f3c2883..7db6804b 100644 --- a/src/Resources/app/administration/src/module/frosh-tools/snippet/de-DE.json +++ b/src/Resources/app/administration/src/module/frosh-tools/snippet/de-DE.json @@ -88,6 +88,51 @@ "transactionTitle": "Bezahlstatus", "deliveryTitle": "Versandstatus" }, + "statistics": { + "title": "Statistiken", + "hitRate": "Trefferquote", + "hits": "Treffer", + "misses": "Fehlschläge", + "usedMemory": "Belegt", + "freeMemory": "Frei", + "maxMemory": "Max", + "totalMemory": "Gesamt", + "wastedMemory": "Verschwendeter Speicher", + "cachedScripts": "Gecachte Skripte", + "internedStrings": "Interned Strings", + "lastRestart": "Letzter Neustart", + "never": "Nie", + "notAvailable": "Nicht verfügbar", + "peakMemory": "Spitzenspeicher", + "evictedKeys": "Verdrängt", + "expiredKeys": "Abgelaufen", + "totalKeys": "Schlüssel gesamt", + "connectedClients": "Clients", + "opsPerSec": "Ops/sek", + "version": "Version", + "uptime": "Laufzeit", + "entries": "Einträge", + "fragmentation": "Fragmentierung", + "availableMemory": "Verfügbar", + "serverInfo": "Datenbank-Server", + "threads": "Threads", + "slowQueries": "Langsame Abfragen", + "queriesPerSecond": "Abfragen/sek", + "totalQueries": "Abfragen gesamt", + "bufferPool": "InnoDB Buffer Pool", + "bufferPoolHitRate": "Buffer Pool Trefferquote", + "threadsConnected": "Verbunden", + "threadsRunning": "Aktiv", + "tmpDiskTables": "Tmp Disk Tabellen", + "tmpTables": "Tmp Tabellen", + "tableStatistics": "Tabellengrößen", + "tableName": "Tabelle", + "engine": "Engine", + "rows": "Zeilen", + "dataSize": "Datengröße", + "indexSize": "Indexgröße", + "totalSize": "Gesamtgröße" + }, "fastly": { "title": "Fastly", "statsTitle": "Fastly Statistiken", diff --git a/src/Resources/app/administration/src/module/frosh-tools/snippet/en-GB.json b/src/Resources/app/administration/src/module/frosh-tools/snippet/en-GB.json index 345bf8c7..51880848 100644 --- a/src/Resources/app/administration/src/module/frosh-tools/snippet/en-GB.json +++ b/src/Resources/app/administration/src/module/frosh-tools/snippet/en-GB.json @@ -88,6 +88,51 @@ "transactionTitle": "Transaction status", "deliveryTitle": "Delivery status" }, + "statistics": { + "title": "Statistics", + "hitRate": "Hit Rate", + "hits": "Hits", + "misses": "Misses", + "usedMemory": "Used", + "freeMemory": "Free", + "maxMemory": "Max", + "totalMemory": "Total", + "wastedMemory": "Wasted Memory", + "cachedScripts": "Cached Scripts", + "internedStrings": "Interned Strings", + "lastRestart": "Last Restart", + "never": "Never", + "notAvailable": "Not available", + "peakMemory": "Peak Memory", + "evictedKeys": "Evicted", + "expiredKeys": "Expired", + "totalKeys": "Total Keys", + "connectedClients": "Clients", + "opsPerSec": "Ops/sec", + "version": "Version", + "uptime": "Uptime", + "entries": "Entries", + "fragmentation": "Fragmentation", + "availableMemory": "Available", + "serverInfo": "Database Server", + "threads": "Threads", + "slowQueries": "Slow Queries", + "queriesPerSecond": "Queries/sec", + "totalQueries": "Total Queries", + "bufferPool": "InnoDB Buffer Pool", + "bufferPoolHitRate": "Buffer Pool Hit Rate", + "threadsConnected": "Connected", + "threadsRunning": "Running", + "tmpDiskTables": "Tmp Disk Tables", + "tmpTables": "Tmp Tables", + "tableStatistics": "Table Sizes", + "tableName": "Table", + "engine": "Engine", + "rows": "Rows", + "dataSize": "Data Size", + "indexSize": "Index Size", + "totalSize": "Total Size" + }, "fastly": { "title": "Fastly", "statsTitle": "Fastly Statistics",