Skip to main content
Glama
admin.html27.2 kB
<!-- Template pour l'administration --> <div class="admin-management"> <div class="flex justify-between items-center mb-6"> <h2 class="text-2xl font-bold">Administration</h2> <div class="flex gap-3"> <button class="btn btn-secondary" id="export-data">📦 Export données</button> <button class="btn btn-danger" id="restart-server">🔄 Redémarrer</button> </div> </div> <!-- Métriques système --> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"> <div class="card"> <div class="card-body text-center"> <div class="text-3xl mb-2">👥</div> <div class="text-2xl font-bold" id="total-users-count">0</div> <div class="text-sm text-secondary">Utilisateurs</div> </div> </div> <div class="card"> <div class="card-body text-center"> <div class="text-3xl mb-2">🔗</div> <div class="text-2xl font-bold" id="active-sessions-count">0</div> <div class="text-sm text-secondary">Sessions actives</div> </div> </div> <div class="card"> <div class="card-body text-center"> <div class="text-3xl mb-2">💾</div> <div class="text-2xl font-bold" id="database-size">0 MB</div> <div class="text-sm text-secondary">Taille BDD</div> </div> </div> <div class="card"> <div class="card-body text-center"> <div class="text-3xl mb-2">⚡</div> <div class="text-2xl font-bold" id="system-load">0%</div> <div class="text-sm text-secondary">Charge système</div> </div> </div> </div> <!-- Gestion des utilisateurs --> <div class="card mb-6"> <div class="card-header"> <h3 class="card-title">Gestion des utilisateurs</h3> <button class="btn btn-primary btn-sm" id="add-user">➕ Nouvel utilisateur</button> </div> <div class="card-body"> <div class="table-container"> <table class="table"> <thead> <tr> <th>Utilisateur</th> <th>Rôle</th> <th>Dernière connexion</th> <th>Sessions actives</th> <th>Actions</th> </tr> </thead> <tbody id="users-table-body"> <!-- Les utilisateurs seront ajoutés dynamiquement --> </tbody> </table> </div> </div> </div> <!-- Monitoring système --> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"> <!-- Activité récente --> <div class="card"> <div class="card-header"> <h3 class="card-title">Activité récente</h3> </div> <div class="card-body"> <div id="recent-activity" class="space-y-2 max-h-64 overflow-y-auto"> <!-- L'activité sera ajoutée dynamiquement --> </div> </div> </div> <!-- Statistiques système --> <div class="card"> <div class="card-header"> <h3 class="card-title">Statistiques système</h3> </div> <div class="card-body"> <div class="space-y-4"> <div class="flex justify-between"> <span>Mémoire utilisée:</span> <span id="memory-usage" class="font-semibold">0 MB</span> </div> <div class="flex justify-between"> <span>Processus actifs:</span> <span id="active-processes" class="font-semibold">0</span> </div> <div class="flex justify-between"> <span>Uptime:</span> <span id="system-uptime" class="font-semibold">--</span> </div> <div class="flex justify-between"> <span>Version:</span> <span id="server-version" class="font-semibold">1.0.0</span> </div> </div> </div> </div> </div> <!-- Actions système --> <div class="card"> <div class="card-header"> <h3 class="card-title">Actions système</h3> </div> <div class="card-body"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <button class="btn btn-secondary w-full" id="backup-database"> 💾 Sauvegarder BDD </button> <button class="btn btn-secondary w-full" id="rotate-logs"> 📄 Rotation logs </button> <button class="btn btn-secondary w-full" id="clear-cache"> 🗑️ Vider cache </button> <button class="btn btn-warning w-full" id="reset-permissions"> 🔄 Reset permissions </button> <button class="btn btn-warning w-full" id="clean-sessions"> 🧹 Nettoyer sessions </button> <button class="btn btn-danger w-full" id="factory-reset"> ⚠️ Reset usine </button> </div> </div> </div> </div> <!-- Modal pour créer/éditer un utilisateur --> <div id="user-modal" class="modal hidden"> <div class="modal-content"> <div class="modal-header"> <h3 class="modal-title" id="user-modal-title">Nouvel utilisateur</h3> <button class="modal-close" id="close-user-modal">✕</button> </div> <div class="modal-body"> <form id="user-form"> <input type="hidden" id="user-id" value=""> <div class="form-group"> <label class="form-label required">Nom d'utilisateur</label> <input type="text" class="form-input" id="user-username" required> </div> <div class="form-group"> <label class="form-label">Nom complet</label> <input type="text" class="form-input" id="user-fullname"> </div> <div class="form-group"> <label class="form-label">Email</label> <input type="email" class="form-input" id="user-email"> </div> <div class="form-group"> <label class="form-label required">Rôle</label> <select class="form-input" id="user-role" required> <option value="user">Utilisateur</option> <option value="admin">Administrateur</option> </select> </div> <div class="form-group" id="password-group"> <label class="form-label required">Mot de passe</label> <input type="password" class="form-input" id="user-password" required> <p class="form-help">Minimum 8 caractères</p> </div> <div class="form-group"> <label class="checkbox-item"> <input type="checkbox" id="user-active" checked> <span class="checkmark"></span> Compte actif </label> </div> </form> </div> <div class="modal-footer"> <button class="btn btn-secondary" id="cancel-user">Annuler</button> <button class="btn btn-primary" id="save-user">Enregistrer</button> </div> </div> </div> <script> class AdminManager { constructor() { this.users = []; this.editingUserId = null; this.init(); } init() { this.loadData(); this.setupEventListeners(); // Auto-refresh toutes les 30 secondes setInterval(() => { this.loadMetrics(); this.loadRecentActivity(); }, 30000); } setupEventListeners() { // Boutons principaux document.getElementById('add-user').addEventListener('click', () => { this.showUserModal(); }); document.getElementById('export-data').addEventListener('click', () => { this.exportData(); }); document.getElementById('restart-server').addEventListener('click', () => { this.restartServer(); }); // Modal utilisateur document.getElementById('close-user-modal').addEventListener('click', () => { this.hideUserModal(); }); document.getElementById('cancel-user').addEventListener('click', () => { this.hideUserModal(); }); document.getElementById('save-user').addEventListener('click', () => { this.saveUser(); }); // Actions système document.getElementById('backup-database').addEventListener('click', () => { this.backupDatabase(); }); document.getElementById('rotate-logs').addEventListener('click', () => { this.rotateLogs(); }); document.getElementById('clear-cache').addEventListener('click', () => { this.clearCache(); }); document.getElementById('reset-permissions').addEventListener('click', () => { this.resetPermissions(); }); document.getElementById('clean-sessions').addEventListener('click', () => { this.cleanSessions(); }); document.getElementById('factory-reset').addEventListener('click', () => { this.factoryReset(); }); } async loadData() { await Promise.all([ this.loadMetrics(), this.loadUsers(), this.loadRecentActivity() ]); } async loadMetrics() { try { const response = await fetch('/api/admin/metrics'); const metrics = await response.json(); document.getElementById('total-users-count').textContent = metrics.total_users || 0; document.getElementById('active-sessions-count').textContent = metrics.active_sessions || 0; document.getElementById('database-size').textContent = `${metrics.database_size || 0} MB`; document.getElementById('system-load').textContent = `${metrics.system_load || 0}%`; document.getElementById('memory-usage').textContent = `${metrics.memory_usage || 0} MB`; document.getElementById('active-processes').textContent = metrics.active_processes || 0; document.getElementById('system-uptime').textContent = this.formatUptime(metrics.uptime || 0); document.getElementById('server-version').textContent = metrics.version || '1.0.0'; } catch (error) { console.error('Erreur chargement métriques admin:', error); } } async loadUsers() { try { const response = await fetch('/api/admin/users'); this.users = await response.json(); this.renderUsers(); } catch (error) { console.error('Erreur chargement utilisateurs:', error); window.showToast('Erreur lors du chargement des utilisateurs', 'error'); } } renderUsers() { const tbody = document.getElementById('users-table-body'); tbody.innerHTML = ''; if (this.users.length === 0) { tbody.innerHTML = ` <tr> <td colspan="5" class="text-center py-4"> Aucun utilisateur trouvé </td> </tr> `; return; } this.users.forEach(user => { const row = document.createElement('tr'); row.innerHTML = ` <td> <div class="flex items-center gap-2"> <div class="user-avatar-sm">${(user.full_name || user.username).charAt(0)}</div> <div> <div class="font-medium">${user.full_name || user.username}</div> <div class="text-sm text-secondary">${user.username}</div> </div> </div> </td> <td> <span class="badge ${user.role === 'admin' ? 'badge-warning' : 'badge-info'}"> ${user.role === 'admin' ? '👑 Admin' : '👤 User'} </span> </td> <td class="text-sm"> ${user.last_login ? this.formatDate(user.last_login) : 'Jamais'} </td> <td class="text-sm"> <span class="badge ${user.active_sessions > 0 ? 'badge-success' : 'badge-secondary'}"> ${user.active_sessions || 0} </span> </td> <td> <div class="flex gap-2"> <button class="btn btn-secondary btn-sm" onclick="adminManager.editUser(${user.id})"> ✏️ </button> <button class="btn btn-danger btn-sm" onclick="adminManager.deleteUser(${user.id})" ${user.username === 'admin' ? 'disabled' : ''}> 🗑️ </button> </div> </td> `; tbody.appendChild(row); }); } async loadRecentActivity() { try { const response = await fetch('/api/admin/activity'); const activities = await response.json(); const container = document.getElementById('recent-activity'); container.innerHTML = ''; if (activities.length === 0) { container.innerHTML = '<p class="text-secondary">Aucune activité récente</p>'; return; } activities.slice(0, 10).forEach(activity => { const item = document.createElement('div'); item.className = 'flex items-center gap-3 p-2 bg-gray-50 rounded'; item.innerHTML = ` <div class="text-lg">${this.getActivityIcon(activity.type)}</div> <div class="flex-1"> <div class="text-sm">${activity.message}</div> <div class="text-xs text-secondary">${this.formatDate(activity.timestamp)}</div> </div> `; container.appendChild(item); }); } catch (error) { console.error('Erreur chargement activité:', error); } } showUserModal(user = null) { const modal = document.getElementById('user-modal'); const title = document.getElementById('user-modal-title'); const passwordGroup = document.getElementById('password-group'); const passwordInput = document.getElementById('user-password'); if (user) { title.textContent = 'Éditer l\'utilisateur'; this.editingUserId = user.id; document.getElementById('user-id').value = user.id; document.getElementById('user-username').value = user.username; document.getElementById('user-fullname').value = user.full_name || ''; document.getElementById('user-email').value = user.email || ''; document.getElementById('user-role').value = user.role; document.getElementById('user-active').checked = user.is_active !== false; // Pour l'édition, le mot de passe est optionnel passwordInput.required = false; passwordGroup.querySelector('.form-label').textContent = 'Nouveau mot de passe (optionnel)'; } else { title.textContent = 'Nouvel utilisateur'; this.editingUserId = null; document.getElementById('user-form').reset(); document.getElementById('user-active').checked = true; // Pour la création, le mot de passe est requis passwordInput.required = true; passwordGroup.querySelector('.form-label').textContent = 'Mot de passe'; } modal.classList.remove('hidden'); } hideUserModal() { document.getElementById('user-modal').classList.add('hidden'); this.editingUserId = null; } async saveUser() { const form = document.getElementById('user-form'); if (!form.checkValidity()) { form.reportValidity(); return; } const userData = { username: document.getElementById('user-username').value, full_name: document.getElementById('user-fullname').value || null, email: document.getElementById('user-email').value || null, role: document.getElementById('user-role').value, is_active: document.getElementById('user-active').checked }; const password = document.getElementById('user-password').value; if (password) { userData.password = password; } try { const url = this.editingUserId ? `/api/admin/users/${this.editingUserId}` : '/api/admin/users'; const method = this.editingUserId ? 'PUT' : 'POST'; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userData) }); if (response.ok) { this.hideUserModal(); this.loadUsers(); window.showToast( this.editingUserId ? 'Utilisateur modifié avec succès' : 'Utilisateur créé avec succès', 'success' ); } else { const error = await response.json(); window.showToast(`Erreur: ${error.detail || 'Erreur inconnue'}`, 'error'); } } catch (error) { console.error('Erreur sauvegarde utilisateur:', error); window.showToast('Erreur lors de la sauvegarde', 'error'); } } editUser(userId) { const user = this.users.find(u => u.id === userId); if (user) { this.showUserModal(user); } } async deleteUser(userId) { const user = this.users.find(u => u.id === userId); if (!user) return; if (!confirm(`Êtes-vous sûr de vouloir supprimer l'utilisateur "${user.username}" ?`)) { return; } try { const response = await fetch(`/api/admin/users/${userId}`, { method: 'DELETE' }); if (response.ok) { this.loadUsers(); window.showToast('Utilisateur supprimé avec succès', 'success'); } else { const error = await response.json(); window.showToast(`Erreur: ${error.detail || 'Erreur inconnue'}`, 'error'); } } catch (error) { console.error('Erreur suppression utilisateur:', error); window.showToast('Erreur lors de la suppression', 'error'); } } // Actions système async backupDatabase() { try { const response = await fetch('/api/admin/backup', { method: 'POST' }); if (response.ok) { window.showToast('Sauvegarde créée avec succès', 'success'); } else { window.showToast('Erreur lors de la sauvegarde', 'error'); } } catch (error) { console.error('Erreur sauvegarde:', error); window.showToast('Erreur lors de la sauvegarde', 'error'); } } async rotateLogs() { try { const response = await fetch('/api/admin/logs/rotate', { method: 'POST' }); if (response.ok) { window.showToast('Rotation des logs effectuée', 'success'); } else { window.showToast('Erreur lors de la rotation', 'error'); } } catch (error) { console.error('Erreur rotation logs:', error); window.showToast('Erreur lors de la rotation des logs', 'error'); } } async clearCache() { try { const response = await fetch('/api/admin/cache/clear', { method: 'POST' }); if (response.ok) { window.showToast('Cache vidé avec succès', 'success'); } else { window.showToast('Erreur lors du vidage du cache', 'error'); } } catch (error) { console.error('Erreur vidage cache:', error); window.showToast('Erreur lors du vidage du cache', 'error'); } } async resetPermissions() { if (!confirm('Voulez-vous vraiment remettre à zéro toutes les permissions ?')) { return; } try { const response = await fetch('/api/admin/permissions/reset', { method: 'POST' }); if (response.ok) { window.showToast('Permissions remises à zéro', 'success'); } else { window.showToast('Erreur lors du reset des permissions', 'error'); } } catch (error) { console.error('Erreur reset permissions:', error); window.showToast('Erreur lors du reset des permissions', 'error'); } } async cleanSessions() { try { const response = await fetch('/api/admin/sessions/clean', { method: 'POST' }); if (response.ok) { window.showToast('Sessions nettoyées avec succès', 'success'); this.loadMetrics(); } else { window.showToast('Erreur lors du nettoyage', 'error'); } } catch (error) { console.error('Erreur nettoyage sessions:', error); window.showToast('Erreur lors du nettoyage des sessions', 'error'); } } async factoryReset() { const confirmation = prompt('Tapez "RESET" pour confirmer la remise à zéro complète:'); if (confirmation !== 'RESET') { return; } try { const response = await fetch('/api/admin/factory-reset', { method: 'POST' }); if (response.ok) { window.showToast('Remise à zéro effectuée. Redirection...', 'success'); setTimeout(() => { window.location.href = '/login'; }, 2000); } else { window.showToast('Erreur lors de la remise à zéro', 'error'); } } catch (error) { console.error('Erreur factory reset:', error); window.showToast('Erreur lors de la remise à zéro', 'error'); } } async exportData() { try { const response = await fetch('/api/admin/export'); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `mcp-bridge-export-${new Date().toISOString().split('T')[0]}.zip`; a.click(); window.URL.revokeObjectURL(url); window.showToast('Données exportées avec succès', 'success'); } else { window.showToast('Erreur lors de l\'export', 'error'); } } catch (error) { console.error('Erreur export:', error); window.showToast('Erreur lors de l\'export des données', 'error'); } } async restartServer() { if (!confirm('Voulez-vous vraiment redémarrer le serveur ?')) { return; } try { const response = await fetch('/api/admin/restart', { method: 'POST' }); if (response.ok) { window.showToast('Redémarrage en cours...', 'warning', 5000); // Rediriger vers la page de login après 5 secondes setTimeout(() => { window.location.href = '/login'; }, 5000); } else { window.showToast('Erreur lors du redémarrage', 'error'); } } catch (error) { console.error('Erreur redémarrage:', error); window.showToast('Erreur lors du redémarrage', 'error'); } } // Utilitaires formatUptime(seconds) { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (days > 0) return `${days}j ${hours}h ${minutes}m`; if (hours > 0) return `${hours}h ${minutes}m`; return `${minutes}m`; } formatDate(dateString) { const date = new Date(dateString); const now = new Date(); const diff = now - date; if (diff < 60000) return 'À l\'instant'; if (diff < 3600000) return `Il y a ${Math.floor(diff / 60000)} min`; if (diff < 86400000) return `Il y a ${Math.floor(diff / 3600000)} h`; return date.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }); } getActivityIcon(type) { const icons = { login: '🔑', logout: '🚪', error: '❌', warning: '⚠️', info: 'ℹ️', user_created: '👤', user_deleted: '🗑️', permission_changed: '🔐', backup: '💾', restart: '🔄' }; return icons[type] || 'ℹ️'; } } // Initialiser le gestionnaire d'administration const adminManager = new AdminManager(); </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Jonathan97480/McpHomeAssistant'

If you have feedback or need assistance with the MCP directory API, please join our Discord server