Release v2.5.6
This commit is contained in:
@@ -9,6 +9,12 @@ const TASKS_PER_PAGE = 25;
|
||||
const QUEUE_PAGE_URL = '/settings/tasks-background';
|
||||
const SCHEDULED_PAGE_URL = '/settings/tasks-scheduled';
|
||||
|
||||
const DATE_FILTER_FIELDS = {
|
||||
createdAt: 'queued',
|
||||
startedAt: 'started',
|
||||
finishedAt: 'finished'
|
||||
};
|
||||
|
||||
function formatIntervalLabel(intervalMs) {
|
||||
const value = Math.max(1, Number(intervalMs) || 0);
|
||||
if (value % 60000 === 0) {
|
||||
@@ -36,6 +42,54 @@ function normalizeSourceFilter(sourceType, sourceId) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeDateInput(value) {
|
||||
const normalizedValue = String(value || '').trim();
|
||||
return /^\d{4}-\d{2}-\d{2}$/.test(normalizedValue) ? normalizedValue : '';
|
||||
}
|
||||
|
||||
function normalizeDateField(value) {
|
||||
const normalizedField = String(value || '').trim();
|
||||
return DATE_FILTER_FIELDS[normalizedField] ? normalizedField : 'createdAt';
|
||||
}
|
||||
|
||||
function getIsoDateString(value) {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return date.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function getTaskDateValue(task, dateField) {
|
||||
if (!task || !dateField) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return task[dateField] || '';
|
||||
}
|
||||
|
||||
function taskMatchesDateFilter(task, dateFilter) {
|
||||
if (!dateFilter || (!dateFilter.from && !dateFilter.to)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const taskDate = getIsoDateString(getTaskDateValue(task, dateFilter.field));
|
||||
if (!taskDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dateFilter.from && taskDate < dateFilter.from) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dateFilter.to && taskDate > dateFilter.to) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function taskMatchesFilters(task, sourceFilter) {
|
||||
if (!sourceFilter || !sourceFilter.sourceType || !sourceFilter.sourceId) {
|
||||
return true;
|
||||
@@ -107,12 +161,14 @@ function buildQueuePageViewModel(data, message, currentUser) {
|
||||
queryState.sourceType = sourceFilter.sourceType;
|
||||
queryState.sourceId = sourceFilter.sourceId;
|
||||
const filteredTasks = tasks.filter(function (task) {
|
||||
return taskMatchesFilters(task, sourceFilter) && taskMatchesSearch(queryState.search, [
|
||||
return taskMatchesFilters(task, sourceFilter) && taskMatchesDateFilter(task, { field: 'createdAt', from: '', to: '' }) && taskMatchesSearch(queryState.search, [
|
||||
'title',
|
||||
'key',
|
||||
'category',
|
||||
'status',
|
||||
'errorMessage',
|
||||
'createdAt',
|
||||
'startedAt',
|
||||
'finishedAt',
|
||||
function (item) { return item && item.metadata && item.metadata.sourceName; },
|
||||
function (item) { return item && item.metadata && item.metadata.sourceType; },
|
||||
function (item) { return item && item.metadata && item.metadata.sourceId; }
|
||||
@@ -199,6 +255,11 @@ function buildQueuePageViewModel(data, message, currentUser) {
|
||||
summary: summary,
|
||||
statusCards: statusCards,
|
||||
activeSourceLabel: queryState.sourceType && queryState.sourceId ? `${queryState.sourceType} #${queryState.sourceId}` : '',
|
||||
search: queryState.search,
|
||||
sort: sort,
|
||||
direction: direction,
|
||||
sourceType: queryState.sourceType,
|
||||
sourceId: queryState.sourceId,
|
||||
clearFiltersUrl: QUEUE_PAGE_URL,
|
||||
hasActiveFilters: hasActiveFilters,
|
||||
pagination: pagination,
|
||||
|
||||
@@ -77,6 +77,8 @@ module.exports = function registerFontRoutes(app, deps) {
|
||||
const backgroundTaskQueue = deps.backgroundTaskQueue;
|
||||
const setAuthMessageCookie = deps.setAuthMessageCookie;
|
||||
|
||||
const FONT_FAMILY_MAX_LENGTH = 255;
|
||||
|
||||
if (!pool || !pages || !upload || !uploadDir) {
|
||||
throw new Error('registerFontRoutes requires pool, pages, upload, and uploadDir.');
|
||||
}
|
||||
@@ -106,7 +108,7 @@ module.exports = function registerFontRoutes(app, deps) {
|
||||
return res.status(400).send('No font file was uploaded.');
|
||||
}
|
||||
|
||||
const result = await fontLibrary.addFontFile(uploadDir, req.file, String(req.body && req.body.font_family || '').trim());
|
||||
const result = await fontLibrary.addFontFile(uploadDir, req.file, String(req.body && req.body.font_family || '').trim().slice(0, FONT_FAMILY_MAX_LENGTH));
|
||||
if (backgroundTaskQueue && typeof backgroundTaskQueue.enqueueTask === 'function') {
|
||||
await backgroundTaskQueue.enqueueTask({
|
||||
key: `font-sync:${result && result.font && result.font.id ? result.font.id : Date.now()}`,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// RBAC role duplicate helpers.
|
||||
|
||||
function buildDuplicateRoleName(roleName) {
|
||||
const source = String(roleName || 'Role').trim() || 'Role';
|
||||
return 'Copy of ' + source;
|
||||
}
|
||||
|
||||
function buildDuplicateRole(role, duplicateName) {
|
||||
const source = role || {};
|
||||
return {
|
||||
id: null,
|
||||
name: String(duplicateName || buildDuplicateRoleName(source.name)).trim(),
|
||||
description: String(source.description || '').trim()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildDuplicateRoleName,
|
||||
buildDuplicateRole
|
||||
};
|
||||
@@ -1,5 +1,81 @@
|
||||
// Shared RBAC form view-model helpers.
|
||||
|
||||
function normalizeSectionId(value) {
|
||||
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
||||
}
|
||||
|
||||
function buildPermissionSections(permissionGroups) {
|
||||
const sections = [];
|
||||
const sectionIndex = new Map();
|
||||
const actionDefinitions = [
|
||||
{ key: 'read', label: 'Read' },
|
||||
{ key: 'create', label: 'Create' },
|
||||
{ key: 'update', label: 'Update' },
|
||||
{ key: 'delete', label: 'Delete' },
|
||||
{ key: 'allow', label: 'Allow' }
|
||||
];
|
||||
|
||||
(Array.isArray(permissionGroups) ? permissionGroups : []).forEach(function (group) {
|
||||
const sectionTitle = String(group && group.categoryName || 'Permissions').trim() || 'Permissions';
|
||||
const sectionKey = normalizeSectionId(sectionTitle) || 'permissions';
|
||||
|
||||
if (!sectionIndex.has(sectionKey)) {
|
||||
sectionIndex.set(sectionKey, {
|
||||
id: sectionKey,
|
||||
title: sectionTitle,
|
||||
sectionOrder: Number(group && group.sectionOrder) || 999,
|
||||
groups: []
|
||||
});
|
||||
sections.push(sectionIndex.get(sectionKey));
|
||||
}
|
||||
|
||||
const section = sectionIndex.get(sectionKey);
|
||||
const groupPermissions = Array.isArray(group && group.permissions) ? group.permissions.slice() : [];
|
||||
const sectionGroup = Object.assign({}, group, {
|
||||
permissions: groupPermissions
|
||||
});
|
||||
|
||||
section.groups.push(sectionGroup);
|
||||
});
|
||||
|
||||
sections.forEach(function (section) {
|
||||
section.actions = actionDefinitions.map(function (actionDefinition) {
|
||||
return {
|
||||
key: actionDefinition.key,
|
||||
label: actionDefinition.label
|
||||
};
|
||||
});
|
||||
|
||||
section.groups.sort(function (left, right) {
|
||||
const leftOrder = Number(left.resourceOrder) || 999;
|
||||
const rightOrder = Number(right.resourceOrder) || 999;
|
||||
if (leftOrder !== rightOrder) {
|
||||
return leftOrder - rightOrder;
|
||||
}
|
||||
return String(left.title || '').localeCompare(String(right.title || ''));
|
||||
});
|
||||
|
||||
section.groups.forEach(function (group) {
|
||||
group.cells = section.actions.map(function (action) {
|
||||
return (Array.isArray(group.permissions) ? group.permissions : []).find(function (permission) {
|
||||
return String(permission.actionKey || '').trim().toLowerCase() === action.key;
|
||||
}) || null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sections.sort(function (left, right) {
|
||||
const leftOrder = Number(left.sectionOrder) || 999;
|
||||
const rightOrder = Number(right.sectionOrder) || 999;
|
||||
if (leftOrder !== rightOrder) {
|
||||
return leftOrder - rightOrder;
|
||||
}
|
||||
return String(left.title || '').localeCompare(String(right.title || ''));
|
||||
});
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
function buildRbacAddViewModel(message, currentUser, formValues, permissionGroups, users, pagination, messageVariant) {
|
||||
return {
|
||||
title: 'Create role',
|
||||
@@ -19,11 +95,12 @@ function buildRbacAddViewModel(message, currentUser, formValues, permissionGroup
|
||||
currentUser: currentUser || null,
|
||||
formValues: formValues || {},
|
||||
permissionGroups: permissionGroups || [],
|
||||
permissionSections: buildPermissionSections(permissionGroups),
|
||||
users: users || [],
|
||||
pagination: pagination || null,
|
||||
roleEditBasePath: '/rbac/new',
|
||||
scripts: ['js/rbac-permissions.js'],
|
||||
formAttrs: 'data-async-save data-async-save-close-url="/rbac" data-async-save-new-redirect="response-url"'
|
||||
formAttrs: 'data-async-save data-async-save-close-url="/rbac" data-async-save-new-redirect="response-url" data-async-save-new-url="/rbac/new"'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,10 +123,12 @@ function buildRbacEditViewModel(role, message, currentUser, permissionGroups, us
|
||||
role: role,
|
||||
inUse: Boolean(role && role.inUse),
|
||||
permissionGroups: permissionGroups || [],
|
||||
permissionSections: buildPermissionSections(permissionGroups),
|
||||
users: users || [],
|
||||
pagination: pagination || null,
|
||||
roleEditBasePath: '/rbac/' + Number(role && role.id) + '/edit',
|
||||
scripts: ['js/rbac-permissions.js']
|
||||
scripts: ['js/rbac-permissions.js'],
|
||||
formAttrs: 'data-async-save data-async-save-close-url="/rbac" data-async-save-new-url="/rbac/new"'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// User duplicate helpers.
|
||||
|
||||
function buildDuplicateUserName(name) {
|
||||
const source = String(name || 'User').trim() || 'User';
|
||||
return 'Copy of ' + source;
|
||||
}
|
||||
|
||||
function buildDuplicateUser(user, duplicateName, duplicateUsername) {
|
||||
const source = user || {};
|
||||
return {
|
||||
id: null,
|
||||
name: String(duplicateName || buildDuplicateUserName(source.name)).trim(),
|
||||
username: String(duplicateUsername || '').trim()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildDuplicateUserName,
|
||||
buildDuplicateUser
|
||||
};
|
||||
@@ -32,7 +32,7 @@ function buildUsersAddViewModel(message, currentUser, roles, formValues, message
|
||||
footerDeleteTitle: 'Delete is available after the user has been created.',
|
||||
footerShowDelete: true,
|
||||
message: message,
|
||||
messageVariant: messageVariant || 'primary',
|
||||
messageVariant: messageVariant || 'success',
|
||||
currentUser: currentUser || null,
|
||||
roles: mapRolesForForm(roles, []),
|
||||
formValues: formValues || {},
|
||||
@@ -55,11 +55,13 @@ function buildUsersEditViewModel(user, message, currentUser, roles) {
|
||||
footerDeleteConfirmMessage: 'Delete this user?',
|
||||
footerDeleteTitle: user && user.inUse ? 'Delete is disabled while this user is the only account left.' : '',
|
||||
footerShowDelete: true,
|
||||
showSaveSecondaryActions: true,
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
user: user,
|
||||
inUse: Boolean(user && user.inUse),
|
||||
roles: mapRolesForForm(roles, user && user.roleIds)
|
||||
roles: mapRolesForForm(roles, user && user.roleIds),
|
||||
formAttrs: 'data-async-save data-async-save-close-url="/users" data-async-save-new-url="/users/new"'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user