Bump version to 1.4.5
Publish Docker Image / build-and-push (push) Successful in 27s

This commit is contained in:
2026-07-21 21:53:55 +01:00
parent 5e7df5e55c
commit 370ec81c33
7 changed files with 25 additions and 23 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pulse-signage",
"version": "1.4.2",
"version": "1.4.5",
"private": false,
"description": "Pulse Signage application with MySQL and media uploads",
"repository": {
+5 -2
View File
@@ -202,6 +202,9 @@ function getLegacyPermissionTargets(permissionKey) {
const sectionKey = parts[0];
const actionKey = parts[1];
if (normalizedKey === 'screens.allow') {
return ['clients.allow'];
}
if (actionKey === 'view') {
return [`${sectionKey}.read`];
}
@@ -265,7 +268,7 @@ async function backfillLegacyRbacSchema(pool) {
for (const row of rolePermissionRows || []) {
const currentKey = getPermissionKey(row);
const targetKeys = getLegacyPermissionTargets(currentKey);
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage')) {
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage') || currentKey === 'screens.allow') {
for (const targetKey of targetKeys) {
addRoleTarget(Number(row.role_id), targetKey);
}
@@ -276,7 +279,7 @@ async function backfillLegacyRbacSchema(pool) {
for (const row of permissionRows || []) {
const currentKey = getPermissionKey(row);
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage')) {
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage') || currentKey === 'screens.allow') {
legacyPermissionRowIds.push(Number(row.id));
}
}
+1 -2
View File
@@ -27,8 +27,7 @@ const PERMISSION_SECTIONS = [
actions: [
{ key: 'read', name: 'Read', description: 'View the screen list and open screen details.' },
{ key: 'create', name: 'Create', description: 'Create new screens.' },
{ key: 'edit', name: 'Update', description: 'Edit screens and send screen commands.' },
{ key: 'allow', name: 'Allow', description: 'Use the connected client actions on the screen page.' },
{ key: 'edit', name: 'Update', description: 'Edit screens.' },
{ key: 'delete', name: 'Delete', description: 'Delete screens.' }
]
},
+1 -1
View File
@@ -12,7 +12,7 @@ const registerAdminPagesRoutes = require('./web/routes/admin-pages');
const registerAdminAccountRoutes = require('./web/routes/admin-account');
const registerAdminUsersRoutes = require('./web/routes/admin-users');
const registerAdminManageRoutes = require('./web/routes/admin-manage');
const registerAdminScreenCommandRoutes = require('./web/routes/admin-screen-commands');
const registerAdminScreenCommandRoutes = require('./web/routes/admin-client-commands');
const registerAdminContentRoutes = require('./web/routes/admin-content');
const { createWebBootstrap } = require('./web/bootstrap');
const { requirePermission } = require('./rbac');
@@ -91,7 +91,7 @@
var reloadConfirmMessage = 'Reloading will restart the player page. Continue?';
var blackoutCommandValue = blackout ? 'false' : 'true';
return '<div class="d-flex flex-wrap gap-1"><form method="post" action="/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-confirm-message="' + escapeHtml(reloadConfirmMessage) + '" data-async-command><input type="hidden" name="command" value="reload" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-danger" data-action="reload"><i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>Reload</button></form><form method="post" action="/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="previous" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-outline-secondary" data-action="previous" aria-label="Previous slide"><i class="bi bi-skip-backward-fill" aria-hidden="true"></i></button></form><form method="post" action="/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="next" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-outline-secondary" data-action="next" aria-label="Next slide"><i class="bi bi-skip-forward-fill" aria-hidden="true"></i></button></form><form method="post" action="/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="pause" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="' + pauseButtonClass + '" data-action="pause"><i class="bi bi-' + pauseButtonIcon + ' me-1" aria-hidden="true"></i>' + pauseButtonLabel + '</button></form><form method="post" action="/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="blackout" /><input type="hidden" name="blackout" value="' + blackoutCommandValue + '" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="' + blackoutButtonClass + '" data-action="blackout"><i class="bi bi-' + blackoutButtonIcon + ' me-1" aria-hidden="true"></i>' + blackoutButtonLabel + '</button></form></div>';
return '<div class="d-flex flex-wrap gap-1"><form method="post" action="/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-confirm-message="' + escapeHtml(reloadConfirmMessage) + '" data-async-command><input type="hidden" name="command" value="reload" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-danger" data-action="reload"><i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>Reload</button></form><form method="post" action="/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="previous" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-outline-secondary" data-action="previous" aria-label="Previous slide"><i class="bi bi-skip-backward-fill" aria-hidden="true"></i></button></form><form method="post" action="/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="next" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-outline-secondary" data-action="next" aria-label="Next slide"><i class="bi bi-skip-forward-fill" aria-hidden="true"></i></button></form><form method="post" action="/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="pause" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="' + pauseButtonClass + '" data-action="pause"><i class="bi bi-' + pauseButtonIcon + ' me-1" aria-hidden="true"></i>' + pauseButtonLabel + '</button></form><form method="post" action="/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="blackout" /><input type="hidden" name="blackout" value="' + blackoutCommandValue + '" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="' + blackoutButtonClass + '" data-action="blackout"><i class="bi bi-' + blackoutButtonIcon + ' me-1" aria-hidden="true"></i>' + blackoutButtonLabel + '</button></form></div>';
}
function updateClientActionCell(cell, client) {
@@ -119,7 +119,7 @@
if (connectionInput) {
connectionInput.value = client.id || '';
}
pauseForm.action = '/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands';
pauseForm.action = '/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
var reloadButton = cell.querySelector('button[data-action="reload"]');
@@ -132,7 +132,7 @@
if (reloadInput) {
reloadInput.value = client.id || '';
}
reloadForm.action = '/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands';
reloadForm.action = '/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
reloadForm.setAttribute('data-confirm-message', 'Reloading will restart the player page. Continue?');
}
}
@@ -162,7 +162,7 @@
if (blackoutConnectionInput) {
blackoutConnectionInput.value = client.id || '';
}
blackoutForm.action = '/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands';
blackoutForm.action = '/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
var previousButton = cell.querySelector('button[data-action="previous"]');
@@ -182,7 +182,7 @@
if (previousConnectionInput) {
previousConnectionInput.value = client.id || '';
}
previousForm.action = '/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands';
previousForm.action = '/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
var nextButton = cell.querySelector('button[data-action="next"]');
@@ -202,7 +202,7 @@
if (nextConnectionInput) {
nextConnectionInput.value = client.id || '';
}
nextForm.action = '/admin/screens/' + encodeURIComponent(client.screen_slug) + '/commands';
nextForm.action = '/admin/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
}
@@ -416,7 +416,7 @@
body.append('deviceId', String(deviceId || '').trim());
body.append('clientName', String(clientName || '').trim());
return fetch('/admin/screens/' + encodeURIComponent(String(screenSlug || '').trim()) + '/commands', {
return fetch('/admin/clients/' + encodeURIComponent(String(screenSlug || '').trim()) + '/commands', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
@@ -6,7 +6,7 @@ module.exports = function registerAdminScreenCommandRoutes(app, deps) {
const withClientNameReservation = deps.withClientNameReservation;
const requirePermission = deps.requirePermission;
app.post('/admin/screens/:slug/commands', requirePermission('screens.allow'), async function (req, res, next) {
app.post('/admin/clients/:slug/commands', requirePermission('clients.allow'), async function (req, res, next) {
try {
const slug = String(req.params.slug || '').trim();
const command = String((req.body && req.body.command) || req.query.command || '').trim().toLowerCase();
+9 -9
View File
@@ -10,7 +10,7 @@
<h3 class="card-title">Active connections</h3>
</div>
<div class="card-body table-responsive p-0">
<table id="dashboard-clients-table" class="table table-striped w-100 mb-0" data-has-actions-column="{{#if (hasPermission currentUser 'screens.allow')}}true{{else}}false{{/if}}">
<table id="dashboard-clients-table" class="table table-striped w-100 mb-0" data-has-actions-column="{{#if (hasPermission currentUser 'clients.allow')}}true{{else}}false{{/if}}">
<thead>
<tr>
<th>Client</th>
@@ -19,7 +19,7 @@
<th>IP</th>
<th>Viewport</th>
<th>Connected/Updated</th>
{{#if (hasPermission currentUser 'screens.allow')}}<th>Actions</th>{{/if}}
{{#if (hasPermission currentUser 'clients.allow')}}<th>Actions</th>{{/if}}
</tr>
</thead>
<tbody id="dashboard-clients-table-body">
@@ -57,32 +57,32 @@
<span class="empty">Unknown</span>
{{/if}}
</td>
{{#if (hasPermission currentUser 'screens.allow')}}
{{#if (hasPermission currentUser 'clients.allow')}}
<td data-label="Actions" class="text-end">
<div class="actions justify-content-end">
<form method="post" action="/admin/screens/{{screen_slug}}/commands" class="inline-form" data-confirm-message="Reloading will restart the player page. Continue?" data-async-command>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-confirm-message="Reloading will restart the player page. Continue?" data-async-command>
<input type="hidden" name="command" value="reload" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-danger">Reload</button>
</form>
<form method="post" action="/admin/screens/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="previous" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-warning" aria-label="Previous slide">◀</button>
</form>
<form method="post" action="/admin/screens/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="next" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-warning" aria-label="Next slide">▶</button>
</form>
<form method="post" action="/admin/screens/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="pause" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-info">
<i class="bi bi-pause-fill me-1" aria-hidden="false"></i>Pause
</button>
</form>
<form method="post" action="/admin/screens/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="blackout" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm {{#if blackout}}btn-success{{else}}btn-secondary{{/if}}">
@@ -95,7 +95,7 @@
</tr>
{{/each}}
{{else}}
<tr><td colspan="{{#if (hasPermission currentUser 'screens.allow')}}7{{else}}6{{/if}}" class="empty">No connected clients yet.</td></tr>
<tr><td colspan="{{#if (hasPermission currentUser 'clients.allow')}}7{{else}}6{{/if}}" class="empty">No connected clients yet.</td></tr>
{{/if}}
</tbody>
</table>