// Configuration globale let config = { theme: 'dark', tabs: [], apps: [], active_plugin: 'librehardwaremonitor', plugins: {} }; let allSensors = {}; let availablePlugins = []; // Initialisation document.addEventListener('DOMContentLoaded', async () => { // Charger les plugins disponibles await loadPlugins(); // Charger la configuration await loadConfig(); // Charger les capteurs await loadSensors(); // Charger la config Plexamp await loadPlexampConfig(); // Appliquer le thème applyTheme(config.theme); // Setup event listeners setupEventListeners(); // Afficher les onglets et apps renderTabs(); renderApps(); }); // === Gestion des sections === function setupEventListeners() { // Navigation entre sections document.querySelectorAll('.section-tab').forEach(tab => { tab.addEventListener('click', () => { const section = tab.dataset.section; switchSection(section); }); }); // Bouton sauvegarder document.getElementById('save-btn').addEventListener('click', saveConfig); // Bouton ajouter onglet document.getElementById('add-tab-btn').addEventListener('click', addTab); // Bouton ajouter app document.getElementById('add-app-btn').addEventListener('click', addApp); // Sélecteur de thème document.querySelectorAll('input[name="theme"]').forEach(radio => { radio.addEventListener('change', (e) => { config.theme = e.target.value; applyTheme(config.theme); }); }); // Configuration du plugin (host/port) const pluginHost = document.getElementById('plugin-host'); const pluginPort = document.getElementById('plugin-port'); if (pluginHost) { pluginHost.addEventListener('change', updatePluginConfig); } if (pluginPort) { pluginPort.addEventListener('change', updatePluginConfig); } // Bouton tester connexion const testBtn = document.getElementById('test-connection-btn'); if (testBtn) { testBtn.addEventListener('click', testPluginConnection); } } function switchSection(sectionName) { // Désactiver tous les tabs et sections document.querySelectorAll('.section-tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.config-section').forEach(s => s.classList.remove('active')); // Activer le bon document.querySelector(`.section-tab[data-section="${sectionName}"]`).classList.add('active'); document.getElementById(`section-${sectionName}`).classList.add('active'); // Rafraîchir la section ordre quand on y accède if (sectionName === 'order') { renderOrderSection(); } } // === Gestion des Plugins === async function loadPlugins() { try { const response = await fetch('/api/plugins'); availablePlugins = await response.json(); renderPluginSelector(); } catch (error) { console.error('Erreur chargement plugins:', error); } } function renderPluginSelector() { const container = document.getElementById('plugin-selector'); if (!container) return; container.innerHTML = ''; availablePlugins.forEach(plugin => { const card = document.createElement('div'); card.className = 'plugin-card' + (plugin.active ? ' active' : ''); card.dataset.pluginId = plugin.id; const icon = plugin.id === 'librehardwaremonitor' ? '🖥️' : '📊'; card.innerHTML = `
${icon} ${plugin.name}
${plugin.description}
Port par défaut: ${plugin.default_port} | Site web ↗
`; card.addEventListener('click', () => selectPlugin(plugin.id)); container.appendChild(card); }); // Mettre à jour l'affichage de la config updatePluginConfigDisplay(); updatePluginInfo(); } function selectPlugin(pluginId) { const currentPlugin = config.active_plugin; if (pluginId === currentPlugin) return; // Confirmer le changement (les capteurs seront différents) if (config.tabs.some(t => t.sensors.length > 0)) { if (!confirm('Changer de plugin va afficher des capteurs différents. Les capteurs configurés pour ce plugin seront conservés si vous revenez. Continuer ?')) { return; } } // Mettre à jour visuellement document.querySelectorAll('.plugin-card').forEach(card => { card.classList.toggle('active', card.dataset.pluginId === pluginId); }); // Mettre à jour la config config.active_plugin = pluginId; // Mettre à jour l'affichage updatePluginConfigDisplay(); updatePluginInfo(); // Sauvegarder et recharger switchPluginOnServer(pluginId); } async function switchPluginOnServer(pluginId) { try { const response = await fetch('/api/plugins/switch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ plugin_id: pluginId }) }); const result = await response.json(); if (result.success) { showNotification('Plugin changé, rechargement des capteurs...', 'success'); // Recharger la config et les capteurs await loadConfig(); await loadSensors(); renderTabs(); } else { showNotification('Erreur: ' + result.error, 'error'); } } catch (error) { console.error('Erreur changement plugin:', error); showNotification('Erreur de connexion', 'error'); } } function updatePluginConfigDisplay() { const pluginId = config.active_plugin || 'librehardwaremonitor'; const pluginConfig = config.plugins?.[pluginId] || {}; const hostInput = document.getElementById('plugin-host'); const portInput = document.getElementById('plugin-port'); if (hostInput) { hostInput.value = pluginConfig.host || '127.0.0.1'; } if (portInput) { // Trouver le port par défaut du plugin const plugin = availablePlugins.find(p => p.id === pluginId); portInput.value = pluginConfig.port || plugin?.default_port || 8085; } } function updatePluginConfig() { const pluginId = config.active_plugin || 'librehardwaremonitor'; const host = document.getElementById('plugin-host').value || '127.0.0.1'; const port = parseInt(document.getElementById('plugin-port').value) || 8085; if (!config.plugins) config.plugins = {}; config.plugins[pluginId] = { host, port }; } function updatePluginInfo() { const container = document.getElementById('plugin-info'); if (!container) return; const pluginId = config.active_plugin || 'librehardwaremonitor'; if (pluginId === 'librehardwaremonitor') { container.innerHTML = `

