Bump version to 1.4.6
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "1.4.5",
|
"version": "1.4.6",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage application with MySQL and media uploads",
|
"description": "Pulse Signage application with MySQL and media uploads",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -205,11 +205,17 @@ function getLegacyPermissionTargets(permissionKey) {
|
|||||||
if (normalizedKey === 'screens.allow') {
|
if (normalizedKey === 'screens.allow') {
|
||||||
return ['clients.allow'];
|
return ['clients.allow'];
|
||||||
}
|
}
|
||||||
|
if (actionKey === 'edit') {
|
||||||
|
return [`${sectionKey}.update`];
|
||||||
|
}
|
||||||
|
if (actionKey === 'update') {
|
||||||
|
return [`${sectionKey}.update`];
|
||||||
|
}
|
||||||
if (actionKey === 'view') {
|
if (actionKey === 'view') {
|
||||||
return [`${sectionKey}.read`];
|
return [`${sectionKey}.read`];
|
||||||
}
|
}
|
||||||
if (actionKey === 'manage') {
|
if (actionKey === 'manage') {
|
||||||
return [`${sectionKey}.read`, `${sectionKey}.create`, `${sectionKey}.edit`, `${sectionKey}.delete`];
|
return [`${sectionKey}.read`, `${sectionKey}.create`, `${sectionKey}.update`, `${sectionKey}.delete`];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [normalizedKey].filter(Boolean);
|
return [normalizedKey].filter(Boolean);
|
||||||
@@ -248,6 +254,14 @@ async function backfillLegacyRbacSchema(pool) {
|
|||||||
JOIN permissions p ON p.id = rp.permission_id`
|
JOIN permissions p ON p.id = rp.permission_id`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const permissionIdByKey = new Map();
|
||||||
|
for (const row of permissionRows || []) {
|
||||||
|
const currentKey = getPermissionKey(row);
|
||||||
|
if (currentKey) {
|
||||||
|
permissionIdByKey.set(currentKey, Number(row.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const rolePermissionTargets = new Map();
|
const rolePermissionTargets = new Map();
|
||||||
const desiredPermissionKeys = new Set(PERMISSIONS.map(function (permission) {
|
const desiredPermissionKeys = new Set(PERMISSIONS.map(function (permission) {
|
||||||
return permission.key;
|
return permission.key;
|
||||||
@@ -268,7 +282,7 @@ async function backfillLegacyRbacSchema(pool) {
|
|||||||
for (const row of rolePermissionRows || []) {
|
for (const row of rolePermissionRows || []) {
|
||||||
const currentKey = getPermissionKey(row);
|
const currentKey = getPermissionKey(row);
|
||||||
const targetKeys = getLegacyPermissionTargets(currentKey);
|
const targetKeys = getLegacyPermissionTargets(currentKey);
|
||||||
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage') || currentKey === 'screens.allow') {
|
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage') || currentKey.endsWith('.edit') || currentKey === 'screens.allow') {
|
||||||
for (const targetKey of targetKeys) {
|
for (const targetKey of targetKeys) {
|
||||||
addRoleTarget(Number(row.role_id), targetKey);
|
addRoleTarget(Number(row.role_id), targetKey);
|
||||||
}
|
}
|
||||||
@@ -279,8 +293,21 @@ async function backfillLegacyRbacSchema(pool) {
|
|||||||
|
|
||||||
for (const row of permissionRows || []) {
|
for (const row of permissionRows || []) {
|
||||||
const currentKey = getPermissionKey(row);
|
const currentKey = getPermissionKey(row);
|
||||||
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage') || currentKey === 'screens.allow') {
|
const permissionId = Number(row.id);
|
||||||
legacyPermissionRowIds.push(Number(row.id));
|
if (currentKey.endsWith('.edit')) {
|
||||||
|
const targetKey = currentKey.replace(/\.edit$/, '.update');
|
||||||
|
const targetPermissionId = permissionIdByKey.get(targetKey);
|
||||||
|
if (targetPermissionId) {
|
||||||
|
legacyPermissionRowIds.push(permissionId);
|
||||||
|
} else if (targetKey) {
|
||||||
|
await pool.query('UPDATE permissions SET permission_key = ? WHERE id = ?', [targetKey, permissionId]);
|
||||||
|
permissionIdByKey.delete(currentKey);
|
||||||
|
permissionIdByKey.set(targetKey, permissionId);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (currentKey.endsWith('.view') || currentKey.endsWith('.manage') || currentKey.endsWith('.edit') || currentKey === 'screens.allow') {
|
||||||
|
legacyPermissionRowIds.push(permissionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,11 +380,11 @@ async function backfillLegacyRbacSchema(pool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [currentPermissionRows] = await pool.query('SELECT id, permission_key FROM permissions');
|
const [currentPermissionRows] = await pool.query('SELECT id, permission_key FROM permissions');
|
||||||
const permissionIdByKey = new Map();
|
const permissionIdByKeyAfterBackfill = new Map();
|
||||||
for (const row of currentPermissionRows || []) {
|
for (const row of currentPermissionRows || []) {
|
||||||
const currentKey = getPermissionKey(row);
|
const currentKey = getPermissionKey(row);
|
||||||
if (currentKey) {
|
if (currentKey) {
|
||||||
permissionIdByKey.set(currentKey, Number(row.id));
|
permissionIdByKeyAfterBackfill.set(currentKey, Number(row.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +392,7 @@ async function backfillLegacyRbacSchema(pool) {
|
|||||||
for (const [roleId, permissionKeys] of rolePermissionTargets.entries()) {
|
for (const [roleId, permissionKeys] of rolePermissionTargets.entries()) {
|
||||||
const expandedPermissionKeys = normalizePermissionKeys(Array.from(permissionKeys.values()));
|
const expandedPermissionKeys = normalizePermissionKeys(Array.from(permissionKeys.values()));
|
||||||
for (const permissionKey of expandedPermissionKeys) {
|
for (const permissionKey of expandedPermissionKeys) {
|
||||||
const permissionId = permissionIdByKey.get(permissionKey);
|
const permissionId = permissionIdByKeyAfterBackfill.get(permissionKey);
|
||||||
if (!permissionId) {
|
if (!permissionId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-12
@@ -27,7 +27,7 @@ const PERMISSION_SECTIONS = [
|
|||||||
actions: [
|
actions: [
|
||||||
{ key: 'read', name: 'Read', description: 'View the screen list and open screen details.' },
|
{ key: 'read', name: 'Read', description: 'View the screen list and open screen details.' },
|
||||||
{ key: 'create', name: 'Create', description: 'Create new screens.' },
|
{ key: 'create', name: 'Create', description: 'Create new screens.' },
|
||||||
{ key: 'edit', name: 'Update', description: 'Edit screens.' },
|
{ key: 'update', name: 'Update', description: 'Update screens.' },
|
||||||
{ key: 'delete', name: 'Delete', description: 'Delete screens.' }
|
{ key: 'delete', name: 'Delete', description: 'Delete screens.' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -39,7 +39,7 @@ const PERMISSION_SECTIONS = [
|
|||||||
actions: [
|
actions: [
|
||||||
{ key: 'read', name: 'Read', description: 'View playlists and playlist contents.' },
|
{ key: 'read', name: 'Read', description: 'View playlists and playlist contents.' },
|
||||||
{ key: 'create', name: 'Create', description: 'Create new playlists.' },
|
{ key: 'create', name: 'Create', description: 'Create new playlists.' },
|
||||||
{ key: 'edit', name: 'Update', description: 'Edit playlists and playlist slides.' },
|
{ key: 'update', name: 'Update', description: 'Update playlists and playlist slides.' },
|
||||||
{ key: 'delete', name: 'Delete', description: 'Delete playlists.' }
|
{ key: 'delete', name: 'Delete', description: 'Delete playlists.' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -51,7 +51,7 @@ const PERMISSION_SECTIONS = [
|
|||||||
actions: [
|
actions: [
|
||||||
{ key: 'read', name: 'Read', description: 'View slides.' },
|
{ key: 'read', name: 'Read', description: 'View slides.' },
|
||||||
{ key: 'create', name: 'Create', description: 'Create new slides.' },
|
{ key: 'create', name: 'Create', description: 'Create new slides.' },
|
||||||
{ key: 'edit', name: 'Update', description: 'Edit slide content.' },
|
{ key: 'update', name: 'Update', description: 'Update slide content.' },
|
||||||
{ key: 'delete', name: 'Delete', description: 'Delete slides.' }
|
{ key: 'delete', name: 'Delete', description: 'Delete slides.' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -63,7 +63,7 @@ const PERMISSION_SECTIONS = [
|
|||||||
actions: [
|
actions: [
|
||||||
{ key: 'read', name: 'Read', description: 'View slide templates.' },
|
{ key: 'read', name: 'Read', description: 'View slide templates.' },
|
||||||
{ key: 'create', name: 'Create', description: 'Create new slide templates.' },
|
{ key: 'create', name: 'Create', description: 'Create new slide templates.' },
|
||||||
{ key: 'edit', name: 'Update', description: 'Edit slide templates.' },
|
{ key: 'update', name: 'Update', description: 'Update slide templates.' },
|
||||||
{ key: 'delete', name: 'Delete', description: 'Delete slide templates.' }
|
{ key: 'delete', name: 'Delete', description: 'Delete slide templates.' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -75,7 +75,7 @@ const PERMISSION_SECTIONS = [
|
|||||||
actions: [
|
actions: [
|
||||||
{ key: 'read', name: 'Read', description: 'View canvas sizes.' },
|
{ key: 'read', name: 'Read', description: 'View canvas sizes.' },
|
||||||
{ key: 'create', name: 'Create', description: 'Create new canvas sizes.' },
|
{ key: 'create', name: 'Create', description: 'Create new canvas sizes.' },
|
||||||
{ key: 'edit', name: 'Update', description: 'Edit canvas sizes.' },
|
{ key: 'update', name: 'Update', description: 'Update canvas sizes.' },
|
||||||
{ key: 'delete', name: 'Delete', description: 'Delete canvas sizes.' }
|
{ key: 'delete', name: 'Delete', description: 'Delete canvas sizes.' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -87,7 +87,7 @@ const PERMISSION_SECTIONS = [
|
|||||||
actions: [
|
actions: [
|
||||||
{ key: 'read', name: 'Read', description: 'View users and role assignments.' },
|
{ key: 'read', name: 'Read', description: 'View users and role assignments.' },
|
||||||
{ key: 'create', name: 'Create', description: 'Create new users.' },
|
{ key: 'create', name: 'Create', description: 'Create new users.' },
|
||||||
{ key: 'edit', name: 'Update', description: 'Edit users, passwords, and role assignments.' },
|
{ key: 'update', name: 'Update', description: 'Update users, passwords, and role assignments.' },
|
||||||
{ key: 'delete', name: 'Delete', description: 'Delete users.' }
|
{ key: 'delete', name: 'Delete', description: 'Delete users.' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -99,7 +99,7 @@ const PERMISSION_SECTIONS = [
|
|||||||
actions: [
|
actions: [
|
||||||
{ key: 'read', name: 'Read', description: 'View roles and permissions.' },
|
{ key: 'read', name: 'Read', description: 'View roles and permissions.' },
|
||||||
{ key: 'create', name: 'Create', description: 'Create new roles.' },
|
{ key: 'create', name: 'Create', description: 'Create new roles.' },
|
||||||
{ key: 'edit', name: 'Update', description: 'Edit role details and permissions.' },
|
{ key: 'update', name: 'Update', description: 'Update role details and permissions.' },
|
||||||
{ key: 'delete', name: 'Delete', description: 'Delete roles.' }
|
{ key: 'delete', name: 'Delete', description: 'Delete roles.' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -139,22 +139,23 @@ function normalizePermissionKeys(permissionKeys) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
normalized.push(normalizedPermissionKey);
|
|
||||||
|
|
||||||
const parts = normalizedPermissionKey.split('.');
|
const parts = normalizedPermissionKey.split('.');
|
||||||
if (parts.length !== 2) {
|
if (parts.length !== 2) {
|
||||||
|
normalized.push(normalizedPermissionKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionKey = parts[0];
|
const sectionKey = parts[0];
|
||||||
const actionKey = parts[1];
|
const actionKey = parts[1];
|
||||||
if (actionKey === 'create' || actionKey === 'edit' || actionKey === 'delete') {
|
normalized.push(`${sectionKey}.${actionKey}`);
|
||||||
|
|
||||||
|
if (actionKey === 'create' || actionKey === 'update' || actionKey === 'delete') {
|
||||||
normalized.push(`${sectionKey}.read`);
|
normalized.push(`${sectionKey}.read`);
|
||||||
}
|
}
|
||||||
if (actionKey === 'manage') {
|
if (actionKey === 'manage') {
|
||||||
normalized.push(`${sectionKey}.read`);
|
normalized.push(`${sectionKey}.read`);
|
||||||
normalized.push(`${sectionKey}.create`);
|
normalized.push(`${sectionKey}.create`);
|
||||||
normalized.push(`${sectionKey}.edit`);
|
normalized.push(`${sectionKey}.update`);
|
||||||
normalized.push(`${sectionKey}.delete`);
|
normalized.push(`${sectionKey}.delete`);
|
||||||
}
|
}
|
||||||
if (actionKey === 'view') {
|
if (actionKey === 'view') {
|
||||||
@@ -177,7 +178,7 @@ function hasPermission(currentUser, permissionKey) {
|
|||||||
? currentUser.permissions
|
? currentUser.permissions
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return permissionKeys.map(normalizePermissionKey).includes(normalizedPermissionKey);
|
return normalizePermissionKeys(permissionKeys).includes(normalizedPermissionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasAnyPermission(currentUser, permissionKeys) {
|
function hasAnyPermission(currentUser, permissionKeys) {
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ async function start() {
|
|||||||
pages: pages,
|
pages: pages,
|
||||||
getAuditUserId: getAuditUserId,
|
getAuditUserId: getAuditUserId,
|
||||||
rbacData: rbacData,
|
rbacData: rbacData,
|
||||||
|
readArrayField: readArrayField,
|
||||||
permissions: require('./rbac').PERMISSIONS,
|
permissions: require('./rbac').PERMISSIONS,
|
||||||
normalizePermissionKeys: require('./rbac').normalizePermissionKeys,
|
normalizePermissionKeys: require('./rbac').normalizePermissionKeys,
|
||||||
requirePermission: requirePermission
|
requirePermission: requirePermission
|
||||||
|
|||||||
@@ -158,6 +158,16 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header .card-tools {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-form-card {
|
.admin-form-card {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -199,7 +209,6 @@
|
|||||||
|
|
||||||
.dashboard-hero-copy {
|
.dashboard-hero-copy {
|
||||||
margin: 0.75rem 0 0;
|
margin: 0.75rem 0 0;
|
||||||
max-width: 42rem;
|
|
||||||
color: var(--bs-secondary-color);
|
color: var(--bs-secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +350,10 @@
|
|||||||
color: var(--bs-secondary-color);
|
color: var(--bs-secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.connected-updated-secondary {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.connection-count,
|
.connection-count,
|
||||||
td[data-label="Name"],
|
td[data-label="Name"],
|
||||||
td[data-label="Title"],
|
td[data-label="Title"],
|
||||||
@@ -1155,6 +1168,13 @@ td[data-label="Slides"] {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.table > :not(caption) > thead > tr > th:last-child,
|
||||||
|
table.table > :not(caption) > tbody > tr > td:last-child,
|
||||||
|
td[data-label="Actions"] {
|
||||||
|
width: 1%;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
td[data-label="Actions"] {
|
td[data-label="Actions"] {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
var reloadConfirmMessage = 'Reloading will restart the player page. Continue?';
|
var reloadConfirmMessage = 'Reloading will restart the player page. Continue?';
|
||||||
var blackoutCommandValue = blackout ? 'false' : 'true';
|
var blackoutCommandValue = blackout ? 'false' : 'true';
|
||||||
|
|
||||||
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>';
|
return '<div class="actions justify-content-end"><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) {
|
function updateClientActionCell(cell, client) {
|
||||||
@@ -229,7 +229,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderClientRow(client, hasActionsColumn) {
|
function renderClientRow(client, hasActionsColumn) {
|
||||||
var connectedAt = client.connectedAt ? '<div>' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '</div>' + (client.lastSeenAt ? '<div class="subtle">' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '</div>' : '') : '<span class="empty">Unknown</span>';
|
var connectedAt = client.connectedAt ? '<div>' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '</div>' + (client.lastSeenAt ? '<div class="subtle connected-updated-secondary">' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '</div>' : '') : '<span class="empty">Unknown</span>';
|
||||||
var clientIpValue = normalizeDisplayIp(client.clientIp);
|
var clientIpValue = normalizeDisplayIp(client.clientIp);
|
||||||
var clientIp = clientIpValue ? escapeHtml(clientIpValue) : '<span class="empty">Unknown</span>';
|
var clientIp = clientIpValue ? escapeHtml(clientIpValue) : '<span class="empty">Unknown</span>';
|
||||||
var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : '<span class="empty">Unknown</span>';
|
var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : '<span class="empty">Unknown</span>';
|
||||||
@@ -324,7 +324,7 @@
|
|||||||
row.setAttribute('data-client-device-id', client.deviceId || '');
|
row.setAttribute('data-client-device-id', client.deviceId || '');
|
||||||
row.setAttribute('data-client-screen-slug', client.screen_slug || '');
|
row.setAttribute('data-client-screen-slug', client.screen_slug || '');
|
||||||
if (row.cells && row.cells.length >= 6) {
|
if (row.cells && row.cells.length >= 6) {
|
||||||
var connectedAt = client.connectedAt ? '<div>' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '</div>' + (client.lastSeenAt ? '<div class="subtle">' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '</div>' : '') : '<span class="empty">Unknown</span>';
|
var connectedAt = client.connectedAt ? '<div>' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '</div>' + (client.lastSeenAt ? '<div class="subtle connected-updated-secondary">' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '</div>' : '') : '<span class="empty">Unknown</span>';
|
||||||
var clientIpValue = normalizeDisplayIp(client.clientIp);
|
var clientIpValue = normalizeDisplayIp(client.clientIp);
|
||||||
var clientIp = clientIpValue ? escapeHtml(clientIpValue) : '<span class="empty">Unknown</span>';
|
var clientIp = clientIpValue ? escapeHtml(clientIpValue) : '<span class="empty">Unknown</span>';
|
||||||
var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : '<span class="empty">Unknown</span>';
|
var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : '<span class="empty">Unknown</span>';
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/slides/:id/edit', requirePermission('slides.edit'), async function (req, res, next) {
|
app.get('/admin/slides/:id/edit', requirePermission('slides.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
||||||
if (!slide) {
|
if (!slide) {
|
||||||
@@ -111,7 +111,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/slides/:id', requirePermission('slides.edit'), upload.any(), async function (req, res, next) {
|
app.post('/admin/slides/:id', requirePermission('slides.update'), upload.any(), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
||||||
if (!slide) {
|
if (!slide) {
|
||||||
@@ -227,7 +227,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/templates/:id/edit', requirePermission('templates.edit'), async function (req, res, next) {
|
app.get('/admin/templates/:id/edit', requirePermission('templates.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const template = await common.fetchTemplateById(pool, Number(req.params.id));
|
const template = await common.fetchTemplateById(pool, Number(req.params.id));
|
||||||
if (!template) {
|
if (!template) {
|
||||||
@@ -240,7 +240,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/templates/:id', requirePermission('templates.edit'), upload.any(), async function (req, res, next) {
|
app.post('/admin/templates/:id', requirePermission('templates.update'), upload.any(), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const template = await common.fetchTemplateById(pool, Number(req.params.id));
|
const template = await common.fetchTemplateById(pool, Number(req.params.id));
|
||||||
if (!template) {
|
if (!template) {
|
||||||
@@ -340,7 +340,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/canvas-sizes/:id/edit', requirePermission('canvas-sizes.edit'), async function (req, res, next) {
|
app.get('/admin/canvas-sizes/:id/edit', requirePermission('canvas-sizes.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const canvasSize = await common.fetchCanvasSizeById(pool, Number(req.params.id));
|
const canvasSize = await common.fetchCanvasSizeById(pool, Number(req.params.id));
|
||||||
if (!canvasSize) {
|
if (!canvasSize) {
|
||||||
@@ -352,7 +352,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/canvas-sizes/:id', requirePermission('canvas-sizes.edit'), async function (req, res, next) {
|
app.post('/admin/canvas-sizes/:id', requirePermission('canvas-sizes.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const canvasSize = await common.fetchCanvasSizeById(pool, Number(req.params.id));
|
const canvasSize = await common.fetchCanvasSizeById(pool, Number(req.params.id));
|
||||||
if (!canvasSize) {
|
if (!canvasSize) {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/playlists/:id', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.post('/admin/playlists/:id', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
const connection = await pool.getConnection();
|
const connection = await pool.getConnection();
|
||||||
try {
|
try {
|
||||||
const name = String(req.body.name || '').trim();
|
const name = String(req.body.name || '').trim();
|
||||||
@@ -273,7 +273,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/playlists/:id/slides', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.post('/admin/playlists/:id/slides', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
@@ -308,7 +308,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/playlists/:id/slides/:playlistSlideId', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.post('/admin/playlists/:id/slides/:playlistSlideId', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
@@ -332,7 +332,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/playlists/:id/slides/:playlistSlideId/move', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.post('/admin/playlists/:id/slides/:playlistSlideId/move', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
const connection = await pool.getConnection();
|
const connection = await pool.getConnection();
|
||||||
try {
|
try {
|
||||||
const playlist = await common.fetchPlaylistById(connection, Number(req.params.id));
|
const playlist = await common.fetchPlaylistById(connection, Number(req.params.id));
|
||||||
@@ -379,7 +379,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/playlists/:id/slides/:playlistSlideId/config', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.get('/admin/playlists/:id/slides/:playlistSlideId/config', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
@@ -416,7 +416,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/playlists/:id/slides/:playlistSlideId/config', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.post('/admin/playlists/:id/slides/:playlistSlideId/config', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
@@ -496,7 +496,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/playlists/:id/slides/:playlistSlideId/delete', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.post('/admin/playlists/:id/slides/:playlistSlideId/delete', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
@@ -541,7 +541,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/screens/:id', requirePermission('screens.edit'), async function (req, res, next) {
|
app.post('/admin/screens/:id', requirePermission('screens.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const name = String(req.body.name || '').trim();
|
const name = String(req.body.name || '').trim();
|
||||||
if (!name) {
|
if (!name) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module.exports = function registerAdminPagesRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/screens', requireQueryPermission('screens.read', 'screens.edit'), async function (req, res, next) {
|
app.get('/admin/screens', requireQueryPermission('screens.read', 'screens.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const data = await buildDashboardState(pool);
|
const data = await buildDashboardState(pool);
|
||||||
if (req.query.edit) {
|
if (req.query.edit) {
|
||||||
@@ -47,7 +47,7 @@ module.exports = function registerAdminPagesRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/screens/:id/edit', requirePermission('screens.edit'), async function (req, res, next) {
|
app.get('/admin/screens/:id/edit', requirePermission('screens.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const screen = await common.fetchScreenById(pool, Number(req.params.id));
|
const screen = await common.fetchScreenById(pool, Number(req.params.id));
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
@@ -60,7 +60,7 @@ module.exports = function registerAdminPagesRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/playlists', requireQueryPermission('playlists.read', 'playlists.edit'), async function (req, res, next) {
|
app.get('/admin/playlists', requireQueryPermission('playlists.read', 'playlists.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const data = await common.fetchAdminData(pool);
|
const data = await common.fetchAdminData(pool);
|
||||||
if (req.query.edit) {
|
if (req.query.edit) {
|
||||||
@@ -80,7 +80,7 @@ module.exports = function registerAdminPagesRoutes(app, deps) {
|
|||||||
res.send(pages.renderPlaylistFormPage(req.query.message ? String(req.query.message) : '', req.currentUser));
|
res.send(pages.renderPlaylistFormPage(req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/playlists/:id/edit', requirePermission('playlists.edit'), async function (req, res, next) {
|
app.get('/admin/playlists/:id/edit', requirePermission('playlists.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ module.exports = function registerAdminRbacRoutes(app, deps) {
|
|||||||
const getAuditUserId = deps.getAuditUserId;
|
const getAuditUserId = deps.getAuditUserId;
|
||||||
const rbacData = deps.rbacData;
|
const rbacData = deps.rbacData;
|
||||||
const permissions = Array.isArray(deps.permissions) ? deps.permissions : [];
|
const permissions = Array.isArray(deps.permissions) ? deps.permissions : [];
|
||||||
|
const readArrayField = deps.readArrayField;
|
||||||
const normalizePermissionKeys = deps.normalizePermissionKeys;
|
const normalizePermissionKeys = deps.normalizePermissionKeys;
|
||||||
const requirePermission = deps.requirePermission;
|
const requirePermission = deps.requirePermission;
|
||||||
|
|
||||||
@@ -222,7 +223,7 @@ module.exports = function registerAdminRbacRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/rbac/:id/edit', requirePermission('rbac.edit'), async function (req, res, next) {
|
app.get('/admin/rbac/:id/edit', requirePermission('rbac.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const roleId = Number(req.params.id);
|
const roleId = Number(req.params.id);
|
||||||
if (!Number.isInteger(roleId) || roleId <= 0) {
|
if (!Number.isInteger(roleId) || roleId <= 0) {
|
||||||
@@ -247,7 +248,7 @@ module.exports = function registerAdminRbacRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/rbac/:id', requirePermission('rbac.edit'), async function (req, res, next) {
|
app.post('/admin/rbac/:id', requirePermission('rbac.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const roleId = Number(req.params.id);
|
const roleId = Number(req.params.id);
|
||||||
if (!Number.isInteger(roleId) || roleId <= 0) {
|
if (!Number.isInteger(roleId) || roleId <= 0) {
|
||||||
@@ -261,21 +262,70 @@ module.exports = function registerAdminRbacRoutes(app, deps) {
|
|||||||
|
|
||||||
const name = String(req.body.name || '').trim();
|
const name = String(req.body.name || '').trim();
|
||||||
const description = String(req.body.description || '').trim();
|
const description = String(req.body.description || '').trim();
|
||||||
|
const shouldSyncPermissions = Object.prototype.hasOwnProperty.call(req.body || {}, 'permissions_present');
|
||||||
|
const shouldSyncUsers = Object.prototype.hasOwnProperty.call(req.body || {}, 'users_present');
|
||||||
|
const selectedPermissionKeys = shouldSyncPermissions
|
||||||
|
? readArrayField(req.body, ['permission_keys[]', 'permission_keys'])
|
||||||
|
: [];
|
||||||
|
const selectedUserIds = shouldSyncUsers
|
||||||
|
? readArrayField(req.body, ['user_ids[]', 'user_ids'])
|
||||||
|
: [];
|
||||||
|
const normalizedPermissionKeys = shouldSyncPermissions ? normalizePermissionKeys(selectedPermissionKeys) : [];
|
||||||
|
const normalizedUserIds = shouldSyncUsers ? normalizeSelectedIds(selectedUserIds) : [];
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return res.redirect('/admin/rbac/' + roleId + '/edit?message=' + encodeURIComponent('Role name is required.'));
|
return res.redirect('/admin/rbac/' + roleId + '/edit?message=' + encodeURIComponent('Role name is required.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
await pool.query(
|
const validPermissionKeys = new Set(permissions.map(function (permission) {
|
||||||
'UPDATE roles SET name = ?, description = ?, modified_by = ? WHERE id = ?',
|
return String(permission.key || '').trim();
|
||||||
[name, description || null, getAuditUserId(req), roleId]
|
}));
|
||||||
);
|
if (shouldSyncPermissions && normalizedPermissionKeys.some(function (permissionKey) {
|
||||||
|
return !validPermissionKeys.has(permissionKey);
|
||||||
|
})) {
|
||||||
|
return res.redirect('/admin/rbac/' + roleId + '/edit?message=' + encodeURIComponent('One or more selected permissions are invalid.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let availableUsers = [];
|
||||||
|
if (shouldSyncUsers) {
|
||||||
|
availableUsers = await rbacData.fetchUsersWithRoles(pool);
|
||||||
|
}
|
||||||
|
const validUserIds = new Set(availableUsers.map(function (user) {
|
||||||
|
return Number(user.id);
|
||||||
|
}));
|
||||||
|
if (shouldSyncUsers && normalizedUserIds.some(function (userId) {
|
||||||
|
return !validUserIds.has(userId);
|
||||||
|
})) {
|
||||||
|
return res.redirect('/admin/rbac/' + roleId + '/edit?message=' + encodeURIComponent('One or more selected users are invalid.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = await pool.getConnection();
|
||||||
|
try {
|
||||||
|
await connection.beginTransaction();
|
||||||
|
await connection.query(
|
||||||
|
'UPDATE roles SET name = ?, description = ?, modified_by = ? WHERE id = ?',
|
||||||
|
[name, description || null, getAuditUserId(req), roleId]
|
||||||
|
);
|
||||||
|
if (shouldSyncPermissions) {
|
||||||
|
await rbacData.syncRolePermissions(connection, roleId, normalizedPermissionKeys);
|
||||||
|
}
|
||||||
|
if (shouldSyncUsers) {
|
||||||
|
await rbacData.syncRoleUsers(connection, roleId, normalizedUserIds);
|
||||||
|
}
|
||||||
|
await connection.commit();
|
||||||
|
} catch (error) {
|
||||||
|
await connection.rollback();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
connection.release();
|
||||||
|
}
|
||||||
res.redirect('/admin/rbac/' + roleId + '/edit?message=' + encodeURIComponent('Role updated.'));
|
res.redirect('/admin/rbac/' + roleId + '/edit?message=' + encodeURIComponent('Role updated.'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/rbac/:id/permissions', requirePermission('rbac.edit'), async function (req, res, next) {
|
app.post('/admin/rbac/:id/permissions', requirePermission('rbac.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const roleId = Number(req.params.id);
|
const roleId = Number(req.params.id);
|
||||||
if (!Number.isInteger(roleId) || roleId <= 0) {
|
if (!Number.isInteger(roleId) || roleId <= 0) {
|
||||||
@@ -309,7 +359,7 @@ module.exports = function registerAdminRbacRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/rbac/:id/users', requirePermission('rbac.edit'), async function (req, res, next) {
|
app.post('/admin/rbac/:id/users', requirePermission('rbac.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const roleId = Number(req.params.id);
|
const roleId = Number(req.params.id);
|
||||||
if (!Number.isInteger(roleId) || roleId <= 0) {
|
if (!Number.isInteger(roleId) || roleId <= 0) {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ module.exports = function registerAdminUsersRoutes(app, deps) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/users/:id/edit', requirePermission('users.edit'), async function (req, res, next) {
|
app.get('/admin/users/:id/edit', requirePermission('users.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const userId = Number(req.params.id);
|
const userId = Number(req.params.id);
|
||||||
if (!Number.isInteger(userId) || userId <= 0) {
|
if (!Number.isInteger(userId) || userId <= 0) {
|
||||||
@@ -164,7 +164,7 @@ module.exports = function registerAdminUsersRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/users/:id/roles', requirePermission('users.edit'), async function (req, res, next) {
|
app.post('/admin/users/:id/roles', requirePermission('users.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const userId = Number(req.params.id);
|
const userId = Number(req.params.id);
|
||||||
if (!Number.isInteger(userId) || userId <= 0) {
|
if (!Number.isInteger(userId) || userId <= 0) {
|
||||||
@@ -192,7 +192,7 @@ module.exports = function registerAdminUsersRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/users/:id/username', requirePermission('users.edit'), async function (req, res, next) {
|
app.post('/admin/users/:id/username', requirePermission('users.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const userId = Number(req.params.id);
|
const userId = Number(req.params.id);
|
||||||
const name = String(req.body.name || '').trim();
|
const name = String(req.body.name || '').trim();
|
||||||
@@ -226,7 +226,7 @@ module.exports = function registerAdminUsersRoutes(app, deps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/admin/users/:id/password', requirePermission('users.edit'), async function (req, res, next) {
|
app.post('/admin/users/:id/password', requirePermission('users.update'), async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
const userId = Number(req.params.id);
|
const userId = Number(req.params.id);
|
||||||
const password = String(req.body.password || '');
|
const password = String(req.body.password || '');
|
||||||
|
|||||||
@@ -8,13 +8,22 @@
|
|||||||
<div class="card card-outline card-primary">
|
<div class="card card-outline card-primary">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Saved sizes</h3>
|
<h3 class="card-title">Saved sizes</h3>
|
||||||
<div class="card-tools">
|
{{#if (hasPermission currentUser 'canvas-sizes.create')}}
|
||||||
<a class="btn btn-primary btn-sm" href="/admin/canvas-sizes/new">Add canvas size</a>
|
<div class="card-tools">
|
||||||
</div>
|
<a class="btn btn-primary btn-sm" href="/admin/canvas-sizes/new">Add canvas size</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive p-0">
|
<div class="card-body table-responsive p-0">
|
||||||
<table class="table table-striped w-100 mb-0">
|
<table class="table table-striped w-100 mb-0">
|
||||||
<thead><tr><th>Name</th><th>Dimensions</th><th>Templates</th><th>Actions</th></tr></thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dimensions</th>
|
||||||
|
<th>Templates</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#if canvasSizes.length}}
|
{{#if canvasSizes.length}}
|
||||||
{{#each canvasSizes}}
|
{{#each canvasSizes}}
|
||||||
@@ -23,12 +32,20 @@
|
|||||||
<td data-label="Dimensions">{{width}}x{{height}}</td>
|
<td data-label="Dimensions">{{width}}x{{height}}</td>
|
||||||
<td data-label="Templates">{{templateCount}}</td>
|
<td data-label="Templates">{{templateCount}}</td>
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<div class="actions">
|
{{#if (anyPermission ../currentUser 'canvas-sizes.update' 'canvas-sizes.delete')}}
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/canvas-sizes/{{id}}/edit">Edit</a>
|
<div class="actions">
|
||||||
<form class="inline-form" method="post" action="/admin/canvas-sizes/{{id}}/delete" data-confirm-message="Delete this canvas size?">
|
{{#if (hasPermission ../currentUser 'canvas-sizes.update')}}
|
||||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
<a class="btn btn-sm btn-primary" href="/admin/canvas-sizes/{{id}}/edit">Edit</a>
|
||||||
</form>
|
{{/if}}
|
||||||
</div>
|
{{#if (hasPermission ../currentUser 'canvas-sizes.delete')}}
|
||||||
|
<form class="inline-form" method="post" action="/admin/canvas-sizes/{{id}}/delete" data-confirm-message="Delete this canvas size?">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">-</span>
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
{{#if connectedAt}}
|
{{#if connectedAt}}
|
||||||
<div>{{connectedAtLabel}}</div>
|
<div>{{connectedAtLabel}}</div>
|
||||||
{{#if lastSeenAt}}
|
{{#if lastSeenAt}}
|
||||||
<div class="subtle"><i>{{lastSeenAtLabel}}</i></div>
|
<div class="subtle connected-updated-secondary">{{lastSeenAtLabel}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="empty">Unknown</span>
|
<span class="empty">Unknown</span>
|
||||||
|
|||||||
@@ -8,13 +8,21 @@
|
|||||||
<div class="card card-outline card-primary">
|
<div class="card card-outline card-primary">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Existing playlists</h3>
|
<h3 class="card-title">Existing playlists</h3>
|
||||||
<div class="card-tools">
|
{{#if (hasPermission currentUser 'playlists.create')}}
|
||||||
<a class="btn btn-primary btn-sm" href="/admin/playlists/new">Create playlist</a>
|
<div class="card-tools">
|
||||||
</div>
|
<a class="btn btn-primary btn-sm" href="/admin/playlists/new">Create playlist</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive p-0">
|
<div class="card-body table-responsive p-0">
|
||||||
<table class="table table-striped w-100 mb-0">
|
<table class="table table-striped w-100 mb-0">
|
||||||
<thead><tr><th>Name</th><th>Slides</th><th>Actions</th></tr></thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Slides</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#if playlists.length}}
|
{{#if playlists.length}}
|
||||||
{{#each playlists}}
|
{{#each playlists}}
|
||||||
@@ -22,12 +30,20 @@
|
|||||||
<td data-label="Name">{{name}}</td>
|
<td data-label="Name">{{name}}</td>
|
||||||
<td data-label="Slides"><span class="chip">{{slideCount}} slide{{#unless slideCountIsOne}}s{{/unless}}</span></td>
|
<td data-label="Slides"><span class="chip">{{slideCount}} slide{{#unless slideCountIsOne}}s{{/unless}}</span></td>
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<div class="actions">
|
{{#if (anyPermission ../currentUser 'playlists.update' 'playlists.delete')}}
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/playlists/{{id}}/edit">Edit</a>
|
<div class="actions">
|
||||||
<form class="inline-form" method="post" action="/admin/playlists/{{id}}/delete" data-confirm-message="Delete this playlist?">
|
{{#if (hasPermission ../currentUser 'playlists.update')}}
|
||||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
<a class="btn btn-sm btn-primary" href="/admin/playlists/{{id}}/edit">Edit</a>
|
||||||
</form>
|
{{/if}}
|
||||||
</div>
|
{{#if (hasPermission ../currentUser 'playlists.delete')}}
|
||||||
|
<form class="inline-form" method="post" action="/admin/playlists/{{id}}/delete" data-confirm-message="Delete this playlist?">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">-</span>
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
+31
-32
@@ -5,13 +5,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4">
|
<form method="post" action="/admin/rbac/{{role.id}}" id="role-edit-form" data-async-save data-async-save-close-url="/admin/rbac">
|
||||||
<div class="col-12 col-xl-4 d-flex flex-column gap-4">
|
<input type="hidden" name="permissions_present" value="1" />
|
||||||
<div class="card card-outline card-primary admin-form-card">
|
<input type="hidden" name="users_present" value="1" />
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Role details</h3>
|
<div class="row g-4">
|
||||||
</div>
|
<div class="col-12 col-xl-4 d-flex flex-column gap-4">
|
||||||
<form method="post" action="/admin/rbac/{{role.id}}" id="role-details-form" data-async-save data-async-save-close-url="/admin/rbac">
|
<div class="card card-outline card-primary admin-form-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Role details</h3>
|
||||||
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="role-name-edit" class="form-label">Role Name</label>
|
<label for="role-name-edit" class="form-label">Role Name</label>
|
||||||
@@ -22,24 +25,20 @@
|
|||||||
<textarea id="role-description-edit" name="description" class="form-control" rows="5">{{role.description}}</textarea>
|
<textarea id="role-description-edit" name="description" class="form-control" rows="5">{{role.description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer d-flex justify-content-end">
|
<div class="card-footer d-flex justify-content-end gap-2 flex-wrap">
|
||||||
<div class="btn-group" role="group" aria-label="Role actions">
|
<div class="btn-group" role="group" aria-label="Role actions">
|
||||||
{{{saveActionButtons formId="role-details-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
|
{{{saveActionButtons formId="role-edit-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
|
||||||
</div>
|
</div>
|
||||||
{{#unless role.user_count}}
|
{{#unless role.user_count}}
|
||||||
<form method="post" action="/admin/rbac/{{role.id}}/delete" class="inline-form" data-confirm-message="Delete {{role.name}}?">
|
<button type="submit" class="btn btn-danger" form="role-delete-form">Delete</button>
|
||||||
<button type="submit" class="btn btn-danger">Delete</button>
|
|
||||||
</form>
|
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card card-outline card-secondary admin-form-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Users</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="/admin/rbac/{{role.id}}/users" id="role-users-form" data-async-save data-async-save-close-url="/admin/rbac/{{role.id}}/edit">
|
|
||||||
|
<div class="card card-outline card-secondary admin-form-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Users</h3>
|
||||||
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="px-3 pt-3">
|
<div class="px-3 pt-3">
|
||||||
<p class="text-body-secondary mb-3">Choose the users who should belong to this role.</p>
|
<p class="text-body-secondary mb-3">Choose the users who should belong to this role.</p>
|
||||||
@@ -74,17 +73,15 @@
|
|||||||
<div class="alert alert-warning mb-0">Create at least one user before assigning this role.</div>
|
<div class="alert alert-warning mb-0">Create at least one user before assigning this role.</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-xl-8 d-flex flex-column gap-4">
|
|
||||||
<div class="card card-outline card-secondary admin-form-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Permissions</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="/admin/rbac/{{role.id}}/permissions" id="role-permissions-form" data-async-save data-async-save-close-url="/admin/rbac/{{role.id}}/edit">
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-xl-8 d-flex flex-column gap-4">
|
||||||
|
<div class="card card-outline card-secondary admin-form-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Permissions</h3>
|
||||||
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="text-body-secondary mb-3">Each section below is a resource. Pick the actions this role should have for that resource.</p>
|
<p class="text-body-secondary mb-3">Each section below is a resource. Pick the actions this role should have for that resource.</p>
|
||||||
{{#if permissionGroups.length}}
|
{{#if permissionGroups.length}}
|
||||||
@@ -125,10 +122,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-footer d-flex justify-content-end">
|
<div class="card-footer d-flex justify-content-end">
|
||||||
<div class="btn-group" role="group" aria-label="Permission actions">
|
<div class="btn-group" role="group" aria-label="Permission actions">
|
||||||
{{{saveActionButtons formId="role-permissions-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
|
{{{saveActionButtons formId="role-edit-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="/admin/rbac/{{role.id}}/delete" id="role-delete-form" class="d-none" data-confirm-message="Delete {{role.name}}?"></form>
|
||||||
+21
-13
@@ -10,9 +10,11 @@
|
|||||||
<div class="card card-outline card-primary admin-form-card">
|
<div class="card card-outline card-primary admin-form-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Existing roles</h3>
|
<h3 class="card-title">Existing roles</h3>
|
||||||
<div class="card-tools">
|
{{#if (hasPermission currentUser 'rbac.create')}}
|
||||||
<a class="btn btn-primary btn-sm" href="/admin/rbac/new">Add role</a>
|
<div class="card-tools">
|
||||||
</div>
|
<a class="btn btn-primary btn-sm" href="/admin/rbac/new">Add role</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive p-0">
|
<div class="card-body table-responsive p-0">
|
||||||
<table class="table table-striped w-100 mb-0 users-table">
|
<table class="table table-striped w-100 mb-0 users-table">
|
||||||
@@ -46,16 +48,22 @@
|
|||||||
<td data-label="Users">{{user_count}}</td>
|
<td data-label="Users">{{user_count}}</td>
|
||||||
<td data-label="Permissions">{{permission_count}}</td>
|
<td data-label="Permissions">{{permission_count}}</td>
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<div class="actions users-row-actions">
|
{{#if (anyPermission ../currentUser 'rbac.update' 'rbac.delete')}}
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/rbac/{{id}}/edit">Edit</a>
|
<div class="actions users-row-actions">
|
||||||
{{#unless user_count}}
|
{{#if (hasPermission ../currentUser 'rbac.update')}}
|
||||||
<form method="post" action="/admin/rbac/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{name}}?">
|
<a class="btn btn-sm btn-primary" href="/admin/rbac/{{id}}/edit">Edit</a>
|
||||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
{{/if}}
|
||||||
</form>
|
{{#if (hasPermission ../currentUser 'rbac.delete')}}
|
||||||
{{else}}
|
{{#unless user_count}}
|
||||||
<span class="empty">In use</span>
|
<form method="post" action="/admin/rbac/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{name}}?">
|
||||||
{{/unless}}
|
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||||
</div>
|
</form>
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">In use</span>
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -8,13 +8,23 @@
|
|||||||
<div class="card card-outline card-primary">
|
<div class="card card-outline card-primary">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Existing screens</h3>
|
<h3 class="card-title">Existing screens</h3>
|
||||||
<div class="card-tools">
|
{{#if (hasPermission currentUser 'screens.create')}}
|
||||||
<a class="btn btn-primary btn-sm" href="/admin/screens/new">Add screen</a>
|
<div class="card-tools">
|
||||||
</div>
|
<a class="btn btn-primary btn-sm" href="/admin/screens/new">Add screen</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive p-0">
|
<div class="card-body table-responsive p-0">
|
||||||
<table class="table table-striped w-100 mb-0">
|
<table class="table table-striped w-100 mb-0">
|
||||||
<thead><tr><th>Name</th><th>Player URL</th><th>Playlist</th><th>Connected clients</th><th>Actions</th></tr></thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Player URL</th>
|
||||||
|
<th>Playlist</th>
|
||||||
|
<th>Connected clients</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#if screens.length}}
|
{{#if screens.length}}
|
||||||
{{#each screens}}
|
{{#each screens}}
|
||||||
@@ -30,12 +40,20 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<div class="actions">
|
{{#if (anyPermission ../currentUser 'screens.update' 'screens.delete')}}
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/screens/{{id}}/edit">Edit</a>
|
<div class="actions">
|
||||||
<form class="inline-form" method="post" action="/admin/screens/{{id}}/delete" data-confirm-message="Delete this screen?">
|
{{#if (hasPermission ../currentUser 'screens.update')}}
|
||||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
<a class="btn btn-sm btn-primary" href="/admin/screens/{{id}}/edit">Edit</a>
|
||||||
</form>
|
{{/if}}
|
||||||
</div>
|
{{#if (hasPermission ../currentUser 'screens.delete')}}
|
||||||
|
<form class="inline-form" method="post" action="/admin/screens/{{id}}/delete" data-confirm-message="Delete this screen?">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">-</span>
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -8,13 +8,21 @@
|
|||||||
<div class="card card-outline card-primary">
|
<div class="card card-outline card-primary">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Existing slides</h3>
|
<h3 class="card-title">Existing slides</h3>
|
||||||
<div class="card-tools">
|
{{#if (hasPermission currentUser 'slides.create')}}
|
||||||
<a class="btn btn-primary btn-sm" href="/admin/slides/new">Create slide</a>
|
<div class="card-tools">
|
||||||
</div>
|
<a class="btn btn-primary btn-sm" href="/admin/slides/new">Create slide</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive p-0">
|
<div class="card-body table-responsive p-0">
|
||||||
<table class="table table-striped w-100 mb-0">
|
<table class="table table-striped w-100 mb-0">
|
||||||
<thead><tr><th>Title</th><th>Template</th><th>Actions</th></tr></thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Template</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#if slides.length}}
|
{{#if slides.length}}
|
||||||
{{#each slides}}
|
{{#each slides}}
|
||||||
@@ -22,12 +30,20 @@
|
|||||||
<td data-label="Title">{{title}}</td>
|
<td data-label="Title">{{title}}</td>
|
||||||
<td data-label="Template">{{template_name}}</td>
|
<td data-label="Template">{{template_name}}</td>
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<div class="actions">
|
{{#if (anyPermission ../currentUser 'slides.update' 'slides.delete')}}
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/slides/{{id}}/edit">Edit</a>
|
<div class="actions">
|
||||||
<form class="inline-form" method="post" action="/admin/slides/{{id}}/delete" data-confirm-message="Delete this slide?">
|
{{#if (hasPermission ../currentUser 'slides.update')}}
|
||||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
<a class="btn btn-sm btn-primary" href="/admin/slides/{{id}}/edit">Edit</a>
|
||||||
</form>
|
{{/if}}
|
||||||
</div>
|
{{#if (hasPermission ../currentUser 'slides.delete')}}
|
||||||
|
<form class="inline-form" method="post" action="/admin/slides/{{id}}/delete" data-confirm-message="Delete this slide?">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">-</span>
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -8,13 +8,22 @@
|
|||||||
<div class="card card-outline card-primary">
|
<div class="card card-outline card-primary">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Existing templates</h3>
|
<h3 class="card-title">Existing templates</h3>
|
||||||
<div class="card-tools">
|
{{#if (hasPermission currentUser 'templates.create')}}
|
||||||
<a class="btn btn-primary btn-sm" href="/admin/templates/new">Create template</a>
|
<div class="card-tools">
|
||||||
</div>
|
<a class="btn btn-primary btn-sm" href="/admin/templates/new">Create template</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive p-0">
|
<div class="card-body table-responsive p-0">
|
||||||
<table class="table table-striped w-100 mb-0">
|
<table class="table table-striped w-100 mb-0">
|
||||||
<thead><tr><th>Name</th><th>Canvas</th><th>Regions</th><th>Actions</th></tr></thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Canvas</th>
|
||||||
|
<th>Regions</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#if templates.length}}
|
{{#if templates.length}}
|
||||||
{{#each templates}}
|
{{#each templates}}
|
||||||
@@ -23,12 +32,20 @@
|
|||||||
<td data-label="Canvas">{{canvas_size_width}}x{{canvas_size_height}}</td>
|
<td data-label="Canvas">{{canvas_size_width}}x{{canvas_size_height}}</td>
|
||||||
<td data-label="Regions">{{regionCount}}</td>
|
<td data-label="Regions">{{regionCount}}</td>
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<div class="actions">
|
{{#if (anyPermission ../currentUser 'templates.update' 'templates.delete')}}
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/templates/{{id}}/edit">Edit</a>
|
<div class="actions">
|
||||||
<form class="inline-form" method="post" action="/admin/templates/{{id}}/delete" data-confirm-message="Delete this template?">
|
{{#if (hasPermission ../currentUser 'templates.update')}}
|
||||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
<a class="btn btn-sm btn-primary" href="/admin/templates/{{id}}/edit">Edit</a>
|
||||||
</form>
|
{{/if}}
|
||||||
</div>
|
{{#if (hasPermission ../currentUser 'templates.delete')}}
|
||||||
|
<form class="inline-form" method="post" action="/admin/templates/{{id}}/delete" data-confirm-message="Delete this template?">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">-</span>
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -8,9 +8,11 @@
|
|||||||
<div class="card card-outline card-primary">
|
<div class="card card-outline card-primary">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Existing users</h3>
|
<h3 class="card-title">Existing users</h3>
|
||||||
<div class="card-tools">
|
{{#if (hasPermission currentUser 'users.create')}}
|
||||||
<a class="btn btn-primary btn-sm" href="/admin/users/new">Add user</a>
|
<div class="card-tools">
|
||||||
</div>
|
<a class="btn btn-primary btn-sm" href="/admin/users/new">Add user</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive p-0">
|
<div class="card-body table-responsive p-0">
|
||||||
<table class="table table-striped w-100 mb-0 users-table">
|
<table class="table table-striped w-100 mb-0 users-table">
|
||||||
@@ -61,16 +63,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<div class="actions users-row-actions">
|
{{#if (anyPermission ../currentUser 'users.update' 'users.delete')}}
|
||||||
{{#unless isCurrentUser}}
|
<div class="actions users-row-actions">
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/users/{{id}}/edit">Edit</a>
|
{{#unless isCurrentUser}}
|
||||||
<form method="post" action="/admin/users/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{username}}?">
|
{{#if (hasPermission ../currentUser 'users.update')}}
|
||||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
<a class="btn btn-sm btn-primary" href="/admin/users/{{id}}/edit">Edit</a>
|
||||||
</form>
|
{{/if}}
|
||||||
{{else}}
|
{{#if (hasPermission ../currentUser 'users.delete')}}
|
||||||
<span class="empty">Protected</span>
|
<form method="post" action="/admin/users/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{username}}?">
|
||||||
{{/unless}}
|
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||||
</div>
|
</form>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">Protected</span>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="empty">-</span>
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
Reference in New Issue
Block a user