Skip to content

Commit ec74795

Browse files
committed
Merge branches 'next' and 'next' of github.com:devforth/adminforth into next
2 parents 4f27ea8 + bc72148 commit ec74795

File tree

7 files changed

+153
-116
lines changed

7 files changed

+153
-116
lines changed

adminforth/servers/express.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ class ExpressServer implements IExpressHttpServer {
217217
this.server.listen(...args);
218218
}
219219

220-
async processAuthorizeCallbacks(adminUser: AdminUser, toReturn: { error?: string, allowed: boolean }, response: Response, extra: any) {
220+
async processAuthorizeCallbacks(adminUser: AdminUser, toReturn: { error?: string, allowed: boolean }, response: Response, extra: HttpExtra) {
221221
const adminUserAuthorize = this.adminforth.config.auth.adminUserAuthorize as (AdminUserAuthorizeFunction[] | undefined);
222222

223223
for (const hook of listify(adminUserAuthorize)) {
@@ -269,7 +269,15 @@ class ExpressServer implements IExpressHttpServer {
269269
} else {
270270
req.adminUser = adminforthUser;
271271
const toReturn: { error?: string, allowed: boolean } = { allowed: true };
272-
await this.processAuthorizeCallbacks(adminforthUser, toReturn, res, {});
272+
await this.processAuthorizeCallbacks(adminforthUser, toReturn, res, {
273+
body: req.body,
274+
query: req.query,
275+
headers: req.headers,
276+
cookies: cookies as any,
277+
requestUrl: req.url,
278+
meta: {},
279+
response: res
280+
});
273281
if (!toReturn.allowed) {
274282
res.status(401).send('Unauthorized by AdminForth');
275283
} else {

adminforth/spa/src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './utils';
2+
export * from './listUtils';
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { nextTick, onMounted, ref, resolveComponent } from 'vue';
2+
import { callAdminForthApi } from '@/utils';
3+
import { type AdminForthResourceCommon } from '../types/Common';
4+
import { useAdminforth } from '@/adminforth';
5+
import { showErrorTost } from '@/composables/useFrontendApi'
6+
7+
8+
export async function getList(resource: AdminForthResourceCommon, isPageLoaded: boolean, page: number | null , pageSize: number, sort: any, checkboxes:{ value: any[] }, filters: any = [] ) {
9+
let rows: any[] = [];
10+
let totalRows: number | null = null;
11+
if (!isPageLoaded) {
12+
return;
13+
}
14+
const data = await callAdminForthApi({
15+
path: '/get_resource_data',
16+
method: 'POST',
17+
body: {
18+
source: 'list',
19+
resourceId: resource.resourceId,
20+
limit: pageSize,
21+
offset: ((page || 1) - 1) * pageSize,
22+
filters: filters,
23+
sort: sort,
24+
}
25+
});
26+
if (data.error) {
27+
showErrorTost(data.error);
28+
rows = [];
29+
totalRows = 0;
30+
return {rows, totalRows, error: data.error};
31+
}
32+
rows = data.data?.map((row: any) => {
33+
if (resource?.columns?.find(c => c.primaryKey)?.foreignResource) {
34+
row._primaryKeyValue = row[resource.columns.find(c => c.primaryKey)!.name].pk;
35+
} else if (resource) {
36+
row._primaryKeyValue = row[resource.columns.find(c => c.primaryKey)!.name];
37+
}
38+
return row;
39+
});
40+
totalRows = data.total;
41+
42+
// if checkboxes have items which are not in current data, remove them
43+
checkboxes.value = checkboxes.value.filter((pk: any) => rows.some((r: any) => r._primaryKeyValue === pk));
44+
await nextTick();
45+
return { rows, totalRows };
46+
}
47+
48+
49+
50+
export async function startBulkAction(actionId: string, resource: AdminForthResourceCommon, checkboxes: { value: any[] },
51+
bulkActionLoadingStates: {value: Record<string, boolean>}, getListInner: () => Promise<any>) {
52+
const action = resource?.options?.bulkActions?.find(a => a.id === actionId);
53+
const { confirm, alert } = useAdminforth();
54+
55+
if (action?.confirm) {
56+
const confirmed = await confirm({
57+
message: action.confirm,
58+
});
59+
if (!confirmed) {
60+
return;
61+
}
62+
}
63+
bulkActionLoadingStates.value[actionId] = true;
64+
65+
const data = await callAdminForthApi({
66+
path: '/start_bulk_action',
67+
method: 'POST',
68+
body: {
69+
resourceId: resource.resourceId,
70+
actionId: actionId,
71+
recordIds: checkboxes.value
72+
}
73+
});
74+
bulkActionLoadingStates.value[actionId] = false;
75+
if (data?.ok) {
76+
checkboxes.value = [];
77+
await getListInner();
78+
79+
if (data.successMessage) {
80+
alert({
81+
message: data.successMessage,
82+
variant: 'success'
83+
});
84+
}
85+
86+
}
87+
if (data?.error) {
88+
showErrorTost(data.error);
89+
}
90+
}
Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import { onMounted, ref, resolveComponent } from 'vue';
2-
import type { CoreConfig } from './spa_types/core';
3-
import type { ValidationObject } from './types/Common.js';
4-
import router from "./router";
5-
import { useCoreStore } from './stores/core';
6-
import { useUserStore } from './stores/user';
1+
import { nextTick, onMounted, ref, resolveComponent } from 'vue';
2+
import type { CoreConfig } from '../spa_types/core';
3+
import type { ValidationObject } from '../types/Common.js';
4+
import router from "../router";
5+
import { useCoreStore } from '../stores/core';
6+
import { useUserStore } from '../stores/user';
77
import { Dropdown } from 'flowbite';
8-
import adminforth from './adminforth';
8+
import adminforth from '../adminforth';
99
import sanitizeHtml from 'sanitize-html'
1010
import debounce from 'debounce';
1111
import type { AdminForthResourceColumnInputCommon, Predicate } from '@/types/Common';
12-
import { i18nInstance } from './i18n'
12+
import { i18nInstance } from '../i18n'
13+
14+
1315

1416
const LS_LANG_KEY = `afLanguage`;
1517
const MAX_CONSECUTIVE_EMPTY_RESULTS = 2;
@@ -112,7 +114,7 @@ export const loadFile = (file: string) => {
112114
baseUrl = new URL(`./${path}`, import.meta.url).href;
113115
} else if (file.startsWith('@@/')) {
114116
path = file.replace('@@/', '');
115-
baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
117+
baseUrl = new URL(`../custom/${path}`, import.meta.url).href;
116118
} else {
117119
baseUrl = new URL(`./${file}`, import.meta.url).href;
118120
}
@@ -513,4 +515,4 @@ export function btoa_function(source: string): string {
513515

514516
export function atob_function(source: string): string {
515517
return atob(source);
516-
}
518+
}

adminforth/spa/src/views/CreateView.vue

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -200,23 +200,25 @@ async function saveRecord() {
200200
});
201201
if (response?.error && response?.error !== 'Operation aborted by hook') {
202202
showErrorTost(response.error);
203-
}
204-
saving.value = false;
205-
if (route.query.returnTo) {
206-
router.push(<string>route.query.returnTo);
207203
} else {
208-
router.push({
209-
name: 'resource-show',
210-
params: {
211-
resourceId: route.params.resourceId,
212-
primaryKey: response.newRecordId
213-
}
214-
});
215-
alert({
216-
message: t('Record created successfully!'),
217-
variant: 'success'
218-
});
204+
saving.value = false;
205+
if (route.query.returnTo) {
206+
router.push(<string>route.query.returnTo);
207+
} else {
208+
router.push({
209+
name: 'resource-show',
210+
params: {
211+
resourceId: route.params.resourceId,
212+
primaryKey: response.newRecordId
213+
}
214+
});
215+
alert({
216+
message: t('Record created successfully!'),
217+
variant: 'success'
218+
});
219+
}
219220
}
221+
saving.value = false;
220222
}
221223
222224
function scrollToInvalidField() {

adminforth/spa/src/views/EditView.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ async function saveRecord() {
198198
if (columnIsUpdated) {
199199
updates[key] = record.value[key];
200200
}
201+
saving.value = false;
201202
}
202203
203204
const resp = await callAdminForthApi({

adminforth/spa/src/views/ListView.vue

Lines changed: 21 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
<button
4949
v-if="!action.showInThreeDotsDropdown"
5050
:key="action.id"
51-
@click="startBulkAction(action.id!)"
51+
@click="startBulkActionInner(action.id!)"
5252
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded-default border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
5353
:class="action.buttonCustomCssClass || ''"
5454
>
@@ -104,8 +104,8 @@
104104
:threeDotsDropdownItems="(coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems as [])"
105105
:bulkActions="coreStore.resource?.options?.bulkActions"
106106
:checkboxes="checkboxes"
107-
@startBulkAction="startBulkAction"
108-
:updateList="getList"
107+
@startBulkAction="startBulkActionInner"
108+
:updateList="getListInner"
109109
:clearCheckboxes="clearCheckboxes"
110110
></ThreeDotsMenu>
111111
</BreadcrumbsWithButtons>
@@ -126,7 +126,7 @@
126126
@update:page="page = $event"
127127
@update:sort="sort = $event"
128128
@update:checkboxes="checkboxes = $event"
129-
@update:records="getList"
129+
@update:records="getListInner"
130130
:sort="sort"
131131
:pageSize="pageSize"
132132
:totalRows="totalRows"
@@ -164,7 +164,7 @@
164164
@update:page="page = $event"
165165
@update:sort="sort = $event"
166166
@update:checkboxes="checkboxes = $event"
167-
@update:records="getList"
167+
@update:records="getListInner"
168168
:sort="sort"
169169
:pageSize="pageSize"
170170
:totalRows="totalRows"
@@ -209,10 +209,9 @@ import ResourceListTable from '@/components/ResourceListTable.vue';
209209
import { useCoreStore } from '@/stores/core';
210210
import { useFiltersStore } from '@/stores/filters';
211211
import { callAdminForthApi, currentQuery, getIcon, setQuery } from '@/utils';
212-
import { computed, onMounted, onUnmounted, ref, watch, nextTick, type Ref } from 'vue';
212+
import { computed, onMounted, onUnmounted, ref, watch, type Ref } from 'vue';
213213
import { useRoute } from 'vue-router';
214-
import { showErrorTost } from '@/composables/useFrontendApi'
215-
import { getCustomComponent, initThreeDotsDropdown } from '@/utils';
214+
import { getCustomComponent, initThreeDotsDropdown, getList, startBulkAction } from '@/utils';
216215
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
217216
import { Tooltip } from '@/afcl'
218217
import type { AdminForthComponentDeclarationFull } from '@/types/Common';
@@ -228,7 +227,7 @@ import Filters from '@/components/Filters.vue';
228227
import { useAdminforth } from '@/adminforth';
229228
230229
const filtersShow = ref(false);
231-
const { confirm, alert, list } = useAdminforth();
230+
const { list } = useAdminforth();
232231
const coreStore = useCoreStore();
233232
const filtersStore = useFiltersStore();
234233
@@ -257,45 +256,6 @@ const listBufferSize = computed(() => coreStore.resource?.options?.listBufferSiz
257256
258257
const isPageLoaded = ref(false);
259258
260-
async function getList() {
261-
rows.value = null;
262-
if (!isPageLoaded.value) {
263-
return;
264-
}
265-
const data = await callAdminForthApi({
266-
path: '/get_resource_data',
267-
method: 'POST',
268-
body: {
269-
source: 'list',
270-
resourceId: route.params.resourceId,
271-
limit: pageSize.value,
272-
offset: ((page.value || 1) - 1) * pageSize.value,
273-
filters: filtersStore.filters,
274-
sort: sort.value,
275-
}
276-
});
277-
if (data.error) {
278-
showErrorTost(data.error);
279-
rows.value = [];
280-
totalRows.value = 0;
281-
return {error: data.error};
282-
}
283-
rows.value = data.data?.map((row: any) => {
284-
if (coreStore.resource?.columns?.find(c => c.primaryKey)?.foreignResource) {
285-
row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey)!.name].pk;
286-
} else if (coreStore.resource) {
287-
row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey)!.name];
288-
}
289-
return row;
290-
});
291-
totalRows.value = data.total;
292-
293-
// if checkboxes have items which are not in current data, remove them
294-
checkboxes.value = checkboxes.value.filter(pk => rows.value!.some(r => r._primaryKeyValue === pk));
295-
await nextTick();
296-
return {}
297-
}
298-
299259
function clearCheckboxes() {
300260
checkboxes.value = [];
301261
}
@@ -351,48 +311,21 @@ async function refreshExistingList(pk?: any) {
351311
return {}
352312
}
353313
314+
async function startBulkActionInner(actionId: string) {
315+
await startBulkAction(actionId, coreStore.resource!, checkboxes, bulkActionLoadingStates, getListInner);
316+
}
354317
355-
async function startBulkAction(actionId: string) {
356-
const action = coreStore.resource?.options?.bulkActions?.find(a => a.id === actionId);
357-
if (action?.confirm) {
358-
const confirmed = await confirm({
359-
message: action.confirm,
360-
});
361-
if (!confirmed) {
362-
return;
363-
}
364-
}
365-
bulkActionLoadingStates.value[actionId] = true;
366-
367-
const data = await callAdminForthApi({
368-
path: '/start_bulk_action',
369-
method: 'POST',
370-
body: {
371-
resourceId: route.params.resourceId,
372-
actionId: actionId,
373-
recordIds: checkboxes.value
374-
375-
}
376-
});
377-
bulkActionLoadingStates.value[actionId] = false;
378-
if (data?.ok) {
379-
checkboxes.value = [];
380-
await getList();
381-
382-
if (data.successMessage) {
383-
alert({
384-
message: data.successMessage,
385-
variant: 'success'
386-
});
387-
}
388-
389-
}
390-
if (data?.error) {
391-
showErrorTost(data.error);
318+
async function getListInner() {
319+
rows.value = null; // to show loading state
320+
const result = await getList(coreStore.resource!, isPageLoaded.value, page.value, pageSize.value, sort.value, checkboxes, filtersStore.filters);
321+
if (!result) {
322+
return { error: 'No result returned from getList' };
392323
}
324+
rows.value = result.rows;
325+
totalRows.value = result.totalRows ?? 0;
326+
return result.error ? { error: result.error } : {};
393327
}
394328
395-
396329
class SortQuerySerializer {
397330
static serialize(sort: {field: string, direction: 'asc' | 'desc'}[]) {
398331
return sort.map(s => `${s.field}__${s.direction}`).join(',');
@@ -471,12 +404,11 @@ async function init() {
471404
472405
watch([page, sort, () => filtersStore.filters], async () => {
473406
// console.log('🔄️ page/sort/filter change fired, page:', page.value);
474-
await getList();
407+
await getListInner();
475408
}, { deep: true });
476409
477410
list.refresh = async () => {
478-
const result = await getList();
479-
411+
const result = await getListInner();
480412
if (!result) {
481413
return {};
482414
}

0 commit comments

Comments
 (0)