diff --git a/server/src/wled_controller/core/profiles/profile_engine.py b/server/src/wled_controller/core/profiles/profile_engine.py
index a1048e7..7ef18f6 100644
--- a/server/src/wled_controller/core/profiles/profile_engine.py
+++ b/server/src/wled_controller/core/profiles/profile_engine.py
@@ -80,8 +80,8 @@ class ProfileEngine:
for profile in profiles:
should_be_active = (
profile.enabled
- and len(profile.conditions) > 0
- and self._evaluate_conditions(profile, running_procs, topmost_proc)
+ and (len(profile.conditions) == 0
+ or self._evaluate_conditions(profile, running_procs, topmost_proc))
)
is_active = profile.id in self._active_profiles
diff --git a/server/src/wled_controller/static/index.html b/server/src/wled_controller/static/index.html
index 6c7166a..2b6284d 100644
--- a/server/src/wled_controller/static/index.html
+++ b/server/src/wled_controller/static/index.html
@@ -1131,13 +1131,16 @@
const apiKey = localStorage.getItem('wled_api_key');
const loginBtn = document.getElementById('login-btn');
const logoutBtn = document.getElementById('logout-btn');
+ const tabBar = document.querySelector('.tab-bar');
if (apiKey) {
loginBtn.style.display = 'none';
logoutBtn.style.display = 'inline-block';
+ if (tabBar) tabBar.style.display = '';
} else {
loginBtn.style.display = 'inline-block';
logoutBtn.style.display = 'none';
+ if (tabBar) tabBar.style.display = 'none';
}
}
@@ -1157,8 +1160,18 @@
updateAuthUI();
showToast(t('auth.logout.success'), 'info');
- // Clear the UI
- document.getElementById('targets-panel-content').innerHTML = `
${t('auth.please_login')}
`;
+ // Stop background activity
+ if (window.stopDashboardWS) window.stopDashboardWS();
+ if (window.stopPerfPolling) window.stopPerfPolling();
+ if (window.stopUptimeTimer) window.stopUptimeTimer();
+ if (window.disconnectAllKCWebSockets) window.disconnectAllKCWebSockets();
+
+ // Clear all tab panels
+ const loginMsg = `${t('auth.please_login')}
`;
+ document.getElementById('dashboard-content').innerHTML = loginMsg;
+ document.getElementById('profiles-content').innerHTML = loginMsg;
+ document.getElementById('targets-panel-content').innerHTML = loginMsg;
+ document.getElementById('streams-list').innerHTML = loginMsg;
}
// Initialize on load
@@ -1184,12 +1197,10 @@
const error = document.getElementById('api-key-error');
const cancelBtn = document.getElementById('modal-cancel-btn');
- if (message) {
- description.textContent = message;
- }
+ description.textContent = message || t('auth.message');
input.value = '';
- input.placeholder = 'Enter your API key...';
+ input.placeholder = t('auth.placeholder');
error.style.display = 'none';
modal.style.display = 'flex';
lockBody();
@@ -1199,6 +1210,9 @@
const closeXBtn = document.getElementById('modal-close-x-btn');
if (closeXBtn) closeXBtn.style.display = hideCancel ? 'none' : '';
+ // Hide login button while modal is open
+ document.getElementById('login-btn').style.display = 'none';
+
setTimeout(() => input.focus(), 100);
}
@@ -1206,6 +1220,7 @@
const modal = document.getElementById('api-key-modal');
modal.style.display = 'none';
unlockBody();
+ updateAuthUI();
}
function submitApiKey(event) {
diff --git a/server/src/wled_controller/static/js/app.js b/server/src/wled_controller/static/js/app.js
index b6d5710..8049496 100644
--- a/server/src/wled_controller/static/js/app.js
+++ b/server/src/wled_controller/static/js/app.js
@@ -307,12 +307,12 @@ window.addEventListener('beforeunload', () => {
// ─── Initialization ───
document.addEventListener('DOMContentLoaded', async () => {
- // Initialize locale first
- await initLocale();
-
- // Load API key from localStorage
+ // Load API key from localStorage before anything that triggers API calls
setApiKey(localStorage.getItem('wled_api_key'));
+ // Initialize locale (dispatches languageChanged which may trigger API calls)
+ await initLocale();
+
// Restore active tab before showing content to avoid visible jump
initTabs();
@@ -326,7 +326,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (!apiKey) {
setTimeout(() => {
if (typeof window.showApiKeyModal === 'function') {
- window.showApiKeyModal('Welcome! Please login with your API key to get started.', true);
+ window.showApiKeyModal(null, true);
}
}, 100);
return;
diff --git a/server/src/wled_controller/static/js/core/api.js b/server/src/wled_controller/static/js/core/api.js
index b755c4c..98b8467 100644
--- a/server/src/wled_controller/static/js/core/api.js
+++ b/server/src/wled_controller/static/js/core/api.js
@@ -48,18 +48,19 @@ export function handle401Error() {
window.updateAuthUI();
}
+ const expiredMsg = typeof window.t === 'function'
+ ? window.t('auth.session_expired')
+ : 'Your session has expired. Please login again.';
+
if (typeof window.showApiKeyModal === 'function') {
- window.showApiKeyModal('Your session has expired or the API key is invalid. Please login again.', true);
+ window.showApiKeyModal(expiredMsg, true);
} else {
- // showToast imported at call sites to avoid circular dep
- import('./state.js').then(() => {
- const toast = document.getElementById('toast');
- if (toast) {
- toast.textContent = 'Authentication failed. Please reload the page and login.';
- toast.className = 'toast error show';
- setTimeout(() => { toast.className = 'toast'; }, 3000);
- }
- });
+ const toast = document.getElementById('toast');
+ if (toast) {
+ toast.textContent = expiredMsg;
+ toast.className = 'toast error show';
+ setTimeout(() => { toast.className = 'toast'; }, 3000);
+ }
}
}
diff --git a/server/src/wled_controller/static/js/core/i18n.js b/server/src/wled_controller/static/js/core/i18n.js
index b8a4e46..f7e1d4d 100644
--- a/server/src/wled_controller/static/js/core/i18n.js
+++ b/server/src/wled_controller/static/js/core/i18n.js
@@ -2,10 +2,9 @@
* Internationalization — translations, locale detection, text updates.
*/
-import { apiKey } from './state.js';
-
let currentLocale = 'en';
let translations = {};
+let _initialized = false;
const supportedLocales = {
'en': 'English',
@@ -100,14 +99,9 @@ export function updateAllText() {
el.title = t(key);
});
- // Re-render dynamic content with new translations
- if (apiKey) {
- import('../core/api.js').then(({ loadDisplays }) => loadDisplays());
- if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
- if (typeof window.loadPictureSources === 'function') window.loadPictureSources();
- // Force perf section rebuild with new locale
- const perfEl = document.querySelector('.dashboard-perf-persistent');
- if (perfEl) perfEl.remove();
- if (typeof window.loadDashboard === 'function') window.loadDashboard();
+ // Notify subscribers that the language changed (skip initial load)
+ if (_initialized) {
+ document.dispatchEvent(new CustomEvent('languageChanged'));
}
+ _initialized = true;
}
diff --git a/server/src/wled_controller/static/js/features/dashboard.js b/server/src/wled_controller/static/js/features/dashboard.js
index c84e775..09b619f 100644
--- a/server/src/wled_controller/static/js/features/dashboard.js
+++ b/server/src/wled_controller/static/js/features/dashboard.js
@@ -593,6 +593,15 @@ export function stopUptimeTimer() {
_stopUptimeTimer();
}
+// Re-render dashboard when language changes
+document.addEventListener('languageChanged', () => {
+ if (!apiKey) return;
+ // Force perf section rebuild with new locale
+ const perfEl = document.querySelector('.dashboard-perf-persistent');
+ if (perfEl) perfEl.remove();
+ loadDashboard();
+});
+
export function stopDashboardWS() {
if (_dashboardWS) {
_dashboardWS.close();
diff --git a/server/src/wled_controller/static/js/features/profiles.js b/server/src/wled_controller/static/js/features/profiles.js
index b6d92ad..9b917cb 100644
--- a/server/src/wled_controller/static/js/features/profiles.js
+++ b/server/src/wled_controller/static/js/features/profiles.js
@@ -2,14 +2,17 @@
* Profiles — profile cards, editor, condition builder, process picker.
*/
-import { _profilesCache, set_profilesCache } from '../core/state.js';
+import { apiKey, _profilesCache, set_profilesCache } from '../core/state.js';
import { API_BASE, getHeaders, escapeHtml, handle401Error } from '../core/api.js';
-import { t, updateAllText } from '../core/i18n.js';
+import { t } from '../core/i18n.js';
import { showToast, showConfirm } from '../core/ui.js';
import { Modal } from '../core/modal.js';
const profileModal = new Modal('profile-editor-modal');
+// Re-render profiles when language changes
+document.addEventListener('languageChanged', () => { if (apiKey) loadProfiles(); });
+
export async function loadProfiles() {
const container = document.getElementById('profiles-content');
if (!container) return;
@@ -39,7 +42,11 @@ function renderProfiles(profiles) {
html += '';
container.innerHTML = html;
- updateAllText();
+ // Localize data-i18n elements within the profiles container only
+ // (calling global updateAllText() would trigger loadProfiles() again → infinite loop)
+ container.querySelectorAll('[data-i18n]').forEach(el => {
+ el.textContent = t(el.getAttribute('data-i18n'));
+ });
}
function createProfileCard(profile) {
@@ -141,7 +148,9 @@ export async function openProfileEditor(profileId) {
}
profileModal.open();
- updateAllText();
+ modal.querySelectorAll('[data-i18n]').forEach(el => {
+ el.textContent = t(el.getAttribute('data-i18n'));
+ });
}
export function closeProfileEditorModal() {
diff --git a/server/src/wled_controller/static/js/features/streams.js b/server/src/wled_controller/static/js/features/streams.js
index ed15797..09b3308 100644
--- a/server/src/wled_controller/static/js/features/streams.js
+++ b/server/src/wled_controller/static/js/features/streams.js
@@ -18,6 +18,7 @@ import {
_currentTestStreamId, set_currentTestStreamId,
_currentTestPPTemplateId, set_currentTestPPTemplateId,
_lastValidatedImageSource, set_lastValidatedImageSource,
+ apiKey,
} from '../core/state.js';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
import { t } from '../core/i18n.js';
@@ -25,6 +26,9 @@ import { Modal } from '../core/modal.js';
import { showToast, showConfirm, openLightbox, openFullImageLightbox, showOverlaySpinner, hideOverlaySpinner } from '../core/ui.js';
import { openDisplayPicker, formatDisplayLabel } from './displays.js';
+// Re-render picture sources when language changes
+document.addEventListener('languageChanged', () => { if (apiKey) loadPictureSources(); });
+
// ===== Modal instances =====
const templateModal = new Modal('template-modal');
diff --git a/server/src/wled_controller/static/js/features/tabs.js b/server/src/wled_controller/static/js/features/tabs.js
index 88fa1f0..055d4b9 100644
--- a/server/src/wled_controller/static/js/features/tabs.js
+++ b/server/src/wled_controller/static/js/features/tabs.js
@@ -10,12 +10,13 @@ export function switchTab(name) {
localStorage.setItem('activeTab', name);
if (name === 'dashboard') {
// Use window.* to avoid circular imports with feature modules
- if (typeof window.loadDashboard === 'function') window.loadDashboard();
- if (typeof window.startDashboardWS === 'function') window.startDashboardWS();
+ if (apiKey && typeof window.loadDashboard === 'function') window.loadDashboard();
+ if (apiKey && typeof window.startDashboardWS === 'function') window.startDashboardWS();
} else {
if (typeof window.stopDashboardWS === 'function') window.stopDashboardWS();
if (typeof window.stopPerfPolling === 'function') window.stopPerfPolling();
if (typeof window.stopUptimeTimer === 'function') window.stopUptimeTimer();
+ if (!apiKey) return;
if (name === 'streams') {
if (typeof window.loadPictureSources === 'function') window.loadPictureSources();
} else if (name === 'targets') {
diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js
index ef09e4f..bc80b88 100644
--- a/server/src/wled_controller/static/js/features/targets.js
+++ b/server/src/wled_controller/static/js/features/targets.js
@@ -3,6 +3,7 @@
*/
import {
+ apiKey,
_targetEditorDevices, set_targetEditorDevices,
_deviceBrightnessCache,
kcWebSockets,
@@ -17,6 +18,9 @@ import { createKCTargetCard, connectKCWebSocket, disconnectKCWebSocket } from '.
// createPatternTemplateCard is imported via window.* to avoid circular deps
// (pattern-templates.js calls window.loadTargetsTab)
+// Re-render targets tab when language changes
+document.addEventListener('languageChanged', () => { if (apiKey) loadTargetsTab(); });
+
class TargetEditorModal extends Modal {
constructor() {
super('target-editor-modal');
diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json
index 587bb68..7715df6 100644
--- a/server/src/wled_controller/static/locales/en.json
+++ b/server/src/wled_controller/static/locales/en.json
@@ -19,6 +19,7 @@
"auth.logout.confirm": "Are you sure you want to logout?",
"auth.logout.success": "Logged out successfully",
"auth.please_login": "Please login to view",
+ "auth.session_expired": "Your session has expired or the API key is invalid. Please login again.",
"displays.title": "Available Displays",
"displays.layout": "\uD83D\uDDA5\uFE0F Displays",
"displays.information": "Display Information",
diff --git a/server/src/wled_controller/static/locales/ru.json b/server/src/wled_controller/static/locales/ru.json
index 2eb37fb..d0468ea 100644
--- a/server/src/wled_controller/static/locales/ru.json
+++ b/server/src/wled_controller/static/locales/ru.json
@@ -19,6 +19,7 @@
"auth.logout.confirm": "Вы уверены, что хотите выйти?",
"auth.logout.success": "Выход выполнен успешно",
"auth.please_login": "Пожалуйста, войдите для просмотра",
+ "auth.session_expired": "Ваша сессия истекла или API ключ недействителен. Пожалуйста, войдите снова.",
"displays.title": "Доступные Дисплеи",
"displays.layout": "\uD83D\uDDA5\uFE0F Дисплеи",
"displays.information": "Информация о Дисплеях",
diff --git a/server/src/wled_controller/static/style.css b/server/src/wled_controller/static/style.css
index 5141cde..092d459 100644
--- a/server/src/wled_controller/static/style.css
+++ b/server/src/wled_controller/static/style.css
@@ -66,6 +66,8 @@ header {
align-items: center;
padding: 20px 0 10px;
margin-bottom: 10px;
+ position: relative;
+ z-index: 2100;
}
.header-title {