adminforth 1.3.12 → 1.3.14

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.
@@ -59,7 +59,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
59
59
 
60
60
  async createRecord({ resource, record, adminUser }: {
61
61
  resource: AdminForthResource; record: any; adminUser: any;
62
- }): Promise<any> {
62
+ }): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
63
63
  // transform value using setFieldValue and call createRecordOriginalValues
64
64
  const filledRecord = {...record};
65
65
  const recordWithOriginalValues = {...record};
@@ -75,10 +75,40 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
75
75
  }
76
76
  recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
77
77
  }
78
+
79
+ async function checkUnique(column: AdminForthResourceColumn, value: any) {
80
+ const existingRecord = await this.getData({
81
+ resource,
82
+ filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }],
83
+ limit: 1,
84
+ sort: [],
85
+ offset: 0,
86
+ getTotals: false
87
+ });
88
+ return existingRecord.data.length > 0;
89
+ }
90
+ let error: string | null = null;
91
+ await Promise.race(
92
+ resource.dataSourceColumns.map(async (col) => {
93
+ if (col.isUnique && !col.virtual && !error) {
94
+ const exists = await checkUnique(col, recordWithOriginalValues[col.name]);
95
+ if (exists) {
96
+ error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
97
+ }
98
+ }
99
+ })
100
+ );
101
+ if (error) {
102
+ return { error, ok: false };
103
+ }
104
+
78
105
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', recordWithOriginalValues);
79
106
  await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
80
107
 
81
- return recordWithOriginalValues;
108
+ return {
109
+ ok: true,
110
+ createdRecord: recordWithOriginalValues,
111
+ }
82
112
  }
83
113
 
84
114
  updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
@@ -66,9 +66,37 @@ export default class AdminForthBaseConnector {
66
66
  }
67
67
  recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
68
68
  }
69
+ function checkUnique(column, value) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ const existingRecord = yield this.getData({
72
+ resource,
73
+ filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }],
74
+ limit: 1,
75
+ sort: [],
76
+ offset: 0,
77
+ getTotals: false
78
+ });
79
+ return existingRecord.data.length > 0;
80
+ });
81
+ }
82
+ let error = null;
83
+ yield Promise.race(resource.dataSourceColumns.map((col) => __awaiter(this, void 0, void 0, function* () {
84
+ if (col.isUnique && !col.virtual && !error) {
85
+ const exists = yield checkUnique(col, recordWithOriginalValues[col.name]);
86
+ if (exists) {
87
+ error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
88
+ }
89
+ }
90
+ })));
91
+ if (error) {
92
+ return { error, ok: false };
93
+ }
69
94
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', recordWithOriginalValues);
70
95
  yield this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
71
- return recordWithOriginalValues;
96
+ return {
97
+ ok: true,
98
+ createdRecord: recordWithOriginalValues,
99
+ };
72
100
  });
73
101
  }
