adminforth 1.1.66 → 1.1.68

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/auth.ts CHANGED
@@ -3,6 +3,7 @@ import jwt from 'jsonwebtoken';
3
3
 
4
4
  import crypto from 'crypto';
5
5
  import AdminForth from './index.js';
6
+ import { th } from '@faker-js/faker';
6
7
 
7
8
  // Function to generate a password hash using PBKDF2
8
9
  function calcPasswordHash(password, salt, iterations = 100000, keyLength = 64, digest = 'sha512') {
@@ -26,7 +27,18 @@ class AdminForthAuth {
26
27
  this.adminforth = adminforth;
27
28
  }
28
29
 
29
- issueJWT(payload) {
30
+ removeAuthCookie(response) {
31
+ response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
32
+ }
33
+
34
+ setAuthCookie({ response, username, pk}: {
35
+ response: any, username: string, pk: string | null
36
+ }) {
37
+ const token = this.issueJWT({ username, pk, }, 'auth');
38
+ response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
39
+ }
40
+
41
+ issueJWT(payload: Object, type: string) {
30
42
  // read ADMINFORH_SECRET from environment if not drop error
31
43
  const secret = process.env.ADMINFORTH_SECRET;
32
44
  if (!secret) {
@@ -35,10 +47,10 @@ class AdminForthAuth {
35
47
 
36
48
  // issue JWT token
37
49
  const expiresIn = process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h';
38
- return jwt.sign(payload, secret, { expiresIn });
50
+ return jwt.sign({...payload, t: type}, secret, { expiresIn });
39
51
  }
40
52
 
41
- async verify(jwtToken) {
53
+ async verify(jwtToken: string, mustHaveType: string): Promise<Object> {
42
54
  // read ADMINFORH_SECRET from environment if not drop error
43
55
  const secret = process.env.ADMINFORTH_SECRET;
44
56
  if (!secret) {
@@ -58,7 +70,10 @@ class AdminForthAuth {
58
70
  }
59
71
  return null;
60
72
  }
61
- const { pk } = decoded;
73
+ const { pk, t } = decoded;
74
+ if (t !== mustHaveType) {
75
+ throw new Error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
76
+ }
62
77
  if (pk === null) {
63
78
  decoded.isRoot = true;
64
79
  } else {
@@ -34,8 +34,8 @@ class MongoConnector implements AdminForthDataSourceConnector {
34
34
  };
35
35
 
36
36
  SortDirectionsMap = {
37
- [AdminForthSortDirections.ASC]: 1,
38
- [AdminForthSortDirections.DESC]: -1,
37
+ [AdminForthSortDirections.asc]: 1,
38
+ [AdminForthSortDirections.desc]: -1,
39
39
  };
40
40
 
41
41
  async discoverFields(resource) {
@@ -68,13 +68,13 @@ class MongoConnector implements AdminForthDataSourceConnector {
68
68
  if (!value) {
69
69
  return null;
70
70
  }
71
- return dayjs.unix(value).toISOString();
71
+ return dayjs(Date.parse(value)).toISOString();
72
72
 
73
73
  } else if (field.type == AdminForthDataTypes.DATE) {
74
74
  if (!value) {
75
75
  return null;
76
76
  }
77
- return dayjs(value).toISOString().split('T')[0];
77
+ return dayjs(Date.parse(value)).toISOString().split('T')[0];
78
78
 
79
79
  } else if (field.type == AdminForthDataTypes.BOOLEAN) {
80
80
  return !!value;
@@ -37,8 +37,8 @@ class PostgresConnector implements AdminForthDataSourceConnector {
37
37
  };
38
38
 
39
39
  SortDirectionsMap = {
40
- [AdminForthSortDirections.ASC]: 'ASC',
41
- [AdminForthSortDirections.DESC]: 'DESC',
40
+ [AdminForthSortDirections.asc]: 'ASC',
41
+ [AdminForthSortDirections.desc]: 'DESC',
42
42
  };
43
43
 
44
44
  async discoverFields(resource) {
@@ -141,8 +141,8 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
141
141
  };
142
142
 
143
143
  SortDirectionsMap = {
144
- [AdminForthSortDirections.ASC]: 'ASC',
145
- [AdminForthSortDirections.DESC]: 'DESC',
144
+ [AdminForthSortDirections.asc]: 'ASC',
145
+ [AdminForthSortDirections.desc]: 'DESC',
146
146
  };
147
147
 
148
148
 
package/dist/auth.js CHANGED
@@ -27,7 +27,14 @@ class AdminForthAuth {
27
27
  constructor(adminforth) {
28
28
  this.adminforth = adminforth;
29
29
  }
30
- issueJWT(payload) {
30
+ removeAuthCookie(response) {
31
+ response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
32
+ }
33
+ setAuthCookie({ response, username, pk }) {
34
+ const token = this.issueJWT({ username, pk, }, 'auth');
35
+ response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
36
+ }
37
+ issueJWT(payload, type) {
31
38
  // read ADMINFORH_SECRET from environment if not drop error
32
39
  const secret = process.env.ADMINFORTH_SECRET;
33
40
  if (!secret) {
@@ -35,9 +42,9 @@ class AdminForthAuth {
35
42
  }
36
43
  // issue JWT token
37
44
  const expiresIn = process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h';
38
- return jwt.sign(payload, secret, { expiresIn });
45
+ return jwt.sign(Object.assign(Object.assign({}, payload), { t: type }), secret, { expiresIn });
39
46
  }
40
- verify(jwtToken) {
47
+ verify(jwtToken, mustHaveType) {
41
48
  return __awaiter(this, void 0, void 0, function* () {
42
49
  // read ADMINFORH_SECRET from environment if not drop error
43
50
  const secret = process.env.ADMINFORTH_SECRET;
@@ -61,7 +68,10 @@ class AdminForthAuth {
61
68
  }
62
69
  return null;
63
70
  }
64
- const { pk } = decoded;
71
+ const { pk, t } = decoded;
72
+ if (t !== mustHaveType) {
73
+ throw new Error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
74
+ }
65
75
  if (pk === null) {
66
76
  decoded.isRoot = true;
67
77
  }
@@ -25,8 +25,8 @@ class MongoConnector {
25
25
  [AdminForthFilterOperators.NIN]: (value) => ({ $nin: value }),
26
26
  };
27
27
  this.SortDirectionsMap = {
28
- [AdminForthSortDirections.ASC]: 1,
29
- [AdminForthSortDirections.DESC]: -1,
28
+ [AdminForthSortDirections.asc]: 1,
29
+ [AdminForthSortDirections.desc]: -1,
30
30
  };
31
31
  this.db = new MongoClient(url);
32
32
  (() => __awaiter(this, void 0, void 0, function* () {
@@ -71,13 +71,13 @@ class MongoConnector {
71
71
  if (!value) {
72
72
  return null;
73
73
  }
74
- return dayjs.unix(value).toISOString();
74
+ return dayjs(Date.parse(value)).toISOString();
75
75
  }
76
76
  else if (field.type == AdminForthDataTypes.DATE) {
77
77
  if (!value) {
78
78
  return null;
79
79
  }
80
- return dayjs(value).toISOString().split('T')[0];
80
+ return dayjs(Date.parse(value)).toISOString().split('T')[0];
81
81
  }
82
82
  else if (field.type == AdminForthDataTypes.BOOLEAN) {
83
83
  return !!value;
@@ -26,8 +26,8 @@ class PostgresConnector {
26
26
  [AdminForthFilterOperators.NIN]: 'NOT IN',
27
27
  };
28
28
  this.SortDirectionsMap = {
29
- [AdminForthSortDirections.ASC]: 'ASC',
30
- [AdminForthSortDirections.DESC]: 'DESC',
29
+ [AdminForthSortDirections.asc]: 'ASC',
30
+ [AdminForthSortDirections.desc]: 'DESC',
31
31
  };
32
32
  this.db = new Client({
33
33
  connectionString: url
@@ -26,8 +26,8 @@ class SQLiteConnector {
26
26
  [AdminForthFilterOperators.NIN]: 'NOT IN',
27
27
  };
28
28
  this.SortDirectionsMap = {
29
- [AdminForthSortDirections.ASC]: 'ASC',
30
- [AdminForthSortDirections.DESC]: 'DESC',
29
+ [AdminForthSortDirections.asc]: 'ASC',
30
+ [AdminForthSortDirections.desc]: 'DESC',
31
31
  };
32
32
  this.db = betterSqlite3(url.replace('sqlite://', ''));
33
33
  }
package/dist/index.js CHANGED
@@ -541,7 +541,7 @@ class AdminForth {
541
541
  const { username, password } = body;
542
542
  let token;
543
543
  if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
544
- token = this.auth.issueJWT({ username, pk: null });
544
+ this.auth.setAuthCookie({ response, username, pk: null });
545
545
  }
546
546
  else {
547
547
  // get resource from db
@@ -575,15 +575,12 @@ class AdminForth {
575
575
  console.log('User record', userRecord, passwordHash); // why does it has no hash?
576
576
  const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
577
577
  if (valid) {
578
- token = this.auth.issueJWT({
579
- username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
580
- });
578
+ this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
581
579
  }
582
580
  else {
583
581
  return { error: INVALID_MESSAGE };
584
582
  }
585
583
  }
586
- response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
587
584
  return { ok: true };
588
585
  }),
589
586
  });
@@ -599,7 +596,7 @@ class AdminForth {
599
596
  method: 'POST',
600
597
  path: '/logout',
601
598
  handler: (_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
602
- response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
599
+ this.auth.removeAuthCookie({ response });
603
600
  return { ok: true };
604
601
  }),
605
602
  });
@@ -14,8 +14,8 @@ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser'])
14
14
  :mode="'split'"
15
15
  :theme="'light'"
16
16
  :language="'JSON'"
17
- :prev="props.record[props.meta.resourceColumns.resourceDataColumnName].oldRecord"
18
- :current="props.record[props.meta.resourceColumns.resourceDataColumnName].newRecord"
17
+ :prev="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).oldRecord, null, 2)"
18
+ :current="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).newRecord, null, 2)"
19
19
  />
20
20
  </template>
21
21
 
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import { AllowedActionsEnum, AdminForthSortDirections } from "../../types/AdminForthConfig.js";
10
11
  import AdminForthPlugin from "../base.js";
11
12
  class AuditLogPlugin extends AdminForthPlugin {
12
13
  constructor(options) {
@@ -16,16 +17,41 @@ class AuditLogPlugin extends AdminForthPlugin {
16
17
  const recordIdFieldName = (_a = resource.columns.find((c) => c.primaryKey === true)) === null || _a === void 0 ? void 0 : _a.name;
17
18
  const recordId = (data === null || data === void 0 ? void 0 : data[recordIdFieldName]) || (oldRecord === null || oldRecord === void 0 ? void 0 : oldRecord[recordIdFieldName]);
18
19
  const connector = this.adminforth.connectors[resource.dataSource];
19
- const newRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
20
- let newData = {
21
- 'oldRecord': oldRecord || {},
22
- 'newRecord': newRecord
23
- };
24
- // console.log('newData', newData)
20
+ const newRecord = action == AllowedActionsEnum.delete ? {} : (yield connector.getRecordByPrimaryKey(resource, recordId)) || {};
21
+ if (action !== AllowedActionsEnum.delete) {
22
+ oldRecord = oldRecord ? JSON.parse(JSON.stringify(oldRecord)) : {};
23
+ }
24
+ else {
25
+ oldRecord = data;
26
+ }
27
+ if (action !== AllowedActionsEnum.delete) {
28
+ const columnsNamesList = resource.columns.map((c) => c.name);
29
+ columnsNamesList.forEach((key) => {
30
+ if (JSON.stringify(oldRecord[key]) == JSON.stringify(newRecord[key])) {
31
+ delete oldRecord[key];
32
+ delete newRecord[key];
33
+ }
34
+ });
35
+ }
36
+ const backendOnlyColumns = resource.columns.filter((c) => c.backendOnly);
37
+ backendOnlyColumns.forEach((c) => {
38
+ if (JSON.stringify(oldRecord[c.name]) != JSON.stringify(newRecord[c.name])) {
39
+ if (action !== AllowedActionsEnum.delete) {
40
+ newRecord[c.name] = '<hidden value after>';
41
+ }
42
+ if (action !== AllowedActionsEnum.create) {
43
+ oldRecord[c.name] = '<hidden value before>';
44
+ }
45
+ }
46
+ else {
47
+ delete oldRecord[c.name];
48
+ delete newRecord[c.name];
49
+ }
50
+ });
25
51
  const record = {
26
52
  [this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
27
53
  [this.options.resourceColumns.resourceActionColumnName]: action,
28
- [this.options.resourceColumns.resourceDataColumnName]: newData,
54
+ [this.options.resourceColumns.resourceDataColumnName]: { 'oldRecord': oldRecord || {}, 'newRecord': newRecord },
29
55
  [this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
30
56
  [this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
31
57
  [this.options.resourceColumns.resourceCreatedColumnName]: new Date()
@@ -54,18 +80,23 @@ class AuditLogPlugin extends AdminForthPlugin {
54
80
  // type: AdminForthDataTypes.STRING
55
81
  };
56
82
  }
83
+ diffColumn.showIn = ['show'];
57
84
  diffColumn.components = {
58
85
  show: {
59
86
  file: this.componentPath('AuditLogView.vue'),
60
87
  meta: Object.assign(Object.assign({}, this.options), { pluginInstanceId: this.pluginInstanceId })
61
88
  }
62
89
  };
90
+ resource.options.defaultSort = {
91
+ columnName: this.options.resourceColumns.resourceCreatedColumnName,
92
+ direction: AdminForthSortDirections.desc
93
+ };
63
94
  return;
64
95
  }
65
96
  const defaultHooks = {
66
97
  create: { afterSave: [] },
67
98
  edit: { afterSave: [] },
68
- delete: { afterSave: [] }
99
+ delete: { beforeSave: [] }
69
100
  };
70
101
  if (!resource.hooks) {
71
102
  resource.hooks = defaultHooks;
@@ -74,10 +105,11 @@ class AuditLogPlugin extends AdminForthPlugin {
74
105
  resource.hooks = Object.assign(Object.assign({}, defaultHooks), resource.hooks);
75
106
  }
76
107
  Object.keys(resource.hooks).forEach((hook) => {
77
- if (!Array.isArray(resource.hooks[hook].afterSave)) {
78
- resource.hooks[hook].afterSave = [resource.hooks[hook].afterSave];
108
+ const hookToUse = hook == AllowedActionsEnum.delete ? 'beforeSave' : 'afterSave';
109
+ if (!Array.isArray(resource.hooks[hook][hookToUse])) {
110
+ resource.hooks[hook][hookToUse] = [resource.hooks[hook][hookToUse]];
79
111
  }
80
- resource.hooks[hook].afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ resource, record, adminUser, oldRecord }) {
112
+ resource.hooks[hook][hookToUse].push((_a) => __awaiter(this, [_a], void 0, function* ({ resource, record, adminUser, oldRecord }) {
81
113
  return yield this.createLogRecord(resource, hook, record, adminUser, oldRecord);
82
114
  }));
83
115
  });
@@ -149,7 +149,7 @@ class ExpressServer {
149
149
  res.status(401).send('Unauthorized by AdminForth');
150
150
  return;
151
151
  }
152
- const adminforthUser = yield this.adminforth.auth.verify(jwt);
152
+ const adminforthUser = yield this.adminforth.auth.verify(jwt, 'auth');
153
153
  if (!adminforthUser) {
154
154
  res.status(401).send('Unauthorized by AdminForth');
155
155
  }
@@ -252,7 +252,8 @@ const props = defineProps([
252
252
  'rows',
253
253
  'totalRows',
254
254
  'pageSize',
255
- 'checkboxes'
255
+ 'checkboxes',
256
+ 'sort'
256
257
  ])
257
258
 
258
259
  // emits, update page
@@ -288,6 +289,10 @@ watch(() => props.checkboxes, (newCheckboxes) => {
288
289
  checkboxesInternal.value = newCheckboxes;
289
290
  });
290
291
 
292
+ watch(() => props.sort, (newSort) => {
293
+ sort.value = newSort;
294
+ });
295
+
291
296
  function addToCheckedValues(id) {
292
297
  if (checkboxesInternal.value.includes(id)) {
293
298
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
@@ -89,6 +89,7 @@
89
89
  @update:sort="sort = $event"
90
90
  @update:checkboxes="checkboxes = $event"
91
91
  @update:records="getList"
92
+ :sort="sort"
92
93
  :pageSize="pageSize"
93
94
  :totalRows="totalRows"
94
95
  :checkboxes="checkboxes"
@@ -229,6 +230,15 @@ async function init() {
229
230
  resourceId: route.params.resourceId
230
231
  });
231
232
 
233
+ if (coreStore.resource.options?.defaultSort) {
234
+ sort.value = [{
235
+ field: coreStore.resource.options.defaultSort.columnName,
236
+ direction: coreStore.resource.options.defaultSort.direction
237
+ }];
238
+ } else {
239
+ sort.value = [];
240
+ }
241
+
232
242
  await getList();
233
243
  columnsMinMax.value = await callAdminForthApi({
234
244
  path: '/get_min_max_for_columns',
@@ -250,7 +260,6 @@ onMounted(async () => {
250
260
  watch(() => route.params.resourceId, async () => {
251
261
  filtersStore.setFilters([]);
252
262
  checkboxes.value = [];
253
- sort.value = [];
254
263
  await init();
255
264
  });
256
265
 
@@ -99,7 +99,7 @@ export var AdminForthFilterOperators;
99
99
  ;
100
100
  export var AdminForthSortDirections;
101
101
  (function (AdminForthSortDirections) {
102
- AdminForthSortDirections["ASC"] = "asc";
103
- AdminForthSortDirections["DESC"] = "desc";
102
+ AdminForthSortDirections["asc"] = "asc";
103
+ AdminForthSortDirections["desc"] = "desc";
104
104
  })(AdminForthSortDirections || (AdminForthSortDirections = {}));
105
105
  ;
package/index.ts CHANGED
@@ -626,7 +626,7 @@ class AdminForth implements AdminForthClass {
626
626
  const { username, password } = body;
627
627
  let token;
628
628
  if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
629
- token = this.auth.issueJWT({ username, pk: null });
629
+ this.auth.setAuthCookie({ response, username, pk: null });
630
630
  } else {
631
631
  // get resource from db
632
632
  if (!this.config.auth) {
@@ -664,15 +664,11 @@ class AdminForth implements AdminForthClass {
664
664
  console.log('User record', userRecord, passwordHash) // why does it has no hash?
665
665
  const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
666
666
  if (valid) {
667
- token = this.auth.issueJWT({
668
- username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
669
- });
667
+ this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
670
668
  } else {
671
669
  return { error: INVALID_MESSAGE };
672
670
  }
673
671
  }
674
-
675
- response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
676
672
  return { ok: true };
677
673
  },
678
674
  });
@@ -690,7 +686,7 @@ class AdminForth implements AdminForthClass {
690
686
  method: 'POST',
691
687
  path: '/logout',
692
688
  handler: async ({ response }) => {
693
- response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
689
+ this.auth.removeAuthCookie({ response });
694
690
  return { ok: true };
695
691
  },
696
692
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.1.66",
3
+ "version": "1.1.68",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,8 +14,8 @@ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser'])
14
14
  :mode="'split'"
15
15
  :theme="'light'"
16
16
  :language="'JSON'"
17
- :prev="props.record[props.meta.resourceColumns.resourceDataColumnName].oldRecord"
18
- :current="props.record[props.meta.resourceColumns.resourceDataColumnName].newRecord"
17
+ :prev="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).oldRecord, null, 2)"
18
+ :current="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).newRecord, null, 2)"
19
19
  />
20
20
  </template>
21
21
 
@@ -6,7 +6,8 @@ import {
6
6
  AdminUser,
7
7
  AdminForthDataTypes,
8
8
  AdminForthResourcePages,
9
- AdminForthFilterOperators
9
+ AdminForthFilterOperators,
10
+ AdminForthSortDirections
10
11
  } from "../../types/AdminForthConfig.js";
11
12
  import AdminForthPlugin from "../base.js";
12
13
  import { PluginOptions } from "./types.js";
@@ -30,17 +31,43 @@ export default class AuditLogPlugin extends AdminForthPlugin {
30
31
  const recordIdFieldName = resource.columns.find((c) => c.primaryKey === true)?.name;
31
32
  const recordId = data?.[recordIdFieldName] || oldRecord?.[recordIdFieldName];
32
33
  const connector = this.adminforth.connectors[resource.dataSource];
33
- const newRecord = await connector.getRecordByPrimaryKey(resource, recordId);
34
34
 
35
- let newData = {
36
- 'oldRecord': oldRecord || {},
37
- 'newRecord': newRecord
35
+ const newRecord = action == AllowedActionsEnum.delete ? {} : (await connector.getRecordByPrimaryKey(resource, recordId)) || {};
36
+ if (action !== AllowedActionsEnum.delete) {
37
+ oldRecord = oldRecord ? JSON.parse(JSON.stringify(oldRecord)) : {};
38
+ } else {
39
+ oldRecord = data
38
40
  }
39
- // console.log('newData', newData)
41
+
42
+ if (action !== AllowedActionsEnum.delete) {
43
+ const columnsNamesList = resource.columns.map((c) => c.name);
44
+ columnsNamesList.forEach((key) => {
45
+ if (JSON.stringify(oldRecord[key]) == JSON.stringify(newRecord[key])) {
46
+ delete oldRecord[key];
47
+ delete newRecord[key];
48
+ }
49
+ });
50
+ }
51
+
52
+ const backendOnlyColumns = resource.columns.filter((c) => c.backendOnly);
53
+ backendOnlyColumns.forEach((c) => {
54
+ if (JSON.stringify(oldRecord[c.name]) != JSON.stringify(newRecord[c.name])) {
55
+ if (action !== AllowedActionsEnum.delete) {
56
+ newRecord[c.name] = '<hidden value after>'
57
+ }
58
+ if (action !== AllowedActionsEnum.create) {
59
+ oldRecord[c.name] = '<hidden value before>'
60
+ }
61
+ } else {
62
+ delete oldRecord[c.name];
63
+ delete newRecord[c.name];
64
+ }
65
+ });
66
+
40
67
  const record = {
41
68
  [this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
42
69
  [this.options.resourceColumns.resourceActionColumnName]: action,
43
- [this.options.resourceColumns.resourceDataColumnName]: newData,
70
+ [this.options.resourceColumns.resourceDataColumnName]: { 'oldRecord': oldRecord || {}, 'newRecord': newRecord },
44
71
  [this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
45
72
  [this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
46
73
  [this.options.resourceColumns.resourceCreatedColumnName]: new Date()
@@ -69,7 +96,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
69
96
  // type: AdminForthDataTypes.STRING
70
97
  }
71
98
  }
72
-
99
+
100
+ diffColumn.showIn = ['show']
73
101
  diffColumn.components = {
74
102
  show: {
75
103
  file: this.componentPath('AuditLogView.vue'),
@@ -79,13 +107,17 @@ export default class AuditLogPlugin extends AdminForthPlugin {
79
107
  }
80
108
  }
81
109
  }
110
+ resource.options.defaultSort = {
111
+ columnName: this.options.resourceColumns.resourceCreatedColumnName,
112
+ direction: AdminForthSortDirections.desc
113
+ }
82
114
  return;
83
115
  }
84
116
 
85
117
  const defaultHooks = {
86
118
  create: { afterSave: [] },
87
119
  edit: { afterSave: [] },
88
- delete: { afterSave: [] }
120
+ delete: { beforeSave: [] }
89
121
  }
90
122
 
91
123
  if ( !resource.hooks ) {
@@ -95,10 +127,11 @@ export default class AuditLogPlugin extends AdminForthPlugin {
95
127
  }
96
128
 
97
129
  Object.keys(resource.hooks).forEach((hook) => {
98
- if(!Array.isArray(resource.hooks[hook].afterSave)){
99
- resource.hooks[hook].afterSave = [resource.hooks[hook].afterSave]
130
+ const hookToUse = hook == AllowedActionsEnum.delete ? 'beforeSave' : 'afterSave';
131
+ if(!Array.isArray(resource.hooks[hook][hookToUse])){
132
+ resource.hooks[hook][hookToUse] = [resource.hooks[hook][hookToUse]]
100
133
  }
101
- resource.hooks[hook].afterSave.push(async ({resource, record, adminUser, oldRecord}) => {
134
+ resource.hooks[hook][hookToUse].push(async ({resource, record, adminUser, oldRecord}) => {
102
135
  return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, oldRecord)
103
136
  })
104
137
  })
@@ -157,7 +157,7 @@ class ExpressServer implements ExpressHttpServer {
157
157
  res.status(401).send('Unauthorized by AdminForth');
158
158
  return
159
159
  }
160
- const adminforthUser = await this.adminforth.auth.verify(jwt);
160
+ const adminforthUser = await this.adminforth.auth.verify(jwt, 'auth');
161
161
  if (!adminforthUser) {
162
162
  res.status(401).send('Unauthorized by AdminForth');
163
163
  } else {
@@ -252,7 +252,8 @@ const props = defineProps([
252
252
  'rows',
253
253
  'totalRows',
254
254
  'pageSize',
255
- 'checkboxes'
255
+ 'checkboxes',
256
+ 'sort'
256
257
  ])
257
258
 
258
259
  // emits, update page
@@ -288,6 +289,10 @@ watch(() => props.checkboxes, (newCheckboxes) => {
288
289
  checkboxesInternal.value = newCheckboxes;
289
290
  });
290
291
 
292
+ watch(() => props.sort, (newSort) => {
293
+ sort.value = newSort;
294
+ });
295
+
291
296
  function addToCheckedValues(id) {
292
297
  if (checkboxesInternal.value.includes(id)) {
293
298
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
@@ -89,6 +89,7 @@
89
89
  @update:sort="sort = $event"
90
90
  @update:checkboxes="checkboxes = $event"
91
91
  @update:records="getList"
92
+ :sort="sort"
92
93
  :pageSize="pageSize"
93
94
  :totalRows="totalRows"
94
95
  :checkboxes="checkboxes"
@@ -229,6 +230,15 @@ async function init() {
229
230
  resourceId: route.params.resourceId
230
231
  });
231
232
 
233
+ if (coreStore.resource.options?.defaultSort) {
234
+ sort.value = [{
235
+ field: coreStore.resource.options.defaultSort.columnName,
236
+ direction: coreStore.resource.options.defaultSort.direction
237
+ }];
238
+ } else {
239
+ sort.value = [];
240
+ }
241
+
232
242
  await getList();
233
243
  columnsMinMax.value = await callAdminForthApi({
234
244
  path: '/get_min_max_for_columns',
@@ -250,7 +260,6 @@ onMounted(async () => {
250
260
  watch(() => route.params.resourceId, async () => {
251
261
  filtersStore.setFilters([]);
252
262
  checkboxes.value = [];
253
- sort.value = [];
254
263
  await init();
255
264
  });
256
265
 
@@ -101,8 +101,9 @@ export interface AdminForthClass {
101
101
  createResourceRecord(params: { resource: AdminForthResource, record: any, adminUser: AdminUser }): Promise<any>;
102
102
 
103
103
  auth: {
104
+ verify(jwt : string, mustHaveType: string): Promise<any>;
104
105
 
105
- verify(jwt : string): Promise<any>;
106
+ issueJWT(payload: Object, type: string): string;
106
107
  }
107
108
 
108
109
  /**
@@ -544,6 +545,10 @@ export type AdminForthResource = {
544
545
  },
545
546
  },
546
547
  options?: {
548
+ defaultSort?: {
549
+ columnName: string,
550
+ direction: AdminForthSortDirections | string,
551
+ }
547
552
  bulkActions?: Array<{
548
553
  id?: string,
549
554
  label: string,
@@ -1072,8 +1077,8 @@ export enum AdminForthFilterOperators {
1072
1077
  };
1073
1078
 
1074
1079
  export enum AdminForthSortDirections {
1075
- ASC = 'asc',
1076
- DESC = 'desc',
1080
+ asc = 'asc',
1081
+ desc = 'desc',
1077
1082
  };
1078
1083
 
1079
1084