adminizer 4.3.0-build.80 → 4.3.0-build.82

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 (199) hide show
  1. package/assets/add-CZdDpNV2.js +1 -0
  2. package/assets/add-form-BjXOLDij.js +1 -0
  3. package/assets/add-group-form-BNmpvW8K.js +1 -0
  4. package/assets/add-group-urhA47Qf.js +1 -0
  5. package/assets/add-user-CzWUu7wG.js +1 -0
  6. package/assets/{add-user-form-yp020h4r.js → add-user-form-DrclnTOd.js} +1 -1
  7. package/assets/{app-CpGtjhxF.css → app-CSVBI-Lw.css} +1 -1
  8. package/assets/app-layout-CbyH1MDK.js +1 -0
  9. package/assets/{app-shell-xEVbINZV.js → app-shell-CHEYhVco.js} +1 -1
  10. package/assets/{app-sidebar-4lm9f_CG.js → app-sidebar-DeF8WNmW.js} +1 -1
  11. package/assets/app-sidebar-header-8KWnVpkN.js +1 -0
  12. package/assets/app.js +1628 -1628
  13. package/assets/breadcrumbs-C439DWrC.js +1 -0
  14. package/assets/catalog-CCSagWKW.js +6 -0
  15. package/assets/dashboard-DUNXib6b.js +3 -0
  16. package/assets/date-picker-DbJ8nHp5.js +1 -0
  17. package/assets/del-modal-_b3CSNHb.js +1 -0
  18. package/assets/{dynamic-controls-PxBg8yMz.js → dynamic-controls-BOYmRX_z.js} +1 -1
  19. package/assets/{field-renderer-CsBa_tMB.css → field-renderer-CRNLShJD.css} +1 -1
  20. package/assets/field-renderer-D0crIeQS.js +407 -0
  21. package/assets/{form-CSTeMDf5.js → form-B6LFaTgS.js} +1 -1
  22. package/assets/form-C8pRhEOA.js +1 -0
  23. package/assets/geo-json-uSUIgJf5.js +4 -0
  24. package/assets/{handsontable-D6MvFmcM.js → handsontable-PVakPtJV.js} +55 -55
  25. package/assets/home-D--JrFFj.js +2 -0
  26. package/assets/icon-DM49pZW3.js +1 -0
  27. package/assets/{init-user-L-oxFTOv.js → init-user-DNrB8Xt5.js} +1 -1
  28. package/assets/input-error--IOnWghB.js +1 -0
  29. package/assets/jsoneditor-D95dIAP2.js +3878 -0
  30. package/assets/list-CQivvagG.js +1 -0
  31. package/assets/{list-table-vIvydJV2.js → list-table-h_zXp43g.js} +4 -4
  32. package/assets/{login-DLMJ6l_r.js → login-6Xb8bcPq.js} +1 -1
  33. package/assets/manifest.json +259 -260
  34. package/assets/material-icon-BedgD2NE.js +1 -0
  35. package/assets/{media-manager-ASGJ4PcS.js → media-manager-Bms57WSN.js} +2 -2
  36. package/assets/module-DqrIORMW.js +1 -0
  37. package/assets/{monaco-B1uVVM7f.js → monaco-ICtx6xoc.js} +1 -1
  38. package/assets/{nav-footer-DbGCtmmN.js → nav-footer-BwkQkwow.js} +1 -1
  39. package/assets/{nav-main-BcWhl71g.js → nav-main-9Eb_A0rf.js} +1 -1
  40. package/assets/nav-user-CRAfyE-x.js +1 -0
  41. package/assets/notification-db0RD9I6.js +27 -0
  42. package/assets/{pagination-render-BceTnewI.js → pagination-render-D3UyVAlr.js} +1 -1
  43. package/assets/{register-C_W_hOmq.js → register-BIyjCy8M.js} +1 -1
  44. package/assets/tabs-BK_5wdPA.js +1 -0
  45. package/assets/theme-switcher-CKiJbMkh.js +1 -0
  46. package/assets/toast-editor-BZM5RHph.js +845 -0
  47. package/assets/{user-info-BrWerGLc.js → user-info-CcYdmjMZ.js} +1 -1
  48. package/assets/{user-menu-content-D61_auxX.js → user-menu-content-DbUk8ZeY.js} +1 -1
  49. package/assets/vanilla-picker-l5rcX3cq.js +8 -0
  50. package/assets/{vendor-DoC2WAmd.js → vendor-wzZzN_Jv.js} +2 -2
  51. package/assets/welcome-CZlWN4gM.js +1 -0
  52. package/controllers/add.js +1 -1
  53. package/controllers/catalog/FrontentCatalogAdapter.d.ts +4 -4
  54. package/controllers/edit.js +1 -15
  55. package/controllers/list.js +2 -2
  56. package/controllers/login.js +2 -2
  57. package/controllers/navigation/edit.js +1 -1
  58. package/controllers/notifications/NotificationController.d.ts +5 -5
  59. package/controllers/notifications/NotificationController.js +77 -78
  60. package/controllers/remove.js +1 -11
  61. package/controllers/view.js +1 -1
  62. package/helpers/configHelper.d.ts +1 -1
  63. package/helpers/fieldsHelper.d.ts +1 -1
  64. package/helpers/fieldsHelper.js +1 -1
  65. package/helpers/inertiaAddHelper.d.ts +1 -1
  66. package/helpers/translationHelper.d.ts +1 -1
  67. package/helpers/translationHelper.js +1 -1
  68. package/index.d.ts +14 -13
  69. package/index.js +14 -13
  70. package/interfaces/adminpanelConfig.d.ts +1 -1
  71. package/interfaces/global.d.ts +3 -3
  72. package/interfaces/types.d.ts +10 -1
  73. package/lib/Adminizer.d.ts +7 -7
  74. package/lib/Adminizer.js +7 -7
  75. package/lib/{v4/DataAccessor.d.ts → DataAccessor.d.ts} +4 -4
  76. package/lib/{v4/DataAccessor.js → DataAccessor.js} +3 -3
  77. package/lib/{v4/I18n.js → I18n.js} +2 -2
  78. package/lib/{v4/PolicyManager.d.ts → PolicyManager.d.ts} +1 -1
  79. package/lib/{v4/PolicyManager.js → PolicyManager.js} +2 -2
  80. package/lib/{v4/catalog → catalog}/AbstractCatalog.d.ts +1 -1
  81. package/lib/{v4/catalog → catalog}/Navigation.d.ts +2 -2
  82. package/lib/{v4/catalog → catalog}/Navigation.js +1 -1
  83. package/lib/{v4/controls → controls}/AbstractControls.d.ts +1 -1
  84. package/lib/{v4/controls → controls}/codeEditor/MonacoEditor.d.ts +1 -1
  85. package/lib/{v4/controls → controls}/geojsoneditor/GeoEditor.d.ts +1 -1
  86. package/lib/{v4/controls → controls}/jsoneditor/JsonEditor.d.ts +1 -1
  87. package/lib/{v4/controls → controls}/markdown/ToastUiEditor.d.ts +1 -1
  88. package/lib/{v4/controls → controls}/table/Handsontable.d.ts +1 -1
  89. package/lib/{v4/controls → controls}/wysiwyg/CKeditor.d.ts +1 -1
  90. package/lib/{v4/datatable → datatable}/NodeTable.d.ts +1 -1
  91. package/lib/{v4/model → model}/AbstractModel.d.ts +12 -0
  92. package/lib/model/AbstractModel.js +178 -0
  93. package/lib/{v4/model → model}/ModelHandler.js +1 -1
  94. package/lib/{v4/model → model}/adapter/sequelize.d.ts +2 -4
  95. package/lib/{v4/model → model}/adapter/sequelize.js +33 -13
  96. package/lib/{v4/notifications → notifications}/AbstractNotificationService.d.ts +14 -7
  97. package/lib/notifications/AbstractNotificationService.js +233 -0
  98. package/lib/{v4/notifications → notifications}/GeneralNotificationService.d.ts +1 -1
  99. package/lib/{v4/notifications → notifications}/GeneralNotificationService.js +4 -2
  100. package/lib/notifications/NotificationHandler.d.ts +27 -0
  101. package/lib/notifications/NotificationHandler.js +41 -0
  102. package/lib/{v4/notifications → notifications}/SystemNotificationService.d.ts +5 -4
  103. package/lib/notifications/SystemNotificationService.js +173 -0
  104. package/lib/{v4/widgets → widgets}/abstractCustom.d.ts +1 -1
  105. package/lib/{v4/widgets → widgets}/abstractLink.d.ts +1 -1
  106. package/lib/{v4/widgets → widgets}/abstractWidgetBase.d.ts +1 -1
  107. package/lib/{v4/widgets → widgets}/widgetHandler.d.ts +2 -2
  108. package/migrations/index.d.ts +3 -0
  109. package/migrations/index.js +4 -0
  110. package/migrations/umzug/0001.d.ts +7 -0
  111. package/migrations/umzug/0001.js +119 -0
  112. package/migrations/umzug.d.ts +2 -0
  113. package/migrations/umzug.js +8 -0
  114. package/models/NotificationAP.d.ts +8 -0
  115. package/models/NotificationAP.js +8 -0
  116. package/models/UserAP.d.ts +1 -1
  117. package/package.json +5 -4
  118. package/system/Router.js +4 -8
  119. package/system/bindControls.js +6 -6
  120. package/system/bindInertia.js +4 -3
  121. package/system/bindNavigation.js +1 -1
  122. package/system/bindNotifications.js +3 -3
  123. package/system/bindReqFunctions.js +8 -25
  124. package/system/defaults.js +1 -1
  125. package/ui/components/ui/table.d.ts +1 -1
  126. package/ui/components/ui/table.js +2 -2
  127. package/assets/add-DSapOiv0.js +0 -1
  128. package/assets/add-form-C04ZI5LT.js +0 -1
  129. package/assets/add-group-DWR7SBTO.js +0 -1
  130. package/assets/add-group-form-CyRwxTs3.js +0 -1
  131. package/assets/add-user-acATGxUL.js +0 -1
  132. package/assets/app-layout-BmT5KQwc.js +0 -1
  133. package/assets/app-sidebar-header-CxXmzszq.js +0 -1
  134. package/assets/breadcrumbs-livOfa2Z.js +0 -1
  135. package/assets/catalog-CG_EB2kz.js +0 -6
  136. package/assets/dashboard-CsmJfdLb.js +0 -3
  137. package/assets/date-picker-By3kj-Rx.js +0 -1
  138. package/assets/del-modal-DlcbcdR5.js +0 -1
  139. package/assets/field-renderer-Bp1lfm0v.js +0 -407
  140. package/assets/form-CxHRGp7p.js +0 -1
  141. package/assets/geo-json-CCgjEQfu.js +0 -4
  142. package/assets/home-D00XjLjD.js +0 -2
  143. package/assets/icon-ButfCBrt.js +0 -1
  144. package/assets/input-error-CHsDLcK0.js +0 -1
  145. package/assets/jsoneditor-CBqATVR0.js +0 -3833
  146. package/assets/list-4TOTsjqE.js +0 -1
  147. package/assets/material-icon-sXk4BK-N.js +0 -1
  148. package/assets/module-mxngd2cA.js +0 -1
  149. package/assets/nav-user-BN2veV3l.js +0 -1
  150. package/assets/notification-BEfA5sT6.js +0 -1
  151. package/assets/tabs-CgZW_LLn.js +0 -1
  152. package/assets/theme-switcher-D1G6OSMc.js +0 -1
  153. package/assets/toast-editor-CfgZXoQZ.js +0 -845
  154. package/assets/vanilla-picker-B6E6ObS_.js +0 -8
  155. package/assets/welcome-FHRCdke0.js +0 -1
  156. package/lib/v4/model/AbstractModel.js +0 -79
  157. package/lib/v4/notifications/AbstractNotificationService.js +0 -150
  158. package/lib/v4/notifications/NotificationHandler.d.ts +0 -69
  159. package/lib/v4/notifications/NotificationHandler.js +0 -127
  160. package/lib/v4/notifications/SystemNotificationService.js +0 -124
  161. package/migrations/db-migrate/20230303171134-adminpanel-init.js +0 -122
  162. package/migrations/db-migrate/20231223215835-add-widgets.js +0 -32
  163. package/migrations/db-migrate/20240804191001-added-navigationap.js +0 -53
  164. package/migrations/db-migrate/20241121184236-v4-init.js +0 -134
  165. /package/lib/{v4/I18n.d.ts → I18n.d.ts} +0 -0
  166. /package/lib/{v4/POWCaptcha.d.ts → POWCaptcha.d.ts} +0 -0
  167. /package/lib/{v4/POWCaptcha.js → POWCaptcha.js} +0 -0
  168. /package/lib/{v4/catalog → catalog}/AbstractCatalog.js +0 -0
  169. /package/lib/{v4/catalog → catalog}/CatalogHandler.d.ts +0 -0
  170. /package/lib/{v4/catalog → catalog}/CatalogHandler.js +0 -0
  171. /package/lib/{v4/controls → controls}/AbstractControls.js +0 -0
  172. /package/lib/{v4/controls → controls}/ControlsHandler.d.ts +0 -0
  173. /package/lib/{v4/controls → controls}/ControlsHandler.js +0 -0
  174. /package/lib/{v4/controls → controls}/codeEditor/MonacoEditor.js +0 -0
  175. /package/lib/{v4/controls → controls}/geojsoneditor/GeoEditor.js +0 -0
  176. /package/lib/{v4/controls → controls}/jsoneditor/JsonEditor.js +0 -0
  177. /package/lib/{v4/controls → controls}/markdown/ToastUiEditor.js +0 -0
  178. /package/lib/{v4/controls → controls}/table/Handsontable.js +0 -0
  179. /package/lib/{v4/controls → controls}/wysiwyg/CKeditor.js +0 -0
  180. /package/lib/{v4/datatable → datatable}/NodeTable.js +0 -0
  181. /package/lib/{v4/helper → helper}/jwt.d.ts +0 -0
  182. /package/lib/{v4/helper → helper}/jwt.js +0 -0
  183. /package/lib/{v4/inertia → inertia}/flash.d.ts +0 -0
  184. /package/lib/{v4/inertia → inertia}/flash.js +0 -0
  185. /package/lib/{v4/inertia → inertia}/inertiaAdapter.d.ts +0 -0
  186. /package/lib/{v4/inertia → inertia}/inertiaAdapter.js +0 -0
  187. /package/lib/{v4/model → model}/ModelHandler.d.ts +0 -0
  188. /package/lib/{v4/model → model}/adapter/waterline.d.ts +0 -0
  189. /package/lib/{v4/model → model}/adapter/waterline.js +0 -0
  190. /package/lib/{v4/widgets → widgets}/abstractAction.d.ts +0 -0
  191. /package/lib/{v4/widgets → widgets}/abstractAction.js +0 -0
  192. /package/lib/{v4/widgets → widgets}/abstractCustom.js +0 -0
  193. /package/lib/{v4/widgets → widgets}/abstractInfo.d.ts +0 -0
  194. /package/lib/{v4/widgets → widgets}/abstractInfo.js +0 -0
  195. /package/lib/{v4/widgets → widgets}/abstractLink.js +0 -0
  196. /package/lib/{v4/widgets → widgets}/abstractSwitch.d.ts +0 -0
  197. /package/lib/{v4/widgets → widgets}/abstractSwitch.js +0 -0
  198. /package/lib/{v4/widgets → widgets}/abstractWidgetBase.js +0 -0
  199. /package/lib/{v4/widgets → widgets}/widgetHandler.js +0 -0
