Skip to content

Commit a5e0a34

Browse files
committed
Merge branch 'next' of github.com:devforth/adminforth into next
2 parents a55ff66 + 60a808e commit a5e0a34

File tree

10 files changed

+99
-14
lines changed

10 files changed

+99
-14
lines changed

adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,15 +595,15 @@ For example "OAuth2 plugin", when registers a login button component for login p
595595
596596
```ts title="/index.ts"
597597
// plugin CODE
598-
YYY.push({
598+
adminforth.config.customization.loginPageInjections.underLoginButton.push({
599599
file: '@@/..vue',
600600
meta: {
601601
afOrder: this.pluginOptions.YYY || 0
602602
}
603603
})
604604
```
605605
606-
So you can jsut pass `YYY` option to the plugin to control the order of the injection.
606+
So you can just pass `YYY` option to the plugin to control the order of the injection.
607607
608608
## Custom scripts in head
609609

adminforth/modules/configValidator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,11 @@ export default class ConfigValidator implements IConfigValidator {
776776
return col as AdminForthResourceColumn;
777777
})
778778

779+
// Check for multiple sticky columns
780+
if (res.columns.filter(c => c.listSticky).length > 1) {
781+
errors.push(`Resource "${res.resourceId}" has more than one listSticky column. Only one column can be sticky in the list view.`);
782+
}
783+
779784
const options: Partial<AdminForthResource['options']> = {...resInput.options, bulkActions: undefined, allowedActions: undefined};
780785

781786
options.allowedActions = this.validateAndNormalizeAllowedActions(resInput, errors);

adminforth/servers/express.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import path from 'path';
33
import fs from 'fs';
44
import { Express } from 'express';
55
import fetch from 'node-fetch';
6-
import { IAdminForth, IExpressHttpServer } from '../types/Back.js';
6+
import { AdminUserAuthorizeFunction, IAdminForth, IExpressHttpServer, HttpExtra } from '../types/Back.js';
77
import { WebSocketServer } from 'ws';
88
import { WebSocketClient } from './common.js';
99
import { AdminUser } from '../types/Common.js';
1010
import http from 'http';
1111
import { randomUUID } from 'crypto';
12+
import { listify } from '../modules/utils.js';
1213

