diff --git a/package-lock.json b/package-lock.json index c15ab9d..fbe2bd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pulse-signage", - "version": "1.2.0", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pulse-signage", - "version": "1.2.0", + "version": "1.3.3", "dependencies": { "bootstrap-icons": "1.11.3", "dotenv": "^17.4.2", diff --git a/package.json b/package.json index 53c2824..1f53ccf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse-signage", - "version": "1.3.2", + "version": "1.3.3", "private": false, "description": "Pulse Signage application with MySQL and media uploads", "repository": { diff --git a/src/web/public/css/theme-custom.css b/src/web/public/css/theme-custom.css index b695e6c..f5a097a 100644 --- a/src/web/public/css/theme-custom.css +++ b/src/web/public/css/theme-custom.css @@ -123,10 +123,37 @@ --bs-navbar-nav-link-padding-x: 10px; } +.app-sidebar { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.sidebar-wrapper { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + overflow-y: auto; +} + +.min-h-0 { + min-height: 0; +} + .sidebar-brand .brand-text.fw-light { margin: 0; } +.sidebar-version { + font-size: 0.8rem; + letter-spacing: 0.04em; +} + +.sidebar-version__inner { + opacity: 0.8; +} + .card { box-shadow: none; } diff --git a/src/web/public/js/dashboard/dashboard-page.js b/src/web/public/js/dashboard/dashboard-page.js index 5028ae1..1bfebca 100644 --- a/src/web/public/js/dashboard/dashboard-page.js +++ b/src/web/public/js/dashboard/dashboard-page.js @@ -82,14 +82,16 @@ function renderClientActionCell(client) { var paused = Boolean(client.paused); var pauseButtonClass = 'btn btn-sm btn-info'; + var pauseButtonIcon = paused ? 'bi-play-fill' : 'bi-pause-fill'; var pauseButtonLabel = paused ? 'Resume' : 'Pause'; var blackout = Boolean(client.blackout); var blackoutButtonClass = 'btn btn-sm ' + (blackout ? 'btn-success' : 'btn-secondary'); + var blackoutButtonIcon = blackout ? 'bi-eye' : 'bi-eye-slash'; var blackoutButtonLabel = blackout ? 'Restore' : 'Blackout'; var reloadConfirmMessage = 'Reloading will restart the player page. Continue?'; var blackoutCommandValue = blackout ? 'false' : 'true'; - return '
'; + return '
'; } function updateClientActionCell(cell, client) { @@ -105,7 +107,7 @@ var paused = Boolean(client.paused); setButtonVariant(pauseButton, ['btn-secondary', 'btn-outline-primary'], 'btn-info'); - pauseButton.innerHTML = '' + (paused ? 'Resume' : 'Pause'); + pauseButton.innerHTML = '' + (paused ? 'Resume' : 'Pause'); var pauseForm = pauseButton.form; if (pauseForm) { @@ -142,8 +144,9 @@ } var blackout = Boolean(client.blackout); + var blackoutButtonIcon = blackout ? 'bi-eye' : 'bi-eye-slash'; setButtonVariant(blackoutButton, ['btn-success', 'btn-secondary'], blackout ? 'btn-success' : 'btn-secondary'); - blackoutButton.innerHTML = '' + (blackout ? 'Restore' : 'Blackout'); + blackoutButton.innerHTML = '' + (blackout ? 'Restore' : 'Blackout'); var blackoutForm = blackoutButton.form; if (blackoutForm) { @@ -357,8 +360,9 @@ var label = allBlackout ? 'Restore all clients' : 'Blackout all clients'; var blackoutForm = blackoutButton.form; var blackoutInput = blackoutForm ? blackoutForm.querySelector('input[name="blackout"]') : null; + var blackoutButtonIcon = allBlackout ? 'bi-eye' : 'bi-eye-slash'; - blackoutButton.innerHTML = (allBlackout ? '' : '') + escapeHtml(label); + blackoutButton.innerHTML = '' + escapeHtml(label); setButtonVariant(blackoutButton, ['btn-success', 'btn-danger', 'btn-secondary', 'btn-outline-secondary', 'btn-outline-dark'], 'btn-secondary'); if (blackoutInput) { blackoutInput.value = allBlackout ? 'false' : 'true'; diff --git a/src/web/routes/admin-manage.js b/src/web/routes/admin-manage.js index 7b1591b..2117ce7 100644 --- a/src/web/routes/admin-manage.js +++ b/src/web/routes/admin-manage.js @@ -20,6 +20,57 @@ module.exports = function registerAdminManageRoutes(app, deps) { const forwardPlayerCommand = deps.forwardPlayerCommand; const PLAYER_PUBLIC_BASE_URL = deps.playerPublicBaseUrl; + async function fetchAllScreenSlugs() { + const [rows] = await pool.query('SELECT slug FROM screens ORDER BY slug ASC'); + return (rows || []) + .map(function (row) { + return String(row && row.slug ? row.slug : '').trim(); + }) + .filter(Boolean); + } + + app.post('/admin/commands', async function (req, res, next) { + try { + const command = String((req.body && req.body.command) || req.query.command || '').trim().toLowerCase(); + const blackoutValue = req.body && Object.prototype.hasOwnProperty.call(req.body, 'blackout') + ? req.body.blackout + : req.query.blackout; + + if (!command) { + return res.status(400).json({ error: 'Command is required' }); + } + + if (command !== 'reload' && command !== 'blackout') { + return res.status(400).json({ error: 'Unsupported command' }); + } + + const slugs = await fetchAllScreenSlugs(); + if (!slugs.length) { + await broadcastDashboardState(); + return res.json({ ok: true, command: command, sent: 0 }); + } + + const payload = command === 'blackout' + ? { + command: 'blackout', + blackout: blackoutValue === true || blackoutValue === 'true' || blackoutValue === '1' ? true : false + } + : 'reload'; + + const sentCount = await notifyPlayerScreens(slugs, payload); + await broadcastDashboardState(); + + return res.json({ + ok: true, + command: command, + sent: sentCount, + blackout: command === 'blackout' ? Boolean(payload.blackout) : undefined + }); + } catch (error) { + next(error); + } + }); + app.post('/admin/playlists', async function (req, res, next) { try { const name = String(req.body.name || '').trim(); diff --git a/src/web/views/clients/list.hbs b/src/web/views/clients/list.hbs index 5f11111..4663ff1 100644 --- a/src/web/views/clients/list.hbs +++ b/src/web/views/clients/list.hbs @@ -67,12 +67,16 @@
- +
- +
diff --git a/src/web/views/dashboard/index.hbs b/src/web/views/dashboard/index.hbs index 17e1d62..16dfc84 100644 --- a/src/web/views/dashboard/index.hbs +++ b/src/web/views/dashboard/index.hbs @@ -68,7 +68,7 @@ type="submit" class="btn btn-secondary dashboard-action-button" id="dashboard-blackout-all-button" - >Blackout all clients + >Blackout all clients Blank active players immediately. diff --git a/src/web/views/layout.hbs b/src/web/views/layout.hbs index c608a40..1553190 100644 --- a/src/web/views/layout.hbs +++ b/src/web/views/layout.hbs @@ -6,6 +6,8 @@ {{title}} - Pulse + + @@ -126,14 +128,14 @@ -