diff --git a/client/src/Admin.jsx b/client/src/Admin.jsx index 43a6629..96e7c86 100644 --- a/client/src/Admin.jsx +++ b/client/src/Admin.jsx @@ -19,6 +19,10 @@ function Admin() { const [selectedOutputDevice, setSelectedOutputDevice] = useState(null); const [selectedSampleRate, setSelectedSampleRate] = useState(48000); + // Channel names (Phase 2.5) + const [channelNames, setChannelNames] = useState({ inputs: {}, outputs: {} }); + const [editingChannelNames, setEditingChannelNames] = useState(false); + // Gestion formulaire nouveau groupe const [showGroupForm, setShowGroupForm] = useState(false); const [editingGroup, setEditingGroup] = useState(null); @@ -85,16 +89,19 @@ function Admin() { }; const loadAudioDevices = async () => { - const [devicesRes, currentDeviceRes] = await Promise.all([ + const [devicesRes, currentDeviceRes, channelNamesRes] = await Promise.all([ fetch(`${API_URL}/admin/audio/devices`), - fetch(`${API_URL}/admin/audio/device`) + fetch(`${API_URL}/admin/audio/device`), + fetch(`${API_URL}/admin/audio/channels/names`) ]); const devicesData = await devicesRes.json(); const currentData = await currentDeviceRes.json(); + const channelNamesData = await channelNamesRes.json(); setAudioDevices(devicesData.devices || []); setCurrentDevice(currentData.device || {}); + setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} }); // Initialiser les sélections avec les valeurs actuelles setSelectedInputDevice(currentData.device?.inputDeviceId ?? null); @@ -221,6 +228,38 @@ function Admin() { // ========== Gestion audio devices (Phase 2.5) ========== + const handleSaveChannelNames = async () => { + try { + const res = await fetch(`${API_URL}/admin/audio/channels/names`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(channelNames) + }); + + if (res.ok) { + alert('Noms de canaux sauvegardés avec succès!'); + setEditingChannelNames(false); + await loadAudioDevices(); + } else { + const error = await res.json(); + alert(`Erreur: ${error.error}`); + } + } catch (err) { + console.error('Erreur sauvegarde noms canaux:', err); + alert('Erreur lors de la sauvegarde'); + } + }; + + const updateChannelName = (type, channelId, name) => { + setChannelNames(prev => ({ + ...prev, + [type]: { + ...prev[type], + [channelId]: name + } + })); + }; + const handleSaveAudioDevice = async () => { try { const res = await fetch(`${API_URL}/admin/audio/device`, { @@ -529,6 +568,80 @@ function Admin() { +
+
+

Nommage des canaux physiques

+ {!editingChannelNames ? ( + + ) : ( +
+ + +
+ )} +
+ +
+
+

Entrées (Inputs)

+
+ {Array.from({length: 8}, (_, i) => ( +
+ {i} + updateChannelName('inputs', i, e.target.value)} + placeholder={`Input ${i}`} + disabled={!editingChannelNames} + style={{ + padding: 'var(--spacing-sm)', + background: editingChannelNames ? 'var(--color-bg)' : 'var(--color-surface-hover)', + border: '1px solid var(--color-border)', + borderRadius: '6px', + color: 'var(--color-text)', + fontSize: '0.9rem' + }} + /> +
+ ))} +
+
+ +
+

Sorties (Outputs)

+
+ {Array.from({length: 8}, (_, i) => ( +
+ {i} + updateChannelName('outputs', i, e.target.value)} + placeholder={`Output ${i}`} + disabled={!editingChannelNames} + style={{ + padding: 'var(--spacing-sm)', + background: editingChannelNames ? 'var(--color-bg)' : 'var(--color-surface-hover)', + border: '1px solid var(--color-border)', + borderRadius: '6px', + color: 'var(--color-text)', + fontSize: '0.9rem' + }} + /> +
+ ))} +
+
+
+
+ {currentDevice && Object.keys(currentDevice).length > 0 && (

Configuration actuelle

diff --git a/server/api/admin.js b/server/api/admin.js index 5132520..64c559e 100644 --- a/server/api/admin.js +++ b/server/api/admin.js @@ -516,6 +516,68 @@ router.get('/audio/device', (req, res) => { } }); +/** + * GET /admin/audio/channels/names + * Récupère les noms personnalisés des canaux physiques + */ +router.get('/audio/channels/names', (req, res) => { + try { + const config = configManager.get(); + const channelNames = config.audio?.channelNames || { inputs: {}, outputs: {} }; + + res.json({ + channelNames + }); + } catch (error) { + console.error('Erreur GET /admin/audio/channels/names:', error); + res.status(500).json({ error: 'Failed to load channel names' }); + } +}); + +/** + * PUT /admin/audio/channels/names + * Sauvegarde les noms personnalisés des canaux physiques + * Body: { inputs: { "0": "Micro Principal", ... }, outputs: { "0": "Retour Scène", ... } } + */ +router.put('/audio/channels/names', (req, res) => { + try { + const { inputs, outputs } = req.body; + + if (!inputs && !outputs) { + return res.status(400).json({ + error: 'Missing required fields: inputs or outputs' + }); + } + + const config = configManager.get(); + + if (!config.audio.channelNames) { + config.audio.channelNames = { inputs: {}, outputs: {} }; + } + + if (inputs) { + config.audio.channelNames.inputs = inputs; + } + + if (outputs) { + config.audio.channelNames.outputs = outputs; + } + + configManager.save(config); + + addLog('info', 'Channel names updated', { inputCount: Object.keys(inputs || {}).length, outputCount: Object.keys(outputs || {}).length }); + + res.json({ + message: 'Channel names updated', + channelNames: config.audio.channelNames + }); + + } catch (error) { + console.error('Erreur PUT /admin/audio/channels/names:', error); + res.status(500).json({ error: 'Failed to update channel names' }); + } +}); + /** * POST /admin/audio/device * Sélectionne et configure une carte son