9a2bec6d2f
- Page Routing : matrices Entrées→Groupes et Groupes→Sorties avec checkboxes, éditeur de noms de canaux (ajout/suppression dynamique), sauvegarde YAML directe - Page Config : section Utilisateurs Audio Serveur (CRUD complet, modal avec sélecteur de groupe, canaux entrée/sortie) - IPC main.js : handlers server-audio-users:list/create/update/delete, routing:get, routing:save - preload.js : namespaces electronAPI.serverAudioUsers et electronAPI.routing - showModal : support du type 'select' avec options - loadViewData : routing et config lisibles sans serveur (YAML direct)
285 lines
11 KiB
HTML
285 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>PTT Live Server</title>
|
||
<link rel="stylesheet" href="styles.css">
|
||
</head>
|
||
<body>
|
||
<!-- Toast Container -->
|
||
<div class="toast-container" id="toast-container"></div>
|
||
|
||
<div id="app">
|
||
<!-- Header -->
|
||
<header class="header">
|
||
<div class="header-left">
|
||
<h1>🎙️ PTT Live Server</h1>
|
||
<span class="version" id="version">v0.3.0</span>
|
||
</div>
|
||
<div class="header-right">
|
||
<div class="server-status">
|
||
<span class="status-indicator" id="status-indicator">⚪</span>
|
||
<span id="status-text">Arrêté</span>
|
||
</div>
|
||
<button id="btn-start" class="btn btn-primary">Démarrer</button>
|
||
<button id="btn-stop" class="btn btn-secondary" disabled>Arrêter</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Content -->
|
||
<main class="main-content">
|
||
<!-- Sidebar Navigation -->
|
||
<nav class="sidebar">
|
||
<button class="nav-item active" data-view="dashboard">
|
||
📊 Dashboard
|
||
</button>
|
||
<button class="nav-item" data-view="config">
|
||
⚙️ Configuration
|
||
</button>
|
||
<button class="nav-item" data-view="groups">
|
||
👥 Groupes
|
||
</button>
|
||
<button class="nav-item" data-view="routing">
|
||
🔀 Routing
|
||
</button>
|
||
<button class="nav-item" data-view="monitoring">
|
||
📈 Monitoring
|
||
</button>
|
||
<button class="nav-item" data-view="logs">
|
||
📝 Logs
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- Content Area -->
|
||
<div class="content">
|
||
<!-- Dashboard View -->
|
||
<div id="view-dashboard" class="view active">
|
||
<h2>Dashboard</h2>
|
||
|
||
<!-- Stats Cards -->
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<div class="stat-label">Uptime</div>
|
||
<div class="stat-value" id="stat-uptime">--</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">Utilisateurs</div>
|
||
<div class="stat-value" id="stat-users">--</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">Groupes actifs</div>
|
||
<div class="stat-value" id="stat-groups">--</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">Connexions totales</div>
|
||
<div class="stat-value" id="stat-total-connections">--</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- QR Code Section -->
|
||
<div class="section">
|
||
<h3>📱 Connexion rapide clients</h3>
|
||
<div class="qr-container">
|
||
<div class="qr-wrapper">
|
||
<img id="qr-code" width="256" height="256" alt="QR Code connexion" />
|
||
<div class="qr-placeholder" id="qr-placeholder">
|
||
<span class="qr-placeholder-icon">📷</span>
|
||
<span>En attente du démarrage du serveur</span>
|
||
</div>
|
||
</div>
|
||
<div class="qr-info">
|
||
<p><strong>URL clients :</strong></p>
|
||
<p class="url-text" id="client-url">--</p>
|
||
<button class="btn btn-small" id="btn-copy-url">Copier l'URL</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Active Users -->
|
||
<div class="section">
|
||
<h3>👤 Utilisateurs connectés</h3>
|
||
<div id="users-list" class="users-list">
|
||
<p class="empty-state">Aucun utilisateur connecté</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Configuration View -->
|
||
<div id="view-config" class="view">
|
||
<h2>Configuration Audio</h2>
|
||
|
||
<div class="section">
|
||
<h3>🔌 Périphériques Audio</h3>
|
||
<div class="form-group">
|
||
<label>Device Input</label>
|
||
<select id="input-device" class="form-control">
|
||
<option>Chargement...</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Device Output</label>
|
||
<select id="output-device" class="form-control">
|
||
<option>Chargement...</option>
|
||
</select>
|
||
</div>
|
||
<button class="btn btn-primary" id="btn-save-device">Appliquer</button>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>🔗 Utilisateurs Audio Serveur</h3>
|
||
<p class="config-note" style="margin-bottom: 1rem">Participants LiveKit côté serveur avec canaux physiques d'entrée et de sortie dédiés (mix-minus natif). Voir aussi la page <strong>Routing</strong> pour le câblage entre canaux et groupes.</p>
|
||
<button class="btn btn-primary btn-small" id="btn-add-server-audio-user">➕ Ajouter</button>
|
||
<div id="server-audio-users-list" class="groups-list" style="margin-top: 1rem">
|
||
<p class="empty-state">Chargement...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>💾 Sauvegarde de configuration</h3>
|
||
<div class="config-actions">
|
||
<button class="btn btn-secondary" id="btn-export-config">Exporter config.yaml</button>
|
||
<button class="btn btn-secondary" id="btn-import-config">Importer config.yaml</button>
|
||
</div>
|
||
<p class="config-note">L'import remplace config.yaml (backup automatique en .bak). Redémarrez le serveur pour appliquer.</p>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>🎚️ Paramètres Audio</h3>
|
||
<div class="form-group">
|
||
<label>Sample Rate</label>
|
||
<select id="sample-rate" class="form-control">
|
||
<option value="44100">44.1 kHz</option>
|
||
<option value="48000" selected>48 kHz</option>
|
||
<option value="96000">96 kHz</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Bitrate par défaut (kbps)</label>
|
||
<input type="number" id="default-bitrate" class="form-control" value="96" min="32" max="320" step="32">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Jitter Buffer (ms)</label>
|
||
<input type="number" id="jitter-buffer" class="form-control" value="40" min="20" max="100" step="10">
|
||
</div>
|
||
<button class="btn btn-primary" id="btn-save-audio">Sauvegarder</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Groups View -->
|
||
<div id="view-groups" class="view">
|
||
<h2>Gestion des Groupes</h2>
|
||
<button class="btn btn-primary" id="btn-add-group">➕ Nouveau groupe</button>
|
||
<div id="groups-list" class="groups-list">
|
||
<p class="empty-state">Chargement des groupes...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Routing View -->
|
||
<div id="view-routing" class="view">
|
||
<h2>Routing Audio</h2>
|
||
|
||
<!-- Noms des canaux -->
|
||
<div class="section">
|
||
<h3>🏷️ Noms des Canaux</h3>
|
||
<p class="config-note" style="margin-bottom: 1rem">Définissez les canaux physiques disponibles. Les matrices de routing se mettent à jour après la sauvegarde.</p>
|
||
<div class="channel-names-grid">
|
||
<div class="channel-names-col">
|
||
<div class="channel-names-col-header">
|
||
<h4>Canaux Entrée</h4>
|
||
<button class="btn btn-small btn-secondary" id="btn-add-input-channel">➕ Ajouter</button>
|
||
</div>
|
||
<div id="channel-names-inputs" class="channel-names-list">
|
||
<p class="empty-state">Chargement...</p>
|
||
</div>
|
||
</div>
|
||
<div class="channel-names-col">
|
||
<div class="channel-names-col-header">
|
||
<h4>Canaux Sortie</h4>
|
||
<button class="btn btn-small btn-secondary" id="btn-add-output-channel">➕ Ajouter</button>
|
||
</div>
|
||
<div id="channel-names-outputs" class="channel-names-list">
|
||
<p class="empty-state">Chargement...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Matrice Entrées → Groupes -->
|
||
<div class="section">
|
||
<h3>🎙️ Entrées → Groupes</h3>
|
||
<p class="config-note" style="margin-bottom: 1rem">Quels canaux physiques d'entrée alimentent quels groupes LiveKit.</p>
|
||
<div class="routing-matrix-wrapper" id="routing-input-matrix">
|
||
<p class="empty-state">Chargement...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Matrice Groupes → Sorties -->
|
||
<div class="section">
|
||
<h3>🔊 Groupes → Sorties</h3>
|
||
<p class="config-note" style="margin-bottom: 1rem">Vers quels canaux physiques de sortie chaque groupe est routé.</p>
|
||
<div class="routing-matrix-wrapper" id="routing-output-matrix">
|
||
<p class="empty-state">Chargement...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Actions -->
|
||
<div class="routing-actions">
|
||
<button class="btn btn-primary" id="btn-save-routing">Sauvegarder</button>
|
||
<button class="btn btn-secondary" id="btn-reload-routing">Recharger</button>
|
||
<span class="config-note routing-restart-note hidden" id="routing-server-note">⚠️ Redémarrez le serveur pour appliquer les changements.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Monitoring View -->
|
||
<div id="view-monitoring" class="view">
|
||
<h2>Monitoring Audio</h2>
|
||
<div class="section">
|
||
<h3>🔊 VU-Mètres</h3>
|
||
<div id="vu-meters" class="vu-meters">
|
||
<p class="empty-state">En attente de données audio...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Logs View -->
|
||
<div id="view-logs" class="view">
|
||
<h2>Logs Serveur</h2>
|
||
<div class="logs-controls">
|
||
<button class="btn btn-small" id="btn-clear-logs">Effacer</button>
|
||
<button class="btn btn-small btn-secondary" id="btn-export-logs">Exporter JSON</button>
|
||
<select id="log-level-filter" class="form-control form-control-small">
|
||
<option value="">Tous les niveaux</option>
|
||
<option value="error">Erreurs</option>
|
||
<option value="warn">Warnings</option>
|
||
<option value="info">Info</option>
|
||
<option value="debug">Debug</option>
|
||
</select>
|
||
</div>
|
||
<div id="logs-container" class="logs-container">
|
||
<p class="empty-state">Aucun log</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Modal générique -->
|
||
<div id="modal-overlay" class="modal-overlay hidden">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3 id="modal-title"></h3>
|
||
</div>
|
||
<div class="modal-body" id="modal-body"></div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" id="modal-cancel">Annuler</button>
|
||
<button class="btn btn-primary" id="modal-confirm">Confirmer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Le QR Code est généré côté Main Process (lib qrcode Node), pas de dépendance CDN -->
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|