adminizer 4.5.0-build.187 → 4.5.0-build.189

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/controllers/catalog/FrontendCatalogAdapter.js +3 -3
  2. package/controllers/catalog/FrontentCatalogAdapter.js +3 -3
  3. package/controllers/history-actions/HistoryController.js +15 -7
  4. package/controllers/list.js +1 -1
  5. package/controllers/media-manager/mediaManagerAdapter.js +3 -3
  6. package/controllers/notifications/NotificationController.d.ts +3 -3
  7. package/controllers/notifications/NotificationController.js +74 -52
  8. package/helpers/diffHelpers.js +8 -8
  9. package/index.d.ts +2 -0
  10. package/index.js +2 -0
  11. package/lib/Adminizer.js +1 -1
  12. package/lib/history-actions/AbstractHistoryAdapter.js +2 -2
  13. package/lib/history-actions/DefaultHistoryAdapter.js +5 -5
  14. package/lib/inertia/inertiaAdapter.js +4 -4
  15. package/lib/media-manager/DefaultMediaManager.d.ts +1 -1
  16. package/lib/media-manager/DefaultMediaManager.js +5 -5
  17. package/lib/media-manager/Thumb.js +7 -7
  18. package/lib/media-manager/helpers/MediaManagerHelper.js +5 -5
  19. package/lib/model/adapter/sequelize.js +24 -24
  20. package/lib/notifications/AbstractNotificationService.js +13 -13
  21. package/lib/notifications/GeneralNotificationService.js +1 -1
  22. package/lib/notifications/SystemNotificationService.js +19 -19
  23. package/models/MediaManagerAssociationsAP.js +1 -1
  24. package/package.json +1 -1
  25. package/system/bindCors.js +6 -6
  26. package/system/bindModels.js +1 -1
  27. package/system/bindNotifications.js +2 -2
  28. package/system/defaults.js +9 -0
  29. package/ui/components/monaco-editor.js +1 -1
  30. package/ui/components/ui/dialog-stack.js +2 -2
  31. package/ui/lib/utils.js +4 -4
