adminforth 1.1.13 → 1.1.15

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 (47) hide show
  1. package/auth.ts +17 -3
  2. package/dist/auth.js +32 -18
  3. package/dist/index.js +114 -58
  4. package/dist/modules/codeInjector.js +0 -1
  5. package/dist/modules/utils.js +2 -12
  6. package/dist/plugins/AccessControl/index.js +66 -0
  7. package/dist/plugins/AccessControl/types.js +1 -0
  8. package/dist/plugins/ForeignInlineListPlugin/index.js +1 -1
  9. package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +248 -0
  10. package/dist/servers/express.js +2 -2
  11. package/dist/spa/spa/src/App.vue +7 -3
  12. package/dist/spa/spa/src/components/Toast.vue +5 -4
  13. package/dist/spa/spa/src/components/ValueRenderer.vue +1 -1
  14. package/dist/spa/spa/src/composables/useStores.ts +2 -1
  15. package/dist/spa/spa/src/router/index.ts +13 -1
  16. package/dist/spa/spa/src/stores/core.ts +13 -11
  17. package/dist/spa/spa/src/stores/user.ts +54 -0
  18. package/dist/spa/spa/src/utils.ts +9 -4
  19. package/dist/spa/spa/src/views/CreateView.vue +7 -0
  20. package/dist/spa/spa/src/views/EditView.vue +8 -1
  21. package/dist/spa/spa/src/views/ListView.vue +10 -1
  22. package/dist/spa/spa/src/views/LoginView.vue +4 -0
  23. package/dist/types/AdminForthConfig.js +9 -0
  24. package/dist/types/FrontendAPI.js +4 -4
  25. package/index.ts +106 -42
  26. package/modules/codeInjector.ts +0 -1
  27. package/modules/utils.ts +2 -10
  28. package/package.json +3 -2
  29. package/plugins/AccessControl/index.ts +83 -0
  30. package/plugins/AccessControl/types.ts +14 -0
  31. package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +11 -0
  32. package/plugins/ForeignInlineListPlugin/index.ts +2 -1
  33. package/servers/express.ts +2 -2
  34. package/spa/src/App.vue +7 -3
  35. package/spa/src/components/Toast.vue +5 -4
  36. package/spa/src/components/ValueRenderer.vue +1 -1
  37. package/spa/src/composables/useStores.ts +2 -1
  38. package/spa/src/router/index.ts +13 -1
  39. package/spa/src/stores/core.ts +13 -11
  40. package/spa/src/stores/user.ts +54 -0
  41. package/spa/src/utils.ts +9 -4
  42. package/spa/src/views/CreateView.vue +7 -0
  43. package/spa/src/views/EditView.vue +8 -1
  44. package/spa/src/views/ListView.vue +10 -1
  45. package/spa/src/views/LoginView.vue +4 -0
  46. package/types/AdminForthConfig.ts +73 -21
  47. package/types/FrontendAPI.ts +11 -5
@@ -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>
@@ -149,7 +149,7 @@ class ExpressServer {
149
149
  res.status(401).send('Unauthorized by AdminForth');
150
150
  return;
151
151
  }
152
- const adminforthUser = this.adminforth.auth.verify(jwt);
152
+ const adminforthUser = yield this.adminforth.auth.verify(jwt);
153
153
  if (!adminforthUser) {
154
154
  res.status(401).send('Unauthorized by AdminForth');
155
155
  }
@@ -191,7 +191,7 @@ class ExpressServer {
191
191
  this.message = message;
192
192
  }
193
193
  };
194
- const input = { body, query, headers, cookies, response, _raw_express_req: req, _raw_express_res: res };
194
+ const input = { body, query, headers, cookies, adminUser, response, _raw_express_req: req, _raw_express_res: res };
195
195
  let output;
