Bump version to 1.4.6
This commit is contained in:
@@ -158,6 +158,16 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-header .card-tools {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.admin-form-card {
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -199,7 +209,6 @@
|
||||
|
||||
.dashboard-hero-copy {
|
||||
margin: 0.75rem 0 0;
|
||||
max-width: 42rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
@@ -341,6 +350,10 @@
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.connected-updated-secondary {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.connection-count,
|
||||
td[data-label="Name"],
|
||||
td[data-label="Title"],
|
||||
@@ -1155,6 +1168,13 @@ td[data-label="Slides"] {
|
||||
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"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -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/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) {
|
||||
@@ -229,7 +229,7 @@
|
||||
}
|
||||
|
||||
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 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>';
|
||||
@@ -324,7 +324,7 @@
|
||||
row.setAttribute('data-client-device-id', client.deviceId || '');
|
||||
row.setAttribute('data-client-screen-slug', client.screen_slug || '');
|
||||
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 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>';
|
||||
|
||||
@@ -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 {
|
||||
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const template = await common.fetchTemplateById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const template = await common.fetchTemplateById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const canvasSize = await common.fetchCanvasSizeById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const canvasSize = await common.fetchCanvasSizeById(pool, Number(req.params.id));
|
||||
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();
|
||||
try {
|
||||
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 {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||
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();
|
||||
try {
|
||||
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 {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const name = String(req.body.name || '').trim();
|
||||
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 {
|
||||
const data = await buildDashboardState(pool);
|
||||
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 {
|
||||
const screen = await common.fetchScreenById(pool, Number(req.params.id));
|
||||
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 {
|
||||
const data = await common.fetchAdminData(pool);
|
||||
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));
|
||||
});
|
||||
|
||||
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 {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||
if (!playlist) {
|
||||
|
||||
@@ -4,6 +4,7 @@ module.exports = function registerAdminRbacRoutes(app, deps) {
|
||||
const getAuditUserId = deps.getAuditUserId;
|
||||
const rbacData = deps.rbacData;
|
||||
const permissions = Array.isArray(deps.permissions) ? deps.permissions : [];
|
||||
const readArrayField = deps.readArrayField;
|
||||
const normalizePermissionKeys = deps.normalizePermissionKeys;
|
||||
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 {
|
||||
const roleId = Number(req.params.id);
|
||||
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 {
|
||||
const roleId = Number(req.params.id);
|
||||
if (!Number.isInteger(roleId) || roleId <= 0) {
|
||||
@@ -261,21 +262,70 @@ module.exports = function registerAdminRbacRoutes(app, deps) {
|
||||
|
||||
const name = String(req.body.name || '').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) {
|
||||
return res.redirect('/admin/rbac/' + roleId + '/edit?message=' + encodeURIComponent('Role name is required.'));
|
||||
}
|
||||
|
||||
await pool.query(
|
||||
'UPDATE roles SET name = ?, description = ?, modified_by = ? WHERE id = ?',
|
||||
[name, description || null, getAuditUserId(req), roleId]
|
||||
);
|
||||
const validPermissionKeys = new Set(permissions.map(function (permission) {
|
||||
return String(permission.key || '').trim();
|
||||
}));
|
||||
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.'));
|
||||
} catch (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 {
|
||||
const roleId = Number(req.params.id);
|
||||
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 {
|
||||
const roleId = Number(req.params.id);
|
||||
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 {
|
||||
const userId = Number(req.params.id);
|
||||
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 {
|
||||
const userId = Number(req.params.id);
|
||||
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 {
|
||||
const userId = Number(req.params.id);
|
||||
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 {
|
||||
const userId = Number(req.params.id);
|
||||
const password = String(req.body.password || '');
|
||||
|
||||
@@ -8,13 +8,22 @@
|
||||
<div class="card card-outline card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Saved sizes</h3>
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/canvas-sizes/new">Add canvas size</a>
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'canvas-sizes.create')}}
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/canvas-sizes/new">Add canvas size</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-body table-responsive p-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>
|
||||
{{#if canvasSizes.length}}
|
||||
{{#each canvasSizes}}
|
||||
@@ -23,12 +32,20 @@
|
||||
<td data-label="Dimensions">{{width}}x{{height}}</td>
|
||||
<td data-label="Templates">{{templateCount}}</td>
|
||||
<td data-label="Actions">
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-primary" href="/admin/canvas-sizes/{{id}}/edit">Edit</a>
|
||||
<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>
|
||||
</div>
|
||||
{{#if (anyPermission ../currentUser 'canvas-sizes.update' 'canvas-sizes.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'canvas-sizes.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/canvas-sizes/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#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>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
{{#if connectedAt}}
|
||||
<div>{{connectedAtLabel}}</div>
|
||||
{{#if lastSeenAt}}
|
||||
<div class="subtle"><i>{{lastSeenAtLabel}}</i></div>
|
||||
<div class="subtle connected-updated-secondary">{{lastSeenAtLabel}}</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="empty">Unknown</span>
|
||||
|
||||
@@ -8,13 +8,21 @@
|
||||
<div class="card card-outline card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Existing playlists</h3>
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/playlists/new">Create playlist</a>
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'playlists.create')}}
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/playlists/new">Create playlist</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-body table-responsive p-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>
|
||||
{{#if playlists.length}}
|
||||
{{#each playlists}}
|
||||
@@ -22,12 +30,20 @@
|
||||
<td data-label="Name">{{name}}</td>
|
||||
<td data-label="Slides"><span class="chip">{{slideCount}} slide{{#unless slideCountIsOne}}s{{/unless}}</span></td>
|
||||
<td data-label="Actions">
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-primary" href="/admin/playlists/{{id}}/edit">Edit</a>
|
||||
<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>
|
||||
</div>
|
||||
{{#if (anyPermission ../currentUser 'playlists.update' 'playlists.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'playlists.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/playlists/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#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>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
+31
-32
@@ -5,13 +5,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xl-4 d-flex flex-column gap-4">
|
||||
<div class="card card-outline card-primary admin-form-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Role details</h3>
|
||||
</div>
|
||||
<form method="post" action="/admin/rbac/{{role.id}}" id="role-details-form" data-async-save data-async-save-close-url="/admin/rbac">
|
||||
<form method="post" action="/admin/rbac/{{role.id}}" id="role-edit-form" data-async-save data-async-save-close-url="/admin/rbac">
|
||||
<input type="hidden" name="permissions_present" value="1" />
|
||||
<input type="hidden" name="users_present" value="1" />
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xl-4 d-flex flex-column gap-4">
|
||||
<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="mb-3">
|
||||
<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>
|
||||
</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">
|
||||
{{{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>
|
||||
{{#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">Delete</button>
|
||||
</form>
|
||||
<button type="submit" class="btn btn-danger" form="role-delete-form">Delete</button>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card card-outline card-secondary admin-form-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Users</h3>
|
||||
</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="px-3 pt-3">
|
||||
<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>
|
||||
{{/if}}
|
||||
</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">
|
||||
<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}}
|
||||
@@ -125,10 +122,12 @@
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-end">
|
||||
<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>
|
||||
</form>
|
||||
</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-header">
|
||||
<h3 class="card-title">Existing roles</h3>
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/rbac/new">Add role</a>
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'rbac.create')}}
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/rbac/new">Add role</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-body table-responsive p-0">
|
||||
<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="Permissions">{{permission_count}}</td>
|
||||
<td data-label="Actions">
|
||||
<div class="actions users-row-actions">
|
||||
<a class="btn btn-sm btn-primary" href="/admin/rbac/{{id}}/edit">Edit</a>
|
||||
{{#unless user_count}}
|
||||
<form method="post" action="/admin/rbac/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{name}}?">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<span class="empty">In use</span>
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{#if (anyPermission ../currentUser 'rbac.update' 'rbac.delete')}}
|
||||
<div class="actions users-row-actions">
|
||||
{{#if (hasPermission ../currentUser 'rbac.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/rbac/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'rbac.delete')}}
|
||||
{{#unless user_count}}
|
||||
<form method="post" action="/admin/rbac/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{name}}?">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<span class="empty">In use</span>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
@@ -8,13 +8,23 @@
|
||||
<div class="card card-outline card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Existing screens</h3>
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/screens/new">Add screen</a>
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'screens.create')}}
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/screens/new">Add screen</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-body table-responsive p-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>
|
||||
{{#if screens.length}}
|
||||
{{#each screens}}
|
||||
@@ -30,12 +40,20 @@
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-label="Actions">
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-primary" href="/admin/screens/{{id}}/edit">Edit</a>
|
||||
<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>
|
||||
</div>
|
||||
{{#if (anyPermission ../currentUser 'screens.update' 'screens.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'screens.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/screens/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#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>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
@@ -8,13 +8,21 @@
|
||||
<div class="card card-outline card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Existing slides</h3>
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/slides/new">Create slide</a>
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'slides.create')}}
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/slides/new">Create slide</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-body table-responsive p-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>
|
||||
{{#if slides.length}}
|
||||
{{#each slides}}
|
||||
@@ -22,12 +30,20 @@
|
||||
<td data-label="Title">{{title}}</td>
|
||||
<td data-label="Template">{{template_name}}</td>
|
||||
<td data-label="Actions">
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-primary" href="/admin/slides/{{id}}/edit">Edit</a>
|
||||
<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>
|
||||
</div>
|
||||
{{#if (anyPermission ../currentUser 'slides.update' 'slides.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'slides.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/slides/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#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>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
@@ -8,13 +8,22 @@
|
||||
<div class="card card-outline card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Existing templates</h3>
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/templates/new">Create template</a>
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'templates.create')}}
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/templates/new">Create template</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-body table-responsive p-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>
|
||||
{{#if templates.length}}
|
||||
{{#each templates}}
|
||||
@@ -23,12 +32,20 @@
|
||||
<td data-label="Canvas">{{canvas_size_width}}x{{canvas_size_height}}</td>
|
||||
<td data-label="Regions">{{regionCount}}</td>
|
||||
<td data-label="Actions">
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-primary" href="/admin/templates/{{id}}/edit">Edit</a>
|
||||
<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>
|
||||
</div>
|
||||
{{#if (anyPermission ../currentUser 'templates.update' 'templates.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'templates.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/templates/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#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>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
<div class="card card-outline card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Existing users</h3>
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/users/new">Add user</a>
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'users.create')}}
|
||||
<div class="card-tools">
|
||||
<a class="btn btn-primary btn-sm" href="/admin/users/new">Add user</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-body table-responsive p-0">
|
||||
<table class="table table-striped w-100 mb-0 users-table">
|
||||
@@ -61,16 +63,24 @@
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="Actions">
|
||||
<div class="actions users-row-actions">
|
||||
{{#unless isCurrentUser}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/users/{{id}}/edit">Edit</a>
|
||||
<form method="post" action="/admin/users/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{username}}?">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<span class="empty">Protected</span>
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{#if (anyPermission ../currentUser 'users.update' 'users.delete')}}
|
||||
<div class="actions users-row-actions">
|
||||
{{#unless isCurrentUser}}
|
||||
{{#if (hasPermission ../currentUser 'users.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/admin/users/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'users.delete')}}
|
||||
<form method="post" action="/admin/users/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{username}}?">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="empty">Protected</span>
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{else}}
|
||||
<span class="empty">-</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
Reference in New Issue
Block a user