Integración del panel de Laravel Pulse con Laravel Nova  

Hoy les propongo una modificación que hice en el backend de uno de mis clientes para tener el dashboard de Laravel Pulse directamente en el panel principal de Laravel Nova.
Publicado el 22/10/2024 a las 11:20

Aunque Laravel Pulse ofrece un endpoint al que podemos acceder a través de un enlace simple y usar el dashboard de Pulse como una aplicación independiente, tuve la necesidad de integrar la visualización directamente en el dashboard de Laravel para crear una mejor experiencia de usuario.

Objetivo

Aquí les indico el resultado que obtendremos con esta integración. En la versión estándar, encontré los siguientes problemas: cuando se activaba la versión oscura en Laravel Nova, también había que seleccionarla en Pulse, o viceversa. Además, al disponer de un menú en Nova sobre el estado de varias aplicaciones, cada vez que se accedía a Pulse, era necesario regresar siempre para elegir otra opción.

La idea es crear una "custom route" en Laravel Nova que genere un elemento iframe con una versión modificada del dashboard de Pulse, sin usar el endpoint original. De este modo, podemos personalizar el diseño original, integrar un único estado de tema oscuro/claro y personalizar el CSS.

Creación de las "custom routes"

Primero, creamos un archivo para la definición de las "routes" o modificamos el existente si ya tienes rutas personalizadas. Yo he creado un archivo en app/Nova/Routes/tools.php.

Route::get('status/pulse')
    ->uses('App\Nova\Controllers\Tools\Status\PulseController@index')
    ->name('tools.status.pulse');

Route::get('status/pulse/iframe')
    ->uses('App\Nova\Controllers\Tools\Status\PulseController@iframe')
    ->name('tools.status.pulse.iframe');

He creado una "route" para la página principal de Laravel Nova y otra para realizar el "embed" mediante un "iframe" de una copia del dashboard de Laravel Pulse, modificada según nuestras necesidades. Posteriormente, también definiremos el controlador para la ejecución de estas dos funciones. Obviamente, pueden cambiar el valor del "path" según sus preferencias.

Creación del controlador

Personalmente, prefiero tener todo el entorno de Nova en el directorio app\Nova y dejar los directorios estándar para las funciones de la aplicación operativa. Por lo tanto, creo el controlador necesario en el directorio app/Nova/Controllers/Tools/Status, como se indica en la definición de las "routes".

namespace App\Nova\Controllers\Tools\Status;

use App\Http\Controllers\Controller;

class PulseController extends Controller
{
    /**
     * Application nova function
     */
    public function index()
    {
        return inertia('StatusPulse');
    }

    /**
     * Application nova function
     */
    public function iframe()
    {
        return view('admin::tools.status.pulse.index');
    }
}

El primer método se utiliza para llamar a la página principal mediante un componente Vue, dado que Nova realiza el rendering inicial en el lado del cliente. Mientras que el método "iframe" se procesará en el lado del servidor, cargando una vista Blade con la copia original del dashboard de Laravel Pulse, pero modificada según nuestras necesidades.

Creación del componente Vue

Para hacer que funcione la ruta /status/pulse en relación con el path de Laravel Nova, debemos crear un componente Vue, luego implementar un archivo JavaScript y cargarlo durante la inicialización de Laravel Nova. Por ejemplo, yo uso esta técnica también para cargar Livewire, que utilizo junto con Nova.

import StatusPulse from './pages/Status/Pulse.vue'

Nova.booting((app, store) => {
    Nova.inertia('StatusPulse', StatusPulse)
})

Creamos un archivo nova.js en el entorno donde se gestionan los assets. Yo utilizo ViteJS, pero estoy seguro de que también pueden implementarlo con otras herramientas. En el mismo directorio que nova.js, creamos una plantilla "vue" en ./pages/Status/Pulse.vue e indicamos este código.

<template>
  <div>
    <iframe src="/nova/status/pulse/iframe?period=24_hours"
            width="100%" style="min-height:100vw"></iframe>
  </div>
</template>

Creación de la vista Blade

Ahora el entorno debería estar listo para mostrar una página que renderice la vista Blade asociada al controlador definido anteriormente mediante el método "iframe". Por lo tanto, simplemente copia el archivo del paquete de Laravel Pulse (solo para probar el funcionamiento por ahora, luego haremos las personalizaciones) desde app\vendor\laravel\pulse\resources a nuestra vista Blade.