@@ -15,7 +15,11 @@ function generateAssociationsFromSchema(models, schemas) {
15
15
  const targetModel = models[field.collection];
16
16
  const targetSchema = schemas[field.collection];
17
17
  const inverseField = targetSchema?.[field.via];
18
- const throughTableName = [modelName, field.collection].sort().join("");
18
+ // Normalize join table to lowercase for consistency across dialects
19
+ const throughTableName = [modelName, field.collection]
20
+ .sort()
21
+ .join("")
22
+ .toLowerCase();
19
23
  // 💡 M:N связь
20
24
  if (inverseField && inverseField.collection === modelName) {
21
25
  model.belongsToMany(targetModel, {
@@ -444,7 +448,7 @@ export class SequelizeModel extends AbstractModel {
444
448
  where,
445
449
  include: Object.values(this.model.associations)
446
450
  });
447
- return reloaded.map(r => r.get({ plain: true }));
451
+ return reloaded.map((r) => r.get({ plain: true }));
448
452
  }
449
453
  // --- DESTROY ONE ---
450
454
  async _destroyOne(criteria) {
@@ -514,7 +518,7 @@ export class SequelizeModel extends AbstractModel {
514
518
  }
515
519
  }
516
520
  }
517
- const raw = records.map(r => r.get({ plain: true }));
521
+ const raw = records.map((r) => r.get({ plain: true }));
518
522
  await this.model.destroy({ where });
519
523
  return raw;
520
524
  }
