New page at /admin/system-info with three tabs: - Overview: PHP version, SAPI, environment, 6 health checks (DB, schema, storage, RBAC baseline, admin role, scheduler heartbeat) - Modules: table of active modules with version, dependencies, permission count - Permissions: active/inactive counts and per-source breakdown Gated behind new system_info.view permission assigned to Admin role. No mutations — purely diagnostic/observability. Includes SystemHealthService, SystemInfoService, SystemHealthRepository with interface, DI registration, i18n keys (de+en), idempotent DB update script, and 17 new PHPUnit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
122 lines
3.4 KiB
PHP
122 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Service\System;
|
|
|
|
use MintyPHP\App\Module\ModuleRegistry;
|
|
use MintyPHP\Repository\System\SystemHealthRepositoryInterface;
|
|
|
|
class SystemInfoService
|
|
{
|
|
public function __construct(
|
|
private readonly SystemHealthService $healthService,
|
|
private readonly SystemHealthRepositoryInterface $healthRepository,
|
|
private readonly ?ModuleRegistry $moduleRegistry = null
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function buildPageData(): array
|
|
{
|
|
return [
|
|
'overview' => $this->buildOverview(),
|
|
'modules' => $this->buildModuleInventory(),
|
|
'permissions' => $this->buildPermissionSummary(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function buildOverview(): array
|
|
{
|
|
return [
|
|
'php_version' => PHP_VERSION,
|
|
'php_sapi' => PHP_SAPI,
|
|
'app_environment' => $this->resolveEnvironment(),
|
|
'health_checks' => $this->healthService->runAll(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{id: string, version: string, enabled_by_default: bool, requires: list<string>, permissions_count: int}>
|
|
*/
|
|
private function buildModuleInventory(): array
|
|
{
|
|
if ($this->moduleRegistry === null) {
|
|
return [];
|
|
}
|
|
|
|
$modules = [];
|
|
foreach ($this->moduleRegistry->getModules() as $manifest) {
|
|
$modules[] = [
|
|
'id' => $manifest->id,
|
|
'version' => $manifest->version,
|
|
'enabled_by_default' => $manifest->enabledByDefault,
|
|
'requires' => $manifest->requires,
|
|
'permissions_count' => count($manifest->permissions),
|
|
];
|
|
}
|
|
|
|
usort($modules, static fn (array $a, array $b): int => strcmp($a['id'], $b['id']));
|
|
|
|
return $modules;
|
|
}
|
|
|
|
/**
|
|
* @return array{active_count: int, inactive_count: int, by_source: array<string, int>}
|
|
*/
|
|
private function buildPermissionSummary(): array
|
|
{
|
|
$activeKeys = $this->healthRepository->listAllActivePermissionKeys();
|
|
$bySource = $this->derivePermissionsBySource($activeKeys);
|
|
|
|
return [
|
|
'active_count' => $this->healthRepository->countActivePermissions(),
|
|
'inactive_count' => $this->healthRepository->countInactivePermissions(),
|
|
'by_source' => $bySource,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $activeKeys
|
|
* @return array<string, int>
|
|
*/
|
|
private function derivePermissionsBySource(array $activeKeys): array
|
|
{
|
|
$moduleKeys = [];
|
|
if ($this->moduleRegistry !== null) {
|
|
foreach ($this->moduleRegistry->getModules() as $manifest) {
|
|
foreach ($manifest->permissions as $perm) {
|
|
$key = $perm['key'] ?? '';
|
|
if ($key !== '') {
|
|
$moduleKeys[$key] = $manifest->id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$counts = [];
|
|
foreach ($activeKeys as $key) {
|
|
$source = $moduleKeys[$key] ?? 'core';
|
|
$counts[$source] = ($counts[$source] ?? 0) + 1;
|
|
}
|
|
|
|
ksort($counts);
|
|
|
|
return $counts;
|
|
}
|
|
|
|
private function resolveEnvironment(): string
|
|
{
|
|
if (defined('APP_ENV')) {
|
|
return (string) APP_ENV;
|
|
}
|
|
|
|
$env = getenv('APP_ENV');
|
|
|
|
return is_string($env) && $env !== '' ? $env : 'unknown';
|
|
}
|
|
}
|