adminforth 1.2.31 → 1.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import betterSqlite3 from 'better-sqlite3';
|
|
2
1
|
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
|
|
3
2
|
import AdminForthBaseConnector from './baseConnector.js';
|
|
4
3
|
import dayjs from 'dayjs';
|
|
5
|
-
import { createClient
|
|
6
|
-
import { isObject } from 'util';
|
|
7
|
-
import { date } from 'zod';
|
|
4
|
+
import { createClient } from '@clickhouse/client'
|
|
8
5
|
|
|
9
6
|
|
|
10
7
|
|
|
@@ -12,6 +9,7 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
|
|
|
12
9
|
|
|
13
10
|
client: any;
|
|
14
11
|
dbName: string;
|
|
12
|
+
url: string;
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
15
|
* url: http[s]://[username:password@]hostname:port[/database][?param1=value1¶m2=value2]
|
|
@@ -20,6 +18,7 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
|
|
|
20
18
|
constructor({ url }: { url: string }) {
|
|
21
19
|
super();
|
|
22
20
|
this.dbName = new URL(url).pathname.replace('/', '');
|
|
21
|
+
this.url = url;;
|
|
23
22
|
// create connection here
|
|
24
23
|
this.client = createClient({
|
|
25
24
|
url: url.replace('clickhouse://', 'http://'),
|
|
@@ -36,15 +35,23 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
|
|
|
36
35
|
// level: ClickHouseLogLevel.TRACE,
|
|
37
36
|
// }
|
|
38
37
|
});
|
|
38
|
+
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
async discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}> {
|
|
42
42
|
const tableName = resource.table;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
|
|
44
|
+
let rows;
|
|
45
|
+
try {
|
|
46
|
+
const q = await this.client.query({
|
|
47
|
+
query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
|
|
48
|
+
format: 'JSONEachRow',
|
|
49
|
+
});
|
|
50
|
+
rows = await q.json();
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(` 🛑Error connecting to datasource URL ${this.url}:`, e);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
48
55
|
|
|
49
56
|
const fieldTypes = {};
|
|
50
57
|
rows.forEach((row) => {
|
|
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
|
|
11
11
|
import AdminForthBaseConnector from './baseConnector.js';
|
|
12
12
|
import dayjs from 'dayjs';
|
|
13
|
-
import { createClient } from '@clickhouse/client';
|
|
13
|
+
import { createClient } from '@clickhouse/client';
|
|
14
14
|
class ClickhouseConnector extends AdminForthBaseConnector {
|
|
15
15
|
/**
|
|
16
16
|
* url: http[s]://[username:password@]hostname:port[/database][?param1=value1¶m2=value2]
|
|
@@ -35,6 +35,8 @@ class ClickhouseConnector extends AdminForthBaseConnector {
|
|
|
35
35
|
[AdminForthSortDirections.desc]: 'DESC',
|
|
36
36
|
};
|
|
37
37
|
this.dbName = new URL(url).pathname.replace('/', '');
|
|
38
|
+
this.url = url;
|
|
39
|
+
;
|
|
38
40
|
// create connection here
|
|
39
41
|
this.client = createClient({
|
|
40
42
|
url: url.replace('clickhouse://', 'http://'),
|
|
@@ -54,11 +56,18 @@ class ClickhouseConnector extends AdminForthBaseConnector {
|
|
|
54
56
|
discoverFields(resource) {
|
|
55
57
|
return __awaiter(this, void 0, void 0, function* () {
|
|
56
58
|
const tableName = resource.table;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
let rows;
|
|
60
|
+
try {
|
|
61
|
+
const q = yield this.client.query({
|
|
62
|
+
query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
|
|
63
|
+
format: 'JSONEachRow',
|
|
64
|
+
});
|
|
65
|
+
rows = yield q.json();
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
console.error(` 🛑Error connecting to datasource URL ${this.url}:`, e);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
62
71
|
const fieldTypes = {};
|
|
63
72
|
rows.forEach((row) => {
|
|
64
73
|
const field = {};
|
package/dist/index.js
CHANGED
|
@@ -83,9 +83,13 @@ class AdminForth {
|
|
|
83
83
|
throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}'`);
|
|
84
84
|
}
|
|
85
85
|
const fieldTypes = yield this.connectors[res.dataSource].discoverFields(res);
|
|
86
|
-
if (!Object.keys(fieldTypes).length) {
|
|
86
|
+
if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
|
|
87
87
|
throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
|
|
88
88
|
}
|
|
89
|
+
if (fieldTypes === null) {
|
|
90
|
+
console.error(`DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
89
93
|
if (!res.columns) {
|
|
90
94
|
res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
|
|
91
95
|
}
|
|
@@ -246,10 +246,15 @@ export default class ConfigValidator {
|
|
|
246
246
|
state: 'danger',
|
|
247
247
|
icon: 'flowbite:trash-bin-outline',
|
|
248
248
|
allowed: (_b) => __awaiter(this, [_b], void 0, function* ({ resource, adminUser, allowedActions }) { return allowedActions.delete; }),
|
|
249
|
-
action: (_c) => __awaiter(this, [_c], void 0, function* ({ selectedIds }) {
|
|
249
|
+
action: (_c) => __awaiter(this, [_c], void 0, function* ({ selectedIds, adminUser }) {
|
|
250
250
|
const connector = this.adminforth.connectors[res.dataSource];
|
|
251
251
|
yield Promise.all(selectedIds.map((recordId) => __awaiter(this, void 0, void 0, function* () {
|
|
252
|
+
const record = yield connector.getRecordByPrimaryKey(res, recordId);
|
|
252
253
|
yield connector.deleteRecord({ resource: res, recordId });
|
|
254
|
+
// call afterDelete hook
|
|
255
|
+
yield Promise.all(res.hooks.delete.afterSave.map((hook) => __awaiter(this, void 0, void 0, function* () {
|
|
256
|
+
yield hook({ resource: res, record, adminUser });
|
|
257
|
+
})));
|
|
253
258
|
})));
|
|
254
259
|
})
|
|
255
260
|
});
|
package/index.ts
CHANGED
|
@@ -111,10 +111,13 @@ class AdminForth implements IAdminForth {
|
|
|
111
111
|
throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}'`);
|
|
112
112
|
}
|
|
113
113
|
const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
|
|
114
|
-
if (!Object.keys(fieldTypes).length) {
|
|
114
|
+
if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
|
|
115
115
|
throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
if (fieldTypes === null) {
|
|
118
|
+
console.error(`DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
118
121
|
if (!res.columns) {
|
|
119
122
|
res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
|
|
120
123
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
AdminForthComponentDeclaration ,
|
|
6
6
|
AdminForthResourcePages, AllowedActionsEnum,
|
|
7
7
|
type AdminForthComponentDeclarationFull,
|
|
8
|
+
type AfterSaveFunction,
|
|
8
9
|
} from "../types/AdminForthConfig.js";
|
|
9
10
|
|
|
10
11
|
import fs from 'fs';
|
|
@@ -143,7 +144,7 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
if (this.config.resources) {
|
|
146
|
-
this.config.resources.forEach((res) => {
|
|
147
|
+
this.config.resources.forEach((res: AdminForthResource) => {
|
|
147
148
|
if (!res.table) {
|
|
148
149
|
errors.push(`Resource "${res.dataSource}" is missing table`);
|
|
149
150
|
}
|
|
@@ -266,10 +267,21 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
266
267
|
state: 'danger',
|
|
267
268
|
icon: 'flowbite:trash-bin-outline',
|
|
268
269
|
allowed: async ({ resource, adminUser, allowedActions }) => { return allowedActions.delete },
|
|
269
|
-
action: async ({ selectedIds }) => {
|
|
270
|
+
action: async ({ selectedIds, adminUser }) => {
|
|
270
271
|
const connector = this.adminforth.connectors[res.dataSource];
|
|
271
272
|
await Promise.all(selectedIds.map(async (recordId) => {
|
|
273
|
+
const record = await connector.getRecordByPrimaryKey(res, recordId);
|
|
274
|
+
|
|
272
275
|
await connector.deleteRecord({ resource: res, recordId });
|
|
276
|
+
// call afterDelete hook
|
|
277
|
+
await Promise.all(
|
|
278
|
+
(res.hooks.delete.afterSave as AfterSaveFunction[]).map(
|
|
279
|
+
async (hook) => {
|
|
280
|
+
await hook({ resource: res, record, adminUser });
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
273
285
|
}));
|
|
274
286
|
}
|
|
275
287
|
});
|
package/modules/restApi.ts
CHANGED
|
@@ -251,10 +251,10 @@ export default class AdminForthRestAPI {
|
|
|
251
251
|
|
|
252
252
|
function checkAccess(action: AllowedActionsEnum, allowedActions: AllowedActions): { allowed: boolean, error?: string } {
|
|
253
253
|
const allowed = (allowedActions[action] as boolean | string | undefined);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
if (allowed !== true) {
|
|
255
|
+
return { error: typeof allowed === 'string' ? allowed : 'Action is not allowed', allowed: false };
|
|
256
|
+
}
|
|
257
|
+
return { allowed: true };
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
server.endpoint({
|