adminizer 4.3.0-build.74 → 4.3.0-build.76

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 (99) hide show
  1. package/assets/add-CCznNk7p.js +1 -0
  2. package/assets/add-form-Cujvebyq.js +1 -0
  3. package/assets/add-group-KxM-aCt4.js +1 -0
  4. package/assets/add-group-form-BrDR-i0l.js +1 -0
  5. package/assets/add-user-form-CrfboTix.js +1 -0
  6. package/assets/add-user-uiWxY8Il.js +1 -0
  7. package/assets/{app-GsYdx39W.css → app-CpGtjhxF.css} +1 -1
  8. package/assets/app-layout-DlouPwk2.js +1 -0
  9. package/assets/{app-shell-DHzMWA1L.js → app-shell-B969C0PB.js} +1 -1
  10. package/assets/app-sidebar-DBaEEAgj.js +1 -0
  11. package/assets/app-sidebar-header-DAKTYE0g.js +1 -0
  12. package/assets/app.js +5817 -1629
  13. package/assets/breadcrumbs-DHQ3AkEO.js +1 -0
  14. package/assets/{catalog-BavcTVb4.js → catalog-Dt5pVGyH.js} +3 -3
  15. package/assets/{dashboard-xr-OyVyG.js → dashboard-SxHpjYT5.js} +2 -2
  16. package/assets/date-picker--zSt9NKp.js +1 -0
  17. package/assets/del-modal-B73-X7Bm.js +1 -0
  18. package/assets/{field-renderer-Sd68OLyc.js → field-renderer-BP9UmRzN.js} +3 -3
  19. package/assets/form-NLJGRnF1.js +1 -0
  20. package/assets/form-lSJpGHTy.js +1 -0
  21. package/assets/{geo-json-CVaNUZbb.js → geo-json-Dq8ViYH4.js} +1 -1
  22. package/assets/home-C5a2kMDP.js +2 -0
  23. package/assets/{icon-ChQBPA5l.js → icon-BbhNOU2o.js} +1 -1
  24. package/assets/init-user--zYviDj5.js +1 -0
  25. package/assets/{input-error-g1h6ps7t.js → input-error-1aQ9bwyz.js} +1 -1
  26. package/assets/list-D-3gH3Qy.js +1 -0
  27. package/assets/{list-table-Br0K3rXg.js → list-table-D6PqwSh1.js} +1 -1
  28. package/assets/login-BQhUl8bB.js +1 -0
  29. package/assets/manifest.json +109 -221
  30. package/assets/{material-icon-BIcI8APG.js → material-icon-8nTeT-mJ.js} +1 -1
  31. package/assets/{media-manager-CTstvyHm.js → media-manager-D1nqMyHv.js} +3 -3
  32. package/assets/module-DvUc6HiL.js +1 -0
  33. package/assets/nav-footer-xpg-_O5z.js +1 -0
  34. package/assets/nav-main-FLS_mh3K.js +1 -0
  35. package/assets/{nav-user-vIwYShwE.js → nav-user-CfXR0Kcu.js} +1 -1
  36. package/assets/notification-DsrY4egH.js +1 -0
  37. package/assets/register-DQ_hsYCS.js +1 -0
  38. package/assets/{tabs-CwgzWt48.js → tabs-DD2mZktY.js} +1 -1
  39. package/assets/theme-switcher-BKpJx2Op.js +1 -0
  40. package/assets/{toast-editor-C0550yms.js → toast-editor-CexR_aza.js} +4 -4
  41. package/assets/{user-info-Db5Y8KeN.js → user-info-obJxhBMF.js} +1 -1
  42. package/assets/user-menu-content-Bs0bmqDB.js +1 -0
  43. package/assets/welcome-DOcJbFZS.js +1 -0
  44. package/controllers/add.js +0 -2
  45. package/controllers/edit.js +3 -3
  46. package/controllers/login.js +21 -56
  47. package/controllers/remove.js +11 -2
  48. package/helpers/diffHelpers.d.ts +1 -1
  49. package/helpers/diffHelpers.js +52 -7
  50. package/index.d.ts +0 -1
  51. package/index.js +0 -1
  52. package/interfaces/types.d.ts +12 -2
  53. package/lib/Adminizer.d.ts +0 -8
  54. package/lib/Adminizer.js +0 -9
  55. package/lib/v4/DataAccessor.d.ts +1 -1
  56. package/lib/v4/model/AbstractModel.js +12 -0
  57. package/lib/v4/model/adapter/sequelize.js +1 -1
  58. package/lib/v4/notifications/AbstractNotificationService.js +1 -0
  59. package/lib/v4/notifications/GeneralNotificationService.js +1 -1
  60. package/lib/v4/notifications/SystemNotificationService.js +4 -3
  61. package/package.json +1 -1
  62. package/assets/VanillaJSONEditor-x8tDbio1.js +0 -3833
  63. package/assets/add-BNb5A_qF.js +0 -1
  64. package/assets/add-form-CGi_qv3J.js +0 -1
  65. package/assets/add-group-RRjHTu93.js +0 -1
  66. package/assets/add-group-form-DSd4UrnU.js +0 -1
  67. package/assets/add-user-DSuvcKp6.js +0 -1
  68. package/assets/add-user-form-BAssKDpI.js +0 -1
  69. package/assets/app-layout-ContoN1K.js +0 -1
  70. package/assets/app-sidebar-D7l5BJPV.js +0 -1
  71. package/assets/app-sidebar-header-CK8t32cm.js +0 -1
  72. package/assets/breadcrumbs-DnNOEnT4.js +0 -1
  73. package/assets/date-picker-DBHnomTY.js +0 -1
  74. package/assets/debounce-D6eaSoDg.js +0 -1
  75. package/assets/del-modal-BeF4r3gk.js +0 -1
  76. package/assets/form-C9P9m_9q.js +0 -1
  77. package/assets/form-DomTBI89.js +0 -1
  78. package/assets/form-state-702tb_BV.js +0 -1
  79. package/assets/handsontable-CZQ5Ietj.js +0 -347
  80. package/assets/home-BWHeZm-1.js +0 -2
  81. package/assets/index-C148XJoK.js +0 -1
  82. package/assets/index-Vcq4gwWv.js +0 -1
  83. package/assets/init-user-DqMDju_E.js +0 -1
  84. package/assets/list-EEgAn-nx.js +0 -1
  85. package/assets/login-CUDpGsHl.js +0 -1
  86. package/assets/module-CpZBs0fN.js +0 -1
  87. package/assets/monaco-editor-XX8Hz76f.js +0 -11
  88. package/assets/multi-select-BFWbEjH-.js +0 -1
  89. package/assets/nav-footer-bO8D-Yir.js +0 -1
  90. package/assets/nav-main-DVgvko8g.js +0 -1
  91. package/assets/notification-BTXEslqa.js +0 -1
  92. package/assets/register-Kfu8zmmu.js +0 -1
  93. package/assets/theme-switcher-fzVA8btX.js +0 -1
  94. package/assets/use-appearance-CXP5cHaQ.js +0 -1
  95. package/assets/use-window-size-DLSEoR6e.js +0 -1
  96. package/assets/user-menu-content-Bc8YNZPf.js +0 -1
  97. package/assets/welcome-MKPFhPtG.js +0 -1
  98. package/lib/v4/model/adapter/typeorm.d.ts +0 -24
  99. package/lib/v4/model/adapter/typeorm.js +0 -88