ℹ️ Configuration LibreHardwareMonitor

Pour utiliser ce plugin :

`; } else if (pluginId === 'hwinfo') { container.innerHTML = `

ℹ️ Configuration HWiNFO

Pour utiliser ce plugin, vous avez besoin de 2 logiciels :

Note : La version gratuite de HWiNFO limite le Shared Memory à 12 heures. Après, il faut réactiver manuellement.

`; } } async function testPluginConnection() { const statusEl = document.getElementById('connection-status'); const pluginId = config.active_plugin || 'librehardwaremonitor'; const host = document.getElementById('plugin-host').value || '127.0.0.1'; const port = parseInt(document.getElementById('plugin-port').value) || 8085; statusEl.className = 'loading'; statusEl.textContent = '⏳ Test en cours...'; try { const response = await fetch('/api/plugins/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ plugin_id: pluginId, config: { host, port } }) }); const result = await response.json(); if (result.success) { statusEl.className = 'success'; statusEl.textContent = `✅ ${result.message}`; // Mettre à jour la config si le test réussit updatePluginConfig(); } else { statusEl.className = 'error'; statusEl.textContent = `❌ ${result.message}`; } } catch (error) { statusEl.className = 'error'; statusEl.textContent = '❌ Erreur de connexion au serveur'; } } // === Chargement config === async function loadConfig() { try { const response = await fetch('/api/config'); config = await response.json(); // Assurer qu'il y a au moins un onglet if (!config.tabs || config.tabs.length === 0) { config.tabs = [{ id: 'tab1', name: 'Général', sensors: [] }]; } // Valeurs par défaut pour les plugins if (!config.active_plugin) { config.active_plugin = 'librehardwaremonitor'; } if (!config.plugins) { config.plugins = {}; } // Sélectionner le thème const themeRadio = document.querySelector(`input[name="theme"][value="${config.theme}"]`); if (themeRadio) { themeRadio.checked = true; } // Remplir le sélecteur d'onglet pour les apps updateAppsTabSelector(); // Mettre à jour l'affichage des plugins renderPluginSelector(); } catch (error) { console.error('Erreur chargement config:', error); showNotification('Erreur de chargement', 'error'); } } function updateAppsTabSelector() { const select = document.getElementById('apps-tab-select'); if (!select) return; // Sauvegarder la valeur actuelle const currentValue = config.apps_tab || ''; // Garder la première option select.innerHTML = ''; // Ajouter les onglets config.tabs.forEach(tab => { const option = document.createElement('option'); option.value = tab.id; option.textContent = tab.name; select.appendChild(option); }); // Sélectionner la valeur actuelle select.value = currentValue; // Utiliser onchange au lieu de addEventListener pour éviter les doublons select.onchange = (e) => { config.apps_tab = e.target.value; showNotification('Onglet des apps modifié (pensez à sauvegarder)', 'success'); }; } // === Chargement capteurs === async function loadSensors() { const loadingDiv = document.getElementById('sensors-loading'); const listDiv = document.getElementById('sensors-list'); try { const response = await fetch('/api/lhm/hierarchy'); allSensors = await response.json(); loadingDiv.style.display = 'none'; listDiv.style.display = 'block'; renderSensorsHierarchical(); } catch (error) { console.error('Erreur chargement capteurs:', error); loadingDiv.innerHTML = '⚠️ Erreur: Impossible de charger les capteurs. Vérifiez que LibreHardwareMonitor est lancé.'; } } function renderSensorsHierarchical() { const listDiv = document.getElementById('sensors-list'); listDiv.innerHTML = ''; // Créer un Set des capteurs déjà sélectionnés const selectedSensors = new Map(); config.tabs.forEach(tab => { tab.sensors.forEach(sensor => { selectedSensors.set(sensor.id, { tabId: tab.id, showGraph: sensor.show_graph, viz_type: sensor.viz_type // Utiliser snake_case pour cohérence }); }); }); // Fonction récursive pour afficher l'arbre function renderNode(node, parentDiv, level = 0) { // Ignorer le nœud racine "Sensor" if (level === 0 && node.name === "Sensor") { node.children.forEach(child => renderNode(child, parentDiv, 0)); return; } // Si c'est un capteur (a une valeur et un ID) if (node.value && node.id) { const isSelected = selectedSensors.has(node.id); const sensorConfig = selectedSensors.get(node.id) || {}; const item = document.createElement('div'); item.className = 'sensor-item'; item.style.marginLeft = `${level * 15}px`; item.innerHTML = `
${node.name}
${node.value} ${node.type}
`; parentDiv.appendChild(item); } // Si c'est un groupe (a des enfants) else if (node.children && node.children.length > 0) { const groupDiv = document.createElement('div'); groupDiv.className = 'sensor-group'; groupDiv.setAttribute('data-level', level); // Header du groupe (repliable) const header = document.createElement('div'); header.className = 'group-header'; header.style.marginLeft = `${level * 15}px`; // Compter les capteurs dans ce groupe et ses enfants function countSensors(n) { let count = 0; if (n.value && n.id) count = 1; if (n.children) { n.children.forEach(child => { count += countSensors(child); }); } return count; } const sensorCount = countSensors(node); // Icône selon le niveau let icon = '📦'; if (level === 1) { if (node.name.includes('CPU') || node.name.includes('Core')) icon = '🔧'; else if (node.name.includes('GPU') || node.name.includes('NVIDIA') || node.name.includes('AMD') || node.name.includes('Radeon') || node.name.includes('GeForce')) icon = '🎮'; else if (node.name.includes('Memory') || node.name.includes('RAM')) icon = '💾'; else if (node.name.includes('Storage') || node.name.includes('Disk')) icon = '💿'; } else if (level >= 2) { if (node.name.includes('Temperature')) icon = '🌡️'; else if (node.name.includes('Voltage')) icon = '⚡'; else if (node.name.includes('Clock') || node.name.includes('Frequency')) icon = '🔄'; else if (node.name.includes('Load') || node.name.includes('Usage')) icon = '📊'; else if (node.name.includes('Fan')) icon = '🌀'; else if (node.name.includes('Power')) icon = '🔋'; else icon = '📁'; } header.innerHTML = ` ▼ ${icon} ${node.name} ${sensorCount} capteur${sensorCount > 1 ? 's' : ''} `; // Container pour les enfants const childrenDiv = document.createElement('div'); childrenDiv.className = 'group-children'; // Rendre les enfants node.children.forEach(child => { renderNode(child, childrenDiv, level + 1); }); // Toggle pour replier/déplier header.addEventListener('click', () => { const isExpanded = childrenDiv.style.display !== 'none'; childrenDiv.style.display = isExpanded ? 'none' : 'block'; header.querySelector('.group-toggle').textContent = isExpanded ? '▶' : '▼'; }); groupDiv.appendChild(header); groupDiv.appendChild(childrenDiv); parentDiv.appendChild(groupDiv); } } // Rendre tous les nœuds racine allSensors.forEach(node => { renderNode(node, listDiv); }); // Event listeners pour les checkboxes document.querySelectorAll('.sensor-checkbox').forEach(checkbox => { checkbox.addEventListener('change', handleSensorToggle); }); document.querySelectorAll('.sensor-tab-select').forEach(select => { select.addEventListener('change', handleSensorTabChange); }); document.querySelectorAll('.sensor-viz-select').forEach(select => { select.addEventListener('change', handleSensorVizChange); }); // Utiliser la délégation d'événements pour les boutons de config // (car ils sont créés dynamiquement quand on déplie les groupes) const sensorsContainer = document.getElementById('sensors-list'); if (sensorsContainer) { sensorsContainer.addEventListener('click', (e) => { if (e.target.classList.contains('sensor-config-btn')) { openSensorConfig(e); } }); } } // Garder l'ancienne fonction pour la compatibilité avec findSensor function findSensor(sensorId) { function searchTree(node) { if (node.id === sensorId) { return { id: node.id, name: node.name, type: node.type, value: node.value }; } if (node.children) { for (const child of node.children) { const found = searchTree(child); if (found) return found; } } return null; } for (const rootNode of allSensors) { const found = searchTree(rootNode); if (found) return found; } return null; } function handleSensorToggle(e) { const sensorId = e.target.dataset.sensorId; const isChecked = e.target.checked; const sensorItem = e.target.closest('.sensor-item'); const tabSelect = sensorItem.querySelector('.sensor-tab-select'); const vizSelect = sensorItem.querySelector('.sensor-viz-select'); const configBtn = sensorItem.querySelector('.sensor-config-btn'); tabSelect.disabled = !isChecked; vizSelect.disabled = !isChecked; // Le bouton config est actif dès que le capteur est coché // (on peut configurer la taille même sans graphique) if (configBtn) { configBtn.disabled = !isChecked; } if (isChecked) { // Ajouter le capteur au premier onglet par défaut const sensor = findSensor(sensorId); // Vérifier que le capteur existe if (!sensor) { console.error('Capteur non trouvé:', sensorId); console.log('allSensors:', allSensors); e.target.checked = false; tabSelect.disabled = true; vizSelect.disabled = true; if (configBtn) configBtn.disabled = true; return; } const firstTab = config.tabs[0]; if (!firstTab.sensors.find(s => s.id === sensorId)) { firstTab.sensors.push({ id: sensorId, name: sensor.name, type: sensor.type, show_graph: false, viz_type: 'none' }); } tabSelect.value = firstTab.id; } else { // Retirer le capteur de tous les onglets config.tabs.forEach(tab => { tab.sensors = tab.sensors.filter(s => s.id !== sensorId); }); } } function handleSensorTabChange(e) { const sensorId = e.target.dataset.sensorId; const newTabId = e.target.value; // Retirer le capteur de tous les onglets config.tabs.forEach(tab => { tab.sensors = tab.sensors.filter(s => s.id !== sensorId); }); // Ajouter au nouvel onglet const sensor = findSensor(sensorId); const targetTab = config.tabs.find(t => t.id === newTabId); if (targetTab) { targetTab.sensors.push({ id: sensorId, name: sensor.name, type: sensor.type, show_graph: false }); } } function handleSensorVizChange(e) { const sensorId = e.target.dataset.sensorId; const vizType = e.target.value; // Trouver et mettre à jour config.tabs.forEach(tab => { const sensor = tab.sensors.find(s => s.id === sensorId); if (sensor) { if (vizType === 'none') { sensor.show_graph = false; sensor.viz_type = 'none'; } else { sensor.show_graph = true; sensor.viz_type = vizType; // 'line' ou 'gauge' } } }); // Le bouton config reste toujours actif si le capteur est coché // (on peut configurer la taille même sans graphique) } // === Configuration Modal === let currentConfigSensorId = null; function openSensorConfig(e) { const sensorId = e.target.dataset.sensorId; currentConfigSensorId = sensorId; // Trouver le capteur dans la config let sensorConfig = null; config.tabs.forEach(tab => { const sensor = tab.sensors.find(s => s.id === sensorId); if (sensor) sensorConfig = sensor; }); if (!sensorConfig) return; // Remplir la modal avec les valeurs actuelles document.getElementById('config-card-size').value = sensorConfig.card_size || 'medium'; document.getElementById('config-font-family').value = sensorConfig.font_family || 'system'; document.getElementById('config-font-bold').checked = sensorConfig.font_bold !== false; // true par défaut document.getElementById('config-font-size').value = sensorConfig.font_size || 'small'; document.getElementById('config-hide-value-mobile').checked = sensorConfig.hide_value_mobile || false; document.getElementById('config-show-type-badge').checked = sensorConfig.show_type_badge !== false; // true par défaut // Afficher les options selon le type de visualisation const gaugeOptions = document.getElementById('gauge-options-group'); const chartOptions = document.getElementById('chart-options-group'); if (sensorConfig.viz_type === 'gauge') { gaugeOptions.style.display = 'block'; chartOptions.style.display = 'none'; const gaugeOpts = sensorConfig.gauge_options || {}; document.getElementById('config-gauge-show-value').checked = gaugeOpts.show_value !== false; document.getElementById('config-gauge-size').value = gaugeOpts.size || 'medium'; document.getElementById('config-gauge-style').value = gaugeOpts.style || 'arc'; } else if (sensorConfig.viz_type === 'line') { gaugeOptions.style.display = 'none'; chartOptions.style.display = 'block'; const chartOpts = sensorConfig.chart_options || {}; document.getElementById('config-chart-height').value = chartOpts.height || 'medium'; } else { gaugeOptions.style.display = 'none'; chartOptions.style.display = 'none'; } // Afficher la modal document.getElementById('sensor-config-modal').style.display = 'flex'; } function closeSensorConfig() { document.getElementById('sensor-config-modal').style.display = 'none'; currentConfigSensorId = null; } function saveSensorConfig() { if (!currentConfigSensorId) return; // Trouver le capteur dans la config config.tabs.forEach(tab => { const sensor = tab.sensors.find(s => s.id === currentConfigSensorId); if (sensor) { // Sauvegarder la taille de la carte sensor.card_size = document.getElementById('config-card-size').value; sensor.font_family = document.getElementById('config-font-family').value; sensor.font_bold = document.getElementById('config-font-bold').checked; sensor.font_size = document.getElementById('config-font-size').value; sensor.hide_value_mobile = document.getElementById('config-hide-value-mobile').checked; sensor.show_type_badge = document.getElementById('config-show-type-badge').checked; // Sauvegarder les options selon le type if (sensor.viz_type === 'gauge') { sensor.gauge_options = { show_value: document.getElementById('config-gauge-show-value').checked, size: document.getElementById('config-gauge-size').value, style: document.getElementById('config-gauge-style').value }; } else if (sensor.viz_type === 'line') { sensor.chart_options = { height: document.getElementById('config-chart-height').value }; } } }); closeSensorConfig(); showNotification('Configuration mise à jour', 'success'); } // Event listeners pour la modal document.querySelector('.modal-close').addEventListener('click', closeSensorConfig); document.querySelector('.modal-cancel').addEventListener('click', closeSensorConfig); document.querySelector('.modal-save').addEventListener('click', saveSensorConfig); // Fermer la modal en cliquant en dehors document.getElementById('sensor-config-modal').addEventListener('click', (e) => { if (e.target.id === 'sensor-config-modal') { closeSensorConfig(); } }); // === Gestion des onglets === function renderTabs() { const tabsList = document.getElementById('tabs-list'); tabsList.innerHTML = ''; config.tabs.forEach((tab, index) => { const item = document.createElement('div'); item.className = 'tab-item'; item.innerHTML = ` ☰ ${tab.sensors.length} capteurs ${config.tabs.length > 1 ? `` : ''} `; // Event listener pour le nom item.querySelector('input').addEventListener('input', (e) => { const tabToUpdate = config.tabs.find(t => t.id === tab.id); if (tabToUpdate) { tabToUpdate.name = e.target.value; // Mettre à jour le sélecteur d'onglet des apps updateAppsTabSelector(); } }); tabsList.appendChild(item); }); // Mettre à jour le sélecteur d'onglet des apps updateAppsTabSelector(); } function addTab() { const newId = 'tab' + Date.now(); // Utiliser timestamp pour ID unique config.tabs.push({ id: newId, name: `Onglet ${config.tabs.length + 1}`, sensors: [] }); renderTabs(); renderSensors(); // Re-render pour ajouter le nouvel onglet aux selects } function removeTab(tabId) { if (config.tabs.length === 1) { showNotification('Impossible de supprimer le dernier onglet', 'error'); return; } // Si c'était l'onglet des apps, réinitialiser if (config.apps_tab === tabId) { config.apps_tab = ''; } config.tabs = config.tabs.filter(t => t.id !== tabId); renderTabs(); renderSensors(); } // === Gestion des applications === function renderApps() { const appsList = document.getElementById('apps-list'); appsList.innerHTML = ''; config.apps.forEach((app, index) => { const item = document.createElement('div'); item.className = 'app-item'; item.innerHTML = ` `; // Event listeners item.querySelectorAll('input').forEach(input => { input.addEventListener('input', (e) => { const idx = parseInt(e.target.dataset.appIndex); const field = e.target.dataset.field; config.apps[idx][field] = e.target.value; }); }); appsList.appendChild(item); }); } function addApp() { config.apps.push({ name: 'Nouvelle App', path: '', icon: '🚀' }); renderApps(); } function removeApp(index) { config.apps.splice(index, 1); renderApps(); } // === Sauvegarde === async function saveConfig() { try { const response = await fetch('/api/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const result = await response.json(); if (result.success) { showNotification('✅ Configuration sauvegardée !'); } else { showNotification('❌ Erreur de sauvegarde', 'error'); } } catch (error) { console.error('Erreur sauvegarde:', error); showNotification('❌ Erreur de sauvegarde', 'error'); } } // === Thème === function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme); } // === Notifications === function showNotification(message, type = 'success') { const notification = document.getElementById('notification'); notification.textContent = message; notification.className = `notification ${type}`; notification.classList.add('show'); setTimeout(() => { notification.classList.remove('show'); }, 3000); } // === Section Ordre des capteurs === let sortableInstances = []; function renderOrderSection() { const container = document.getElementById('order-tabs-container'); if (!container) return; container.innerHTML = ''; // Détruire les anciennes instances Sortable sortableInstances.forEach(instance => instance.destroy()); sortableInstances = []; // Créer une section pour chaque onglet config.tabs.forEach((tab, tabIndex) => { const section = document.createElement('div'); section.className = 'order-tab-section'; section.innerHTML = `