@@ -529,11 +533,9 @@ export class SequelizeModel extends AbstractModel {
529
533
  return Object.keys(this.model.associations).map(key => ({ association: key }));
530
534
  }
531
535
  }
532
- /** SequelizeAdapter — адаптер, заменяющий WaterlineAdapter */
533
536
  export class SequelizeAdapter extends AbstractAdapter {
534
537
  sequelize;
535
538
  Model = SequelizeModel;
536
- alter = false;
537
539
  constructor(sequelize) {
538
540
  super("sequelize", sequelize);
539
541
  this.sequelize = sequelize;
@@ -552,18 +554,33 @@ export class SequelizeAdapter extends AbstractAdapter {
552
554
  const model = this.getModel(modelName);
553
555
  return model?.getAttributes();
554
556
  }
555
- /**Registration of system models*/
557
+ /** Registration of system models */
556
558
  static async registerSystemModels(sequelize, alter = true) {
557
- let modelsDir = path.resolve(import.meta.dirname, "../../../../models");
558
- if (!fs.existsSync(modelsDir)) {
559
- modelsDir = path.resolve(import.meta.dirname, "../../../../src/models");
559
+ // Try multiple possible model locations
560
+ const possiblePaths = [
561
+ path.resolve(import.meta.dirname, "../../../../models"),
562
+ path.resolve(import.meta.dirname, "../../../../src/models"),
563
+ path.resolve(import.meta.dirname, "../../../models"),
564
+ path.resolve(import.meta.dirname, "../../../src/models"),
565
+ path.resolve(process.cwd(), "node_modules/adminizer/src/models"),
566
+ path.resolve(process.cwd(), "node_modules/adminizer/models")
567
+ ];
568
+ let modelsDir = null;
569
+ for (const possiblePath of possiblePaths) {
570
+ if (fs.existsSync(possiblePath)) {
571
+ modelsDir = possiblePath;
572
+ break;
573
+ }
560
574
  }
561
- let files = fs.readdirSync(modelsDir).filter(f => f.endsWith(".js"));
575
+ if (!modelsDir) {
576
+ return;
577
+ }
578
+ let files = fs.readdirSync(modelsDir).filter((f) => f.endsWith(".js"));
562
579
  if (!files.length) {
563
- files = fs.readdirSync(modelsDir).filter(f => f.endsWith(".ts") && !f.endsWith(".d.ts"));
580
+ files = fs.readdirSync(modelsDir).filter((f) => f.endsWith(".ts") && !f.endsWith(".d.ts"));
564
581
  }
565
582
  if (!files.length) {
566
- throw `Model files not found in dir ${modelsDir}`;
583
+ return;
567
584
  }
568
585
  const schemas = {};
569
586
  for (const file of files) {
@@ -575,9 +592,12 @@ export class SequelizeAdapter extends AbstractAdapter {
575
592
  generateSequelizeModel(sequelize, modelName, definition);
576
593
  }
577
594
  generateAssociationsFromSchema(sequelize.models, schemas);
578
- if (alter === true) {
595
+ // Check ORM_ALTER environment variable before sync
596
+ if (process.env.ORM_ALTER !== 'false' && alter) {
579
597
  await sequelize.sync();
580
598
  }
599
+ else {
600
+ }
581
601
  }
582
602
  }
583
603
  /**Generation of the SEQUELIZE model from the definition, analogue of Waterinecollection.extenD*/
@@ -1,22 +1,29 @@
1
1
  import { EventEmitter } from 'events';
2
- import { Adminizer } from '../../Adminizer';
3
- import { INotification, INotificationEvent } from '../../../interfaces/types';
2
+ import { Adminizer } from '../Adminizer';
3
+ import { INotification, INotificationEvent } from '../../interfaces/types';
4
+ import { NotificationAPModel } from "../../models/NotificationAP";
5
+ import { UserAP } from "../../models/UserAP";
4
6
  export declare abstract class AbstractNotificationService extends EventEmitter {
5
- protected clients: Map<string, (event: INotificationEvent) => void>;
7
+ protected clients: Map<number, Map<string, (event: INotificationEvent) => void>>;
6
8
  protected adminizer: Adminizer;
7
9
  abstract readonly notificationClass: string;
8
10
  abstract readonly icon: string;
9
11
  abstract readonly iconColor: string;
10
12
  constructor(adminizer: Adminizer);
11
- addClient(clientId: string, sendFn: (event: INotificationEvent) => void): void;
13
+ private _bindAccessRight;
14
+ addClient(clientId: string, sendFn: (event: INotificationEvent) => void, user: UserAP): void;
12
15
  removeClient(clientId: string): void;
16
+ getUserClients(userId: number): Map<string, (event: INotificationEvent) => void>;
17
+ getClientCount(): number;
18
+ getAllClients(): Map<string, (event: INotificationEvent) => void>;
13
19
  abstract dispatchNotification(notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
14
20
  protected broadcast(event: INotificationEvent): void;
15
21
  sendHeartbeat(clientId: string): void;
16
- protected generateId(): string;
17
- getClientCount(): number;
18
22
  protected createUserNotification(notificationId: string, userId?: number): Promise<void>;
19
23
  protected getUserNotification(notificationId: string, userId: number): Promise<any>;
20
- getNotifications(userId: number, limit?: number, unreadOnly?: boolean): Promise<INotification[]>;
24
+ getNotifications(userId: number, limit?: number, skip?: number, unreadOnly?: boolean): Promise<INotification[]>;
25
+ protected prepareNotification(notificationsDB: NotificationAPModel[], userId: number): Promise<INotification[]>;
26
+ search(s: string, userId: number): Promise<INotification[]>;
21
27
  markAsRead(userId: number, id: string): Promise<void>;
28
+ markAllAsRead(userId: number): Promise<void>;
22
29
  }
@@ -0,0 +1,233 @@
1
+ import { EventEmitter } from 'events';
2
+ import { Adminizer } from '../Adminizer';
3
+ export class AbstractNotificationService extends EventEmitter {
4
+ clients = new Map();
5
+ adminizer;
6
+ constructor(adminizer) {
7
+ super();
8
+ this.adminizer = adminizer;
9
+ this._bindAccessRight();
10
+ }
11
+ _bindAccessRight() {
12
+ setTimeout(() => {
13
+ this.adminizer.accessRightsHelper.registerToken({
14
+ id: `notification-${this.notificationClass}`,
15
+ name: this.notificationClass,
16
+ description: `Access to notification ${this.notificationClass}`,
17
+ department: 'notification',
18
+ });
19
+ }, 100);
20
+ }
21
+ // Добавление клиента
22
+ addClient(clientId, sendFn, user) {
23
+ const userId = user.id;
24
+ // Если у пользователя еще нет Map клиентов - создаем
25
+ if (!this.clients.has(userId)) {
26
+ this.clients.set(userId, new Map());
27
+ }
28
+ // Получаем Map клиентов пользователя и добавляем нового клиента
29
+ const userClients = this.clients.get(userId);
30
+ userClients.set(clientId, sendFn);
31
+ Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} connected for user ${userId}. Total users: ${this.clients.size}, user clients: ${userClients.size}`);
32
+ }
33
+ // Удаление клиента
34
+ removeClient(clientId) {
35
+ // Ищем клиента во всех пользовательских Map
36
+ for (const [userId, userClients] of this.clients.entries()) {
37
+ if (userClients.has(clientId)) {
38
+ userClients.delete(clientId);
39
+ // Если у пользователя больше нет клиентов - удаляем его Map
40
+ if (userClients.size === 0) {
41
+ this.clients.delete(userId);
42
+ }
43
+ Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} disconnected from user ${userId}. Total users: ${this.clients.size}`);
44
+ return;
45
+ }
46
+ }
47
+ Adminizer.log.warn(`[${this.notificationClass}] Client ${clientId} not found for removal`);
48
+ }
49
+ // Получение клиентов конкретного пользователя
50
+ getUserClients(userId) {
51
+ return this.clients.get(userId) || new Map();
52
+ }
53
+ // Получение количества подключенных клиентов
54
+ getClientCount() {
55
+ return this.clients.size;
56
+ }
57
+ // Получение всех клиентов (для обратной совместимости)
58
+ getAllClients() {
59
+ const allClients = new Map();
60
+ for (const userClients of this.clients.values()) {
61
+ for (const [clientId, sendFn] of userClients.entries()) {
62
+ allClients.set(clientId, sendFn);
63
+ }
64
+ }
65
+ return allClients;
66
+ }
67
+ // Рассылка события всем клиентам
68
+ broadcast(event) {
69
+ for (const userClients of this.clients.values()) {
70
+ userClients.forEach((sendFn, clientId) => {
71
+ try {
72
+ sendFn(event);
73
+ }
74
+ catch (error) {
75
+ Adminizer.log.error(`[${this.notificationClass}] Error sending to client ${clientId}:`, error);
76
+ this.removeClient(clientId);
77
+ }
78
+ });
79
+ }
80
+ }
81
+ // Отправка heartbeat
82
+ sendHeartbeat(clientId) {
83
+ for (const userClients of this.clients.values()) {
84
+ const sendFn = userClients.get(clientId);
85
+ if (sendFn) {
86
+ const heartbeatEvent = {
87
+ type: 'heartbeat',
88
+ data: 'ping'
89
+ };
90
+ sendFn(heartbeatEvent);
91
+ return;
92
+ }
93
+ }
94
+ }
95
+ // Метод для создания записи в UserNotificationAP
96
+ async createUserNotification(notificationId, userId) {
97
+ if (this.adminizer.modelHandler.model.has('usernotificationap') && userId) {
98
+ try {
99
+ await this.adminizer.modelHandler.model.get('usernotificationap')["_create"]({
100
+ userId: userId,
101
+ notificationId: notificationId,
102
+ read: false
103
+ });
104
+ }
105
+ catch (error) {
106
+ Adminizer.log.error(`Error creating user notification record:`, error);
107
+ }
108
+ }
109
+ }
110
+ // Метод для получения UserNotificationAP по ID уведомления и пользователя
111
+ async getUserNotification(notificationId, userId) {
112
+ if (this.adminizer.modelHandler.model.has('usernotificationap')) {
113
+ try {
114
+ return await this.adminizer.modelHandler.model.get('usernotificationap')["_findOne"]({
115
+ where: { notificationId: notificationId, userId: userId }
116
+ });
117
+ }
118
+ catch (error) {
119
+ Adminizer.log.error(`Error getting user notification:`, error);
120
+ return null;
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+ async getNotifications(userId, limit = 20, skip = 0, unreadOnly = false) {
126
+ if (!this.adminizer.modelHandler.model.has('notificationap')) {
127
+ return [];
128
+ }
129
+ try {
130
+ let query = { notificationClass: this.notificationClass };
131
+ // Если запрашиваются уведомления для конкретного пользователя получаем ID уведомлений пользователя
132
+ const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
133
+ where: {
134
+ userId: userId
135
+ }
136
+ }, { populate: [['notificationId', {}]] });
137
+ const notificationIds = userNotifications.map((un) => un.notificationId.id);
138
+ if (unreadOnly) {
139
+ // Только непрочитанные
140
+ query.id = userNotifications
141
+ .filter((un) => !un.read)
142
+ .map((un) => un.notificationId.id);
143
+ }
144
+ else {
145
+ query.id = notificationIds;
146
+ }
147
+ if (query.id.length === 0)
148
+ return [];
149
+ const notificationsDB = await this.adminizer.modelHandler.model.get('notificationap')["_find"]({
150
+ where: query,
151
+ sort: 'createdAt DESC',
152
+ limit: limit,
153
+ skip: skip
154
+ });
155
+ return await this.prepareNotification(notificationsDB, userId);
156
+ }
157
+ catch (error) {
158
+ Adminizer.log.error('Error fetching notifications:', error);
159
+ return [];
160
+ }
161
+ }
162
+ async prepareNotification(notificationsDB, userId) {
163
+ let notifications = [];
164
+ for (const notification of notificationsDB) {
165
+ let readStatus = false;
166
+ // Получаем статус прочтения из UserNotificationAP
167
+ const userNotification = await this.getUserNotification(notification.id, userId);
168
+ readStatus = userNotification ? userNotification.read : false;
169
+ notifications.push({
170
+ ...notification,
171
+ read: readStatus,
172
+ icon: {
173
+ icon: this.icon,
174
+ iconColor: this.iconColor
175
+ },
176
+ });
177
+ }
178
+ return notifications;
179
+ }
180
+ async search(s, userId) {
181
+ try {
182
+ const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
183
+ where: {
184
+ userId: userId
185
+ }
186
+ }, { populate: [['notificationId', {}]] });
187
+ const notificationIds = userNotifications.map((un) => un.notificationId.id);
188
+ if (notificationIds.length === 0)
189
+ return [];
190
+ const notificationsDB = await this.adminizer.modelHandler.model.get('notificationap')["_find"]({
191
+ where: {
192
+ message: { contains: s },
193
+ id: notificationIds,
194
+ notificationClass: this.notificationClass
195
+ },
196
+ sort: "createdAt DESC"
197
+ });
198
+ return await this.prepareNotification(notificationsDB, userId);
199
+ }
200
+ catch (error) {
201
+ Adminizer.log.error('Error searching notifications:', error);
202
+ return [];
203
+ }
204
+ }
205
+ async markAsRead(userId, id) {
206
+ try {
207
+ const userNotification = await this.getUserNotification(id, userId);
208
+ if (userNotification) {
209
+ await this.adminizer.modelHandler.model.get('usernotificationap')["_update"]({
210
+ id: userNotification.id
211
+ }, { read: true });
212
+ }
213
+ }
214
+ catch (error) {
215
+ Adminizer.log.error('Error marking notification as read:', error);
216
+ }
217
+ }
218
+ async markAllAsRead(userId) {
219
+ try {
220
+ const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
221
+ where: { userId: userId }
222
+ });
223
+ for (const userNotification of userNotifications) {
224
+ await this.adminizer.modelHandler.model.get('usernotificationap')["_update"]({
225
+ where: { id: userNotification.id },
226
+ }, { read: true });
227
+ }
228
+ }
229
+ catch (error) {
230
+ Adminizer.log.error('Error marking all notifications as read:', error);
231
+ }
232
+ }
233
+ }
@@ -1,5 +1,5 @@
1
1
  import { AbstractNotificationService } from './AbstractNotificationService';
