adminforth 1.4.3-next.9 → 1.5.2

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 (143) hide show
  1. package/dist/basePlugin.d.ts.map +1 -1
  2. package/dist/basePlugin.js +2 -3
  3. package/dist/basePlugin.js.map +1 -1
  4. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  5. package/dist/dataConnectors/postgres.js +29 -9
  6. package/dist/dataConnectors/postgres.js.map +1 -1
  7. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  8. package/dist/dataConnectors/sqlite.js +12 -5
  9. package/dist/dataConnectors/sqlite.js.map +1 -1
  10. package/dist/index.d.ts +4 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +18 -5
  13. package/dist/index.js.map +1 -1
  14. package/dist/modules/codeInjector.d.ts +4 -2
  15. package/dist/modules/codeInjector.d.ts.map +1 -1
  16. package/dist/modules/codeInjector.js +98 -79
  17. package/dist/modules/codeInjector.js.map +1 -1
  18. package/dist/modules/configValidator.d.ts.map +1 -1
  19. package/dist/modules/configValidator.js +5 -4
  20. package/dist/modules/configValidator.js.map +1 -1
  21. package/dist/modules/operationalResource.d.ts.map +1 -1
  22. package/dist/modules/operationalResource.js.map +1 -1
  23. package/dist/modules/restApi.d.ts +8 -3
  24. package/dist/modules/restApi.d.ts.map +1 -1
  25. package/dist/modules/restApi.js +112 -39
  26. package/dist/modules/restApi.js.map +1 -1
  27. package/dist/modules/socketBroker.d.ts +16 -0
  28. package/dist/modules/socketBroker.d.ts.map +1 -0
  29. package/dist/modules/socketBroker.js +99 -0
  30. package/dist/modules/socketBroker.js.map +1 -0
  31. package/dist/modules/utils.d.ts +2 -1
  32. package/dist/modules/utils.d.ts.map +1 -1
  33. package/dist/modules/utils.js +5 -2
  34. package/dist/modules/utils.js.map +1 -1
  35. package/dist/servers/common.d.ts +21 -0
  36. package/dist/servers/common.d.ts.map +1 -0
  37. package/dist/servers/common.js +13 -0
  38. package/dist/servers/common.js.map +1 -0
  39. package/dist/servers/express.d.ts +5 -0
  40. package/dist/servers/express.d.ts.map +1 -1
  41. package/dist/servers/express.js +54 -7
  42. package/dist/servers/express.js.map +1 -1
  43. package/dist/spa/package-lock.json +59 -0
  44. package/dist/spa/package.json +1 -0
  45. package/dist/spa/src/App.vue +58 -36
  46. package/dist/spa/src/afcl/Button.vue +6 -0
  47. package/dist/spa/src/afcl/Input.vue +13 -4
  48. package/dist/spa/src/afcl/LinkButton.vue +23 -0
  49. package/dist/spa/src/afcl/Select.vue +8 -7
  50. package/dist/spa/src/afcl/VerticalTabs.vue +47 -0
  51. package/dist/spa/src/afcl/index.ts +4 -1
  52. package/dist/spa/src/components/Filters.vue +0 -1
  53. package/dist/spa/src/components/GroupsTable.vue +4 -4
  54. package/dist/spa/src/components/ResourceListTable.vue +21 -21
  55. package/dist/spa/src/components/ValueRenderer.vue +3 -1
  56. package/dist/spa/src/composables/useStores.ts +4 -1
  57. package/dist/spa/src/renderers/CompactField.vue +4 -4
  58. package/dist/spa/src/renderers/CompactUUID.vue +4 -4
  59. package/dist/spa/src/renderers/CountryFlag.vue +3 -3
  60. package/dist/spa/src/renderers/HumanNumber.vue +5 -4
  61. package/dist/spa/src/renderers/RelativeTime.vue +5 -4
  62. package/dist/spa/src/router/index.ts +2 -0
  63. package/dist/spa/src/stores/core.ts +39 -6
  64. package/dist/spa/src/types/Back.ts +102 -9
  65. package/dist/spa/src/types/Common.ts +6 -1
  66. package/dist/spa/src/types/FrontendAPI.ts +7 -0
  67. package/dist/spa/src/views/EditView.vue +1 -0
  68. package/dist/spa/src/views/ListView.vue +2 -2
  69. package/dist/spa/src/views/LoginView.vue +7 -7
  70. package/dist/spa/src/views/PageNotFound.vue +20 -0
  71. package/dist/spa/src/views/ResourceParent.vue +0 -1
  72. package/dist/spa/src/views/ShowView.vue +1 -0
  73. package/dist/spa/src/websocket.ts +84 -0
  74. package/dist/spa/vite.config.ts +16 -34
  75. package/dist/types/Back.d.ts +70 -4
  76. package/dist/types/Back.d.ts.map +1 -1
  77. package/dist/types/Back.js.map +1 -1
  78. package/dist/types/Common.d.ts +4 -0
  79. package/dist/types/Common.d.ts.map +1 -1
  80. package/dist/types/FrontendAPI.d.ts +6 -0
  81. package/dist/types/FrontendAPI.d.ts.map +1 -1
  82. package/dist/types/FrontendAPI.js.map +1 -1
  83. package/package.json +3 -3
  84. package/dist/plugins/audit-log/types.d.ts +0 -35
  85. package/dist/plugins/audit-log/types.d.ts.map +0 -1
  86. package/dist/plugins/audit-log/types.js +0 -2
  87. package/dist/plugins/audit-log/types.js.map +0 -1
  88. package/dist/plugins/chat-gpt/types.d.ts +0 -82
  89. package/dist/plugins/chat-gpt/types.d.ts.map +0 -1
  90. package/dist/plugins/chat-gpt/types.js +0 -2
  91. package/dist/plugins/chat-gpt/types.js.map +0 -1
  92. package/dist/plugins/email-password-reset/types.d.ts +0 -28
  93. package/dist/plugins/email-password-reset/types.d.ts.map +0 -1
  94. package/dist/plugins/email-password-reset/types.js +0 -2
  95. package/dist/plugins/email-password-reset/types.js.map +0 -1
  96. package/dist/plugins/foreign-inline-list/types.d.ts +0 -19
  97. package/dist/plugins/foreign-inline-list/types.d.ts.map +0 -1
  98. package/dist/plugins/foreign-inline-list/types.js +0 -2
  99. package/dist/plugins/foreign-inline-list/types.js.map +0 -1
  100. package/dist/plugins/import-export/types.d.ts +0 -3
  101. package/dist/plugins/import-export/types.d.ts.map +0 -1
  102. package/dist/plugins/import-export/types.js +0 -2
  103. package/dist/plugins/import-export/types.js.map +0 -1
  104. package/dist/plugins/rich-editor/custom/async-queue.d.ts +0 -8
  105. package/dist/plugins/rich-editor/custom/async-queue.d.ts.map +0 -1
  106. package/dist/plugins/rich-editor/custom/async-queue.js +0 -29
  107. package/dist/plugins/rich-editor/custom/async-queue.js.map +0 -1
  108. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts +0 -8
  109. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts.map +0 -1
  110. package/dist/plugins/rich-editor/dist/custom/async-queue.js +0 -29
  111. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +0 -1
  112. package/dist/plugins/rich-editor/types.d.ts +0 -153
  113. package/dist/plugins/rich-editor/types.d.ts.map +0 -1
  114. package/dist/plugins/rich-editor/types.js +0 -16
  115. package/dist/plugins/rich-editor/types.js.map +0 -1
  116. package/dist/plugins/two-factors-auth/types.d.ts +0 -18
  117. package/dist/plugins/two-factors-auth/types.d.ts.map +0 -1
  118. package/dist/plugins/two-factors-auth/types.js +0 -2
  119. package/dist/plugins/two-factors-auth/types.js.map +0 -1
  120. package/dist/plugins/upload/types.d.ts +0 -132
  121. package/dist/plugins/upload/types.d.ts.map +0 -1
  122. package/dist/plugins/upload/types.js +0 -2
  123. package/dist/plugins/upload/types.js.map +0 -1
  124. package/dist/spa/src/acl/Button.vue +0 -21
  125. package/dist/spa/src/acl/Link.vue +0 -9
  126. package/dist/spa/src/components/Dropdown.vue +0 -167
  127. package/dist/spa/src/types/AdminForthConfig.js +0 -150
  128. package/dist/spa/src/types/AdminForthConfig.ts +0 -1816
  129. package/dist/spa/src/types/Commons.ts +0 -702
  130. package/dist/spa/src/types/FrontAndBack.ts +0 -698
  131. package/dist/types/AdminForthConfig.d.ts +0 -1665
  132. package/dist/types/AdminForthConfig.d.ts.map +0 -1
  133. package/dist/types/AdminForthConfig.js +0 -146
  134. package/dist/types/AdminForthConfig.js.map +0 -1
  135. package/dist/types/Commons.d.ts +0 -616
  136. package/dist/types/Commons.d.ts.map +0 -1
  137. package/dist/types/Commons.js +0 -65
  138. package/dist/types/Commons.js.map +0 -1
  139. package/dist/types/FrontAndBack.d.ts +0 -613
  140. package/dist/types/FrontAndBack.d.ts.map +0 -1
  141. package/dist/types/FrontAndBack.js +0 -62
  142. package/dist/types/FrontAndBack.js.map +0 -1
  143. /package/dist/spa/src/{components/AfTooltip.vue → afcl/Tooltip.vue} +0 -0
