Skip to content

Commit aa4ae8d

Browse files
committed
Merge branch 'next' of https://github.com/devforth/adminforth into feature/AdminForth/1323/show-beautiful-404-error-when-
2 parents 08bd858 + 640187e commit aa4ae8d

40 files changed

+672
-213
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
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>
55

6+
[![Ask AI](http://tluma.ai/badge)](http://tluma.ai/ask-ai/devforth/adminforth)
67

78
* [Try live demo](https://demo.adminforth.dev/) (Read-only mode)
89

@@ -136,4 +137,4 @@ This will always run latest version of adminforth package.
136137

137138
## Star History
138139

139-
[![Star History Chart](https://api.star-history.com/svg?repos=adminforth/adminforth&type=date&legend=top-left)](https://www.star-history.com/#adminforth/adminforth&type=date&legend=top-left)
140+
[![Star History Chart](https://api.star-history.com/svg?repos=devforth/adminforth&type=date&legend=top-left)](https://www.star-history.com/#devforth/adminforth&type=date&legend=top-left)

adminforth/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class AdminForthAuth implements IAdminForthAuth {
4545
this.adminforth = adminforth;
4646
}
4747

48-
getClientIp(headers: object) {
48+
getClientIp(headers: object) {
4949
const clientIpHeader = this.adminforth.config.auth.clientIpHeader;
5050

5151
const headersLower = Object.keys(headers).reduce((acc, key) => {

adminforth/dataConnectors/clickhouse.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,21 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
499499
return res;
500500
}
501501

502+
async deleteMany({ resource, recordIds }: { resource: AdminForthResource; recordIds: string[] }): Promise<number> {
503+
const pkColumn = resource.dataSourceColumns.find((col) => col.primaryKey);
504+
if (!pkColumn || !recordIds || recordIds.length === 0) {
505+
return 0;
506+
}
507+
const paramNames = recordIds.map((_, idx) => `id${idx}`);
508+
const conditions = paramNames.map((name) => `${pkColumn.name} = {${name}:${pkColumn._underlineType}}`).join(' OR ');
509+
const queryParams = paramNames.reduce((acc, name, idx) => {acc[name] = recordIds[idx]; return acc;}, {} as Record<string, any>);
510+
await this.client.command({
511+
query: `ALTER TABLE ${this.dbName}.${resource.table} DELETE WHERE ${conditions}`,
512+
query_params: queryParams,
513+
});
514+
return recordIds.length;
515+
}
516+
502517
close() {
503518
this.client.disconnect();
504519
}

adminforth/dataConnectors/mongo.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,15 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
397397
return res.deletedCount > 0;
398398
}
399399

400+
async deleteMany({ resource, recordIds }: { resource: AdminForthResource; recordIds: string[] }): Promise<number> {
401+
if (!recordIds || recordIds.length === 0) {
402+
return 0;
403+
}
404+
const collection = this.client.db().collection(resource.table);
405+
const res = await collection.deleteMany({[this.getPrimaryKey(resource)]: { $in: recordIds }});
406+
return res.deletedCount ?? 0;
407+
}
408+
400409
async close() {
401410
await this.client.close()
402411
}

adminforth/dataConnectors/mysql.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,17 @@ class MysqlConnector extends AdminForthBaseConnector implements IAdminForthDataS
420420
return res.rowCount > 0;
421421
}
422422

423+
async deleteMany({ resource, recordIds }: { resource: AdminForthResource; recordIds: string[] }): Promise<number> {
424+
if (!recordIds || recordIds.length === 0) {
425+
return 0;
426+
}
427+
const placeholders = recordIds.map(() => '?').join(',');
428+
const query = `DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} IN (${placeholders})`;
429+
dbLogger.trace(`🪲📜 MySQL Q: ${query} values: ${JSON.stringify([recordIds])}`);
430+
const [result] = await this.client.execute(query, recordIds);
431+
return result.affectedRows ?? 0;
432+
}
433+
423434
async close() {
424435
await this.client.end();
425436
}

adminforth/dataConnectors/postgres.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,17 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
455455
return res.rowCount > 0;
456456
}
457457

458+
async deleteMany({ resource, recordIds }: { resource: AdminForthResource; recordIds: string[]}): Promise<number> {
459+
if (!recordIds || recordIds.length === 0) {
460+
return 0;
461+
}
462+
const placeholders = recordIds.map((_, idx) => `$${idx + 1}`).join(', ');
463+
const query = `DELETE FROM "${resource.table}" WHERE "${this.getPrimaryKey(resource)}" IN (${placeholders})`;
464+
dbLogger.trace(`🪲📜 PG Q: ${query}, values: ${JSON.stringify([recordIds])}`);
465+
const res = await this.client.query(query, recordIds);
466+
return res.rowCount ?? 0;
467+
}
468+
458469
async close() {
459470
await this.client.end();
460471
}

adminforth/dataConnectors/sqlite.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,17 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
382382
return res.changes > 0;
383383
}
384384

385+
async deleteMany({ resource, recordIds }: { resource: AdminForthResource, recordIds: string[] }): Promise<number> {
386+
if (!recordIds || recordIds.length === 0) {
387+
return 0;
388+
}
389+
const placeholders = recordIds.map(() => '?').join(',');
390+
const q = this.client.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} IN (${placeholders})`);
391+
dbLogger.trace(`🪲📜 SQLITE Q: ${q}, params: ${JSON.stringify(recordIds)}`);
392+
const res = await q.run(...recordIds);
393+
return res.changes ?? 0;
394+
}
395+
385396
close() {
386397
this.client.close();
387398
}

adminforth/documentation/blog/2025-02-19-compose-aws-ec2-ecr-terraform-github-actions/index.md

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -294,14 +294,6 @@ resource "aws_security_group" "instance_sg" {
294294
cidr_blocks = ["0.0.0.0/0"]
295295
}
296296
297-
ingress {
298-
description = "Allow Docker registry"
299-
from_port = 5000
300-
to_port = 5000
301-
protocol = "tcp"
302-
cidr_blocks = ["0.0.0.0/0"]
303-
}
304-
305297
# SSH
306298
ingress {
307299
description = "Allow SSH"
@@ -351,26 +343,52 @@ resource "aws_instance" "app_instance" {
351343
352344
user_data = <<-EOF
353345
#!/bin/bash
354-
sudo apt-get update
355-
sudo apt-get install ca-certificates curl python3 python3-pip -y
356-
sudo install -m 0755 -d /etc/apt/keyrings
357-
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
358-
sudo chmod a+r /etc/apt/keyrings/docker.asc
346+
set -euo pipefail
347+
348+
LOG_FILE="/home/ubuntu/user_data.log"
349+
exec > >(tee -a "$LOG_FILE") 2>&1
350+
chown ubuntu:ubuntu "$LOG_FILE" || true
351+
352+
touch /home/ubuntu/user_data_started
353+
354+
on_error() {
355+
echo "failed" > /home/ubuntu/user_data_failed
356+
}
357+
trap on_error ERR
358+
359+
export DEBIAN_FRONTEND=noninteractive
360+
361+
apt-get update
362+
apt-get install -y --no-install-recommends \
363+
ca-certificates \
364+
curl
365+
366+
# Install the latest Docker Engine from Docker's official Ubuntu repo
367+
install -m 0755 -d /etc/apt/keyrings
368+
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
369+
chmod a+r /etc/apt/keyrings/docker.asc
359370
360-
# Add the repository to Apt sources:
361371
echo \
362372
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
363-
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
364-
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
365-
sudo apt-get update
373+
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
374+
> /etc/apt/sources.list.d/docker.list
366375
367-
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin screen
376+
apt-get update
377+
apt-get install -y --no-install-recommends \
378+
docker-ce \
379+
docker-ce-cli \
380+
containerd.io \
381+
docker-buildx-plugin \
382+
docker-compose-plugin
368383
369-
systemctl start docker
370-
systemctl enable docker
371-
usermod -a -G docker ubuntu
384+
# Ensure Docker daemon is up
385+
systemctl enable --now docker
372386
373-
sudo snap install aws-cli --classic
387+
# Allow ubuntu user to run docker without sudo (new SSH session required)
388+
usermod -aG docker ubuntu || true
389+
390+
docker --version
391+
docker compose version
374392
375393
echo "done" > /home/ubuntu/user_data_done
376394
@@ -669,20 +687,10 @@ jobs:
669687
with:
670688
terraform_version: 1.10.1
671689
672-
- name: Import Registry CA
673-
run: |
674-
mkdir -p deploy/.keys
675-
echo "$VAULT_REGISTRY_CA_PEM" > deploy/.keys/ca.pem
676-
echo "$VAULT_REGISTRY_CA_KEY" > deploy/.keys/ca.key
677-
env:
678-
VAULT_REGISTRY_CA_PEM: ${{ secrets.VAULT_REGISTRY_CA_PEM }}
679-
VAULT_REGISTRY_CA_KEY: ${{ secrets.VAULT_REGISTRY_CA_KEY }}
680-
681-
682690
- name: Set up Docker Buildx
683691
uses: docker/setup-buildx-action@v3
684692
685-
- name: Import registry SSH keys
693+
- name: Import SSH keys
686694
run: |
687695
mkdir -p deploy/.keys
688696
echo "$VAULT_SSH_PRIVATE_KEY" > deploy/.keys/id_rsa
@@ -732,8 +740,6 @@ Go to your GitHub repository, then `Settings` -> `Secrets` -> `New repository se
732740
- `VAULT_AWS_SECRET_ACCESS_KEY` - your AWS secret key
733741
- `VAULT_SSH_PRIVATE_KEY` - execute `cat deploy/.keys/id_rsa` and paste to GitHub secrets
734742
- `VAULT_SSH_PUBLIC_KEY` - execute `cat deploy/.keys/id_rsa.pub` and paste to GitHub secrets
735-
- `VAULT_REGISTRY_CA_PEM` - execute `cat deploy/.keys/ca.pem` and paste to GitHub secrets
736-
- `VAULT_REGISTRY_CA_KEY` - execute `cat deploy/.keys/ca.key` and paste to GitHub secrets
737743
- `VAULT_ADMINFORTH_SECRET` - generate some random string and paste to GitHub secrets, e.g. `openssl rand -base64 32 | tr -d '\n'`
738744

739745

adminforth/documentation/docs/tutorial/03-Customization/10-menuConfiguration.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,23 @@ auth: {
278278
This syntax can be use to get unique avatar for each user of hardcode avatar, but it makes more sense to use it with [upload plugin](https://adminforth.dev/docs/tutorial/Plugins/upload/#using-plugin-for-uploading-avatar)
279279

280280

281+
## Custom URL
282+
You can use the url property to override default navigation. This is useful for linking to pre-filtered lists or external sites.
283+
284+
285+
```ts title='./index.ts'
286+
287+
menu: [
288+
{
289+
label: 'Posts',
290+
icon: 'flowbite:book-open-outline',
291+
resourceId: 'posts',
292+
//diff-add
293+
url: '/resource/aparts?filter__country__in=["DE"]',
294+
//diff-add
295+
isOpenInNewTab: true // You can also add isOpenInNewTab: true to open the link in a new browser tab
296+
},
297+
],
298+
299+
```
300+
> 👆 Please note start internal URLs with a leading / to ensure correct routing.

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,33 @@ You might need to put some extra item at bottom of list
285285
</div>
286286
</div>
287287

288+
### Add classes to the input directly
289+
290+
You might need to put some extra item at bottom of list
291+
292+
<div class="split-screen" >
293+
<div >
294+
```html
295+
<Select
296+
class="w-full"
297+
:options="[
298+
{label: 'Last 7 days', value: '7'},
299+
{label: 'Last 30 days', value: '30'},
300+
{label: 'Last 90 days', value: '90'},
301+
]"
302+
v-model="selected"
303+
//diff-add
304+
classesForInput="py-[4px] text-sm bg-white rounded"
305+
>
306+
307+
</Select>
308+
```
309+
</div>
310+
<div>
311+
![AFCL Select](image-99.png)
312+
</div>
313+
</div>
314+
288315
## Input
289316

290317

0 commit comments

Comments
 (0)