feat: ajout nommage canaux physiques (Phase 2.5)
- API GET /admin/audio/channels/names - API PUT /admin/audio/channels/names - Interface admin : nommage 8 inputs/outputs - Mode édition avec sauvegarde/annulation - Stockage dans config.yaml (section audio.channelNames) - Formulaire organisé en 2 colonnes (inputs/outputs)
This commit is contained in:
+115
-2
@@ -19,6 +19,10 @@ function Admin() {
|
|||||||
const [selectedOutputDevice, setSelectedOutputDevice] = useState(null);
|
const [selectedOutputDevice, setSelectedOutputDevice] = useState(null);
|
||||||
const [selectedSampleRate, setSelectedSampleRate] = useState(48000);
|
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
|
// Gestion formulaire nouveau groupe
|
||||||
const [showGroupForm, setShowGroupForm] = useState(false);
|
const [showGroupForm, setShowGroupForm] = useState(false);
|
||||||
const [editingGroup, setEditingGroup] = useState(null);
|
const [editingGroup, setEditingGroup] = useState(null);
|
||||||
@@ -85,16 +89,19 @@ function Admin() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadAudioDevices = async () => {
|
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/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 devicesData = await devicesRes.json();
|
||||||
const currentData = await currentDeviceRes.json();
|
const currentData = await currentDeviceRes.json();
|
||||||
|
const channelNamesData = await channelNamesRes.json();
|
||||||
|
|
||||||
setAudioDevices(devicesData.devices || []);
|
setAudioDevices(devicesData.devices || []);
|
||||||
setCurrentDevice(currentData.device || {});
|
setCurrentDevice(currentData.device || {});
|
||||||
|
setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} });
|
||||||
|
|
||||||
// Initialiser les sélections avec les valeurs actuelles
|
// Initialiser les sélections avec les valeurs actuelles
|
||||||
setSelectedInputDevice(currentData.device?.inputDeviceId ?? null);
|
setSelectedInputDevice(currentData.device?.inputDeviceId ?? null);
|
||||||
@@ -221,6 +228,38 @@ function Admin() {
|
|||||||
|
|
||||||
// ========== Gestion audio devices (Phase 2.5) ==========
|
// ========== 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 () => {
|
const handleSaveAudioDevice = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/admin/audio/device`, {
|
const res = await fetch(`${API_URL}/admin/audio/device`, {
|
||||||
@@ -529,6 +568,80 @@ function Admin() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="audio-section">
|
||||||
|
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--spacing-md)'}}>
|
||||||
|
<h3>Nommage des canaux physiques</h3>
|
||||||
|
{!editingChannelNames ? (
|
||||||
|
<button onClick={() => setEditingChannelNames(true)} className="btn-secondary">
|
||||||
|
Modifier les noms
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div style={{display: 'flex', gap: 'var(--spacing-sm)'}}>
|
||||||
|
<button onClick={handleSaveChannelNames} className="btn-primary">
|
||||||
|
Sauvegarder
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { setEditingChannelNames(false); loadAudioDevices(); }} className="btn-secondary">
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--spacing-xl)'}}>
|
||||||
|
<div>
|
||||||
|
<h4 style={{marginBottom: 'var(--spacing-md)', color: 'var(--color-text-secondary)'}}>Entrées (Inputs)</h4>
|
||||||
|
<div style={{display: 'grid', gap: 'var(--spacing-sm)'}}>
|
||||||
|
{Array.from({length: 8}, (_, i) => (
|
||||||
|
<div key={`input-${i}`} style={{display: 'grid', gridTemplateColumns: '40px 1fr', gap: 'var(--spacing-sm)', alignItems: 'center'}}>
|
||||||
|
<span style={{color: 'var(--color-text-secondary)', fontSize: '0.85rem'}}>{i}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={channelNames.inputs?.[i] || ''}
|
||||||
|
onChange={(e) => 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'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 style={{marginBottom: 'var(--spacing-md)', color: 'var(--color-text-secondary)'}}>Sorties (Outputs)</h4>
|
||||||
|
<div style={{display: 'grid', gap: 'var(--spacing-sm)'}}>
|
||||||
|
{Array.from({length: 8}, (_, i) => (
|
||||||
|
<div key={`output-${i}`} style={{display: 'grid', gridTemplateColumns: '40px 1fr', gap: 'var(--spacing-sm)', alignItems: 'center'}}>
|
||||||
|
<span style={{color: 'var(--color-text-secondary)', fontSize: '0.85rem'}}>{i}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={channelNames.outputs?.[i] || ''}
|
||||||
|
onChange={(e) => 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'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{currentDevice && Object.keys(currentDevice).length > 0 && (
|
{currentDevice && Object.keys(currentDevice).length > 0 && (
|
||||||
<div className="current-config">
|
<div className="current-config">
|
||||||
<h3>Configuration actuelle</h3>
|
<h3>Configuration actuelle</h3>
|
||||||
|
|||||||
@@ -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
|
* POST /admin/audio/device
|
||||||
* Sélectionne et configure une carte son
|
* Sélectionne et configure une carte son
|
||||||
|
|||||||
Reference in New Issue
Block a user