@@ -1 +1 @@
1
- import{c as x,j as l,bx as h,by as p,bz as j}from"./app.js";function v(){return I}function I(g){const t=g.trim().split(" ");if(t.length===0)return"";if(t.length===1)return t[0].charAt(0).toUpperCase();const e=t[0].charAt(0),m=t[t.length-1].charAt(0);return`${e}${m}`.toUpperCase()}function A(g){const t=x.c(22),{user:e,showEmail:m}=g,u=m===void 0?!1:m,d=v();let s;t[0]!==e.avatar||t[1]!==e.login?(s=l.jsx(h,{src:e.avatar,alt:e.login}),t[0]=e.avatar,t[1]=e.login,t[2]=s):s=t[2];let a;t[3]!==d||t[4]!==e.login?(a=d(e.login),t[3]=d,t[4]=e.login,t[5]=a):a=t[5];let i;t[6]!==a?(i=l.jsx(p,{className:"rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white",children:a}),t[6]=a,t[7]=i):i=t[7];let n;t[8]!==s||t[9]!==i?(n=l.jsxs(j,{className:"h-8 w-8 overflow-hidden rounded-full",children:[s,i]}),t[8]=s,t[9]=i,t[10]=n):n=t[10];let r;t[11]!==e.login?(r=l.jsx("span",{className:"truncate font-medium",children:e.login}),t[11]=e.login,t[12]=r):r=t[12];let o;t[13]!==u||t[14]!==e.email?(o=u&&l.jsx("span",{className:"text-muted-foreground truncate text-xs",children:e.email}),t[13]=u,t[14]=e.email,t[15]=o):o=t[15];let c;t[16]!==r||t[17]!==o?(c=l.jsxs("div",{className:"grid flex-1 text-left text-sm leading-tight",children:[r,o]}),t[16]=r,t[17]=o,t[18]=c):c=t[18];let f;return t[19]!==n||t[20]!==c?(f=l.jsxs(l.Fragment,{children:[n,c]}),t[19]=n,t[20]=c,t[21]=f):f=t[21],f}export{A as UserInfo};
1
+ import{c as x,j as l,bD as h,bE as p,bF as j}from"./app.js";function v(){return I}function I(g){const t=g.trim().split(" ");if(t.length===0)return"";if(t.length===1)return t[0].charAt(0).toUpperCase();const e=t[0].charAt(0),m=t[t.length-1].charAt(0);return`${e}${m}`.toUpperCase()}function A(g){const t=x.c(22),{user:e,showEmail:m}=g,u=m===void 0?!1:m,d=v();let s;t[0]!==e.avatar||t[1]!==e.login?(s=l.jsx(h,{src:e.avatar,alt:e.login}),t[0]=e.avatar,t[1]=e.login,t[2]=s):s=t[2];let a;t[3]!==d||t[4]!==e.login?(a=d(e.login),t[3]=d,t[4]=e.login,t[5]=a):a=t[5];let i;t[6]!==a?(i=l.jsx(p,{className:"rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white",children:a}),t[6]=a,t[7]=i):i=t[7];let n;t[8]!==s||t[9]!==i?(n=l.jsxs(j,{className:"h-8 w-8 overflow-hidden rounded-full",children:[s,i]}),t[8]=s,t[9]=i,t[10]=n):n=t[10];let r;t[11]!==e.login?(r=l.jsx("span",{className:"truncate font-medium",children:e.login}),t[11]=e.login,t[12]=r):r=t[12];let o;t[13]!==u||t[14]!==e.email?(o=u&&l.jsx("span",{className:"text-muted-foreground truncate text-xs",children:e.email}),t[13]=u,t[14]=e.email,t[15]=o):o=t[15];let c;t[16]!==r||t[17]!==o?(c=l.jsxs("div",{className:"grid flex-1 text-left text-sm leading-tight",children:[r,o]}),t[16]=r,t[17]=o,t[18]=c):c=t[18];let f;return t[19]!==n||t[20]!==c?(f=l.jsxs(l.Fragment,{children:[n,c]}),t[19]=n,t[20]=c,t[21]=f):f=t[21],f}export{A as UserInfo};
@@ -0,0 +1 @@
1
+ import{c as p,j as t,bG as f,aV as x,bH as d,aU as h,a1 as j}from"./app.js";import{UserInfo as b}from"./user-info-obJxhBMF.js";function g(){return y}function y(){document.body.style.removeProperty("pointer-events")}function N(u){const e=p.c(11),{user:a,logout:c,logoutBtn:i}=u,m=g();let o;e[0]!==a?(o=t.jsx(f,{className:"p-0 font-normal",children:t.jsx("div",{className:"flex items-center gap-2 px-1 py-1.5 text-left text-sm",children:t.jsx(b,{user:a,showEmail:!0})})}),e[0]=a,e[1]=o):o=e[1];let n;e[2]===Symbol.for("react.memo_cache_sentinel")?(n=t.jsx(x,{}),e[2]=n):n=e[2];let r;e[3]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(d,{className:"mr-2"}),e[3]=r):r=e[3];let s;e[4]!==m||e[5]!==c||e[6]!==i?(s=t.jsx(h,{asChild:!0,children:t.jsxs(j,{className:"block w-full cursor-pointer",method:"get",href:c,as:"button",onClick:m,children:[r,i]})}),e[4]=m,e[5]=c,e[6]=i,e[7]=s):s=e[7];let l;return e[8]!==o||e[9]!==s?(l=t.jsxs(t.Fragment,{children:[o,n,s]}),e[8]=o,e[9]=s,e[10]=l):l=e[10],l}export{N as UserMenuContent};
@@ -0,0 +1 @@
1
+ import{c as l,q as m,j as p}from"./app.js";import{A as x}from"./app-layout-DlouPwk2.js";import"./app-shell-B969C0PB.js";import"./app-sidebar-DBaEEAgj.js";import"./nav-main-FLS_mh3K.js";import"./material-icon-8nTeT-mJ.js";import"./app-sidebar-header-DAKTYE0g.js";import"./breadcrumbs-DHQ3AkEO.js";import"./nav-user-CfXR0Kcu.js";import"./user-info-obJxhBMF.js";import"./user-menu-content-Bs0bmqDB.js";import"./theme-switcher-BKpJx2Op.js";import"./icon-BbhNOU2o.js";const a=[];function N(){const t=l.c(8),e=m();let r;t[0]!==e.props.title?(r=e.props.title&&p.jsx("div",{className:"text-3xl mb-8",children:e.props.title}),t[0]=e.props.title,t[1]=r):r=t[1];let s;t[2]===Symbol.for("react.memo_cache_sentinel")?(s=p.jsx("div",{className:"text-3xl mb-8",children:"Adminizer"}),t[2]=s):s=t[2];let o;t[3]!==e.props.text?(o=e.props.text&&p.jsx("div",{dangerouslySetInnerHTML:{__html:e.props.text}}),t[3]=e.props.text,t[4]=o):o=t[4];let i;return t[5]!==r||t[6]!==o?(i=p.jsx(x,{breadcrumbs:a,children:p.jsxs("div",{className:"flex h-full flex-1 flex-col gap-4 rounded-xl p-4",children:[r,s,o]})}),t[5]=r,t[6]=o,t[7]=i):i=t[7],i}export{N as default};
@@ -87,8 +87,6 @@ export default async function add(req, res) {
87
87
  }