74
102
  updateRecord({ resource, recordId, newValues }) {
package/dist/index.js CHANGED
@@ -159,22 +159,11 @@ class AdminForth {
159
159
  return __awaiter(this, arguments, void 0, function* ({ resource, record, adminUser }) {
160
160
  var _c, _d, _e, _f, _g;
161
161
  for (const column of resource.columns) {
162
+ // TODO: assuming specifity for AdminForthResourcePages.create better to move it to api for this button
162
163
  if (((_c = column.required) === null || _c === void 0 ? void 0 : _c.create) &&
163
164
  record[column.name] === undefined &&
164
165
  column.showIn.includes(AdminForthResourcePages.create)) {
165
- return { error: `Column '${column.name}' is required` };
166
- }
167
- if (column.isUnique) {
168
- const existingRecord = yield this.connectors[resource.dataSource].getData({
169
- resource,
170
- filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value: record[column.name] }],
171
- limit: 1,
172
- sort: [],
173
- offset: 0
174
- });
175
- if (existingRecord.data.length > 0) {
176
- return { error: `Record with ${column.name} ${record[column.name]} already exists` };
177
- }
166
+ return { error: `Column '${column.name}' is required`, ok: false };
178
167
  }
179
168
  }
180
169
  // execute hook if needed
@@ -184,7 +173,7 @@ class AdminForth {
184
173
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
185
174
  }
186
175
  if (resp.error) {
187
- return { error: resp.error };
176
+ return { error: resp.error, ok: false };
188
177
  }
189
178
  }
190
179
  // remove virtual columns from record
@@ -195,20 +184,25 @@ class AdminForth {
195
184
  }
196
185
  const connector = this.connectors[resource.dataSource];
197
186
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record createResourceRecord', record);
198
- yield connector.createRecord({ resource, record, adminUser });
187
+ const { ok, error, createdRecord } = yield connector.createRecord({ resource, record, adminUser });
199
188
  const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
200
189
  // execute hook if needed
201
190
  for (const hook of listify((_g = (_f = resource.hooks) === null || _f === void 0 ? void 0 : _f.create) === null || _g === void 0 ? void 0 : _g.afterSave)) {
202
191
  console.log('Hook afterSave', hook);
203
- const resp = yield hook({ recordId: primaryKey, resource, record, adminUser });
192
+ const resp = yield hook({
193
+ recordId: primaryKey,
194
+ resource,
195
+ record: createdRecord,
196
+ adminUser
197
+ });
204
198
  if (!resp || (!resp.ok && !resp.error)) {
205
199
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
206
200
  }
207
201
  if (resp.error) {
208
- return { error: resp.error };
202
+ return { error: resp.error, ok: false };
209
203
  }
210
204
  }
211
- return { ok: true };
205
+ return { ok, error, createdRecord };
212
206
  });
213
207
  }
214
208
  resource(resourceId) {
@@ -122,6 +122,9 @@ export default class ConfigValidator {
122
122
  if (this.config.customization.brandLogo) {
123
123
  errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
124
124
  }
125
+ if (this.config.customization.showBrandNameInSidebar === undefined) {
126
+ this.config.customization.showBrandNameInSidebar = true;
127
+ }
125
128
  if (this.config.customization.favicon) {
126
129
  errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
127
130
  }
@@ -58,9 +58,14 @@ export default class OperationalResource {
58
58
  });
59
59
  });
60
60
  }
