Skip to content

Commit ccb5ca7

Browse files
committed
Merge branch 'AdminForth/765' of https://github.com/devforth/adminforth into AdminForth/765
2 parents d06d876 + 7dada6a commit ccb5ca7

File tree

24 files changed

+7131
-1492
lines changed

24 files changed

+7131
-1492
lines changed

adapters/install-adapters.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env bash
2-
ADAPTERS="adminforth-completion-adapter-open-ai-chat-gpt adminforth-email-adapter-aws-ses adminforth-email-adapter-mailgun adminforth-google-oauth-adapter adminforth-github-oauth-adapter adminforth-facebook-oauth-adapter adminforth-keycloak-oauth-adapter adminforth-microsoft-oauth-adapter adminforth-twitch-oauth-adapter adminforth-image-generation-adapter-openai adminforth-storage-adapter-amazon-s3 adminforth-storage-adapter-local"
2+
ADAPTERS="adminforth-completion-adapter-open-ai-chat-gpt adminforth-email-adapter-aws-ses adminforth-email-adapter-mailgun adminforth-google-oauth-adapter adminforth-github-oauth-adapter adminforth-facebook-oauth-adapter adminforth-keycloak-oauth-adapter adminforth-microsoft-oauth-adapter adminforth-twitch-oauth-adapter adminforth-image-generation-adapter-openai adminforth-storage-adapter-amazon-s3 adminforth-storage-adapter-local adminforth-image-vision-adapter-openai"
33

