adminforth 1.3.13 → 1.3.15

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.
@@ -57,9 +57,24 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
57
57
  throw new Error('Method not implemented.');
58
58
  }
59
59
 
60
+ async checkUnique(resource: AdminForthResource, column: AdminForthResourceColumn, value: any) {
61
+ process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 checkUnique|||', column, value);
62
+ const existingRecord = await this.getData({
63
+ resource,
64
+ filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }],
65
+ limit: 1,
66
+ sort: [],
67
+ offset: 0,
68
+ getTotals: false
69
+ });
70
+ process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 existingRecord|||', existingRecord);
71
+
72
+ return existingRecord.data.length > 0;
73
+ }
74
+
60
75
  async createRecord({ resource, record, adminUser }: {
61
76
  resource: AdminForthResource; record: any; adminUser: any;
62
- }): Promise<any> {
77
+ }): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
63
78
  // transform value using setFieldValue and call createRecordOriginalValues
64
79
  const filledRecord = {...record};
65
80
  const recordWithOriginalValues = {...record};
@@ -75,10 +90,33 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
75
90
  }
76
91
  recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
77
92
  }
93
+
94
+
95
+ let error: string | null = null;
96
+ await Promise.all(
97
+ resource.dataSourceColumns.map(async (col) => {
98
+
99
+ if (col.isUnique && !col.virtual && !error) {
100
+
101
+ const exists = await this.checkUnique(resource, col, recordWithOriginalValues[col.name]);
102
+ if (exists) {
103
+ error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
104
+ }
105
+ }
106
+ })
107
+ );
108
+ process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 error', error);
109
+ if (error) {
110
+ return { error, ok: false };
111
+ }
112
+
78
113
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', recordWithOriginalValues);
79
114
  await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
80
115
 
81
- return recordWithOriginalValues;
116
+ return {
117
+ ok: true,
118
+ createdRecord: recordWithOriginalValues,
119
+ }
82
120
  }
83
121
 
84
122
  updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
@@ -50,6 +50,21 @@ export default class AdminForthBaseConnector {
50
50
  createRecordOriginalValues({ resource, record }) {
51
51
  throw new Error('Method not implemented.');
52
52
  }
53
+ checkUnique(resource, column, value) {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 checkUnique|||', column, value);
56
+ const existingRecord = yield this.getData({
57
+ resource,
58
+ filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }],
59
+ limit: 1,
60
+ sort: [],
61
+ offset: 0,
62
+ getTotals: false
63
+ });
64
+ process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 existingRecord|||', existingRecord);
65
+ return existingRecord.data.length > 0;
66
+ });
67
+ }
53
68
  createRecord(_a) {
54
69
  return __awaiter(this, arguments, void 0, function* ({ resource, record, adminUser }) {
55
70
  // transform value using setFieldValue and call createRecordOriginalValues
@@ -66,9 +81,25 @@ export default class AdminForthBaseConnector {
66
81
  }
67
82
  recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
68
83
  }
84
+ let error = null;
85
+ yield Promise.all(resource.dataSourceColumns.map((col) => __awaiter(this, void 0, void 0, function* () {
86
+ if (col.isUnique && !col.virtual && !error) {
87
+ const exists = yield this.checkUnique(resource, col, recordWithOriginalValues[col.name]);
88
+ if (exists) {
89
+ error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
90
+ }
91
+ }
92
+ })));
93
+ process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 error', error);
94
+ if (error) {
95
+ return { error, ok: false };
96
+ }
69
97
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', recordWithOriginalValues);
70
98
  yield this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
71
- return recordWithOriginalValues;
99
+ return {
100
+ ok: true,
101
+ createdRecord: recordWithOriginalValues,
102
+ };
72
103
  });
73
104
  }
74
105
  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,28 @@ 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 });
188
+ if (!ok) {
189
+ return { ok, error };
190
+ }
199
191
  const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
200
192
  // execute hook if needed
201
193
  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
194
  console.log('Hook afterSave', hook);
203
- const resp = yield hook({ recordId: primaryKey, resource, record, adminUser });
195
+ const resp = yield hook({
196
+ recordId: primaryKey,
197
+ resource,
198
+ record: createdRecord,
199
+ adminUser
200
+ });
204
201
  if (!resp || (!resp.ok && !resp.error)) {
205
202
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
206
203
  }
207
204
  if (resp.error) {
208
- return { error: resp.error };
205
+ return { error: resp.error, ok: false };
209
206
  }
