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.
- package/assets/add-CCznNk7p.js +1 -0
- package/assets/add-form-Cujvebyq.js +1 -0
- package/assets/add-group-KxM-aCt4.js +1 -0
- package/assets/add-group-form-BrDR-i0l.js +1 -0
- package/assets/add-user-form-CrfboTix.js +1 -0
- package/assets/add-user-uiWxY8Il.js +1 -0
- package/assets/{app-GsYdx39W.css → app-CpGtjhxF.css} +1 -1
- package/assets/app-layout-DlouPwk2.js +1 -0
- package/assets/{app-shell-DHzMWA1L.js → app-shell-B969C0PB.js} +1 -1
- package/assets/app-sidebar-DBaEEAgj.js +1 -0
- package/assets/app-sidebar-header-DAKTYE0g.js +1 -0
- package/assets/app.js +5817 -1629
- package/assets/breadcrumbs-DHQ3AkEO.js +1 -0
- package/assets/{catalog-BavcTVb4.js → catalog-Dt5pVGyH.js} +3 -3
- package/assets/{dashboard-xr-OyVyG.js → dashboard-SxHpjYT5.js} +2 -2
- package/assets/date-picker--zSt9NKp.js +1 -0
- package/assets/del-modal-B73-X7Bm.js +1 -0
- package/assets/{field-renderer-Sd68OLyc.js → field-renderer-BP9UmRzN.js} +3 -3
- package/assets/form-NLJGRnF1.js +1 -0
- package/assets/form-lSJpGHTy.js +1 -0
- package/assets/{geo-json-CVaNUZbb.js → geo-json-Dq8ViYH4.js} +1 -1
- package/assets/home-C5a2kMDP.js +2 -0
- package/assets/{icon-ChQBPA5l.js → icon-BbhNOU2o.js} +1 -1
- package/assets/init-user--zYviDj5.js +1 -0
- package/assets/{input-error-g1h6ps7t.js → input-error-1aQ9bwyz.js} +1 -1
- package/assets/list-D-3gH3Qy.js +1 -0
- package/assets/{list-table-Br0K3rXg.js → list-table-D6PqwSh1.js} +1 -1
- package/assets/login-BQhUl8bB.js +1 -0
- package/assets/manifest.json +109 -221
- package/assets/{material-icon-BIcI8APG.js → material-icon-8nTeT-mJ.js} +1 -1
- package/assets/{media-manager-CTstvyHm.js → media-manager-D1nqMyHv.js} +3 -3
- package/assets/module-DvUc6HiL.js +1 -0
- package/assets/nav-footer-xpg-_O5z.js +1 -0
- package/assets/nav-main-FLS_mh3K.js +1 -0
- package/assets/{nav-user-vIwYShwE.js → nav-user-CfXR0Kcu.js} +1 -1
- package/assets/notification-DsrY4egH.js +1 -0
- package/assets/register-DQ_hsYCS.js +1 -0
- package/assets/{tabs-CwgzWt48.js → tabs-DD2mZktY.js} +1 -1
- package/assets/theme-switcher-BKpJx2Op.js +1 -0
- package/assets/{toast-editor-C0550yms.js → toast-editor-CexR_aza.js} +4 -4
- package/assets/{user-info-Db5Y8KeN.js → user-info-obJxhBMF.js} +1 -1
- package/assets/user-menu-content-Bs0bmqDB.js +1 -0
- package/assets/welcome-DOcJbFZS.js +1 -0
- package/controllers/add.js +0 -2
- package/controllers/edit.js +3 -3
- package/controllers/login.js +21 -56
- package/controllers/remove.js +11 -2
- package/helpers/diffHelpers.d.ts +1 -1
- package/helpers/diffHelpers.js +52 -7
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/interfaces/types.d.ts +12 -2
- package/lib/Adminizer.d.ts +0 -8
- package/lib/Adminizer.js +0 -9
- package/lib/v4/DataAccessor.d.ts +1 -1
- package/lib/v4/model/AbstractModel.js +12 -0
- package/lib/v4/model/adapter/sequelize.js +1 -1
- package/lib/v4/notifications/AbstractNotificationService.js +1 -0
- package/lib/v4/notifications/GeneralNotificationService.js +1 -1
- package/lib/v4/notifications/SystemNotificationService.js +4 -3
- package/package.json +1 -1
- package/assets/VanillaJSONEditor-x8tDbio1.js +0 -3833
- package/assets/add-BNb5A_qF.js +0 -1
- package/assets/add-form-CGi_qv3J.js +0 -1
- package/assets/add-group-RRjHTu93.js +0 -1
- package/assets/add-group-form-DSd4UrnU.js +0 -1
- package/assets/add-user-DSuvcKp6.js +0 -1
- package/assets/add-user-form-BAssKDpI.js +0 -1
- package/assets/app-layout-ContoN1K.js +0 -1
- package/assets/app-sidebar-D7l5BJPV.js +0 -1
- package/assets/app-sidebar-header-CK8t32cm.js +0 -1
- package/assets/breadcrumbs-DnNOEnT4.js +0 -1
- package/assets/date-picker-DBHnomTY.js +0 -1
- package/assets/debounce-D6eaSoDg.js +0 -1
- package/assets/del-modal-BeF4r3gk.js +0 -1
- package/assets/form-C9P9m_9q.js +0 -1
- package/assets/form-DomTBI89.js +0 -1
- package/assets/form-state-702tb_BV.js +0 -1
- package/assets/handsontable-CZQ5Ietj.js +0 -347
- package/assets/home-BWHeZm-1.js +0 -2
- package/assets/index-C148XJoK.js +0 -1
- package/assets/index-Vcq4gwWv.js +0 -1
- package/assets/init-user-DqMDju_E.js +0 -1
- package/assets/list-EEgAn-nx.js +0 -1
- package/assets/login-CUDpGsHl.js +0 -1
- package/assets/module-CpZBs0fN.js +0 -1
- package/assets/monaco-editor-XX8Hz76f.js +0 -11
- package/assets/multi-select-BFWbEjH-.js +0 -1
- package/assets/nav-footer-bO8D-Yir.js +0 -1
- package/assets/nav-main-DVgvko8g.js +0 -1
- package/assets/notification-BTXEslqa.js +0 -1
- package/assets/register-Kfu8zmmu.js +0 -1
- package/assets/theme-switcher-fzVA8btX.js +0 -1
- package/assets/use-appearance-CXP5cHaQ.js +0 -1
- package/assets/use-window-size-DLSEoR6e.js +0 -1
- package/assets/user-menu-content-Bc8YNZPf.js +0 -1
- package/assets/welcome-MKPFhPtG.js +0 -1
- package/lib/v4/model/adapter/typeorm.d.ts +0 -24
- package/lib/v4/model/adapter/typeorm.js +0 -88
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as x,j as l,
|
|
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};
|
package/controllers/add.js
CHANGED
|
@@ -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);
|
package/controllers/edit.js
CHANGED
|
@@ -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
|
-
//
|
|
118
|
+
// Create diff
|
|
119
119
|
const cleanOldRecord = sanitizeForDiff(record);
|
|
120
120
|
const cleanNewRecord = sanitizeForDiff(newRecord[0]);
|
|
121
|
-
//
|
|
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}`, {
|
package/controllers/login.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
package/controllers/remove.js
CHANGED
|
@@ -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
|
-
|
|
71
|
-
|
|
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 {
|
package/helpers/diffHelpers.d.ts
CHANGED
|
@@ -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[];
|
package/helpers/diffHelpers.js
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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";
|
package/interfaces/types.d.ts
CHANGED
|
@@ -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';
|
package/lib/Adminizer.d.ts
CHANGED
|
@@ -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 {
|
package/lib/v4/DataAccessor.d.ts
CHANGED
|
@@ -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
|
-
|
|
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}`);
|
|
@@ -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
|
|
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.
|
|
48
|
+
this.broadcastToChannel('system', event);
|
|
48
49
|
}
|
|
49
50
|
Adminizer.log.info(`[System] Notification dispatched: ${fullNotification.title}`);
|
|
50
51
|
return true;
|