+
{{ t('environments.health.memory') }}
{{ healthByEnvId[env.id!].memoryUsage }} / {{ healthByEnvId[env.id!].memoryLimit }}
@@ -440,11 +443,13 @@ onMounted(loadApplication)
@@ -473,48 +478,59 @@ onMounted(loadApplication)
-
-
{{ t('environments.logFiles.title') }}
+
+
{{ t('environments.logFiles.title') }}
diff --git a/frontend/pages/applications/index.vue b/frontend/pages/applications/index.vue
index c9711a2..76ef6b0 100644
--- a/frontend/pages/applications/index.vue
+++ b/frontend/pages/applications/index.vue
@@ -130,11 +130,13 @@ onMounted(loadApplications)
diff --git a/src/Entity/Application.php b/src/Entity/Application.php
index 4959169..b7c8715 100644
--- a/src/Entity/Application.php
+++ b/src/Entity/Application.php
@@ -12,6 +12,7 @@ use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\ApplicationRepository;
+use App\State\ApplicationProvider;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@@ -24,11 +25,13 @@ use Symfony\Component\Serializer\Attribute\Groups;
new GetCollection(
normalizationContext: ['groups' => ['app:read']],
security: "is_granted('ROLE_ADMIN')",
+ provider: ApplicationProvider::class,
),
new Get(
uriVariables: ['slug'],
normalizationContext: ['groups' => ['app:read', 'app:detail']],
security: "is_granted('ROLE_ADMIN')",
+ provider: ApplicationProvider::class,
),
new Post(
security: "is_granted('ROLE_ADMIN')",
diff --git a/src/Entity/Environment.php b/src/Entity/Environment.php
index ecb3d7e..761ed13 100644
--- a/src/Entity/Environment.php
+++ b/src/Entity/Environment.php
@@ -196,6 +196,13 @@ class Environment
public function getMaintenance(): bool
{
- return file_exists((string) $this->maintenanceFilePath);
+ return $this->maintenance ?? false;
+ }
+
+ public function setMaintenance(bool $maintenance): static
+ {
+ $this->maintenance = $maintenance;
+
+ return $this;
}
}
diff --git a/src/Service/AppPathResolver.php b/src/Service/AppPathResolver.php
new file mode 100644
index 0000000..edceab5
--- /dev/null
+++ b/src/Service/AppPathResolver.php
@@ -0,0 +1,20 @@
+basePath, '/') . '/' . ltrim($relativePath, '/');
+ }
+}
diff --git a/src/Service/DeployService.php b/src/Service/DeployService.php
index f53b5ad..02e8848 100644
--- a/src/Service/DeployService.php
+++ b/src/Service/DeployService.php
@@ -7,19 +7,33 @@ namespace App\Service;
use App\Entity\Environment;
use Symfony\Component\Process\Process;
-final class DeployService
+final readonly class DeployService
{
+ public function __construct(
+ private AppPathResolver $pathResolver,
+ ) {}
+
/**
* @return array{success: bool, output: string, exitCode: int}
*/
public function deploy(Environment $environment, string $tag): array
{
- $scriptPath = $environment->getDeployScriptPath();
+ $relativePath = $environment->getDeployScriptPath();
- if (null === $scriptPath || !file_exists($scriptPath)) {
+ if (null === $relativePath) {
return [
'success' => false,
- 'output' => sprintf('Deploy script not found: %s', $scriptPath ?? 'null'),
+ 'output' => 'Deploy script path is not configured.',
+ 'exitCode' => 1,
+ ];
+ }
+
+ $scriptPath = $this->pathResolver->resolve($relativePath);
+
+ if (!file_exists($scriptPath)) {
+ return [
+ 'success' => false,
+ 'output' => sprintf('Deploy script not found: %s', $scriptPath),
'exitCode' => 1,
];
}
diff --git a/src/State/ApplicationProvider.php b/src/State/ApplicationProvider.php
new file mode 100644
index 0000000..6231273
--- /dev/null
+++ b/src/State/ApplicationProvider.php
@@ -0,0 +1,54 @@
+applicationRepository->findAll();
+ foreach ($apps as $app) {
+ $this->resolveMaintenanceStatus($app);
+ }
+
+ return $apps;
+ }
+
+ $slug = $uriVariables['slug'] ?? '';
+ $app = $this->applicationRepository->findOneBy(['slug' => $slug]);
+
+ if (null === $app) {
+ throw new NotFoundHttpException(sprintf('Application "%s" not found.', $slug));
+ }
+
+ $this->resolveMaintenanceStatus($app);
+
+ return $app;
+ }
+
+ private function resolveMaintenanceStatus(Application $app): void
+ {
+ foreach ($app->getEnvironments() as $env) {
+ $path = $env->getMaintenanceFilePath();
+ if (null !== $path) {
+ $env->setMaintenance(file_exists($this->pathResolver->resolve($path)));
+ }
+ }
+ }
+}
diff --git a/src/State/MaintenanceToggleProcessor.php b/src/State/MaintenanceToggleProcessor.php
index a694651..42c90af 100644
--- a/src/State/MaintenanceToggleProcessor.php
+++ b/src/State/MaintenanceToggleProcessor.php
@@ -7,21 +7,27 @@ namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Environment;
+use App\Service\AppPathResolver;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final readonly class MaintenanceToggleProcessor implements ProcessorInterface
{
+ public function __construct(
+ private AppPathResolver $pathResolver,
+ ) {}
+
/**
* @param Environment $data
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Environment
{
- $maintenancePath = $data->getMaintenanceFilePath();
+ $relativePath = $data->getMaintenanceFilePath();
- if (null === $maintenancePath) {
+ if (null === $relativePath) {
throw new BadRequestHttpException('Maintenance file path is not configured for this environment.');
}
+ $maintenancePath = $this->pathResolver->resolve($relativePath);
$requestData = $context['request']?->toArray() ?? [];
$enableMaintenance = $requestData['maintenance'] ?? false;