210
207
  }
211
- return { ok: true };
208
+ return { ok, error, createdRecord };
212
209
  });
213
210
  }
214
211
  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,
@@ -317,6 +319,7 @@ export default class AdminForthRestAPI {
317
319
  offset,
318
320
  filters,
319
321
  sort,
322
+ getTotals: true,
320
323
  });
321
324
  // for foreign keys, add references
322
325
  yield Promise.all(resource.columns.filter((col) => col.foreignResource).map((col) => __awaiter(this, void 0, void 0, function* () {
@@ -486,11 +489,12 @@ export default class AdminForthRestAPI {
486
489
  const { record } = body;
487
490
  const response = yield this.adminforth.createResourceRecord({ resource, record, adminUser });
488
491
  if (response.error) {
489
- return { error: response.error };
492
+ return { error: response.error, ok: false };
490
493
  }
491
494
  const connector = this.adminforth.connectors[resource.dataSource];
492
495
  return {
493
- newRecordId: record[connector.getPrimaryKey(resource)]
496
+ newRecordId: response.createdRecord[connector.getPrimaryKey(resource)],
497
+ ok: true
494
498
  };
495
499
  })
496
500
  });
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,33 @@ 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 });
246
-
238
+ const { ok, error, createdRecord } = await connector.createRecord({ resource, record, adminUser });
239
+ if (!ok) {
240
+ return { ok, error };
241
+ }
242
+
247
243
  const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
248
244
 
249
245
  // execute hook if needed
250
246
  for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
251
247
  console.log('Hook afterSave', hook);
252
- const resp = await hook({ recordId: primaryKey, resource, record, adminUser });
248
+ const resp = await hook({
249
+ recordId: primaryKey,
250
+ resource,
251
+ record: createdRecord,
252
+ adminUser
253
+ });
254
+
253
255
  if (!resp || (!resp.ok && !resp.error)) {
254
256
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
255
257
  }
256
258
 
257
259
  if (resp.error) {
258
- return { error: resp.error };
260
+ return { error: resp.error, ok: false };
259
261
  }
260
262
  }
261
263
 
262
- return { ok: true };
264
+ return { ok, error, createdRecord };
263
265
  }
264
266
 
265
267
  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,
@@ -259,8 +261,6 @@ export default class AdminForthRestAPI {
259
261
  },
260
262
  });
261
263
 
