Blog / Organiser ses outils Laravel sur un unique sous-domaine
7 min - 06 Dec 2023
TL;DR: Comment organiser les outils de maintenance tels que Laravel Pulse et Laravel Telescope sur un sous-domaine d’un projet Laravel.
Vous trouverez le code source via ce Github Repository. Découvrez-en plus sur Capsules, X ou Bluesky.
Avec les nombreux outils fournis par le framework Laravel, tels que Telescope ou plus récemment Pulse, il est devenu essentiel de les centraliser sur un même tableau de bord. Voici comment regrouper ces outils sur un sous-domaine dédié.
Initialement, une seule route est configurée dans notre projet Laravel vierge.
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::get( '/', fn() => Inertia::render( 'Welcome' ) );
La création d'un sous-domaine regroupant les outils souhaités ne nécessite que quelques lignes dans le fichier web.php
. Pour cet article, il est essentiel d'ajouter des variables d'environnement supplémentaires dans le fichier .env
ainsi que dans les fichiers de configuration avant de passer à la suite.
.env
APP_DOMAIN=article.test
APP_URL=http://${APP_DOMAIN}
TOOLS_DOMAIN=tools.${APP_DOMAIN}
De même, cela implique des modifications dans les fichiers de configuration associés, tels que app.php
, ainsi que la création d'un nouveau fichier de configuration appelé tools.php
.
config/app.php
...
/*
|--------------------------------------------------------------------------
| Application Domain
|--------------------------------------------------------------------------
|
| This value is the domain of your application. This value is used when the
| framework needs to access the domain in routes.
|
*/
'domain' => env('APP_DOMAIN'),
...
config/tools.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Tools Domain
|--------------------------------------------------------------------------
|
| This value is the domain of your tools. This value is used when the
| framework needs to access the domain in routes.
|
*/
'domain' => env('TOOLS_DOMAIN'),
];
Il est maintenant temps de configurer les routes associées à ces changements.
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::domain( config( 'tools.domain' ) )->group( function()
{
Route::get( '/', fn() => Inertia::render( 'Tools' ) )->name( 'tools' );
});
Route::domain( config( 'app.domain' ) )->group( function()
{
Route::get( '/', fn() => Inertia::render( 'App' ) )->name( 'app' );
});
La commande php artisan route:list
offre une vue d'ensemble des routes créées, permettant ainsi de vérifier si tout semble en ordre.
php artisan route:list
GET|HEAD tools.article.test/ ................................................. tools
GET|HEAD article.test/ ......................................................... app
...
Pour afficher les routes, il est nécessaire de créer une page dédiée au domaine général et une autre pour le sous-domaine tools
. En copiant la page par défaut avec une légère modification du titre, il sera possible de distinguer les deux pages. La page par défaut est la suivante :
<script setup>
import logotype from '/public/assets/capsules-logotype-red-blue-home.svg';
</script>
<template>
<div class="w-full min-h-screen flex flex-col font-sans text-primary-black">
<div class="grow mx-8 lg:mx-auto max-w-screen-lg overflow-auto flex flex-col items-center justify-center text-center">
<img class="w-24 h-24 select-none" v-bind:src="logotype">
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />
</div>
</div>
</template>
resources/js/pages/App.vue
...
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />
<h2 class="mt-4 text-4xl font-bold select-none header-mode" v-text="'Application'" />
...
resources/js/pages/Tools.vue
...
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />
<h2 class="mt-4 text-4xl font-bold select-none header-mode" v-text="'Tools'" />
...
Maintenant que l'accès au sous-domaine est établi, il est nécessaire de créer un menu affichant les différents onglets avant d'implémenter les outils. Ce menu peut se trouver à l'extrême gauche avec les boutons Pulse et Horizon, le contenu serait positionné à droite.
resources/js/pages/Tools.vue
<script setup>
import { Link } from '@inertiajs/vue3';
import logotype from '/public/assets/capsules-logotype-red-blue.svg';
import pulse from '/public/assets/tools/pulse.svg';
import telescope from '/public/assets/tools/telescope.svg';
const props = defineProps( { service : { type : String } } );
</script>
<template>
<div class="w-full h-screen flex">
<div class="p-4 h-full bg-white">
<div class="space-y-8">
<img class="h-8 w-8" v-bind:src="logotype">
<div class="flex flex-col space-y-1">
<Link class="py-2 rounded-md" v-bind:class=" props.service === 'pulse' ? 'bg-slate-100' : 'hover:bg-slate-50' " href="/pulse" v-bind:replace="true" as="button"><img class="mx-2 h-4 w-4" v-bind:src="pulse"></link>
<Link class="py-2 rounded-md" v-bind:class=" props.service === 'telescope' ? 'bg-slate-100' : 'hover:bg-slate-50' " href="/telescope" v-bind:replace="true" as="button"><img class="mx-2 h-4 w-4" v-bind:src="telescope"></link>
</div>
</div>
</div>
<div class="grow overflow-auto">
<div class="h-full flex">
<div class="w-full flex flex-col items-center justify-center">
<img class="w-24 h-24 select-none" v-bind:src="logotype">
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />
<h2 class="mt-4 text-4xl font-bold select-none header-mode" v-text=" props.service ? props.service : 'Tools'" />
</div>
</div>
</div>
</div>
</template>
Link
, sont maintenant disponibles pour accéder aux différents outils. Actuellement, la page affiche un écran d'accueil "Tools" lorsqu'aucun service n'est chargé.Il est nécessaire d'implémenter deux routes dans le fichier web.php
ainsi que dans le contrôleur ToolsController.php
pour activer nos outils.
routes/web.php
use App\Http\Controllers\ToolsController;
Route::domain( config( 'tools.domain' ) )->group( function()
{
Route::get( 'pulse', [ ToolsController::class, 'pulse' ] )->name( 'tools.pulse' );
Route::get( 'telescope', [ ToolsController::class, 'telescope' ] )->name( 'tools.telescope' );
Route::get( '{any}', fn() => redirect()->route( 'tools.pulse' ) )->where( 'any', '.*' );
});
/
. Une route de redirection a été mise en place pour rediriger vers l'outil de choix dans le cas où aucune URL ne correspond.app/Http/Controllers/ToolsController.php
<?php
namespace App\Http\Controllers;
use Inertia\Inertia;
use Inertia\Response;
class ToolsController extends Controller
{
public function pulse() : Response
{
return Inertia::render( 'Tools', [ 'service' => 'pulse' ] );
}
public function telescope() : Response
{
return Inertia::render( 'Tools', [ 'service' => 'telescope' ] );
}
}
Il ne reste plus qu'à installer Pulse et Telescope. Pour cela, une base de données est nécessaire. Les instructions d'installation sont résumées plus bas.
.env
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=tools DB_USERNAME=root DB_PASSWORD=
Laravel Pulse
> composer require laravel/pulse
> php artisan vendor:publish --provider="Laravel\\Pulse\\PulseServiceProvider"
> php artisan migrate
Laravel Telescope
> composer require laravel/telescope
> php artisan telescope:install
> php artisan migrate
Une fois ces outils installés, les chemins /pulse
et /telescope
donnent accès directement aux outils. Il faut donc les modifier, ce qui peut être réalisé depuis leurs fichiers de configuration respectifs. Il suffit alors de modifier les chemins dans le fichier .env
.
.env
PULSE_PATH=pulse-custom-path
TELESCOPE_PATH=telescope-custom-path
Il faut maintenant injecter les chemins depuis le ToolsController
dans le composant Tools
. Ce dernier chargera ensuite la balise <iframe>
avec l'URL donné.
app\Http\Controllers\ToolsController.php
<?php
namespace App\Http\Controllers;
use Inertia\Inertia;
use Inertia\Response;
class ToolsController extends Controller
{
public function pulse() : Response
{
return Inertia::render( 'Tools', [ 'service' => 'pulse', 'url' => redirect()->to( config( 'pulse.path' ) )->getTargetUrl() ] );
}
public function telescope() : Response
{
return Inertia::render( 'Tools', [ 'service' => 'telescope', 'url' => redirect()->to( config( 'telescope.path' ) )->getTargetUrl() ] );
}
}
resources/js/pages/Tools.vue
<script setup>
import { Link } from '@inertiajs/vue3';
import logotype from '/public/assets/capsules-logotype-red-blue.svg';
import pulse from '/public/assets/tools/pulse.svg';
import telescope from '/public/assets/tools/telescope.svg';
const props = defineProps( { service : { type : String, required : true }, url : { type : String, required : true } } );
</script>
<template>
<div class="w-full h-screen flex">
<div class="p-4 h-full bg-white">
<div class="space-y-8">
<img class="h-8 w-8" v-bind:src="logotype">
<div class="flex flex-col space-y-1">
<Link class="py-2 rounded-md" v-bind:class=" props.service === 'pulse' ? 'bg-slate-100' : 'hover:bg-slate-50' " href="/pulse" v-bind:replace="true" as="button"><img class="mx-2 h-4 w-4" v-bind:src="pulse"></link>
<Link class="py-2 rounded-md" v-bind:class=" props.service === 'telescope' ? 'bg-slate-100' : 'hover:bg-slate-50' " href="/telescope" v-bind:replace="true" as="button"><img class="mx-2 h-4 w-4" v-bind:src="telescope"></link>
</div>
</div>
</div>
<div class="grow overflow-auto">
<iframe class="w-full h-full" v-bind:src="props.url" />
</div>
</div>
</template>
Les outils sont maintenant accessibles via le menu latéral ! 🎉
Plusieurs informations supplémentaires destinées à étayer cet article :
auth.basic
à une route déclenchera l'ouverture d'une fenêtre modale de connexion. Route::get( 'pulse', [ ToolsController::class, 'pulse' ] )->middleware( 'auth.basic' )->name( 'tools.pulse' );
Gate
auprès des différents ServiceProvider
tel que le TelescopeServiceProvider.php
vérifiant si l’utilisateur est connecté via Auth::check()
.protected function gate() : void
{
Gate::define( 'viewTelescope', fn() => Auth::check() );
}
SESSION_DOMAIN
au fichier .env
, en associant le nom de domaine global avec un point .
devant.SESSION_DOMAIN=".article.test"
<iframe>
en raison de l'en-tête X-Frame-Options: SAMEORIGIN
. Si vous en êtes le propriétaire, vous pouvez ajouter l'en-tête suivant à la directive Content-Security-Policy
dans la configuration Nginx de votre outil intégrable pour valider l'origine : add_header Content-Security-Policy "frame-ancestors 'self' {website-url};"
. Si vous ne possédez pas l'URL, il est recommandé d'utiliser un lien externe.public function google() : RedirectResponse
{
return Redirect::away( "http://google.com" );
}
Ravi d’avoir pu aider !