61
- create(record) {
61
+ create(recordValues) {
62
62
  return __awaiter(this, void 0, void 0, function* () {
63
- return yield this.dataConnector.createRecord({ resource: this.resourceConfig, record, adminUser: null });
63
+ const { ok, createdRecord, error } = yield this.dataConnector.createRecord({
64
+ resource: this.resourceConfig,
65
+ record: recordValues,
66
+ adminUser: null
67
+ });
68
+ return { ok, createdRecord, error };
64
69
  });
65
70
  }
66
71
  update(primaryKey, record) {
@@ -135,6 +135,7 @@ export default class AdminForthRestAPI {
135
135
  brandName: this.adminforth.config.customization.brandName,
136
136
  usernameFieldName: usernameColumn.label,
137
137
  loginBackgroundImage: this.adminforth.config.auth.loginBackgroundImage,
138
+ loginBackgroundPosition: this.adminforth.config.auth.loginBackgroundPosition,
138
139
  title: (_j = this.adminforth.config.customization) === null || _j === void 0 ? void 0 : _j.title,
139
140
  demoCredentials: this.adminforth.config.auth.demoCredentials,
140
141
  loginPromptHTML: this.adminforth.config.auth.loginPromptHTML,
@@ -208,6 +209,7 @@ export default class AdminForthRestAPI {
208
209
  menu: newMenu,
209
210
  config: {
210
211
  brandName: this.adminforth.config.customization.brandName,
212
+ showBrandNameInSidebar: this.adminforth.config.customization.showBrandNameInSidebar,
211
213
  brandLogo: this.adminforth.config.customization.brandLogo,
212
214
  datesFormat: this.adminforth.config.customization.datesFormat,
213
215
  deleteConfirmation: this.adminforth.config.deleteConfirmation,
@@ -486,11 +488,12 @@ export default class AdminForthRestAPI {
486
488
  const { record } = body;
487
489
  const response = yield this.adminforth.createResourceRecord({ resource, record, adminUser });
488
490
  if (response.error) {
489
- return { error: response.error };
491
+ return { error: response.error, ok: false };
490
492
  }
491
493
  const connector = this.adminforth.connectors[resource.dataSource];
492
494
  return {
493
- newRecordId: record[connector.getPrimaryKey(resource)]
495
+ newRecordId: response.createdRecord[connector.getPrimaryKey(resource)],
496
+ ok: true
494
497
  };
495
498
  })
496
499
  });
package/index.ts CHANGED
@@ -23,6 +23,7 @@ import ConfigValidator from './modules/configValidator.js';
23
23
  import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
24
24
  import ClickhouseConnector from './dataConnectors/clickhouse.js';
25
25
  import OperationalResource from './modules/operationalResource.js';
26
+ import { error } from 'console';
26
27
 
27
28
  // exports
28
29
  export * from './types/AdminForthConfig.js';
@@ -198,28 +199,20 @@ class AdminForth implements IAdminForth {
198
199
  return users.data[0] || null;
199
200
  }
200
201
 
201
- async createResourceRecord({ resource, record, adminUser }: { resource: AdminForthResource, record: any, adminUser: AdminUser }) {
202
+ async createResourceRecord(
203
+ { resource, record, adminUser }:
204
+ { resource: AdminForthResource, record: any, adminUser: AdminUser }
205
+ ): Promise<{ ok: boolean, error?: string, createdRecord?: any }> {
206
+
202
207
  for (const column of resource.columns) {
203
- if (
204
- (column.required as {create?: boolean, edit?: boolean}) ?.create &&
205
- record[column.name] === undefined &&
206
- column.showIn.includes(AdminForthResourcePages.create)
207
- ) {
208
- return { error: `Column '${column.name}' is required` };
209
- }
210
-
211
- if (column.isUnique) {
212
- const existingRecord = await this.connectors[resource.dataSource].getData({
213
- resource,
214
- filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value: record[column.name] }],
215
- limit: 1,
216
- sort: [],
217
- offset: 0
218
- });
219
- if (existingRecord.data.length > 0) {
220
- return { error: `Record with ${column.name} ${record[column.name]} already exists` };
221
- }
222
- }
208
+ // TODO: assuming specifity for AdminForthResourcePages.create better to move it to api for this button
209
+ if (
210
+ (column.required as {create?: boolean, edit?: boolean}) ?.create &&
211
+ record[column.name] === undefined &&
212
+ column.showIn.includes(AdminForthResourcePages.create)
213
+ ) {
214
+ return { error: `Column '${column.name}' is required`, ok: false };
215
+ }
223
216
  }
224
217
 
225
218
  // execute hook if needed
@@ -230,7 +223,7 @@ class AdminForth implements IAdminForth {
230
223
  }
231
224
 
232
225
  if (resp.error) {
233
- return { error: resp.error };
226
+ return { error: resp.error, ok: false };
234
227
  }
235
228
  }
236
229
 
@@ -242,24 +235,30 @@ class AdminForth implements IAdminForth {
242
235
  }
243
236
  const connector = this.connectors[resource.dataSource];
244
237
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record createResourceRecord', record);
245
- await connector.createRecord({ resource, record, adminUser });
238
+ const { ok, error, createdRecord } = await connector.createRecord({ resource, record, adminUser });
246
239
 
247
240
  const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
248
241
 
249
242
  // execute hook if needed
250
243
  for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
251
244
  console.log('Hook afterSave', hook);
252
- const resp = await hook({ recordId: primaryKey, resource, record, adminUser });
245
+ const resp = await hook({
246
+ recordId: primaryKey,
247
+ resource,
248
+ record: createdRecord,
249
+ adminUser
250
+ });
251
+
253
252
  if (!resp || (!resp.ok && !resp.error)) {
254
253
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
255
254
  }
256
255
 
257
256
  if (resp.error) {
258
- return { error: resp.error };
257
+ return { error: resp.error, ok: false };
259
258
  }
260
259
  }
261
260
 
262
- return { ok: true };
261
+ return { ok, error, createdRecord };
263
262
  }
264
263
 