88
88
  try {
89
89
  let record = await entity.model.create(reqData, dataAccessor);
90
- // log system event notification
91
- await req.adminizer.logSystemCreatedEvent(req.i18n.__('Created'), `user ${req.user.login} ${req.i18n.__('create')} ${entity.name} ${record.id}`, { test: 'test' });
92
90
  // save associations media to json
93
91
  await saveRelationsMediaManager(fields, rawReqData, entity.model.identity, record.id);
94
92
  Adminizer.log.debug(`A new record was created: `, record);
@@ -115,12 +115,12 @@ export default async function edit(req, res) {
115
115
  try {
116
116
  let newRecord = await entity.model.update(params, reqData, dataAccessor);
117
117
  await saveRelationsMediaManager(fields, rawReqData, entity.model.identity, newRecord[0].id);
118
- // Создаем clean объекты для сравнения (исключаем системные поля)
118
+ // Create diff
119
119
  const cleanOldRecord = sanitizeForDiff(record);
120
120
  const cleanNewRecord = sanitizeForDiff(newRecord[0]);
121
- // Получаем diff
121
+ // Get diff
122
122
  const changesDiff = diff(cleanOldRecord, cleanNewRecord);
123
- // Форматируем для лога
123
+ // Format changes
124
124
  const formattedChanges = formatChanges(changesDiff, cleanOldRecord, cleanNewRecord);
125
125
  // log system event notification
126
126
  await req.adminizer.logSystemUpdatedEvent(req.i18n.__('Updated'), `user ${req.user.login} ${req.i18n.__('update')} ${entity.name} ${record.id}`, {
@@ -14,25 +14,25 @@ export default async function login(req, res) {
14
14
  let login = req.body.login;
15
15
  let password = req.body.password;
16
16
  let captchaSolution = req.body.captchaSolution;
17
- // Handle pretend (impersonation) first against internal users list
17
+ let user;
18
+ try {
19
+ // TODO refactor CRUD functions for DataAccessor usage
20
+ user = await req.adminizer.modelHandler.model.get("UserAP")["_findOne"]({ login: login });
21
+ }
22
+ catch (e) {
23
+ return res.status(500).send({ error: e.message || 'Internal Server Error' });
24
+ }
18
25
  if (req.body.pretend) {
19
- try {
20
- // TODO refactor CRUD functions for DataAccessor usage
21
- const pretendUser = await req.adminizer.modelHandler.model.get("UserAP")["_findOne"]({ login });
22
- if (!pretendUser) {
23
- return res.sendStatus(404);
24
- }
25
- if (req.user.isAdministrator) {
26
- req.session.userPretended = pretendUser;
27
- return req.Inertia.redirect(`${req.adminizer.config.routePrefix}`);
28
- }
29
- return req.Inertia.redirect(`${req.adminizer.config.routePrefix}/`);
26
+ if (!user) {
27
+ return res.sendStatus(404);
30
28
  }
31
- catch (e) {
32
- return res.status(500).send({ error: e.message || 'Internal Server Error' });
29
+ if (req.user.isAdministrator) {
30
+ req.session.userPretended = user;
31
+ // return res.sendStatus(200);
32
+ return req.Inertia.redirect(`${req.adminizer.config.routePrefix}`);
33
33
  }
34
+ return req.Inertia.redirect(`${req.adminizer.config.routePrefix}/`);
34
35
  }
35
- let authenticatedExternally = false;
36
36
  // Verify CAPTCHA solution if enabled
37
37
  if (req.adminizer.config.auth.captcha !== false) {
38
38
  const isCaptchaValid = powCaptcha.check(captchaSolution, `login:${req.ip}`);
@@ -40,22 +40,17 @@ export default async function login(req, res) {
40
40
  return inertiaAdminMessage(req, "Invalid CAPTCHA solution", 'captchaSolution');
41
41
  }
42
42
  }
43
- // First check UserAP model (local auth)
44
- let user;
45
- try {
46
- // TODO refactor CRUD functions for DataAccessor usage
47
- user = await req.adminizer.modelHandler.model.get("UserAP")["_findOne"]({ login: login });
48
- }
49
- catch (e) {
50
- return res.status(500).send({ error: e.message || 'Internal Server Error' });
43
+ if (!user) {
44
+ return inertiaAdminMessage(req, "Wrong username", 'login');
51
45
  }
52
- if (user) {
53
- // User found in UserAP, validate password
54
- if (req.adminizer.config.registration.confirmationRequired && user.isConfirmed === false && !user.isAdministrator) {
46
+ else {
47
+ if (req.adminizer.config.registration.confirmationRequired && !user.isConfirmed && !user.isAdministrator) {
48
+ //Here we use the captchaSolution key to output messages unrelated to the form fields.
55
49
  return inertiaAdminMessage(req, "Profile is not confirmed, please contact to administrator", 'captchaSolution');
56
50
  }
57
51
  if (passwordHash.verify(login + password + process.env.AP_PASSWORD_SALT, user.passwordHashed)) {
58
52
  if (user.expires && Date.now() > Date.parse(user.expires)) {
53
+ //Here we use the captchaSolution key to output messages unrelated to the form fields.
59
54
  return inertiaAdminMessage(req, "Profile expired, contact the administrator", 'captchaSolution');
60
55
  }
61
56
  const token = signUser(user, req.adminizer.jwtSecret);
@@ -71,36 +66,6 @@ export default async function login(req, res) {
71
66
  return inertiaAdminMessage(req, "Wrong password", 'password');
72
67
  }
73
68
  }
74
- // If user not found in UserAP and external auth handler is configured, try external auth
75
- if (typeof req.adminizer.authHandler === 'function') {
76
- try {
77
- const extUser = await req.adminizer.authHandler(req, login, password);
78
- if (!extUser) {
79
- return inertiaAdminMessage(req, "Wrong username or password", 'login');
80
- }
81
- const externalUser = extUser;
82
- if (req.adminizer.config.registration.confirmationRequired && externalUser.isConfirmed === false && !externalUser.isAdministrator) {
83
- return inertiaAdminMessage(req, "Profile is not confirmed, please contact to administrator", 'captchaSolution');
84
- }
85
- if (externalUser.expires && Date.now() > Date.parse(externalUser.expires)) {
86
- return inertiaAdminMessage(req, "Profile expired, contact the administrator", 'captchaSolution');
87
- }
88
- const token = signUser(externalUser, req.adminizer.jwtSecret);
89
- res.setHeader('Set-Cookie', serialize('adminizer_jwt', token, {
90
- httpOnly: true,
91
- sameSite: 'lax',
92
- path: '/',
93
- maxAge: 60 * 60 * 24 * 7 * 2,
94
- }));
95
- return req.Inertia.redirect(`${req.adminizer.config.routePrefix}`);
96
- }
97
- catch (err) {
98
- Adminizer.log.error('External auth handler error', err);
99
- return res.status(500).send({ error: 'Internal Server Error' });
100
- }
101
- }
102
- // If neither UserAP nor external auth worked
103
- return inertiaAdminMessage(req, "Wrong username or password", 'login');
104
69
  }
105
70
  if (req.method.toUpperCase() === "GET") {
106
71
  // Generate new CAPTCHA task if enabled
@@ -2,6 +2,8 @@ import { ControllerHelper } from "../helpers/controllerHelper";
2
2
  import { deleteRelationsMediaManager } from "../lib/media-manager/helpers/MediaManagerHelper";
3
3
  import { DataAccessor } from "../lib/v4/DataAccessor";
4
4
  import { Adminizer } from "../lib/Adminizer";
5
+ import { formatChanges, sanitizeForDiff } from "../helpers/diffHelpers";
6
+ import { diff } from "deep-object-diff";
5
7
  export default async function remove(req, res) {
6
8
  // Checking id of the record
7
9
  if (!req.params.id) {
@@ -60,6 +62,7 @@ export default async function remove(req, res) {
60
62
  const q = {};
61
63
  q[fieldId] = record[fieldId];
62
64
  destroyedRecord = await entity.model.destroy(q, dataAccessor);
65
+ console.log(destroyedRecord[0]);
63
66
  // delete relations media manager
64
67
  await deleteRelationsMediaManager(req.adminizer, entity.name, destroyedRecord);
65
68
  }
@@ -67,8 +70,14 @@ export default async function remove(req, res) {
67
70
  Adminizer.log.error('adminpanel > error', e);
68
71
  }
69
72
  if (destroyedRecord) {
70
- // log system event notification
71
- await req.adminizer.logSystemDeletedEvent(req.i18n.__('Deleted'), `user ${req.user.login} ${req.i18n.__('delete')} ${entity.name} ${destroyedRecord[0].id}`, { test: 'test' });
73
+ const cleanRecordBeforeDelete = sanitizeForDiff(destroyedRecord[0]);
74
+ const changesDiff = diff(cleanRecordBeforeDelete, {});
75
+ const formattedChanges = formatChanges(changesDiff, cleanRecordBeforeDelete, {});
76
+ // log system event notification с diff
77
+ await req.adminizer.logSystemDeletedEvent(req.i18n.__('Deleted'), `user ${req.user.login} ${req.i18n.__('delete')} ${entity.name} ${destroyedRecord[0].id}`, {
78
+ changes: formattedChanges,
79
+ summary: `${req.i18n.__('Deleted')} ${formattedChanges.length} ${req.i18n.__('fields')}`
80
+ });
72
81
  req.flash.setFlashMessage('success', req.i18n.__('Record was removed successfully !'));
73
82
  }
74
83
  else {
@@ -1,2 +1,2 @@
1
1
  export declare function sanitizeForDiff(data: any): any;
2
- export declare function formatChanges(diffObj: any, oldData: any, newData: any): any[];
2
+ export declare function formatChanges(diffObj: any, oldData: any, newData: any, operation?: 'add' | 'remove' | 'update'): any[];
@@ -6,6 +6,7 @@ export function sanitizeForDiff(data) {
6
6
  const systemFields = ['id', 'createdAt', 'updatedAt', 'deletedAt', '__v', '_id'];
7
7
  systemFields.forEach(field => delete result[field]);
8
8
  // Очищаем чувствительные данные
9
+ // TODO: Вынести в переменные окружения или в конфиг
9
10
  const sensitiveFields = ['password', 'token', 'secret', 'apiKey', 'creditCard'];
10
11
  sensitiveFields.forEach(field => {
11
12
  if (result[field] !== undefined) {
@@ -14,11 +15,55 @@ export function sanitizeForDiff(data) {
14
15
  });
15
16
  return result;
16
17
  }
17
- export function formatChanges(diffObj, oldData, newData) {
18
- return Object.entries(diffObj).map(([key, value]) => ({
19
- field: key,
20
- oldValue: oldData[key],
21
- newValue: newData[key],
22
- type: typeof value
23
- }));
18
+ export function formatChanges(diffObj, oldData, newData, operation) {
19
+ const changes = [];
20
+ // Специальная обработка для операции добавления
21
+ if (operation === 'add') {
22
+ for (const [key, value] of Object.entries(newData)) {
23
+ if (value === '***HIDDEN***')
24
+ continue;
25
+ changes.push({
26
+ field: key,
27
+ oldValue: undefined,
28
+ newValue: value,
29
+ type: typeof value,
30
+ operation: 'add'
31
+ });
32
+ }
33
+ return changes;
34
+ }
35
+ // Стандартная обработка для remove и update
36
+ for (const [key, value] of Object.entries(diffObj)) {
37
+ // Для добавленных полей (когда oldData[key] undefined)
38
+ if (oldData[key] === undefined && newData[key] !== undefined) {
39
+ changes.push({
40
+ field: key,
41
+ oldValue: undefined,
42
+ newValue: newData[key],
43
+ type: 'added',
44
+ operation: 'add'
45
+ });
46
+ }
47
+ // Для удаленных полей (когда newData[key] undefined)
48
+ else if (newData[key] === undefined && oldData[key] !== undefined) {
49
+ changes.push({
50
+ field: key,
51
+ oldValue: oldData[key],
52
+ newValue: undefined,
53
+ type: 'deleted',
54
+ operation: 'remove'
55
+ });
56
+ }
57
+ // Для измененных полей
58
+ else {
59
+ changes.push({
60
+ field: key,
61
+ oldValue: oldData[key],
62
+ newValue: newData[key],
63
+ type: typeof value,
64
+ operation: 'update'
65
+ });
66
+ }
67
+ }
68
+ return changes;
24
69
  }
package/index.d.ts CHANGED
@@ -16,7 +16,6 @@ export * from "./interfaces/types";
16
16
  export * from "./interfaces/MaaterialIcons";
17
17
  export * from "./lib/v4/model/AbstractModel";
18
18
  export * from "./lib/v4/model/adapter/waterline";
19
- export * from "./lib/v4/model/adapter/typeorm";
20
19
  export * from "./lib/v4/model/adapter/sequelize";
21
20
  export * from "./lib/Adminizer";
22
21
  export * from "./models/GroupAP";
package/index.js CHANGED
@@ -16,7 +16,6 @@ export * from "./interfaces/types";
16
16
  export * from "./interfaces/MaaterialIcons";
17
17
  export * from "./lib/v4/model/AbstractModel";
18
18
  export * from "./lib/v4/model/adapter/waterline";
19
- export * from "./lib/v4/model/adapter/typeorm";
20
19
  export * from "./lib/v4/model/adapter/sequelize";
21
20
  export * from "./lib/Adminizer";
22
21
  export * from "./models/GroupAP";
@@ -25,6 +25,16 @@ export interface PropsField {
25
25
  isIn?: string[] | number[] | boolean[];
26
26
  options?: Record<string, unknown> | Record<string, unknown>[];
27
27
  }
28
+ export interface DiffChanges {
29
+ field: string;
30
+ oldValue: any;
31
+ newValue: any;
32
+ type: string;
33
+ }
34
+ export interface Diff {
35
+ changes: DiffChanges[];
36
+ summary: string;
37
+ }
28
38
  export interface INotification {
29
39
  id: string;
30
40
  title: string;
@@ -37,8 +47,8 @@ export interface INotification {
37
47
  iconColor: string;
38
48
  };
39
49
  notificationClass: string;
40
- channel: string;
41
- metadata?: Record<string | number, any>;
50
+ channel: string | 'created' | 'updated' | 'deleted' | 'system';
51
+ metadata?: Record<string | number, any> | Diff;
42
52
  }
43
53
  export interface INotificationEvent {
44
54
  type: 'notification' | 'heartbeat' | 'connected' | 'error';
@@ -14,8 +14,6 @@ import { ControlsHandler } from "./v4/controls/ControlsHandler";
14
14
  import { CatalogHandler } from "./v4/catalog/CatalogHandler";
15
15
  import { NotificationHandler } from './v4/notifications/NotificationHandler';
16
16
  import { INotification } from "../interfaces/types";
17
- import { UserAP } from "models/UserAP";
18
- export type AuthHandler = (req: ReqType, login: string, password: string) => Promise<Partial<UserAP> | null>;
19
17
  export declare class Adminizer {
20
18
  /**
21
19
  * If you convey this default Middleware, it will add it to the very top of the router,
@@ -37,14 +35,8 @@ export declare class Adminizer {
37
35
  controlsHandler: ControlsHandler;
38
36
  catalogHandler: CatalogHandler;
39
37
  jwtSecret: string;
40
- authHandler?: AuthHandler;
41
38
  static logger: winston.Logger;
42
39
  constructor(ormAdapters: AbstractAdapter[]);
43
- /**
44
- * Registers an external authentication handler. If a user is not found in UserAP
45
- * during login, this handler will be invoked to authenticate against an external source.
46
- */
47
- setAuthHandler(handler: AuthHandler): void;
48
40
  getMiddleware(): (req: express.Request, res: express.Response, next: () => void) => void;
49
41
  /**
50
42
  * Vite middleware
package/lib/Adminizer.js CHANGED
@@ -54,8 +54,6 @@ export class Adminizer {
54
54
  catalogHandler;
55
55
  // Constants
56
56
  jwtSecret = process.env.JWT_SECRET ?? uuid();
57
- // External auth handler
58
- authHandler;
59
57
  static logger = winston.createLogger({
60
58
  level: process.env.LOG_LEVEL ?? "debug",
61
59
  format: winston.format.combine(winston.format.timestamp(), winston.format.printf(({ timestamp, level, message, ...meta }) => {
@@ -72,13 +70,6 @@ export class Adminizer {
72
70
  this._emitter = new EventEmitter();
73
71
  this.ormAdapters = ormAdapters;
74
72
  }
75
- /**
76
- * Registers an external authentication handler. If a user is not found in UserAP
77
- * during login, this handler will be invoked to authenticate against an external source.
78
- */
79
- setAuthHandler(handler) {
80
- this.authHandler = handler;
81
- }
82
73
  getMiddleware() {
83
74
  return (req, res, next) => {
84
75
  try {
@@ -7,7 +7,7 @@ import { Fields } from "../../helpers/fieldsHelper";
7
7
  import { Adminizer } from "../Adminizer";
8
8
  import { UserAP } from "models/UserAP";
9
9
  export declare class DataAccessor {
10
- private readonly adminizer;
10
+ readonly adminizer: Adminizer;
11
11
  user: UserAP;
12
12
  entity: Entity;
13
13
  action: ActionType;
@@ -1,3 +1,4 @@
1
+ import { formatChanges, sanitizeForDiff } from "../../../helpers/diffHelpers";
1
2
  export class AbstractModel {
2
3
  modelname;
3
4
  attributes;
@@ -12,6 +13,17 @@ export class AbstractModel {
12
13
  async create(data, dataAccessor) {
13
14
  let _data = await dataAccessor.setUserRelationAccess(dataAccessor.process(data));
14
15
  let record = await this._create(_data);
16
+ // console.log(record.toJSON());
17
+ //Create diff
18
+ const cleanNewRecord = sanitizeForDiff(_data);
19
+ const formattedChanges = formatChanges({}, {}, cleanNewRecord, 'add');
20
+ // log system event notification с diff
21
+ await dataAccessor.adminizer.logSystemCreatedEvent('Created',
22
+ //@ts-ignore
23
+ `user ${dataAccessor.user.login} create ${dataAccessor.entity.name} ${record[this.primaryKey]}`, {
24
+ changes: formattedChanges,
25
+ summary: `Created ${formattedChanges.length} fields`
26
+ });
15
27
  return dataAccessor.process(record);
16
28
  }
17
29
  async findOne(criteria, dataAccessor) {
@@ -325,7 +325,7 @@ export class SequelizeModel extends AbstractModel {
325
325
  }
326
326
  await instance.reload({ include: Object.values(this.model.associations) });
327
327
  const pk = this.primaryKey;
328
- const fresh = await this.model.findByPk(instance.get(pk), { include: assocNames.map(a => ({ association: a })) });
328
+ const fresh = (await this.model.findByPk(instance.get(pk), { include: assocNames.map(a => ({ association: a })) })).toJSON();
329
329
  // console.debug(">> Результат после reload:", fresh?.toJSON());
330
330
  return fresh;
331
331
  }
@@ -8,6 +8,7 @@ export class AbstractNotificationService extends EventEmitter {
8
8
  this.adminizer = adminizer;
9
9
  }
10
10
  // Добавление клиента
11
+ // TODO - Проверить, что клиент имеет права на получение уведомлений
11
12
  addClient(clientId, sendFn) {
12
13
  this.clients.set(clientId, sendFn);
13
14
  Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} connected. Total: ${this.clients.size}`);
@@ -33,7 +33,7 @@ export class GeneralNotificationService extends AbstractNotificationService {
33
33
  const event = {
34
34
  type: 'notification',
35
35
  data: {
36
- ...notificationDB.toJSON(),
36
+ ...notificationDB,
37
37
  read: false,
38
38
  icon: {
39
39
  icon: this.icon,
@@ -16,6 +16,7 @@ export class SystemNotificationService extends AbstractNotificationService {
16
16
  // Сохраняем в базу
17
17
  if (this.adminizer.modelHandler.model.has('notificationap')) {
18
18
  try {
19
+ // Idia how to keep in DB notification raw data ['user %s created %s in model %s', user, instance.name, modelName] as print_f()
19
20
  notificationDB = await this.adminizer.modelHandler.model.get('notificationap')["_create"](fullNotification);
20
21
  const users = await this.adminizer.modelHandler.model.get('userap')["_find"]({ isAdministrator: 1 });
21
22
  for (const user of users) {
@@ -29,7 +30,7 @@ export class SystemNotificationService extends AbstractNotificationService {
29
30
  const event = {
30
31
  type: 'notification',
31
32
  data: {
32
- ...notificationDB.toJSON(),
33
+ ...notificationDB,
33
34
  icon: {
34
35
  icon: this.icon,
35
36
  iconColor: this.iconColor
@@ -37,14 +38,14 @@ export class SystemNotificationService extends AbstractNotificationService {
37
38
  },
38
39
  notificationClass: this.notificationClass,
39
40
  userId: notification.userId ?? null,
40
- channel: notification.channel // Добавляем информацию о канале
41
+ channel: notification.channel ?? 'system' // Добавляем информацию о канале
41
42
  };
42
43
  // Отправляем на все каналы или на конкретный канал
43
44
  if (notification.channel) {
44
45
  this.broadcastToChannel(notification.channel, event);
45
46
  }
46
47
  else {
47
- this.broadcast(event);
48
+ this.broadcastToChannel('system', event);
48
49
  }
49
50
  Adminizer.log.info(`[System] Notification dispatched: ${fullNotification.title}`);
50
51
  return true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adminizer",
3
3
  "type": "module",
4
- "version": "4.3.0-build.74",
4
+ "version": "4.3.0-build.76",
5
5
  "main": "index.js",
6
6
  "exports": {
7
7
  ".": "./index.js",