adminforth 1.2.36 → 1.2.38

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/basePlugin.ts CHANGED
@@ -3,7 +3,10 @@ import { getComponentNameFromPath } from './modules/utils.js';
3
3
  import { currentFileDir } from './modules/utils.js';
4
4
  import path from 'path';
5
5
  import fs from 'fs';
6
- import { v4 as uuidv4 } from 'uuid';
6
+
7
+ // @ts-ignore
8
+ import sha256 from 'crypto-js/sha256';
9
+
7
10
 
8
11
  export default class AdminForthPlugin implements IAdminForthPlugin {
9
12
 
@@ -12,19 +15,28 @@ export default class AdminForthPlugin implements IAdminForthPlugin {
12
15
  customFolderName: string = 'custom';
13
16
  pluginInstanceId: string;
14
17
  customFolderPath: string;
18
+ pluginOptions: any;
15
19
 
16
20
  constructor(pluginOptions: any, metaUrl: string) {
17
21
  // set up plugin here
18
22
  this.pluginDir = currentFileDir(metaUrl);
19
- this.pluginInstanceId = uuidv4();
20
23
  this.customFolderPath = path.join(this.pluginDir, this.customFolderName);
24
+ this.pluginOptions = pluginOptions;
21
25
  }
22
26
 
23
27
  setupEndpoints(server: any) {
24
28
 
25
29
  }
26
30
 
31
+ instanceUniqueRepresentation(pluginOptions: any) : string {
32
+ return 'non-uniquely-identified';
33
+ }
34
+
27
35
  modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
36
+ this.pluginInstanceId = sha256(
37
+ `af_pl_${this.constructor.name}_${resourceConfig.resourceId}_${this.instanceUniqueRepresentation(this.pluginOptions)}`
38
+ ).toString();
39
+ console.log(`5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣ Plugin instance id: ${this.pluginInstanceId}`);
28
40
  this.adminforth = adminforth;
29
41
  }
30
42
 
@@ -2,18 +2,24 @@ import { getComponentNameFromPath } from './modules/utils.js';
2
2
  import { currentFileDir } from './modules/utils.js';
3
3
  import path from 'path';
4
4
  import fs from 'fs';
5
- import { v4 as uuidv4 } from 'uuid';
5
+ // @ts-ignore
6
+ import sha256 from 'crypto-js/sha256';
6
7
  export default class AdminForthPlugin {
7
8
  constructor(pluginOptions, metaUrl) {
8
9
  this.customFolderName = 'custom';
9
10
  // set up plugin here
10
11
  this.pluginDir = currentFileDir(metaUrl);
11
- this.pluginInstanceId = uuidv4();
12
12
  this.customFolderPath = path.join(this.pluginDir, this.customFolderName);
13
+ this.pluginOptions = pluginOptions;
13
14
  }
14
15
  setupEndpoints(server) {
15
16
  }
17
+ instanceUniqueRepresentation(pluginOptions) {
18
+ return 'non-uniquely-identified';
19
+ }
16
20
  modifyResourceConfig(adminforth, resourceConfig) {
21
+ this.pluginInstanceId = sha256(`af_pl_${this.constructor.name}_${resourceConfig.resourceId}_${this.instanceUniqueRepresentation(this.pluginOptions)}`).toString();
22
+ console.log(`5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣5️⃣ Plugin instance id: ${this.pluginInstanceId}`);
17
23
  this.adminforth = adminforth;
18
24
  }
19
25
  componentPath(componentFile) {
@@ -11,7 +11,8 @@ import { AdminForthResourcePages, AllowedActionsEnum, } from "../types/AdminFort
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
13
  import { guessLabelFromName } from './utils.js';
14
- import { v4 as uuid } from 'uuid';
14
+ // @ts-ignore
15
+ import sha256 from 'crypto-js/sha256';
15
16
  export default class ConfigValidator {
16
17
  constructor(adminforth, config) {
17
18
  this.adminforth = adminforth;
@@ -248,6 +249,7 @@ export default class ConfigValidator {
248
249
  allowed: (_b) => __awaiter(this, [_b], void 0, function* ({ resource, adminUser, allowedActions }) { return allowedActions.delete; }),
249
250
  action: (_c) => __awaiter(this, [_c], void 0, function* ({ selectedIds, adminUser }) {
250
251
  const connector = this.adminforth.connectors[res.dataSource];
252
+ // for now if at least one error, stop and return error
251
253
  let error = null;
252
254
  yield Promise.all(selectedIds.map((recordId) => __awaiter(this, void 0, void 0, function* () {
253
255
  const record = yield connector.getRecordByPrimaryKey(res, recordId);
@@ -257,6 +259,9 @@ export default class ConfigValidator {
257
259
  error = resp.error;
258
260
  }
259
261
  })));
262
+ if (error) {
263
+ return;
264
+ }
260
265
  yield connector.deleteRecord({ resource: res, recordId });
261
266
  // call afterDelete hook
262
267
  yield Promise.all(res.hooks.delete.afterSave.map((hook) => __awaiter(this, void 0, void 0, function* () {
@@ -270,10 +275,12 @@ export default class ConfigValidator {
270
275
  })
271
276
  });
272
277
  }
273
- const newBulkActions = bulkActions.map((action) => {
274
- return Object.assign(action, { id: uuid() });
278
+ bulkActions.map((action) => {
279
+ if (!action.id) {
280
+ action.id = sha256(action.label).toString();
281
+ }
275
282
  });
276
- res.options.bulkActions = newBulkActions;
283
+ res.options.bulkActions = bulkActions;
277
284
  // if pageInjection is a string, make array with one element. Also check file exists
278
285
  const possibleInjections = ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom'];
279
286
  if (res.options.pageInjections) {
@@ -200,9 +200,9 @@ export default class AdminForthRestAPI {
200
200
  function interpretResource(adminUser, resource, meta, source) {
201
201
  return __awaiter(this, void 0, void 0, function* () {
202
202
  var _a;
203
- if (process.env.HEAVY_DEBUG) {
204
- console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
205
- }
203
+ // if (process.env.HEAVY_DEBUG) {
204
+ // console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
205
+ // }
206
206
  const allowedActions = {};
207
207
  yield Promise.all(Object.entries(((_a = resource.options) === null || _a === void 0 ? void 0 : _a.allowedActions) || {}).map((_b) => __awaiter(this, [_b], void 0, function* ([key, value]) {
208
208
  if (process.env.HEAVY_DEBUG) {
@@ -246,7 +246,6 @@ export default class AdminForthRestAPI {
246
246
  yield Promise.all(resource.options.bulkActions.map((action) => __awaiter(this, void 0, void 0, function* () {
247
247
  if (action.allowed) {
248
248
  const res = yield action.allowed({ adminUser, resource, allowedActions });
249
- console.log('🪲🪲🪲🪲checking for allowedActions', allowedActions, 'res', res);
250
249
  if (res) {
251
250
  allowedBulkActions.push(action);
252
251
  }
@@ -622,13 +621,10 @@ export default class AdminForthRestAPI {
622
621
  if (!execAllowed) {
623
622
  return { error: `Action '${actionId}' is not allowed` };
624
623
  }
625
- yield action.action({ selectedIds: recordIds, adminUser, resource });
626
- return {
627
- actionId,
624
+ const response = yield action.action({ selectedIds: recordIds, adminUser, resource });
625
+ return Object.assign({ actionId,
628
626
  recordIds,
629
- resourceId,
630
- status: 'success'
631
- };
627
+ resourceId }, response);
632
628
  })
633
629
  });
634
630
  // setup endpoints for all plugins
@@ -41,7 +41,7 @@
41
41
  :value="currentValues[column.name]"
42
42
  @update:value="setCurrentValue(column.name, $event)"
43
43
  :meta="column.components[props.source].meta"
44
- :record="props.record"
44
+ :record="currentValues"
45
45
  @update:inValidity="customComponentsInValidity[column.name] = $event"
46
46
  @update:emptiness="customComponentsEmptiness[column.name] = $event"
47
47
  />
@@ -238,6 +238,8 @@ const setCurrentValue = (key, value) => {
238
238
  } else {
239
239
  currentValues.value[key] = value;
240
240
  }
241
+ currentValues.value = { ...currentValues.value };
242
+ console.log('3️⃣ setCurrentValue', key, value);
241
243
  emit('update:record', currentValues.value);
242
244
  };
243
245
 
@@ -228,7 +228,7 @@ import { callAdminForthApi, getIcon } from '@/utils';
228
228
  import ValueRenderer from '@/components/ValueRenderer.vue';
229
229
  import { getCustomComponent } from '@/utils';
230
230
  import { useCoreStore } from '@/stores/core';
231
- import { showSuccesTost } from '@/composables/useFrontendApi';
231
+ import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
232
232
  import SkeleteLoader from '@/components/SkeleteLoader.vue';
233
233
 
234
234
  import {
@@ -368,22 +368,24 @@ async function deleteRecord(row) {
368
368
  if (data) {
369
369
  try {
370
370
  const res = await callAdminForthApi({
371
- path: '/delete_record',
372
- method: 'POST',
373
- body: {
374
- resourceId: props.resource.resourceId,
375
- primaryKey: row._primaryKeyValue,
376
- }});
371
+ path: '/delete_record',
372
+ method: 'POST',
373
+ body: {
374
+ resourceId: props.resource.resourceId,
375
+ primaryKey: row._primaryKeyValue,
376
+ }
377
+ });
377
378
  if (!res.error){
378
379
  emits('update:records', true)
379
380
  showSuccesTost('Record deleted successfully')
380
381
  } else {
381
- console.error(res.error)
382
+ showErrorTost(res.error)
382
383
  }
383
384
 
384
385
  } catch (e) {
386
+ showErrorTost(`Something went wrong, please try again later`);
385
387
  console.error(e);
386
- };
387
- }
388
+ };
389
+ }
388
390
  }
389
391
  </script>
@@ -42,7 +42,6 @@ export async function callAdminForthApi({ path, method, body=undefined }: {
42
42
 
43
43
  export function getCustomComponent({ file, meta }: { file: string, meta: any }) {
44
44
  const name = file.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
45
- console.log('resolving name', name);
46
45
  return resolveComponent(name);
47
46
  }
48
47
 
@@ -124,11 +124,7 @@ import { getCustomComponent } from '@/utils';
124
124
 
125
125
  import {
126
126
  IconBanOutline,
127
- IconEyeSolid,
128
127
  IconFilterOutline,
129
- IconPenSolid,
130
- IconTrashBinSolid,
131
- IconInboxOutline,
132
128
  IconPlusOutline
133
129
  } from '@iconify-prerendered/vue-flowbite';
134
130
 
@@ -137,13 +133,11 @@ import Filters from '@/components/Filters.vue';
137
133
  const filtersShow = ref(false);
138
134
 
139
135
  const coreStore = useCoreStore();
140
- const modalStore = useModalStore();
141
136
  const filtersStore = useFiltersStore();
142
137
 
143
138
  const route = useRoute();
144
139
 
145
140
  const page = ref(1);
146
- const filters = filtersStore.filters;
147
141
  const columnsMinMax = ref({});
148
142
  const sort = ref([]);
149
143
 
@@ -200,11 +194,6 @@ async function getList() {
200
194
  totalRows.value = data.total;
201
195
  }
202
196
 
203
-
204
-
205
-
206
-
207
-
208
197
  async function startBulkAction(actionId) {
209
198
  const data = await callAdminForthApi({
210
199
  path: '/start_bulk_action',
@@ -216,10 +205,13 @@ async function startBulkAction(actionId) {
216
205
 
217
206
  }
218
207
  });
219
- if (data?.status === 'success') {
208
+ if (data?.ok) {
220
209
  checkboxes.value = [];
210
+ await getList();
211
+ }
212
+ if (data?.error) {
213
+ showErrorTost(data.error);
221
214
  }
222
- await getList();
223
215
  }
224
216
 
225
217
 
@@ -11,7 +11,9 @@ import {
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
13
  import { guessLabelFromName } from './utils.js';
14
- import { v4 as uuid } from 'uuid';
14
+
15
+ // @ts-ignore
16
+ import sha256 from 'crypto-js/sha256';
15
17
 
16
18
  export default class ConfigValidator implements IConfigValidator {
17
19
 
@@ -269,6 +271,8 @@ export default class ConfigValidator implements IConfigValidator {
269
271
  allowed: async ({ resource, adminUser, allowedActions }) => { return allowedActions.delete },
270
272
  action: async ({ selectedIds, adminUser }) => {
271
273
  const connector = this.adminforth.connectors[res.dataSource];
274
+
275
+ // for now if at least one error, stop and return error
272
276
  let error = null;
273
277
 
274
278
  await Promise.all(
@@ -285,6 +289,10 @@ export default class ConfigValidator implements IConfigValidator {
285
289
  }
286
290
  )
287
291
  )
292
+
293
+ if (error) {
294
+ return;
295
+ }
288
296
 
289
297
  await connector.deleteRecord({ resource: res, recordId });
290
298
  // call afterDelete hook
@@ -307,10 +315,12 @@ export default class ConfigValidator implements IConfigValidator {
307
315
  });
308
316
  }
309
317
 
310
- const newBulkActions = bulkActions.map((action) => {
311
- return Object.assign(action, { id: uuid() });
318
+ bulkActions.map((action) => {
319
+ if (!action.id) {
320
+ action.id = sha256(action.label).toString();
321
+ }
312
322
  });
313
- res.options.bulkActions = newBulkActions;
323
+ res.options.bulkActions = bulkActions;
314
324
 
315
325
  // if pageInjection is a string, make array with one element. Also check file exists
316
326
  const possibleInjections = ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom'];
@@ -225,9 +225,9 @@ export default class AdminForthRestAPI {
225
225
  });
226
226
 
227
227
  async function interpretResource(adminUser: AdminUser, resource: AdminForthResource, meta: any, source: ActionCheckSource): Promise<{allowedActions: AllowedActionsResolved}> {
228
- if (process.env.HEAVY_DEBUG) {
229
- console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
230
- }
228
+ // if (process.env.HEAVY_DEBUG) {
229
+ // console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
230
+ // }
231
231
  const allowedActions = {};
232
232
 
233
233
  await Promise.all(
@@ -281,7 +281,6 @@ export default class AdminForthRestAPI {
281
281
  resource.options.bulkActions.map(async (action) => {
282
282
  if (action.allowed) {
283
283
  const res = await action.allowed({ adminUser, resource, allowedActions });
284
- console.log('🪲🪲🪲🪲checking for allowedActions', allowedActions, 'res', res);
285
284
  if (res) {
286
285
  allowedBulkActions.push(action);
287
286
  }
@@ -712,13 +711,13 @@ export default class AdminForthRestAPI {
712
711
  if (!execAllowed) {
713
712
  return { error: `Action '${actionId}' is not allowed` };
714
713
  }
715
- await action.action({selectedIds: recordIds, adminUser, resource});
714
+ const response = await action.action({selectedIds: recordIds, adminUser, resource});
716
715
 
717
716
  return {
718
717
  actionId,
719
718
  recordIds,
720
719
  resourceId,
721
- status: 'success'
720
+ ...response
722
721
  }
723
722
  }
724
723
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.2.36",
3
+ "version": "1.2.38",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,6 +18,7 @@
18
18
  "@clickhouse/client": "^1.4.0",
19
19
  "better-sqlite3": "^10.0.0",
20
20
  "crypto": "^1.0.1",
21
+ "crypto-js": "^4.2.0",
21
22
  "dayjs": "^1.11.11",
22
23
  "express": "^4.19.2",
23
24
  "filewatcher": "^3.0.1",
@@ -41,7 +41,7 @@
41
41
  :value="currentValues[column.name]"
42
42
  @update:value="setCurrentValue(column.name, $event)"
43
43
  :meta="column.components[props.source].meta"
44
- :record="props.record"
44
+ :record="currentValues"
45
45
  @update:inValidity="customComponentsInValidity[column.name] = $event"
46
46
  @update:emptiness="customComponentsEmptiness[column.name] = $event"
47
47
  />
@@ -238,6 +238,8 @@ const setCurrentValue = (key, value) => {
238
238
  } else {
239
239
  currentValues.value[key] = value;
240
240
  }
241
+ currentValues.value = { ...currentValues.value };
242
+ console.log('3️⃣ setCurrentValue', key, value);
241
243
  emit('update:record', currentValues.value);
242
244
  };
243
245
 
@@ -228,7 +228,7 @@ import { callAdminForthApi, getIcon } from '@/utils';
228
228
  import ValueRenderer from '@/components/ValueRenderer.vue';
229
229
  import { getCustomComponent } from '@/utils';
230
230
  import { useCoreStore } from '@/stores/core';
231
- import { showSuccesTost } from '@/composables/useFrontendApi';
231
+ import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
232
232
  import SkeleteLoader from '@/components/SkeleteLoader.vue';
233
233
 
234
234
  import {
@@ -368,22 +368,24 @@ async function deleteRecord(row) {
368
368
  if (data) {
369
369
  try {
370
370
  const res = await callAdminForthApi({
371
- path: '/delete_record',
372
- method: 'POST',
373
- body: {
374
- resourceId: props.resource.resourceId,
375
- primaryKey: row._primaryKeyValue,
376
- }});
371
+ path: '/delete_record',
372
+ method: 'POST',
373
+ body: {
374
+ resourceId: props.resource.resourceId,
375
+ primaryKey: row._primaryKeyValue,
376
+ }
377
+ });
377
378
  if (!res.error){
378
379
  emits('update:records', true)
379
380
  showSuccesTost('Record deleted successfully')
380
381
  } else {
381
- console.error(res.error)
382
+ showErrorTost(res.error)
382
383
  }
383
384
 
384
385
  } catch (e) {
386
+ showErrorTost(`Something went wrong, please try again later`);
385
387
  console.error(e);
386
- };
387
- }
388
+ };
389
+ }
388
390
  }
389
391
  </script>
package/spa/src/utils.ts CHANGED
@@ -42,7 +42,6 @@ export async function callAdminForthApi({ path, method, body=undefined }: {
42
42
 
43
43
  export function getCustomComponent({ file, meta }: { file: string, meta: any }) {
44
44
  const name = file.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
45
- console.log('resolving name', name);
46
45
  return resolveComponent(name);
47
46
  }
48
47
 
@@ -124,11 +124,7 @@ import { getCustomComponent } from '@/utils';
124
124
 
125
125
  import {
126
126
  IconBanOutline,
127
- IconEyeSolid,
128
127
  IconFilterOutline,
129
- IconPenSolid,
130
- IconTrashBinSolid,
131
- IconInboxOutline,
132
128
  IconPlusOutline
133
129
  } from '@iconify-prerendered/vue-flowbite';
134
130
 
@@ -137,13 +133,11 @@ import Filters from '@/components/Filters.vue';
137
133
  const filtersShow = ref(false);
138
134
 
139
135
  const coreStore = useCoreStore();
140
- const modalStore = useModalStore();
141
136
  const filtersStore = useFiltersStore();
142
137
 
143
138
  const route = useRoute();
144
139
 
145
140
  const page = ref(1);
146
- const filters = filtersStore.filters;
147
141
  const columnsMinMax = ref({});
148
142
  const sort = ref([]);
149
143
 
@@ -200,11 +194,6 @@ async function getList() {
200
194
  totalRows.value = data.total;
201
195
  }
202
196
 
203
-
204
-
205
-
206
-
207
-
208
197
  async function startBulkAction(actionId) {
209
198
  const data = await callAdminForthApi({
210
199
  path: '/start_bulk_action',
@@ -216,10 +205,13 @@ async function startBulkAction(actionId) {
216
205
 
217
206
  }
218
207
  });
219
- if (data?.status === 'success') {
208
+ if (data?.ok) {
220
209
  checkboxes.value = [];
210
+ await getList();
211
+ }
212
+ if (data?.error) {
213
+ showErrorTost(data.error);
221
214
  }
222
- await getList();
223
215
  }
224
216
 
225
217
 
@@ -276,6 +276,7 @@ export interface IAdminForthPlugin {
276
276
  customFolderName: string;
277
277
  pluginInstanceId: string;
278
278
  customFolderPath: string;
279
+ pluginOptions: any;
279
280
 
280
281
  /**
281
282
  * AdminForth plugins concept is based on modification of full AdminForth configuration
@@ -289,6 +290,17 @@ export interface IAdminForthPlugin {
289
290
  modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource): void;
290
291
  componentPath(componentFile: string): string;
291
292
 
293
+ /**
294
+ * If plugin should support multiple installations per one resource, this function that should return unique string for each instance of plugin.
295
+ * For example if plugin is installed for one column and this column defined as
296
+ * `targetColumn` in plugin options, then this method should return `${pluginOptions.targetColumn}`.
297
+ *
298
+ * If plugin should support only one installation per resource, option can return 'single'
299
+ * @param pluginOptions - options of plugin
300
+ */
301
+ instanceUniqueRepresentation(pluginOptions: any) : string;
302
+
303
+
292
304
  /**
293
305
  * Optional method which will be called after AdminForth discovers all resources and their columns.
294
306
  * Can be used to validate types of columns, check if some columns are missing, etc.