Skip to content

Commit 7a7e08e

Browse files
committed
Merge branch 'next' of https://github.com/devforth/adminforth into fix-mongo-id-filter
2 parents 5ba0c0e + 21e1ea1 commit 7a7e08e

File tree

416 files changed

+31463
-9634
lines changed

Some content is hidden

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

416 files changed

+31463
-9634
lines changed

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Follow SOLID principles
2+
Use DRY and avoid duplication
3+
Keep the code KISS unless complexity is required
4+
Avoid unnecessary abstractions.
5+
Cover edge cases and clear error handling

README.md

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# AdminForth - fully free Node.js admin panel framework on Vue & Tailwind
1+
# AdminForth - free powerfull Node.js admin panel framework on Vue & Tailwind
22

33

44
<a href="https://adminforth.dev"><img src="https://img.shields.io/badge/website-adminforth.dev-blue" style="height:24px"/></a> <a href="https://adminforth.dev"><img src="https://img.shields.io/npm/dw/adminforth" style="height:24px"/></a> <a href="https://devforth.io"><img src="https://raw.githubusercontent.com/devforth/OnLogs/e97944fffc24fec0ce2347b205c9bda3be8de5c5/.assets/df_powered_by.svg" style="height:28px"/></a>
@@ -20,12 +20,15 @@
2020
<br/>
2121

2222
Why AdminForth:
23-
* AdminForth is always free and open-source (no paid versions, no cloud subscriptions sh*t)
24-
* Init AdminForth with your database URL in Node.js file, easily describe the tables you wish to see in admin, and get fully functional UI for your data (filter, create, edit, remove)
25-
* Define Vue components to change look of various parts of admin (place in data cell, instead of row, add something above the table, inject something to header or sidebar, add custom page with charts or custom components)
26-
* Rich build-in Components library (AdminForth AFCL) with premade easy-to-use build-blocks which follow your theme
23+
24+
* Init AdminForth project with `npx adminforth create-app` and pass your database URL, import the tables you wish to see in admin using `npx adminforth resource`, and get fully functional UI for your data (filter, create, edit, remove)
25+
* Modern look and simple Tailwind-ish ability to adjust it
26+
* Supports Postgres, MySQL, Mongo, SQLite, Clickhouse
27+
* Define Vue components to change look of various parts of admin using `npx adminforth component` (edit data cells, edit fields, add something above the table, inject something to header or sidebar, add custom page with charts or custom components)
28+
* Build-in Components library (AdminForth AFCL) with premade easy-to-use build-blocks which follow your theme
2729
* Define express APIs and call them from your components and pages
28-
* Use various modern back-office-must-have plugins like audit log, files/image upload, TOTP 2FA, I18N, Copilot-style AI writing and image generation
30+
* Use various modern back-office-must-have plugins like audit log, files/image upload, TOTP 2FA, I18N, Copilot-style AI writing and image generation and many more
31+
* AdminForth is always free and open-source (no paid versions, no cloud subscriptions sh*t)
2932

3033

3134
## Project initialisation
@@ -54,41 +57,76 @@ npx adminforth create-app
5457

5558

5659

57-
5860
# For developers
5961

6062
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.
6163

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

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

65-
```sh
66-
cd adminforth
67-
npm ci
68-
npm run build
71+
npm start
72+
```
6973

70-
# this will install all official plugins and link adminforth package, if plugin installed it will git pull and npm ci
71-
npm run install-plugins
74+
## Adding columns to a database in dev-demo
75+
76+
Open `./migrations` folder. There is prisma migration folder for the sqlite, postgres and mysql and `clickhouse_migrations` folder for the clickhouse:
77+
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:
7280

73-
# same for official adapters
74-
npm run install-adapters
81+
82+
```
83+
npm run makemigration:sqlite -- --name init
7584
```
7685