2
- import { INotification } from '../../../interfaces/types';
2
+ import { INotification } from '../../interfaces/types';
3
3
  export declare class GeneralNotificationService extends AbstractNotificationService {
4
4
  readonly notificationClass = "general";
5
5
  readonly icon: string;
@@ -1,5 +1,5 @@
1
1
  import { AbstractNotificationService } from './AbstractNotificationService';
2
- import { Adminizer } from '../../Adminizer';
2
+ import { Adminizer } from '../Adminizer';
3
3
  export class GeneralNotificationService extends AbstractNotificationService {
4
4
  notificationClass = 'general';
5
5
  icon = 'info';
@@ -23,7 +23,9 @@ export class GeneralNotificationService extends AbstractNotificationService {
23
23
  const users = await this.adminizer.modelHandler.model.get('userap')["_find"]({});
24
24
  for (const user of users) {
25
25
  try {
26
- await this.createUserNotification(notificationDB.id, user.id);
26
+ if (this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
27
+ await this.createUserNotification(notificationDB.id, user.id);
28
+ }
27
29
  }
28
30
  catch (error) {
29
31
  Adminizer.log.error('Error creating UserNotificationAP:', error);
@@ -0,0 +1,27 @@
1
+ import { EventEmitter } from 'events';
2
+ import { AbstractNotificationService } from "./AbstractNotificationService";
3
+ import { INotification } from '../../interfaces/types';
4
+ export declare class NotificationHandler extends EventEmitter {
5
+ private services;
6
+ constructor();
7
+ /**
8
+ * Register a new notification service
9
+ * @param service
10
+ */
11
+ registerService(service: AbstractNotificationService): void;
12
+ /**
13
+ * Get a notification service by class
14
+ * @param notificationClass
15
+ */
16
+ getService(notificationClass: string): AbstractNotificationService | undefined;
17
+ /**
18
+ * Get all registered services
19
+ */
20
+ getAllServices(): AbstractNotificationService[];
21
+ /**
22
+ * Dispatch a notification to a service
23
+ * @param notificationClass
24
+ * @param notification
25
+ */
26
+ dispatchNotification(notificationClass: string, notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
27
+ }
@@ -0,0 +1,41 @@
1
+ import { EventEmitter } from 'events';
2
+ import { Adminizer } from '../Adminizer';
3
+ export class NotificationHandler extends EventEmitter {
4
+ services = new Map();
5
+ constructor() {
6
+ super();
7
+ }
8
+ /**
9
+ * Register a new notification service
10
+ * @param service
11
+ */
12
+ registerService(service) {
13
+ this.services.set(service.notificationClass, service);
14
+ Adminizer.log.info(`Notification service registered: ${service.notificationClass}`);
15
+ }
16
+ /**
17
+ * Get a notification service by class
18
+ * @param notificationClass
19
+ */
20
+ getService(notificationClass) {
21
+ return this.services.get(notificationClass);
22
+ }
23
+ /**
24
+ * Get all registered services
25
+ */
26
+ getAllServices() {
27
+ return Array.from(this.services.values());
28
+ }
29
+ /**
30
+ * Dispatch a notification to a service
31
+ * @param notificationClass
32
+ * @param notification
33
+ */
34
+ async dispatchNotification(notificationClass, notification) {
35
+ const service = this.getService(notificationClass);
36
+ if (!service) {
37
+ throw new Error(`Notification service not found: ${notificationClass}`);
38
+ }
39
+ return service.dispatchNotification(notification);
40
+ }
41
+ }
@@ -1,5 +1,5 @@
1
1
  import { AbstractNotificationService } from './AbstractNotificationService';
2
- import { INotification } from '../../../interfaces/types';
2
+ import { INotification } from '../../interfaces/types';
3
3
  export declare class SystemNotificationService extends AbstractNotificationService {
4
4
  readonly notificationClass = "system";
5
5
  readonly icon: string;
@@ -7,12 +7,13 @@ export declare class SystemNotificationService extends AbstractNotificationServi
7
7
  private crudChannels;
8
8
  dispatchNotification(notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
9
9
  private broadcastToChannel;
10
- addClientToChannel(clientId: string, channel: string): void;
11
- removeClientFromChannel(clientId: string, channel: string): void;
12
- removeClientFromAllChannels(clientId: string): void;
10
+ addClientToChannel(clientId: string, channel: string, userId: number): void;
11
+ removeClientFromChannel(clientId: string, channel: string, userId: number): void;
12
+ removeClientFromAllChannels(clientId: string, userId: number): void;
13
13
  removeClient(clientId: string): void;
14
14
  logSystemEvent(title: string, message: string, channel?: string, metadata?: Record<string | number, any>): Promise<boolean>;
15
15
  logCreatedEvent(title: string, message: string, metadata?: Record<string | number, any>): Promise<boolean>;
16
16
  logUpdatedEvent(title: string, message: string, metadata?: Record<string | number, any>): Promise<boolean>;
17
17
  logDeletedEvent(title: string, message: string, metadata?: Record<string | number, any>): Promise<boolean>;
18
+ getUserChannels(userId: number): Map<string, Set<string>>;
18
19
  }
@@ -0,0 +1,173 @@
1
+ import { AbstractNotificationService } from './AbstractNotificationService';
2
+ import { Adminizer } from '../Adminizer';
3
+ export class SystemNotificationService extends AbstractNotificationService {
4
+ notificationClass = 'system';
5
+ icon = 'settings';
6
+ iconColor = '#1eb707';
7
+ // Изменяем структуру каналов: храним по userId -> channel -> clientIds
8
+ crudChannels = new Map();
9
+ async dispatchNotification(notification) {
10
+ const fullNotification = {
11
+ ...notification,
12
+ notificationClass: this.notificationClass,
13
+ channel: notification.channel ?? ''
14
+ };
15
+ let notificationDB;
16
+ // Сохраняем в базу
17
+ if (this.adminizer.modelHandler.model.has('notificationap')) {
18
+ try {
19
+ notificationDB = await this.adminizer.modelHandler.model.get('notificationap')["_create"](fullNotification);
20
+ const users = await this.adminizer.modelHandler.model.get('userap')["_find"]({});
21
+ for (const user of users) {
22
+ try {
23
+ if (this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
24
+ await this.createUserNotification(notificationDB.id, user.id);
25
+ }
26
+ }
27
+ catch (error) {
28
+ Adminizer.log.error('Error creating UserNotificationAP:', error);
29
+ }
30
+ }
31
+ const event = {
32
+ type: 'notification',
33
+ data: {
34
+ ...notificationDB,
35
+ icon: {
36
+ icon: this.icon,
37
+ iconColor: this.iconColor
38
+ },
39
+ },
40
+ notificationClass: this.notificationClass,
41
+ userId: notification.userId ?? null,
42
+ channel: notification.channel ?? 'system'
43
+ };
44
+ // Отправляем на все каналы или на конкретный канал
45
+ if (notification.channel) {
46
+ this.broadcastToChannel(notification.channel, event);
47
+ }
48
+ else {
49
+ this.broadcastToChannel('system', event);
50
+ }
51
+ Adminizer.log.info(`[System] Notification dispatched: ${fullNotification.title}`);
52
+ return true;
53
+ }
54
+ catch (error) {
55
+ Adminizer.log.error('Error saving system notification to database:', error);
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ // Обновляем broadcastToChannel для работы с новой структурой
61
+ broadcastToChannel(channel, event) {
62
+ // Отправляем всем пользователям, подписанным на этот канал
63
+ this.crudChannels.forEach((userChannels, userId) => {
64
+ const channelClients = userChannels.get(channel);
65
+ if (channelClients) {
66
+ const userClients = this.clients.get(userId);
67
+ if (userClients) {
68
+ channelClients.forEach(clientId => {
69
+ const sendFn = userClients.get(clientId);
70
+ if (sendFn) {
71
+ try {
72
+ sendFn(event);
73
+ }
74
+ catch (error) {
75
+ Adminizer.log.error(`[${this.notificationClass}] Error sending to client ${clientId} on channel ${channel}:`, error);
76
+ this.removeClient(clientId);
77
+ }
78
+ }
79
+ });
80
+ }
81
+ }
82
+ });
83
+ }
84
+ // Добавляем клиента к каналу с привязкой к пользователю
85
+ addClientToChannel(clientId, channel, userId) {
86
+ if (!this.crudChannels.has(userId)) {
87
+ this.crudChannels.set(userId, new Map());
88
+ }
89
+ const userChannels = this.crudChannels.get(userId);
90
+ if (!userChannels.has(channel)) {
91
+ userChannels.set(channel, new Set());
92
+ }
93
+ userChannels.get(channel).add(clientId);
94
+ Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} (user ${userId}) added to channel ${channel}`);
95
+ }
96
+ // Удаляем клиента из канала конкретного пользователя
97
+ removeClientFromChannel(clientId, channel, userId) {
98
+ const userChannels = this.crudChannels.get(userId);
99
+ if (userChannels) {
100
+ const channelClients = userChannels.get(channel);
101
+ if (channelClients) {
102
+ channelClients.delete(clientId);
103
+ Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} (user ${userId}) removed from channel ${channel}`);
104
+ // Если в канале больше нет клиентов - удаляем канал
105
+ if (channelClients.size === 0) {
106
+ userChannels.delete(channel);
107
+ }
108
+ }
109
+ // Если у пользователя больше нет каналов - удаляем запись пользователя
110
+ if (userChannels.size === 0) {
111
+ this.crudChannels.delete(userId);
112
+ }
113
+ }
114
+ }
115
+ // Удаляем клиента из всех каналов пользователя
116
+ removeClientFromAllChannels(clientId, userId) {
117
+ const userChannels = this.crudChannels.get(userId);
118
+ if (userChannels) {
119
+ userChannels.forEach((clients, channel) => {
120
+ clients.delete(clientId);
121
+ Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} (user ${userId}) removed from channel ${channel}`);
122
+ // Если в канале больше нет клиентов - удаляем канал
123
+ if (clients.size === 0) {
124
+ userChannels.delete(channel);
125
+ }
126
+ });
127
+ // Если у пользователя больше нет каналов - удаляем запись пользователя
128
+ if (userChannels.size === 0) {
129
+ this.crudChannels.delete(userId);
130
+ }
131
+ }
132
+ }
133
+ // Переопределяем removeClient для очистки каналов
134
+ removeClient(clientId) {
135
+ // Находим userId по clientId
136
+ let foundUserId = null;
137
+ for (const [userId, userClients] of this.clients.entries()) {
138
+ if (userClients.has(clientId)) {
139
+ foundUserId = userId;
140
+ break;
141
+ }
142
+ }
143
+ // Удаляем клиента из основного хранилища
144
+ super.removeClient(clientId);
145
+ // Удаляем клиента из каналов
146
+ if (foundUserId !== null) {
147
+ this.removeClientFromAllChannels(clientId, foundUserId);
148
+ }
149
+ }
150
+ // Специальный метод для системных событий с указанием канала
151
+ async logSystemEvent(title, message, channel, metadata) {
152
+ return this.dispatchNotification({
153
+ title: title,
154
+ message: message,
155
+ metadata: metadata,
156
+ channel: channel
157
+ });
158
+ }
159
+ // Методы для CRUD операций
160
+ async logCreatedEvent(title, message, metadata) {
161
+ return this.logSystemEvent(title, message, 'created', metadata);
162
+ }
163
+ async logUpdatedEvent(title, message, metadata) {
164
+ return this.logSystemEvent(title, message, 'updated', metadata);
165
+ }
166
+ async logDeletedEvent(title, message, metadata) {
167
+ return this.logSystemEvent(title, message, 'deleted', metadata);
168
+ }
169
+ // Новый метод для получения каналов пользователя
170
+ getUserChannels(userId) {
171
+ return this.crudChannels.get(userId) || new Map();
172
+ }
173
+ }
@@ -1,4 +1,4 @@
1
- import { MaterialIcon } from "../../../interfaces/MaaterialIcons";
1
+ import { MaterialIcon } from "../../interfaces/MaaterialIcons";
2
2
  export declare abstract class CustomBase {
3
3
  /** Widget unique id */
4
4
  abstract readonly id: string;