Files
noteflow/routes/timezone-logs.routes.js
T
Claude 27840c342f Add: Système de logs visibles dans le navigateur (sans accès Docker)
NOUVEAU SYSTÈME DE LOGGING:

Au lieu d'utiliser les logs Docker (inaccessibles depuis Portainer),
création d'un système de logs accessible directement dans le navigateur.

FICHIERS AJOUTÉS:

1. services/timezone-logger.js:
   - Service singleton qui stocke les logs en mémoire
   - Limite à 500 logs max (pour éviter les fuites mémoire)
   - Méthodes: log(), getLogs(), clearLogs(), getLogsByCategory()

2. routes/timezone-logs.routes.js:
   - GET /api/timezone-logs - Logs en JSON
   - GET /api/timezone-logs/html - Interface web avec design moderne
   - GET /api/timezone-logs/category/:cat - Filtrer par catégorie
   - POST /api/timezone-logs/clear - Vider les logs

3. VOIR-LOGS-TIMEZONE.md:
   - Documentation complète du système
   - Procédure pas à pas
   - Explications des logs attendus

FICHIERS MODIFIÉS:

1. config/database.js:
   - Utilise timezoneLogger au lieu de logger.debug
   - Catégorie: PARSER

2. routes/calendar.routes.js:
   - Logs avec timezoneLogger lors de la synchronisation
   - Catégorie: SYNC (synchronisation Google Calendar)
   - Catégorie: GET (envoi au frontend)

3. server.js:
   - Ajout de la route /api/timezone-logs

INTERFACE WEB DE LOGS:

Accessible sur: http://localhost:2222/api/timezone-logs/html

Fonctionnalités:
- ✅ Design moderne avec dégradés et animations
- ✅ Statistiques en temps réel (total, par catégorie)
- ✅ Filtrage par catégorie (SYNC, PARSER, GET)
- ✅ Auto-refresh toutes les 3 secondes
- ✅ Code couleur par catégorie
- ✅ Affichage des données JSON formatées
- ✅ Bouton pour vider les logs
- ✅ Responsive, fonctionne sur mobile

UTILISATION:

1. Rebuild: docker-compose build --no-cache notes-app
2. Ouvrir: http://localhost:2222/api/timezone-logs/html
3. Synchroniser Google Calendar
4. Observer les logs en temps réel dans le navigateur

Plus besoin d'accès aux logs Docker/Portainer !
2025-11-16 17:37:30 +00:00

332 lines
8.9 KiB
JavaScript