77-
To run dev demo:
78-
```sh
79-
cd dev-demo
80-
cp .env.sample .env
81-
npm ci
82-
npm run migrate
83-
npm start
86+
and
87+
88+
```
89+
npm run migrate:sqlite
8490
```
8591

86-
## Adding columns to a database in dev-demo
92+
to apply migration
93+
94+
> use :sqlite, :mysql or :postgres for you case
8795
88-
Open `.prisma` file, modify it, and run:
96+
### Migrations for the clickhouse
8997

98+
In order to make migration for the clickhouse, go to the `./migrations/clickhouse_migrations` folder and add migration file to the folder.
99+
100+
Then run
90101
```
91-
npm run namemigration -- --name desctiption_of_changes
102+
npm run migrate:clickhouse
92103
```
93104

105+
to apply the migration.
106+
107+
## Testing CLI commands during development
108+
109+
110+
Make sure you have not `adminforth` globally installed. If you have it, remove it:
111+
112+
113+
```sh
114+
npm uninstall -g adminforth
115+
```
116+
117+
Then, in the root of the project, run once:
118+
119+
```
120+
cd adminforth/adminforth
121+
npm run build
122+
```
123+
124+
This will automatically make an npm link to the `adminforth` package in the root of the project.
125+
126+
Then, go to testing app, e.g. created with CLI, and use next command:
127+
128+
```
129+
npx -g adminforth <your command under development>
130+
```
94131

132+
This will always run latest version of adminforth package.

adapters/install-adapters.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#!/usr/bin/env bash
2-
ADAPTERS="adminforth-completion-adapter-open-ai-chat-gpt adminforth-email-adapter-aws-ses adminforth-google-oauth-adapter adminforth-github-oauth-adapter adminforth-facebook-oauth-adapter adminforth-keycloak-oauth-adapter adminforth-microsoft-oauth-adapter"
2+
ADAPTERS="adminforth-completion-adapter-open-ai-chat-gpt adminforth-email-adapter-aws-ses \
3+
adminforth-email-adapter-mailgun adminforth-google-oauth-adapter adminforth-github-oauth-adapter \
4+
adminforth-facebook-oauth-adapter adminforth-keycloak-oauth-adapter adminforth-microsoft-oauth-adapter \
5+
adminforth-twitch-oauth-adapter adminforth-image-generation-adapter-openai adminforth-storage-adapter-amazon-s3 \
6+
adminforth-storage-adapter-local adminforth-image-vision-adapter-openai adminforth-key-value-adapter-ram \
7+
adminforth-login-captcha-adapter-cloudflare adminforth-login-captcha-adapter-recaptcha"
38

49
# for each
510
install_adapter() {

adminforth/auth.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,8 @@ class AdminForthAuth implements IAdminForthAuth {
6565
headersLower['client-ip'] ||
6666
headersLower['client-address'] ||
6767
headersLower['client'] ||
68-
headersLower['x-host'] ||
69-
headersLower['host'] ||
70-
'unknown';
68+
headersLower['x-host'] ||
69+
null;
7170
}
7271
}
7372

@@ -76,19 +75,18 @@ class AdminForthAuth implements IAdminForthAuth {
7675
response.setHeader('Set-Cookie', `adminforth_${brandSlug}_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
7776
}
7877