@@ -157,13 +157,13 @@ export class FrontendCatalog {
157
157
  async deleteItem(item, req) {
158
158
  if (item.id === 0)
159
159
  item.id = null;
160
- // Получаем всех непосредственных потомков текущего элемента
160
+ // Get all immediate children of the current element
161
161
  const children = await this.catalog.getChilds(item.id, undefined, req);
162
- // Рекурсивно удаляем всех потомков
162
+ // Recursively removing all children
163
163
  for (const child of children) {
164
164
  await this.deleteItem(child, req);
165
165
  }
166
- // После удаления всех потомков удаляем сам элемент
166
+ // After deleting all descendants, delete the element itself
167
167
  await this.catalog.deleteItem(item.type, item.id, req);
168
168
  return { ok: true };
169
169
  }
@@ -154,13 +154,13 @@ export class FrontendCatalog {
154
154
  async deleteItem(item, req) {
155
155
  if (item.id === 0)
156
156
  item.id = null;
157
- // Получаем всех непосредственных потомков текущего элемента
157
+ // Get all immediate children of the current element
158
158
  const children = await this.catalog.getChilds(item.id, undefined, req);
159
- // Рекурсивно удаляем всех потомков
159
+ // Recursively removing all children
160
160
  for (const child of children) {
161
161
  await this.deleteItem(child, req);
162
162
  }
163
- // После удаления всех потомков удаляем сам элемент
163
+ // After deleting all descendants, delete the element itself
164
164
  await this.catalog.deleteItem(item.type, item.id, req);
165
165
  return { ok: true };
166
166
  }
@@ -1,7 +1,8 @@
1
1
  import { Adminizer } from "../../lib/Adminizer.js";
2
2
  export class HistoryController {
3
3
  static async index(req, res) {
4
- if (!HistoryController.checkHistoryPermission(req, res))
4
+ const isUiRequest = req.method.toUpperCase() === 'GET';
5
+ if (!HistoryController.checkHistoryPermission(req, res, isUiRequest))
5
6
  return;
6
7
  const adapter = HistoryController.getAdapter(req);
7
8
  if (req.method.toUpperCase() === 'GET') {
@@ -21,7 +22,7 @@ export class HistoryController {
21
22
  const models = rawModels.map(model => {
22
23
  const normalizedModelName = model.toLowerCase();
23
24
  const configModel = normalizedModelConfig.get(normalizedModelName);
24
- const title = req.i18n.__(configModel?.title) ?? model; // если title нет использовать оригинальное имя
25
+ const title = req.i18n.__(configModel?.title) ?? model; // if there is no title, use the original name
25
26
  return {
26
27
  name: model,
27
28
  title
@@ -115,18 +116,25 @@ export class HistoryController {
115
116
  const adapter = req.adminizer.config.history?.adapter ?? 'default';
116
117
  return req.adminizer.historyHandler.get(adapter);
117
118
  }
118
- static checkHistoryPermission(req, res) {
119
+ static checkHistoryPermission(req, res, shouldRedirectToLogin = false) {
119
120
  if (!req.adminizer?.historyHandler) {
120
121
  res.status(401).json({ error: 'History system not initialized' });
121
122
  return false;
122
123
  }
123
- if (req.adminizer.config.auth.enable && !req.user) {
124
- res.status(401).json({ error: 'Unauthorized' });
125
- return false;
124
+ if (req.adminizer.config.auth.enable) {
125
+ if (!req.user) {
126
+ if (shouldRedirectToLogin) {
127
+ res.redirect(`${req.adminizer.config.routePrefix}/model/userap/login`);
128
+ }
129
+ else {
130
+ res.status(401).json({ error: 'Unauthorized' });
131
+ }
132
+ return false;
133
+ }
126
134
  }
127
135
  const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`history-${req.adminizer.config.history?.adapter ?? 'default'}`, req.user);
128
136
  if (!hasPermission) {
129
- res.status(403).json({ error: 'Forbidden' });
137
+ res.sendStatus(403);
130
138
  return false;
131
139
  }
132
140
  return true;
@@ -104,7 +104,7 @@ function setColumns(fields, orderColumn, direction, searchPairs, req) {
104
104
  title: req.i18n.__(field.config.title),
105
105
  data: String(i),
106
106
  direction: String(i) === orderColumn ? direction : undefined,
107
- searchColumnValue: searchValue || undefined, // undefined, если поиска нет
107
+ searchColumnValue: searchValue || undefined, // undefined if there is no search
108
108
  };
109
109
  nodeTreeColumns.push({
110
110
  data: String(i),
@@ -200,9 +200,9 @@ export class MediaManagerAdapter {
200
200
  checkMIMEType(allowedTypes, type) {
201
201
  const [category] = type.split("/"); // "image/jpeg" → "image"
202
202
  const wildcardType = `${category}/*`; // "image/*"
203
- // Разрешено, если:
204
- // 1. Точное совпадение (например, "image/jpeg" в allowedTypes)
205
- // 2. Разрешена вся категория (например, "image/*" в allowedTypes)
203
+ // Allowed if:
204
+ // 1. Exact match (e.g. "image/jpeg" in allowedTypes)
205
+ // 2. The entire category is allowed (for example, "image/*" in allowedTypes)
206
206
  return allowedTypes.includes(type) || allowedTypes.includes(wildcardType);
207
207
  }
208
208
  }
@@ -1,9 +1,9 @@
1
1
  export declare class NotificationController {
2
2
  static search(req: ReqType, res: ResType): Promise<void>;
3
- static viewAll(req: ReqType, res: ResType): Promise<import("express").Response<any, Record<string, any>>>;
4
- static getNotificationClasses(req: ReqType, res: ResType): Promise<ResType>;
3
+ static viewAll(req: ReqType, res: ResType): Promise<void>;
4
+ static getNotificationClasses(req: ReqType, res: ResType): Promise<void>;
5
5
  static getNotificationsStream(req: ReqType, res: ResType): Promise<void>;
6
- static getNotificationsByClass(req: ReqType, res: ResType): Promise<ResType>;
6
+ static getNotificationsByClass(req: ReqType, res: ResType): Promise<void>;
7
7
  static getUserNotifications(req: ReqType, res: ResType): Promise<void>;
8
8
  static markAsRead(req: ReqType, res: ResType): Promise<void>;
9
9
  static markAllAsRead(req: ReqType, res: ResType): Promise<void>;
@@ -1,7 +1,8 @@
1
1
  import { Adminizer } from '../../lib/Adminizer.js';
2
2
  export class NotificationController {
3
3
  static async search(req, res) {
4
- NotificationController.checkNotifPermission(req, res);
4
+ if (!NotificationController.checkNotifPermission(req, res))
5
+ return;
5
6
  if (req.method.toUpperCase() === 'POST') {
6
7
  const { s, notificationClass } = req.body;
7
8
  const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
@@ -14,7 +15,9 @@ export class NotificationController {
14
15
  }
15
16
  }
16
17
  static async viewAll(req, res) {
17
- NotificationController.checkNotifPermission(req, res);
18
+ const isUiRequest = req.method.toUpperCase() === 'GET';
19
+ if (!NotificationController.checkNotifPermission(req, res, isUiRequest))
20
+ return;
18
21
  if (req.method.toUpperCase() === 'POST') {
19
22
  const messages = {
20
23
  "Make read": "",
@@ -27,82 +30,89 @@ export class NotificationController {
27
30
  "Diff": "",
28
31
  "The end of the list has been reached": "",
29
32
  };
30
- return res.json(Object.fromEntries(Object.keys(messages).map(key => [key, req.i18n.__(key)])));
33
+ res.json(Object.fromEntries(Object.keys(messages).map(key => [key, req.i18n.__(key)])));
34
+ return;
31
35
  }
32
36
  if (req.method.toUpperCase() === 'GET') {
33
- return req.Inertia.render({
37
+ req.Inertia.render({
34
38
  component: 'notification',
35
39
  props: {
36
40
  title: req.i18n.__('Notifications'),
37
41
  }
38
42
  });
43
+ return;
39
44
  }
40
- return res.status(405);
45
+ res.status(405).end();
41
46
  }
42
47
  static async getNotificationClasses(req, res) {
43
- NotificationController.checkNotifPermission(req, res);
44
- if (req.adminizer.config.notifications.enabled === false)
45
- return res.json([]);
48
+ if (!NotificationController.checkNotifPermission(req, res))
49
+ return;
50
+ if (req.adminizer.config.notifications.enabled === false) {
51
+ Adminizer.log.warn('[Notifications] Notifications disabled in config');
52
+ res.json([]);
53
+ return;
54
+ }
46
55
  const services = req.adminizer.notificationHandler.getAllServices();
47
56
  let activeServices = [];
48
57
  for (const service of services) {
49
- // Получаем только клиентов текущего пользователя
50
- const userClients = service.getUserClients(req.user.id);
51
- if (userClients.size > 0) {
58
+ const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${service.notificationClass}`, req.user);
59
+ if (hasPermission) {
52
60
  activeServices.push({
53
61
  displayName: req.i18n.__(service.displayName),
54
62
  notificationClass: service.notificationClass,
55
63
  });
56
64
  }
57
65
  }
58
- return res.json({
66
+ res.json({
59
67
  activeServices: activeServices,
60
68
  initTab: req.adminizer.config?.notifications?.initTab || null
61
69
  });
62
70
  }
63
- // Единый SSE endpoint для всех уведомлений
64
71
  static async getNotificationsStream(req, res) {
65
- NotificationController.checkNotifPermission(req, res);
66
- // Устанавливаем заголовки для SSE
72
+ if (!NotificationController.checkNotifPermission(req, res))
73
+ return;
67
74
  res.setHeader('Content-Type', 'text/event-stream');
68
- res.setHeader('Cache-Control', 'no-cache');
75
+ res.setHeader('Cache-Control', 'no-cache, no-transform');
69
76
  res.setHeader('Connection', 'keep-alive');
77
+ res.setHeader('X-Accel-Buffering', 'no');
78
+ res.setHeader('Content-Encoding', 'identity');
70
79
  res.setHeader('Access-Control-Allow-Origin', '*');
71
80
  res.flushHeaders();
81
+ res.write(': stream-open\n\n');
72
82
  const clientId = `user-${req.user?.id}-${Date.now()}`;
73
- // Функция для отправки событий клиенту
83
+ // Function for sending events to the client
74
84
  const sendEvent = (event) => {
75
- // Фильтруем уведомления по правам пользователя
85
+ // Filtering notifications by user rights
76
86
  if (event.type === 'notification') {
77
87
  const notificationClass = event.notificationClass;
78
- // ЕДИНАЯ проверка прав через AccessRightsHelper
88
+ // UNIFIED rights check via AccessRightsHelper
79
89
  const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
80
90
  if (!hasPermission) {
81
- return; // Пользователь не имеет прав на этот класс уведомлений
91
+ return; // The user does not have rights to this notification class
82
92
  }
83
- // Проверка персональных уведомлений (только для целевого пользователя)
93
+ // Checking personal notifications (target user only)
84
94
  if (event.userId !== null && event.userId !== req.user.id) {
85
95
  return;
86
96
  }
87
97
  }
88
98
  res.write(`event: ${event.type}\n`);
89
99
  res.write(`data: ${JSON.stringify(event.data)}\n\n`);
100
+ res.flush?.();
90
101
  };
91
- // Подключаем клиента ко всем сервисам
102
+ // We connect the client to all services
92
103
  const services = req.adminizer.notificationHandler.getAllServices();
93
104
  const allowedServices = services.filter(service => req.adminizer.accessRightsHelper.hasPermission(`notification-${service.notificationClass}`, req.user));
94
105
  allowedServices.forEach(service => {
95
106
  service.addClient(clientId, sendEvent, req.user);
96
- // Для системного сервиса добавляем клиента в CRUD каналы
107
+ // For a system service, add a client to CRUD channels
97
108
  // if (service.notificationClass === 'system') {
98
109
  // const systemService = service as SystemNotificationService;
99
- // // Добавляем клиента в основные CRUD каналы с указанием userId
110
+ // // Add the client to the main CRUD channels indicating userId
100
111
  // ['created', 'updated', 'deleted', 'system'].forEach(channel => {
101
112
  // systemService.addClientToChannel(clientId, channel, req.user.id);
102
113
  // });
103
114
  // }
104
115
  });
105
- // Отправляем приветственное сообщение
106
116
  sendEvent({
107
117
  type: 'connected',
108
118
  data: {
@@ -110,12 +120,12 @@ export class NotificationController {
110
120
  clientId: clientId
111
121
  }
112
122
  });
113
- // Обработка закрытия соединения
123
+ // Handling connection closure
114
124
  req.on('close', () => {
115
- // Отключаем клиента от всех сервисов
125
+ // Disconnecting the client from all services
116
126
  services.forEach(service => {
117
127
  service.removeClient(clientId);
118
- // Для системного сервиса удаляем из всех каналов
128
+ // For system service, remove from all channels
119
129
  // if (service.notificationClass === 'system') {
120
130
  // const systemService = service as SystemNotificationService;
121
131
  // if (systemService.removeClientFromAllChannels) {
@@ -125,12 +135,11 @@ export class NotificationController {
125
135
  });
126
136
  res.end();
127
137
  });
128
- // Heartbeat для поддержания соединения
138
+ // Heartbeat to keep you connected
129
139
  const heartbeatInterval = setInterval(() => {
130
140
  if (!res.writableEnded) {
131
- services.forEach(service => {
132
- service.sendHeartbeat(clientId);
133
- });
141
+ res.write(': keepalive\n\n');
142
+ res.flush?.();
134
143
  }
135
144
  else {
136
145
  clearInterval(heartbeatInterval);
@@ -140,34 +149,39 @@ export class NotificationController {
140
149
  clearInterval(heartbeatInterval);
141
150
  });
142
151
  }
143
- // API для получения уведомлений по классу
152
+ // API for receiving class notifications
144
153
  static async getNotificationsByClass(req, res) {
145
- NotificationController.checkNotifPermission(req, res);
154
+ if (!NotificationController.checkNotifPermission(req, res))
155
+ return;
146
156
  try {
147
157
  const { notificationClass } = req.params;
148
158
  const { limit = 20, skip = 0, unreadOnly = false } = req.query;
149
- // Проверяем права доступа
159
+ // Checking access rights
150
160
  const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
151
161
  if (!hasPermission) {
152
- return res.status(403).json({ error: 'Forbidden' });
162
+ res.status(403).json({ error: 'Forbidden' });
163
+ return;
153
164
  }
154
165
  const service = req.adminizer.notificationHandler.getService(notificationClass);
155
- if (!service)
156
- return res.json({});
166
+ if (!service) {
167
+ res.json({});
168
+ return;
169
+ }
157
170
  const notifications = await service.getNotifications(req.user?.id, Number(limit), Number(skip), unreadOnly === 'true');
158
- return res.json(notifications);
171
+ res.json(notifications);
159
172
  }
160
173
  catch (error) {
161
174
  Adminizer.log.error('Error getting notifications:', error);
162
- return res.status(500).json({ error: 'Internal server error' });
175
+ res.status(500).json({ error: 'Internal server error' });
163
176
  }
164
177
  }
165
- // API для получения всех уведомлений пользователя
178
+ // API for receiving all user notifications
166
179
  static async getUserNotifications(req, res) {
167
- NotificationController.checkNotifPermission(req, res);
180
+ if (!NotificationController.checkNotifPermission(req, res))
181
+ return;
168
182
  const { limit = 4, skip = 0, unreadOnly = false } = req.query;
169
183
  try {
170
- // Фильтруем сервисы по правам доступа
184
+ // Filtering services by access rights
171
185
  const services = req.adminizer.notificationHandler.getAllServices();
172
186
  const allowedServices = services.filter(service => req.adminizer.accessRightsHelper.hasPermission(`notification-${service.notificationClass}`, req.user));
173
187
  const allNotifications = [];
@@ -175,7 +189,7 @@ export class NotificationController {
175
189
  const notifications = await service.getNotifications(req.user.id, Number(limit), Number(skip), unreadOnly === 'true');
176
190
  allNotifications.push(...notifications);
177
191
  }
178
- // Сортируем по дате создания
192
+ // Sort by creation date
179
193
  const sortedNotifications = allNotifications.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, Number(limit));
180
194
  res.json(sortedNotifications);
181
195
  }
@@ -184,9 +198,10 @@ export class NotificationController {
184
198
  res.status(500).json({ error: 'Internal server error' });
185
199
  }
186
200
  }
187
- // API для пометки как прочитанного
201
+ // API for marking as read
188
202
  static async markAsRead(req, res) {
189
- NotificationController.checkNotifPermission(req, res);
203
+ if (!NotificationController.checkNotifPermission(req, res))
204
+ return;
190
205
  try {
191
206
  const { notificationClass, id } = req.params;
192
207
  const service = req.adminizer.notificationHandler.getService(notificationClass);
@@ -199,7 +214,8 @@ export class NotificationController {
199
214
  }
200
215
  }
201
216
  static async markAllAsRead(req, res) {
202
- NotificationController.checkNotifPermission(req, res);
217
+ if (!NotificationController.checkNotifPermission(req, res))
218
+ return;
203
219
  try {
204
220
  const services = req.adminizer.notificationHandler.getAllServices();
205
221
  for (const service of services) {
@@ -212,15 +228,21 @@ export class NotificationController {
212
228
  res.status(500).json({ error: 'Internal server error' });
213
229
  }
214
230
  }
215
- static checkNotifPermission(req, res) {
231
+ static checkNotifPermission(req, res, shouldRedirectToLogin = false) {
216
232
  if (!req.adminizer?.notificationHandler) {
217
233
  res.status(500).json({ error: 'Notification system not initialized' });
218
- return;
234
+ return false;
219
235
  }
220
- // // Проверяем аутентификацию
236
+ // We use the redirect only for UI pages; API requests receive 401.
221
237
  if (req.adminizer.config.auth.enable && !req.user) {
222
- res.status(401).json({ error: 'Unauthorized' });
223
- return;
238
+ if (shouldRedirectToLogin) {
239
+ res.redirect(`${req.adminizer.config.routePrefix}/model/userap/login`);
240
+ }
241
+ else {
242
+ res.status(401).json({ error: 'Unauthorized' });
243
+ }
244
+ return false;
224
245
  }
246
+ return true;
225
247
  }
226
248
  }
@@ -2,11 +2,11 @@ export function sanitizeForDiff(data) {
2
2
  if (!data)
3
3
  return {};
4
4
  const result = { ...data };
5
- // Удаляем системные поля
5
+ // Removing system fields
6
6
  const systemFields = ['id', 'createdAt', 'updatedAt', 'deletedAt', '__v', '_id'];
7
7
  systemFields.forEach(field => delete result[field]);
8
- // Очищаем чувствительные данные
9
- // TODO: Вынести в переменные окружения или в конфиг
8
+ // Cleaning sensitive data
9
+ // TODO: Place in environment variables or config
10
10
  const sensitiveFields = ['password', 'token', 'secret', 'apiKey', 'creditCard'];
11
11
  sensitiveFields.forEach(field => {
12
12
  if (result[field] !== undefined) {
@@ -17,7 +17,7 @@ export function sanitizeForDiff(data) {
17
17
  }
18
18
  export function formatChanges(diffObj, oldData, newData, operation) {
19
19
  const changes = [];
20
- // Специальная обработка для операции добавления
20
+ // Special handling for append operation
21
21
  if (operation === 'add') {
22
22
  for (const [key, value] of Object.entries(newData)) {
23
23
  if (value === '***HIDDEN***')
@@ -32,9 +32,9 @@ export function formatChanges(diffObj, oldData, newData, operation) {
32
32
  }
33
33
  return changes;
34
34
  }
35
- // Стандартная обработка для remove и update
35
+ // Standard handling for remove and update
36
36
  for (const [key, value] of Object.entries(diffObj)) {
37
- // Для добавленных полей (когда oldData[key] undefined)
37
+ // For added fields (when oldData[key] is undefined)
38
38
  if (oldData[key] === undefined && newData[key] !== undefined) {
39
39
  changes.push({
40
40
  field: key,
@@ -44,7 +44,7 @@ export function formatChanges(diffObj, oldData, newData, operation) {
44
44
  operation: 'add'
45
45
  });
46
46
  }
47
- // Для удаленных полей (когда newData[key] undefined)
47
+ // For removed fields (when newData[key] is undefined)
48
48
  else if (newData[key] === undefined && oldData[key] !== undefined) {
49
49
  changes.push({
50
50
  field: key,
@@ -54,7 +54,7 @@ export function formatChanges(diffObj, oldData, newData, operation) {
54
54
  operation: 'remove'
55
55
  });
56
56
  }
57
- // Для измененных полей
57
+ // For changed fields
58
58
  else {
59
59
  changes.push({
60
60
  field: key,
package/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export * from "./lib/model/AbstractModel";
19
19
  export * from "./lib/model/adapter/waterline";
20
20
  export * from "./lib/model/adapter/sequelize";
21
21
  export * from "./lib/media-manager/AbstractMediaManager";
22
+ export * from "./lib/controls/AbstractControls";
23
+ export * from "./lib/controls/ControlsHandler";
22
24
  export * from "./lib/Adminizer";
23
25
  export * from "./models/GroupAP";
24
26
  export * from "./models/UserAP";
package/index.js CHANGED
@@ -19,6 +19,8 @@ export * from "./lib/model/AbstractModel.js";
19
19
  export * from "./lib/model/adapter/waterline.js";
20
20
  export * from "./lib/model/adapter/sequelize.js";
21
21
  export * from "./lib/media-manager/AbstractMediaManager.js";
22
+ export * from "./lib/controls/AbstractControls.js";
23
+ export * from "./lib/controls/ControlsHandler.js";
22
24
  export * from "./lib/Adminizer.js";
23
25
  export * from "./models/GroupAP.js";
24
26
  export * from "./models/UserAP.js";
package/lib/Adminizer.js CHANGED
@@ -226,7 +226,7 @@ export class Adminizer {
226
226
  */
227
227
  this._emitter.emit('adminizer:loaded');
228
228
  }
229
- // Хелпер для отправки уведомлений
229
+ // Helper for sending notifications
230
230
  async sendNotification(notification) {
231
231
  if (this.config.notifications.enabled) {
232
232
  const notificationClass = notification.notificationClass || 'general';
@@ -144,12 +144,12 @@ export class AbstractHistoryAdapter {
144
144
  accessHistory.push(historyRecord);
145
145
  }
146
146
  }
147
- // Группируем записи по модели для оптимизации
147
+ // Grouping records by model for optimization
148
148
  const fieldsCache = new Map();
149
149
  for (const historyRecord of accessHistory) {
150
150
  const entity = this.findEntityObject(historyRecord);
151
151
  const modelKey = historyRecord.modelName;
152
- // Используем кэш, чтобы не создавать DataAccessor для каждой записи
152
+ // We use a cache so as not to create a DataAccessor for each record
153
153
  if (!fieldsCache.has(modelKey)) {
154
154
  const dataAccessor = new DataAccessor(this.adminizer, user, entity, "edit");
155
155
  let fields = dataAccessor.getFieldsConfig();
@@ -29,9 +29,9 @@ export class DefaultHistoryAdapter extends AbstractHistoryAdapter {
29
29
  let totalFetched = 0;
30
30
  let resultItems = [];
31
31
  let currentSkip = skip;
32
- // Дозагружаем пока не наберем нужное количество
32
+ // Add more until you get the required amount
33
33
  while (resultItems.length < limit) {
34
- // Запрашиваем с запасом, чтобы уменьшить количество запросов к БД
34
+ // We request with a reserve to reduce the number of requests to the database
35
35
  const fetchLimit = Math.min(limit * 2, 50);
36
36
  const history = await this.adminizer.modelHandler.model.get(this.model)["_find"]({
37
37
  where: query,
@@ -40,10 +40,10 @@ export class DefaultHistoryAdapter extends AbstractHistoryAdapter {
40
40
  skip: currentSkip
41
41
  });
42
42
  if (history.length === 0) {
43
- break; // Больше нет данных
43
+ break; // No more data
44
44
  }
45
45
  const filteredHistory = await this._getAllHistory(history, user);
46
- // Добавляем отфильтрованные записи к результату
46
+ // Add filtered records to the result
47
47
  for (const item of filteredHistory) {
48
48
  if (resultItems.length < limit) {
49
49
  resultItems.push(item);
@@ -51,7 +51,7 @@ export class DefaultHistoryAdapter extends AbstractHistoryAdapter {
51
51
  }
52
52
  totalFetched += history.length;
53
53
  currentSkip += history.length;
54
- // Если получили меньше чем запросили, значит в БД кончились данные
54
+ // If you receive less than what you requested, it means the database has run out of data
55
55
  if (history.length < fetchLimit) {
56
56
  break;
57
57
  }
@@ -19,7 +19,7 @@ const inertiaExpressAdapter = function ({ version, html, flashMessages, enableRe
19
19
  secure: process.env.NODE_ENV === 'production' && process.env.CSRF_COOKIE_INSECURE !== '1',
20
20
  sameSite: 'lax',
21
21
  });
22
- // Проверяем CSRF только для не-GET запросов И не-API routes
22
+ // Checking CSRF only for non-GET requests AND non-API routes
23
23
  if (!['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
24
24
  const csrfCookie = req.cookies['XSRF-TOKEN'];
25
25
  const csrfHeader = req.headers[csrf.headerName || 'x-xsrf-token'];
@@ -134,7 +134,7 @@ const inertiaExpressAdapter = function ({ version, html, flashMessages, enableRe
134
134
  return next();
135
135
  };
136
136
  };
137
- // Вспомогательная функция для определения API routes
137
+ // Helper function for defining API routes
138
138
  function isApiRequest(req) {
139
139
  const adminizer = req.adminizer;
140
140
  if (!adminizer?.config?.cors?.enabled) {
@@ -142,9 +142,9 @@ function isApiRequest(req) {
142
142
  }
143
143
  const corsConfig = adminizer.config.cors;
144
144
  const routePrefix = adminizer.config.routePrefix || '';
145
- // Создаем базовый путь для API
145
+ // Create a base path for the API
146
146
  const apiBasePath = `${routePrefix}/${corsConfig.path?.replace('*', '')}`;
147
- // Проверяем начинается ли путь с API base path
147
+ // Checking if the path starts with API base path
148
148
  return req.path.startsWith(apiBasePath);
149
149
  }
150
150
  export default inertiaExpressAdapter;
@@ -12,7 +12,7 @@ export declare class DefaultMediaManager extends AbstractMediaManager {
12
12
  next: boolean;
13
13
  }>;
14
14
  searchAll(s: string, group: string): Promise<MediaManagerItem[]>;
15
- setRelations(data: MediaManagerWidgetData[], model: string, modelId: string | number, //Обновлено
15
+ setRelations(data: MediaManagerWidgetData[], model: string, modelId: string | number, //Updated
16
16
  widgetName: string): Promise<void>;
17
17
  getRelations(model: string, widgetName: string, modelId: string | number): Promise<MediaManagerWidgetClientItem[]>;
18
18
  }
@@ -55,12 +55,12 @@ export class DefaultMediaManager extends AbstractMediaManager {
55
55
  }
56
56
  return data;
57
57
  }
58
- async setRelations(data, model, modelId, //Обновлено
58
+ async setRelations(data, model, modelId, //Updated
59
59
  widgetName) {
60
60
  if (modelId == null) {
61
61
  throw new Error("modelId must be a string or number");
62
62
  }
63
- const modelIdStr = String(modelId); //Нормализуем к строке
63
+ const modelIdStr = String(modelId); //Normalize to string
64
64
  let modelAssociations = await this.adminizer.modelHandler.model.get(this.modelAssoc)["_find"]({
65
65
  where: {
66
66
  modelId: modelIdStr,
@@ -79,7 +79,7 @@ export class DefaultMediaManager extends AbstractMediaManager {
79
79
  await this.adminizer.modelHandler.model.get(this.modelAssoc)["_create"]({
80
80
  mediaManagerId: this.id,
81
81
  model: model.toLowerCase(),
82
- modelId: modelIdStr, //Сохраняем как строку
82
+ modelId: modelIdStr, //Save as a string
83
83
  [fieldName]: widgetItem.id,
84
84
  widgetName: widgetName,
85
85
  sortOrder: key + 1,
@@ -90,14 +90,14 @@ export class DefaultMediaManager extends AbstractMediaManager {
90
90
  if (modelId == null) {
91
91
  throw new Error("modelId must be a string or number");
92
92
  }
93
- const modelIdStr = String(modelId); //Нормализуем к строке
93
+ const modelIdStr = String(modelId); //Normalize to string
94
94
  let widgetItems = [];
95
95
  const fieldName = this.adminizer.ormAdapters[0].ormType === 'sequelize' ? 'fileRef' : 'file';
96
96
  let files = await this.adminizer.modelHandler.model.get(this.modelAssoc)['_find']({
97
97
  where: {
98
98
  model: model.toLowerCase(),
99
99
  widgetName: widgetName,
100
- modelId: modelIdStr, //Поиск по строке
100
+ modelId: modelIdStr, //Search by string
101
101
  },
102
102
  sort: "sortOrder ASC"
103
103
  }, { populate: [[fieldName, {}]] });