adminforth 1.1.23 → 1.1.25

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 (37) hide show
  1. package/dist/index.js +12 -12
  2. package/dist/plugins/AccessControl/index.js +3 -56
  3. package/dist/plugins/AuditLog/index.js +13 -0
  4. package/dist/plugins/AuditLog/types.js +1 -0
  5. package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +248 -0
  6. package/dist/spa/index.html +2 -2
  7. package/dist/spa/package-lock.json +1 -189
  8. package/dist/spa/package.json +1 -6
  9. package/dist/spa/spa/src/components/ShowTableItem.vue +14 -0
  10. package/dist/spa/spa/src/views/HomeView.vue +8 -0
  11. package/dist/spa/src/App.vue +57 -76
  12. package/dist/spa/src/components/AcceptModal.vue +1 -1
  13. package/dist/spa/src/components/CustomDatePicker.vue +3 -3
  14. package/dist/spa/src/components/CustomDateRangePicker.vue +3 -3
  15. package/dist/spa/src/components/Dropdown.vue +4 -13
  16. package/dist/spa/src/components/Filters.vue +22 -55
  17. package/dist/spa/src/components/MenuLink.vue +2 -2
  18. package/dist/spa/src/components/ResourceForm.vue +99 -157
  19. package/dist/spa/src/components/ValueRenderer.vue +1 -11
  20. package/dist/spa/src/router/index.ts +2 -3
  21. package/dist/spa/src/stores/core.ts +33 -22
  22. package/dist/spa/src/utils.ts +3 -5
  23. package/dist/spa/src/views/CreateView.vue +7 -15
  24. package/dist/spa/src/views/EditView.vue +8 -36
  25. package/dist/spa/src/views/HomeView.vue +8 -0
  26. package/dist/spa/src/views/ListView.vue +23 -35
  27. package/dist/spa/src/views/LoginView.vue +1 -1
  28. package/dist/spa/src/views/ResourceParent.vue +1 -1
  29. package/dist/spa/src/views/ShowView.vue +9 -20
  30. package/dist/spa/tailwind.config.js +2 -5
  31. package/dist/types/AdminForthConfig.js +9 -0
  32. package/index.ts +16 -18
  33. package/package.json +1 -1
  34. package/plugins/AuditLog/index.ts +29 -0
  35. package/plugins/AuditLog/types.ts +4 -0
  36. package/types/AdminForthConfig.ts +23 -1
  37. package/dist/spa/src/components/CustomRangePicker.vue +0 -148
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import ExpressServer from './servers/express.js';
23
23
  import { v1 as uuid } from 'uuid';
24
24
  import fs from 'fs';
25
25
  import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
26
- import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, AllowedActionsEnum } from './types/AdminForthConfig.js';
26
+ import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, AllowedActionsEnum, ActionCheckSource } from './types/AdminForthConfig.js';
27
27
  import path from 'path';
28
28
  //get array from enum AdminForthResourcePages
29
29
  class AdminForth {
@@ -633,13 +633,13 @@ class AdminForth {
633
633
  };
634
634
  }),
635
635
  });
