diff --git a/CHANGELOG.md b/CHANGELOG.md index a691227..d60c8ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## 2.6.8 - 2026-08-08 + +### Fixed + +- The screen-group command buttons stay disabled until a real target group is selected, so the placeholder "Select Screen Group" state cannot send commands. + ## 2.6.7 - 2026-08-08 ### Fixed diff --git a/package.json b/package.json index a02e80f..896db7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse-signage", - "version": "2.6.7", + "version": "2.6.8", "private": false, "description": "Pulse Signage application with MySQL and media storage", "repository": { diff --git a/src/web/public/js/dashboard/dashboard-page.js b/src/web/public/js/dashboard/dashboard-page.js index 46a65fa..d8e2387 100644 --- a/src/web/public/js/dashboard/dashboard-page.js +++ b/src/web/public/js/dashboard/dashboard-page.js @@ -114,6 +114,7 @@ } var selectedSlug = String(select.value || '').trim(); + var hasSelectedGroup = Boolean(selectedSlug); var actionTarget = '/clients/' + encodeURIComponent(selectedSlug || '__all__') + '/commands'; var selectedName = getSelectedScreenLabel(); var selectedClients = getSelectedScreenClients(latestDashboardState); @@ -134,6 +135,10 @@ var action = String(form.getAttribute('data-screen-command-action') || '').trim(); var button = form.querySelector('button[type="submit"]'); + if (button) { + button.disabled = !hasSelectedGroup; + } + if (action === 'pause' || action === 'blackout') { updateToggleButton(button, form, latestDashboardState); return; diff --git a/src/web/views/signage/clients/list.hbs b/src/web/views/signage/clients/list.hbs index e88f1c2..8549e8e 100644 --- a/src/web/views/signage/clients/list.hbs +++ b/src/web/views/signage/clients/list.hbs @@ -42,17 +42,17 @@
- +
- +
- +
diff --git a/test/web-dashboard-page.test.js b/test/web-dashboard-page.test.js index c2b6fc6..50b17b3 100644 --- a/test/web-dashboard-page.test.js +++ b/test/web-dashboard-page.test.js @@ -342,3 +342,181 @@ test('dashboard move client button opens the move modal for the selected row', ( assert.equal(clientNameInput.value, 'Lobby Client'); assert.equal(playerBaseUrlInput.value, 'http://player.local'); }); + +test('dashboard screen group controls stay disabled until a group is selected', () => { + const script = fs.readFileSync(require.resolve('../src/web/public/js/dashboard/dashboard-page.js'), 'utf8'); + const pauseInput = { value: 'true' }; + const blackoutInput = { value: 'true' }; + const reloadButton = { + disabled: false, + innerHTML: '', + setAttribute() {}, + classList: { add() {}, remove() {} } + }; + const pauseButton = { + disabled: false, + innerHTML: '', + setAttribute() {}, + classList: { add() {}, remove() {} } + }; + const blackoutButton = { + disabled: false, + innerHTML: '', + setAttribute() {}, + classList: { add() {}, remove() {} } + }; + const forms = [ + { + getAttribute(name) { + if (name === 'data-screen-command-action') { + return 'reload'; + } + return ''; + }, + setAttribute() {}, + querySelector(selector) { + if (selector === 'button[type="submit"]') { + return reloadButton; + } + return null; + } + }, + { + getAttribute(name) { + if (name === 'data-screen-command-action') { + return 'pause'; + } + return ''; + }, + setAttribute() {}, + querySelector(selector) { + if (selector === 'button[type="submit"]') { + return pauseButton; + } + if (selector === 'input[name="paused"]') { + return pauseInput; + } + return null; + } + }, + { + getAttribute(name) { + if (name === 'data-screen-command-action') { + return 'blackout'; + } + return ''; + }, + setAttribute() {}, + querySelector(selector) { + if (selector === 'button[type="submit"]') { + return blackoutButton; + } + if (selector === 'input[name="blackout"]') { + return blackoutInput; + } + return null; + } + } + ]; + const select = { + value: '', + options: [ + { textContent: 'Select Screen Group', getAttribute() { return ''; } }, + { textContent: 'Lobby', getAttribute(name) { return name === 'data-screen-name' ? 'Lobby' : ''; } }, + { textContent: 'All Screens', getAttribute(name) { return name === 'data-screen-name' ? 'All screens' : ''; } } + ], + selectedIndex: 0, + dataset: {}, + addEventListener(type, handler) { + if (type === 'change') { + this.changeHandler = handler; + } + } + }; + const context = { + document: { + body: { + classList: { + toggle() {}, + add() {}, + remove() {} + } + }, + getElementById(id) { + if (id === 'screen-command-select') { + return select; + } + if (id === 'dashboard-clients-table') { + return { getAttribute() { return 'false'; } }; + } + if (id === 'dashboard-clients-table-body') { + return null; + } + return null; + }, + querySelector(selector) { + if (selector === '[data-screen-command-select]') { + return select; + } + return null; + }, + querySelectorAll(selector) { + if (selector === '[data-screen-command-form]') { + return forms; + } + return []; + }, + addEventListener() {} + }, + window: { + location: { + search: '', + protocol: 'http:', + host: 'example.test' + }, + webUiHelpers: { + escapeHtml(value) { return String(value); }, + formatDashboardDate(value) { return String(value); }, + getClientRowKey(client) { return String(client && client.id || ''); }, + getClientDisplayName(client) { return String(client && (client.client_name || client.name || client.clientId) || ''); }, + setButtonVariant() {}, + normalizeDisplayIp(value) { return String(value); } + }, + WebSocket: null, + setTimeout() { return 1; }, + clearTimeout() {}, + alert() {}, + prompt() { + return null; + }, + webHandleDashboardState() {} + }, + WebSocket: function MockWebSocket() {}, + JSON: JSON, + Number: Number, + String: String, + Boolean: Boolean, + Array: Array, + Object: Object, + Math: Math, + Set: Set, + URLSearchParams: URLSearchParams, + FormData: function FormData() {} + }; + context.window.document = context.document; + context.window.WebSocket = context.WebSocket; + + vm.runInNewContext(script, context); + + assert.equal(reloadButton.disabled, true); + assert.equal(pauseButton.disabled, true); + assert.equal(blackoutButton.disabled, true); + + select.value = 'lobby'; + select.selectedIndex = 1; + select.changeHandler(); + + assert.equal(reloadButton.disabled, false); + assert.equal(pauseButton.disabled, false); + assert.equal(blackoutButton.disabled, false); +});