feat: ajout interface admin pour configuration carte son (Phase 2.5)

- Nouvel onglet Audio dans l'interface admin
- Sélection carte son d'entrée/sortie via dropdowns
- Configuration sample rate (44.1/48/96kHz)
- Affichage liste toutes cartes disponibles
- Affichage configuration actuelle
- Sauvegarde vers API backend
This commit is contained in:
2026-05-25 09:42:10 +02:00
parent 03b3f94824
commit 5583808279
+165
View File
@@ -12,6 +12,13 @@ function Admin() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Audio devices (Phase 2.5)
const [audioDevices, setAudioDevices] = useState([]);
const [currentDevice, setCurrentDevice] = useState(null);
const [selectedInputDevice, setSelectedInputDevice] = useState(null);
const [selectedOutputDevice, setSelectedOutputDevice] = useState(null);
const [selectedSampleRate, setSelectedSampleRate] = useState(48000);
// Gestion formulaire nouveau groupe
const [showGroupForm, setShowGroupForm] = useState(false);
const [editingGroup, setEditingGroup] = useState(null);
@@ -40,6 +47,8 @@ function Admin() {
await loadStats();
} else if (activeTab === 'logs') {
await loadLogs();
} else if (activeTab === 'audio') {
await loadAudioDevices();
}
setError(null);
@@ -75,6 +84,24 @@ function Admin() {
setLogs(data.logs || []);
};
const loadAudioDevices = async () => {
const [devicesRes, currentDeviceRes] = await Promise.all([
fetch(`${API_URL}/admin/audio/devices`),
fetch(`${API_URL}/admin/audio/device`)
]);
const devicesData = await devicesRes.json();
const currentData = await currentDeviceRes.json();
setAudioDevices(devicesData.devices || []);
setCurrentDevice(currentData.device || {});
// Initialiser les sélections avec les valeurs actuelles
setSelectedInputDevice(currentData.device?.inputDeviceId ?? null);
setSelectedOutputDevice(currentData.device?.outputDeviceId ?? null);
setSelectedSampleRate(currentData.device?.sampleRate || 48000);
};
// ========== Gestion groupes ==========
const handleCreateGroup = async (e) => {
@@ -192,6 +219,33 @@ function Admin() {
setGroupForm({ ...groupForm, channels: newChannels });
};
// ========== Gestion audio devices (Phase 2.5) ==========
const handleSaveAudioDevice = async () => {
try {
const res = await fetch(`${API_URL}/admin/audio/device`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
inputDeviceId: selectedInputDevice !== null ? parseInt(selectedInputDevice) : undefined,
outputDeviceId: selectedOutputDevice !== null ? parseInt(selectedOutputDevice) : undefined,
sampleRate: parseInt(selectedSampleRate)
})
});
if (res.ok) {
alert('Configuration audio sauvegardée avec succès!');
await loadAudioDevices();
} else {
const error = await res.json();
alert(`Erreur: ${error.error}`);
}
} catch (err) {
console.error('Erreur sauvegarde configuration audio:', err);
alert('Erreur lors de la sauvegarde');
}
};
// ========== Gestion utilisateurs ==========
const handleDisconnectUser = async (identity) => {
@@ -243,6 +297,12 @@ function Admin() {
>
Groupes
</button>
<button
className={activeTab === 'audio' ? 'active' : ''}
onClick={() => setActiveTab('audio')}
>
Audio
</button>
<button
className={activeTab === 'users' ? 'active' : ''}
onClick={() => setActiveTab('users')}
@@ -396,6 +456,111 @@ function Admin() {
</div>
)}
{/* TAB: Audio (Phase 2.5) */}
{activeTab === 'audio' && (
<div className="tab-audio">
<div className="tab-header">
<h2>Configuration audio</h2>
</div>
<div className="audio-config-container">
<div className="audio-section">
<h3>Carte son d'entrée (Input)</h3>
<select
value={selectedInputDevice ?? ''}
onChange={(e) => setSelectedInputDevice(e.target.value === '' ? null : parseInt(e.target.value))}
className="device-select"
>
<option value="">-- Sélectionner une carte --</option>
{audioDevices
.filter(d => d.maxInputChannels > 0)
.map(device => (
<option key={device.id} value={device.id}>
{device.name} ({device.maxInputChannels} canaux, {device.defaultSampleRate}Hz)
</option>
))}
</select>
</div>
<div className="audio-section">
<h3>Carte son de sortie (Output)</h3>
<select
value={selectedOutputDevice ?? ''}
onChange={(e) => setSelectedOutputDevice(e.target.value === '' ? null : parseInt(e.target.value))}
className="device-select"
>
<option value="">-- Sélectionner une carte --</option>
{audioDevices
.filter(d => d.maxOutputChannels > 0)
.map(device => (
<option key={device.id} value={device.id}>
{device.name} ({device.maxOutputChannels} canaux, {device.defaultSampleRate}Hz)
</option>
))}
</select>
</div>
<div className="audio-section">
<h3>Sample Rate</h3>
<select
value={selectedSampleRate}
onChange={(e) => setSelectedSampleRate(parseInt(e.target.value))}
className="device-select"
>
<option value={44100}>44100 Hz (CD quality)</option>
<option value={48000}>48000 Hz (Recommended)</option>
<option value={96000}>96000 Hz (High quality)</option>
</select>
</div>
<div className="audio-actions">
<button onClick={handleSaveAudioDevice} className="btn-primary">
Sauvegarder la configuration
</button>
</div>
{currentDevice && Object.keys(currentDevice).length > 0 && (
<div className="current-config">
<h3>Configuration actuelle</h3>
<div className="config-info">
<p><strong>Input Device ID:</strong> {currentDevice.inputDeviceId ?? 'Non configuré'}</p>
<p><strong>Output Device ID:</strong> {currentDevice.outputDeviceId ?? 'Non configuré'}</p>
<p><strong>Sample Rate:</strong> {currentDevice.sampleRate ?? 48000} Hz</p>
</div>
</div>
)}
<div className="audio-devices-list">
<h3>Toutes les cartes son disponibles</h3>
<table className="devices-table">
<thead>
<tr>
<th>ID</th>
<th>Nom</th>
<th>Entrées</th>
<th>Sorties</th>
<th>Sample Rate</th>
<th>API</th>
</tr>
</thead>
<tbody>
{audioDevices.map(device => (
<tr key={device.id}>
<td>{device.id}</td>
<td>{device.name}</td>
<td>{device.maxInputChannels}</td>
<td>{device.maxOutputChannels}</td>
<td>{device.defaultSampleRate} Hz</td>
<td>{device.hostAPIName}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
{/* TAB: Utilisateurs */}
{activeTab === 'users' && (
<div className="tab-users">