diff --git a/README.md b/README.md index 6b3289f..8b3d753 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Exemple : un mapping `mcp/graylog/install.sh` peut servir le fichier `install.sh ## Fonctionnement - `/admin` affiche l’écran de connexion puis l’interface de gestion. +- `/` redirige vers l’URL configurée dans `/admin/settings`, ou vers `/admin` si aucune URL n’est configurée. - Un mapping lie un chemin public `.sh` à une URL Git, une référence Git et un chemin de fichier dans le dépôt. - Le bouton `Synchroniser` clone ou met à jour le dépôt, extrait la référence demandée et copie le fichier dans le cache. - Les chemins publics hors `/admin` servent uniquement les scripts déjà synchronisés. @@ -54,11 +55,12 @@ docker compose -f compose.yaml -f compose.dev.yaml run --rm -e APP_ENV=test app ## Servir un script 1. Se connecter sur `/admin`. -2. Créer un mapping : +2. Optionnel : configurer l’URL de redirection de `/` via `Configuration`. +3. Créer un mapping : - Chemin public : `mcp/graylog/install.sh` - Dépôt : `https://forge.lclr.dev/AI/graylog-mcp.git` - Réf. : `main` - Fichier : `install.sh` - Token : optionnel, pour dépôt privé -3. Cliquer sur `Synchroniser`. -4. Appeler `http://localhost:8080/mcp/graylog/install.sh`. +4. Cliquer sur `Synchroniser`. +5. Appeler `http://localhost:8080/mcp/graylog/install.sh`. diff --git a/migrations/Version20260505081600.php b/migrations/Version20260505081600.php new file mode 100644 index 0000000..6b805b2 --- /dev/null +++ b/migrations/Version20260505081600.php @@ -0,0 +1,27 @@ +addSql('CREATE TABLE app_setting (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(190) NOT NULL, value CLOB DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL)'); + $this->addSql('CREATE UNIQUE INDEX uniq_app_setting_name ON app_setting (name)'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE app_setting'); + } +} diff --git a/src/Controller/Admin/SettingsController.php b/src/Controller/Admin/SettingsController.php new file mode 100644 index 0000000..7aecbfb --- /dev/null +++ b/src/Controller/Admin/SettingsController.php @@ -0,0 +1,37 @@ +createForm(SettingsType::class, [ + 'rootRedirectUrl' => $settings->rootRedirectUrl(), + ]); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $data = $form->getData(); + $settings->setRootRedirectUrl($data['rootRedirectUrl'] ?? null); + $entityManager->flush(); + + $this->addFlash('success', 'Configuration mise à jour.'); + + return $this->redirectToRoute('admin_dashboard'); + } + + return $this->render('admin/settings.html.twig', [ + 'form' => $form, + ], new Response(status: $form->isSubmitted() ? Response::HTTP_UNPROCESSABLE_ENTITY : Response::HTTP_OK)); + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php new file mode 100644 index 0000000..6fdf7b0 --- /dev/null +++ b/src/Controller/HomeController.php @@ -0,0 +1,22 @@ +rootRedirectUrl(); + if ($redirectUrl !== null) { + return new RedirectResponse($redirectUrl); + } + + return $this->redirectToRoute('admin_dashboard'); + } +} diff --git a/src/Entity/AppSetting.php b/src/Entity/AppSetting.php new file mode 100644 index 0000000..719403f --- /dev/null +++ b/src/Entity/AppSetting.php @@ -0,0 +1,87 @@ +id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = trim($name); + + return $this; + } + + public function getValue(): ?string + { + return $this->value; + } + + public function setValue(?string $value): self + { + $value = $value === null ? null : trim($value); + $this->value = $value === '' ? null : $value; + + return $this; + } + + public function getCreatedAt(): ?DateTimeImmutable + { + return $this->createdAt; + } + + public function getUpdatedAt(): ?DateTimeImmutable + { + return $this->updatedAt; + } + + #[ORM\PrePersist] + public function initializeTimestamps(): void + { + $now = new DateTimeImmutable(); + $this->createdAt ??= $now; + $this->updatedAt ??= $now; + } + + #[ORM\PreUpdate] + public function refreshUpdatedAt(): void + { + $this->updatedAt = new DateTimeImmutable(); + } +} diff --git a/src/Form/SettingsType.php b/src/Form/SettingsType.php new file mode 100644 index 0000000..55ae832 --- /dev/null +++ b/src/Form/SettingsType.php @@ -0,0 +1,34 @@ +add('rootRedirectUrl', UrlType::class, [ + 'default_protocol' => null, + 'required' => false, + 'constraints' => [ + new Url( + protocols: ['http', 'https'], + requireTld: false, + message: 'L’URL de redirection doit être une URL http ou https.' + ), + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => null, + ]); + } +} diff --git a/src/Repository/AppSettingRepository.php b/src/Repository/AppSettingRepository.php new file mode 100644 index 0000000..6c94d54 --- /dev/null +++ b/src/Repository/AppSettingRepository.php @@ -0,0 +1,30 @@ +findOneBy(['name' => $name])?->getValue(); + } + + public function setValue(string $name, ?string $value): AppSetting + { + $setting = $this->findOneBy(['name' => $name]) ?? (new AppSetting())->setName($name); + $setting->setValue($value); + + $this->getEntityManager()->persist($setting); + + return $setting; + } +} diff --git a/src/Service/AppSettings.php b/src/Service/AppSettings.php new file mode 100644 index 0000000..6ec034a --- /dev/null +++ b/src/Service/AppSettings.php @@ -0,0 +1,23 @@ +settings->getValue(AppSetting::ROOT_REDIRECT_URL); + } + + public function setRootRedirectUrl(?string $url): void + { + $this->settings->setValue(AppSetting::ROOT_REDIRECT_URL, $url); + } +} diff --git a/templates/admin/dashboard.html.twig b/templates/admin/dashboard.html.twig index fed1a70..13ede38 100644 --- a/templates/admin/dashboard.html.twig +++ b/templates/admin/dashboard.html.twig @@ -5,7 +5,10 @@ {% block body %}

