adminforth 1.1.65 → 1.1.67

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 (62) hide show
  1. package/auth.ts +19 -4
  2. package/dataConnectors/mongo.ts +7 -13
  3. package/dataConnectors/postgres.ts +2 -2
  4. package/dataConnectors/sqlite.ts +2 -2
  5. package/dist/auth.js +14 -4
  6. package/dist/dataConnectors/mongo.js +1 -9
  7. package/dist/index.js +4 -9
  8. package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
  9. package/dist/plugins/AuditLogPlugin/index.js +17 -13
  10. package/dist/servers/express.js +1 -1
  11. package/dist/spa/index.html +2 -2
  12. package/dist/spa/package-lock.json +17 -4
  13. package/dist/spa/package.json +2 -1
  14. package/dist/spa/spa/src/App.vue +24 -12
  15. package/dist/spa/spa/src/composables/useStores.ts +16 -4
  16. package/dist/spa/spa/src/main.ts +1 -1
  17. package/dist/spa/src/App.vue +125 -45
  18. package/dist/spa/src/assets/logo.svg +19 -1
  19. package/dist/spa/src/components/AcceptModal.vue +2 -9
  20. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  21. package/dist/spa/src/components/CustomDatePicker.vue +16 -3
  22. package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
  23. package/dist/spa/src/components/Dropdown.vue +6 -1
  24. package/dist/spa/src/components/Filters.vue +40 -19
  25. package/dist/spa/src/components/ResourceForm.vue +26 -24
  26. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  27. package/dist/spa/src/components/Toast.vue +66 -0
  28. package/dist/spa/src/components/ValueRenderer.vue +14 -8
  29. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  30. package/dist/spa/src/composables/useStores.ts +113 -0
  31. package/dist/spa/src/main.ts +1 -1
  32. package/dist/spa/src/router/index.ts +30 -20
  33. package/dist/spa/src/spa_types/core.ts +51 -0
  34. package/dist/spa/src/stores/core.ts +48 -31
  35. package/dist/spa/src/stores/filters.ts +22 -0
  36. package/dist/spa/src/stores/modal.ts +13 -3
  37. package/dist/spa/src/stores/toast.ts +15 -0
  38. package/dist/spa/src/stores/user.ts +54 -0
  39. package/dist/spa/src/utils.ts +62 -7
  40. package/dist/spa/src/views/CreateView.vue +67 -15
  41. package/dist/spa/src/views/EditView.vue +45 -11
  42. package/dist/spa/src/views/ListView.vue +82 -366
  43. package/dist/spa/src/views/LoginView.vue +16 -4
  44. package/dist/spa/src/views/ShowView.vue +105 -21
  45. package/dist/spa/tailwind.config.js +1 -1
  46. package/dist/spa/vite.config.ts +6 -0
  47. package/index.ts +7 -12
  48. package/package.json +1 -1
  49. package/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
  50. package/plugins/AuditLogPlugin/index.ts +22 -15
  51. package/servers/express.ts +1 -1
  52. package/spa/src/App.vue +24 -12
  53. package/spa/src/composables/useStores.ts +16 -4
  54. package/spa/src/main.ts +1 -1
  55. package/types/AdminForthConfig.ts +18 -4
  56. package/types/FrontendAPI.ts +1 -15
  57. package/dist/plugins/AccessControl/index.js +0 -66
  58. package/dist/plugins/AccessControl/types.js +0 -1
  59. package/dist/spa/public/favicon.ico +0 -0
  60. package/dist/spa/spa/public/favicon.ico +0 -0
  61. package/dist/types.js +0 -30
  62. /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
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 {
@@ -1,11 +1,11 @@
1
1
  import dayjs from 'dayjs';
2
2
  import { MongoClient } from 'mongodb';
3
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
3
+ import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
4
4
 
5
- class MongoConnector {
5
+ class MongoConnector implements AdminForthDataSourceConnector {
6
6
  db: MongoClient
7
7
 
8
- constructor({ url }) {
8
+ constructor({ url }: { url: string }) {
9
9
  this.db = new MongoClient(url);
10
10
  (async () => {
11
11
  try {
@@ -65,16 +65,10 @@ class MongoConnector {
65
65
 
66
66
  getFieldValue(field, value) {
67
67
  if (field.type == AdminForthDataTypes.DATETIME) {
68
- if (!value) {
69
- return null;
70
- }
71
- if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
72
- return dayjs.unix(+value).toISOString();
73
- } else if (field._underlineType == 'varchar') {
74
- return dayjs.unix(+value).toISOString();
75
- } else {
76
- throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps)`);
77
- }
68
+ if (!value) {
69
+ return null;
70
+ }
71
+ return dayjs.unix(value).toISOString();
78
72
 
79
73
  } else if (field.type == AdminForthDataTypes.DATE) {
80
74
  if (!value) {
@@ -1,11 +1,11 @@
1
1
  import dayjs from 'dayjs';
2
2
  import pkg from 'pg';
3
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
3
+ import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
4
4
 
5
5
  const { Client } = pkg;
6
6
 
7
7
 
8
- class PostgresConnector {
8
+ class PostgresConnector implements AdminForthDataSourceConnector {
9
9
 
10
10
  db: any;
11
11
 
@@ -1,9 +1,9 @@
1
1
  import betterSqlite3 from 'better-sqlite3';
2
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
2
+ import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
3
3
 
4
4
  import dayjs from 'dayjs';
5
5
 
6
- class SQLiteConnector {
6
+ class SQLiteConnector implements AdminForthDataSourceConnector {
7
7
 
8
8
  db: any;
9
9
 
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
  }
@@ -71,15 +71,7 @@ class MongoConnector {
71
71
  if (!value) {
72
72
  return null;
73
73
  }
74
- if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
75
- return dayjs.unix(+value).toISOString();
76
- }
77
- else if (field._underlineType == 'varchar') {
78
- return dayjs.unix(+value).toISOString();
79
- }
80
- else {
81
- throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps)`);
82
- }
74
+ return dayjs.unix(value).toISOString();
83
75
  }
84
76
  else if (field.type == AdminForthDataTypes.DATE) {
85
77
  if (!value) {
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
  });
@@ -633,11 +630,9 @@ class AdminForth {
633
630
  let userFullName = '';
634
631
  if (adminUser.isRoot) {
635
632
  username = this.config.rootUser.username;
636
- console.log(username, 'this is the root');
637
633
  }
638
634
  else {
639
635
  const dbUser = adminUser.dbUser;
640
- console.log(dbUser, 'this is the db user', adminUser, 'this is the admin user');
641
636
  username = dbUser[this.config.auth.usernameField];
642
637
  userFullName = dbUser[this.config.auth.userFullNameField];
643
638
  }
@@ -1031,7 +1026,7 @@ class AdminForth {
1031
1026
  }
1032
1027
  // execute hook if needed
1033
1028
  for (const hook of listify((_3 = (_2 = resource.hooks) === null || _2 === void 0 ? void 0 : _2.edit) === null || _3 === void 0 ? void 0 : _3.afterSave)) {
1034
- const resp = yield hook({ resource, record, adminUser });
1029
+ const resp = yield hook({ resource, record, adminUser, oldRecord });
1035
1030
  if (!resp || (!resp.ok && !resp.error)) {
1036
1031
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
1037
1032
  }
@@ -2,19 +2,20 @@
2
2
  import VueDiff from 'vue-diff';
3
3
  import { ref } from 'vue';
4
4
  import 'vue-diff/dist/index.css';
5
-
6
- const props = defineProps(['prev', 'current', 'meta']);
5
+ import { app } from '@/main';
7
6
 
8
7
  app.use(VueDiff);
8
+
9
+ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
9
10
  </script>
10
11
 
11
12
  <template>
12
13
  <Diff
13
14
  :mode="'split'"
14
15
  :theme="'light'"
15
- :language="'plaintext'"
16
- :prev="props.prev"
17
- :current="props.current"
16
+ :language="'JSON'"
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)"
18
19
  />
19
- </template>
20
+ </template>
20
21
 
@@ -11,20 +11,25 @@ import AdminForthPlugin from "../base.js";
11
11
  class AuditLogPlugin extends AdminForthPlugin {
12
12
  constructor(options) {
13
13
  super(options, import.meta.url);
14
- this.createLogRecord = (resource, action, data, user) => __awaiter(this, void 0, void 0, function* () {
14
+ this.createLogRecord = (resource, action, data, user, oldRecord) => __awaiter(this, void 0, void 0, function* () {
15
15
  var _a;
16
16
  const recordIdFieldName = (_a = resource.columns.find((c) => c.primaryKey === true)) === null || _a === void 0 ? void 0 : _a.name;
17
- const recordId = data[recordIdFieldName];
17
+ const recordId = (data === null || data === void 0 ? void 0 : data[recordIdFieldName]) || (oldRecord === null || oldRecord === void 0 ? void 0 : oldRecord[recordIdFieldName]);
18
+ 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
+ };
18
24
  const record = {
19
25
  [this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
20
26
  [this.options.resourceColumns.resourceActionColumnName]: action,
21
- [this.options.resourceColumns.resourceDataColumnName]: data,
27
+ [this.options.resourceColumns.resourceDataColumnName]: newData,
22
28
  [this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
23
29
  [this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
24
30
  [this.options.resourceColumns.resourceCreatedColumnName]: new Date()
25
31
  };
26
32
  const auditLogResource = this.adminforth.config.resources.find((r) => r.resourceId === this.auditLogResource);
27
- console.log('rec', record);
28
33
  yield this.adminforth.createResourceRecord({ resource: auditLogResource, record, adminUser: user });
29
34
  return { ok: true };
30
35
  });
@@ -49,18 +54,17 @@ class AuditLogPlugin extends AdminForthPlugin {
49
54
  };
50
55
  }
51
56
  diffColumn.components = {
52
- showRow: {
57
+ show: {
53
58
  file: this.componentPath('AuditLogView.vue'),
54
59
  meta: Object.assign(Object.assign({}, this.options), { pluginInstanceId: this.pluginInstanceId })
55
60
  }
56
61
  };
57
- resource.columns.push(diffColumn);
58
62
  return;
59
63
  }
60
64
  const defaultHooks = {
61
- create: { beforeSave: [] },
62
- edit: { beforeSave: [] },
63
- delete: { beforeSave: [] }
65
+ create: { afterSave: [] },
66
+ edit: { afterSave: [] },
67
+ delete: { afterSave: [] }
64
68
  };
65
69
  if (!resource.hooks) {
66
70
  resource.hooks = defaultHooks;
@@ -69,11 +73,11 @@ class AuditLogPlugin extends AdminForthPlugin {
69
73
  resource.hooks = Object.assign(Object.assign({}, defaultHooks), resource.hooks);
70
74
  }
71
75
  Object.keys(resource.hooks).forEach((hook) => {
72
- if (!Array.isArray(resource.hooks[hook].beforeSave)) {
73
- resource.hooks[hook].beforeSave = [resource.hooks[hook].beforeSave];
76
+ if (!Array.isArray(resource.hooks[hook].afterSave)) {
77
+ resource.hooks[hook].afterSave = [resource.hooks[hook].afterSave];
74
78
  }
75
- resource.hooks[hook].beforeSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ resource, record, adminUser }) {
76
- return yield this.createLogRecord(resource, hook, record, adminUser);
79
+ resource.hooks[hook].afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ resource, record, adminUser, oldRecord }) {
80
+ return yield this.createLogRecord(resource, hook, record, adminUser, oldRecord);
77
81
  }));
78
82
  });
79
83
  });
@@ -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
  }
@@ -2,9 +2,9 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <link rel="icon" href="/favicon.ico">
5
+ <link rel="icon" href="/* IMPORTANT:ADMINFORTH FAVICON */">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Vite App</title>
7
+ <title>/* IMPORTANT:ADMINFORTH TITLE */</title>
8
8
  <!--
9
9
  <script>
10
10
  // On page load or when changing themes, best to add inline in `head` to avoid FOUC
@@ -17,6 +17,7 @@
17
17
  "flowbite-datepicker": "^1.2.6",
18
18
  "pinia": "^2.1.7",
19
19
  "unhead": "^1.9.12",
20
+ "uuid": "^10.0.0",
20
21
  "vue": "^3.4.21",
21
22
  "vue-router": "^4.3.0",
22
23
  "vue-slider-component": "^4.1.0-beta.7"
@@ -36,7 +37,7 @@
36
37
  "sass": "^1.77.2",
37
38
  "tailwindcss": "^3.4.3",
38
39
  "typescript": "~5.4.0",
39
- "vite": "^5.2.12",
40
+ "vite": "^5.2.13",
40
41
  "vue-tsc": "^2.0.11"
41
42
  }
42
43
  },
@@ -4032,10 +4033,22 @@
4032
4033
  "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
4033
4034
  "dev": true
4034
4035
  },
4036
+ "node_modules/uuid": {
4037
+ "version": "10.0.0",
4038
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
4039
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
4040
+ "funding": [
4041
+ "https://github.com/sponsors/broofa",
4042
+ "https://github.com/sponsors/ctavan"
4043
+ ],
4044
+ "bin": {
4045
+ "uuid": "dist/bin/uuid"
4046
+ }
4047
+ },
4035
4048
  "node_modules/vite": {
4036
- "version": "5.2.12",
4037
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz",
4038
- "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==",
4049
+ "version": "5.2.13",
4050
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz",
4051
+ "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==",
4039
4052
  "dev": true,
4040
4053
  "dependencies": {
4041
4054
  "esbuild": "^0.20.1",
@@ -21,6 +21,7 @@
21
21
  "flowbite-datepicker": "^1.2.6",
22
22
  "pinia": "^2.1.7",
23
23
  "unhead": "^1.9.12",
24
+ "uuid": "^10.0.0",
24
25
  "vue": "^3.4.21",
25
26
  "vue-router": "^4.3.0",
26
27
  "vue-slider-component": "^4.1.0-beta.7"
@@ -40,7 +41,7 @@
40
41
  "sass": "^1.77.2",
41
42
  "tailwindcss": "^3.4.3",
42
43
  "typescript": "~5.4.0",
43
- "vite": "^5.2.12",
44
+ "vite": "^5.2.13",
44
45
  "vue-tsc": "^2.0.11"
45
46
  }
46
47
  }
@@ -163,7 +163,7 @@
163
163
  </style>
164
164
 
165
165
  <script setup lang="ts">
166
- import { computed, onMounted, ref, watch, defineComponent } from 'vue';
166
+ import { computed, onMounted, ref, watch, defineComponent, onBeforeMount } from 'vue';
167
167
  import { RouterLink, RouterView } from 'vue-router';
168
168
  import { initFlowbite } from 'flowbite'
169
169
  import './index.scss'
@@ -211,6 +211,8 @@ const theme = ref('light');
211
211
  function toggleTheme() {
212
212
  theme.value = theme.value === 'light' ? 'dark' : 'light';
213
213
  document.documentElement.classList.toggle('dark');
214
+ window.localStorage.setItem('theme', theme.value);
215
+
214
216
  }
215
217
 
216
218
  function clickOnMenuItem (label: string) {
@@ -237,14 +239,8 @@ async function initRouter() {
237
239
  }
238
240
 
239
241
  async function loadMenu() {
240
- await coreStore.fetchMenuAndResource()
242
+ await coreStore.fetchMenuAndResource();
241
243
  loginRedirectCheckIsReady.value = true;
242
-
243
- coreStore.menu.forEach((item, i) => {
244
- if (item.open) {
245
- opened.value.push(i);
246
- }
247
- });
248
244
  }
249
245
 
250
246
 
@@ -256,16 +252,32 @@ watch(route, () => {
256
252
  })
257
253
  });
258
254
 
255
+ watch (()=>coreStore.menu, () => {
256
+ coreStore.menu.forEach((item, i) => {
257
+ if (item.open) {
258
+ opened.value.push(i);
259
+ };
260
+ });
261
+ })
262
+
263
+ watch([loggedIn, routerIsReady, loginRedirectCheckIsReady], ([l,r,lr]) => {
264
+ if (l && r && lr) {
265
+ setTimeout(() => {
266
+ initFlowbite();
267
+ });
268
+ }
269
+ })
270
+
259
271
  // initialize components based on data attribute selectors
260
272
  onMounted(async () => {
261
273
  loadMenu(); // run this in async mode
262
274
  // before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
263
275
  await initRouter()
264
- setTimeout(() => {
265
- initFlowbite();
266
- });
267
-
276
+ })
268
277
 
278
+ onBeforeMount(()=>{
279
+ theme.value = window.localStorage.getItem('theme') || 'light';
280
+ document.documentElement.classList.toggle('dark', theme.value === 'dark');
269
281
  })
270
282
 
271
283
  </script>
@@ -1,4 +1,5 @@
1
- import type { FrontendAPIInterface, ConfirmParams, AlertParams, FilterParams,Operator } from '../types/FrontendAPI';
1
+ import type { FrontendAPIInterface, ConfirmParams, AlertParams, } from '../types/FrontendAPI';
2
+ import type { AdminForthFilterOperators } from '@/types/AdminForthConfig';
2
3
  import { useToastStore } from '../stores/toast';
3
4
  import { useModalStore } from '../stores/modal';
4
5
  import { useCoreStore } from '@/stores/core';
@@ -6,9 +7,20 @@ import { useFiltersStore } from '@/stores/filters';
6
7
  import router from '@/router'
7
8
  import type { AdminForthResourceColumn } from '@/types/AdminForthConfig';
8
9
 
9
-
10
-
11
-
10
+ type FilterParams = {
11
+ /**
12
+ * Field of resource to filter
13
+ */
14
+ field: string;
15
+ /**
16
+ * Operator of filter
17
+ */
18
+ operator: AdminForthFilterOperators;
19
+ /**
20
+ * Value of filter
21
+ */
22
+ value: string | number | boolean ;
23
+ }
12
24
 
13
25
  declare global {
14
26
  interface Window {
@@ -6,7 +6,7 @@ import { createPinia } from 'pinia'
6
6
  import App from './App.vue'
7
7
  import router from './router'
8
8
 
9
- const app = createApp(App)
9
+ export const app = createApp(App)
10
10
  /* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
11
11
 
12
12