Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e342c9aeb | ||
| 419d3b24cb | |||
|
|
777224709d | ||
| 0a4b0cdc14 | |||
| 3fd745196f | |||
| 8481fe8fef | |||
| 061ab13d2b | |||
|
|
656aaf816f | ||
| 36777e50a8 | |||
| fdd1182caf | |||
| 98237daa56 |
4
.env
4
.env
@@ -44,6 +44,10 @@ DEFAULT_URI=http://localhost
|
||||
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> apps ###
|
||||
APPS_BASE_PATH=/mnt/apps
|
||||
###< apps ###
|
||||
|
||||
###> gitea ###
|
||||
GITEA_API_URL=https://gitea.malio.fr
|
||||
GITEA_API_TOKEN=change_me_in_env_local
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"symfony/http-client": "8.0.*",
|
||||
"symfony/mime": "8.0.*",
|
||||
"symfony/monolog-bundle": "^4.0",
|
||||
"symfony/process": "8.0.*",
|
||||
"symfony/property-access": "8.0.*",
|
||||
"symfony/property-info": "8.0.*",
|
||||
"symfony/rate-limiter": "8.0.*",
|
||||
|
||||
132
composer.lock
generated
132
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bfd26e903d79f710cfe95452c05f2a25",
|
||||
"content-hash": "51813b5c3b6dacd3cc99cfe121ab918b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/doctrine-common",
|
||||
@@ -6234,6 +6234,71 @@
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v8.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
|
||||
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Process\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v8.0.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-30T15:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/property-access",
|
||||
"version": "v8.0.8",
|
||||
@@ -11018,71 +11083,6 @@
|
||||
],
|
||||
"time": "2024-10-20T05:08:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v8.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
|
||||
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Process\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v8.0.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-30T15:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "2.0.1",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
parameters:
|
||||
app.version: '0.1.17'
|
||||
app.version: '0.1.20'
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="border-t border-neutral-100 px-4 py-4 sm:px-8">
|
||||
<div class="flex justify-end gap-3">
|
||||
<div class="flex justify-center gap-3">
|
||||
<slot name="footer">
|
||||
<MalioButton
|
||||
:label="cancelLabel"
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"containerName": "Nom du container",
|
||||
"deployScriptPath": "Chemin du script de deploiement",
|
||||
"maintenanceFilePath": "Chemin du fichier de maintenance",
|
||||
"pathHint": "Prefixe automatique : /mnt/apps",
|
||||
"appUrl": "URL de l'application",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler"
|
||||
|
||||
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@@ -7,7 +7,7 @@
|
||||
"name": "nuxt-app",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@malio/layer-ui": "^1.2.0",
|
||||
"@malio/layer-ui": "^1.2.2",
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/i18n": "^10.2.3",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
@@ -1668,9 +1668,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@malio/layer-ui": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.2.1/layer-ui-1.2.1.tgz",
|
||||
"integrity": "sha512-kY6Jeg11wceSgeJ/OX0xsYMENfXogb+nGduP7yVmc6HHIwKDtpn7VLRcJPlhNBUsKAvcFNk6IU08o6izdTMEQg==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.2.2/layer-ui-1.2.2.tgz",
|
||||
"integrity": "sha512-nV4FL19rYSiXqMDTUlAtp6AYdj7YiwpHbf7/usiOPj7llpjHIC3GmcOX0X7oQeOMTtSU1aKL8k8wn1bhptrHYg==",
|
||||
"dependencies": {
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@malio/layer-ui": "^1.2.0",
|
||||
"@malio/layer-ui": "^1.2.2",
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/i18n": "^10.2.3",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
|
||||
@@ -256,20 +256,23 @@ onMounted(loadApplication)
|
||||
<p v-if="application.description" class="text-neutral-500 mt-2">{{ application.description }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<MalioButton
|
||||
:label="t('applications.detail.editButton')"
|
||||
variant="secondary"
|
||||
icon-name="mdi:pencil"
|
||||
icon-position="left"
|
||||
@click="openEditAppModal"
|
||||
/>
|
||||
<MalioButton
|
||||
:label="t('applications.detail.deleteButton')"
|
||||
variant="danger"
|
||||
icon-name="mdi:trash-can-outline"
|
||||
icon-position="left"
|
||||
@click="handleDeleteApp"
|
||||
/>
|
||||
<div class="flex items-center">
|
||||
<MalioButtonIcon
|
||||
:aria-label="t('applications.detail.editButton')"
|
||||
variant="filled"
|
||||
icon="mdi:pencil"
|
||||
@click="openEditAppModal"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<MalioButtonIcon
|
||||
:aria-label="t('applications.detail.editButton')"
|
||||
variant="filled"
|
||||
icon="mdi:trash-can-outline"
|
||||
button-class="bg-m-btn-danger hover:bg-m-btn-danger-hover active:bg-m-btn-danger-active text-white cursor-pointer"
|
||||
@click="handleDeleteApp"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -277,11 +280,11 @@ onMounted(loadApplication)
|
||||
<div class="rounded-lg bg-tertiary-500 p-5 mb-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-neutral-400">{{ t('applications.detail.registryImage') }} :</span>
|
||||
<span class="font-bold">{{ t('applications.detail.registryImage') }} :</span>
|
||||
<span class="text-neutral-800 ml-1 font-mono">{{ application.registryImage }}</span>
|
||||
</div>
|
||||
<div v-if="application.giteaUrl">
|
||||
<span class="text-neutral-400">{{ t('applications.detail.giteaUrl') }} :</span>
|
||||
<span class="font-bold">{{ t('applications.detail.giteaUrl') }} :</span>
|
||||
<a :href="application.giteaUrl" target="_blank" class="text-primary-500 hover:underline ml-1">
|
||||
{{ application.giteaUrl }}
|
||||
</a>
|
||||
@@ -354,7 +357,7 @@ onMounted(loadApplication)
|
||||
|
||||
<!-- Log files -->
|
||||
<div v-if="env.logFiles.length" class="mt-4 border-t border-neutral-200 pt-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-neutral-400 mb-2">{{ t('environments.logFiles.title') }}</p>
|
||||
<p class="text-sm font-bold uppercase tracking-wider mb-2">{{ t('environments.logFiles.title') }}</p>
|
||||
<div v-for="lf in env.logFiles" :key="lf.id" class="text-sm text-neutral-700 flex gap-2">
|
||||
<span class="font-medium">{{ lf.label }} :</span>
|
||||
<span class="font-mono text-neutral-400">{{ lf.path }}</span>
|
||||
@@ -362,9 +365,9 @@ onMounted(loadApplication)
|
||||
</div>
|
||||
|
||||
<!-- Health metrics -->
|
||||
<div v-if="healthByEnvId[env.id!]" class="mt-4 border-t border-neutral-200 pt-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-neutral-400 mb-3">{{ t('environments.health.title') }}</p>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<div v-if="healthByEnvId[env.id!]" class="mt-4 border-t border-neutral-200 py-3">
|
||||
<p class="text-sm font-bold uppercase tracking-wider mb-2">{{ t('environments.health.title') }}</p>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-5 gap-3">
|
||||
<div>
|
||||
<p class="text-xs text-neutral-400">{{ t('environments.health.status') }}</p>
|
||||
<span
|
||||
@@ -386,7 +389,7 @@ onMounted(loadApplication)
|
||||
<p class="text-xs text-neutral-400">{{ t('environments.health.cpu') }}</p>
|
||||
<p class="text-sm text-neutral-800 mt-1">{{ healthByEnvId[env.id!].cpuPercent }}%</p>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div>
|
||||
<p class="text-xs text-neutral-400">{{ t('environments.health.memory') }}</p>
|
||||
<p class="text-sm text-neutral-800 mt-1">
|
||||
{{ healthByEnvId[env.id!].memoryUsage }} / {{ healthByEnvId[env.id!].memoryLimit }}
|
||||
@@ -440,11 +443,13 @@ onMounted(loadApplication)
|
||||
<MalioInputText
|
||||
v-model="editForm.registryImage"
|
||||
:label="t('applications.form.registryImage')"
|
||||
hint="Ex : gitea.malio.fr/malio-dev/sirh"
|
||||
required
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="editForm.giteaUrl"
|
||||
:label="t('applications.form.giteaUrl')"
|
||||
hint="Ex : https://gitea.malio.fr/malio-dev/sirh"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -473,48 +478,59 @@ onMounted(loadApplication)
|
||||
<MalioInputText
|
||||
v-model="envForm.name"
|
||||
:label="t('environments.form.name')"
|
||||
groupClass="mt-0"
|
||||
required
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="envForm.containerName"
|
||||
:label="t('environments.form.containerName')"
|
||||
groupClass="mt-0"
|
||||
required
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="envForm.deployScriptPath"
|
||||
:label="t('environments.form.deployScriptPath')"
|
||||
:hint="t('environments.form.pathHint')"
|
||||
required
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="envForm.maintenanceFilePath"
|
||||
:label="t('environments.form.maintenanceFilePath')"
|
||||
:hint="t('environments.form.pathHint')"
|
||||
required
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="envForm.appUrl"
|
||||
:label="t('environments.form.appUrl')"
|
||||
groupClass="mt-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Log files -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p class="text-sm font-medium text-neutral-700">{{ t('environments.logFiles.title') }}</p>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<p class="text-md font-bold">{{ t('environments.logFiles.title') }}</p>
|
||||
<button type="button" @click="addLogFile" class="text-primary-500 hover:underline text-sm font-semibold">
|
||||
+ {{ t('environments.logFiles.addButton') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(lf, index) in envForm.logFiles" :key="index" class="flex gap-2 mb-2">
|
||||
<MalioInputText v-model="lf.label" :label="t('environments.logFiles.label')" groupClass="mt-0" inputClass="flex-1" required />
|
||||
<MalioInputText v-model="lf.path" :label="t('environments.logFiles.path')" groupClass="mt-0" inputClass="flex-[2]" required />
|
||||
<MalioButtonIcon
|
||||
icon="mdi:delete-outline"
|
||||
:aria-label="t('environments.logFiles.remove')"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
button-class="text-red-500 hover:bg-red-50 hover:text-red-700 my-1"
|
||||
@click="removeLogFile(index)"
|
||||
/>
|
||||
<div class="w-1/3">
|
||||
<MalioInputText v-model="lf.label" :label="t('environments.logFiles.label')" groupClass="mt-0" inputClass="flex-1" required />
|
||||
</div>
|
||||
<div class="w-2/3">
|
||||
<MalioInputText v-model="lf.path" :label="t('environments.logFiles.path')" groupClass="mt-0" inputClass="flex-[2]" :hint="t('environments.form.pathHint')" required />
|
||||
</div>
|
||||
<div class="h-[46px] flex items-center">
|
||||
<MalioButtonIcon
|
||||
icon="mdi:delete-outline"
|
||||
:aria-label="t('environments.logFiles.remove')"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
button-class="text-red-500 hover:bg-red-50 hover:text-red-700 my-1"
|
||||
@click="removeLogFile(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -130,11 +130,13 @@ onMounted(loadApplications)
|
||||
<MalioInputText
|
||||
v-model="createForm.registryImage"
|
||||
:label="t('applications.form.registryImage')"
|
||||
hint="Ex : gitea.malio.fr/malio-dev/sirh"
|
||||
required
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="createForm.giteaUrl"
|
||||
:label="t('applications.form.giteaUrl')"
|
||||
hint="Ex : https://gitea.malio.fr/malio-dev/sirh"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -75,10 +75,8 @@ RUN echo "APP_ENV=prod" > /var/www/html/.env
|
||||
RUN mkdir -p /var/www/html/var/log /var/www/html/var/uploads \
|
||||
&& chown -R www-data:www-data /var/www/html/var
|
||||
|
||||
# Allow www-data to use Docker socket
|
||||
# The socket GID varies per host; we set it at container startup via entrypoint
|
||||
# As fallback, install docker group with common GID
|
||||
RUN groupadd -g 999 docker 2>/dev/null; usermod -aG docker www-data
|
||||
# Allow www-data to use Docker socket (GID 987 matches host's docker group)
|
||||
RUN groupadd -g 987 dockerhost 2>/dev/null; usermod -aG dockerhost www-data
|
||||
|
||||
WORKDIR /var/www/html
|
||||
EXPOSE 80
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
services:
|
||||
app:
|
||||
image: gitea.malio.fr/malio-dev/central:${CENTRAL_IMAGE_TAG:-latest}
|
||||
container_name: central-app
|
||||
env_file: .env
|
||||
ports:
|
||||
- "8084:80"
|
||||
volumes:
|
||||
- ./config/jwt:/var/www/html/config/jwt:ro
|
||||
- ./uploads:/var/www/html/var/uploads
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/www/sirh:/var/www/sirh
|
||||
- /var/www/lesstime:/var/www/lesstime
|
||||
- /var/www/inventory:/var/www/inventory
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: unless-stopped
|
||||
app:
|
||||
image: gitea.malio.fr/malio-dev/central:${CENTRAL_IMAGE_TAG:-latest}
|
||||
container_name: central-app
|
||||
env_file: .env
|
||||
ports:
|
||||
- "8084:80"
|
||||
group_add:
|
||||
- "987"
|
||||
volumes:
|
||||
- ./config/jwt:/var/www/html/config/jwt:ro
|
||||
- ./uploads:/var/www/html/var/uploads
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/www:/mnt/apps
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -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')",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
20
src/Service/AppPathResolver.php
Normal file
20
src/Service/AppPathResolver.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
final readonly class AppPathResolver
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire('%env(APPS_BASE_PATH)%')]
|
||||
private string $basePath,
|
||||
) {}
|
||||
|
||||
public function resolve(string $relativePath): string
|
||||
{
|
||||
return rtrim($this->basePath, '/') . '/' . ltrim($relativePath, '/');
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
54
src/State/ApplicationProvider.php
Normal file
54
src/State/ApplicationProvider.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Entity\Application;
|
||||
use App\Repository\ApplicationRepository;
|
||||
use App\Service\AppPathResolver;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
final readonly class ApplicationProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ApplicationRepository $applicationRepository,
|
||||
private AppPathResolver $pathResolver,
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Application|array
|
||||
{
|
||||
if ($operation instanceof GetCollection) {
|
||||
$apps = $this->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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user