44
# for each
55
install_adapter() {

adminforth/dataConnectors/clickhouse.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
231231
const column = resource.dataSourceColumns.find((col) => col.name == field);
232232
let placeholder = `{f$?:${column._underlineType}}`;
233233
let operator = this.OperatorsMap[filter.operator];
234+
235+
if (column._underlineType.startsWith('Decimal')) {
236+
field = `toDecimal64(${field}, 8)`;
237+
placeholder = `toDecimal64({f$?:String}, 8)`;
238+
}
239+
234240
if ((filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) && column._underlineType == 'UUID') {
235241
placeholder = '{f$?:String}';
236242
field = `toString(${field})`;

adminforth/documentation/docs/tutorial/03-Customization/06-customPages.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,9 +468,13 @@ import { Link } from '@/afcl';
468468
469469
If you set `customLayout: true` in the `meta` object, it will not include default layout like sidebar and header, so you can create your own layout for this page.
470470
471-
> **Redirects to login page**: Any route which has sidebar and header (e.g. default CRUD pages or menu item with `component`) uses internal AdminForth REST API to fetch menu items and user information, so it passes authentication check and if authentication cookie is not provided or has expired JWT user gets redirected to the login page.
472-
> In case if you set `customLayout: true`, it will not call these APIs so user will not be automatically redirected to the login page in case of expired or not-provided authentication cookie. That feature allows you to implement public pages without authentication, e.g. Terms of Service, Privacy Policy and many others. In case if you need to check if user is logged in just call any custom API which has `admin.express.authorize` middleware. Obviously for public pages you should create API endpoint WITHOUT `admin.express.authorize` middleware.
471+
### Disable redirects to login page (Public pages)
473472
473+
Any route which has sidebar and header (e.g. default CRUD pages or menu item with `component`) uses internal AdminForth REST API to fetch menu items and user information, so it passes authentication check and if authentication cookie is not provided or has expired JWT user gets redirected to the login page.
474+
475+
In case if you set `customLayout: true`, it will not call these APIs so user will not be automatically redirected to the login page in case of expired or not-provided authentication cookie. That feature allows you to implement public pages without authentication, e.g. Terms of Service, Privacy Policy and many others. In case if you need to check if user is logged in just call any custom API which has `admin.express.authorize` middleware. Obviously for public pages if they use any APIs you should create API endpoint WITHOUT `admin.express.authorize` middleware.
476+
477+
> Please note that AdminForth uses classic SPA Vue app, so even public pages will be rendered by JavaScript in the browser and not on the server side. If your public page should be indexed by search engines, you should use some SSR framework like Nuxt.js to create such pages. At the same time public pages can still be usefull if you don't focus on old-fashioned search engines (modern search engines can index SPA pages as well) or if indexing is not important for such pages at all (e.g. Terms of Service, Privacy Policy, Contact Us and many others).
474478
475479
### Passing meta attributes to the page
476480

adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,41 @@ const closeDialog = () => {
595595
}
596596
```
597597

598+
### Before open/close dialog handlers
599+
If you want to run custom logic before the dialog opens or closes by passing callback props:
600+
601+
```ts
602+
<Dialog
603+
class="w-96"
604+
:beforeCloseFunction="onBeforeOpen"
605+
:beforeOpenFunction="onBeforeClose"
606+
>
607+
<template #trigger>
608+
<Button>Dialog Toggle</Button>
609+
</template>
610+
611+
<div class="space-y-4">
612+
<p>This is the first paragraph of dialog content.</p>
613+
<p>And this is the second paragraph.</p>
614+
</div>
615+
</Dialog>
616+
```
617+
Now you can pass before open/close functions:
618+
```ts
619+
const counter = ref(0);
620+
621+
function onBeforeOpen() {
622+
counter.value++;
623+
console.log(`custom open function called ${counter.value}`);
624+
}
625+
626+
function onBeforeClose() {
627+
counter.value++;
628+
console.log(`custom close function called ${counter.value}`);
629+
}
630+
```
631+
632+
598633
## Dropzone
599634

600635
```ts

adminforth/documentation/docs/tutorial/07-Plugins/16-email-invite.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,25 @@ export default {
9393
},
9494
},
9595
plugins: [
96+
//diff-add
9697
new EmailInvitePlugin({
98+
//diff-add
9799
emailField: 'email',
100+
//diff-add
101+
passwordField: 'password_hash',
102+
//diff-add
98103
sendFrom: 'noreply@yourapp.com',
104+
//diff-add
99105
adapter: new EmailAdapterAwsSes({
106+
//diff-add
100107
region: 'us-east-1',
108+
//diff-add
101109
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
110+
//diff-add
102111
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
112+
//diff-add
103113
}),
114+
//diff-add
104115
}),
105116
],
106117
};
@@ -153,6 +164,7 @@ export default {
153164
new EmailInvitePlugin({
154165
emailField: 'email',
155166
sendFrom: 'noreply@yourapp.com',
167+
passwordField: 'password_hash',
156168
adapter: new EmailAdapterAwsSes(/* ... */),
157169
//diff-add
158170
emailConfirmedField: 'email_confirmed', // Enable email confirmation
@@ -161,3 +173,37 @@ export default {
161173
};
162174
```
163175

176+
## Mailgun usage example
177+
If you want to use this plugin with Mailgun, first install it:
178+
179+
```bash
180+
npm install @adminforth/email-adapter-mailgun
181+
```
182+
183+
Then, in the adapter options, add:
184+
185+
``` ts
186+
//diff-add
187+
import EmailAdapterMailgun from "@adminforth/email-adapter-mailgun";
188+
189+
...
190+
191+
plugins: [
192+
new EmailInvitePlugin({
193+
emailField: 'email',
194+
passwordField: 'password_hash',
195+
sendFrom: 'noreply@yourapp.com',
196+
//diff-add
197+
adapter: new EmailAdapterMailgun({
198+
//diff-add
199+
apiKey: process.env.MAILGUN_API_KEY as string,
200+
//diff-add
201+
domain: process.env.MAILGUN_DOMAIN as string,
202+
//diff-add
203+
baseUrl: process.env.MAILGUN_REGION_URL as string,
204+
//diff-add
205+
}),
206+
}),
207+
],
208+
209+
```

adminforth/documentation/docs/tutorial/07-Plugins/17-bulk-ai-flow.md

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ model apartments {
4242
listed Boolean
4343
realtor_id String?
4444
//diff-add
45-
apartment_image String?
45+
apartment_image String?
4646
}
4747
```
4848
@@ -73,6 +73,12 @@ Add column to `aparts` resource configuration:
7373
import BulkAiFlowPlugin from '@adminforth/bulk-ai-flow';
7474
//diff-add
7575
import AdminForthImageVisionAdapterOpenAi from '@adminforth/image-vision-adapter-openai';
76+
//diff-add
77+
import UploadPlugin from '@adminforth/upload';
78+
//diff-add
79+
import { randomUUID } from 'crypto';
80+
//diff-add
81+
import AdminForthAdapterS3Storage from '@adminforth/storage-adapter-amazon-s3'
7682

7783
export const admin = new AdminForth({
7884
...
@@ -126,6 +132,12 @@ export const admin = new AdminForth({
126132
actionName: 'Analyze',
127133
//diff-add
128134
attachFiles: async ({ record }: { record: any }) => {
135+
//diff-add
136+
if (!record.apartment_image) {
137+
//diff-add
138+
return [];
139+
//diff-add
140+
}
129141
//diff-add
130142
return [`https://tmpbucket-adminforth.s3.eu-central-1.amazonaws.com/${record.apartment_image}`];
131143
//diff-add
@@ -166,6 +178,7 @@ export const admin = new AdminForth({
166178

167179
});
168180
```
181+
> ⚠️ Make sure your attachFiles function returns a valid array of image URLs or an empty array. Returning anything else may cause an error.
169182
170183
## Usage
171184
1. Select fields you want to fill
@@ -177,3 +190,125 @@ export const admin = new AdminForth({
177190
![alt text](Bulk-vision-2.png)
178191
6. Save changhes
179192
![alt text](Bulk-vision-3.png)
193+
194+
195+
## Text-to-Text Processing
196+
This is the most basic plugin usage. You can connect any text completion adapter to fill one or several string/number/boolean fields from other fields.
197+
198+
### Example: Translate Names to English
199+
Normalize user names by translating them from any language to English for internal processing.
200+
201+
```ts
202+
import CompletionAdapterOpenAIChatGPT from '@adminforth/completion-adapter-open-ai-chat-gpt/index.js';
203+
204+
// Add to your resource plugins array
205+
new BulkAiFlowPlugin({
206+
actionName: 'Translate surnames',
207+
textCompleteAdapter: new CompletionAdapterOpenAIChatGPT({
208+
openAiApiKey: process.env.OPENAI_API_KEY as string,
209+
model: 'gpt-4o',
210+
expert: {
211+
temperature: 0.7
212+
}
213+
}),
214+
fillPlainFields: {
215+
'full_name_en': 'Translate this name to English: {{users_full_name}}',
216+
},
217+
}),
218+
```
219+
220+
## Image-to-Text Analysis (Vision)
221+
Analyze images and extract information to fill text, number, enum, or boolean fields.
222+
223+
### Example: Age Detection from Photos
224+
225+
```ts
226+
import AdminForthImageVisionAdapterOpenAi from '@adminforth/image-vision-adapter-openai/index.js';
227+
228+
// Add to your resource plugins array
229+
new BulkAiFlowPlugin({
230+
actionName: 'Guess age',
231+
visionAdapter: new AdminForthImageVisionAdapterOpenAi({
232+
openAiApiKey: process.env.OPENAI_API_KEY as string,
233+
model: 'gpt-4.1-mini',
234+
}),
235+
fillFieldsFromImages: {
236+
'age': 'Analyze the image and estimate the age of the person. Return only a number.',
237+
},
238+
attachFiles: async ({ record }) => {
239+
if (!record.image_url) {
240+
return [];
241+
}
242+
return [`https://users-images.s3.eu-north-1.amazonaws.com/${record.image_url}`];
243+
},
244+
}),
245+
```
246+
247+
## Text-to-Image generation or image editing
248+
Generate new images based on existing data and/or images using AI image generation adapters.
249+
250+
### Example: Creating Cartoon Avatars
251+
252+
```ts
253+
import ImageGenerationAdapterOpenAI from '@adminforth/image-generation-adapter-openai/index.js';
254+
255+
// Add to your resource plugins array
256+
new BulkAiFlowPlugin({
257+
actionName: 'Generate cartoon avatars',
258+
imageGenerationAdapter: new ImageGenerationAdapterOpenAI({
259+
openAiApiKey: process.env.OPENAI_API_KEY as string,
260+
model: 'gpt-image-1',
261+
}),
262+
attachFiles: async ({ record }) => {
263+
if (!record.user_photo) {
264+
return [];
265+
}
266+
return [`https://bulk-ai-flow-playground.s3.eu-north-1.amazonaws.com/${record.users_photo}`];
267+
},
268+
generateImages: {
269+
users_avatar: {
270+
prompt: 'Transform this photo into a cartoon-style avatar. Maintain the person\'s features but apply cartoon styling. Do not add text or logos.',
271+
outputSize: '1024x1024',
272+
countToGenerate: 2,
273+
rateLimit: '3/1h'
274+
},
275+
},
276+
bulkGenerationRateLimit: "1/1h"
277+
}),
278+
```
279+
280+
## Rate Limiting and Best Practices
281+
282+
- Use `rateLimit` for individual image generation operations and for the bulk image generation
283+
```ts
284+
new BulkAiFlowPlugin({
285+
...
286+
287+
generateImages: {
288+
users_avatar: {
289+
... //image re-generation limits
290+
//diff-add
291+
rateLimit: '1/5m' // one request per 5 minutes
292+
}
293+
},
294+
295+
...
296+
297+
//diff-add
298+
rateLimits: { // bulk generation limits
299+
//diff-add
300+
fillFieldsFromImages: "5/1d", // 5 requests per day
301+
//diff-add
302+
fillPlainFields: "3/1h", // 3 requests per one hour
303+
//diff-add
304+
generateImages: "1/2m", // 2 request per one minute
305+
//diff-add
306+
}
307+
308+
...
309+
310+
})
311+
```
312+
313+
- Consider using lower resolution (`512x512`) for faster generation and lower costs
314+
- Test prompts thoroughly before applying to large datasets

adminforth/spa/src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@
195195
</div>
196196
</div>
197197
<AcceptModal />
198-
<div v-if="toastStore.toasts.length>0" class="fixed bottom-5 right-5 flex gap-1 flex-col-reverse z-50">
198+
<div v-if="toastStore.toasts.length>0" class="fixed bottom-5 right-5 flex gap-1 flex-col-reverse z-[100]">
199199
<transition-group
200200
name="fade"
201201
tag="div"

adminforth/spa/src/adminforth.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class FrontendAPI implements FrontendAPIInterface {
3535
refreshMenuBadges: () => void;
3636
}
3737

38+
public show: {
39+
refresh(): void;
40+
}
41+
3842
closeUserMenuDropdown(): void {
3943
console.log('closeUserMenuDropdown')
4044
}
@@ -72,6 +76,12 @@ class FrontendAPI implements FrontendAPIInterface {
7276
updateFilter: this.updateListFilter.bind(this),
7377
clearFilters: this.clearListFilters.bind(this),
7478
}
79+
80+
this.show = {
81+
refresh: () => {
82+
console.log('show.refresh')
83+
}
84+
}
7585
}
7686

7787
confirm(params: ConfirmParams): Promise<boolean> {

0 commit comments

Comments
 (0)