From 7abc5d4d6ae3b93a79a8eb2b934347e70e27c27d Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Mon, 6 Oct 2025 11:54:32 +0200 Subject: [PATCH] Documente la crate pmoapp --- pmoapp/src/lib.rs | 236 +++++++++++++++++++++-- pmoupnp/src/devices/device_instance.rs | 5 +- pmoupnp/src/object_trait.rs | 5 +- pmoupnp/src/services/mod.rs | 5 +- pmoupnp/src/services/service_instance.rs | 7 +- 5 files changed, 229 insertions(+), 29 deletions(-) diff --git a/pmoapp/src/lib.rs b/pmoapp/src/lib.rs index f1b8ced1..763181ae 100644 --- a/pmoapp/src/lib.rs +++ b/pmoapp/src/lib.rs @@ -1,30 +1,242 @@ //! # pmoapp - Application web UPnP pour PMOMusic //! -//! Cette crate fournit l'application web frontend pour le contrôle UPnP, -//! intégrée via RustEmbed pour être servie par pmoserver. +//! Cette crate fournit l'application web frontend pour le contrôle et la visualisation +//! des devices UPnP MediaRenderer, intégrée via RustEmbed pour être servie par pmoserver. +//! +//! ## Vue d'ensemble +//! +//! `pmoapp` est une application Vue.js 3 moderne avec TypeScript qui offre une interface +//! utilisateur pour : +//! - Visualiser les logs système en temps réel (Server-Sent Events) +//! - Contrôler les devices UPnP MediaRenderer +//! - Afficher et formater automatiquement le XML dans les logs //! //! ## Fonctionnalités //! -//! - 📦 **Frontend intégré** : Application web compilée et embarquée dans le binaire -//! - 🎨 **Interface de contrôle** : UI pour gérer les devices UPnP MediaRenderer -//! - 🚀 **Zero configuration** : Pas besoin de servir des fichiers statiques séparés +//! ### 📦 Frontend intégré +//! - Application web compilée et embarquée dans le binaire Rust +//! - Aucun fichier statique externe à gérer en production +//! - Intégration via `RustEmbed` pour une distribution simplifiée +//! +//! ### 🎨 Interface utilisateur +//! - **LogView** : Visualisation des logs en temps réel avec filtres par niveau +//! - **Auto-scroll** : Défilement automatique des nouveaux logs (désactivable) +//! - **Formatage XML** : Détection et coloration syntaxique automatique du XML +//! - **Design responsive** : Compatible desktop et mobile +//! - **Thème sombre** : Style inspiré de VS Code pour une meilleure lisibilité +//! +//! ### 🚀 Zero configuration +//! - Pas besoin de serveur web séparé pour les assets +//! - Les fichiers sont servis directement depuis la mémoire du binaire +//! - Configuration automatique du routing Vue Router +//! +//! ## Architecture +//! +//! ### Stack technique +//! +//! - **Frontend** : Vue.js 3 avec Composition API +//! - **Langage** : TypeScript +//! - **Build** : Vite (rapide, moderne, HMR) +//! - **Routing** : Vue Router +//! - **Markdown** : Marked.js pour le rendu +//! - **Sécurité** : DOMPurify pour la sanitization HTML +//! +//! ### Structure des fichiers +//! +//! ```text +//! pmoapp/ +//! ├── Cargo.toml # Dépendances Rust (rust-embed) +//! ├── src/ +//! │ └── lib.rs # Point d'entrée Rust (ce fichier) +//! └── webapp/ +//! ├── src/ +//! │ ├── main.ts # Point d'entrée Vue.js +//! │ ├── App.vue # Composant racine +//! │ ├── router/ # Configuration Vue Router +//! │ └── components/ +//! │ ├── LogView.vue # Visualiseur de logs SSE +//! │ └── ... +//! ├── dist/ # Build output (généré, non versionné) +//! ├── package.json # Dépendances npm +//! └── vite.config.ts # Configuration Vite +//! ``` +//! +//! ## Workflow de build +//! +//! ### 1. Build de la webapp (Vue.js) +//! +//! ```bash +//! # Installation des dépendances +//! cd pmoapp/webapp +//! npm install +//! +//! # Build de production +//! npm run build +//! # Génère : webapp/dist/index.html, assets/*.js, assets/*.css +//! ``` +//! +//! ### 2. Compilation Rust +//! +//! ```bash +//! cargo build +//! # RustEmbed inclut automatiquement les fichiers de webapp/dist/ +//! ``` +//! +//! ### 3. Utilisation avec Makefile +//! +//! ```bash +//! # Build complet (webapp + Rust) +//! make build +//! +//! # Ou juste la webapp +//! make webapp +//! +//! # Clean +//! make clean +//! ``` //! //! ## Utilisation //! +//! ### Exemple basique +//! //! ```rust,no_run //! use pmoapp::Webapp; //! use pmoserver::ServerBuilder; //! -//! # async fn example() { -//! let mut server = ServerBuilder::new("MyApp").build(); -//! server.add_spa::("/app").await; -//! # } +//! #[tokio::main] +//! async fn main() { +//! let mut server = ServerBuilder::new("MyApp") +//! .http_port(8080) +//! .build(); +//! +//! // Ajouter la webapp comme Single Page Application +//! server.add_spa::("/app").await; +//! +//! // Ajouter une redirection de la racine vers /app +//! server.add_redirect("/", "/app").await; +//! +//! server.start().await; +//! server.wait().await; +//! } //! ``` //! -//! ## Structure +//! ### Exemple avec logs SSE //! -//! La webapp est construite avec Vite et Vue.js, et les fichiers statiques -//! sont embarqués dans le binaire au moment de la compilation via `RustEmbed`. +//! ```rust,no_run +//! use pmoapp::Webapp; +//! use pmoserver::{ServerBuilder, logs::{LogState, SseLayer}}; +//! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +//! +//! #[tokio::main] +//! async fn main() { +//! // Configuration des logs avec SSE +//! let log_state = LogState::new(1000); // Buffer de 1000 logs +//! tracing_subscriber::registry() +//! .with(tracing_subscriber::fmt::layer()) +//! .with(SseLayer::new(log_state.clone())) +//! .init(); +//! +//! let mut server = ServerBuilder::new("MyApp").build(); +//! +//! // Endpoints SSE pour les logs +//! server.add_handler_with_state("/log-sse", pmoserver::logs::log_sse, log_state.clone()).await; +//! server.add_handler_with_state("/log-dump", pmoserver::logs::log_dump, log_state).await; +//! +//! // Webapp (consommera les logs via /log-sse) +//! server.add_spa::("/app").await; +//! server.add_redirect("/", "/app").await; +//! +//! server.start().await; +//! server.wait().await; +//! } +//! ``` +//! +//! ## Développement +//! +//! ### Mode développement Vue.js +//! +//! Pour développer la webapp avec Hot Module Replacement : +//! +//! ```bash +//! cd pmoapp/webapp +//! npm run dev +//! # Serveur de dev sur http://localhost:5173 +//! ``` +//! +//! ### Rebuild après modifications +//! +//! Après avoir modifié le code Vue.js : +//! +//! ```bash +//! # Rebuild webapp + recompile Rust +//! make build +//! +//! # Ou séparément +//! make webapp # Build Vue.js seulement +//! cargo build # Recompile Rust (intègre le nouveau dist/) +//! ``` +//! +//! ## Composants Vue.js +//! +//! ### LogView +//! +//! Composant principal pour la visualisation des logs : +//! +//! - **Connexion SSE** : Stream temps réel via EventSource +//! - **Filtrage** : Par niveau (TRACE, DEBUG, INFO, WARN, ERROR) +//! - **Auto-scroll** : Activable/désactivable +//! - **Formatage** : Markdown + détection XML automatique +//! - **Buffer** : Limite à 1000 logs en mémoire +//! - **Déduplication** : Évite les logs en double +//! +//! ### Formatage XML +//! +//! Le composant LogView détecte automatiquement le XML dans les messages : +//! +//! ``` +//! Input: "INFO: ..." +//! Output: Bloc de code avec coloration syntaxique XML +//! ``` +//! +//! - Détection via regex : ``, ``, etc.) +//! - Conversion en bloc markdown : ` ```xml ... ``` ` +//! - Rendu avec coloration et scrollbar pour le XML long +//! +//! ## Intégration avec pmoupnp +//! +//! La webapp communique avec les devices UPnP via les endpoints HTTP fournis par +//! `pmoserver` et `pmoupnp` : +//! +//! - `/log-sse` : Stream de logs (Server-Sent Events) +//! - `/log-dump` : Historique des logs +//! - `/device/*/description.xml` : Descripteurs UPnP +//! - `/service/*/control` : Endpoints de contrôle SOAP +//! - `/service/*/event` : Souscription aux événements UPnP +//! +//! ## Notes de déploiement +//! +//! ### Taille du binaire +//! +//! La webapp ajoutera ~150KB au binaire (compressé avec gzip par RustEmbed). +//! +//! ### Cache du navigateur +//! +//! Les assets sont servis avec des hashes dans les noms de fichiers +//! (`index-BBZcSinC.js`) pour un cache busting automatique. +//! +//! ### Compatibilité navigateurs +//! +//! - Chrome/Edge : ✅ Moderne +//! - Firefox : ✅ Moderne +//! - Safari : ✅ iOS 13+ +//! - IE11 : ❌ Non supporté (utilise ES modules) +//! +//! ## Voir aussi +//! +//! - [`pmoserver`] : Serveur HTTP Axum pour servir la webapp +//! - [`pmoupnp`] : Bibliothèque UPnP MediaRenderer +//! - [Vue.js Documentation](https://vuejs.org/) +//! - [Vite Documentation](https://vitejs.dev/) use rust_embed::RustEmbed; diff --git a/pmoupnp/src/devices/device_instance.rs b/pmoupnp/src/devices/device_instance.rs index 9c74eb71..26526aa1 100644 --- a/pmoupnp/src/devices/device_instance.rs +++ b/pmoupnp/src/devices/device_instance.rs @@ -316,10 +316,7 @@ impl DeviceInstance { return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } - let mut xml = String::from_utf8_lossy(&xml_output).to_string(); - - // Ajouter l'en-tête XML - xml.insert_str(0, "\n"); + let xml = String::from_utf8_lossy(&xml_output).to_string(); ( StatusCode::OK, diff --git a/pmoupnp/src/object_trait.rs b/pmoupnp/src/object_trait.rs index 99c151e9..f7b0f2a8 100644 --- a/pmoupnp/src/object_trait.rs +++ b/pmoupnp/src/object_trait.rs @@ -112,10 +112,7 @@ pub trait UpnpObject: Clone + Debug { elem.write_with_config(&mut buf, config) .expect("Failed to write XML"); - let mut xml_string = "\n".to_string(); - xml_string.push_str(&String::from_utf8(buf).expect("Invalid UTF-8")); - - xml_string + String::from_utf8(buf).expect("Invalid UTF-8") } /// Convertit l'objet en représentation Markdown. diff --git a/pmoupnp/src/services/mod.rs b/pmoupnp/src/services/mod.rs index cd22ad46..15a48c68 100644 --- a/pmoupnp/src/services/mod.rs +++ b/pmoupnp/src/services/mod.rs @@ -447,10 +447,7 @@ impl Service { elem.write_with_config(&mut buf, config) .expect("Failed to write XML"); - let mut xml_string = "\n".to_string(); - xml_string.push_str(&String::from_utf8(buf).expect("Invalid UTF-8")); - - xml_string + String::from_utf8(buf).expect("Invalid UTF-8") } } diff --git a/pmoupnp/src/services/service_instance.rs b/pmoupnp/src/services/service_instance.rs index 7fab502d..de9dbae1 100644 --- a/pmoupnp/src/services/service_instance.rs +++ b/pmoupnp/src/services/service_instance.rs @@ -395,11 +395,8 @@ impl ServiceInstance { return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } - let mut xml = String::from_utf8_lossy(&xml_output).to_string(); - - // Ajouter l'en-tête XML - xml.insert_str(0, "\n"); - + let xml = String::from_utf8_lossy(&xml_output).to_string(); + ( StatusCode::OK, [(axum::http::header::CONTENT_TYPE, "text/xml; charset=\"utf-8\"")],