Skip to content

Commit 71c57e2

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth
2 parents 5f386f5 + 779af5d commit 71c57e2

79 files changed

Lines changed: 2289 additions & 11900 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/settings.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"files.watcherExclude": {
3+
"**/.cache": true,
4+
"**/.cache/**": true,
5+
"**/.git": true,
6+
"**/.git/**": true,
7+
"**/.pnpm-store": true,
8+
"**/.pnpm-store/**": true,
9+
"**/.turbo": true,
10+
"**/.turbo/**": true,
11+
"**/.vite": true,
12+
"**/.vite/**": true,
13+
"**/build": true,
14+
"**/build/**": true,
15+
"**/coverage": true,
16+
"**/coverage/**": true,
17+
"**/dist": true,
18+
"**/dist/**": true,
19+
"**/node_modules": true,
20+
"**/node_modules/**": true
21+
},
22+
"search.exclude": {
23+
"**/.cache": true,
24+
"**/.git": true,
25+
"**/.pnpm-store": true,
26+
"**/.turbo": true,
27+
"**/.vite": true,
28+
"**/build": true,
29+
"**/coverage": true,
30+
"**/dist": true,
31+
"**/node_modules": true
32+
},
33+
"git.autoRepositoryDetection": "openEditors"
34+
}

adminforth/basePlugin.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default class AdminForthPlugin implements IAdminForthPlugin {
2020
resourceConfig: AdminForthResource;
2121
className: string;
2222
activationOrder: number = 0;
23+
pluginsScope: 'resource' | 'global' = 'resource';
2324
shouldHaveSingleInstancePerWholeApp?: () => boolean;
2425

2526
constructor(pluginOptions: any, metaUrl: string) {
@@ -41,14 +42,27 @@ export default class AdminForthPlugin implements IAdminForthPlugin {
4142
}
4243

4344

45+
initializePluginInstanceId = (resourceConfig?: AdminForthResource) => {
46+
const uniqueness = this.instanceUniqueRepresentation(this.pluginOptions);
47+
let seed = '';
48+
if (resourceConfig) {
49+
seed = `af_pl_${this.constructor.name}_${resourceConfig?.resourceId || '_'}_${uniqueness}`;
50+
} else {
51+
seed = `af_pl_${this.constructor.name}_global_${uniqueness}`;
52+
}
53+
this.pluginInstanceId = md5hash(seed);
54+
afLogger.trace({seed, pluginInstanceId: this.pluginInstanceId}, `🪲 AdminForthPlugin.initializePluginInstanceId`);
55+
}
56+
4457
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource, allPluginInstances?: {pi: AdminForthPlugin, resource: AdminForthResource}[]) {
4558
this.resourceConfig = resourceConfig;
46-
const uniqueness = this.instanceUniqueRepresentation(this.pluginOptions);
59+
this.initializePluginInstanceId(resourceConfig);
60+
this.adminforth = adminforth;
61+
}
4762

48-
const seed = `af_pl_${this.constructor.name}_${resourceConfig.resourceId}_${uniqueness}`;
49-
this.pluginInstanceId = md5hash(seed);
50-
afLogger.trace({seed, pluginInstanceId: this.pluginInstanceId}, `🪲 AdminForthPlugin.modifyResourceConfig`);
63+
modifyGlobalConfig(adminforth: IAdminForth) {
5164
this.adminforth = adminforth;
65+
this.initializePluginInstanceId();
5266
}
5367

5468
/**
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const globalPlugins = []

adminforth/commands/createApp/templates/index.ts.hbs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path from 'path';
66
import { Filters } from 'adminforth';
77
import { initApi } from './api.js';
88
import { logger } from 'adminforth';
9+
import { globalPlugins } from './globalPlugins.js';
910

1011
const ADMIN_BASE_URL = '';
1112

@@ -66,6 +67,7 @@ export const admin = new AdminForth({
6667
resourceId: 'adminuser'
6768
},
6869
],
70+
globalPlugins: globalPlugins,
6971
});
7072

7173
if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {

adminforth/commands/createApp/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,11 @@ async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations,
428428
src: 'custom/package.json.hbs',
429429
dest: 'custom/package.json',
430430
data: {}
431+
},
432+
{
433+
src: 'globalPlugins.ts.hbs',
434+
dest: 'globalPlugins.ts',
435+
data: {},
431436
}
432437
];
433438

adminforth/dataConnectors/baseConnector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
3737
client: any;
3838

3939
/**
40-
* @deprecated Since 1.2.9. Will be removed in 2.0.0. Use .client instead.
40+
* @deprecated Since 1.2.9. Will be removed in 4.0.0. Use .client instead.
4141
*/
4242
get db() {
4343
afLogger.warn('.db is deprecated, use .client instead');

adminforth/documentation/docs/tutorial/03-Customization/02-customFieldRendering.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,30 @@ For fields containing sensitive data (like passwords, API keys, tokens, or other
605605
606606
The renderer wraps the standard value output and adds a click-to-reveal blur effect. Clicking again hides the value.
607607
608+
For long values (like API keys) you can enable compact mode by passing `compact: true` via `meta`. When set, the value is shortened the same way as the `CompactUUID` renderer (first 4 + `...` + last 4 characters). In compact mode you can additionally pass `copy: true` to render a copy-to-clipboard button next to the value. The copy button is only shown once the value is revealed (blur removed):
609+
610+
```ts title='./resources/anyResource.ts'
611+
columns: [
612+
...
613+
{
614+
name: 'api_key',
615+
components: {
616+
show: {
617+
//diff-add
618+
file: '@/renderers/SensitiveBlurCell.vue',
619+
//diff-add
620+
meta: { compact: true, copy: true },
621+
},
622+
list: {
623+
//diff-add
624+
file: '@/renderers/SensitiveBlurCell.vue',
625+
//diff-add
626+
meta: { compact: true, copy: true },
627+
},
628+
},
629+
...
630+
```
631+
608632
609633
### Custom filter component for square meters
610634

adminforth/documentation/docs/tutorial/03-Customization/11-dataApi.md

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,20 +300,49 @@ This lets you answer questions like:
300300

301301
### Available aggregates
302302
- Aggregates.count()
303-
- Aggregates.avg(field)
303+
- Aggregates.countDistinct(field)
304+
- Aggregates.avg(field)
304305
- Aggregates.sum(field)
306+
- Aggregates.min(field)
307+
- Aggregates.max(field)
305308
- Aggregates.median(field)
306309

307310
### Available grouping
308-
- GroupBy.Field(field)
309-
- GroupBy.DateTrunc(field, unit, timezone)
311+
- GroupBy.Field(field, as?)
312+
- GroupBy.DateTrunc(field, unit, timezone?, as?)
313+
314+
You can pass either one grouping rule or an array of grouping rules.
315+
When you use a single grouping rule, the grouping value is returned in `group`.
316+
When you use several grouping rules, the values are returned in `group1`, `group2`, etc.
317+
318+
To use explicit response keys, pass the optional `as` argument:
319+
320+
```ts
321+
GroupBy.Field('country', 'country')
322+
GroupBy.DateTrunc('created_at', 'month', 'Europe/Kyiv', 'month')
323+
```
310324

311325
Example:
312326
```ts
313327
GroupBy.DateTrunc('created_at', 'month', 'Europe/Kyiv')
314328
```
315329

316330
### Response format
331+
Without grouping, each row contains only requested aggregate aliases:
332+
333+
```ts
334+
[
335+
{
336+
count: number | string,
337+
avgPrice?: number | null,
338+
sum?: number | null,
339+
medianPrice?: number | null,
340+
}
341+
]
342+
```
343+
344+
With one grouping rule:
345+
317346
```ts
318347
[
319348
{
@@ -326,6 +355,35 @@ GroupBy.DateTrunc('created_at', 'month', 'Europe/Kyiv')
326355
]
327356
```
328357

358+
With several grouping rules:
359+
360+
```ts
361+
[
362+
{
363+
group1: string,
364+
group2: string,
365+
count: number | string,
366+
avgPrice?: number | null,
367+
}
368+
]
369+
```
370+
371+
With explicit grouping aliases:
372+
373+
```ts
374+
[
375+
{
376+
country: string,
377+
month: string,
378+
count: number | string,
379+
uniqueOwners?: number | string,
380+
minPrice?: number | null,
381+
maxPrice?: number | null,
382+
avgPrice?: number | null,
383+
}
384+
]
385+
```
386+
329387
### Get daily apartment stats (count, avg, sum, median) for listed apartments
330388
```ts
331389
const rows = await admin.resource('apartments').aggregate(
@@ -369,4 +427,32 @@ What is happening here:
369427
- [] → no filters (all records)
370428
- GroupBy.Field('country')
371429
→ grouping by country
372-
- same aggregates (count, avg, sum, median)
430+
- same aggregates (count, avg, sum, median)
431+
432+
### Get apartment stats grouped by country and month
433+
```ts
434+
const rows = await admin.resource('apartments').aggregate(
435+
[],
436+
{
437+
count: Aggregates.count(),
438+
uniqueOwners: Aggregates.countDistinct('owner_id'),
439+
minPrice: Aggregates.min('price'),
440+
maxPrice: Aggregates.max('price'),
441+
avgPrice: Aggregates.avg('price'),
442+
},
443+
[
444+
GroupBy.Field('country', 'country'),
445+
GroupBy.DateTrunc('created_at', 'month', 'Europe/Kyiv', 'month'),
446+
],
447+
);
448+
```
449+
450+
What is happening here:
451+
- [] → no filters (all records)
452+
- GroupBy.Field('country', 'country')
453+
→ groups by country and returns the value in the `country` key
454+
- GroupBy.DateTrunc('created_at', 'month', 'Europe/Kyiv', 'month')
455+
→ groups by month and returns the value in the `month` key
456+
- countDistinct('owner_id') → number of unique owners in each group
457+
- min('price') and max('price') → price range in each group
458+
- the result has one row per country and month combination

adminforth/documentation/docs/tutorial/03-Customization/12-security.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ new AdminForth({
5353
5454
The format is `requests/period`, where period can use `s`, `m`, `h`, or `d`.
5555
Because rate limits are keyed by client IP, configure `auth.clientIpHeader` when AdminForth runs behind a trusted CDN or reverse proxy. See [Trusting client IP addresses](#trusting-client-ip-addresses).
56+
> It is important to provide '`auth.clientIpHeader`, because otherwise adminforth will automatically detect client IP in headers and if you don't use proxy, hacker can change IP like `x-forwarded-for: 1.1.1.1` in request headers and skip rate limit
5657
5758
5859
## Password strength

adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,26 @@ And `edit` action will be available as quick action:
542542
543543

544544

545+
## Show
546+
547+
### Next record button
548+
549+
By default, when a user opens a record from the list view, a **Next** button appears on the show page. It allows navigating through records one by one, respecting the current filters and sorting applied in the list. When the user reaches the last record on the current page, AdminForth automatically fetches the next page and continues navigation seamlessly.
550+
551+
To disable the Next button for a resource, set `showNextButton` to `false`:
552+
553+
```typescript title="./resources/apartments.ts"
554+
export default {
555+
resourceId: 'aparts',
556+
options: {
557+
//diff-add
558+
showNextButton: false,
559+
}
560+
}
561+
```
562+
563+
> ☝️ The Next button is only shown when the user navigates to the show page from the list view. Opening a record directly via URL will not display the button.
564+
545565
## Creating
546566

547567
### Fill with default values

0 commit comments

Comments
 (0)