6 Commits

Author SHA1 Message Date
gitea-actions
b9712643de chore: bump version to v0.1.24
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 12s
2026-04-08 07:11:01 +00:00
e954402959 fix : install docker-compose-plugin in prod Dockerfile
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Deploy scripts use `docker compose` (Compose V2 plugin) which is not
included in docker.io package.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:10:33 +02:00
gitea-actions
98d9032068 chore: bump version to v0.1.23
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 53s
2026-04-07 12:38:52 +00:00
5f6277d412 feat : update Malio UI + CLAUDE.md
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-04-07 14:38:42 +02:00
gitea-actions
8fb71e6370 chore: bump version to v0.1.22
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 31s
2026-04-07 10:16:54 +00:00
e128b45caa fix : affichage log docker et symfony
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-04-07 12:16:45 +02:00
10 changed files with 93 additions and 27 deletions

View File

@@ -31,6 +31,10 @@ frontend/services/dto/ # Types TypeScript
frontend/i18n/locales/ # Fichiers de traduction
```
## Composants UI
La librairie `@malio/layer-ui` fournit les composants de formulaire et d'action. La documentation complète des props, events et exemples d'utilisation se trouve dans `frontend/node_modules/@malio/layer-ui/COMPONENTS.md`. Toujours s'y référer avant d'utiliser un composant Malio.
## Commandes
```bash

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.21'
app.version: '0.1.24'

View File

@@ -119,6 +119,7 @@
"deployScriptPath": "Chemin du script de deploiement",
"maintenanceFilePath": "Chemin du fichier de maintenance",
"pathHint": "Prefixe automatique : /mnt/apps",
"pathHintLog": "Chemin dans le container, ex : var/log/prod.log",
"appUrl": "URL de l'application",
"save": "Enregistrer",
"cancel": "Annuler"

View File

@@ -7,7 +7,7 @@
"name": "nuxt-app",
"hasInstallScript": true,
"dependencies": {
"@malio/layer-ui": "^1.2.2",
"@malio/layer-ui": "^1.2.3",
"@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.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==",
"version": "1.2.3",
"resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.2.3/layer-ui-1.2.3.tgz",
"integrity": "sha512-5nRnBzRkXfs3PfKwKl6sH2ikrmSK7lTifcd0TX1QZP3rFRVRTgcT6mrsrpsbR9PwI27OeCNm0X6d0Ii92Rq7Yg==",
"dependencies": {
"@nuxt/icon": "^2.2.1",
"@nuxtjs/tailwindcss": "^6.14.0",

View File

@@ -11,7 +11,7 @@
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
},
"dependencies": {
"@malio/layer-ui": "^1.2.2",
"@malio/layer-ui": "^1.2.3",
"@nuxt/icon": "^2.2.1",
"@nuxtjs/i18n": "^10.2.3",
"@nuxtjs/tailwindcss": "^6.14.0",

View File

@@ -579,7 +579,7 @@ onMounted(loadApplication)
<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 />
<MalioInputText v-model="lf.path" :label="t('environments.logFiles.path')" groupClass="mt-0" inputClass="flex-[2]" :hint="`${t('environments.form.pathHint')}/${application?.slug ?? ''}`" required />
</div>
<div class="h-[46px] flex items-center">
<MalioButtonIcon

View File

@@ -20,18 +20,12 @@
v-model="username"
/>
<div>
<label class="text-sm font-semibold text-neutral-700" for="password">
Mot de passe
</label>
<input
id="password"
v-model="password"
type="password"
autocomplete="current-password"
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
/>
</div>
<MalioInputPassword
v-model="password"
label="Mot de passe"
autocomplete="current-password"
inputClass="w-full"
/>
<MalioButton
label="Se connecter"

View File

@@ -40,7 +40,7 @@ FROM php:8.4-fpm AS production
RUN apt-get update && apt-get install -y \
libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \
nginx supervisor docker.io \
nginx supervisor docker.io docker-compose-plugin \
&& docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -35,25 +35,91 @@ final readonly class LogService
$process->setTimeout(10);
$process->run();
return $process->getOutput() . $process->getErrorOutput();
$output = $process->getOutput() . $process->getErrorOutput();
return $this->formatDockerOutput($output);
}
public function getSymfonyLog(string $relativePath, int $lines = 100, ?string $level = null): string
private function formatDockerOutput(string $output): string
{
$path = $this->pathResolver->resolve($relativePath);
$rawLines = explode("\n", trim($output));
$formatted = [];
if (!file_exists($path)) {
return sprintf('Log file not found: %s', $path);
foreach ($rawLines as $line) {
if ('' === $line) {
continue;
}
// Try parsing as Symfony monolog JSON format
$parsed = $this->parseSymfonyLogLine($line);
if (null !== $parsed) {
if ('doctrine' === $parsed['channel']) {
continue;
}
$formatted[] = sprintf('[%s] %s.%s: %s', $parsed['date'], $parsed['channel'], $parsed['level'], $parsed['message']);
continue;
}
// Try parsing as JSON log ({"message":"...","level":...})
$json = json_decode($line, true);
if (\is_array($json) && isset($json['message'])) {
$date = isset($json['datetime']) ? substr($json['datetime'], 0, 19) : '';
$date = str_replace('T', ' ', $date);
$channel = $json['channel'] ?? 'app';
$level = $json['level_name'] ?? 'INFO';
if ('doctrine' === $channel) {
continue;
}
$formatted[] = sprintf('[%s] %s.%s: %s', $date, $channel, $level, $json['message']);
continue;
}
// Keep raw lines that don't match any format (nginx access logs, etc.)
$formatted[] = $line;
}
return implode("\n", $formatted);
}
public function getSymfonyLog(string $containerName, string $logPath, int $lines = 100, ?string $level = null): string
{
$check = new Process(['which', 'docker']);
$check->setTimeout(5);
$check->run();
if (!$check->isSuccessful()) {
// Fallback: try reading from filesystem (dev mode)
$localPath = $this->pathResolver->resolve($logPath);
if (!file_exists($localPath)) {
return sprintf('Log file not found: %s (Docker CLI unavailable, local path: %s)', $logPath, $localPath);
}
$readLines = (null !== $level && '' !== $level) ? $lines * 5 : $lines;
$process = new Process(['tail', '-n', (string) $readLines, $localPath]);
$process->setTimeout(10);
$process->run();
return $this->formatSymfonyOutput($process->getOutput(), $lines, $level);
}
// Read more lines than requested to compensate for filtering
$readLines = (null !== $level && '' !== $level) ? $lines * 5 : $lines;
$process = new Process(['tail', '-n', (string) $readLines, $path]);
$process = new Process(['docker', 'exec', $containerName, 'tail', '-n', (string) $readLines, $logPath]);
$process->setTimeout(10);
$process->run();
$rawLines = explode("\n", trim($process->getOutput()));
if (!$process->isSuccessful()) {
return sprintf('Error reading log: %s', trim($process->getErrorOutput()));
}
return $this->formatSymfonyOutput($process->getOutput(), $lines, $level);
}
private function formatSymfonyOutput(string $output, int $lines, ?string $level): string
{
$rawLines = explode("\n", trim($output));
$formatted = [];
foreach ($rawLines as $line) {

View File

@@ -42,6 +42,7 @@ final readonly class SymfonyLogProvider implements ProviderInterface
$level = $request?->query->get('level');
$content = $this->logService->getSymfonyLog(
$environment->getContainerName(),
$logFile->getPath(),
$lines,
$level,