Skip to content

Commit 875bd25

Browse files
committed
Merge branch 'next' of github.com:devforth/adminforth into next
2 parents faaeb44 + e13b879 commit 875bd25

File tree

132 files changed

+3648
-4374
lines changed

Some content is hidden

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

132 files changed

+3648
-4374
lines changed

README.md

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,44 +61,50 @@ npx adminforth create-app
6161

6262
The most convenient way to add new features or fixes is using `dev-demo`. It imports the source code of the repository and plugins so you can edit them and see changes on the fly.
6363

64-
Fork repo, pull it and do next:
64+
To run dev demo:
65+
```sh
66+
cd dev-demo
6567

68+
npm run setup-dev-demo
69+
npm run migrate:all
6670

67-
```sh
68-
cd adminforth
69-
npm ci
70-
npm run build
71+
npm start
7172
```
7273

73-
To run dev demo:
74-
```sh
75-
cd dev-demo
76-
cp .env.sample .env
74+
## Adding columns to a database in dev-demo
7775

78-
# this will install all official plugins and link adminforth package, if plugin installed it will git pull and npm ci
79-
npm run install-plugins
76+
Open `./migrations` folder. There is prisma migration folder for the sqlite, postgres and mysql and `clickhouse_migrations` folder for the clickhouse:
8077

81-
# same for official adapters
82-
npm run install-adapters
78+
### Migrations for the MySQL, SQLite and Postgres
79+
To make migration add to the .prisma file in folder with database you need and add new tables or columns. Then run:
8380

84-
npm ci
8581

86-
./run_inventory.sh
82+
```
83+
npm run makemigration:sqlite -- --name init
84+
```
85+
86+
and
8787

88-
npm run migrate:local
89-
npm start
88+
```
89+
npm run migrate:sqlite
9090
```
9191

92-
## Adding columns to a database in dev-demo
92+
to apply migration
93+
94+
> use :sqlite, :mysql or :postgres for you case
95+
96+
### Migrations for the clickhouse
9397

94-
Open `.prisma` file, modify it, and run:
98+
In order to make migration for the clickhouse, go to the `./migrations/clickhouse_migrations` folder and add migration file to the folder.
9599

100+
Then run
96101
```
97-
npm run namemigration -- --name desctiption_of_changes
102+
npm run migrate:clickhouse
98103
```
99104

105+
to apply the migration.
100106

101-
### Testing CLI commands during development
107+
## Testing CLI commands during development
102108

103109

104110
Make sure you have not `adminforth` globally installed. If you have it, remove it:

adminforth/basePlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default class AdminForthPlugin implements IAdminForthPlugin {
3636
return 'non-uniquely-identified';
3737
}
3838