265
264
  resource(resourceId: string) {
@@ -130,7 +130,9 @@ export default class ConfigValidator implements IConfigValidator {
130
130
  if (this.config.customization.brandLogo) {
131
131
  errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
132
132
  }
133
-
133
+ if (this.config.customization.showBrandNameInSidebar === undefined) {
134
+ this.config.customization.showBrandNameInSidebar = true;
135
+ }
134
136
  if (this.config.customization.favicon) {
135
137
  errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
136
138
  }
@@ -1,3 +1,4 @@
1
+ import { error } from 'console';
1
2
  import { IAdminForthFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource, IAdminForth } from '../types/AdminForthConfig.js';
2
3
 
3
4
 
@@ -63,8 +64,13 @@ export default class OperationalResource implements IOperationalResource {
63
64
  });
64
65
  }
65
66
 
66
- async create(record: any): Promise<any> {
67
- return await this.dataConnector.createRecord({ resource: this.resourceConfig, record, adminUser: null });
67
+ async create(recordValues: any): Promise<{ ok: boolean; createdRecord: any; error?: string; }> {
68
+ const { ok, createdRecord, error } = await this.dataConnector.createRecord({
69
+ resource: this.resourceConfig,
70
+ record: recordValues,
71
+ adminUser: null
72
+ });
73
+ return { ok, createdRecord, error };
68
74
  }
69
75
 
70
76
  async update(primaryKey: any, record: any): Promise<any> {
@@ -166,6 +166,7 @@ export default class AdminForthRestAPI {
166
166
  brandName: this.adminforth.config.customization.brandName,
167
167
  usernameFieldName: usernameColumn.label,
168
168
  loginBackgroundImage: this.adminforth.config.auth.loginBackgroundImage,
169
+ loginBackgroundPosition: this.adminforth.config.auth.loginBackgroundPosition,
169
170
  title: this.adminforth.config.customization?.title,
170
171
  demoCredentials: this.adminforth.config.auth.demoCredentials,
171
172
  loginPromptHTML: this.adminforth.config.auth.loginPromptHTML,
@@ -244,6 +245,7 @@ export default class AdminForthRestAPI {
244
245
  menu: newMenu,
245
246
  config: {
246
247
  brandName: this.adminforth.config.customization.brandName,
248
+ showBrandNameInSidebar: this.adminforth.config.customization.showBrandNameInSidebar,
247
249
  brandLogo: this.adminforth.config.customization.brandLogo,
248
250
  datesFormat: this.adminforth.config.customization.datesFormat,
249
251
  deleteConfirmation: this.adminforth.config.deleteConfirmation,
@@ -568,12 +570,13 @@ export default class AdminForthRestAPI {
568
570
 
569
571
  const response = await this.adminforth.createResourceRecord({ resource, record, adminUser });
570
572
  if (response.error) {
571
- return { error: response.error };
573
+ return { error: response.error, ok: false };
572
574
  }
573
575
  const connector = this.adminforth.connectors[resource.dataSource];
574
576
 
575
577
  return {
576
- newRecordId: record[connector.getPrimaryKey(resource)]
578
+ newRecordId: response.createdRecord[connector.getPrimaryKey(resource)],
579
+ ok: true
577
580
  }
578
581
  }
579
582
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/spa/src/App.vue CHANGED
@@ -68,7 +68,10 @@
68
68
  <div class="h-full px-3 pb-4 overflow-y-auto bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder">
69
69
  <div class="flex ms-2 md:me-24 m-4 ">
70
70
  <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
71
- <span class="self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText">
71
+ <span
72
+ v-if="coreStore.config?.showBrandNameInSidebar"
73
+ class="self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
74
+ >
72
75
  {{ coreStore.config?.brandName }}
73
76
  </span>
74
77
  </div>
@@ -70,7 +70,7 @@
70
70
  </td>
71
71
  </tr>
72
72
 
73
- <tr @click="onClick($event,row)" v-else v-for="(row, rowI) in rows" :key="row.id"
73
+ <tr @click="onClick($event,row)" v-else v-for="(row, rowI) in rows" :key="`row_${row._primaryKeyValue}`"
74
74
  class="bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover cursor-pointer"
75
75
  :class="{'border-b': rowI !== rows.length - 1}"
76
76
  >
@@ -80,8 +80,8 @@
80
80
  @click="(e)=>{e.stopPropagation()}"
81
81
  id="checkbox-table-search-1"
82
82
  type="checkbox"
83
- :checked="checkboxesInternal.includes(row.id)"
84
- @change="(e)=>{addToCheckedValues(row.id)}"
83
+ :checked="checkboxesInternal.includes(row._primaryKeyValue)"
84
+ @change="(e)=>{addToCheckedValues(row._primaryKeyValue)}"
85
85
  class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer">
86
86
  <label for="checkbox-table-search-1" class="sr-only">checkbox</label>
87
87
  </div>
@@ -299,6 +299,7 @@ watch(() => props.sort, (newSort) => {
299
299
  });
300
300
 
301
301
  function addToCheckedValues(id) {
302
+ console.log('checking', checkboxesInternal.value, 'id', id)
302
303
  if (checkboxesInternal.value.includes(id)) {
303
304
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
304
305
  } else {
@@ -312,13 +313,13 @@ const columnsListed = computed(() => props.resource?.columns?.filter(c => c.show
312
313
  async function selectAll(value) {
313
314
  if (!allFromThisPageChecked.value) {
314
315
  props.rows.forEach((r) => {
315
- if (!checkboxesInternal.value.includes(r.id)) {
316
- checkboxesInternal.value.push(r.id)
316
+ if (!checkboxesInternal.value.includes(r._primaryKeyValue)) {
317
+ checkboxesInternal.value.push(r._primaryKeyValue)
317
318
  }
318
319
  });
319
320
  } else {
320
321
  props.rows.forEach((r) => {
321
- checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r.id);
322
+ checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r._primaryKeyValue);
322
323
  });
323
324
  }
324
325
  checkboxesInternal.value = [ ...checkboxesInternal.value ];
@@ -328,7 +329,7 @@ const totalPages = computed(() => Math.ceil(props.totalRows / props.pageSize));
328
329
 
329
330
  const allFromThisPageChecked = computed(() => {
330
331
  if (!props.rows) return false;
331
- return props.rows.every((r) => checkboxesInternal.value.includes(r.id));
332
+ return props.rows.every((r) => checkboxesInternal.value.includes(r._primaryKeyValue));
332
333
  });
333
334
  const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
334
335
  const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));
@@ -32,6 +32,7 @@ export type CoreConfig = {
32
32
  usernameField: string,
33
33
  passwordHashField: string,
34
34
  loginBackgroundImage: string,
35
+ loginBackgroundPosition: string,
35
36
  userFullnameField: string,
36
37
  },
37
38
  emptyFieldPlaceholder?: {
@@ -1,24 +1,38 @@
1
1
  <template>
2
- <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
3
- :style="coreStore.config?.loginBackgroundImage ? {
2
+ <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800 relative w-screen h-screen"
3
+ :style="coreStore.config?.loginBackgroundImage && backgroundPosition === 'over' ? {
4
4
  'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
6
6
  'background-position': 'center',
7
7
  'background-blend-mode': 'darken'
8
8
  }: {}"
9
- >
9
+ >
10
+
11
+ <img v-if="coreStore.config?.loginBackgroundImage && backgroundPosition !== 'over'"
12
+ :src="loadFile(coreStore.config?.loginBackgroundImage)"
13
+ class="position-absolute top-0 left-0 h-screen object-cover w-0"
14
+ :class="{
15
+ '1/2': 'md:w-1/2',
16
+ '1/3': 'md:w-1/3',
17
+ '2/3': 'md:w-2/3',
18
+ '3/4': 'md:w-3/4',
19
+ '2/5': 'md:w-2/5',
20
+ '3/5': 'md:w-3/5',
21
+ }[backgroundPosition]"
22
+ />
10
23
 
11
24
  <!-- Main modal -->
12
- <div id="authentication-modal" tabindex="-1" class=" overflow-y-auto overflow-x-hidden z-50 min-w-[400px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
13
- <div class="relative p-4 w-full max-w-md max-h-full">
25
+ <div id="authentication-modal" tabindex="-1"
26
+ class="overflow-y-auto flex flex-grow
27
+ overflow-x-hidden z-50 min-w-[350px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
28
+ <div class="relative p-4 w-full max-h-full max-w-[400px]">
14
29
  <!-- Modal content -->
15
30
  <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
16
31
  <!-- Modal header -->
17
32
  <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
18
33
  <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
19
- Sign in to {{ coreStore.config?.brandName }}
34
+ Sign in to {{ coreStore.config?.brandName }}
20
35
  </h3>
21
-
22
36
  </div>
23
37
  <!-- Modal body -->
24
38
  <div class="p-4 md:p-5">
@@ -87,7 +101,7 @@
87
101
 
88
102
  <script setup>
89
103
 
90
- import { onMounted, ref, watchEffect } from 'vue';
104
+ import { onMounted, ref, computed } from 'vue';
91
105
  import { useCoreStore } from '@/stores/core';
92
106
  import { useUserStore } from '@/stores/user';
93
107
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
@@ -103,11 +117,14 @@ const inProgress = ref(false);
103
117
  const coreStore = useCoreStore();
104
118
  const user = useUserStore();
105
119
 
106
-
107
120
  const showPw = ref(false);
108
121
 
109
122
  const error = ref(null);
110
123
 
124
+ const backgroundPosition = computed(() => {
125
+ return coreStore.config?.loginBackgroundPosition || '1/2';
126
+ });
127
+
111
128
  onMounted(async () => {
112
129
  await coreStore.getPublicConfig();
113
130
  if (coreStore.config?.demoCredentials) {
@@ -226,7 +226,7 @@ export interface IAdminForthDataSourceConnectorBase extends IAdminForthDataSourc
226
226
  resource: AdminForthResource,
227
227
  record: any
228
228
  adminUser: AdminUser
229
- }): Promise<void>;
229
+ }): Promise<{ok: boolean, error?: string, createdRecord?: any}>;
230
230
 
231
231
  getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }>;
232
232
  }
@@ -264,7 +264,9 @@ export interface IAdminForth {
264
264
  [key: string]: IAdminForthDataSourceConnectorBase;
265
265
  };
266
266
 
267
- createResourceRecord(params: { resource: AdminForthResource, record: any, adminUser: AdminUser }): Promise<any>;
267
+ createResourceRecord(
268
+ params: { resource: AdminForthResource, record: any, adminUser: AdminUser }
269
+ ): Promise<{ ok: boolean, error?: string, createdRecord?: any }>;
268
270
 
269
271
  auth: IAdminForthAuth;
270
272
 
@@ -1130,6 +1132,16 @@ export type AdminForthConfig = {
1130
1132
  */
1131
1133
  loginBackgroundImage?: string,
1132
1134
 
1135
+
1136
+ /**
1137
+ * Position of background image on login page
1138
+ * 'over' - image will be displayed over full login page under login form
1139
+ * '1/2' - image will be displayed on left 1/2 of login page
1140
+ *
1141
+ * Default: '1/2'
1142
+ */
1143
+ loginBackgroundPosition?: 'over' | '1/2' | '1/3' | '2/3' | '3/4' | '2/5' | '3/5',
1144
+
1133
1145
  /**
1134
1146
  * Function or functions which will be called before user try to login.
1135
1147
  * Each function will resive User object as an argument
@@ -1193,6 +1205,13 @@ export type AdminForthConfig = {
1193
1205
  */
1194
1206
  brandName?: string,
1195
1207
 
1208
+
1209
+ /**
1210
+ * Whether to show brand name in sidebar
1211
+ * default is true
1212
+ */
1213
+ showBrandNameInSidebar?: boolean,
1214
+
1196
1215
  /**
1197
1216
  * Path to your app logo
1198
1217
  *
@@ -1379,7 +1398,7 @@ export interface IOperationalResource {
1379
1398
 
1380
1399
  count: (filter: IAdminForthFilter | IAdminForthFilter[]) => Promise<number>;
1381
1400
 
1382
- create: (record: any) => Promise<any>;
1401
+ create: (record: any) => Promise<{ ok: boolean; createdRecord: any; error?: string; }>;
1383
1402
 
1384
1403
  update: (primaryKey: any, record: any) => Promise<any>;
1385
1404