Mappings

-

Nouveau mapping

+

+ Nouveau mapping + Configuration +

{% for message in app.flashes('success') %}

{{ message }}

diff --git a/templates/admin/settings.html.twig b/templates/admin/settings.html.twig new file mode 100644 index 0000000..1954592 --- /dev/null +++ b/templates/admin/settings.html.twig @@ -0,0 +1,17 @@ +{% extends 'base.html.twig' %} + +{% block title %}Configuration{% endblock %} + +{% block body %} +
+

Configuration

+ + {{ form_start(form) }} + {{ form_row(form.rootRedirectUrl, { + label: 'URL de redirection de /', + help: 'Laisser vide pour rediriger vers /admin.' + }) }} + + {{ form_end(form) }} +
+{% endblock %} diff --git a/tests/Controller/Admin/SettingsControllerTest.php b/tests/Controller/Admin/SettingsControllerTest.php new file mode 100644 index 0000000..7d7be8a --- /dev/null +++ b/tests/Controller/Admin/SettingsControllerTest.php @@ -0,0 +1,60 @@ +client->request('GET', '/admin/settings'); + + self::assertResponseRedirects('/admin/login'); + } + + public function testAdminCanUpdateRootRedirectUrl(): void + { + $this->loginAsAdmin(); + + $crawler = $this->client->request('GET', '/admin/settings'); + $form = $crawler->selectButton('Enregistrer')->form([ + 'settings[rootRedirectUrl]' => 'https://example.com/installers', + ]); + + $this->client->submit($form); + + self::assertResponseRedirects('/admin'); + + self::assertSame( + 'https://example.com/installers', + $this->entityManager->getRepository(AppSetting::class)->getValue(AppSetting::ROOT_REDIRECT_URL) + ); + } + + public function testRootRedirectUrlMustBeHttpOrHttps(): void + { + $this->loginAsAdmin(); + + $crawler = $this->client->request('GET', '/admin/settings'); + $form = $crawler->selectButton('Enregistrer')->form([ + 'settings[rootRedirectUrl]' => 'javascript:alert(1)', + ]); + + $this->client->submit($form); + + self::assertResponseStatusCodeSame(422); + self::assertNull($this->entityManager->getRepository(AppSetting::class)->getValue(AppSetting::ROOT_REDIRECT_URL)); + } + + private function loginAsAdmin(): void + { + $user = (new User())->setUsername('admin')->setPasswordHash('unused'); + $this->entityManager->persist($user); + $this->entityManager->flush(); + + $this->client->loginUser($user); + } +} diff --git a/tests/Controller/HomeControllerTest.php b/tests/Controller/HomeControllerTest.php new file mode 100644 index 0000000..0b78347 --- /dev/null +++ b/tests/Controller/HomeControllerTest.php @@ -0,0 +1,29 @@ +client->request('GET', '/'); + + self::assertResponseRedirects('/admin'); + } + + public function testRootRedirectsToConfiguredUrl(): void + { + $setting = (new AppSetting()) + ->setName(AppSetting::ROOT_REDIRECT_URL) + ->setValue('https://example.com/installers'); + $this->entityManager->persist($setting); + $this->entityManager->flush(); + + $this->client->request('GET', '/'); + + self::assertResponseRedirects('https://example.com/installers'); + } +}