feat(system): page Status avec endpoint API Platform et composants Vue
- Nouveau domaine System/Domain/Model/SystemStatus (value object) - QueryHandler agrégeant métriques mangas, chapitres, jobs (global/24h/7j), stockage et sources - Endpoint GET /api/system/status via API Platform (singleton) - Calcul de l'espace disque par RecursiveDirectoryIterator sur public/images - Page Vue /system/status avec 6 cards (Mangas, Chapitres, Jobs, Stockage, Sources, Système) - Nettoyage du router : suppression des PlaceholderComponent et routes placeholder - Sidebar : suppression des entrées sans page réelle
This commit is contained in:
parent
c2b55e9018
commit
ca8791cc0d
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\System\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Domain\System\Infrastructure\ApiPlatform\State\Provider\GetSystemStatusStateProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'System',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/system/status',
|
||||
provider: GetSystemStatusStateProvider::class,
|
||||
)
|
||||
]
|
||||
)]
|
||||
class GetSystemStatusResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public string $id = 'current';
|
||||
|
||||
public int $totalMangas = 0;
|
||||
public int $monitoredMangas = 0;
|
||||
/** @var array<string, int> */
|
||||
public array $mangasByStatus = [];
|
||||
public int $totalChapters = 0;
|
||||
public int $downloadedChapters = 0;
|
||||
public int $pendingChapters = 0;
|
||||
public int $totalJobs = 0;
|
||||
public int $completedJobs = 0;
|
||||
public int $failedJobs = 0;
|
||||
public int $pendingJobs = 0;
|
||||
public int $inProgressJobs = 0;
|
||||
public int $totalJobsLast24h = 0;
|
||||
public int $completedJobsLast24h = 0;
|
||||
public int $failedJobsLast24h = 0;
|
||||
public float $successRateLast24h = 0.0;
|
||||
public int $totalJobsLast7d = 0;
|
||||
public int $completedJobsLast7d = 0;
|
||||
public int $failedJobsLast7d = 0;
|
||||
public float $successRateLast7d = 0.0;
|
||||
public string $storagePath = '';
|
||||
public int $storageTotalBytes = 0;
|
||||
public int $storageFreeBytes = 0;
|
||||
public int $storageUsedBytes = 0;
|
||||
public string $storageTotalHuman = '';
|
||||
public string $storageFreeHuman = '';
|
||||
public string $storageUsedHuman = '';
|
||||
public int $totalSources = 0;
|
||||
/** @var array<string, int> */
|
||||
public array $sourcesByHealth = [];
|
||||
public string $phpVersion = '';
|
||||
public string $generatedAt = '';
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\System\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\System\Application\Query\GetSystemStatusQuery;
|
||||
use App\Domain\System\Application\QueryHandler\GetSystemStatusQueryHandler;
|
||||
use App\Domain\System\Domain\Model\SystemStatus;
|
||||
use App\Domain\System\Infrastructure\ApiPlatform\Resource\GetSystemStatusResource;
|
||||
|
||||
final class GetSystemStatusStateProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GetSystemStatusQueryHandler $handler,
|
||||
) {
|
||||
}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): GetSystemStatusResource
|
||||
{
|
||||
$response = $this->handler->handle(new GetSystemStatusQuery());
|
||||
|
||||
return $this->toResource($response);
|
||||
}
|
||||
|
||||
private function toResource(SystemStatus $response): GetSystemStatusResource
|
||||
{
|
||||
$resource = new GetSystemStatusResource();
|
||||
$resource->id = 'current';
|
||||
|
||||
$resource->totalMangas = $response->totalMangas;
|
||||
$resource->monitoredMangas = $response->monitoredMangas;
|
||||
$resource->mangasByStatus = $response->mangasByStatus;
|
||||
|
||||
$resource->totalChapters = $response->totalChapters;
|
||||
$resource->downloadedChapters = $response->downloadedChapters;
|
||||
$resource->pendingChapters = $response->totalChapters - $response->downloadedChapters;
|
||||
|
||||
$resource->totalJobs = $response->totalJobs;
|
||||
$resource->completedJobs = $response->completedJobs;
|
||||
$resource->failedJobs = $response->failedJobs;
|
||||
$resource->pendingJobs = $response->pendingJobs;
|
||||
$resource->inProgressJobs = $response->inProgressJobs;
|
||||
|
||||
$resource->totalJobsLast24h = $response->totalJobsLast24h;
|
||||
$resource->completedJobsLast24h = $response->completedJobsLast24h;
|
||||
$resource->failedJobsLast24h = $response->failedJobsLast24h;
|
||||
$resource->successRateLast24h = $response->totalJobsLast24h > 0
|
||||
? round($response->completedJobsLast24h / $response->totalJobsLast24h * 100, 1)
|
||||
: 0.0;
|
||||
|
||||
$resource->totalJobsLast7d = $response->totalJobsLast7d;
|
||||
$resource->completedJobsLast7d = $response->completedJobsLast7d;
|
||||
$resource->failedJobsLast7d = $response->failedJobsLast7d;
|
||||
$resource->successRateLast7d = $response->totalJobsLast7d > 0
|
||||
? round($response->completedJobsLast7d / $response->totalJobsLast7d * 100, 1)
|
||||
: 0.0;
|
||||
|
||||
$resource->storagePath = $response->storagePath;
|
||||
$resource->storageTotalBytes = $response->storageTotalBytes;
|
||||
$resource->storageFreeBytes = $response->storageFreeBytes;
|
||||
$resource->storageUsedBytes = $response->storageUsedBytes;
|
||||
$resource->storageTotalHuman = $this->formatBytes($response->storageTotalBytes);
|
||||
$resource->storageFreeHuman = $this->formatBytes($response->storageFreeBytes);
|
||||
$resource->storageUsedHuman = $this->formatBytes($response->storageUsedBytes);
|
||||
|
||||
$resource->totalSources = $response->totalSources;
|
||||
$resource->sourcesByHealth = $response->sourcesByHealth;
|
||||
|
||||
$resource->phpVersion = $response->phpVersion;
|
||||
$resource->generatedAt = $response->generatedAt->format(\DateTimeInterface::ATOM);
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
private function formatBytes(int $bytes): string
|
||||
{
|
||||
if ($bytes <= 0) {
|
||||
return '0 B';
|
||||
}
|
||||
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$exp = (int) floor(log($bytes, 1024));
|
||||
$exp = min($exp, count($units) - 1);
|
||||
|
||||
return round($bytes / (1024 ** $exp), 2) . ' ' . $units[$exp];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\System\Infrastructure\Persistence\Repository;
|
||||
|
||||
use App\Domain\System\Domain\Contract\Repository\SystemStatusRepositoryInterface;
|
||||
use App\Entity\Chapter;
|
||||
use App\Entity\ContentSource;
|
||||
use App\Entity\Manga;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class DoctrineSystemStatusRepository implements SystemStatusRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function countMangas(): int
|
||||
{
|
||||
return (int) $this->entityManager
|
||||
->createQuery('SELECT COUNT(m) FROM ' . Manga::class . ' m')
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countMonitoredMangas(): int
|
||||
{
|
||||
return (int) $this->entityManager
|
||||
->createQuery('SELECT COUNT(m) FROM ' . Manga::class . ' m WHERE m.monitored = true')
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countMangasByStatus(): array
|
||||
{
|
||||
$results = $this->entityManager
|
||||
->createQuery('SELECT m.status, COUNT(m) as cnt FROM ' . Manga::class . ' m GROUP BY m.status')
|
||||
->getResult();
|
||||
|
||||
$counts = [];
|
||||
foreach ($results as $row) {
|
||||
$status = $row['status'] ?? 'unknown';
|
||||
$counts[$status] = (int) $row['cnt'];
|
||||
}
|
||||
|
||||
return $counts;
|
||||
}
|
||||
|
||||
public function countChapters(): int
|
||||
{
|
||||
return (int) $this->entityManager
|
||||
->createQuery('SELECT COUNT(c) FROM ' . Chapter::class . ' c')
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countDownloadedChapters(): int
|
||||
{
|
||||
return (int) $this->entityManager
|
||||
->createQuery('SELECT COUNT(c) FROM ' . Chapter::class . ' c WHERE c.cbzPath IS NOT NULL')
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countContentSources(): int
|
||||
{
|
||||
return (int) $this->entityManager
|
||||
->createQuery('SELECT COUNT(s) FROM ' . ContentSource::class . ' s')
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countContentSourcesByHealth(): array
|
||||
{
|
||||
$results = $this->entityManager
|
||||
->createQuery('SELECT s.healthStatus, COUNT(s) as cnt FROM ' . ContentSource::class . ' s GROUP BY s.healthStatus')
|
||||
->getResult();
|
||||
|
||||
$counts = [];
|
||||
foreach ($results as $row) {
|
||||
$counts[$row['healthStatus']] = (int) $row['cnt'];
|
||||
}
|
||||
|
||||
return $counts;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user