// Route pour afficher les logs de timezone
const express = require('express');
const router = express.Router();
const { authenticateToken } = require('../middleware/auth');
const timezoneLogger = require('../services/timezone-logger');
/**
* GET /api/timezone-logs
* Afficher tous les logs de timezone
*/
router.get('/', authenticateToken, (req, res) => {
const limit = parseInt(req.query.limit) || 100;
const logs = timezoneLogger.getLogs(limit);
res.json({
total: logs.length,
limit,
logs
});
});
/**
* GET /api/timezone-logs/category/:category
* Afficher les logs d'une catégorie spécifique
*/
router.get('/category/:category', authenticateToken, (req, res) => {
const limit = parseInt(req.query.limit) || 50;
const logs = timezoneLogger.getLogsByCategory(req.category.toUpperCase(), limit);
res.json({
category: req.params.category,
total: logs.length,
limit,
logs
});
});
/**
* POST /api/timezone-logs/clear
* Vider tous les logs
*/
router.post('/clear', authenticateToken, (req, res) => {
timezoneLogger.clearLogs();
res.json({ message: 'Logs vidés avec succès' });
});
/**
* GET /api/timezone-logs/html
* Afficher les logs en HTML (pour voir directement dans le navigateur)
*/
router.get('/html', authenticateToken, (req, res) => {
const limit = parseInt(req.query.limit) || 200;
const logs = timezoneLogger.getLogs(limit);
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Logs Timezone - NoteFlow</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0f172a;
color: #e2e8f0;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.header h1 {
color: white;
font-size: 32px;
margin-bottom: 10px;
}
.header .stats {
display: flex;
gap: 20px;
margin-top: 15px;
flex-wrap: wrap;
}
.stat {
background: rgba(255,255,255,0.2);
padding: 10px 20px;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: white;
}
.stat-label {
font-size: 12px;
opacity: 0.9;
color: white;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
button {
background: #3b82f6;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.2s;
}
button:hover {
background: #2563eb;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
button.danger {
background: #ef4444;
}
button.danger:hover {
background: #dc2626;
}
button.success {
background: #10b981;
}
button.success:hover {
background: #059669;
}
.log-entry {
background: #1e293b;
border-left: 4px solid #3b82f6;
padding: 15px;
margin-bottom: 12px;
border-radius: 8px;
transition: all 0.2s;
}
.log-entry:hover {
background: #2d3b52;
transform: translateX(4px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.log-entry.SYNC {
border-left-color: #8b5cf6;
}
.log-entry.PARSER {
border-left-color: #f59e0b;
}
.log-entry.GET {
border-left-color: #10b981;
}
.log-timestamp {
color: #64748b;
font-size: 12px;
font-family: 'Courier New', monospace;
}
.log-category {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 11px;
font-weight: bold;
margin-left: 10px;
text-transform: uppercase;
}
.log-category.SYNC {
background: #8b5cf6;
color: white;
}
.log-category.PARSER {
background: #f59e0b;
color: white;
}
.log-category.GET {
background: #10b981;
color: white;
}
.log-message {
margin: 10px 0;
font-size: 14px;
line-height: 1.6;
color: #e2e8f0;
}
.log-data {
background: #0f172a;
padding: 12px;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 12px;
overflow-x: auto;
margin-top: 10px;
border: 1px solid #334155;
}
.log-data pre {
margin: 0;
color: #94a3b8;
}
.no-logs {
text-align: center;
padding: 60px 20px;
color: #64748b;
}
.filter-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.filter-btn {
padding: 8px 16px;
font-size: 13px;
background: #334155;
}
.filter-btn.active {
background: #3b82f6;
}
</style>
</head>
<body>
<div class="header">
<h1>🔍 Logs de débogage Timezone</h1>
<p style="opacity: 0.9; margin-top: 10px;">Suivi en temps réel des conversions de dates</p>
<div class="stats">
<div class="stat">
<div class="stat-value">${logs.length}</div>
<div class="stat-label">Total logs</div>
</div>
<div class="stat">
<div class="stat-value">${logs.filter(l => l.category === 'SYNC').length}</div>
<div class="stat-label">Synchronisation</div>
</div>
<div class="stat">
<div class="stat-value">${logs.filter(l => l.category === 'PARSER').length}</div>
<div class="stat-label">Parser</div>
</div>
<div class="stat">
<div class="stat-value">${logs.filter(l => l.category === 'GET').length}</div>
<div class="stat-label">Frontend</div>
</div>
</div>
</div>
<div class="controls">
<button onclick="location.reload()" class="success">🔄 Rafraîchir</button>
<button onclick="clearLogs()" class="danger">🗑️ Vider les logs</button>
<button onclick="autoRefresh()" id="autoRefreshBtn">⏱️ Auto-refresh OFF</button>
<div class="filter-buttons" style="margin-left: auto;">
<button class="filter-btn active" onclick="filterLogs('ALL')">Tous</button>
<button class="filter-btn" onclick="filterLogs('SYNC')">Sync</button>
<button class="filter-btn" onclick="filterLogs('PARSER')">Parser</button>
<button class="filter-btn" onclick="filterLogs('GET')">Get</button>
</div>
</div>
<div id="logs-container">
${logs.length === 0 ? '<div class="no-logs">Aucun log pour le moment. Synchronisez avec Google Calendar pour voir les logs.</div>' : ''}
${logs.map(log => `
<div class="log-entry ${log.category}" data-category="${log.category}">
<div>
<span class="log-timestamp">${new Date(log.timestamp).toLocaleString('fr-FR')}</span>
<span class="log-category ${log.category}">${log.category}</span>
</div>
<div class="log-message">${log.message}</div>
${log.data ? `<div class="log-data"><pre>${log.data}</pre></div>` : ''}
</div>
`).join('')}
</div>
<script>
let autoRefreshInterval = null;
function filterLogs(category) {
const entries = document.querySelectorAll('.log-entry');
const buttons = document.querySelectorAll('.filter-btn');
buttons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
entries.forEach(entry => {
if (category === 'ALL' || entry.dataset.category === category) {
entry.style.display = 'block';
} else {
entry.style.display = 'none';
}
});
}
function clearLogs() {
if (confirm('Voulez-vous vraiment vider tous les logs ?')) {
fetch('/api/timezone-logs/clear', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
}).then(() => location.reload());
}
}
function autoRefresh() {
const btn = document.getElementById('autoRefreshBtn');
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
btn.textContent = '⏱️ Auto-refresh OFF';
btn.style.background = '#3b82f6';
} else {
autoRefreshInterval = setInterval(() => location.reload(), 3000);
btn.textContent = '⏱️ Auto-refresh ON (3s)';
btn.style.background = '#10b981';
}
}
</script>
</body>
</html>
`;
res.send(html);
});
module.exports = router;