Skip to content

Commit 07c5de4

Browse files
committed
Merge branch 'next' of github.com:devforth/adminforth into next
2 parents 1dc3cd8 + d83f78a commit 07c5de4

File tree

22 files changed

+370
-52
lines changed

22 files changed

+370
-52
lines changed

adapters/install-adapters.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ install_adapter() {
1717
git pull
1818
else
1919
echo "Repository for $adapter does not exist. Cloning..."
20-
git clone git@github.com:devforth/$adapter.git "$adapter"
20+
git clone https://github.com/devforth/$adapter.git "$adapter"
2121
cd "$adapter"
2222
fi
2323

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { Express } from "express";
1+
import { Express, Request, Response } from "express";
22
import { IAdminForth } from "adminforth";
3-
43
export function initApi(app: Express, admin: IAdminForth) {
54
app.get(`${admin.config.baseUrl}/api/hello/`,
6-
(req, res) => {
7-
res.json({ message: "Hello from AdminForth API!" });
5+
async (req: Request, res: Response) => {
6+
const allUsers = await admin.resource("adminuser").list([]);
7+
res.json({
8+
message: "Hello from AdminForth API!",
9+
users: allUsers,
10+
});
811
}
912
);
1013
}

adminforth/dataConnectors/postgres.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
385385
const tableName = resource.table;
386386
const result = {};
387387
await Promise.all(columns.map(async (col) => {
388-
const q = `SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM "${tableName}"`;
388+
const q = `SELECT MIN("${col.name}") as min, MAX("${col.name}") as max FROM "${tableName}"`;
389389
dbLogger.trace(`🪲📜 PG Q: ${q}`);
390390
const stmt = await this.client.query(q);
391391
const { min, max } = stmt.rows[0];

adminforth/documentation/docs/tutorial/05-ListOfAdapters.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ Cones:
255255
npm i @adminforth/key-value-adapter-redis
256256
```
257257

258-
Redis adapter uses redis database.
258+
Redis adapter uses in-memory RAM-based Redis database with O(1) get complexity. It is great fit for most of lightweight tasks which fit in RAM. Also capable with multi-process or replica-based installations as centralized storage. Please note that Redis daemon might be not persisted to disk during restarts without additional settings, so if persistence is critical for your task - you might need to set up it separately (for many tasks like rate-limits ephemeral data are fine
259+
259260

260261
```ts
261262
import RedisKeyValueAdapter from '@adminforth/key-value-adapter-redis';
@@ -274,7 +275,9 @@ adapeter.set('test-key', 'test-value', 120); //expiry in 120 seconds
274275
npm i @adminforth/key-value-adapter-leveldb
275276
```
276277

277-
LebelDB uses local storage for storing keys.
278+
LebelDB uses disk storage with o(log(n)) get complexity. Good fit for large and/or persistent KV datasets which still require fast KV access but don't efficently fit into RAM. Please not that this is a single-process adapter only, so if you will run severall processes of admin - they will not be able to work with this adapter (>=2 processes which look at same level database might lead to unpredicted behaviour - exceptions or crashes).
279+
280+
You can use replicas with isolated disks, however in this case state will be also separated between replicas.
278281

279282
```ts
280283
import LevelDBKeyValueAdapter from '@adminforth/key-value-adapter-leveldb'

adminforth/documentation/docs/tutorial/08-Plugins/02-TwoFactorsAuth.md

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,6 @@ options: {
280280
adminUser: adminUser,
281281
//diff-add
282282
userPk: adminUser.pk,
283-
//diff-add
284-
cookies: extra.cookies,
285-
//diff-add
286-
response: response,
287283
//diff-add
288284
extra: extra,
289285
//diff-add
@@ -393,8 +389,6 @@ hooks: {
393389
const verifyRes = await t2fa.verify(confirmationResult, {
394390
adminUser,
395391
userPk: adminUser.pk,
396-
cookies: extra?.cookies,
397-
response,
398392
extra
399393
});
400394
if (!verifyRes || 'error' in verifyRes) {
@@ -404,7 +398,7 @@ hooks: {
404398
},
405399
},
406400
create: {
407-
beforeSave: async ({ adminUser, adminforth, extra }) => {
401+
beforeSave: async ({ adminUser, adminforth, response, extra }) => {
408402
const t2fa = adminforth.getPluginByClassName('TwoFactorsAuthPlugin');
409403
if (!t2fa) {
410404
return { ok: false, error: 'TwoFactorsAuthPlugin is not configured' };
@@ -418,7 +412,7 @@ hooks: {
418412
const verifyRes = await t2fa.verify(confirmationResult, {
419413
adminUser,
420414
userPk: adminUser.pk,
421-
cookies: extra?.cookies,
415+
extra
422416
});
423417
if (!verifyRes || 'error' in verifyRes) {
424418
return { ok: false, error: verifyRes?.error || 'Two-factor verification failed' };
@@ -534,12 +528,8 @@ app.post(`${ADMIN_BASE_URL}/myCriticalAction`,
534528
adminUser: adminUser,
535529
// diff-add
536530
userPk: adminUser.pk,
537-
// diff-add
538-
cookies: cookies,
539-
//diff-add
540-
response: res,
541531
//diff-add
542-
extra: {...req.headers},
532+
extra: {...req.headers, ...req.cookies, response: res},
543533
//diff-add
544534
});
545535
// diff-add

adminforth/index.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class AdminForth implements IAdminForth {
116116
connectorClasses: any;
117117
runningHotReload: boolean;
118118
activatedPlugins: Array<AdminForthPlugin>;
119+
pluginsById: Record<string, AdminForthPlugin> = {};
119120
configValidator: IConfigValidator;
120121
restApi: AdminForthRestAPI;
121122

@@ -256,6 +257,13 @@ class AdminForth implements IAdminForth {
256257
throw new Error(`Attempt to activate Plugin ${pluginInstance.constructor.name} second time for same resource, but plugin does not support it.
257258
To support multiple plugin instance pre one resource, plugin should return unique string values for each installation from instanceUniqueRepresentation`);
258259
}
260+
const pluginId = pluginInstance.pluginOptions?.id;
261+
if (pluginId) {
262+
if (this.pluginsById[pluginId]) {
263+
throw new Error(`Plugin with id "${pluginId}" already exists!`);
264+
}
265+
this.pluginsById[pluginId] = pluginInstance;
266+
}
259267
this.activatedPlugins.push(pluginInstance);
260268
afLogger.trace(`🔌 Plugin ${pluginInstance.constructor.name} activated successfully`);
261269
}
@@ -282,6 +290,15 @@ class AdminForth implements IAdminForth {
282290
return plugins[0] as T;
283291
}
284292

293+
getPluginById<T = any>(id: string): T {
294+
const plugin = this.pluginsById[id];
295+
if (!plugin) {
296+
const similar = suggestIfTypo(Object.keys(this.pluginsById), id);
297+
throw new Error(`Plugin with id "${id}" not found.${similar ? ` Did you mean "${similar}"?` : ''}`);
298+
}
299+
return plugin as T;
300+
}
301+
285302
validateFieldGroups(fieldGroups: {
286303
groupName: string;
287304
columns: string[];
@@ -422,15 +439,15 @@ class AdminForth implements IAdminForth {
422439
res.columns.forEach((col, i) => {
423440
if (!fieldTypes[col.name] && !col.virtual) {
424441
const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
425-
throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
442+
throw new Error(`Table '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
426443
}
427444
// first find discovered values, but allow override
428445
res.columns[i] = { ...fieldTypes[col.name], ...col };
429446
});
430447

431448
// check if primaryKey column is present
432449
if (!res.columns.some((col) => col.primaryKey)) {
433-
throw new Error(`Resource '${res.table}' has no column defined or auto-discovered. Please set 'primaryKey: true' in a columns which has unique value for each record and index`);
450+
throw new Error(`Table '${res.table}' has no column defined or auto-discovered. Please set 'primaryKey: true' in a columns which has unique value for each record and index`);
434451
}
435452

436453
}));

adminforth/spa/src/afcl/Input.vue

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
ref="input"
1313
v-bind="$attrs"
1414
:type="type"
15-
@input="$emit('update:modelValue', type === 'number' ? Number(($event.target as HTMLInputElement)?.value) : ($event.target as HTMLInputElement)?.value)"
15+
:min="min"
16+
:max="max"
17+
@input="onInput"
1618
:value="modelValue"
1719
aria-describedby="helper-text-explanation"
1820
class="afcl-input inline-flex bg-lightInputBackground text-lightInputText dark:text-darkInputText border border-lightInputBorder rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
@@ -48,8 +50,31 @@ const props = defineProps<{
4850
suffix?: string,
4951
prefix?: string,
5052
readonly?: boolean,
53+
min?: number | null,
54+
max?: number | null,
5155
}>()
5256
57+
const emit = defineEmits<{
58+
(e: 'update:modelValue', value: number | null): void
59+
}>();
60+
61+
const onInput = (e: Event) => {
62+
const el = e.target as HTMLInputElement;
63+
64+
if (props.type === 'number') {
65+
let val = Number(el.value);
66+
67+
if (props.min != null && val < props.min) val = props.min;
68+
if (props.max != null && val > props.max) val = props.max;
69+
70+
el.value = String(val);
71+
emit('update:modelValue', val);
72+
} else {
73+
emit('update:modelValue', el.value);
74+
}
75+
};
76+
77+
5378
const input = ref<HTMLInputElement | null>(null)
5479
5580
defineExpose({

adminforth/spa/src/components/ColumnValueInput.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@
6969
class="w-40"
7070
placeholder="0"
7171
:fullWidth="true"
72-
:min="![undefined, null].includes(column.minValue) ? column.minValue : ''"
73-
:max="![undefined, null].includes(column.maxValue) ? column.maxValue : ''"
72+
:min="![undefined, null].includes(column.minValue) ? column.minValue : null"
73+
:max="![undefined, null].includes(column.maxValue) ? column.maxValue : null"
7474
:prefix="column.inputPrefix"
7575
:suffix="column.inputSuffix"
7676
:readonly="(column.editReadonly && source === 'edit') || readonly"
@@ -107,8 +107,8 @@
107107
class="w-40"
108108
placeholder="0.0"
109109
:fullWidth="true"
110-
:min="![undefined, null].includes(column.minValue) ? column.minValue : ''"
111-
:max="![undefined, null].includes(column.maxValue) ? column.maxValue : ''"
110+
:min="![undefined, null].includes(column.minValue) ? column.minValue : null"
111+
:max="![undefined, null].includes(column.maxValue) ? column.maxValue : null"
112112
:prefix="column.inputPrefix"
113113
:suffix="column.inputSuffix"
114114
:modelValue="value"

adminforth/spa/src/components/Toast.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
<div v-else class="af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
1919
<IconCheckCircleSolid class="w-5 h-5" aria-hidden="true" />
2020
</div>
21-
<div class="flex flex-col items-center justify-center break-all">
21+
<div class="flex flex-col items-center justify-center break-all overflow-hidden [display:-webkit-box] [-webkit-box-orient:vertical] [-webkit-line-clamp:70]">
2222
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
2323
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>
2424
{{toast.message}}
2525
</div>
26-
<div v-if="toast.buttons" class="flex mt-2 gap-2 w-full ml-6">
26+
<div v-if="toast.buttons" class="flex mt-2 gap-2 w-full ml-3">
2727
<div v-for="button in toast.buttons" class="af-toast-button rounded-md bg-lightButtonsBackground hover:bg-lightButtonsHover text-lightButtonsText dark:bg-darkPrimary dark:hover:bg-darkButtonsBackground dark:text-darkButtonsText">
28-
<button @click="onButtonClick(button.value)" class="px-2 py-1 rounded hover:bg-black/5 dark:hover:bg-white/10">
28+
<button @click="onButtonClick(button.value)" class="px-2 py-1 rounded hover:bg-black/5 dark:hover:bg-white/10 text-sm">
2929
{{ button.label }}
3030
</button>
3131
</div>

adminforth/spa/src/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './utils/utils';
2+
export * from './utils/listUtils';

0 commit comments

Comments
 (0)