1314
function replaceAtStart(string, substring) {
1415
if (string.startsWith(substring)) {
@@ -216,6 +217,25 @@ class ExpressServer implements IExpressHttpServer {
216217
this.server.listen(...args);
217218
}
218219

220+
async processAuthorizeCallbacks(adminUser: AdminUser, toReturn: { error?: string, allowed: boolean }, response: Response, extra: any) {
221+
const adminUserAuthorize = this.adminforth.config.auth.adminUserAuthorize as (AdminUserAuthorizeFunction[] | undefined);
222+
223+
for (const hook of listify(adminUserAuthorize)) {
224+
const resp = await hook({
225+
adminUser,
226+
response,
227+
adminforth: this.adminforth,
228+
extra,
229+
});
230+
if (resp?.allowed === false || resp?.error) {
231+
// delete all items from toReturn and add these:
232+
toReturn.allowed = resp?.allowed;
233+
toReturn.error = resp?.error;
234+
break;
235+
}
236+
}
237+
}
238+
219239

220240
authorize(handler) {
221241
return async (req, res, next) => {
@@ -248,7 +268,13 @@ class ExpressServer implements IExpressHttpServer {
248268
res.status(401).send('Unauthorized by AdminForth');
249269
} else {
250270
req.adminUser = adminforthUser;
251-
handler(req, res, next);
271+
const toReturn: { error?: string, allowed: boolean } = { allowed: true };
272+
await this.processAuthorizeCallbacks(adminforthUser, toReturn, res, {});
273+
if (!toReturn.allowed) {
274+
res.status(401).send('Unauthorized by AdminForth');
275+
} else {
276+
handler(req, res, next);
277+
}
252278
}
253279
};
254280
}

adminforth/spa/src/afcl/Table.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@
237237
isLoading.value = false;
238238
}
239239
dataResult.value = result;
240-
} else {
240+
} else if (typeof props.data === 'object' && Array.isArray(props.data)) {
241241
const start = (currentPage.value - 1) * props.pageSize;
242242
const end = start + props.pageSize;
243243
dataResult.value = { data: props.data.slice(start, end), total: props.data.length };

adminforth/spa/src/components/ResourceListTable.vue

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
<tbody>
1616
<!-- table header -->
17-
<tr class="t-header sticky z-10 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
18-
<td scope="col" class="p-4">
17+
<tr class="t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
18+
<td scope="col" class="p-4 sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading">
1919
<Checkbox
2020
:modelValue="allFromThisPageChecked"
2121
:disabled="!rows || !rows.length"
@@ -25,7 +25,7 @@
2525
</Checkbox>
2626
</td>
2727

28-
<td v-for="c in columnsListed" ref="headerRefs" scope="col" class="px-2 md:px-3 lg:px-6 py-3">
28+
<td v-for="c in columnsListed" ref="headerRefs" scope="col" class="px-2 md:px-3 lg:px-6 py-3" :class="{'sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading': c.listSticky}">
2929

3030
<div @click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
3131
class="flex items-center " :class="{'cursor-pointer':c.sortable}">
@@ -90,7 +90,7 @@
9090

9191
:class="{'border-b': rowI !== rows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
9292
>
93-
<td class="w-4 p-4 cursor-default" @click="(e)=>e.stopPropagation()">
93+
<td class="w-4 p-4 cursor-default sticky-column bg-lightListTable dark:bg-darkListTable" @click="(e)=>e.stopPropagation()">
9494
<Checkbox
9595
:model-value="checkboxesInternal.includes(row._primaryKeyValue)"
9696
@change="(e: any)=>{addToCheckedValues(row._primaryKeyValue)}"
@@ -100,7 +100,7 @@
100100
</Checkbox>
101101
</td>
102102

103-
<td v-for="c in columnsListed" class="px-2 md:px-3 lg:px-6 py-4">
103+
<td v-for="c in columnsListed" class="px-2 md:px-3 lg:px-6 py-4" :class="{'sticky-column bg-lightListTable dark:bg-darkListTable': c.listSticky}">
104104
<!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
105105
<component
106106
:is="c?.components?.list ? getCustomComponent(typeof c.components.list === 'string' ? { file: c.components.list } : c.components.list) : ValueRenderer"
@@ -598,4 +598,15 @@ input[type="checkbox"][disabled] {
598598
input[type="checkbox"]:not([disabled]) {
599599
@apply cursor-pointer;
600600
}
601+
td.sticky-column {
602+
@apply sticky left-0 z-10;
603+
&:not(:first-child) {
604+
@apply left-[56px];
605+
}
606+
}
607+
tr:not(:first-child):hover {
608+
td.sticky-column {
609+
@apply bg-lightListTableRowHover dark:bg-darkListTableRowHover;
610+
}
611+
}
601612
</style>

adminforth/spa/src/components/ResourceListTableVirtual.vue

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<tbody>
2020
<!-- table header -->
2121
<tr class="t-header sticky z-10 top-0 text-xs bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-gray-400">
22-
<td scope="col" class="p-4">
22+
<td scope="col" class="p-4 sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading">
2323
<Checkbox
2424
:modelValue="allFromThisPageChecked"
2525
:disabled="!rows || !rows.length"
@@ -29,7 +29,7 @@
2929
</Checkbox>
3030
</td>
3131

32-
<td v-for="c in columnsListed" ref="headerRefs" scope="col" class="px-2 md:px-3 lg:px-6 py-3">
32+
<td v-for="c in columnsListed" ref="headerRefs" scope="col" class="px-2 md:px-3 lg:px-6 py-3" :class="{'sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading': c.listSticky}">
3333

3434
<div @click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
3535
class="flex items-center " :class="{'cursor-pointer':c.sortable}">
@@ -101,7 +101,7 @@
101101
:class="{'border-b': rowI !== visibleRows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
102102
@mounted="(el: any) => updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
103103
>
104-
<td class="w-4 p-4 cursor-default" @click="(e)=>e.stopPropagation()">
104+
<td class="w-4 p-4 cursor-default sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading" @click="(e)=>e.stopPropagation()">
105105
<Checkbox
106106
:model-value="checkboxesInternal.includes(row._primaryKeyValue)"
107107
@change="(e: any)=>{addToCheckedValues(row._primaryKeyValue)}"
@@ -110,7 +110,7 @@
110110
<span class="sr-only">{{ $t('checkbox') }}</span>
111111
</Checkbox>
112112
</td>
113-
<td v-for="c in columnsListed" class="px-2 md:px-3 lg:px-6 py-4">
113+
<td v-for="c in columnsListed" class="px-2 md:px-3 lg:px-6 py-4" :class="{'sticky-column bg-lightListTable dark:bg-darkListTable': c.listSticky}">
114114
<!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
115115
<component
116116
:is="c?.components?.list ? getCustomComponent(typeof c.components.list === 'string' ? { file: c.components.list } : c.components.list) : ValueRenderer"
@@ -733,4 +733,15 @@ input[type="checkbox"][disabled] {
733733
input[type="checkbox"]:not([disabled]) {
734734
@apply cursor-pointer;
735735
}
736+
td.sticky-column {
737+
@apply sticky left-0 z-10;
738+
&:not(:first-child) {
739+
@apply left-[56px];
740+
}
741+
}
742+
tr:not(:first-child):hover {
743+
td.sticky-column {
744+
@apply bg-lightListTableRowHover dark:bg-darkListTableRowHover;
745+
}
746+
}
736747
</style>

adminforth/types/Back.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,19 @@ export type BeforeLoginConfirmationFunction = (params?: {
611611
}
612612
}>;
613613

614+
/**
615+
* Allow to make extra authorization
616+
*/
617+
export type AdminUserAuthorizeFunction = ((params?: {
618+
adminUser: AdminUser,
619+
response: IAdminForthHttpResponse,
620+
adminforth: IAdminForth,
621+
extra?: HttpExtra,
622+
}) => Promise<{
623+
error?: string,
624+
allowed?: boolean,
625+
}>);
626+
614627

615628
/**
616629
* Data source describes database connection which will be used to fetch data for resources.
@@ -1009,6 +1022,11 @@ export interface AdminForthInputConfig {
10091022
*/
10101023
beforeLoginConfirmation?: BeforeLoginConfirmationFunction | Array<BeforeLoginConfirmationFunction>,
10111024

1025+
/**
1026+
* Array of functions which will be called before any request to AdminForth API.
1027+
*/
1028+
adminUserAuthorize?: AdminUserAuthorizeFunction | Array<AdminUserAuthorizeFunction>,
1029+
10121030
/**
10131031
* Optionally if your users table has a field(column) with full name, you can set it here.
10141032
* This field will be used to display user name in the top right corner of the admin panel.

adminforth/types/Common.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,10 @@ export interface AdminForthResourceColumnInputCommon {
872872
*/
873873
masked?: boolean,
874874

875+
/**
876+
* Sticky position for column
877+
*/
878+
listSticky?: boolean;
875879
}
876880

877881
export interface AdminForthResourceColumnCommon extends AdminForthResourceColumnInputCommon {

dev-demo/package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev-demo/resources/apartments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export default {
139139
},
140140
{
141141
name: "country",
142+
listSticky: true,
142143
components: {
143144
list: {
144145
file: "@/renderers/CountryFlag.vue",

0 commit comments

Comments
 (0)