79-
setAuthCookie({ expireInDays, response, username, pk}: {
80-
expireInDays?: number,
78+
setAuthCookie({ expireInDuration, response, username, pk}: {
79+
expireInDuration?: string,
8180
response: any,
8281
username: string,
8382
pk: string | null
8483
}) {
85-
const expiresIn: string = expireInDays ? `${expireInDays}d` : (process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h');
84+
const expiresIn: string = expireInDuration || (process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h');
8685
// might be h,m,d in string
8786
const expiresInSec = parseTimeToSeconds(expiresIn);
8887

89-
const token = this.issueJWT({ username, pk}, 'auth', expiresIn);
88+
const token = this.issueJWT({ username, pk}, 'auth', expiresInSec);
9089
const expiresCookieFormat = new Date(Date.now() + expiresInSec * 1000).toUTCString();
91-
9290
const brandSlug = this.adminforth.config.customization.brandNameSlug;
9391
response.setHeader('Set-Cookie', `adminforth_${brandSlug}_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${expiresCookieFormat}`);
9492
}
@@ -99,16 +97,32 @@ class AdminForthAuth implements IAdminForthAuth {
9997
}
10098

10199
setCustomCookie({ response, payload }: {
102-
response: any, payload: {name: string, value: string, expiry: number, httpOnly: boolean}
100+
response: any, payload: { name: string, value: string, expiry: number | undefined, expirySeconds: number | undefined, httpOnly: boolean }
103101
}) {
104-
const {name, value, expiry, httpOnly} = payload;
102+
const {name, value, expiry, httpOnly, expirySeconds } = payload;
103+
104+
let expiryMs = 24 * 60 * 60 * 1000; // default 1 day
105+
if (expirySeconds !== undefined) {
106+
expiryMs = expirySeconds * 1000;
107+
} else if (expiry !== undefined) {
108+
console.warn('setCustomCookie: expiry(in ms) is deprecated, use expirySeconds instead (seconds), traceback:', new Error().stack);
109+
expiryMs = expiry;
110+
}
111+
105112
const brandSlug = this.adminforth.config.customization.brandNameSlug;
106113
response.setHeader('Set-Cookie', `adminforth_${brandSlug}_${name}=${value}; Path=${this.adminforth.config.baseUrl || '/'};${
107114
httpOnly ? ' HttpOnly;' : ''
108-
} SameSite=Strict; Expires=${new Date(Date.now() + expiry).toUTCString() } `);
115+
} SameSite=Strict; Expires=${new Date(Date.now() + expiryMs).toUTCString() } `);
116+
}
117+
118+
getCustomCookie({ cookies, name }: {
119+
cookies: {key: string, value: string}[], name: string
120+
}): string | null {
121+
const brandSlug = this.adminforth.config.customization.brandNameSlug;
122+
return cookies.find((cookie) => cookie.key === `adminforth_${brandSlug}_${name}`)?.value || null;
109123
}
110124

111-
issueJWT(payload: Object, type: string, expiresIn: string = '24h'): string {
125+
issueJWT(payload: Object, type: string, expiresIn: string | number = '24h'): string {
112126
// read ADMINFORH_SECRET from environment if not drop error
113127
const secret = process.env.ADMINFORTH_SECRET;
114128
if (!secret) {

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/commands/bundle.js

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
1-
import fs from "fs";
2-
import { getInstance } from "./utils.js";
1+
import { callTsProxy, findAdminInstance } from "./callTsProxy.js";
2+
33

44
async function bundle() {
5-
const currentDirectory = process.cwd();
6-
const files = fs.readdirSync(currentDirectory);
7-
let instanceFound = false;
5+
console.log("Bundling admin SPA...");
6+
const instance = await findAdminInstance();
7+
8+
9+
try {
10+
await callTsProxy(`
11+
import { admin } from './${instance.file}.js';
812
9-
for (const file of files) {
10-
if (file.endsWith(".js") || file.endsWith(".ts")) {
11-
try {
12-
const instance = await getInstance(file, currentDirectory);
13-
if (instance) {
14-
await instance.bundleNow({ hotReload: false });
15-
instanceFound = true;
16-
break;
17-
}
18-
} catch (error) {
19-
console.error(`Error: Could not bundle '${file}'`, error);
13+
export async function exec() {
14+
return await admin.bundleNow({ hotReload: false });
2015
}
21-
}
22-
}
23-
if (!instanceFound) {
24-
console.error("Error: No valid instance found to bundle.");
25-
return;
16+
`);
17+
18+
} catch (e) {
19+
console.log(`Running budndle failed`, e);
2620
}
2721
}
2822

0 commit comments

Comments
 (0)