636
- function interpretResource(adminUser, resource, meta) {
636
+ function interpretResource(adminUser, resource, meta, source) {
637
637
  return __awaiter(this, void 0, void 0, function* () {
638
638
  var _b;
639
639
  yield Promise.all(Object.entries(((_b = resource.options) === null || _b === void 0 ? void 0 : _b.allowedActions) || {}).map((_c) => __awaiter(this, [_c], void 0, function* ([key, value]) {
640
640
  // if callable then call
641
641
  if (typeof value === 'function') {
642
- resource.options.allowedActions[key] = yield value(adminUser, resource, meta);
642
+ resource.options.allowedActions[key] = yield value({ adminUser, resource, meta, source });
643
643
  }
644
644
  })));
645
645
  });
@@ -667,7 +667,7 @@ class AdminForth {
667
667
  if (!resource) {
668
668
  return { error: `Resource ${resourceId} not found` };
669
669
  }
670
- yield interpretResource(adminUser, resource, {});
670
+ yield interpretResource(adminUser, resource, {}, ActionCheckSource.DisplayButtons);
671
671
  // exclude "plugins" key
672
672
  return { resource: Object.assign(Object.assign({}, resource), { plugins: undefined }) };
673
673
  }),
@@ -691,7 +691,7 @@ class AdminForth {
691
691
  if (!resource) {
692
692
  return { error: `Resource ${resourceId} not found` };
693
693
  }
694
- yield interpretResource(adminUser, resource, { requestBody: body });
694
+ yield interpretResource(adminUser, resource, { requestBody: body }, ActionCheckSource.DisplayButtons);
695
695
  const { allowed, error } = checkAccess(source, resource);
696
696
  console.log('allowed', allowed, error);
697
697
  if (!allowed) {
@@ -891,7 +891,7 @@ class AdminForth {
891
891
  if (!resource) {
892
892
  return { error: `Resource '${body['resourceId']}' not found` };
893
893
  }
894
- yield interpretResource(adminUser, resource, { requestBody: body });
894
+ yield interpretResource(adminUser, resource, { requestBody: body }, ActionCheckSource.CreateRequest);
895
895
  const { allowed, error } = checkAccess(AllowedActionsEnum.create, resource);
896
896
  if (!allowed) {
897
897
  return { error };
@@ -963,11 +963,6 @@ class AdminForth {
963
963
  if (!resource) {
964
964
  return { error: `Resource '${body['resourceId']}' not found` };
965
965
  }
966
- yield interpretResource(adminUser, resource, { requestBody: body });
967
- const { allowed, error } = checkAccess(AllowedActionsEnum.edit, resource);
968
- if (!allowed) {
969
- return { error };
970
- }
971
966
  const recordId = body['recordId'];
972
967
  const connector = this.connectors[resource.dataSource];
973
968
  const oldRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
@@ -976,6 +971,11 @@ class AdminForth {
976
971
  return { error: `Record with ${primaryKeyColumn.name} ${recordId} not found` };
977
972
  }
978
973
  const record = body['record'];
974
+ yield interpretResource(adminUser, resource, { requestBody: body, record, oldRecord }, ActionCheckSource.EditRequest);
975
+ const { allowed, error } = checkAccess(AllowedActionsEnum.edit, resource);
976
+ if (!allowed) {
977
+ return { error };
978
+ }
979
979
  // execute hook if needed
980
980
  for (const hook of listify((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.edit) === null || _6 === void 0 ? void 0 : _6.beforeSave)) {
981
981
  const resp = yield hook({ resource, record, adminUser });
@@ -1035,7 +1035,7 @@ class AdminForth {
1035
1035
  if (resource.options.allowedActions.delete === false) {
1036
1036
  return { error: `Resource '${resource.resourceId}' does not allow delete action` };
1037
1037
  }
1038
- yield interpretResource(adminUser, resource, { requestBody: body });
1038
+ yield interpretResource(adminUser, resource, { requestBody: body }, ActionCheckSource.DeleteRequest);
1039
1039
  const { allowed, error } = checkAccess(AllowedActionsEnum.delete, resource);
1040
1040
  if (!allowed) {
1041
1041
  return { error };
@@ -1,15 +1,5 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { AllowedActionsEnum } from "../../types/AdminForthConfig.js";
11
1
  import AdminForthPlugin from "../base.js";
12
- class AccessControlPlugin extends AdminForthPlugin {
2
+ class AuditLogPlugin extends AdminForthPlugin {
13
3
  constructor(options) {
14
4
  super(options, import.meta.url);
15
5
  this.options = options;
@@ -17,50 +7,7 @@ class AccessControlPlugin extends AdminForthPlugin {
17
7
  modifyResourceConfig(adminforth, resourceConfig) {
18
8
  super.modifyResourceConfig(adminforth, resourceConfig);
19
9
  this.adminforth = adminforth;
20
- if (!resourceConfig.hooks) {
21
- resourceConfig.hooks = {};
22
- }
23
- const checkAccess = (adminUser, action, meta) => __awaiter(this, void 0, void 0, function* () {
24
- const hasAccessOrError = yield this.options.hasAccess(adminUser, action, meta);
25
- if (hasAccessOrError === true) {
26
- return { ok: true };
27
- }
28
- else {
29
- return { ok: false, error: hasAccessOrError || AccessControlPlugin.defaultError };
30
- }
31
- });
32
- const bindHookCheck = (action, hookName) => {
33
- if (!resourceConfig.hooks[action]) {
34
- resourceConfig.hooks[action] = {};
35
- }
36
- if (!resourceConfig.hooks[action][hookName]) {
37
- resourceConfig.hooks[action][hookName] = [];
38
- }
39
- if (hookName === 'beforeDatasourceRequest') {
40
- resourceConfig.hooks[action][hookName].unshift((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, query }) {
41
- return checkAccess(adminUser, action, { query });
42
- }));
43
- }
44
- else {
45
- resourceConfig.hooks[action][hookName].unshift((_b) => __awaiter(this, [_b], void 0, function* ({ adminUser, record }) {
46
- return checkAccess(adminUser, action, { record });
47
- }));
48
- }
49
- };
50
- // List check
51
- bindHookCheck(AllowedActionsEnum.list, 'beforeDatasourceRequest');
52
- // Show check
53
- bindHookCheck(AllowedActionsEnum.show, 'beforeDatasourceRequest');
54
- // Edit check
55
- bindHookCheck(AllowedActionsEnum.edit, 'beforeDatasourceRequest');
56
- // create check
57
- bindHookCheck(AllowedActionsEnum.create, 'beforeSave');
58
- // edit check
59
- bindHookCheck(AllowedActionsEnum.edit, 'beforeSave');
60
- // delete check
61
- bindHookCheck(AllowedActionsEnum.delete, 'beforeSave');
62
- console.log('resourceConfig', resourceConfig);
63
10
  }
64
11
  }
65
- AccessControlPlugin.defaultError = 'Sorry, you do not have access to this resource.';
66
- export default AccessControlPlugin;
12
+ AuditLogPlugin.defaultError = 'Sorry, you do not have access to this resource.';
13
+ export default AuditLogPlugin;
@@ -0,0 +1,13 @@
1
+ import AdminForthPlugin from "../base.js";
2
+ class AuditLogPlugin extends AdminForthPlugin {
3
+ constructor(options) {
4
+ super(options, import.meta.url);
5
+ this.options = options;
6
+ }
7
+ modifyResourceConfig(adminforth, resourceConfig) {
8
+ super.modifyResourceConfig(adminforth, resourceConfig);
9
+ this.adminforth = adminforth;
10
+ }
11
+ }
12
+ AuditLogPlugin.defaultError = 'Sorry, you do not have access to this resource.';
13
+ export default AuditLogPlugin;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,248 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <!-- todo exclude foreign column-->
4
+ <Filters
5
+ v-if="listResource"
6
+ :columns="listResource.columns.filter((c) => c.name !== listResourceRefColumn.name)"
7
+ v-model:filters="filters"
8
+ :columnsMinMax="columnsMinMax"
9
+ :show="filtersShow"
10
+ @hide="filtersShow = false"
11
+ />
12
+ </Teleport>
13
+
14
+ <td colspan="2">
15
+ <div class="flex items-center gap-1">
16
+ <h4 v-if="listResource"
17
+ class="px-6 py-4"
18
+ >{{ listResource.label }} inline records</h4>
19
+
20
+ <button
21
+ @click="()=>{checkboxes = []}"
22
+ v-if="checkboxes.length"
23
+ data-tooltip-target="tooltip-remove-all"
24
+ data-tooltip-placement="bottom"
25
+ class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
26
+ >
27
+ <IconBanOutline class="w-5 h-5 "/>
28
+ <div id="tooltip-remove-all" role="tooltip"
29
+ class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
30
+ Remove selection
31
+ <div class="tooltip-arrow" data-popper-arrow></div>
32
+ </div>
33
+ </button>
34
+
35
+ <button
36
+ v-if="checkboxes.length"
37
+ v-for="(action,i) in listResource?.options?.bulkActions"
38
+ :key="action.id"
39
+ @click="startBulkAction(action.id)"
40
+ class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
41
+ :class="{'bg-red-100 text-red-800 border-red-400 dark:bg-red-700 dark:text-red-400 dark:border-red-400':action.state==='danger', 'bg-green-100 text-green-800 border-green-400 dark:bg-green-700 dark:text-green-400 dark:border-green-400':action.state==='success',
42
+ 'bg-blue-100 text-blue-800 border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
43
+ }"
44
+ >
45
+ <component
46
+ v-if="action.icon"
47
+ :is="getIcon(action.icon)"
48
+ class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"></component>
49
+
50
+ {{ `${action.label} (${checkboxes.length})` }}
51
+ </button>
52
+
53
+ <RouterLink v-if="listResource?.options?.allowedActions?.create"
54
+ :to="{
55
+ name: 'resource-create',
56
+ params: { resourceId: listResource.resourceId },
57
+ query: {
58
+ values: encodeURIComponent(JSON.stringify({[listResourceRefColumn.name]: props.record[selfPrimaryKeyColumn.name]})),
59
+ returnTo: $route.fullPath,
60
+ },
61
+ }"
62
+ class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
63
+ >
64
+ <IconPlusOutline class="w-4 h-4 me-2"/>
65
+ Create
66
+ </RouterLink>
67
+
68
+ <button
69
+ class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
70
+ @click="()=>{filtersShow = !filtersShow}"
71
+ >
72
+ <IconFilterOutline class="w-4 h-4 me-2"/>
73
+ Filter
74
+ <span
75
+ class="bg-red-100 text-red-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
76
+ v-if="filters.length">
77
+ {{ filters.length }}
78
+ </span>
79
+ </button>
80
+
81
+ </div>
82
+
83
+ <ResourceListTable
84
+ :resource="listResource"
85
+ :rows="rows"
86
+ @update:page="page = $event"
87
+ @update:sort="sort = $event"
88
+ @update:checkboxes="checkboxes = $event"
89
+ :pageSize="pageSize"
90
+ :totalRows="totalRows"
91
+ :checkboxes="checkboxes"
92
+ />
93
+
94
+ </td>
95
+ </template>
96
+
97
+ <script setup>
98
+ import { callAdminForthApi } from '@/utils';
99
+ import { ref, onMounted, watch, computed } from 'vue';
100
+ import ResourceListTable from '@/components/ResourceListTable.vue';
101
+ import Filters from '@/components/Filters.vue';
102
+ import {
103
+ IconBanOutline,
104
+ IconFilterOutline,
105
+ IconPlusOutline,
106
+ } from '@iconify-prerendered/vue-flowbite';
107
+
108
+ import { getIcon } from '@/utils';
109
+
110
+ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
111
+
112
+ const listResource = ref(null);
113
+ const loading = ref(true);
114
+
115
+ const page = ref(1);
116
+ const sort = ref([]);
117
+ const checkboxes = ref([]);
118
+ const pageSize = computed(() => listResource.value?.options?.listPageSize || 10);
119
+
120
+ const rows = ref(null);
121
+ const totalRows = ref(0);
122
+
123
+ const filters = ref([]);
124
+ const filtersShow = ref(false);
125
+ const columnsMinMax = ref(null);
126
+
127
+ const listResourceRefColumn = computed(() => {
128
+ if (!listResource.value) {
129
+ return null;
130
+ }
131
+ return listResource.value.columns.find(c => c.foreignResource?.resourceId === props.resource.resourceId);
132
+ });
133
+
134
+ const selfPrimaryKeyColumn = computed(() => {
135
+ return props.resource.columns.find(c => c.primaryKey);
136
+ });
137
+
138
+ const endFilters = computed(() => {
139
+ if (!listResource.value) {
140
+ return [];
141
+ }
142
+ // get name of the column that is foreign key
143
+ const refColumn = listResourceRefColumn.value;
144
+
145
+ const primaryKeyColumn = selfPrimaryKeyColumn.value;
146
+
147
+ if (!refColumn) {
148
+ window.adminforth.alert({
149
+ message: `Column with foreignResource.resourceId which is equal to '${props.resource.resourceId}' not found in resource which is specified as foreighResourceId '${listResource.value.resourceId}'`,
150
+ variant: 'danger',
151
+ });
152
+ return [];
153
+ }
154
+ return [
155
+ ...filters.value,
156
+ {
157
+ field: refColumn.name,
158
+ operator: 'eq',
159
+ value: props.record[primaryKeyColumn.name],
160
+ },
161
+ ];
162
+ });
163
+
164
+ watch([page], async () => {
165
+ await getList();
166
+ });
167
+
168
+ watch([sort], async () => {
169
+ await getList();
170
+ }, {deep: true});
171
+
172
+ watch([filters], async () => {
173
+ page.value = 1;
174
+ checkboxes.value = [];
175
+ await getList();
176
+ }, {deep: true});
177
+
178
+
179
+ async function startBulkAction(actionId) {
180
+ const data = await callAdminForthApi({
181
+ path: '/start_bulk_action',
182
+ method: 'POST',
183
+ body: {
184
+ resourceId: listResource.value.resourceId,
185
+ actionId: actionId,
186
+ recordIds: checkboxes.value
187
+ }
188
+ });
189
+ if (data?.status === 'success') {
190
+ checkboxes.value = [];
191
+ }
192
+ await getList();
193
+ }
194
+
195
+ async function getList() {
196
+ rows.value = null;
197
+ console.log('getList', listResource.value)
198
+ const data = await callAdminForthApi({
199
+ path: '/get_resource_data',
200
+ method: 'POST',
201
+ body: {
202
+ source: 'list',
203
+ resourceId: listResource.value.resourceId,
204
+ limit: pageSize.value,
205
+ offset: (page.value - 1) * pageSize.value,
206
+ filters: endFilters.value,
207
+ sort: sort.value,
208
+ }
209
+ });
210
+
211
+ if (data.error) {
212
+ window.adminforth.alert({
213
+ message: data.error,
214
+ variant: 'danger',
215
+ timeout: 'unlimited',
216
+ });
217
+ rows.value = [];
218
+ totalRows.value = 0;
219
+ return;
220
+ }
221
+
222
+ rows.value = data.data?.map(row => {
223
+ row._primaryKeyValue = row[listResource.value.columns.find(c => c.primaryKey).name];
224
+ return row;
225
+ });
226
+ totalRows.value = data.total;
227
+ }
228
+
229
+ onMounted( async () => {
230
+ loading.value = true;
231
+ const foreighResourceId = props.meta.foreignResourceId;
232
+ listResource.value = (await callAdminForthApi({
233
+ path: `/plugin/${props.meta.pluginInstanceId}/get_resource`,
234
+ method: 'POST',
235
+ body: {},
236
+ })).resource;
237
+ columnsMinMax.value = await callAdminForthApi({
238
+ path: '/get_min_max_for_columns',
239
+ method: 'POST',
240
+ body: {
241
+ resourceId: foreighResourceId,
242
+ }
243
+ });
244
+ loading.value = false;
245
+ await getList();
246
+ });
247
+
248
+ </script>
@@ -16,8 +16,8 @@
16
16
  </script> -->
17
17
 
18
18
  </head>
19
- <body class=" ">
20
- <div id="app" class="min-h-screen bg-html-bg dark:bg-gray-800"></div>
19
+ <body>
20
+ <div id="app" class="min-h-screen dark:bg-gray-800"></div>
21
21
  <script type="module" src="/src/main.ts"></script>
22
22
  </body>
23
23
  </html>