196
196
  try {
197
197
  output = yield handler(input);
@@ -64,7 +64,6 @@
64
64
  aria-label="Sidebar"
65
65
  >
66
66
  <div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800 border-r">
67
-
68
67
  <div class="flex ms-2 md:me-24 m-4 ">
69
68
  <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
70
69
  <span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
@@ -168,6 +167,7 @@ import { RouterLink, RouterView } from 'vue-router';
168
167
  import { initFlowbite } from 'flowbite'
169
168
  import './index.scss'
170
169
  import { useCoreStore } from '@/stores/core';
170
+ import { useUserStore } from '@/stores/user';
171
171
  import {useModalStore} from '@/stores/modal';
172
172
  import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
173
173
  import AcceptModal from './components/AcceptModal.vue';
@@ -184,6 +184,7 @@ import { FrontendAPI } from '@/composables/useStores'
184
184
  const coreStore = useCoreStore();
185
185
  const modalStore = useModalStore();
186
186
  const toastStore = useToastStore();
187
+ const userStore = useUserStore();
187
188
  const frontendApi = new FrontendAPI();
188
189
  frontendApi.init();
189
190
  const splitAtLast = (str: string, separator: string) => {
@@ -221,8 +222,9 @@ if (opened.value.includes(label)) {
221
222
  }
222
223
 
223
224
  async function logout() {
224
- await coreStore.logout();
225
- router.push({ name: 'login' });
225
+ userStore.unauthorize();
226
+ await userStore.logout();
227
+ router.push({ name: 'login' })
226
228
  }
227
229
 
228
230
 
@@ -244,6 +246,8 @@ async function loadMenu() {
244
246
  });
245
247
  }
246
248
 