262
-
263
-
264
264
  function checkAccess(action: AllowedActionsEnum, allowedActions: AllowedActions): { allowed: boolean, error?: string } {
265
265
  const allowed = (allowedActions[action] as boolean | string | undefined);
266
266
  if (allowed !== true) {
@@ -381,6 +381,7 @@ export default class AdminForthRestAPI {
381
381
  offset,
382
382
  filters,
383
383
  sort,
384
+ getTotals: true,
384
385
  });
385
386
  // for foreign keys, add references
386
387
  await Promise.all(
@@ -568,12 +569,13 @@ export default class AdminForthRestAPI {
568
569
 
569
570
  const response = await this.adminforth.createResourceRecord({ resource, record, adminUser });
570
571
  if (response.error) {
571
- return { error: response.error };
572
+ return { error: response.error, ok: false };
572
573
  }
573
574
  const connector = this.adminforth.connectors[resource.dataSource];
574
575
 
575
576
  return {
576
- newRecordId: record[connector.getPrimaryKey(resource)]
577
+ newRecordId: response.createdRecord[connector.getPrimaryKey(resource)],
578
+ ok: true
577
579
  }
578
580
  }
579
581
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
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>
@@ -115,7 +115,7 @@
115
115
  </template>
116
116
 
117
117
  <script setup>
118
- import { watch, computed, ref, onMounted } from 'vue'
118
+ import { watch, computed } from 'vue'
119
119
  import Dropdown from '@/components/Dropdown.vue';
120
120
  import CustomDateRangePicker from '@/components/CustomDateRangePicker.vue';
121
121
  import { callAdminForthApi } from '@/utils';
@@ -130,7 +130,6 @@
130
130
  </button>
131
131
  </template>
132
132
  <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
133
-
134
133
  <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
135
134
 
136
135
  </td>
@@ -154,10 +153,10 @@ import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@ic
154
153
  import { computedAsync } from '@vueuse/core';
155
154
  import { initFlowbite } from 'flowbite';
156
155
  import { computed, onMounted, ref, watch } from 'vue';
157
- import { useRouter } from 'vue-router';
156
+ import { useRouter, useRoute } from 'vue-router';
158
157
 
159
158
  const router = useRouter();
160
-
159
+ const route = useRoute();
161
160
  const props = defineProps({
162
161
  loading: Boolean,
163
162
  resource: Object,
@@ -168,7 +167,7 @@ const props = defineProps({
168
167
 
169
168
  const unmasked = ref({});
170
169
 
171
- const mode = computed(() => props.record && Object.keys(props.record).length ? 'edit' : 'create');
170
+ const mode = computed(() => route.name === 'resource-create' ? 'create' : 'edit');
172
171
 
173
172
  const emit = defineEmits(['update:record', 'update:isValid']);
174
173
 
@@ -56,12 +56,4 @@ const router = createRouter({
56
56
  ]
57
57
  })
58
58
 
59
- router.beforeEach((to, from, next) => {
60
- const filterStore = useFiltersStore();
61
- if (to.path.startsWith('/resource/')) {
62
- filterStore.clearFilters();
63
- }
64
- next()
65
- })
66
-
67
59
  export default router
@@ -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?: {
@@ -3,11 +3,12 @@ import { defineStore } from 'pinia';
3
3
 
4
4
  export const useFiltersStore = defineStore('filters', () => {
5
5
  const filters: Ref<any[]> = ref([]);
6
+
6
7
  const setFilter = (filter: any) => {
7
- filters.value = [...filters.value, filter];
8
+ filters.value.push(filter);
8
9
  }
9
10
  const setFilters = (f: any) => {
10
- filters.value = [...f];
11
+ filters.value = f;
11
12
  }
12
13
  const getFilters = () => {
13
14
  return filters.value;
@@ -117,8 +117,6 @@ import { initFlowbite } from 'flowbite';
117
117
  import { computed, onMounted, ref, watch } from 'vue';
118
118
  import { useRoute } from 'vue-router';
119
119
  import { showErrorTost } from '@/composables/useFrontendApi'
120
-
121
- import ValueRenderer from '@/components/ValueRenderer.vue';
122
120
  import { getCustomComponent } from '@/utils';
123
121
 
124
122
 
@@ -151,20 +149,7 @@ const DEFAULT_PAGE_SIZE = 10;
151
149
  const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
152
150
 
153
151
 
154
- watch([page], async () => {
155
- await getList();
156
- });
157
-
158
- watch(()=>filtersStore.filters, async () => {
159
- page.value = 1;
160
- checkboxes.value = [];
161
-
162
- await getList();
163
- }, {deep: true});
164
152
 
165
- watch([sort], async () => {
166
- await getList();
167
- }, {deep: true});
168
153
 
169
154
 
170
155
  async function getList() {
@@ -222,6 +207,8 @@ async function init() {
222
207
  resourceId: route.params.resourceId
223
208
  });
224
209
 
210
+ // !!! clear filters should be in same tick with sort assignment so that watch can catch it
211
+ filtersStore.clearFilters();
225
212
  if (coreStore.resource.options?.defaultSort) {
226
213
  sort.value = [{
227
214
  field: coreStore.resource.options.defaultSort.columnName,
@@ -230,8 +217,8 @@ async function init() {
230
217
  } else {
231
218
  sort.value = [];
232
219
  }
233
-
234
- await getList();
220
+ console.log('↘️init fired');
221
+ // await getList();
235
222
  columnsMinMax.value = await callAdminForthApi({
236
223
  path: '/get_min_max_for_columns',
237
224
  method: 'POST',
@@ -241,18 +228,22 @@ async function init() {
241
228
  });
242
229
  }
243
230
 
231
+ watch([page, sort, () => filtersStore.filters], async () => {
232
+ console.log('↘️watch fired getList');
233
+ await getList();
234
+ }, { deep: true });
235
+
236
+ watch(() => filtersStore.filters, async (to, from) => {
237
+ page.value = 1;
238
+ checkboxes.value = [];
239
+ }, {deep: true});
240
+
244
241
  onMounted(async () => {
242
+ console.log('🧱onMounted fired');
245
243
  initFlowbite();
246
-
247
244
  await init();
248
-
249
245
  });
250
246
 
251
- // on route param change
252
- watch(() => route.params.resourceId, async () => {
253
- filtersStore.setFilters([]);
254
- checkboxes.value = [];
255
- await init();
256
- });
247
+
257
248
 
258
249
  </script>
@@ -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<any>;
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