diff --git a/adapters/install-adapters.sh b/adapters/install-adapters.sh
index f6ccadb1..965c74b1 100644
--- a/adapters/install-adapters.sh
+++ b/adapters/install-adapters.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-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 adminforth-image-generation-adapter-openai adminforth-storage-adapter-amazon-s3 adminforth-storage-adapter-local"
+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 adminforth-twitch-oauth-adapter adminforth-image-generation-adapter-openai adminforth-storage-adapter-amazon-s3 adminforth-storage-adapter-local"
# for each
install_adapter() {
diff --git a/adminforth/commands/createApp/templates/index.ts.hbs b/adminforth/commands/createApp/templates/index.ts.hbs
index 9fd82d17..6d6bf096 100644
--- a/adminforth/commands/createApp/templates/index.ts.hbs
+++ b/adminforth/commands/createApp/templates/index.ts.hbs
@@ -1,6 +1,8 @@
import express from 'express';
import AdminForth from 'adminforth';
import usersResource from "./resources/adminuser.js";
+import { fileURLToPath } from 'url';
+import path from 'path';
const ADMIN_BASE_URL = '';
@@ -55,7 +57,7 @@ export const admin = new AdminForth({
],
});
-if (import.meta.url === `file://${process.argv[1]}`) {
+if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
const app = express();
app.use(express.json());
diff --git a/adminforth/commands/createApp/utils.js b/adminforth/commands/createApp/utils.js
index 9d311fc9..6bd99ef6 100644
--- a/adminforth/commands/createApp/utils.js
+++ b/adminforth/commands/createApp/utils.js
@@ -284,14 +284,22 @@ async function writeTemplateFiles(dirname, cwd, options) {
}
async function installDependencies(ctx, cwd) {
- const nodeBinary = process.execPath; // Path to the Node.js binary running this script
- const npmPath = path.join(path.dirname(nodeBinary), 'npm'); // Path to the npm executable
+ const isWindows = process.platform === 'win32';
+ const nodeBinary = process.execPath;
+ const npmPath = path.join(path.dirname(nodeBinary), 'npm');
const customDir = ctx.customDir;
- const res = await Promise.all([
- await execAsync(`${nodeBinary} ${npmPath} install`, { cwd, env: { PATH: process.env.PATH } }),
- await execAsync(`${nodeBinary} ${npmPath} install`, { cwd: customDir, env: { PATH: process.env.PATH } }),
- ]);
+ if (isWindows) {
+ const res = await Promise.all([
+ await execAsync(`npm install`, { cwd, env: { PATH: process.env.PATH } }),
+ await execAsync(`npm install`, { cwd: customDir, env: { PATH: process.env.PATH } }),
+ ]);
+ } else {
+ const res = await Promise.all([
+ await execAsync(`${nodeBinary} ${npmPath} install`, { cwd, env: { PATH: process.env.PATH } }),
+ await execAsync(`${nodeBinary} ${npmPath} install`, { cwd: customDir, env: { PATH: process.env.PATH } }),
+ ]);
+ }
// console.log(chalk.dim(`Dependencies installed in ${cwd} and ${customDir}: \n${res[0].stdout}${res[1].stdout}`));
}
diff --git a/adminforth/commands/createCustomComponent/configUpdater.js b/adminforth/commands/createCustomComponent/configUpdater.js
index 600a3fcf..2ed43e1b 100644
--- a/adminforth/commands/createCustomComponent/configUpdater.js
+++ b/adminforth/commands/createCustomComponent/configUpdater.js
@@ -254,7 +254,7 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
}
-export async function injectLoginComponent(indexFilePath, componentPath) {
+export async function injectLoginComponent(indexFilePath, componentPath, injectionType) {
console.log(chalk.dim(`Reading file: ${indexFilePath}`));
const content = await fs.readFile(indexFilePath, 'utf-8');
const ast = recast.parse(content, {
@@ -263,6 +263,7 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
let updated = false;
let injectionLine = null;
+ let targetProperty = null;
recast.visit(ast, {
visitNewExpression(path) {
@@ -293,20 +294,23 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
const loginPageInjections = getOrCreateProp(customization, 'loginPageInjections');
if (!n.ObjectExpression.check(loginPageInjections)) return false;
- let underInputsProp = loginPageInjections.properties.find(
- p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'underInputs'
+ // Determine target property based on injection type
+ targetProperty = injectionType === 'beforeLogin' ? 'panelHeader' : 'underInputs';
+
+ let targetProp = loginPageInjections.properties.find(
+ p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === targetProperty
);
- if (underInputsProp) {
- const currentVal = underInputsProp.value;
- injectionLine = underInputsProp.loc?.start.line ?? null;
+ if (targetProp) {
+ const currentVal = targetProp.value;
+ injectionLine = targetProp.loc?.start.line ?? null;
if (n.StringLiteral.check(currentVal)) {
if (currentVal.value !== componentPath) {
- underInputsProp.value = b.arrayExpression([
+ targetProp.value = b.arrayExpression([
b.stringLiteral(currentVal.value),
b.stringLiteral(componentPath),
]);
- console.log(chalk.dim(`Converted 'underInputs' to array with existing + new path.`));
+ console.log(chalk.dim(`Converted '${targetProperty}' to array with existing + new path.`));
} else {
console.log(chalk.dim(`Component path already present as string. Skipping.`));
}
@@ -316,26 +320,26 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
);
if (!exists) {
currentVal.elements.push(b.stringLiteral(componentPath));
- console.log(chalk.dim(`Appended new component path to existing 'underInputs' array.`));
+ console.log(chalk.dim(`Appended new component path to existing '${targetProperty}' array.`));
} else {
console.log(chalk.dim(`Component path already present in array. Skipping.`));
}
} else {
- console.warn(chalk.yellow(`⚠️ 'underInputs' is not a string or array. Skipping.`));
+ console.warn(chalk.yellow(`⚠️ '${targetProperty}' is not a string or array. Skipping.`));
return false;
}
} else {
const newProperty = b.objectProperty(
- b.identifier('underInputs'),
- b.stringLiteral(componentPath)
- );
-
- if (newProperty.loc) {
- console.log(chalk.dim(`Adding 'underInputs' at line: ${newProperty.loc.start.line}`));
- }
-
- loginPageInjections.properties.push(newProperty);
- console.log(chalk.dim(`Added 'underInputs': ${componentPath}`));
+ b.identifier(targetProperty),
+ b.stringLiteral(componentPath)
+ );
+
+ if (newProperty.loc) {
+ console.log(chalk.dim(`Adding '${targetProperty}' at line: ${newProperty.loc.start.line}`));
+ }
+
+ loginPageInjections.properties.push(newProperty);
+ console.log(chalk.dim(`Added '${targetProperty}': ${componentPath}`));
}
updated = true;
@@ -353,7 +357,7 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
console.log(
chalk.green(
- `✅ Successfully updated CRUD injection in resource file: ${indexFilePath}` +
+ `✅ Successfully updated login ${targetProperty} injection in: ${indexFilePath}` +
(injectionLine !== null ? `:${injectionLine}` : '')
)
);
diff --git a/adminforth/commands/createCustomComponent/fileGenerator.js b/adminforth/commands/createCustomComponent/fileGenerator.js
index 7da934c8..d6c281d6 100644
--- a/adminforth/commands/createCustomComponent/fileGenerator.js
+++ b/adminforth/commands/createCustomComponent/fileGenerator.js
@@ -129,7 +129,7 @@ export async function generateLoginOrGlobalComponentFile(componentFileName, inje
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let templatePath;
- if (injectionType === 'afterLogin') {
+ if (injectionType === 'afterLogin' || injectionType === 'beforeLogin') {
templatePath = path.join(__dirname, 'templates', 'login', `${injectionType}.vue.hbs`);
} else {
templatePath = path.join(__dirname, 'templates', 'global', `${injectionType}.vue.hbs`);
diff --git a/adminforth/commands/createCustomComponent/main.js b/adminforth/commands/createCustomComponent/main.js
index e9236bf2..76c0ef1f 100644
--- a/adminforth/commands/createCustomComponent/main.js
+++ b/adminforth/commands/createCustomComponent/main.js
@@ -50,6 +50,7 @@ async function handleFieldComponentCreation(config, resources) {
{ name: '📃 show', value: 'show' },
{ name: '✏️ edit', value: 'edit' },
{ name: '➕ create', value: 'create' },
+ { name: '🔍 filter', value: 'filter'},
new Separator(),
{ name: '🔙 BACK', value: '__BACK__' },
]
@@ -256,6 +257,7 @@ async function handleLoginPageInjectionCreation(config) {
const injectionType = await select({
message: 'Select injection type:',
choices: [
+ { name: 'Before Login and password inputs', value: 'beforeLogin' },
{ name: 'After Login and password inputs', value: 'afterLogin' },
{ name: '🔙 BACK', value: '__BACK__' },
],
@@ -286,7 +288,7 @@ async function handleLoginPageInjectionCreation(config) {
console.log(chalk.dim(`Component generation successful: ${absoluteComponentPath}`));
const configFilePath = path.resolve(process.cwd(), 'index.ts');
console.log(chalk.dim(`Injecting component: ${configFilePath}, ${componentFileName}`));
- await injectLoginComponent(configFilePath, `@@/${componentFileName}`);
+ await injectLoginComponent(configFilePath, `@@/${componentFileName}`, injectionType);
console.log(
chalk.bold.greenBright('You can now open the component in your IDE:'),
diff --git a/adminforth/commands/createCustomComponent/templates/customFields/filter.vue.hbs b/adminforth/commands/createCustomComponent/templates/customFields/filter.vue.hbs
new file mode 100644
index 00000000..9a2876be
--- /dev/null
+++ b/adminforth/commands/createCustomComponent/templates/customFields/filter.vue.hbs
@@ -0,0 +1,34 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/adminforth/commands/createCustomComponent/templates/login/beforeLogin.vue.hbs b/adminforth/commands/createCustomComponent/templates/login/beforeLogin.vue.hbs
new file mode 100644
index 00000000..8e5ee197
--- /dev/null
+++ b/adminforth/commands/createCustomComponent/templates/login/beforeLogin.vue.hbs
@@ -0,0 +1,18 @@
+
+
+ Login Page Text
+
+
+
+
+
diff --git a/adminforth/commands/postinstall.js b/adminforth/commands/postinstall.js
new file mode 100644
index 00000000..23e21cd3
--- /dev/null
+++ b/adminforth/commands/postinstall.js
@@ -0,0 +1,15 @@
+import fs from 'fs';
+import path from 'path';
+
+import { execSync } from 'child_process';
+const spaPath = path.join(import.meta.dirname, 'dist', 'spa');
+
+
+if (fs.existsSync(spaPath)){
+ console.log('Installing SPA dependencies...');
+ execSync('npm ci', { cwd: spaPath, stdio: 'inherit' });
+ console.log('Installed spa dependencies');
+} else {
+ console.log('SPA dependencies not found');
+ console.log('current directory', import.meta.dirname);
+}
\ No newline at end of file
diff --git a/adminforth/documentation/blog/2024-10-01-ai-blog/index.md b/adminforth/documentation/blog/2024-10-01-ai-blog/index.md
index 1315e70e..abdfc75d 100644
--- a/adminforth/documentation/blog/2024-10-01-ai-blog/index.md
+++ b/adminforth/documentation/blog/2024-10-01-ai-blog/index.md
@@ -199,7 +199,7 @@ model ContentImage {
Create a migration:
```bash
-npm run makemigration -- --name add-posts
+npm run makemigration -- --name add-posts && npm run migrate:local
```
diff --git a/adminforth/documentation/docs/tutorial/001-gettingStarted.md b/adminforth/documentation/docs/tutorial/001-gettingStarted.md
index 7a8cc336..45f48904 100644
--- a/adminforth/documentation/docs/tutorial/001-gettingStarted.md
+++ b/adminforth/documentation/docs/tutorial/001-gettingStarted.md
@@ -89,7 +89,7 @@ This will create a migration file in `migrations` and apply it to the database.
In future, when you need to add new resources, you need to modify `schema.prisma` (add models, change fields, etc.). After doing any modification you need to create a new migration using next command:
```bash
-npm run makemigration -- --name
+npm run makemigration -- --name init && npm run migrate:local
```
Other developers need to pull migration and run `npm run migrateLocal` to apply any unapplied migrations.
@@ -173,7 +173,7 @@ model apartments {
Run the following command to create a new migration:
```bash
-npm run makemigration -- --name add-apartments
+npm run makemigration -- --name add-apartments && npm run migrate:local
```
### Step3. Create the `apartments` resource
diff --git a/adminforth/documentation/docs/tutorial/01-helloWorld.md b/adminforth/documentation/docs/tutorial/01-helloWorld.md
index 756c1f0d..65c3b59b 100644
--- a/adminforth/documentation/docs/tutorial/01-helloWorld.md
+++ b/adminforth/documentation/docs/tutorial/01-helloWorld.md
@@ -122,7 +122,7 @@ model Post {
Create database using `prisma migrate`:
```bash
-npm run makemigration --name init
+npm run makemigration --name init && npm run migrate:local
```
## Setting up AdminForth
diff --git a/adminforth/documentation/docs/tutorial/03-Customization/02-customFieldRendering.md b/adminforth/documentation/docs/tutorial/03-Customization/02-customFieldRendering.md
index 96d04a6b..0fdd2b51 100644
--- a/adminforth/documentation/docs/tutorial/03-Customization/02-customFieldRendering.md
+++ b/adminforth/documentation/docs/tutorial/03-Customization/02-customFieldRendering.md
@@ -481,4 +481,119 @@ list: '@/renderers/ZeroStylesRichText.vue',
//diff-add
```
-`ZeroStyleRichText` fits well for tasks like email templates preview fields.
\ No newline at end of file
+`ZeroStyleRichText` fits well for tasks like email templates preview fields.
+
+
+### Custom filter component for square meters
+
+
+Sometimes standard filters are not enough, and you want to make a convenient UI for selecting a range of apartment areas. For example, buttons with options for “Small (<25 m²)”, “Medium (25–90 m²)” and “Large (>90 m²)”.
+
+```ts title='./custom/SquareMetersFilter.vue'
+
+
+
{{ $t('Square meters filter') }}
+
+
+
+
+
+
+
+```
+
+```ts title='./resources/apartments.ts'
+ columns: [
+ ...
+ {
+ name: 'square_meter',
+ label: 'Square',
+ //diff-add
+ components: {
+ //diff-add
+ filter: '@@/SquareMetersFilter.vue'
+ //diff-add
+ }
+ },
+ ...
+]
\ No newline at end of file
diff --git a/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md b/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md
index 2375c8a9..df662069 100644
--- a/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md
+++ b/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md
@@ -141,9 +141,9 @@ Here is how it looks:
You can also inject custom components to the login page.
-`loginPageInjections.underInputs` allows to add one or more panels under the login form inputs:
+`loginPageInjections.underInputs` and `loginPageInjections.panelHeader` allows to add one or more panels under or over the login form inputs:
-![login Page Injections underInputs]()
+![login Page Injections underInputs]()
For example:
@@ -172,6 +172,38 @@ Now create file `CustomLoginFooter.vue` in the `custom` folder of your project:
```
+Also you can add `panelHeader`
+
+```ts title="/index.ts"
+
+new AdminForth({
+ ...
+ customization: {
+ loginPageInjections: {
+ underInputs: '@@/CustomLoginFooter.vue',
+//diff-add
+ panelHeader: '@@/CustomLoginHeader.vue',
+ }
+ ...
+ }
+
+ ...
+})
+```
+
+Now create file `CustomLoginHeader.vue` in the `custom` folder of your project:
+
+```html title="./custom/CustomLoginHeader.vue"
+
+
+
+ AdminForth
+
+
+
+```
+
+
## List view page injections shrinking: thin enough to shrink?
diff --git a/adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md b/adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md
index fb0e7d67..dfbd7909 100644
--- a/adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md
+++ b/adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md
@@ -240,6 +240,7 @@ import { Input } from '@/afcl'
```
+