249
+
250
+
247
251
  watch(route, () => {
248
252
  title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
249
253
  useHead({
@@ -46,16 +46,17 @@ const props = defineProps<{
46
46
  toast:{message: string;
47
47
  variant: string;
48
48
  id: string;
49
- duration?: number;}
49
+ timeout?: number|'unlimited';}
50
50
  }>();
51
51
  function closeToast() {
52
52
  emit('close');
53
53
  }
54
54
 
55
55
  onMounted(() => {
56
- setTimeout(() => {
57
- emit('close');
58
- }, props.toast.duration || 5000 );
56
+ if (props.toast.timeout === 'unlimited') return;
57
+ else {
58
+ setTimeout(() => {emit('close');}, props.toast.timeout || 10000 );
59
+ }
59
60
  });
60
61
 
61
62
  </script>
@@ -41,7 +41,7 @@ import { useRoute, useRouter } from 'vue-router';
41
41
  import { useCoreStore } from '@/stores/core';
42
42
 
43
43
  const coreStore = useCoreStore();
44
- const route = useRoute();
44
+ const route = useRoute();
45
45
 
46
46
 
47
47
  dayjs.extend(utc);
@@ -56,7 +56,8 @@ export class FrontendAPI implements FrontendAPIInterface {
56
56
  alert(params: AlertParams): void {
57
57
  this.toastStore.addToast({
58
58
  message: params.message,
59
- variant: params.variant
59
+ variant: params.variant,
60
+ timeout: params.timeout
60
61
  })
61
62
  }
62
63
 
@@ -1,5 +1,6 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
2
  import ResourceParent from '@/views/ResourceParent.vue'
3
+ import { useUserStore } from '@/stores/user'
3
4
  /* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
4
5
 
5
6
  const router = createRouter({
@@ -9,7 +10,15 @@ const router = createRouter({
9
10
  path: '/login',
10
11
  name: 'login',
11
12
  component: () => import('@/views/LoginView.vue'),
12
- meta: { title: 'login' }
13
+ meta: { title: 'login' },
14
+ beforeEnter: async (to, from, next) => {
15
+ const userStore = useUserStore()
16
+ if(localStorage.getItem('isAuthorized') === 'true'){
17
+ next({name: 'home'})
18
+ } else {
19
+ next()
20
+ }
21
+ }
13
22
  },
14
23
  {
15
24
  path: '/resource/:resourceId',
@@ -49,4 +58,7 @@ const router = createRouter({
49
58
  })
50
59
 
51
60
 
61
+
62
+
63
+
52
64
  export default router
@@ -8,7 +8,7 @@ export const useCoreStore = defineStore('core', () => {
8
8
  const resourceById: Ref<Object> = ref({});
9
9
  const menu = ref([]);
10
10
  const config = ref({});
11
- const record = ref({});
11
+ const record: Ref<any | null> = ref({});
12
12
  const resource: Ref<AdminForthResource | null> = ref(null);
13
13
 
14
14
  const resourceColumnsWithFilters = computed(() => {
@@ -67,9 +67,17 @@ export const useCoreStore = defineStore('core', () => {
67
67
  }
68
68
  });
69
69
 
70
- console.log('📦 record', respData);
71
- record.value = respData.data[0];
72
- resourceOptions.value = respData.options;
70
+ if (respData.error) {
71
+ window.adminforth.alert({
72
+ message: respData.error,
73
+ variant: 'danger',
74
+ timeout: 'unlimited'
75
+ });
76
+ record.value = {};
77
+ } else {
78
+ record.value = respData.data[0];
79
+ }
80
+
73
81
  }
74
82
 
75
83
  async function fetchResourceFull({ resourceId }: { resourceId: string }) {
@@ -103,12 +111,7 @@ export const useCoreStore = defineStore('core', () => {
103
111
  config.value = {...config.value, ...res};
104
112
  }
105
113
 
106
- async function logout() {
107
- await callAdminForthApi({
108
- path: '/logout',
109
- method: 'POST',
110
- });
111
- }
114
+
112
115
 
113
116
  const username = computed(() => {
114
117
  const usernameField = config.value.usernameField;
@@ -134,7 +137,6 @@ export const useCoreStore = defineStore('core', () => {
134
137
  fetchResourceFull,
135
138
  resourceColumnsError,
136
139
  resourceOptions,
137
- logout,
138
140
  resource,
139
141
  adminUser,
140
142
  resourceColumnsWithFilters
@@ -0,0 +1,54 @@
1
+ import {ref} from 'vue';
2
+ import {defineStore} from 'pinia';
3
+ import { callAdminForthApi } from '@/utils';
4
+
5
+ export const useUserStore = defineStore('user', () => {
6
+ const isAuthorized = ref(false);
7
+
8
+ function authorize() {
9
+ isAuthorized.value = true;
10
+ localStorage.setItem('isAuthorized', 'true');
11
+ }
12
+
13
+ function unauthorize() {
14
+ isAuthorized.value = false;
15
+ localStorage.setItem('isAuthorized', 'false');
16
+ }
17
+
18
+ async function logout() {
19
+ await callAdminForthApi({
20
+ path: '/logout',
21
+ method: 'POST',
22
+ });
23
+ unauthorize();
24
+ }
25
+
26
+ // async function checkAuth( skipApiCall = false){
27
+ // console.log('checkAuth', isAuthorized.value, skipApiCall)
28
+ // if(isAuthorized.value) {
29
+ // return true}
30
+ // else {
31
+ // if(skipApiCall) return false;
32
+ // const resp = await callAdminForthApi({
33
+ // path: '/check_auth',
34
+ // method: 'POST',
35
+ // });
36
+ // if (resp.status !== 401) {
37
+ // authorize();
38
+ // return true;
39
+ // }
40
+ // else {
41
+ // unauthorize();
42
+ // return false;}
43
+ // }
44
+
45
+ // }
46
+
47
+ return {
48
+ isAuthorized,
49
+ authorize,
50
+ unauthorize,
51
+ logout
52
+ }
53
+
54
+ });
@@ -4,6 +4,7 @@ import type { CoreConfig } from './spa_types/core';
4
4
  import router from "./router";
5
5
  import { useRouter } from 'vue-router';
6
6
  import { useCoreStore } from './stores/core';
7
+ import { useUserStore } from './stores/user';
7
8
 
8
9
  export async function callApi({path, method, body=undefined} ) {
9
10
  const options = {
@@ -16,14 +17,18 @@ export async function callApi({path, method, body=undefined} ) {
16
17
  const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}${path}`;
17
18
  const r = await fetch(fullPath, options);
18
19
  if (r.status == 401) {
19
- console.log('router', router);
20
- router.push({name: 'login'});
20
+ useUserStore().unauthorize();
21
+ router.push({ name: 'login' });
21
22
  return null;
22
- }
23
+ }
23
24
  return await r.json();
24
25
  }
25
26
 
26
- export async function callAdminForthApi({ path, method, body=undefined }) {
27
+ export async function callAdminForthApi({ path, method, body=undefined }: {
28
+ path: string,
29
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE',
30
+ body?: any
31
+ }) {
27
32
  try {
28
33
  return callApi({path: `/adminapi/v1${path}`, method, body} );
29
34
  } catch (e) {
@@ -137,6 +137,13 @@ async function saveRecord() {
137
137
  record: record.value,
138
138
  },
139
139
  });
140
+ if (response.error) {
141
+ window.adminforth.alert({
142
+ message: resp.error,
143
+ variant: 'danger',
144
+ timeout: 'unlimited',
145
+ })
146
+ }
140
147
  saving.value = false;
141
148
  if (route.query.returnTo) {
142
149
  router.push(route.query.returnTo);
@@ -145,7 +145,7 @@ async function saveRecord() {
145
145
  }
146
146
  }
147
147
 
148
- await callAdminForthApi({
148
+ const resp = await callAdminForthApi({
149
149
  method: 'POST',
150
150
  path: `/update_record`,
151
151
  body: {
@@ -154,6 +154,13 @@ async function saveRecord() {
154
154
  record: updates,
155
155
  },
156
156
  });
157
+ if (resp.error) {
158
+ window.adminforth.alert({
159
+ message: resp.error,
160
+ variant: 'danger',
161
+ timeout: 'unlimited',
162
+ })
163
+ }
157
164
  saving.value = false;
158
165
  router.push({ name: 'resource-show', params: { resourceId: route.params.resourceId, primaryKey: coreStore.record[coreStore.primaryKey] } });
159
166
  }
@@ -183,7 +183,16 @@ async function getList() {
183
183
  sort: sort.value,
184
184
  }
185
185
  });
186
-
186
+ if (data.error) {
187
+ window.adminforth.alert({
188
+ message: data.error,
189
+ variant: 'danger',
190
+ timeout: 'unlimited',
191
+ });
192
+ rows.value = [];
193
+ totalRows.value = 0;
194
+ return;
195
+ }
187
196
  rows.value = data.data?.map(row => {
188
197
  row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
189
198
  return row;
@@ -81,6 +81,7 @@
81
81
 
82
82
  import { onMounted, ref, watchEffect } from 'vue';
83
83
  import { useCoreStore } from '@/stores/core';
84
+ import { useUserStore } from '@/stores/user';
84
85
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
85
86
  import { callAdminForthApi, loadFile } from '@/utils';
86
87
  import { useRouter } from 'vue-router';
@@ -91,6 +92,8 @@ const router = useRouter();
91
92
  const inProgress = ref(false);
92
93
 
93
94
  const coreStore = useCoreStore();
95
+ const user = useUserStore();
96
+
94
97
 
95
98
  const showPw = ref(false);
96
99
 
@@ -115,6 +118,7 @@ async function login() {
115
118
  error.value = resp.error;
116
119
  } else {
117
120
  error.value = null;
121
+ user.authorize()
118
122
  router.push('/');
119
123
  await router.isReady();
120
124
  await coreStore.fetchMenuAndResource();
@@ -52,6 +52,15 @@ export var AdminForthResourcePages;
52
52
  AdminForthResourcePages["create"] = "create";
53
53
  AdminForthResourcePages["filter"] = "filter";
54
54
  })(AdminForthResourcePages || (AdminForthResourcePages = {}));
55
+ export var AllowedActionsEnum;
56
+ (function (AllowedActionsEnum) {
57
+ AllowedActionsEnum["show"] = "show";
58
+ AllowedActionsEnum["list"] = "list";
59
+ AllowedActionsEnum["edit"] = "edit";
60
+ AllowedActionsEnum["create"] = "create";
61
+ AllowedActionsEnum["delete"] = "delete";
62
+ AllowedActionsEnum["filter"] = "filter";
63
+ })(AllowedActionsEnum || (AllowedActionsEnum = {}));
55
64
  export var AdminForthDataTypes;
56
65
  (function (AdminForthDataTypes) {
57
66
  AdminForthDataTypes["STRING"] = "string";
@@ -1,7 +1,7 @@
1
1
  export var AlertVariant;
2
2
  (function (AlertVariant) {
3
- AlertVariant["Danger"] = "danger";
4
- AlertVariant["Success"] = "success";
5
- AlertVariant["Warning"] = "warning";
6
- AlertVariant["Info"] = "info";
3
+ AlertVariant["danger"] = "danger";
4
+ AlertVariant["success"] = "success";
5
+ AlertVariant["warning"] = "warning";
6
+ AlertVariant["info"] = "info";
7
7
  })(AlertVariant || (AlertVariant = {}));