adminforth 2.8.1 → 2.9.0

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 (70) hide show
  1. package/dist/auth.d.ts +7 -0
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +5 -0
  4. package/dist/auth.js.map +1 -1
  5. package/dist/modules/codeInjector.d.ts.map +1 -1
  6. package/dist/modules/codeInjector.js +18 -2
  7. package/dist/modules/codeInjector.js.map +1 -1
  8. package/dist/modules/configValidator.d.ts.map +1 -1
  9. package/dist/modules/configValidator.js +21 -5
  10. package/dist/modules/configValidator.js.map +1 -1
  11. package/dist/modules/restApi.d.ts.map +1 -1
  12. package/dist/modules/restApi.js +2 -0
  13. package/dist/modules/restApi.js.map +1 -1
  14. package/dist/modules/styles.d.ts +28 -0
  15. package/dist/modules/styles.d.ts.map +1 -1
  16. package/dist/modules/styles.js +28 -0
  17. package/dist/modules/styles.js.map +1 -1
  18. package/dist/modules/utils.d.ts +1 -0
  19. package/dist/modules/utils.d.ts.map +1 -1
  20. package/dist/modules/utils.js +7 -0
  21. package/dist/modules/utils.js.map +1 -1
  22. package/dist/spa/package-lock.json +5 -4
  23. package/dist/spa/package.json +1 -1
  24. package/dist/spa/src/App.vue +43 -176
  25. package/dist/spa/src/adminforth.ts +14 -10
  26. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  27. package/dist/spa/src/afcl/Card.vue +25 -0
  28. package/dist/spa/src/afcl/LinkButton.vue +2 -2
  29. package/dist/spa/src/afcl/Table.vue +19 -10
  30. package/dist/spa/src/afcl/VerticalTabs.vue +15 -6
  31. package/dist/spa/src/afcl/index.ts +2 -0
  32. package/dist/spa/src/components/Filters.vue +2 -2
  33. package/dist/spa/src/components/MenuLink.vue +90 -23
  34. package/dist/spa/src/components/Sidebar.vue +443 -0
  35. package/dist/spa/src/components/UserMenuSettingsButton.vue +68 -0
  36. package/dist/spa/src/renderers/CompactField.vue +1 -1
  37. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  38. package/dist/spa/src/router/index.ts +9 -0
  39. package/dist/spa/src/spa_types/core.ts +5 -0
  40. package/dist/spa/src/stores/filters.ts +29 -2
  41. package/dist/spa/src/types/Back.ts +29 -0
  42. package/dist/spa/src/types/Common.ts +23 -2
  43. package/dist/spa/src/types/FrontendAPI.ts +10 -0
  44. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  45. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  46. package/dist/spa/src/types/adapters/index.ts +2 -0
  47. package/dist/spa/src/utils.ts +1 -0
  48. package/dist/spa/src/views/ListView.vue +15 -7
  49. package/dist/spa/src/views/LoginView.vue +7 -2
  50. package/dist/spa/src/views/SettingsView.vue +121 -0
  51. package/dist/types/Back.d.ts +38 -0
  52. package/dist/types/Back.d.ts.map +1 -1
  53. package/dist/types/Back.js.map +1 -1
  54. package/dist/types/Common.d.ts +21 -1
  55. package/dist/types/Common.d.ts.map +1 -1
  56. package/dist/types/Common.js.map +1 -1
  57. package/dist/types/FrontendAPI.d.ts +10 -0
  58. package/dist/types/FrontendAPI.d.ts.map +1 -1
  59. package/dist/types/FrontendAPI.js.map +1 -1
  60. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  61. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  62. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  63. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  64. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  65. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  66. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  67. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  68. package/dist/types/adapters/index.d.ts +2 -0
  69. package/dist/types/adapters/index.d.ts.map +1 -1
  70. package/package.json +1 -1
@@ -62,6 +62,15 @@ const router = createRouter({
62
62
  },
63
63
  ]
64
64
  },
65
+ {
66
+ path: '/settings/:page?',
67
+ name: 'settings',
68
+ component: () => import('@/views/SettingsView.vue'),
69
+ meta: {
70
+ title: 'Settings',
71
+ sidebarAndHeader: 'preferIconOnly',
72
+ },
73
+ },
65
74
  /* IMPORTANT:ADMINFORTH ROUTES */
66
75
  { path: "/:pathMatch(.*)*", component: PageNotFound },
67
76
  ]