Atención a la variable {{slot}}, que deberá ser reemplazada por la lista de widgets de Laravel Pulse que queramos mostrar en nuestro dashboard. Por lo tanto, modifiquemos el código:

<main class="py-4">
  <div {{ $attributes->merge(['class' => "mx-auto grid default:grid-cols-{$cols} default:gap-6"]) }}>
    <livewire:pulse.servers cols="full"/>
    <livewire:pulse.usage cols="4" rows="2" />
    <livewire:pulse.queues cols="4" />
    <livewire:pulse.cache cols="4" />
    <livewire:pulse.slow-queries cols="8" />
    <livewire:pulse.exceptions cols="6" />
    <livewire:pulse.slow-requests cols="6" />
    <livewire:pulse.slow-jobs cols="6" />
    <livewire:pulse.slow-outgoing-requests cols="6" />
  </div>
</main>

Ahora estamos listos para realizar una prueba. Si el dashboard se visualiza sin problemas, podemos pasar a la fase de personalización del código. De lo contrario, es importante repetir cuidadosamente los pasos anteriores. En esta demostración, estoy utilizando el código original que uso diariamente en mi implementación, por lo que debería ser bastante confiable en términos de funcionamiento.

Personalización del Dashboard

Para hacer que la página de Pulse sea más coherente, debemos realizar algunas modificaciones. La primera es eliminar el componente `theme-switcher` del código copiado y reemplazar el cambio de tema basándolo en Laravel Nova. Así que, lo primero que debemos hacer es eliminar la línea del switch.

Eliminar el componente indicado del código que copiamos previamente en nuestra vista Blade y agregar un código JavaScript en la sección HEAD que active o desactive el tema oscuro según el estado del tema de Laravel Nova, que en este caso actúa como contenedor.

@livewireScriptConfig

<script>

  function updateTheme() {
    if (window.parent.document.documentElement.classList.contains('dark')) {
      this.theme = 'dark'
      localStorage.theme = 'dark'
      document.documentElement.classList.add('dark')
    } else {
      this.theme = 'light'
      localStorage.theme = 'light'
      document.documentElement.classList.remove('dark')
    }
  }

  const observer = new MutationObserver(function(mutationsList) {
    for (let mutation of mutationsList) {
      if (mutation.attributeName === 'class') {
        updateTheme();
      }
    }
  });

  observer.observe(window.parent.document.documentElement, {
    attributes: true
  });

  updateTheme();

</script>

Inserten este código justo después de la instrucción @livewireScriptConfig, que les indico como referencia. En este punto, deberíamos haber logrado el cambio de tema de Pulse usando el switch de Laravel Nova. Además de la personalización de JavaScript, también podemos modificar el CSS para adaptarlo mejor a nuestro entorno o a las personalizaciones realizadas en el entorno de Laravel Nova.

<style>
  html.dark .dark\:bg-gray-900
  {
    background-color: #101521 !important;
  }
  *, :before, :after {
     --tw-gradient-to-position: 0;
  }
</style>

Yo, por ejemplo, cambié el "background color" en la versión "dark-mode" para hacerlo más visible en comparación con el fondo predeterminado de Laravel Nova. También eliminé los fondos relacionados con la sección del encabezado para lograr una sensación de integración más directa con Nova.

Resultado final

Espero que esta experiencia les sea útil para su trabajo. Nos vemos en la próxima publicación. Cualquier mejora a la solución es bienvenida, usen los comentarios para escribirme. Gracias.

Comentarios
Armando Simón León Kovaleff
Armando Simón León Kovaleff
Great article! It was incredibly helpful in understanding how to integrate Laravel Pulse with Laravel Nova. The step-by-step explanation, especially about customizing the theme and using the iframe, was very clear and insightful.
Emanuel Tomas Cruz Ñaupa
Emanuel Tomas Cruz Ñaupa
Complimenti per l'articolo! Mi è stato molto utile per comprendere meglio l'integrazione di Laravel Pulse con Laravel Nova. La spiegazione è chiara e dettagliata, soprattutto la parte riguardante la personalizzazione del tema e l'uso dell'iframe. Apprezzo anche i suggerimenti per migliorare l'esperienza utente e la coerenza visiva. Grazie per aver condiviso questa soluzione, non vedo l'ora di leggere le prossime pubblicazioni! Continuate così!