📑 ${tab.name} (${tab.sensors.length} capteurs)

${tab.sensors.length === 0 ? '
Aucun capteur dans cet onglet
' : tab.sensors.map((sensor, index) => `
${index + 1} ☰
${sensor.name || sensor.id}
${sensor.type || 'generic'} ${sensor.viz_type === 'gauge' ? '🎯 Jauge' : sensor.viz_type === 'line' ? '📈 Courbe' : '📊 Valeur'}
`).join('') }
`; container.appendChild(section); // Initialiser Sortable sur la liste si elle a des capteurs if (tab.sensors.length > 0) { const listEl = section.querySelector('.order-sensors-list'); const sortable = new Sortable(listEl, { animation: 150, handle: '.order-sensor-handle', ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', onEnd: function(evt) { handleSensorReorder(evt); } }); sortableInstances.push(sortable); } }); } function handleSensorReorder(evt) { const listEl = evt.from; const tabId = listEl.dataset.tabId; const tabIndex = parseInt(listEl.dataset.tabIndex); // Récupérer le nouvel ordre des IDs const newOrder = Array.from(listEl.querySelectorAll('.order-sensor-item')) .map(item => item.dataset.sensorId); // Réorganiser les capteurs dans la config const tab = config.tabs[tabIndex]; const reorderedSensors = []; newOrder.forEach(sensorId => { const sensor = tab.sensors.find(s => s.id === sensorId); if (sensor) { reorderedSensors.push(sensor); } }); // Mettre à jour la config config.tabs[tabIndex].sensors = reorderedSensors; // Mettre à jour les numéros affichés updateOrderNumbers(listEl); console.log(`Ordre mis à jour pour ${tab.name}:`, newOrder); showNotification('Ordre modifié (pensez à sauvegarder)', 'success'); } function updateOrderNumbers(listEl) { const items = listEl.querySelectorAll('.order-sensor-item'); items.forEach((item, index) => { const numberEl = item.querySelector('.order-sensor-number'); if (numberEl) { numberEl.textContent = index + 1; } item.dataset.index = index; }); } // ==================== Plexamp ==================== async function loadPlexampConfig() { try { const response = await fetch('/api/plexamp/config'); const data = await response.json(); document.getElementById('plexamp-enabled').checked = data.enabled; document.getElementById('plexamp-host').value = data.host || '192.168.1.235'; document.getElementById('plexamp-port').value = data.port || 32400; // Le token n'est pas renvoyé pour des raisons de sécurité if (data.has_token) { document.getElementById('plexamp-token').placeholder = '••••••••••••••••'; } // Setup event listeners Plexamp setupPlexampListeners(); } catch (error) { console.error('Erreur chargement config Plexamp:', error); } } function setupPlexampListeners() { const testBtn = document.getElementById('plexamp-test-btn'); const saveBtn = document.getElementById('plexamp-save-btn'); if (testBtn) { testBtn.addEventListener('click', testPlexampConnection); } if (saveBtn) { saveBtn.addEventListener('click', savePlexampConfig); } } async function testPlexampConnection() { const statusEl = document.getElementById('plexamp-status'); statusEl.textContent = '⏳ Test en cours...'; statusEl.className = ''; const config = { host: document.getElementById('plexamp-host').value, port: parseInt(document.getElementById('plexamp-port').value), token: document.getElementById('plexamp-token').value }; // Si le token est vide, on ne peut pas tester if (!config.token) { statusEl.textContent = '⚠️ Entrez un token'; statusEl.className = 'status-warning'; return; } try { const response = await fetch('/api/plexamp/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const result = await response.json(); if (result.success) { statusEl.textContent = '✅ ' + result.message; statusEl.className = 'status-success'; } else { statusEl.textContent = '❌ ' + result.message; statusEl.className = 'status-error'; } } catch (error) { statusEl.textContent = '❌ Erreur: ' + error.message; statusEl.className = 'status-error'; } } async function savePlexampConfig() { const statusEl = document.getElementById('plexamp-status'); const config = { enabled: document.getElementById('plexamp-enabled').checked, host: document.getElementById('plexamp-host').value, port: parseInt(document.getElementById('plexamp-port').value), token: document.getElementById('plexamp-token').value }; try { const response = await fetch('/api/plexamp/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const result = await response.json(); if (result.success) { statusEl.textContent = '✅ Sauvegardé'; statusEl.className = 'status-success'; showNotification('Configuration Plexamp sauvegardée', 'success'); } else { statusEl.textContent = '❌ ' + result.error; statusEl.className = 'status-error'; } } catch (error) { statusEl.textContent = '❌ Erreur: ' + error.message; statusEl.className = 'status-error'; } }