39-
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
39+
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource, allPluginInstances?: {pi: AdminForthPlugin, resource: AdminForthResource}[]) {
4040
this.resourceConfig = resourceConfig;
4141
const uniqueness = this.instanceUniqueRepresentation(this.pluginOptions);
4242

adminforth/documentation/docs/tutorial/03-Customization/04-hooks.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,21 +189,29 @@ Let's limit it:
189189
```ts title='./resources/apartments.ts'
190190
{
191191
...
192-
hooks: {
193-
dropdownList: {
194-
beforeDatasourceRequest: async ({ adminUser, query }: { adminUser: AdminUser, query: any }) => {
195-
if (adminUser.dbUser.role !== "superadmin") {
196-
query.filtersTools.replaceOrAddTopFilter(Filters.EQ("id", adminUser.dbUser.id));
197-
};
198-
return {
199-
"ok": true,
200-
};
201-
}
192+
193+
foreignResource: {
194+
195+
...
196+
197+
hooks: {
198+
dropdownList: {
199+
beforeDatasourceRequest: async ({ adminUser, query }: { adminUser: AdminUser, query: any }) => {
200+
if (adminUser.dbUser.role !== "superadmin") {
201+
query.filtersTools.replaceOrAddTopFilter(Filters.EQ("id", adminUser.dbUser.id));
202+
};
203+
return {
204+
"ok": true,
205+
};
206+
}
207+
},
202208
},
203-
},
209+
}
204210
}
205211
```
206212
213+
> ☝️☝️☝️ This hooks should be written only inside column. If you'll add it in resource hooks - it won't work
214+
207215
In our case we limit the dropdown list to show only the current user, however you can use same sample to list only objects who are related to the current user in case if you will have relation configurations which require to show related objects which belongs to the current user.
208216
209217
Flow diagram for dropdown list:

adminforth/documentation/docs/tutorial/07-Plugins/05-upload.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ To do this add avatar column to the user resource:
524524
role String
525525
created_at DateTime
526526
//diff-add
527-
avatar ?String
527+
avatar String?
528528
}
529529
```
530530
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Working without direct database connection
2+
3+
Out of the box, AdminForth connects directly to your database using one of the supported drivers (PostgreSQL, MySQL, ClickHouse, MongoDB) and executes queries against it.
4+
5+
In some cases, you may not want to expose a direct database connection to AdminForth. Instead, you may prefer to allow AdminForth to access and modify data through your own APIs (for example, REST, GraphQL, or JSON-RPC).
6+
7+
With this approach, AdminForth never connects to the database and never even knows its URL. All read and write operations go through your API layer.
8+
9+
Why do this?
10+
11+
- Your API may enforce additional constraints or validation rules.
12+
13+
- You can precisely log all operations using your own logging or audit systems. (The built-in AuditLog tracks data modifications only and does not log read operations.)
14+
15+
- Your API may contain custom logic, such as distributed workflows or complex data-modification rules.
16+
17+
To implement this, you need to extend the data connector class and implement a small set of methods responsible for data access and mutations.
18+
19+
This example demonstrates how to do this using GraphQL, but the same approach can be adapted to REST or any other protocol. The code comments include detailed guidance for these cases.
20+
21+
Another reason to create a custom data source adapter is to support a database that AdminForth does not yet support. In that case, you are welcome to submit a pull request to AdminForth to add native support for that database.

adminforth/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,24 +216,29 @@ class AdminForth implements IAdminForth {
216216
let activationLoopCounter = 0;
217217
while (true) {
218218
activationLoopCounter++;
219-
if (activationLoopCounter > 1000) {
220-
throw new Error('Plugin activation loop exceeded 1000 iterations, possible infinite loop (some plugin tries to activate himself in a loop)');
219+
if (activationLoopCounter > 10) {
220+
throw new Error('Plugin activation loop exceeded 10 iterations, possible infinite loop (some plugin tries to activate himself in a loop)');
221221
}
222-
const allPluginsAreActivated = allPluginInstances.length === this.activatePlugins.length;
222+
process.env.HEAVY_DEBUG && console.log(`🔌 Plugin activation loop iteration: ${activationLoopCounter}`);
223+
process.env.HEAVY_DEBUG && console.log(`🔌 Activated plugins count: ${this.activatedPlugins.length}/${allPluginInstances.length}`);
224+
const allPluginsAreActivated = allPluginInstances.length === this.activatedPlugins.length;
223225
if (allPluginsAreActivated) {
224226
break;
225227
}
226-
228+
227229
const unactivatedPlugins = allPluginInstances.filter(({pi: pluginInstance}) =>
228230
!this.activatedPlugins.find((p) => p.pluginInstanceId === pluginInstance.pluginInstanceId)
229231
);
232+
233+
process.env.HEAVY_DEBUG && console.log(`🔌 Unactivated plugins remaining: ${unactivatedPlugins.length}`);
230234

231235
process.env.HEAVY_DEBUG && console.log(`🔌 Unactivated plugins count: ${unactivatedPlugins.length}`);
232236

233237
unactivatedPlugins.sort(({pi: a}, {pi: b}) => a.activationOrder - b.activationOrder);
234-
238+
process.env.HEAVY_DEBUG && console.log(`🔌 Activating plugins in order:`, unactivatedPlugins.map(({pi}) => pi.constructor.name));
235239
unactivatedPlugins.forEach(
236240
({pi: pluginInstance, resource}, index) => {
241+
process.env.HEAVY_DEBUG && console.log("Activating plugin:",pluginInstance.constructor.name)
237242
process.env.HEAVY_DEBUG && console.log(`🔌 Activating plugin ${index + 1}/${allPluginInstances.length}: ${pluginInstance.constructor.name} for resource ${resource.resourceId}`);
238243
pluginInstance.modifyResourceConfig(this, resource, allPluginInstances);
239244
process.env.HEAVY_DEBUG && console.log(`🔌 Plugin ${pluginInstance.constructor.name} modifyResourceConfig completed`);
@@ -249,6 +254,7 @@ class AdminForth implements IAdminForth {
249254
process.env.HEAVY_DEBUG && console.log(`🔌 Plugin ${pluginInstance.constructor.name} activated successfully`);
250255
}
251256
);
257+
process.env.HEAVY_DEBUG && console.log(`🔌activated plugins:`, this.activatedPlugins.map((pi) => pi.constructor.name));
252258
}
253259
process.env.HEAVY_DEBUG && console.log('🔌 All plugins activation completed');
254260
}

adminforth/modules/configValidator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ export default class ConfigValidator implements IConfigValidator {
575575
}
576576

577577
// check suggestOnCreate types
578-
if (inCol.suggestOnCreate !== undefined) {
578+
if (inCol.suggestOnCreate !== undefined && typeof inCol.suggestOnCreate !== 'function') {
579579
if (!col.showIn.create) {
580580
errors.push(`Resource "${res.resourceId}" column "${col.name}" suggestOnCreate is present, while column is hidden on create page`);
581581
}

adminforth/modules/restApi.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,9 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
635635
if (col.foreignResource?.unsetLabel) {
636636
col.foreignResource.unsetLabel = await tr(col.foreignResource.unsetLabel, `resource.${resource.resourceId}.foreignResource.unsetLabel`);
637637
}
638+
if (inCol.suggestOnCreate && typeof inCol.suggestOnCreate === 'function') {
639+
col.suggestOnCreate = await inCol.suggestOnCreate(adminUser);
640+
}
638641

639642
return {
640643
...col,

adminforth/spa/src/components/ResourceForm.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ async function searchOptions(columnName: string, searchTerm: string) {
322322
323323
324324
const editableColumns = computed(() => {
325-
return props.resource?.columns?.filter(column => column.showIn?.[mode.value] && (currentValues.value ? checkShowIf(column, currentValues.value) : true));
325+
return props.resource?.columns?.filter(column => column.showIn?.[mode.value] && (currentValues.value ? checkShowIf(column, currentValues.value, props.resource.columns) : true));
326326
});
327327
328328
const isValid = computed(() => {

adminforth/spa/src/components/ShowTable.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424
dark:bg-darkShowTablesBodyBackground dark:border-darkShowTableBodyBorder block md:table-row"
2525
>
2626
<component
27-
v-if="column.components?.showRow && checkShowIf(column, record)"
27+
v-if="column.components?.showRow && checkShowIf(column, record, resource?.columns || [])"
2828
:is="getCustomComponent(column.components.showRow)"
2929
:meta="column.components.showRow.meta"
3030
:column="column"
3131
:resource="coreStore.resource"
3232
:record="coreStore.record"
3333
/>
34-
<template v-else-if="checkShowIf(column, record)">
34+
<template v-else-if="checkShowIf(column, record, resource?.columns || [])">
3535
<td class="px-6 py-4 relative block md:table-cell font-bold md:font-normal pb-0 md:pb-4">
3636
{{ column.label }}
3737
</td>

0 commit comments

Comments
 (0)