Correction du loggueur qui sature les CPU des browsers
This commit is contained in:
@@ -42,13 +42,13 @@
|
||||
</div>
|
||||
<div class="log-content">
|
||||
<div class="message markdown-content">
|
||||
<details v-if="isTooLong(log.message)" class="log-details">
|
||||
<details v-if="log.isTooLong" class="log-details">
|
||||
<summary class="log-summary">
|
||||
<span class="truncated-text">{{ truncateMessage(log.message) }}</span>
|
||||
<span class="truncated-text">{{ log.truncatedMessage }}</span>
|
||||
</summary>
|
||||
<div class="full-message" v-html="renderMarkdown(log.message)"></div>
|
||||
<div class="full-message" v-html="log.renderedHtml"></div>
|
||||
</details>
|
||||
<div v-else v-html="renderMarkdown(log.message)"></div>
|
||||
<div v-else v-html="log.renderedHtml"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,11 +103,14 @@ const levelOrder = {
|
||||
'TRACE': 4
|
||||
}
|
||||
|
||||
// Pré-calculer filteredLogs de manière optimisée
|
||||
const filteredLogs = computed(() => {
|
||||
if (levelFilter.value === 'ALL') {
|
||||
return logs.value
|
||||
}
|
||||
return logs.value.filter(log => log.level === levelFilter.value)
|
||||
// Utiliser la référence directe pour éviter des copies inutiles
|
||||
const filter = levelFilter.value
|
||||
return logs.value.filter(log => log.level === filter)
|
||||
})
|
||||
|
||||
// Fonction pour mettre à jour le niveau de log côté serveur
|
||||
@@ -157,65 +160,62 @@ function formatTimestamp(timestamp) {
|
||||
})
|
||||
}
|
||||
|
||||
function isTooLong(message) {
|
||||
// Un message est trop long s'il a plus d'une ligne OU plus de 200 caractères
|
||||
// Pré-traiter un log : calculer HTML, troncature, etc. UNE SEULE FOIS
|
||||
function preprocessLog(message) {
|
||||
// ÉTAPE 1: Déterminer si trop long
|
||||
const firstLineEnd = message.indexOf('\n')
|
||||
return firstLineEnd !== -1 || message.length > 200
|
||||
}
|
||||
const isTooLong = firstLineEnd !== -1 || message.length > 200
|
||||
|
||||
function truncateMessage(message) {
|
||||
// Prendre la première ligne, ou les 200 premiers caractères si pas de saut de ligne
|
||||
const firstLineEnd = message.indexOf('\n')
|
||||
if (firstLineEnd !== -1) {
|
||||
return message.substring(0, firstLineEnd).trim()
|
||||
}
|
||||
return message.substring(0, 200).trim()
|
||||
}
|
||||
// ÉTAPE 2: Calculer le message tronqué si nécessaire
|
||||
const truncatedMessage = isTooLong
|
||||
? (firstLineEnd !== -1
|
||||
? message.substring(0, firstLineEnd).trim()
|
||||
: message.substring(0, 200).trim())
|
||||
: null
|
||||
|
||||
function renderMarkdown(text) {
|
||||
// ÉTAPE 1 : Pré-processing pour détecter et protéger le XML
|
||||
let processedText = text
|
||||
// ÉTAPE 3: Pré-processing pour détecter et protéger le XML
|
||||
let processedText = message
|
||||
|
||||
// Détecter si le message contient du XML
|
||||
// Pattern : cherche <?xml ou des balises XML racine communes (scpd, root, service, etc.)
|
||||
const hasXml = /<\?xml|<(scpd|root|service|device|actionList|stateVariable)[>\s]/i.test(text)
|
||||
const hasXml = /<\?xml|<(scpd|root|service|device|actionList|stateVariable)[>\s]/i.test(message)
|
||||
|
||||
if (hasXml) {
|
||||
// Extraire tout ce qui ressemble à du XML (du <?xml ou première balise jusqu'à la fin)
|
||||
const xmlStartMatch = text.match(/<\?xml[\s\S]*$/)
|
||||
const xmlStartMatch = message.match(/<\?xml[\s\S]*$/)
|
||||
|
||||
if (xmlStartMatch) {
|
||||
const xmlContent = xmlStartMatch[0]
|
||||
const beforeXml = text.substring(0, text.indexOf(xmlContent))
|
||||
|
||||
// Créer le texte avec le XML dans un bloc de code
|
||||
const beforeXml = message.substring(0, message.indexOf(xmlContent))
|
||||
processedText = beforeXml + '\n```xml\n' + xmlContent + '\n```\n'
|
||||
} else {
|
||||
// Fallback : chercher une balise racine XML
|
||||
const xmlMatch = text.match(/<([a-zA-Z][a-zA-Z0-9:-]*)[>\s][\s\S]*/)
|
||||
const xmlMatch = message.match(/<([a-zA-Z][a-zA-Z0-9:-]*)[>\s][\s\S]*/)
|
||||
if (xmlMatch) {
|
||||
const xmlContent = xmlMatch[0]
|
||||
const beforeXml = text.substring(0, text.indexOf(xmlContent))
|
||||
const beforeXml = message.substring(0, message.indexOf(xmlContent))
|
||||
processedText = beforeXml + '\n```xml\n' + xmlContent + '\n```\n'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ÉTAPE 2 : Détecter et transformer les liens d'images
|
||||
// Pattern pour détecter les URLs d'images (png, jpg, jpeg, gif, webp, svg)
|
||||
// ÉTAPE 4: Détecter et transformer les liens d'images
|
||||
const imageUrlPattern = /(https?:\/\/[^\s]+\.(?:png|jpg|jpeg|gif|webp|svg)(?:\?[^\s]*)?)/gi
|
||||
processedText = processedText.replace(imageUrlPattern, (match) => {
|
||||
return `\n\n`
|
||||
})
|
||||
|
||||
// ÉTAPE 3 : Convertir markdown en HTML
|
||||
// ÉTAPE 5: Convertir markdown en HTML
|
||||
const rawHtml = marked.parse(processedText, { async: false })
|
||||
|
||||
// ÉTAPE 4 : Nettoyer pour la sécurité
|
||||
return DOMPurify.sanitize(rawHtml, {
|
||||
// ÉTAPE 6: Nettoyer pour la sécurité
|
||||
const renderedHtml = DOMPurify.sanitize(rawHtml, {
|
||||
ALLOWED_TAGS: ['strong', 'em', 'code', 'pre', 'a', 'ul', 'ol', 'li', 'p', 'br', 'span', 'img'],
|
||||
ALLOWED_ATTR: ['href', 'target', 'class', 'src', 'alt', 'title']
|
||||
})
|
||||
|
||||
return {
|
||||
isTooLong,
|
||||
truncatedMessage,
|
||||
renderedHtml
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAutoScroll() {
|
||||
@@ -265,6 +265,12 @@ function connectSSE() {
|
||||
logEntry.isHistory = true
|
||||
}
|
||||
|
||||
// PRÉ-TRAITER le log UNE SEULE FOIS à la réception
|
||||
const processed = preprocessLog(logEntry.message)
|
||||
logEntry.isTooLong = processed.isTooLong
|
||||
logEntry.truncatedMessage = processed.truncatedMessage
|
||||
logEntry.renderedHtml = processed.renderedHtml
|
||||
|
||||
logs.value.push(logEntry)
|
||||
|
||||
// Limiter à 1000 logs en mémoire
|
||||
@@ -325,17 +331,32 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
// Désactiver auto-scroll si l'utilisateur scroll manuellement
|
||||
watch(logContainer, (container) => {
|
||||
let scrollHandler = null
|
||||
watch(logContainer, (container, oldContainer) => {
|
||||
// Nettoyer l'ancien listener si existant
|
||||
if (oldContainer && scrollHandler) {
|
||||
oldContainer.removeEventListener('scroll', scrollHandler)
|
||||
}
|
||||
|
||||
if (!container) return
|
||||
|
||||
container.addEventListener('scroll', () => {
|
||||
scrollHandler = () => {
|
||||
const isAtBottom =
|
||||
container.scrollHeight - container.scrollTop <= container.clientHeight + 50
|
||||
|
||||
if (!isAtBottom && autoScroll.value) {
|
||||
autoScroll.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
container.addEventListener('scroll', scrollHandler, { passive: true })
|
||||
})
|
||||
|
||||
// Nettoyer au démontage
|
||||
onUnmounted(() => {
|
||||
if (logContainer.value && scrollHandler) {
|
||||
logContainer.value.removeEventListener('scroll', scrollHandler)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use xmltree::{Element, XMLNode};
|
||||
|
||||
use crate::{
|
||||
devices::{Device, DeviceInstance},
|
||||
UpnpObject, UpnpModel, UpnpInstance, UpnpTyped,
|
||||
UpnpObject, UpnpModel, UpnpInstance,
|
||||
};
|
||||
|
||||
impl UpnpObject for Device {
|
||||
|
||||
@@ -465,7 +465,6 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
devices::Device,
|
||||
services::Service,
|
||||
UpnpModel,
|
||||
};
|
||||
|
||||
@@ -477,13 +476,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_device_registration() {
|
||||
let registry = DeviceRegistry::new();
|
||||
let mut registry = DeviceRegistry::new();
|
||||
let device = Device::new(
|
||||
"TestDevice".to_string(),
|
||||
"MediaRenderer".to_string(),
|
||||
"Test Renderer".to_string(),
|
||||
);
|
||||
let instance = Arc::new(device.create_instance());
|
||||
let instance = device.create_instance();
|
||||
|
||||
assert!(registry.register(instance.clone()).is_ok());
|
||||
assert_eq!(registry.count(), 1);
|
||||
@@ -494,13 +493,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_device_retrieval() {
|
||||
let registry = DeviceRegistry::new();
|
||||
let mut registry = DeviceRegistry::new();
|
||||
let device = Device::new(
|
||||
"TestDevice".to_string(),
|
||||
"MediaRenderer".to_string(),
|
||||
"Test Renderer".to_string(),
|
||||
);
|
||||
let instance = Arc::new(device.create_instance());
|
||||
let instance = device.create_instance();
|
||||
let udn = instance.udn().to_string();
|
||||
|
||||
registry.register(instance.clone()).unwrap();
|
||||
@@ -517,13 +516,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_device_unregistration() {
|
||||
let registry = DeviceRegistry::new();
|
||||
let mut registry = DeviceRegistry::new();
|
||||
let device = Device::new(
|
||||
"TestDevice".to_string(),
|
||||
"MediaRenderer".to_string(),
|
||||
"Test Renderer".to_string(),
|
||||
);
|
||||
let instance = Arc::new(device.create_instance());
|
||||
let instance = device.create_instance();
|
||||
let udn = instance.udn().to_string();
|
||||
|
||||
registry.register(instance).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user