@@ -13,6 +13,7 @@ import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections,
13
13
  export interface ICodeInjector {
14
14
  srcFoldersToSync: Object;
15
15
  allComponentNames: Object;
16
+ devServerPort: number;
16
17
 
17
18
  getServeDir(): string;
18
19
  }
@@ -72,6 +73,12 @@ export interface IExpressHttpServer extends IHttpServer {
72
73
  */
73
74
  serve(app: Express): void;
74
75
 
76
+ /**
77
+ * Method to start listening on port.
78
+ */
79
+ listen(port: number, callback: Function): void;
80
+ listen(port: number, host: string, callback: Function): void;
81
+
75
82
  /**
76
83
  * Method (middleware) to wrap express endpoints with authorization check.
77
84
  * Adds adminUser to request object if user is authorized. Drops request with 401 status if user is not authorized.
@@ -90,6 +97,7 @@ export interface IExpressHttpServer extends IHttpServer {
90
97
  authorize(callable: Function): void;
91
98
  }
92
99
 
100
+
93
101
  export interface IAdminForthFilter {
94
102
  field: string;
95
103
  operator: AdminForthFilterOperators;
@@ -249,15 +257,35 @@ export interface IAdminForthAuth {
249
257
  removeAuthCookie(response: any): void;
250
258
  }
251
259
 
260
+ export interface IAdminForthRestAPI {
261
+
262
+ /**
263
+ * Called by AdminForth to initialize all endpoints for REST API.
264
+ */
265
+ registerEndpoints(server: IHttpServer): void;
266
+
267
+ /**
268
+ * Called by login endpoint to process login callbacks. Also might be called by plugins, to prevent action if user is not allowed to login.
269
+ * For example signup or login via google might want to check if user is allowed to login by calling this method.
270
+ * @param adminUser - plugin/af pases current adminUser
271
+ * @param toReturn - this is an object which will get status of login process. If at least one callback returns error or redirectTo, login process will be stopped (future callbacks will not be called).
272
+ * @param response - http response object
273
+ */
274
+ processLoginCallbacks(adminUser: AdminUser, toReturn: { redirectTo?: string, allowedLogin: boolean, error?: string }, response: any): Promise<void>;
275
+ }
276
+
252
277
  export interface IAdminForth {
253
278
  config: AdminForthConfig;
254
279
  codeInjector: ICodeInjector;
255
280
  express: IHttpServer;
256
281
 
282
+ restApi: IAdminForthRestAPI;
257
283
  activatedPlugins: Array<IAdminForthPlugin>;
258
284
 
259
285
  baseUrlSlashed: string;
260
286
 
287
+ websocket: IWebSocketBroker;
288
+
261
289
  statuses: {
262
290
  dbDiscover: 'running' | 'done',
263
291
  };
@@ -310,6 +338,7 @@ export interface IAdminForth {
310
338
  * This method will be automatically called from AdminForth HTTP adapter to serve AdminForth SPA.
311
339
  */
312
340
  setupEndpoints(server: IHttpServer): void;
341
+
313
342
  }
314
343
 
315
344
 
@@ -424,7 +453,7 @@ export enum AdminForthMenuTypes {
424
453
  /**
425
454
  * Menu item which displayed in the left sidebar of the admin panel.
426
455
  */
427
- export type AdminForthConfigMenuItem = {
456
+ export interface AdminForthConfigMenuItem {
428
457
  type?: AdminForthMenuTypes | keyof typeof AdminForthMenuTypes,
429
458
 
430
459
  /**
@@ -512,6 +541,11 @@ export type AdminForthConfigMenuItem = {
512
541
  * Result of callback if not null will be used as a small badge near the menu item.
513
542
  */
514
543
  badge?: string | ((user: AdminUser) => Promise<string>),
544
+
545
+ /**
546
+ * Item id will be automatically generated from hashed resourceId+Path+label
547
+ */
548
+ _itemId?: string,
515
549
  }
516
550
 
517
551
 
@@ -521,13 +555,17 @@ export type AdminForthConfigMenuItem = {
521
555
  * Modify query to change how data is fetched from database.
522
556
  * Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
523
557
  */
524
- export type BeforeDataSourceRequestFunction = (params: {resource: AdminForthResource, adminUser: AdminUser, query: any,
558
+ export type BeforeDataSourceRequestFunction = (params: {
559
+ resource: AdminForthResource,
560
+ adminUser: AdminUser,
561
+ query: any,
525
562
  extra: {
526
563
  body: any,
527
564
  query: Record<string, string>,
528
565
  headers: Record<string, string>,
529
566
  cookies: Record<string, string>,
530
- }
567
+ },
568
+ adminforth: IAdminForth,
531
569
  }) => Promise<{ok: boolean, error?: string}>;
532
570
 
533
571
  /**
@@ -543,20 +581,35 @@ export type AfterDataSourceResponseFunction = (params: {
543
581
  query: Record<string, string>,
544
582
  headers: Record<string, string>,
545
583
  cookies: { key: string, value: string }[],
546
- }
584
+ },
585
+ adminforth: IAdminForth,
547
586
  }) => Promise<{ok: boolean, error?: string}>;
548
587
 
549
588
  /**
550
589
  * Modify record to change how data is saved to database.
551
590
  * Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
552
591
  */
553
- export type BeforeSaveFunction = (params: {resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any, oldRecord?: any}) => Promise<{ok: boolean, error?: string}>;
592
+ export type BeforeSaveFunction = (params: {
593
+ resource: AdminForthResource,
594
+ recordId: any,
595
+ adminUser: AdminUser,
596
+ record: any,
597
+ oldRecord?: any,
598
+ adminforth: IAdminForth,
599
+ }) => Promise<{ok: boolean, error?: string}>;
554
600
 
555
601
  /**
556
602
  * Modify record to change how data is saved to database.
557
603
  * Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
558
604
  */
559
- export type AfterSaveFunction = (params: {resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any, oldRecord?: any}) => Promise<{ok: boolean, error?: string}>;
605
+ export type AfterSaveFunction = (params: {
606
+ resource: AdminForthResource,
607
+ recordId: any,
608
+ adminUser: AdminUser,
609
+ record: any,
610
+ oldRecord?: any,
611
+ adminforth: IAdminForth,
612
+ }) => Promise<{ok: boolean, error?: string}>;
560
613
 
561
614
  /**
562
615
  * Allow to get user data before login confirmation, will triger when user try to login.
@@ -564,9 +617,9 @@ export type AfterSaveFunction = (params: {resource: AdminForthResource, recordId
564
617
  export type BeforeLoginConfirmationFunction = (params?: {
565
618
  adminUser: AdminUser,
566
619
  response: IAdminForthHttpResponse,
620
+ adminforth: IAdminForth,
567
621
  }) => Promise<{
568
- ok:boolean,
569
- error?:string,
622
+ error?: string,
570
623
  body: {
571
624
  redirectTo?: string,
572
625
  allowedLogin?: boolean,
@@ -678,6 +731,23 @@ export interface AdminForthConfig {
678
731
  * If rememberMeDays is set, then users who check "Remember Me" will be staying logged in for this amount of days.
679
732
  */
680
733
  rememberMeDays?: number,
734
+
735
+
736
+ /**
737
+ * Can be used to limit user access when subscribing from frontend to websocket topics.
738
+ * @param topic - topic where user is trying to subscribe
739
+ * @param user - user object
740
+ * @returns - boolean, true if user is allowed to subscribe to this topic, false otherwise
741
+ */
742
+ websocketTopicAuth?: (topic: string, user: AdminUser) => Promise<boolean>,
743
+
744
+ /**
745
+ * callback which will be called after user subscribes to websocket topic
746
+ * @param topic - topic on which user subscribed
747
+ * @param user - user object
748
+ * @returns
749
+ */
750
+ websocketSubscribed?: (topic: string, user: AdminUser) => void,
681
751
  },
682
752
  /**
683
753
  * Array of resources which will be displayed in the admin panel.
@@ -956,7 +1026,7 @@ export interface IOperationalResource {
956
1026
  * or function which returns Boolean or string with error message
957
1027
  *
958
1028
  */
959
- export type AllowedActionValue = boolean | (({adminUser, resource, meta, source}: {
1029
+ export type AllowedActionValue = boolean | (({adminUser, resource, meta, source, adminforth}: {
960
1030
  adminUser: AdminUser,
961
1031
  resource: AdminForthResource,
962
1032
 
@@ -969,6 +1039,11 @@ export type AllowedActionValue = boolean | (({adminUser, resource, meta, source}
969
1039
  * Source of the check
970
1040
  */
971
1041
  source: ActionCheckSource,
1042
+
1043
+ /**
1044
+ * Instance of AdminForth, can be used e.g. to call data API adminforth.resource(resourceId)...
1045
+ */
1046
+ adminforth: IAdminForth,
972
1047
  }) => Promise<boolean | string>);
973
1048
 
974
1049
 
@@ -1191,4 +1266,22 @@ export interface AdminForthForeignResource extends AdminForthForeignResourceComm
1191
1266
 
1192
1267
  export interface AdminForthResourceColumn extends AdminForthResourceColumnCommon {
1193
1268
  foreignResource?: AdminForthForeignResource,
1269
+ }
1270
+
1271
+ export interface IWebSocketClient {
1272
+ id: string;
1273
+ lastPing: number;
1274
+ topics: Set<string>;
1275
+ adminUser: AdminUser;
1276
+
1277
+ send: (message: string) => void;
1278
+ close: () => void;
1279
+ onMessage: (handler: (message: string) => void) => void;
1280
+ onClose: (handler: () => void) => void;
1281
+ }
1282
+
1283
+ export interface IWebSocketBroker {
1284
+ publish: (topic: string, data: any) => void;
1285
+
1286
+ registerWsClient: (client: IWebSocketClient) => void;
1194
1287
  }
@@ -570,9 +570,14 @@ export type AdminForthResourceColumnCommon = {
570
570
  */
571
571
  editingNote?: string | { create?: string, edit?: string },
572
572
 
573
+ /**
574
+ * Whether AdminForth will allow to edit this field in editing mode.
575
+ */
576
+ editReadonly?: boolean,
577
+
573
578
  /**
574
579
  * On which AdminForth pages this field will be shown. By default all.
575
- * Example: if you want to show field only in create and edit pages, set it to
580
+ * Example: if you want to show field only in create and edit pages, set it to
576
581
  *
577
582
  * ```ts
578
583
  * showIn: [AdminForthResourcePages.CREATE, AdminForthResourcePages.EDIT]
@@ -98,6 +98,13 @@ export interface FrontendAPIInterface {
98
98
  */
99
99
  clearFilters(): void;
100
100
  }
101
+
102
+ menu: {
103
+ /**
104
+ * Refreshes the badges in the menu, by recalling the badge function for each menu item
105
+ */
106
+ refreshMenuBadges(): void;
107
+ }
101
108
  }
102
109
 
103
110
  export type ConfirmParams = {
@@ -124,6 +124,7 @@ onMounted(async () => {
124
124
  await coreStore.fetchRecord({
125
125
  resourceId: route.params.resourceId,
126
126
  primaryKey: route.params.primaryKey,
127
+ source: 'edit',
127
128
  });
128
129
  checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'edit');
129
130
  loading.value = false;
@@ -97,6 +97,7 @@
97
97
  :pageSize="pageSize"
98
98
  :totalRows="totalRows"
99
99
  :checkboxes="checkboxes"
100
+ :customActionsInjection="coreStore.resourceOptions?.pageInjections?.list?.customActionIcons"
100
101
  />
101
102
 
102
103
  <component
@@ -350,8 +351,7 @@ async function init() {
350
351
  }
351
352
 
352
353
  watch([page, sort, () => filtersStore.filters], async () => {
353
- console.log('🔄️ page/sort/filter change fired, page:', page.value);
354
-
354
+ // console.log('🔄️ page/sort/filter change fired, page:', page.value);
355
355
  await getList();
356
356
  }, { deep: true });
357
357
 
@@ -39,7 +39,11 @@
39
39
  <form class="space-y-4" @submit.prevent>
40
40
  <div>
41
41
  <label for="username" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
42
- <input type="username" name="username" id="username"
42
+ <input
43
+ autocomplete="username"
44
+ type="username"
45
+ name="username"
46
+ id="username"
43
47
  ref="usernameInput"
44
48
  @keydown.enter="passwordInput.focus()"
45
49
  class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="name@company.com" required />
@@ -48,6 +52,7 @@
48
52
  <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
49
53
  <input
50
54
  ref="passwordInput"
55
+ autocomplete="current-password"
51
56
  @keydown.enter="login"
52
57
  :type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" required />
53
58
  <button type="button" @click="showPw = !showPw" class="absolute top-12 right-3 -translate-y-1/2 text-gray-400 dark:text-gray-300">
@@ -85,8 +90,6 @@
85
90
  {{ error }}
86
91
  </div>
87
92
  </div>
88
-
89
-
90
93
 
91
94
  <div v-if="coreStore.config?.loginPromptHTML"
92
95
  class="flex items-center p-4 mb-4 text-sm text-gray-800 rounded-lg bg-gray-50 dark:bg-gray-800 dark:text-gray-400" role="alert"
@@ -97,14 +100,11 @@
97
100
  <span class="sr-only">Info</span>
98
101
  <div v-html="coreStore.config?.loginPromptHTML"></div>
99
102
  </div>
100
-
101
103
  <Button @click="login" :loader="inProgress" :disabled="inProgress" class="w-full">
102
104
  Login to your account
103
105
  </Button>
104
106
  </form>
105
107
 
106
-
107
-
108
108
  </div>
109
109
  </div>
110
110
  </div>
@@ -123,7 +123,7 @@ import { useUserStore } from '@/stores/user';
123
123
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
124
124
  import { callAdminForthApi, loadFile } from '@/utils';
125
125
  import { useRouter } from 'vue-router';
126
- import Button from '@/afcl/Button.vue';
126
+ import { Button } from '@/afcl';
127
127
 
128
128
  const passwordInput = ref(null);
129
129
  const usernameInput = ref(null);
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <section class="bg-white dark:bg-gray-900">
3
+ <div class="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
4
+ <div class="mx-auto max-w-screen-sm text-center">
5
+ <h1 class="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-lightPrimary dark:text-darkPrimary">404</h1>
6
+ <p class="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Something's missing.</p>
7
+ <p class="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">Sorry, we can't find that page. You'll find lots to explore on the home page. </p>
8
+ <div class="flex justify-center">
9
+ <LinkButton to="/">Go back home</LinkButton>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ </section>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+
18
+ import { LinkButton } from '@/afcl';
19
+
20
+ </script>
@@ -32,7 +32,6 @@ const limitHeightToPage = computed(() => {
32
32
  return true;
33
33
  }
34
34
  const listPageInjects = coreStore.resource.options.pageInjections.list;
35
- console.log('asdcoreStore.resource', JSON.stringify(listPageInjects, null, 2))
36
35
 
37
36
  for (const pi of [listPageInjects.beforeBreadcrumbs, listPageInjects.afterBreadcrumbs, listPageInjects.bottom]) {
38
37
  if (pi) {
@@ -158,6 +158,7 @@ onMounted(async () => {
158
158
  await coreStore.fetchRecord({
159
159
  resourceId: route.params.resourceId,
160
160
  primaryKey: route.params.primaryKey,
161
+ source: 'show',
161
162
  });
162
163
  checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
163
164
  loading.value = false;
@@ -0,0 +1,84 @@
1
+
2
+ const subscriptions: { [topic: string]: ((data: any) => void)[] } = {};
3
+ const state: {
4
+ status: 'connecting' | 'connected' | 'disconnected';
5
+ ws: WebSocket | null;
6
+ } = {
7
+ status: 'connecting',
8
+ ws: null
9
+ };
10
+
11
+ function doPhysicalSubscribe(topic: string) {
12
+ state.ws!.send(JSON.stringify({ type: 'subscribe', topic }));
13
+ }
14
+
15
+ function doPhysicalUnsubscribe(topic: string) {
16
+ state.ws!.send(JSON.stringify({ type: 'unsubscribe', topic }));
17
+ }
18
+
19
+
20
+ async function init() {
21
+ state.ws = new WebSocket(`${
22
+ window.location.protocol === 'http:' ? 'ws' : 'wss'
23
+ }://${window.location.host}/afws`);
24
+ state.status = 'connecting';
25
+ state.ws.addEventListener('open', () => {
26
+ console.log('connected');
27
+ state.status = 'connected';
28
+ Object.keys(subscriptions).forEach((topic) => {
29
+ doPhysicalSubscribe(topic);
30
+ });
31
+ });
32
+ state.ws.addEventListener('message', (event) => {
33
+ const data = event.data.toString();
34
+ if (data === 'pong') {
35
+ return;
36
+ }
37
+ const message = JSON.parse(data);
38
+ if (message.type === 'message') {
39
+ const topic = message.topic;
40
+ const data = message.data;
41
+ if (subscriptions[topic]) {
42
+ for (const callback of subscriptions[topic]) {
43
+ callback(data);
44
+ }
45
+ }
46
+ }
47
+ });
48
+ state.ws.addEventListener('close', () => {
49
+ console.log('disconnected');
50
+ state.status = 'disconnected';
51
+ });
52
+ }
53
+
54
+ setInterval(() => {
55
+ if (state.status === 'connected') {
56
+ state.ws!.send('ping');
57
+ }
58
+ }, 10_000);
59
+
60
+ try {
61
+ init();
62
+ } catch (e) {
63
+ console.error('Failed to initialize websocket', e);
64
+ }
65
+
66
+ export default {
67
+ subscribe(topic: string, callback: (data: any) => void): void {
68
+ if (!subscriptions[topic]) {
69
+ subscriptions[topic] = [];
70
+ }
71
+ subscriptions[topic].push(callback);
72
+ if (state.status === 'connected') {
73
+ doPhysicalSubscribe(topic);
74
+ }
75
+ },
76
+
77
+ unsubscribe(topic: string): void {
78
+ delete subscriptions[topic];
79
+ if (state.status === 'connected') {
80
+ doPhysicalUnsubscribe(topic);
81
+ }
82
+ }
83
+
84
+ }
@@ -2,48 +2,30 @@ import { fileURLToPath, URL } from 'node:url'
2
2
 
3
3
  import { defineConfig } from 'vite'
4
4
  import vue from '@vitejs/plugin-vue'
5
- import { clear, error } from 'node:console';
5
+ import portfinder from 'portfinder';
6
6
 
7
-
8
- const customLogger = {
9
- info(msg: string) {
10
- // Filter out the lines containing '➜ Local:' or '➜ Network:'
11
- if (!msg.includes('➜')) {
12
- console.log(msg);
13
- }
14
- },
15
- warnOnce(msg: string) {
16
- console.warn('warn once', msg);
17
- if (!this.hasWarned) {
18
- this.hasWarned = true;
19
- }
20
- },
21
- warn(msg: string) {
22
- console.warn(msg);
23
- },
24
- error(msg: string) {
25
- console.error(msg);
26
- },
27
- clear() {
28
- console.clear();
29
- },
30
- clearScreen() {
31
- console.clear();
32
- },
33
- hasWarned: false,
34
- hasErrorLogged: (error: any): boolean => {
35
- return false;
36
- }
7
+ /**
8
+ * Find the next available port after a specified port.
9
+ * @param {number} startPort - The starting port to check.
10
+ * @returns {Promise<number>} - A promise that resolves with the next available port.
11
+ */
12
+ async function getNextAvailablePort(startPort) {
13
+ return await portfinder.getPortPromise({ port: startPort });
37
14
  };
38
15
 
16
+ const appPort = await getNextAvailablePort(5173);
17
+ const hmrPort = await getNextAvailablePort(5273);
18
+ console.log(`SPA port: ${appPort}. HMR port: ${hmrPort}`);
39
19
  // https://vitejs.dev/config/
40
20
  export default defineConfig({
41
21
  base: process.env.VITE_ADMINFORTH_PUBLIC_PATH || '/',
42
22
  server: {
43
- port: 5173,
44
- strictPort: true, // better predictability
23
+ port: appPort,
24
+ hmr: {
25
+ path: '/adminforth-dev-server-ws', // Set your custom WebSocket path here
26
+ port: hmrPort,
27
+ },
45
28
  },
46
- customLogger,
47
29
  plugins: [
48
30
  vue(),
49
31
  ],