@@ -23,6 +23,10 @@ export type CoreConfig = {
23
23
  brandName: string,
24
24
  singleTheme?: 'light' | 'dark',
25
25
  brandLogo: string,
26
+ iconOnlySidebar: {
27
+ logo?: string,
28
+ enabled?: boolean,
29
+ },
26
30
  title: string,
27
31
  datesFormat: string,
28
32
  timeFormat: string,
@@ -45,6 +49,7 @@ export type CoreConfig = {
45
49
  customHeadItems?: {
46
50
  tagName: string;
47
51
  attributes: { [key: string]: string | boolean };
52
+ innerCode?: string;
48
53
  }[],
49
54
  }
50
55
 
@@ -1,9 +1,11 @@
1
- import { ref, type Ref } from 'vue';
1
+ import { ref, computed, type Ref } from 'vue';
2
2
  import { defineStore } from 'pinia';
3
+ import { useCoreStore } from './core';
3
4
 
4
5
  export const useFiltersStore = defineStore('filters', () => {
5
6
  const filters: Ref<any[]> = ref([]);
6
7
  const sort: Ref<any> = ref({});
8
+ const coreStore = useCoreStore();
7
9
 
8
10
  const setSort = (s: any) => {
9
11
  sort.value = s;
@@ -23,5 +25,30 @@ export const useFiltersStore = defineStore('filters', () => {
23
25
  const clearFilters = () => {
24
26
  filters.value = [];
25
27
  }
26
- return {setFilter, getFilters, clearFilters, filters, setFilters, setSort, getSort}
28
+
29
+ const shouldFilterBeHidden = (fieldName: string) => {
30
+ if (coreStore.resource?.columns) {
31
+ const column = coreStore.resource.columns.find((col: any) => col.name === fieldName);
32
+ if (column?.showIn?.filter !== true) {
33
+ return true;
34
+ }
35
+ }
36
+ return false;
37
+ }
38
+
39
+ const visibleFiltersCount = computed(() => {
40
+ return filters.value.filter(f => !shouldFilterBeHidden(f.field)).length;
41
+ });
42
+
43
+ return {
44
+ setFilter,
45
+ getFilters,
46
+ clearFilters,
47
+ filters,
48
+ setFilters,
49
+ setSort,
50
+ getSort,
51
+ visibleFiltersCount,
52
+ shouldFilterBeHidden
53
+ }
27
54
  })
@@ -306,6 +306,10 @@ export interface IAdminForthAuth {
306
306
 
307
307
  removeCustomCookie({response, name}: {response: any, name: string}): void;
308
308
 
309
+ setCustomCookie({response, payload}: {response: any, payload: {name: string, value: string, expiry: number, httpOnly: boolean}}): void;
310
+
311
+ getCustomCookie({cookies, name}: {cookies: {key: string, value: string}[], name: string}): string | null;
312
+
309
313
  setAuthCookie({expireInDays, response, username, pk,}: {expireInDays?: number, response: any, username: string, pk: string}): void;
310
314
 
311
315
  removeAuthCookie(response: any): void;
@@ -668,6 +672,19 @@ interface AdminForthInputConfigCustomization {
668
672
  */
669
673
  brandLogo?: string,
670
674
 
675
+
676
+ /**
677
+ * Path to your app logo for icon only sidebar
678
+ *
679
+ * Example:
680
+ * Place file `logo.svg` to `./custom` folder and set this option:
681
+ *
682
+ */
683
+ iconOnlySidebar?: {
684
+ logo?: string,
685
+ enabled?: boolean,
686
+ },
687
+
671
688
  /**
672
689
  * Path to your app favicon
673
690
  *
@@ -798,6 +815,7 @@ interface AdminForthInputConfigCustomization {
798
815
  customHeadItems?: {
799
816
  tagName: string;
800
817
  attributes: Record<string, string | boolean>;
818
+ innerCode?: string;
801
819
  }[];
802
820
 
803
821
  }
@@ -1036,6 +1054,16 @@ export interface AdminForthInputConfig {
1036
1054
  * If you are using Cloudflare, set this to 'CF-Connecting-IP'. Case-insensitive.
1037
1055
  */
1038
1056
  clientIpHeader?: string,
1057
+
1058
+ /**
1059
+ * Add custom page to the settings page
1060
+ */
1061
+ userMenuSettingsPages: {
1062
+ icon?: string,
1063
+ pageLabel: string,
1064
+ slug?: string,
1065
+ component: string
1066
+ }[],
1039
1067
  },
1040
1068
 
1041
1069
  /**
@@ -1117,6 +1145,7 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
1117
1145
  customHeadItems?: {
1118
1146
  tagName: string;
1119
1147
  attributes: Record<string, string | boolean>;
1148
+ innerCode?: string;
1120
1149
  }[];
1121
1150
 
1122
1151
  }
@@ -261,7 +261,17 @@ export interface AdminForthComponentDeclarationFull {
261
261
  * </script>
262
262
  *
263
263
  */
264
- meta?: any,
264
+ meta?: {
265
+ /**
266
+ * Controls sidebar and header visibility for custom pages
267
+ * - 'default': Show both sidebar and header (default behavior)
268
+ * - 'none': Hide both sidebar and header (full custom layout)
269
+ * - 'preferIconOnly': Show header but prefer icon-only sidebar
270
+ */
271
+ sidebarAndHeader?: 'default' | 'none' | 'preferIconOnly',
272
+
273
+ [key: string]: any,
274
+ }
265
275
  }
266
276
  import { type AdminForthActionInput } from './Back.js'
267
277
  export { type AdminForthActionInput } from './Back.js'
@@ -1073,6 +1083,10 @@ export interface AdminForthConfigForFrontend {
1073
1083
  showBrandNameInSidebar: boolean,
1074
1084
  showBrandLogoInSidebar: boolean,
1075
1085
  brandLogo?: string,
1086
+ iconOnlySidebar?: {
1087
+ logo?: string,
1088
+ enabled?: boolean,
1089
+ },
1076
1090
  singleTheme?: 'light' | 'dark',
1077
1091
  datesFormat: string,
1078
1092
  timeFormat: string,
@@ -1094,7 +1108,14 @@ export interface AdminForthConfigForFrontend {
1094
1108
  customHeadItems?: {
1095
1109
  tagName: string;
1096
1110
  attributes: Record<string, string | boolean>;
1097
- }[],
1111
+ innerCode?: string;
1112
+ }[],
1113
+ settingPages?:{
1114
+ icon?: string,
1115
+ pageLabel: string,
1116
+ slug?: string,
1117
+ component: string,
1118
+ }[],
1098
1119
  }
1099
1120
 
1100
1121
  export interface GetBaseConfigResponse {
@@ -87,12 +87,19 @@ export interface FrontendAPIInterface {
87
87
  * Works only when user located on the list page. If filter already exists, it will be replaced with the new one.
88
88
  * Can be used to set filter from charts or other components in pageInjections.
89
89
  *
90
+ * Filters are automatically marked as hidden (won't count in badge) if:
91
+ * - Column has showIn.filter: false
92
+ *
90
93
  * Example:
91
94
  *
92
95
  * ```ts
93
96
  * import adminforth from '@/adminforth'
94
97
  *
98
+ * // Regular filter (will show in badge if column.showIn.filter !== false)
95
99
  * adminforth.list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
100
+ *
101
+ * // Hidden filter (won't show in badge if column.showIn.filter === false)
102
+ * adminforth.list.setFilter({field: 'internal_status', operator: 'eq', value: 'active'})
96
103
  * ```
97
104
  *
98
105
  * Please note that you can set/update filter even for fields which have showIn.filter=false in resource configuration.
@@ -106,6 +113,9 @@ export interface FrontendAPIInterface {
106
113
  * DEPRECATED: does the same as setFilter, kept for backward compatibility
107
114
  * Update a filter in the list
108
115
  *
116
+ * Filters visibility in badge is automatically determined by column configuration:
117
+ * - Hidden if column has showIn.filter: false
118
+ *
109
119
  * Example:
110
120
  *
111
121
  * ```ts
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Interface for Captcha adapters.
3
+ */
4
+
5
+ export interface CaptchaAdapter {
6
+ /**
7
+ * Returns the script source URL for the captcha widget.
8
+ */
9
+ getScriptSrc(): string;
10
+
11
+ /**
12
+ * Returns the site key for the captcha.
13
+ */
14
+ getSiteKey(): string;
15
+
16
+ /**
17
+ * Returns the widget ID for the captcha.
18
+ */
19
+ getWidgetId(): string;
20
+
21
+ /**
22
+ * Returns the script HTML for the captcha widget.
23
+ */
24
+ getRenderWidgetCode(): string;
25
+
26
+ /**
27
+ * Returns the function name to render the captcha widget.
28
+ */
29
+ getRenderWidgetFunctionName(): string;
30
+ /**
31
+ * Validates the captcha token.
32
+ */
33
+ validate(token: string, ip: string): Promise<Record<string, any>>;
34
+ }
@@ -0,0 +1,16 @@
1
+
2
+ /**
3
+ * Might have implementations like RAM, Redis, Memcached,
4
+ *
5
+ */
6
+ export interface KeyValueAdapter {
7
+
8
+ get(key: string): Promise<string | null>;
9
+
10
+ set(key: string, value: string, expiresInSeconds?: number): Promise<void>;
11
+
12
+ delete(key: string): Promise<void>;
13
+
14
+ }
15
+
16
+
@@ -1,6 +1,8 @@
1
1
  export type { EmailAdapter } from './EmailAdapter.js';
2
2
  export type { CompletionAdapter } from './CompletionAdapter.js';
3
3
  export type { ImageGenerationAdapter } from './ImageGenerationAdapter.js';
4
+ export type { KeyValueAdapter } from './KeyValueAdapter.js';
4
5
  export type { ImageVisionAdapter } from './ImageVisionAdapter.js';
5
6
  export type { OAuth2Adapter } from './OAuth2Adapter.js';
6
7
  export type { StorageAdapter } from './StorageAdapter.js';
8
+ export type { CaptchaAdapter } from './CaptchaAdapter.js';
@@ -30,6 +30,7 @@ export async function callApi({path, method, body=undefined}: {
30
30
  const r = await fetch(fullPath, options);
31
31
  if (r.status == 401 ) {
32
32
  useUserStore().unauthorize();
33
+ useCoreStore().resetAdminUser();
33
34
  await router.push({ name: 'login' });
34
35
  return null;
35
36
  }
@@ -92,8 +92,8 @@
92
92
  {{ $t('Filter') }}
93
93
  <span
94
94
  class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
95
- v-if="filtersStore.filters.length">
96
- {{ filtersStore.filters.length }}
95
+ v-if="filtersStore.visibleFiltersCount">
96
+ {{ filtersStore.visibleFiltersCount }}
97
97
  </span>
98
98
  </button>
99
99
 
@@ -189,7 +189,7 @@ import ResourceListTable from '@/components/ResourceListTable.vue';
189
189
  import { useCoreStore } from '@/stores/core';
190
190
  import { useFiltersStore } from '@/stores/filters';
191
191
  import { callAdminForthApi, currentQuery, getIcon, setQuery } from '@/utils';
192
- import { computed, onMounted, ref, watch, nextTick, type Ref } from 'vue';
192
+ import { computed, onMounted, onUnmounted, ref, watch, nextTick, type Ref } from 'vue';
193
193
  import { useRoute } from 'vue-router';
194
194
  import { showErrorTost } from '@/composables/useFrontendApi'
195
195
  import { getCustomComponent, initThreeDotsDropdown } from '@/utils';
@@ -387,6 +387,13 @@ class SortQuerySerializer {
387
387
 
388
388
  let listAutorefresher: any = null;
389
389
 
390
+ function clearAutoRefresher() {
391
+ if (listAutorefresher) {
392
+ clearInterval(listAutorefresher);
393
+ listAutorefresher = null;
394
+ }
395
+ }
396
+
390
397
  async function init() {
391
398
 
392
399
  await coreStore.fetchResourceFull({
@@ -434,10 +441,7 @@ async function init() {
434
441
  }
435
442
  });
436
443
 
437
- if (listAutorefresher) {
438
- clearInterval(listAutorefresher);
439
- listAutorefresher = null;
440
- }
444
+ clearAutoRefresher();
441
445
  if (coreStore.resource!.options?.listRowsAutoRefreshSeconds) {
442
446
  listAutorefresher = setInterval(async () => {
443
447
  await adminforth.list.silentRefresh();
@@ -505,6 +509,10 @@ onMounted(async () => {
505
509
  initInProcess = false;
506
510
  });
507
511
 
512
+ onUnmounted(() => {
513
+ clearAutoRefresher();
514
+ });
515
+
508
516
  watch([page], async () => {
509
517
  setQuery({ page: page.value });
510
518
  });
@@ -90,6 +90,7 @@
90
90
  v-for="c in coreStore?.config?.loginPageInjections?.underInputs || []"
91
91
  :is="getCustomComponent(c)"
92
92
  :meta="c.meta"
93
+ @update:disableLoginButton="setDisableLoginButton($event)"
93
94
  />
94
95
 
95
96
  <ErrorMessage :error="error" />
@@ -103,7 +104,7 @@
103
104
  <span class="sr-only">{{ $t('Info') }}</span>
104
105
  <div v-html="loginPromptHTML"></div>
105
106
  </div>
106
- <Button @click="login" :loader="inProgress" :disabled="inProgress" class="w-full">
107
+ <Button @click="login" :loader="inProgress" :disabled="inProgress || disableLoginButton" class="w-full">
107
108
  {{ $t('Login to your account') }}
108
109
  </Button>
109
110
  </form>
@@ -117,7 +118,7 @@
117
118
  </template>
118
119
 
119
120
 
120
- <script setup>
121
+ <script setup lang="ts">
121
122
 
122
123
  import { getCustomComponent } from '@/utils';
123
124
  import { onBeforeMount, onMounted, ref, computed } from 'vue';
@@ -148,6 +149,7 @@ const user = useUserStore();
148
149
  const showPw = ref(false);
149
150
 
150
151
  const error = ref(null);
152
+ const disableLoginButton = ref(false);
151
153
 
152
154
  const backgroundPosition = computed(() => {
153
155
  return coreStore.config?.loginBackgroundPosition || '1/2';
@@ -211,5 +213,8 @@ async function login() {
211
213
 
212
214
  }
213
215
 
216
+ function setDisableLoginButton(value: boolean) {
217
+ disableLoginButton.value = value;
218
+ }
214
219
 
215
220
  </script>
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <div class="mt-16 h-full w-full" :class="{ 'hidden': initialTabSet === false }">
3
+ <div v-if="!coreStore?.config?.settingPages || coreStore?.config?.settingPages.length === 0">
4
+ <p>No setting pages configured or still loading...</p>
5
+ </div>
6
+ <VerticalTabs v-else ref="VerticalTabsRef" v-model:active-tab="activeTab" @update:active-tab="setURL({slug: $event, pageLabel: ''})">
7
+ <template v-for="(c,i) in coreStore?.config?.settingPages" :key="`tab:${settingPageSlotName(c,i)}`" v-slot:['tab:'+c.slug]>
8
+ <div class="flex items-center justify-center whitespace-nowrap w-full px-4 gap-2" @click="setURL(c)">
9
+ <component v-if="c.icon" :is="getIcon(c.icon)" class="w-5 h-5 group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
10
+ {{ c.pageLabel }}
11
+ </div>
12
+ </template>
13
+
14
+ <template v-for="(c,i) in coreStore?.config?.settingPages" :key="`${settingPageSlotName(c,i)}-content`" v-slot:[c.slug]>
15
+ <component
16
+ :is="getCustomComponent({file: c.component || ''})"
17
+ :resource="coreStore.resource"
18
+ :adminUser="coreStore.adminUser"
19
+ />
20
+ </template>
21
+ </VerticalTabs>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { ref, onMounted, watch } from 'vue';
27
+ import { useRouter } from 'vue-router';
28
+ import { useCoreStore } from '@/stores/core';
29
+ import { getCustomComponent, getIcon } from '@/utils';
30
+ import { Dropdown } from 'flowbite';
31
+ import adminforth from '@/adminforth';
32
+ import { VerticalTabs } from '@/afcl'
33
+ import { useRoute } from 'vue-router'
34
+
35
+ const route = useRoute()
36
+ const coreStore = useCoreStore();
37
+ const router = useRouter();
38
+
39
+ const routerIsReady = ref(false);
40
+ const loginRedirectCheckIsReady = ref(false);
41
+ const dropdownUserButton = ref<HTMLElement | null>(null);
42
+ const VerticalTabsRef = ref();
43
+ const activeTab = ref('');
44
+ const initialTabSet = ref(false);
45
+
46
+ watch(() => route?.params?.page, (val) => {
47
+ handleURLChange(val as string | null);
48
+ });
49
+
50
+ async function initRouter() {
51
+ await router.isReady();
52
+ routerIsReady.value = true;
53
+ }
54
+
55
+ watch(dropdownUserButton, (el) => {
56
+ if (el) {
57
+ const dd = new Dropdown(
58
+ document.querySelector('#dropdown-user') as HTMLElement,
59
+ document.querySelector('[data-dropdown-toggle="dropdown-user"]') as HTMLElement,
60
+ );
61
+ adminforth.closeUserMenuDropdown = () => dd.hide();
62
+ }
63
+ });
64
+
65
+ onMounted(async () => {
66
+ if (coreStore.adminUser) {
67
+ await loadMenu();
68
+ loginRedirectCheckIsReady.value = true;
69
+ const routeParamsPage = route?.params?.page;
70
+ if (!routeParamsPage) {
71
+ if (coreStore.config?.settingPages?.[0]) {
72
+ setURL(coreStore.config.settingPages[0]);
73
+ }
74
+ } else {
75
+ handleURLChange(routeParamsPage as string | null);
76
+ }
77
+ }
78
+ });
79
+
80
+ async function loadMenu() {
81
+ await initRouter();
82
+ await coreStore.fetchMenuAndResource();
83
+ }
84
+
85
+ function slugifyString(str: string): string {
86
+ return str
87
+ .toString()
88
+ .toLowerCase()
89
+ .replace(/\s+/g, '-')
90
+ .replace(/[^a-z0-9-_]/g, '-');
91
+ }
92
+
93
+ function setURL(item: {
94
+ pageLabel: string;
95
+ slug?: string | undefined;
96
+ }) {
97
+ router.replace({
98
+ name: 'settings',
99
+ params: { page: item?.slug }
100
+ });
101
+ }
102
+
103
+ function handleURLChange(val: string | null) {
104
+ let isParamInTabs;
105
+ for (const c of coreStore?.config?.settingPages || []) {
106
+ if (c.slug ? c.slug === val : slugifyString(c.pageLabel) === val) {
107
+ isParamInTabs = true;
108
+ break;
109
+ }
110
+ }
111
+ if (isParamInTabs) {
112
+ VerticalTabsRef.value.setActiveTab(val);
113
+ activeTab.value = val as string;
114
+ if (!initialTabSet.value) initialTabSet.value = true;
115
+ } else {
116
+ if (coreStore.config?.settingPages?.[0]) {
117
+ setURL(coreStore.config.settingPages[0]);
118
+ }
119
+ }
120
+ }
121
+ </script>
@@ -277,6 +277,22 @@ export interface IAdminForthAuth {
277
277
  response: any;
278
278
  name: string;
279
279
  }): void;
280
+ setCustomCookie({ response, payload }: {
281
+ response: any;
282
+ payload: {
283
+ name: string;
284
+ value: string;
285
+ expiry: number;
286
+ httpOnly: boolean;
287
+ };
288
+ }): void;
289
+ getCustomCookie({ cookies, name }: {
290
+ cookies: {
291
+ key: string;
292
+ value: string;
293
+ }[];
294
+ name: string;
295
+ }): string | null;
280
296
  setAuthCookie({ expireInDays, response, username, pk, }: {
281
297
  expireInDays?: number;
282
298
  response: any;
@@ -643,6 +659,17 @@ interface AdminForthInputConfigCustomization {
643
659
  *
644
660
  */
645
661
  brandLogo?: string;
662
+ /**
663
+ * Path to your app logo for icon only sidebar
664
+ *
665
+ * Example:
666
+ * Place file `logo.svg` to `./custom` folder and set this option:
667
+ *
668
+ */
669
+ iconOnlySidebar?: {
670
+ logo?: string;
671
+ enabled?: boolean;
672
+ };
646
673
  /**
647
674
  * Path to your app favicon
648
675
  *
@@ -761,6 +788,7 @@ interface AdminForthInputConfigCustomization {
761
788
  customHeadItems?: {
762
789
  tagName: string;
763
790
  attributes: Record<string, string | boolean>;
791
+ innerCode?: string;
764
792
  }[];
765
793
  }
766
794
  export interface AdminForthActionInput {
@@ -969,6 +997,15 @@ export interface AdminForthInputConfig {
969
997
  * If you are using Cloudflare, set this to 'CF-Connecting-IP'. Case-insensitive.
970
998
  */
971
999
  clientIpHeader?: string;
1000
+ /**
1001
+ * Add custom page to the settings page
1002
+ */
1003
+ userMenuSettingsPages: {
1004
+ icon?: string;
1005
+ pageLabel: string;
1006
+ slug?: string;
1007
+ component: string;
1008
+ }[];
972
1009
  };
973
1010
  /**
974
1011
  * Array of resources which will be displayed in the admin panel.
@@ -1036,6 +1073,7 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
1036
1073
  customHeadItems?: {
1037
1074
  tagName: string;
1038
1075
  attributes: Record<string, string | boolean>;
1076
+ innerCode?: string;
1039
1077
  }[];
1040
1078
  }
1041
1079
  export interface AdminForthConfig extends Omit<AdminForthInputConfig, 'customization' | 'resources'> {