Codebase review: stability, performance, usability, and i18n fixes
Stability: - Fix race condition: set _is_running before create_task in target processors - Await probe task after cancel in wled_target_processor - Replace raw fetch() with fetchWithAuth() across devices, kc-targets, pattern-templates - Add try/catch to showTestTemplateModal in streams.js - Wrap blocking I/O in asyncio.to_thread (picture_targets, system restore) - Fix dashboardStopAll to filter only running targets with ok guard Performance: - Vectorize fire effect spark loop with numpy in effect_stream - Vectorize FFT band binning with cumulative sum in analysis.py - Rewrite pixel_processor with vectorized numpy (accept ndarray or list) - Add httpx.AsyncClient connection pooling with lock in wled_provider - Optimize _send_pixels_http to avoid np.hstack allocation in wled_client - Mutate chart arrays in-place in dashboard, perf-charts, targets - Merge dashboard 2-batch fetch into single Promise.all - Hoist frame_time outside loop in mapped_stream Usability: - Fix health check interval load/save in device settings - Swap confirm modal button classes (No=secondary, Yes=danger) - Add aria-modal to audio/value source editors, fix close button aria-labels - Add modal footer close button to settings modal - Add dedicated calibration LED count validation error keys i18n: - Replace ~50 hardcoded English strings with t() calls across 12 JS files - Add 50 new keys to en.json, ru.json, zh.json - Localize inline toasts in index.html with window.t fallback - Add data-i18n to command palette footer - Add localization policy to CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -126,7 +126,7 @@ export async function turnOffDevice(deviceId) {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.isAuth) return;
|
||||
showToast('Failed to turn off device', 'error');
|
||||
showToast(t('device.error.power_off_failed'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,23 +143,23 @@ export async function removeDevice(deviceId) {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (response.ok) {
|
||||
showToast('Device removed', 'success');
|
||||
showToast(t('device.removed'), 'success');
|
||||
window.loadDevices();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
showToast(`Failed to remove: ${error.detail}`, 'error');
|
||||
showToast(t('device.error.remove_failed'), 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.isAuth) return;
|
||||
console.error('Failed to remove device:', error);
|
||||
showToast('Failed to remove device', 'error');
|
||||
showToast(t('device.error.remove_failed'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
export async function showSettings(deviceId) {
|
||||
try {
|
||||
const deviceResponse = await fetchWithAuth(`/devices/${deviceId}`);
|
||||
if (!deviceResponse.ok) { showToast('Failed to load device settings', 'error'); return; }
|
||||
if (!deviceResponse.ok) { showToast(t('device.error.settings_load_failed'), 'error'); return; }
|
||||
|
||||
const device = await deviceResponse.json();
|
||||
const isAdalight = isSerialDevice(device.device_type);
|
||||
@@ -171,7 +171,7 @@ export async function showSettings(deviceId) {
|
||||
|
||||
document.getElementById('settings-device-id').value = device.id;
|
||||
document.getElementById('settings-device-name').value = device.name;
|
||||
document.getElementById('settings-health-interval').value = 30;
|
||||
document.getElementById('settings-health-interval').value = device.state_check_interval ?? 30;
|
||||
|
||||
const isMock = isMockDevice(device.device_type);
|
||||
const urlGroup = document.getElementById('settings-url-group');
|
||||
@@ -242,7 +242,7 @@ export async function showSettings(deviceId) {
|
||||
} catch (error) {
|
||||
if (error.isAuth) return;
|
||||
console.error('Failed to load device settings:', error);
|
||||
showToast('Failed to load device settings', 'error');
|
||||
showToast(t('device.error.settings_load_failed'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,12 +256,16 @@ export async function saveDeviceSettings() {
|
||||
const url = settingsModal._getUrl();
|
||||
|
||||
if (!name || !url) {
|
||||
settingsModal.showError('Please fill in all fields correctly');
|
||||
settingsModal.showError(t('device.error.required'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const body = { name, url, auto_shutdown: document.getElementById('settings-auto-shutdown').checked };
|
||||
const body = {
|
||||
name, url,
|
||||
auto_shutdown: document.getElementById('settings-auto-shutdown').checked,
|
||||
state_check_interval: parseInt(document.getElementById('settings-health-interval').value, 10) || 30,
|
||||
};
|
||||
const ledCountInput = document.getElementById('settings-led-count');
|
||||
if (settingsModal.capabilities.includes('manual_led_count') && ledCountInput.value) {
|
||||
body.led_count = parseInt(ledCountInput.value, 10);
|
||||
@@ -283,7 +287,7 @@ export async function saveDeviceSettings() {
|
||||
|
||||
if (!deviceResponse.ok) {
|
||||
const errorData = await deviceResponse.json();
|
||||
settingsModal.showError(`Failed to update device: ${errorData.detail}`);
|
||||
settingsModal.showError(t('device.error.update'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -293,7 +297,7 @@ export async function saveDeviceSettings() {
|
||||
} catch (err) {
|
||||
if (err.isAuth) return;
|
||||
console.error('Failed to save device settings:', err);
|
||||
settingsModal.showError('Failed to save settings');
|
||||
settingsModal.showError(t('device.error.save'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,14 +311,16 @@ export async function saveCardBrightness(deviceId, value) {
|
||||
const bri = parseInt(value);
|
||||
updateDeviceBrightness(deviceId, bri);
|
||||
try {
|
||||
await fetch(`${API_BASE}/devices/${deviceId}/brightness`, {
|
||||
const resp = await fetchWithAuth(`/devices/${deviceId}/brightness`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify({ brightness: bri })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
showToast(t('device.error.brightness'), 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to update brightness:', err);
|
||||
showToast('Failed to update brightness', 'error');
|
||||
if (err.isAuth) return;
|
||||
showToast(t('device.error.brightness'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,9 +329,7 @@ export async function fetchDeviceBrightness(deviceId) {
|
||||
if (_brightnessFetchInFlight.has(deviceId)) return;
|
||||
_brightnessFetchInFlight.add(deviceId);
|
||||
try {
|
||||
const resp = await fetch(`${API_BASE}/devices/${deviceId}/brightness`, {
|
||||
headers: getHeaders()
|
||||
});
|
||||
const resp = await fetchWithAuth(`/devices/${deviceId}/brightness`);
|
||||
if (!resp.ok) return;
|
||||
const data = await resp.json();
|
||||
updateDeviceBrightness(deviceId, data.brightness);
|
||||
@@ -398,9 +402,7 @@ async function _populateSettingsSerialPorts(currentUrl) {
|
||||
|
||||
try {
|
||||
const discoverType = settingsModal.deviceType || 'adalight';
|
||||
const resp = await fetch(`${API_BASE}/devices/discover?timeout=2&device_type=${encodeURIComponent(discoverType)}`, {
|
||||
headers: getHeaders()
|
||||
});
|
||||
const resp = await fetchWithAuth(`/devices/discover?timeout=2&device_type=${encodeURIComponent(discoverType)}`);
|
||||
if (!resp.ok) return;
|
||||
const data = await resp.json();
|
||||
const devices = data.devices || [];
|
||||
|
||||
Reference in New Issue
Block a user