+
+
+
\ No newline at end of file
diff --git a/adminforth/commands/createCustomComponent/templates/global/sidebar.vue.hbs b/adminforth/commands/createCustomComponent/templates/global/sidebar.vue.hbs
new file mode 100644
index 00000000..2f72ea5c
--- /dev/null
+++ b/adminforth/commands/createCustomComponent/templates/global/sidebar.vue.hbs
@@ -0,0 +1,11 @@
+
+
+ {{reason}}
+
+
+
+
\ No newline at end of file
diff --git a/adminforth/commands/createCustomComponent/templates/global/userMenu.vue.hbs b/adminforth/commands/createCustomComponent/templates/global/userMenu.vue.hbs
new file mode 100644
index 00000000..1b4a5028
--- /dev/null
+++ b/adminforth/commands/createCustomComponent/templates/global/userMenu.vue.hbs
@@ -0,0 +1,11 @@
+
+
+ {{reason}}
+
+
+
+
\ No newline at end of file
diff --git a/adminforth/commands/createCustomComponent/templates/login/afterLogin.vue.hbs b/adminforth/commands/createCustomComponent/templates/login/afterLogin.vue.hbs
new file mode 100644
index 00000000..27916fcc
--- /dev/null
+++ b/adminforth/commands/createCustomComponent/templates/login/afterLogin.vue.hbs
@@ -0,0 +1,12 @@
+
+
+ {{reason}}
+
+
+
+
+
diff --git a/adminforth/dataConnectors/baseConnector.ts b/adminforth/dataConnectors/baseConnector.ts
index 3d032415..b2fc3257 100644
--- a/adminforth/dataConnectors/baseConnector.ts
+++ b/adminforth/dataConnectors/baseConnector.ts
@@ -7,7 +7,8 @@ import {
import { suggestIfTypo } from "../modules/utils.js";
-import { AdminForthFilterOperators, AdminForthSortDirections } from "../types/Common.js";
+import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from "../types/Common.js";
+import { randomUUID } from "crypto";
export default class AdminForthBaseConnector implements IAdminForthDataSourceConnectorBase {
@@ -121,8 +122,14 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
return { ok: false, error: `Value for operator '${filters.operator}' should be an array, in filter object: ${JSON.stringify(filters) }` };
}
if (filters.value.length === 0) {
- // nonsense
- return { ok: false, error: `Filter has IN operator but empty value: ${JSON.stringify(filters)}` };
+ // nonsense, and some databases might not accept IN []
+ const colType = resource.dataSourceColumns.find((col) => col.name == (filters as IAdminForthSingleFilter).field)?.type;
+ if (colType === AdminForthDataTypes.STRING || colType === AdminForthDataTypes.TEXT) {
+ filters.value = [randomUUID()];
+ return { ok: true, error: `` };
+ } else {
+ return { ok: false, error: `Value for operator '${filters.operator}' should not be empty array, in filter object: ${JSON.stringify(filters) }` };
+ }
}
filters.value = filters.value.map((val: any) => this.setFieldValue(fieldObj, val));
} else {
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 98989b3b..6df70c5e 100644
--- a/adminforth/documentation/blog/2024-10-01-ai-blog/index.md
+++ b/adminforth/documentation/blog/2024-10-01-ai-blog/index.md
@@ -46,7 +46,8 @@ npx adminforth create-app --app-name ai-blog
Add modules:
```bash
-npm i @adminforth/upload @adminforth/rich-editor @adminforth/text-complete
+cd ai-blog
+npm i @adminforth/upload @adminforth/rich-editor @adminforth/text-complete @adminforth/chat-gpt slugify http-proxy @adminforth/image-generation-adapter-openai @adminforth/completion-adapter-open-ai-chat-gpt
```
@@ -169,7 +170,7 @@ model Post {
//diff-add
published Boolean
//diff-add
- author User? @relation(fields: [authorId], references: [id])
+ author adminuser? @relation(fields: [authorId], references: [id])
//diff-add
authorId String?
//diff-add
@@ -211,7 +212,7 @@ Open `index.ts` file in root directory and update it with the following content:
```ts title="./index.ts"
import express from 'express';
import AdminForth, { Filters, Sorts } from 'adminforth';
-import userResource from './resources/user.js';
+import userResource from './resources/adminuser.js';
import postResource from './resources/posts.js';
import contentImageResource from './resources/content-image.js';
import httpProxy from 'http-proxy';
@@ -231,7 +232,7 @@ export const admin = new AdminForth({
auth: {
usersResourceId: 'adminuser', // resource to get user during login
usernameField: 'email', // field where username is stored, should exist in resource
- passwordHashField: 'passwordHash',
+ passwordHashField: 'password_hash',
},
customization: {
brandName: 'My Admin',
@@ -289,7 +290,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
// api to server recent posts
app.get('/api/posts', async (req, res) => {
- const { offset = 0, limit = 100, slug = null } = req.query;
+ const offset = parseInt(req.query.offset as string) || 0;
+ const limit = parseInt(req.query.limit as string) || 100;
+ const slug = req.query.slug as string | null;
const posts = await admin.resource('post').list(
[Filters.EQ('published', true), ...(slug ? [Filters.LIKE('slug', slug)] : [])],
limit,
@@ -331,13 +334,14 @@ if (import.meta.url === `file://${process.argv[1]}`) {
if (!await admin.resource('adminuser').get([Filters.EQ('email', 'adminforth@adminforth.dev')])) {
await admin.resource('adminuser').create({
email: 'adminforth@adminforth.dev',
- passwordHash: await AdminForth.Utils.generatePasswordHash('adminforth'),
+ role: 'superadmin',
+ password_hash: await AdminForth.Utils.generatePasswordHash('adminforth'),
});
}
});
admin.express.listen(port, () => {
- console.log(`\n⚡ AdminForth is available at http://localhost:${port}\n`)
+ console.log(`\n⚡ AdminForth is available at http://localhost:${port}/admin\n`)
});
}
```
@@ -377,7 +381,14 @@ export default {
type: AdminForthDataTypes.STRING,
},
{
- name: 'createdAt',
+ name: 'role',
+ enum: [
+ { value: 'superadmin', label: 'Super Admin' },
+ { value: 'user', label: 'User' },
+ ]
+ },
+ {
+ name: 'created_at',
type: AdminForthDataTypes.DATETIME,
showIn: {
edit: false,
@@ -403,9 +414,9 @@ export default {
AdminForth.Utils.PASSWORD_VALIDATORS.UP_LOW_NUM,
],
},
- { name: 'passwordHash', backendOnly: true, showIn: { all: false } },
+ { name: 'password_hash', backendOnly: true, showIn: { all: false } },
{
- name: 'publicName',
+ name: 'public_name',
type: AdminForthDataTypes.STRING,
},
{ name: 'avatar' },
@@ -425,7 +436,7 @@ export default {
return { ok: true }
},
},
- }
+ },
plugins: [
new UploadPlugin({
pathColumnName: 'avatar',
@@ -462,6 +473,8 @@ import UploadPlugin from '@adminforth/upload';
import RichEditorPlugin from '@adminforth/rich-editor';
import ChatGptPlugin from '@adminforth/chat-gpt';
import slugify from 'slugify';
+import CompletionAdapterOpenAIChatGPT from "@adminforth/completion-adapter-open-ai-chat-gpt";
+import ImageGenerationAdapterOpenAI from '@adminforth/image-generation-adapter-openai';
export default {
table: 'post',
@@ -561,23 +574,25 @@ export default {
{ originalFilename, originalExtension }: {originalFilename: string, originalExtension: string }
) => `post-previews/${new Date().getFullYear()}/${randomUUID()}/${originalFilename}.${originalExtension}`,
generation: {
- provider: 'openai',
countToGenerate: 2,
- openAiOptions: {
- model: 'gpt-4o',
- apiKey: process.env.OPENAI_API_KEY,
- },
+ adapter: new ImageGenerationAdapterOpenAI({
+ openAiApiKey: process.env.OPENAI_API_KEY as string,
+ model: 'gpt-image-1',
+ }),
fieldsForContext: ['title'],
+ outputSize: '1536x1024'
},
}),
new RichEditorPlugin({
htmlFieldName: 'content',
completion: {
- provider: 'openai-chat-gpt',
- params: {
- apiKey: process.env.OPENAI_API_KEY,
+ adapter: new CompletionAdapterOpenAIChatGPT({
+ openAiApiKey: process.env.OPENAI_API_KEY as string,
model: 'gpt-4o',
- },
+ expert: {
+ temperature: 0.7
+ }
+ }),
expert: {
debounceTime: 250,
}
@@ -618,11 +633,19 @@ export default {
{
name: 'id',
primaryKey: true,
+ showIn: {
+ edit: false,
+ create: false,
+ },
fillOnCreate: () => randomUUID(),
},
{
name: 'createdAt',
type: AdminForthDataTypes.DATETIME,
+ showIn: {
+ edit: false,
+ create: false,
+ },
fillOnCreate: () => (new Date()).toISOString(),
},
{
@@ -673,7 +696,7 @@ Set up your avatar (you can generate it with AI) and public name in user setting

-## Step 5: Create Nuxt project
+## Step 6: Create Nuxt project
Now let's initialize our seo-facing frontend.
In the root directory of your admin app (`ai-blog`) and create a new folder `seo` and run:
@@ -934,7 +957,7 @@ Open `http://localhost:3500` in your browser and you will see your blog with pos
Go to `http://localhost:3500/admin` to add new posts.
-## Step 6: Deploy
+## Step 7: Deploy
We will use Docker to make it easy to deploy with many ways. We will wrap both Node.js adminforth app and Nuxt.js app into single container for simplicity using supervisor. However you can split them into two containers and deploy them separately e.g. using docker compose.
@@ -951,7 +974,7 @@ Open `Dockerfile` in root project directory (`ai-blog`) and put in the following
FROM node:20-slim
EXPOSE 3500
WORKDIR /app
-RUN apk add --no-cache supervisor
+RUN apt-get update && apt-get install -y supervisor
COPY package.json package-lock.json ./
RUN npm ci
COPY seo/package.json seo/package-lock.json seo/
@@ -972,6 +995,8 @@ autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
+stdout_logfile_maxbytes = 0
+stderr_logfile_maxbytes = 0
[program:seo]
command=sh -c "cd seo && node .output/server/index.mjs"
@@ -980,6 +1005,8 @@ autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
+stdout_logfile_maxbytes = 0
+stderr_logfile_maxbytes = 0
[program:prisma]
command=npm run migrate:prod
@@ -987,6 +1014,8 @@ directory=/app
autostart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
+stdout_logfile_maxbytes = 0
+stderr_logfile_maxbytes = 0
EOF
@@ -1011,8 +1040,8 @@ terraform*
Build and run your docker container locally:
```bash
-sudo docker build -t my-ai-blog .
-sudo docker run -p80:3500 -v ./prodDb:/app/db --env-file .env -it --name my-ai-blog -d my-ai-blog
+docker build -t my-ai-blog .
+docker run -p80:3500 -v ./prodDb:/app/db --env-file .env -it --name my-ai-blog -d my-ai-blog
```
Now you can open `http://localhost` in your browser and see your blog.
@@ -1088,7 +1117,7 @@ data "aws_subnet" "default_subnet" {
}
resource "aws_security_group" "instance_sg" {
- name = "my-ai-blog-instance-sg"
+ name = "my-aiblog-instance-sg"
vpc_id = data.aws_vpc.default.id
ingress {
@@ -1118,14 +1147,14 @@ resource "aws_security_group" "instance_sg" {
}
resource "aws_key_pair" "deployer" {
- key_name = "terraform-deployer-key"
+ key_name = "terraform-deployer-my-aiblog-key"
public_key = file("~/.ssh/id_rsa.pub") # Path to your public SSH key
}
resource "aws_instance" "docker_instance" {
ami = data.aws_ami.amazon_linux.id
- instance_type = "t3a.micro"
+ instance_type = "t3a.small"
subnet_id = data.aws_subnet.default_subnet.id
vpc_security_group_ids = [aws_security_group.instance_sg.id]
key_name = aws_key_pair.deployer.key_name
@@ -1164,13 +1193,13 @@ resource "null_resource" "wait_for_user_data" {
connection {
type = "ssh"
- user = "ubuntu"
+ user = "ec2-user"
private_key = file("~/.ssh/id_rsa")
host = aws_instance.docker_instance.public_ip
}
}
- depends_on = [aws_instance.app_instance]
+ depends_on = [aws_instance.docker_instance]
}
@@ -1252,7 +1281,7 @@ terraform apply -auto-approve
> ☝️ To check logs run `ssh -i ~/.ssh/id_rsa ec2-user@$(terraform output instance_public_ip)`, then `sudo docker logs -n100 -f aiblog`
-Terraform config will build Docker image locally and then copy it to EC2 instance. This approach allows to save build resources (CPU/RAM) on EC2 instance, however increases network traffic (image might be around 200MB). If you want to build image on EC2 instance, you can adjust config slightly: remove `null_resource.build_image` and change `null_resource.remote_commands` to build image on EC2 instance, however micro instance most likely will not be able to build and keep app running at the same time, so you will need to increase instance type or terminate app while building image (which introduces downtime so not recommended as well).
+Terraform config will build the Docker image locally and then copy it to the EC2 instance. This approach saves build resources (CPU/RAM) on the EC2 instance, though it increases network traffic (the image might be around 200MB). If you prefer to build the image directly on the EC2 instance, you can slightly adjust the configuration: remove `null_resource.build_image` and modify `null_resource.remote_commands` to perform the build remotely. However, note that building the image on a `t3.small` instance may still consume significant resources and can interfere with running applications. To avoid potential downtime or performance issues, building the image locally remains the recommended approach.
### Add HTTPs and CDN
diff --git a/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ecr-ci/ga-tf-ecr.jpg b/adminforth/documentation/blog/2025-02-19-compose-aws-ec2-ecr-terraform-github-actions/ga-tf-ecr.jpg
similarity index 100%
rename from adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ecr-ci/ga-tf-ecr.jpg
rename to adminforth/documentation/blog/2025-02-19-compose-aws-ec2-ecr-terraform-github-actions/ga-tf-ecr.jpg
diff --git a/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ecr-ci/index.md b/adminforth/documentation/blog/2025-02-19-compose-aws-ec2-ecr-terraform-github-actions/index.md
similarity index 98%
rename from adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ecr-ci/index.md
rename to adminforth/documentation/blog/2025-02-19-compose-aws-ec2-ecr-terraform-github-actions/index.md
index 28b3b0ba..e46b60b6 100644
--- a/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ecr-ci/index.md
+++ b/adminforth/documentation/blog/2025-02-19-compose-aws-ec2-ecr-terraform-github-actions/index.md
@@ -152,6 +152,15 @@ services:
myadmin:
image: ${MYADMIN_REPO}:latest
+ build:
+ context: ../adminforth-app
+ tags:
+ - ${MYADMIN_REPO}:latest
+ cache_from:
+ - type=registry,ref=${MYADMIN_REPO}:cache
+ cache_to:
+ - type=registry,ref=${MYADMIN_REPO}:cache,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true
+
pull_policy: always
restart: always
env_file:
@@ -205,25 +214,12 @@ tfplan
.env.secrets.prod
```
-## Step 6 - buildx bake file
-
-Create file `deploy/docker-bake.hcl`:
+## Step 6 - file with secrets for local deploy
-```hcl title="deploy/docker-bake.hcl"
-variable "MYADMIN_REPO" {
- default = ""
-}
-group "default" {
- targets = ["myadmin"]
-}
+Create file `deploy/.env.secrets.prod`
-target "myadmin" {
- context = "../myadmin"
- tags = ["${MYADMIN_REPO}:latest"]
- cache-from = ["type=registry,ref=${MYADMIN_REPO}:cache"]
- cache-to = ["type=registry,ref=${MYADMIN_REPO}:cache,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true"]
- push = true
-}
+```bash
+ADMINFORTH_SECRET=
```
@@ -469,7 +465,7 @@ resource "null_resource" "sync_files_and_run" {
aws ecr get-login-password --region ${local.aws_region} --profile myaws | docker login --username AWS --password-stdin ${aws_ecr_repository.myadmin_repo.repository_url}
echo "Running build"
- env $(cat .env.ecr | grep -v "#" | xargs) docker buildx bake --progress=plain --push --allow=fs.read=..
+ env $(cat .env.ecr | grep -v "#" | xargs) docker buildx bake --progress=plain --push --allow=fs.read=.. -f compose.yml
# if you will change host, pleasee add -o StrictHostKeyChecking=no
echo "Copy files to the instance"
diff --git a/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ci-registry/ga-tf-aws.jpg b/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-github-actions-registry/ga-tf-aws.jpg
similarity index 100%
rename from adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ci-registry/ga-tf-aws.jpg
rename to adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-github-actions-registry/ga-tf-aws.jpg
diff --git a/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ci-registry/index.md b/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-github-actions-registry/index.md
similarity index 97%
rename from adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ci-registry/index.md
rename to adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-github-actions-registry/index.md
index 046adfb9..b82442a5 100644
--- a/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ci-registry/index.md
+++ b/adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-github-actions-registry/index.md
@@ -75,8 +75,6 @@ This command already creates a `Dockerfile` and `.dockerignore` for you, so you
create folder `deploy` and create file `compose.yml` inside:
```yml title="deploy/compose.yml"
-
-
services:
traefik:
image: "traefik:v2.5"
@@ -91,14 +89,19 @@ services:
myadmin:
image: localhost:5000/myadmin:latest
+ build:
+ context: ../adminforth-app
+ tags:
+ - localhost:5000/myadmin:latest
+ cache_from:
+ - type=registry,ref=localhost:5000/myadmin:cache
+ cache_to:
+ - type=registry,ref=localhost:5000/myadmin:cache,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true
+
pull_policy: always
restart: always
env_file:
- .env.secrets.prod
- environment:
- - NODE_ENV=production
- - DATABASE_URL=sqlite://.db.sqlite
- - PRISMA_DATABASE_URL=file:.db.sqlite
volumes:
- myadmin-db:/code/db
@@ -148,29 +151,16 @@ tfplan
.env.secrets.prod
```
-## Step 6 - buildx bake file
-
-Create file `deploy/docker-bake.hcl`:
-
-```hcl title="deploy/docker-bake.hcl"
-variable "REGISTRY_BASE" {
- default = "appserver.local:5000"
-}
+## Step 6 - file with secrets for local deploy
-group "default" {
- targets = ["myadmin"]
-}
+Create file `deploy/.env.secrets.prod`
-target "myadmin" {
- context = "../myadmin"
- tags = ["${REGISTRY_BASE}/myadmin:latest"]
- cache-from = ["type=registry,ref=${REGISTRY_BASE}/myadmin:cache"]
- cache-to = ["type=registry,ref=${REGISTRY_BASE}/myadmin:cache,mode=max,compression=zstd"]
- push = true
-}
+```bash
+ADMINFORTH_SECRET=
```
+
## Step 7 - main terraform file main.tf
First of all install Terraform as described here [terraform installation](https://developer.hashicorp.com/terraform/install#linux).
@@ -426,7 +416,7 @@ resource "null_resource" "sync_files_and_run" {
echo '{"auths":{"appserver.local:5000":{"auth":"'$(echo -n "ci-user:$(cat ./.keys/registry.pure)" | base64 -w 0)'"}}}' > ~/.docker/config.json
echo "Running build"
- docker buildx bake --progress=plain --push --allow=fs.read=..
+ docker buildx bake --progress=plain --push --allow=fs.read=.. -f compose.yml
# compose temporarily it is not working https://github.com/docker/compose/issues/11072#issuecomment-1848974315
# docker compose --progress=plain -p app -f ./compose.yml build --push
diff --git a/adminforth/documentation/docs/tutorial/04-deploy.md b/adminforth/documentation/docs/tutorial/04-deploy.md
index da6abd63..7f2da57f 100644
--- a/adminforth/documentation/docs/tutorial/04-deploy.md
+++ b/adminforth/documentation/docs/tutorial/04-deploy.md
@@ -36,10 +36,13 @@ docker run -p 3500:3500 \
Now open your browser and go to `http://localhost:3500` to see your AdminForth application running in Docker container.
+## Automating deployments with CI
+
+If you are looking for a professional way to deploy your AdminForth application, you can follow our blog post [how to deploy your AdminForth application with Terraform From GitHub actions](http://localhost:3000/blog/compose-aws-ec2-ecr-terraform-github-actions/)
+
## Adding SSL (https) to AdminForth
-There are lots of ways today to put your application behind SSL gateway. You might simply put AdminForth instance behind free Cloudflare CDN,
-change 3500 port to 80 and Cloudflare will automatically add SSL layer and faster CDN for your application.
+There are lots of ways today to put your application behind SSL gateway. You might simply put AdminForth instance behind free Cloudflare CDN, change 3500 port to 80 and Cloudflare will automatically add SSL layer and faster CDN for your application.
However as a bonus here we will give you independent way to add free LetsEncrypt SSL layer to your AdminForth application.
diff --git a/adminforth/documentation/docs/tutorial/05-Plugins/05-upload.md b/adminforth/documentation/docs/tutorial/05-Plugins/05-upload.md
index 0172b273..1742c851 100644
--- a/adminforth/documentation/docs/tutorial/05-Plugins/05-upload.md
+++ b/adminforth/documentation/docs/tutorial/05-Plugins/05-upload.md
@@ -280,6 +280,8 @@ new UploadPlugin({
}),
//diff-add
fieldsForContext: ['title'],
+//diff-add
+ outputSize: '1536x1024' // size of generated image
},
```
@@ -288,23 +290,26 @@ Here is how it works:

-You can also pass additional parameters to OpenAI API call
+You can also pass additional parameters to [OpenAI API call](https://platform.openai.com/docs/api-reference/images/createEdit) by using `extraParams` property:
```ts title="./apartments.ts"
new ImageGenerationAdapterOpenAI({
//diff-add
- openAiApiKey: process.env.OPENAI_API_KEY as string,
+ openAiApiKey: process.env.OPENAI_API_KEY as string,
//diff-add
- model: 'gpt-image-1',
+ model: 'gpt-image-1',
//diff-add
- extraParams: {
+ extraParams: {
//diff-add
- moderation: 'low',
+ moderation: 'low',
//diff-add
- quality: 'high',
+ quality: 'high',
//diff-add
- }),
+ },
+ //diff-add
+ outputSize: '1536x1024' // size of generated image
+}),
```
@@ -330,6 +335,7 @@ generation: {
},
generationPrompt: "Remove text from the image",
countToGenerate: 3,
+ outputSize: '1024x1024',
}
```
diff --git a/adminforth/spa/package-lock.json b/adminforth/spa/package-lock.json
index 207416b6..178e3bb9 100644
--- a/adminforth/spa/package-lock.json
+++ b/adminforth/spa/package-lock.json
@@ -14,7 +14,6 @@
"apexcharts": "^4.4.0",
"dayjs": "^1.11.11",
"debounce": "^2.1.0",
- "flowbite": "^2.3.0",
"flowbite-datepicker": "^1.2.6",
"javascript-time-ago": "^2.5.11",
"pinia": "^2.1.7",
@@ -38,12 +37,13 @@
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"flag-icons": "^7.2.3",
+ "flowbite": "^3.1.2",
"i18n-iso-countries": "^7.12.0",
"npm-run-all2": "^6.1.2",
"portfinder": "^1.0.32",
"postcss": "^8.4.38",
"sass": "^1.77.2",
- "tailwindcss": "^3.4.3",
+ "tailwindcss": "^3.4.17",
"typescript": "~5.4.0",
"vite": "^5.2.13",
"vue-i18n-extract": "^2.0.7",
@@ -811,6 +811,64 @@
"url": "https://opencollective.com/popperjs"
}
},
+ "node_modules/@rollup/plugin-node-resolve": {
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
+ "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "@types/resolve": "1.20.2",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.78.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+ "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
@@ -1090,8 +1148,7 @@
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
- "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/node": {
"version": "20.12.12",
@@ -1102,6 +1159,12 @@
"undici-types": "~5.26.4"
}
},
+ "node_modules/@types/resolve": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
+ "license": "MIT"
+ },
"node_modules/@types/web-bluetooth": {
"version": "0.0.20",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
@@ -1834,12 +1897,13 @@
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -2591,10 +2655,11 @@
}
},
"node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -2646,22 +2711,39 @@
"dev": true
},
"node_modules/flowbite": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.3.0.tgz",
- "integrity": "sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-3.1.2.tgz",
+ "integrity": "sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
"@popperjs/core": "^2.9.3",
- "mini-svg-data-uri": "^1.4.3"
+ "flowbite-datepicker": "^1.3.1",
+ "mini-svg-data-uri": "^1.4.3",
+ "postcss": "^8.5.1"
}
},
"node_modules/flowbite-datepicker": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.2.6.tgz",
- "integrity": "sha512-UbU/xXs9HFiwWfL4M1vpwIo8EpS0NUQSOvYnp0Z9u3N118nU7lPFGoUOq7su9d0aOJy9FssXzx1SZwN8MXhE1g==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz",
+ "integrity": "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==",
+ "license": "MIT",
"dependencies": {
+ "@rollup/plugin-node-resolve": "^15.2.3",
"flowbite": "^2.0.0"
}
},
+ "node_modules/flowbite-datepicker/node_modules/flowbite": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz",
+ "integrity": "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==",
+ "license": "MIT",
+ "dependencies": {
+ "@popperjs/core": "^2.9.3",
+ "flowbite-datepicker": "^1.3.0",
+ "mini-svg-data-uri": "^1.4.3"
+ }
+ },
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -2715,7 +2797,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -2838,7 +2919,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -2971,7 +3051,6 @@
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
- "dev": true,
"dependencies": {
"hasown": "^2.0.0"
},
@@ -3009,11 +3088,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+ "license": "MIT"
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -3079,10 +3165,11 @@
}
},
"node_modules/jiti": {
- "version": "1.21.0",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
- "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
+ "license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
}
@@ -3149,12 +3236,16 @@
}
},
"node_modules/lilconfig": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
- "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/lines-and-columns": {
@@ -3227,12 +3318,13 @@
}
},
"node_modules/micromatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
- "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "braces": "^3.0.2",
+ "braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
@@ -3318,15 +3410,16 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -3549,8 +3642,7 @@
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-scurry": {
"version": "1.11.1",
@@ -3701,9 +3793,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"funding": [
{
"type": "opencollective",
@@ -3720,8 +3812,8 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
@@ -3799,42 +3891,38 @@
}
}
},
- "node_modules/postcss-load-config/node_modules/lilconfig": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
- "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
- "dev": true,
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
- }
- },
"node_modules/postcss-nested": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
- "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
"dependencies": {
- "postcss-selector-parser": "^6.0.11"
+ "postcss-selector-parser": "^6.1.1"
},
"engines": {
"node": ">=12.0"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
"peerDependencies": {
"postcss": "^8.2.14"
}
},
"node_modules/postcss-selector-parser": {
- "version": "6.0.16",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
- "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -3931,7 +4019,6 @@
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
- "dev": true,
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
@@ -3982,7 +4069,7 @@
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
"integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"@types/estree": "1.0.5"
},
@@ -4307,7 +4394,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -4316,33 +4402,34 @@
}
},
"node_modules/tailwindcss": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
- "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
+ "version": "3.4.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
- "chokidar": "^3.5.3",
+ "chokidar": "^3.6.0",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
- "fast-glob": "^3.3.0",
+ "fast-glob": "^3.3.2",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
- "jiti": "^1.21.0",
- "lilconfig": "^2.1.0",
- "micromatch": "^4.0.5",
+ "jiti": "^1.21.6",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
- "picocolors": "^1.0.0",
- "postcss": "^8.4.23",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
- "postcss-load-config": "^4.0.1",
- "postcss-nested": "^6.0.1",
- "postcss-selector-parser": "^6.0.11",
- "resolve": "^1.22.2",
- "sucrase": "^3.32.0"
+ "postcss-load-config": "^4.0.2",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
},
"bin": {
"tailwind": "lib/cli.js",
@@ -4391,6 +4478,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
diff --git a/adminforth/spa/package.json b/adminforth/spa/package.json
index cf2df1c4..78540e85 100644
--- a/adminforth/spa/package.json
+++ b/adminforth/spa/package.json
@@ -19,7 +19,6 @@
"apexcharts": "^4.4.0",
"dayjs": "^1.11.11",
"debounce": "^2.1.0",
- "flowbite": "^2.3.0",
"flowbite-datepicker": "^1.2.6",
"javascript-time-ago": "^2.5.11",
"pinia": "^2.1.7",
@@ -43,12 +42,13 @@
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"flag-icons": "^7.2.3",
+ "flowbite": "^3.1.2",
"i18n-iso-countries": "^7.12.0",
"npm-run-all2": "^6.1.2",
"portfinder": "^1.0.32",
"postcss": "^8.4.38",
"sass": "^1.77.2",
- "tailwindcss": "^3.4.3",
+ "tailwindcss": "^3.4.17",
"typescript": "~5.4.0",
"vite": "^5.2.13",
"vue-i18n-extract": "^2.0.7",
diff --git a/adminforth/spa/src/afcl/ProgressBar.vue b/adminforth/spa/src/afcl/ProgressBar.vue
index 72a79abe..9e7526de 100644
--- a/adminforth/spa/src/afcl/ProgressBar.vue
+++ b/adminforth/spa/src/afcl/ProgressBar.vue
@@ -24,8 +24,8 @@ interface Props {
formatter?: (value: number) => string
progressFormatter?: (value: number, percentage: number) => string
showLabels?: boolean
- showValues: boolean
- showProgress: boolean
+ showValues?: boolean
+ showProgress?: boolean
}
const props = withDefaults(defineProps(), {
diff --git a/adminforth/spa/src/utils.ts b/adminforth/spa/src/utils.ts
index 27f37655..d3784795 100644
--- a/adminforth/spa/src/utils.ts
+++ b/adminforth/spa/src/utils.ts
@@ -30,9 +30,9 @@ export async function callApi({path, method, body=undefined}: {
return null;
}
return await r.json();
- } catch(e){
+ } catch(e) {
adminforth.alert({variant:'danger', message: window.i18n?.global?.t('Something went wrong, please try again later'),})
- console.error(`error in callApi ${path}`,e);
+ console.error(`error in callApi ${path}`, e);
}
}
diff --git a/adminforth/types/Adapters.ts b/adminforth/types/Adapters.ts
index 7ead0b1c..f502af1c 100644
--- a/adminforth/types/Adapters.ts
+++ b/adminforth/types/Adapters.ts
@@ -1,6 +1,19 @@
export interface EmailAdapter {
+
+ /**
+ * This method is called to validate the configuration of the adapter
+ * and should throw a clear user-readbale error if the configuration is invalid.
+ */
validate(): Promise;
+ /**
+ * This method should send an email using the adapter
+ * @param from - The sender's email address
+ * @param to - The recipient's email address
+ * @param text - The plain text version of the email
+ * @param html - The HTML version of the email
+ * @param subject - The subject of the email
+ */
sendEmail(
from: string,
to: string,
@@ -15,8 +28,19 @@ export interface EmailAdapter {
export interface CompletionAdapter {
+ /**
+ * This method is called to validate the configuration of the adapter
+ * and should throw a clear user-readbale error if the configuration is invalid.
+ */
validate(): void;
+ /**
+ * This method should return a text completion based on the provided content and stop sequence.
+ * @param content - The input text to complete
+ * @param stop - An array of stop sequences to indicate where to stop the completion
+ * @param maxTokens - The maximum number of tokens to generate
+ * @returns A promise that resolves to an object containing the completed text and other metadata
+ */
complete(
content: string,
stop: string[],
@@ -30,10 +54,14 @@ export interface CompletionAdapter {
export interface ImageGenerationAdapter {
+ /**
+ * This method is called to validate the configuration of the adapter
+ * and should throw a clear user-readbale error if the configuration is invalid.
+ */
validate(): void;
/**
- * Return 1 or 10, or Infinity if the adapter supports multiple images
+ * Return max number of images which model can generate in one request
*/
outputImagesMaxCountSupported(): number;
@@ -47,6 +75,14 @@ export interface ImageGenerationAdapter {
*/
inputFileExtensionSupported(): string[];
+ /**
+ * This method should generate an image based on the provided prompt and input files.
+ * @param prompt - The prompt to generate the image
+ * @param inputFiles - An array of input file paths (optional)
+ * @param n - The number of images to generate (default is 1)
+ * @param size - The size of the generated image (default is the lowest dimension supported)
+ * @returns A promise that resolves to an object containing the generated image URLs and any error message
+ */
generate({
prompt,
inputFiles,
@@ -68,11 +104,91 @@ export interface ImageGenerationAdapter {
}
-
+/**
+ * This interface is used to implement OAuth2 authentication adapters.
+ */
export interface OAuth2Adapter {
+ /**
+ * This method should return navigatable URL to the OAuth2 provider authentication page.
+ */
getAuthUrl(): string;
+
+ /**
+ * This method should return the token from the OAuth2 provider using the provided code and redirect URI.
+ * @param code - The authorization code received from the OAuth2 provider
+ * @param redirect_uri - The redirect URI used in the authentication request
+ * @returns A promise that resolves to an object containing the email address of the authenticated user
+ */
getTokenFromCode(code: string, redirect_uri: string): Promise<{ email: string }>;
+
+ /**
+ * This method should return text (content) of SVG icon which will be used in the UI.
+ * Use official SVG icons with simplest possible conent, omit icons which have base64 encoded raster images inside.
+ */
getIcon(): string;
+
+ /**
+ * This method should return the text to be displayed on the button in the UI
+ */
getButtonText?(): string;
+
+ /**
+ * This method should return the name of the adapter
+ */
getName?(): string;
}
+
+
+export interface StorageAdapter {
+ /**
+ * This method should return the presigned URL for the given key capable of upload (adapter user will call PUT multipart form data to this URL within expiresIn seconds after link generation).
+ * By default file which will be uploaded on PUT should be marked for deletion. So if during 24h it is not marked for not deletion, it adapter should delete it forever.
+ * The PUT method should fail if the file already exists.
+ *
+ * Adapter user will always pass next parameters to the method:
+ * @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
+ * @param expiresIn - The expiration time in seconds for the presigned URL
+ * @param contentType - The content type of the file to be uploaded, e.g. "image/png"
+ *
+ * @returns A promise that resolves to an object containing the upload URL and any extra parameters which should be sent with PUT multipart form data
+ */
+ getUploadSignedUrl(key: string, contentType: string, expiresIn?: number): Promise<{
+ uploadUrl: string;
+ uploadExtraParams?: Record;
+ }>;
+
+ /**
+ * This method should return the URL for the given key capable of download (200 GET request with response body or 200 HEAD request without response body).
+ * If adapter configured to use public storage, this method should return the public URL of the file.
+ * If adapter configured to use private storage, this method should return the presigned URL for the file.
+ *
+ * @param key - The key of the file to be downloaded e.g. "uploads/file.txt"
+ * @param expiresIn - The expiration time in seconds for the presigned URL
+ */
+ getDownloadUrl(key: string, expiresIn?: number): Promise;
+
+ /**
+ * This method should mark the file for deletion.
+ * If file is marked for delation and exists more then 24h (since creation date) it should be deleted.
+ * This method should work even if the file does not exist yet (e.g. only presigned URL was generated).
+ * @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
+ */
+ markKeyForDeletation(key: string): Promise;
+
+
+ /**
+ * This method should mark the file to not be deleted.
+ * This method should be used to cancel the deletion of the file if it was marked for deletion.
+ * @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
+ */
+ markKeyForNotDeletation(key: string): Promise;
+
+
+ /**
+ * This method can start needed schedullers, cron jobs, etc. to clean up the storage.
+ */
+ setupLifecycle(): Promise;
+
+}
+
+
\ No newline at end of file
diff --git a/dev-demo/resources/apartments.ts b/dev-demo/resources/apartments.ts
index 24749035..08283634 100644
--- a/dev-demo/resources/apartments.ts
+++ b/dev-demo/resources/apartments.ts
@@ -302,7 +302,8 @@ export default {
// return [`https://tmpbucket-adminforth.s3.eu-central-1.amazonaws.com/${record.apartment_source}`];
// },
generationPrompt: "Add a 10 kittyies to the appartment look, it should be foto-realistic, they should be different colors, sitting all around the appartment",
- countToGenerate: 3,
+ countToGenerate: 1,
+ outputSize: '1024x1024',
// rateLimit: {
// limit: "2/1m",
// errorMessage: