adminforth 1.0.84 → 1.1.1

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.
Files changed (45) hide show
  1. package/dist/index.js +15 -16
  2. package/dist/modules/codeInjector.js +2 -2
  3. package/dist/plugins/ForeignInlineListPlugin/index.js +3 -2
  4. package/dist/servers/express.js +0 -3
  5. package/dist/spa/spa/package-lock.json +13 -0
  6. package/dist/spa/spa/package.json +1 -0
  7. package/dist/spa/spa/src/App.vue +44 -19
  8. package/dist/spa/spa/src/components/AcceptModal.vue +2 -9
  9. package/dist/spa/spa/src/components/Toast.vue +65 -0
  10. package/dist/spa/spa/src/components/ValueRenderer.vue +8 -8
  11. package/dist/spa/spa/src/composables/useStores.ts +51 -0
  12. package/dist/spa/spa/src/stores/core.ts +5 -1
  13. package/dist/spa/spa/src/stores/modal.ts +13 -2
  14. package/dist/spa/spa/src/stores/toast.ts +15 -0
  15. package/dist/spa/spa/src/views/CreateView.vue +5 -5
  16. package/dist/spa/spa/src/views/EditView.vue +8 -7
  17. package/dist/spa/spa/src/views/ListView.vue +8 -9
  18. package/dist/spa/spa/src/views/ShowView.vue +13 -10
  19. package/dist/types/AdminForthConfig.js +25 -17
  20. package/dist/types/FrontendAPI.js +7 -0
  21. package/documentation/docs/Getting Started.md +374 -0
  22. package/documentation/docs/Glossary.md +37 -0
  23. package/documentation/docs/image.png +0 -0
  24. package/documentation/docusaurus.config.ts +1 -1
  25. package/index.ts +29 -35
  26. package/modules/codeInjector.ts +4 -4
  27. package/package.json +1 -1
  28. package/plugins/ForeignInlineListPlugin/index.ts +3 -3
  29. package/servers/express.ts +4 -9
  30. package/spa/package-lock.json +13 -0
  31. package/spa/package.json +1 -0
  32. package/spa/src/App.vue +44 -19
  33. package/spa/src/components/AcceptModal.vue +2 -9
  34. package/spa/src/components/Toast.vue +65 -0
  35. package/spa/src/components/ValueRenderer.vue +8 -8
  36. package/spa/src/composables/useStores.ts +51 -0
  37. package/spa/src/stores/core.ts +5 -1
  38. package/spa/src/stores/modal.ts +13 -2
  39. package/spa/src/stores/toast.ts +15 -0
  40. package/spa/src/views/CreateView.vue +5 -5
  41. package/spa/src/views/EditView.vue +8 -7
  42. package/spa/src/views/ListView.vue +8 -9
  43. package/spa/src/views/ShowView.vue +13 -10
  44. package/types/AdminForthConfig.ts +332 -62
  45. package/types/FrontendAPI.ts +73 -0
@@ -12,8 +12,7 @@
12
12
  <component
13
13
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs || []"
14
14
  :is="getCustomComponent(c)"
15
- :record="coreStore.record"
16
- :columns="coreStore.resourceColumns"
15
+ :resource="coreStore.resource"
17
16
  :adminUser="coreStore.adminUser"
18
17
  />
19
18
 
@@ -75,8 +74,7 @@
75
74
  <component
76
75
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs || []"
77
76
  :is="getCustomComponent(c)"
78
- :record="coreStore.record"
79
- :columns="coreStore.resourceColumns"
77
+ :resource="coreStore.resource"
80
78
  :adminUser="coreStore.adminUser"
81
79
  />
82
80
 
@@ -218,7 +216,9 @@
218
216
  <component
219
217
  :is="listComponentsPerColumn[c.name] || ValueRenderer"
220
218
  :column="c"
221
- :row="row"
219
+ :record="row"
220
+ :adminUser="coreStore.adminUser"
221
+ :resource="coreStore.resource"
222
222
  />
223
223
  </td>
224
224
  <td class=" items-center px-6 py-4">
@@ -332,8 +332,7 @@
332
332
  <component
333
333
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom || []"
334
334
  :is="getCustomComponent(c)"
335
- :record="coreStore.record"
336
- :columns="coreStore.resourceColumns"
335
+ :resource="coreStore.resource"
337
336
  :adminUser="coreStore.adminUser"
338
337
  />
339
338
 
@@ -458,8 +457,8 @@ async function getList() {
458
457
  }
459
458
  });
460
459
  listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
461
- if (column.component?.list) {
462
- acc[column.name] = getCustomComponent(column.component.list);
460
+ if (column.components?.list) {
461
+ acc[column.name] = getCustomComponent(column.components.list);
463
462
  }
464
463
  return acc;
465
464
  }, {});
@@ -4,7 +4,7 @@
4
4
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
5
5
  :is="getCustomComponent(c)"
6
6
  :record="coreStore.record"
7
- :columns="coreStore.resourceColumns"
7
+ :resource="coreStore.resource"
8
8
  :adminUser="coreStore.adminUser"
9
9
  />
10
10
  <BreadcrumbsWithButtons>
@@ -27,7 +27,7 @@
27
27
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.afterBreadcrumbs || []"
28
28
  :is="getCustomComponent(c)"
29
29
  :record="coreStore.record"
30
- :columns="coreStore.resourceColumns"
30
+ :resource="coreStore.resource"
31
31
  :adminUser="coreStore.adminUser"
32
32
  />
33
33
 
@@ -63,7 +63,8 @@
63
63
  v-if="showRowComponentsPerColumn[column.name]"
64
64
  :is="showRowComponentsPerColumn[column.name]"
65
65
  :column="column"
66
- :row="coreStore.record"
66
+ :resource="coreStore.resource"
67
+ :record="coreStore.record"
67
68
  />
68
69
  <template v-else>
69
70
  <td class="px-6 py-4 whitespace-nowrap">
@@ -73,12 +74,13 @@
73
74
  <component
74
75
  v-if="showComponentsPerColumn[column.name]"
75
76
  :is="showComponentsPerColumn[column.name]"
77
+ :resource="coreStore.resource"
76
78
  :column="column"
77
- :row="coreStore.record"
79
+ :record="coreStore.record"
78
80
  />
79
81
  <ValueRenderer v-else
80
82
  :column="column"
81
- :row="coreStore.record"
83
+ :record="coreStore.record"
82
84
  />
83
85
  </td>
84
86
  </template>
@@ -89,8 +91,9 @@
89
91
  <component
90
92
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
91
93
  :is="getCustomComponent(c)"
94
+ :column="column"
92
95
  :record="coreStore.record"
93
- :columns="coreStore.resourceColumns"
96
+ :resource="coreStore.resource"
94
97
  :adminUser="coreStore.adminUser"
95
98
  />
96
99
  </div>
@@ -139,14 +142,14 @@ onMounted(async () => {
139
142
  checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
140
143
 
141
144
  showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
142
- if (column.component?.show) {
143
- acc[column.name] = getCustomComponent(column.component.show);
145
+ if (column.components?.show) {
146
+ acc[column.name] = getCustomComponent(column.components.show);
144
147
  }
145
148
  return acc;
146
149
  }, {});
147
150
  showRowComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
148
- if (column.component?.showRow) {
149
- acc[column.name] = getCustomComponent(column.component.showRow);
151
+ if (column.components?.showRow) {
152
+ acc[column.name] = getCustomComponent(column.components.showRow);
150
153
  }
151
154
  return acc;
152
155
  }, {});
@@ -1,49 +1,57 @@
1
- export var AdminForthMenuType;
2
- (function (AdminForthMenuType) {
1
+ export var AdminForthMenuTypes;
2
+ (function (AdminForthMenuTypes) {
3
3
  /**
4
4
  * HEADING is just a label in the menu.
5
- * Respect `label` and `icon` property in @AdminForthConfigMenuItem
5
+ * Respect `label` and `icon` property in {@link AdminForthConfigMenuItem}
6
6
  */
7
- AdminForthMenuType["HEADING"] = "heading";
7
+ AdminForthMenuTypes["heading"] = "heading";
8
8
  /**
9
9
  * GROUP is a group of menu items.
10
- * Respects `label`, `icon` and `children` properties in @AdminForthConfigMenuItem
11
- * use @AdminForthMenuType.open to set if group is open by default
10
+ * Respects `label`, `icon` and `children` properties in {@link AdminForthConfigMenuItem}
11
+ * use @AdminForthMenuTypes.open to set if group is open by default
12
12
  */
13
- AdminForthMenuType["GROUP"] = "group";
13
+ AdminForthMenuTypes["group"] = "group";
14
14
  /**
15
15
  * RESOURCE is a link to a resource.
16
- * Respects `label`, `icon`, `resourceId`, `homepage`, `isStaticRoute` properties in @AdminForthConfigMenuItem
16
+ * Respects `label`, `icon`, `resourceId`, `homepage`, `isStaticRoute` properties in {@link AdminForthConfigMenuItem}
17
17
  */
18
- AdminForthMenuType["RESOURCE"] = "resource";
18
+ AdminForthMenuTypes["resource"] = "resource";
19
19
  /**
20
20
  * PAGE is a link to a custom page.
21
- * Respects `label`, `icon`, `path`, `component`, `homepage`, `isStaticRoute`, properties in @AdminForthConfigMenuItem
21
+ * Respects `label`, `icon`, `path`, `component`, `homepage`, `isStaticRoute`, properties in {@link AdminForthConfigMenuItem}
22
22
  *
23
23
  * Example:
24
24
  *
25
25
  * ```ts
26
- * {
27
- * type: AdminForthMenuType.PAGE,
26
+ * \{
27
+ * type: AdminForthMenuTypes.PAGE,
28
28
  * label: 'Custom Page',
29
29
  * icon: 'home',
30
30
  * path: '/dash',
31
31
  * component: '@@/Dashboard.vue',
32
32
  * homepage: true,
33
- * }
33
+ * \}
34
34
  * ```
35
35
  *
36
36
  */
37
- AdminForthMenuType["PAGE"] = "page";
37
+ AdminForthMenuTypes["page"] = "page";
38
38
  /**
39
39
  * GAP ads some space between menu items.
40
40
  */
41
- AdminForthMenuType["GAP"] = "gap";
41
+ AdminForthMenuTypes["gap"] = "gap";
42
42
  /**
43
43
  * DIVIDER is a divider between menu items.
44
44
  */
45
- AdminForthMenuType["DIVIDER"] = "divider";
46
- })(AdminForthMenuType || (AdminForthMenuType = {}));
45
+ AdminForthMenuTypes["divider"] = "divider";
46
+ })(AdminForthMenuTypes || (AdminForthMenuTypes = {}));
47
+ export var AdminForthResourcePages;
48
+ (function (AdminForthResourcePages) {
49
+ AdminForthResourcePages["list"] = "list";
50
+ AdminForthResourcePages["show"] = "show";
51
+ AdminForthResourcePages["edit"] = "edit";
52
+ AdminForthResourcePages["create"] = "create";
53
+ AdminForthResourcePages["filter"] = "filter";
54
+ })(AdminForthResourcePages || (AdminForthResourcePages = {}));
47
55
  export var AdminForthDataTypes;
48
56
  (function (AdminForthDataTypes) {
49
57
  AdminForthDataTypes["STRING"] = "string";
@@ -0,0 +1,7 @@
1
+ export var AlertVariant;
2
+ (function (AlertVariant) {
3
+ AlertVariant["Danger"] = "danger";
4
+ AlertVariant["Success"] = "success";
5
+ AlertVariant["Warning"] = "warning";
6
+ AlertVariant["Info"] = "info";
7
+ })(AlertVariant || (AlertVariant = {}));
@@ -0,0 +1,374 @@
1
+
2
+ # Installation
3
+
4
+ ## Prerequisites
5
+
6
+ We recommend using Node v18 and higher
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ mkdir myadmin
12
+ cd myadmin
13
+ npm install adminforth
14
+ ```
15
+
16
+ AdminForth does not provide own HTTP server, but can add own listeners over exisitng [Express](https://expressjs.com/) server (Fastify support is planned in future). This allows to create custom APIs for backoffice in a way you know.
17
+
18
+ Let's install express:
19
+
20
+ ```bash
21
+ npm install express@4.19.2
22
+ ```
23
+
24
+ For demo purposes we will use SQLite data source. You can use postgress, Mongo or Clickhouse as well.
25
+
26
+
27
+ ```bash
28
+ npm install better-sqlite3@10.0.0
29
+ ```
30
+
31
+ You can use adminforth in pure Node, but we recommend using TypeScript for better development experience.
32
+
33
+ ```bash
34
+ npm install typescript@5.4.5 --save-dev
35
+ npm install tsx@4.11.2 --save-dev
36
+ ```
37
+
38
+ # Philosophy
39
+
40
+ AdminForth connects to existing databases and provide a backoffice for managing data including CRUD operations, filtering, sorting, and more.
41
+
42
+ Database should be already created by using any database management tool, ORM or migrator. AdminForth does not provide a way to create tables or columns in the database.
43
+
44
+ Once you have a database, you pass a connection string to AdminForth and define resources(tables) and columns you would like to see in backoffice. For most DB AdminForth can guess column types and constraints (e.g. max-lenght) by connecting to DB. However you can redefine them in AdminForth configuration. Type and constraints definition are take precedence over DB metadata.
45
+
46
+ Also in AdminForth you can define in "Vue" way how each field will be rendered, and create own pages e.g. Dashboards.
47
+
48
+ In the demo we will create a simple database with 2 tables: `apartments` and `users`. We will just use plain SQL to create tables and insert some fake data.
49
+
50
+ Users table will be used to store a credentials for login into backoffice itself.
51
+
52
+ ## Possible configuration options
53
+
54
+ We will use schema with different column types for apartments to show many of AdminForth features.
55
+
56
+ Check [AdminForthConfig](/docs/api/type-aliases/AdminForthConfig.md) for all possible AdminForth Configs.
57
+
58
+
59
+ # Setting up a demo
60
+
61
+ Open `package.json`, set `type` to `module` and add `start` script:
62
+
63
+ ```json
64
+ {
65
+ ...
66
+ "type": "module",
67
+ "scripts": {
68
+ ...
69
+ "start": "ADMINFORTH_SECRET=CHANGE_ME_IN_PRODUCTION NODE_ENV=development tsx watch index.ts"
70
+ },
71
+ }
72
+ ```
73
+
74
+
75
+ Create `index.ts` file with following content:
76
+
77
+ ```typescript
78
+
79
+ import betterSqlite3 from 'better-sqlite3';
80
+ import express from 'express';
81
+ import AdminForth from 'adminforth';
82
+
83
+ const dbFile = 'test.sqlite';
84
+ const db = betterSqlite3(dbFile)
85
+
86
+ const tableExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='apartments';`).get();
87
+ if (!tableExists) {
88
+ await db.prepare(`
89
+ CREATE TABLE apartments (
90
+ id VARCHAR(20) PRIMARY KEY NOT NULL,
91
+ title VARCHAR(255) NOT NULL,
92
+ square_meter REAL,
93
+ price DECIMAL(10, 2) NOT NULL,
94
+ number_of_rooms INT,
95
+ description TEXT,
96
+ property_type VARCHAR(255) DEFAULT 'apartment',
97
+ listed BOOLEAN DEFAULT FALSE,
98
+ created_at TIMESTAMP,
99
+ user_id VARCHAR(255)
100
+ );`).run();
101
+
102
+ await db.prepare(`
103
+ CREATE TABLE users (
104
+ id VARCHAR(255) PRIMARY KEY NOT NULL,
105
+ email VARCHAR(255) NOT NULL,
106
+ password_hash VARCHAR(255) NOT NULL,
107
+ created_at VARCHAR(255) NOT NULL,
108
+ role VARCHAR(255) NOT NULL
109
+ );`).run();
110
+
111
+ for (let i = 0; i < 50; i++) {
112
+ await db.prepare(`
113
+ INSERT INTO apartments (
114
+ id, title, square_meter, price, number_of_rooms, description, created_at, listed, property_type
115
+ ) VALUES ('${i}', 'Apartment ${i}', ${Math.random() * 100}, ${Math.random() * 10000}, ${Math
116
+ .floor(Math.random() * 5) }, 'Next gen appartments', ${Date.now() / 1000 - i * 60 * 60 * 24}, ${i % 2 == 0}, ${i % 2 == 0 ? "'house'" : "'apartment'"});
117
+ `).run();
118
+ }
119
+ }
120
+
121
+ const ADMIN_BASE_URL = '';
122
+
123
+ const admin = new AdminForth({
124
+ baseUrl : ADMIN_BASE_URL,
125
+ rootUser: {
126
+ username: 'adminforth', // use these as credentials to login
127
+ password: 'adminforth',
128
+ },
129
+ auth: {
130
+ resourceId: 'users', // resource for getting user
131
+ usernameField: 'email',
132
+ passwordHashField: 'password_hash',
133
+ },
134
+ customization: {
135
+ brandName: 'My Admin',
136
+ datesFormat: 'D MMM YY HH:mm:ss',
137
+ emptyFieldPlaceholder: '-',
138
+ },
139
+
140
+ dataSources: [
141
+ {
142
+ id: 'maindb',
143
+ url: `sqlite://${dbFile}`
144
+ },
145
+ ],
146
+ resources: [
147
+ {
148
+ dataSource: 'maindb',
149
+ table: 'apartments',
150
+ resourceId: 'apparts', // resourceId is defaulted to table name but you can change it e.g.
151
+ // in case of same table names from different data sources
152
+ label: 'Apartments', // label is defaulted to table name but you can change it
153
+ recordLabel: (r) => `🏡 ${r.title}`,
154
+ columns: [
155
+ {
156
+ name: 'id',
157
+ label: 'Identifier', // if you wish you can redefine label
158
+ showIn: ['filter', 'show'], // show in filter and in show page
159
+ primaryKey: true,
160
+ fillOnCreate: ({initialRecord, adminUser}) => Math.random().toString(36).substring(7), // initialRecord is values user entered, adminUser object of user who creates record
161
+ },
162
+ {
163
+ name: 'title',
164
+ required: true,
165
+ showIn: ['list', 'create', 'edit', 'filter', 'show'], // the default is full set
166
+ maxLength: 255, // you can set max length for string fields
167
+ minLength: 3, // you can set min length for string fields
168
+ },
169
+ {
170
+ name: 'created_at',
171
+ type: AdminForth.Types.DATETIME ,
172
+ allowMinMaxQuery: true,
173
+ showIn: ['list', 'filter', 'show', 'edit'],
174
+ fillOnCreate: ({initialRecord, adminUser}) => (new Date()).toISOString(),
175
+ },
176
+ {
177
+ name: 'price',
178
+ min: 10,
179
+ max: 10000.12,
180
+ allowMinMaxQuery: true, // use better experience for filtering e.g. date range, set it only if you have index on this column or if there will be low number of rows
181
+ editingNote: 'Price is in USD', // you can appear note on editing or creating page
182
+ },
183
+ {
184
+ name: 'square_meter',
185
+ label: 'Square',
186
+ allowMinMaxQuery: true,
187
+ minValue: 1, // you can set min /max value for number fields
188
+ maxValue: 1000,
189
+ },
190
+ {
191
+ name: 'number_of_rooms',
192
+ allowMinMaxQuery: true,
193
+ enum: [
194
+ { value: 1, label: '1 room' },
195
+ { value: 2, label: '2 rooms' },
196
+ { value: 3, label: '3 rooms' },
197
+ { value: 4, label: '4 rooms' },
198
+ { value: 5, label: '5 rooms' },
199
+ ],
200
+ allowCustomValue: true,
201
+ },
202
+ {
203
+ name: 'description',
204
+ sortable: false,
205
+ },
206
+ {
207
+ name: 'property_type',
208
+ enum: [{
209
+ value: 'house',
210
+ label: 'House'
211
+ }, {
212
+ value: 'apartment',
213
+ label: 'Apartment'
214
+ }, {
215
+ value: null,
216
+ label: 'Not defined'
217
+ }],
218
+ },
219
+ {
220
+ name: 'listed',
221
+ required: true, // will be required on create/edit
222
+ },
223
+ {
224
+ name: 'user_id',
225
+ foreignResource: {
226
+ resourceId: 'users',
227
+ }
228
+ }
229
+ ],
230
+ options: {
231
+ listPageSize: 12,
232
+ allowedActions:{
233
+ edit: false,
234
+ delete: true,
235
+ show: true,
236
+ filter: true,
237
+ },
238
+ },
239
+ },
240
+ {
241
+ dataSource: 'maindb',
242
+ table: 'users',
243
+ resourceId: 'users',
244
+ label: 'Users',
245
+ recordLabel: (r) => `👤 ${r.email}`,
246
+ columns: [
247
+ {
248
+ name: 'id',
249
+ primaryKey: true,
250
+ fillOnCreate: ({initialRecord, adminUser}) => Math.random().toString(36).substring(7),
251
+ showIn: ['list', 'filter', 'show'],
252
+ },
253
+ {
254
+ name: 'email',
255
+ required: true,
256
+ isUnique: true,
257
+ validation: [
258
+ {
259
+ regExp: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
260
+ message: 'Email is not valid, must be in format example@test.com'
261
+ },
262
+ ]
263
+ },
264
+ {
265
+ name: 'created_at',
266
+ type: AdminForth.Types.DATETIME,
267
+ showIn: ['list', 'filter', 'show'],
268
+ fillOnCreate: ({initialRecord, adminUser}) => (new Date()).toISOString(),
269
+ },
270
+ {
271
+ name: 'role',
272
+ enum: [
273
+ { value: 'superadmin', label: 'Super Admin' },
274
+ { value: 'user', label: 'User' },
275
+ ]
276
+ },
277
+ {
278
+ name: 'password',
279
+ virtual: true, // field will not be persisted into db
280
+ required: { create: true }, // make required only on create page
281
+ editingNote: { edit: 'Leave empty to keep password unchanged' },
282
+ minLength: 8,
283
+ type: AdminForth.Types.STRING,
284
+ showIn: ['create', 'edit'], // to show field only on create and edit pages
285
+ masked: true, // to show stars in input field
286
+ }
287
+ ],
288
+ hooks: {
289
+ create: {
290
+ beforeSave: async ({ record, adminUser, resource }) => {
291
+ record.password_hash = await AdminForth.Utils.generatePasswordHash(record.password);
292
+ return { ok:true, error: false };
293
+ }
294
+ },
295
+ edit: {
296
+ beforeSave: async ({ record, adminUser, resource}) => {
297
+ if (record.password) {
298
+ record.password_hash = await AdminForth.Utils.generatePasswordHash(record.password);
299
+ }
300
+ return { ok: true, error: false }
301
+ },
302
+ },
303
+ }
304
+ },
305
+ ],
306
+ menu: [
307
+ {
308
+ label: 'Core',
309
+ icon: 'flowbite:brain-solid', // any icon from iconify supported in format <setname>:<icon>, e.g. from here https://icon-sets.iconify.design/flowbite/
310
+ open: true,
311
+ children: [
312
+ {
313
+ homepage: true,
314
+ label: 'Appartments',
315
+ icon: 'flowbite:home-solid',
316
+ resourceId: 'apparts',
317
+ },
318
+ ]
319
+ },
320
+ {
321
+ type: 'gap'
322
+ },
323
+ {
324
+ type: 'divider'
325
+ },
326
+ {
327
+ type: 'heading',
328
+ label: 'SYSTEM',
329
+ },
330
+ {
331
+ label: 'Users',
332
+ icon: 'flowbite:user-solid',
333
+ resourceId: 'users',
334
+ }
335
+ ],
336
+ })
337
+
338
+
339
+ const app = express()
340
+ app.use(express.json());
341
+ const port = 3500;
342
+
343
+ (async () => {
344
+ // needed to compile SPA. Call it here or from a build script e.g. in Docker build time to reduce downtime
345
+ await admin.bundleNow({ hotReload: process.env.NODE_ENV === 'development'});
346
+ console.log('Bundling AdminForth done. For faster serving consider calling bundleNow() from a build script.');
347
+ })();
348
+
349
+
350
+ // serve after you added all api
351
+ admin.express.serve(app, express)
352
+ admin.discoverDatabases();
353
+
354
+
355
+ app.listen(port, () => {
356
+ console.log(`Example app listening at http://localhost:${port}`)
357
+ console.log(`\n⚡ AdminForth is available at http://localhost:${port}${ADMIN_BASE_URL}\n`)
358
+ });
359
+ ```
360
+
361
+
362
+ Now you can run your app:
363
+
364
+ ```bash
365
+ npm start
366
+ ```
367
+
368
+ Open http://localhost:3500 in your browser and login with credentials `adminforth` / `adminforth`.
369
+
370
+ ![alt text](image.png)
371
+
372
+
373
+ After Login you should see:
374
+
@@ -0,0 +1,37 @@
1
+
2
+ # dataSource
3
+
4
+ A DataSource is a connection to one database. Datasources has id for references from resources and URL which follows the standard URI format. For example `mysql://user:password@localhost:3306/database`.
5
+ It used to:
6
+
7
+ * Discover the columns in the database
8
+ * Make queries to get the list and show records
9
+ * Make queries to modify data
10
+
11
+ There might be several datasources in the system for vairous databases e.g. One 2 Mongo DBs and 1 Postgres DB.
12
+
13
+ # resource
14
+
15
+ A Resource is a representation of a table or collection in AdminForth. One resource is one table in the database.
16
+ It has a `name` which should match name in database, a datasource id, and a list of columns.
17
+ Also it has various customization options.
18
+
19
+ # column
20
+
21
+ A Column is a representation of a column in a table. It has a `name` which should be equal to name in database and various configuration options.
22
+
23
+ # record
24
+
25
+ A record is a row in a relational database table. Or Document in document database table.
26
+
27
+ # adminUser
28
+
29
+ Object which represents a user who logged in to the AdminForth
30
+
31
+ # hook
32
+
33
+ Hook is a optional async function which allows to inject in backend logic before exuting the datasource query or after it
34
+
35
+ # component
36
+
37
+ Component is a Vue component which is used to add or modify UI elements in AdminForth.
Binary file
@@ -58,7 +58,7 @@ const config: Config = {
58
58
  [
59
59
  "docusaurus-plugin-typedoc",
60
60
  {
61
- entryPoints: ["../types/AdminForthConfig.ts"],
61
+ entryPoints: ["../types/AdminForthConfig.ts", "../types/FrontendAPI.ts"],
62
62
  plugin: ["./typedoc-plugin.mjs"],
63
63
  readme: "none",
64
64
  indexFormat: "table",