adminforth 1.5.8-next.1 → 1.5.8-next.11
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.
- package/commands/utils.js +36 -3
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +1 -1
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +12 -0
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +7 -0
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +55 -24
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/socketBroker.d.ts +2 -1
- package/dist/modules/socketBroker.d.ts.map +1 -1
- package/dist/modules/socketBroker.js +20 -12
- package/dist/modules/socketBroker.js.map +1 -1
- package/dist/servers/express.d.ts +1 -0
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +11 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/src/App.vue +15 -5
- package/dist/spa/src/afcl/Select.vue +1 -1
- package/dist/spa/src/components/Breadcrumbs.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +3 -2
- package/dist/spa/src/components/CustomDateRangePicker.vue +3 -3
- package/dist/spa/src/components/CustomRangePicker.vue +2 -2
- package/dist/spa/src/components/Filters.vue +4 -4
- package/dist/spa/src/components/GroupsTable.vue +4 -4
- package/dist/spa/src/components/MenuLink.vue +16 -2
- package/dist/spa/src/components/ResourceForm.vue +4 -1
- package/dist/spa/src/components/ResourceListTable.vue +12 -7
- package/dist/spa/src/i18n.ts +55 -0
- package/dist/spa/src/main.ts +5 -6
- package/dist/spa/src/renderers/CountryFlag.vue +2 -2
- package/dist/spa/src/stores/core.ts +31 -5
- package/dist/spa/src/types/Adapters.ts +5 -1
- package/dist/spa/src/types/Back.ts +18 -5
- package/dist/spa/src/types/Common.ts +8 -1
- package/dist/spa/src/utils.ts +2 -0
- package/dist/spa/src/views/ListView.vue +22 -2
- package/dist/spa/src/views/LoginView.vue +1 -1
- package/dist/types/Adapters.d.ts +3 -2
- package/dist/types/Adapters.d.ts.map +1 -1
- package/dist/types/Back.d.ts +18 -5
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +5 -1
- package/dist/types/Common.d.ts.map +1 -1
- package/package.json +2 -3
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
:max="maxFormatted"
|
|
6
6
|
type="number" aria-describedby="helper-text-explanation"
|
|
7
7
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
8
|
-
placeholder="From"
|
|
8
|
+
:placeholder="$t('From')"
|
|
9
9
|
v-model="start"
|
|
10
10
|
>
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
:max="maxFormatted"
|
|
15
15
|
type="number" aria-describedby="helper-text-explanation"
|
|
16
16
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
17
|
-
placeholder="To"
|
|
17
|
+
:placeholder="$t('To')"
|
|
18
18
|
v-model="end"
|
|
19
19
|
>
|
|
20
20
|
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
<input
|
|
52
52
|
v-else-if="[ 'string', 'text' ].includes(c.type)"
|
|
53
53
|
type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
54
|
-
placeholder="Search"
|
|
54
|
+
:placeholder="$t('Search')"
|
|
55
55
|
@input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
|
|
56
56
|
:value="getFilterItem({ column: c, operator: 'ilike' })"
|
|
57
57
|
>
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
<input
|
|
69
69
|
v-else-if="[ 'date', 'time' ].includes(c.type)"
|
|
70
70
|
type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md"
|
|
71
|
-
placeholder="Search datetime"
|
|
71
|
+
:placeholder="$t('Search datetime')"
|
|
72
72
|
@input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
|
|
73
73
|
:value="getFilterItem({ column: c, operator: 'ilike' })"
|
|
74
74
|
>
|
|
@@ -87,14 +87,14 @@
|
|
|
87
87
|
<input
|
|
88
88
|
type="number" aria-describedby="helper-text-explanation"
|
|
89
89
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
90
|
-
placeholder="From"
|
|
90
|
+
:placeholder="$t('From')"
|
|
91
91
|
@input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
|
|
92
92
|
:value="getFilterItem({ column: c, operator: 'gte' })"
|
|
93
93
|
>
|
|
94
94
|
<input
|
|
95
95
|
type="number" aria-describedby="helper-text-explanation"
|
|
96
96
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
97
|
-
placeholder="To"
|
|
97
|
+
:placeholder="$t('To')"
|
|
98
98
|
@input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
|
|
99
99
|
:value="getFilterItem({ column: c, operator: 'lte' })"
|
|
100
100
|
>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:key="column.name"
|
|
21
21
|
v-if="currentValues !== null"
|
|
22
22
|
class="bg-ligftForm dark:bg-gray-800 dark:border-gray-700 block md:table-row"
|
|
23
|
-
:class="{ 'border-b': i !== group.columns.length - 1, 'opacity-50 pointer-events-none': column.editReadonly && source === 'edit'}"
|
|
23
|
+
:class="{ 'border-b': i !== group.columns.length - 1, 'opacity-50 pointer-events-none': column.editReadonly && source === $t('edit')}"
|
|
24
24
|
>
|
|
25
25
|
<td class="px-6 py-4 flex items-center block md:table-cell pb-0 md:pb-4"
|
|
26
26
|
:class="{'rounded-bl-lg border-b-none': i === group.columns.length - 1}"> <!--align-top-->
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
<textarea
|
|
104
104
|
v-else-if="['text', 'richtext'].includes(column.type)"
|
|
105
105
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
106
|
-
placeholder="Text"
|
|
106
|
+
:placeholder="$t('Text')"
|
|
107
107
|
:value="currentValues[column.name]"
|
|
108
108
|
@input="setCurrentValue(column.name, $event.target.value)"
|
|
109
109
|
>
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
<textarea
|
|
112
112
|
v-else-if="['json'].includes(column.type)"
|
|
113
113
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
114
|
-
placeholder="Text"
|
|
114
|
+
:placeholder="$t('Text')"
|
|
115
115
|
:value="currentValues[column.name]"
|
|
116
116
|
@input="setCurrentValue(column.name, $event.target.value)"
|
|
117
117
|
>
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
v-else
|
|
121
121
|
:type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
|
|
122
122
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
123
|
-
placeholder="Text"
|
|
123
|
+
:placeholder="$t('Text')"
|
|
124
124
|
:value="currentValues[column.name]"
|
|
125
125
|
@input="setCurrentValue(column.name, $event.target.value)"
|
|
126
126
|
autocomplete="false"
|
|
@@ -13,14 +13,28 @@
|
|
|
13
13
|
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons dark:text-darkSidebarIcons transition duration-75 group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover" ></component>
|
|
14
14
|
<span class="ms-3">{{ item.label }}</span>
|
|
15
15
|
<span v-if="item.badge" class="inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
16
|
-
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent"
|
|
16
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent"
|
|
17
|
+
>
|
|
18
|
+
|
|
19
|
+
<Tooltip v-if="item.badgeTooltip">
|
|
20
|
+
{{ item.badge }}
|
|
21
|
+
|
|
22
|
+
<template #tooltip>
|
|
23
|
+
{{ item.badgeTooltip }}
|
|
24
|
+
</template>
|
|
25
|
+
</Tooltip>
|
|
26
|
+
<template v-else>
|
|
27
|
+
{{ item.badge }}
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
</span>
|
|
17
31
|
|
|
18
32
|
</RouterLink>
|
|
19
33
|
</template>
|
|
20
34
|
|
|
21
35
|
<script setup lang="ts">
|
|
22
36
|
import { getIcon } from '@/utils';
|
|
23
|
-
|
|
37
|
+
import { Tooltip } from '@/afcl';
|
|
24
38
|
const props = defineProps(['item', 'isChild']);
|
|
25
39
|
|
|
26
40
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="rounded-default">
|
|
3
|
+
<pre>
|
|
4
|
+
</pre>
|
|
3
5
|
<form autocomplete="off" @submit.prevent>
|
|
4
6
|
<div v-if="!groups || groups.length === 0">
|
|
5
7
|
<GroupsTable
|
|
@@ -64,9 +66,10 @@ import { computed, onMounted, ref, watch } from 'vue';
|
|
|
64
66
|
import { useRouter, useRoute } from 'vue-router';
|
|
65
67
|
import { useCoreStore } from "@/stores/core";
|
|
66
68
|
import GroupsTable from '@/components/GroupsTable.vue';
|
|
69
|
+
import { useI18n } from 'vue-i18n';
|
|
67
70
|
|
|
68
|
-
const coreStore = useCoreStore();
|
|
69
71
|
|
|
72
|
+
const coreStore = useCoreStore();
|
|
70
73
|
const router = useRouter();
|
|
71
74
|
const route = useRoute();
|
|
72
75
|
const props = defineProps({
|
|
@@ -233,13 +233,18 @@
|
|
|
233
233
|
<!-- Help text -->
|
|
234
234
|
<span class="text-sm text-gray-700 dark:text-gray-400">
|
|
235
235
|
<span v-if="((page || 1) - 1) * pageSize + 1 > totalRows">{{ $t('Wrong Page') }} </span>
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
236
|
+
<template v-else>
|
|
237
|
+
<span class="hidden sm:inline"
|
|
238
|
+
v-html="$t('Showing {from} to {to} of {total} Entries', {from: ((page || 1) - 1) * pageSize + 1, to: Math.min((page || 1) * pageSize, totalRows), total: totalRows})
|
|
239
|
+
.replace(/\d+/g, '<strong>$&</strong>')">
|
|
240
|
+
</span>
|
|
241
|
+
<span class="sm:hidden"
|
|
242
|
+
v-html="$t('{from} - {to} of {total}', {from: ((page || 1) - 1) * pageSize + 1, to: Math.min((page || 1) * pageSize, totalRows), total: totalRows})
|
|
243
|
+
.replace(/\d+/g, '<strong>$&</strong>')
|
|
244
|
+
">
|
|
245
|
+
</span>
|
|
246
|
+
|
|
247
|
+
</template>
|
|
243
248
|
</span>
|
|
244
249
|
</div>
|
|
245
250
|
</template>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createI18n } from 'vue-i18n';
|
|
2
|
+
import { createApp } from 'vue';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// taken from here https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization
|
|
6
|
+
function slavicPluralRule(choice, choicesLength, orgRule) {
|
|
7
|
+
if (choice === 0) {
|
|
8
|
+
return 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const teen = choice > 10 && choice < 20
|
|
12
|
+
const endsWithOne = choice % 10 === 1
|
|
13
|
+
|
|
14
|
+
if (!teen && endsWithOne) {
|
|
15
|
+
return 1
|
|
16
|
+
}
|
|
17
|
+
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
|
|
18
|
+
return 2
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return choicesLength < 4 ? 2 : 3
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export function initI18n(app: ReturnType<typeof createApp>) {
|
|
26
|
+
const i18n = createI18n({
|
|
27
|
+
legacy: false,
|
|
28
|
+
|
|
29
|
+
missingWarn: false,
|
|
30
|
+
fallbackWarn: false,
|
|
31
|
+
|
|
32
|
+
pluralRules: {
|
|
33
|
+
'uk': slavicPluralRule,
|
|
34
|
+
'bg': slavicPluralRule,
|
|
35
|
+
'cs': slavicPluralRule,
|
|
36
|
+
'hr': slavicPluralRule,
|
|
37
|
+
'mk': slavicPluralRule,
|
|
38
|
+
'pl': slavicPluralRule,
|
|
39
|
+
'sk': slavicPluralRule,
|
|
40
|
+
'sl': slavicPluralRule,
|
|
41
|
+
'sr': slavicPluralRule,
|
|
42
|
+
'be': slavicPluralRule,
|
|
43
|
+
'ru': slavicPluralRule,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
missing: (locale, key) => {
|
|
47
|
+
// very very dirty hack to make work $t("a {key} b", { key: "c" }) as "a c b" when translation is missing
|
|
48
|
+
// e.g. relevant for "Showing {from} to {to} of {total} entries" on list page
|
|
49
|
+
return key + ' ';
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
app.use(i18n);
|
|
54
|
+
|
|
55
|
+
}
|
package/dist/spa/src/main.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { createApp } from 'vue'
|
|
2
|
-
import { createI18n } from 'vue-i18n'
|
|
3
2
|
import { createPinia } from 'pinia'
|
|
4
3
|
/* IMPORTANT:ADMINFORTH IMPORTS */
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
import App from './App.vue'
|
|
8
7
|
import router from './router'
|
|
8
|
+
import { initI18n } from './i18n'
|
|
9
9
|
|
|
10
|
-
export const app = createApp(App)
|
|
10
|
+
export const app: ReturnType<typeof createApp> = createApp(App)
|
|
11
11
|
/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
|
|
12
12
|
|
|
13
|
-
const i18n = createI18n({
|
|
14
|
-
// something vue-i18n options here ...
|
|
15
|
-
})
|
|
16
13
|
app.use(createPinia())
|
|
17
14
|
app.use(router)
|
|
18
|
-
|
|
15
|
+
|
|
16
|
+
initI18n(app);
|
|
17
|
+
|
|
19
18
|
|
|
20
19
|
/* IMPORTANT:ADMINFORTH CUSTOM USES */
|
|
21
20
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ref, computed } from 'vue'
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
3
|
import { callAdminForthApi } from '@/utils';
|
|
4
|
+
import websocket from '@/websocket';
|
|
5
|
+
|
|
4
6
|
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend } from '@/types/Common';
|
|
5
7
|
import type { Ref } from 'vue'
|
|
6
8
|
|
|
@@ -64,19 +66,40 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
64
66
|
console.log('🌍 AdminForth v', resp.version);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
function findItemWithId(items: AdminForthConfigMenuItem[],
|
|
69
|
+
function findItemWithId(items: AdminForthConfigMenuItem[], itemId: string): AdminForthConfigMenuItem | undefined {
|
|
68
70
|
for (const item of items) {
|
|
69
|
-
if (item.
|
|
71
|
+
if (item.itemId === itemId) {
|
|
70
72
|
return item;
|
|
71
73
|
}
|
|
72
74
|
if (item.children) {
|
|
73
|
-
const found = findItemWithId(item.children,
|
|
75
|
+
const found = findItemWithId(item.children, itemId);
|
|
74
76
|
if (found) {
|
|
75
77
|
return found;
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
}
|
|
82
|
+
async function subscribeToMenuBadges() {
|
|
83
|
+
const processItem = (mi: AdminForthConfigMenuItem) => {
|
|
84
|
+
if (mi.badge) {
|
|
85
|
+
console.log('mi 🧪 subsc', mi)
|
|
86
|
+
websocket.subscribe(`/opentopic/update-menu-badge/${mi.itemId}`, ({ badge }) => {
|
|
87
|
+
mi.badge = badge;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
menu.value.forEach((mi) => {
|
|
93
|
+
processItem(mi);
|
|
94
|
+
if (mi.children) {
|
|
95
|
+
mi.children.forEach((child) => {
|
|
96
|
+
console.log('mi 🧪', JSON.stringify(child), mi.badge)
|
|
97
|
+
|
|
98
|
+
processItem(child);
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
80
103
|
async function fetchMenuBadges() {
|
|
81
104
|
const resp: Record<string, string> = await callAdminForthApi({
|
|
82
105
|
path: '/get_menu_badges',
|
|
@@ -85,12 +108,15 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
85
108
|
if (!resp) {
|
|
86
109
|
return;
|
|
87
110
|
}
|
|
88
|
-
Object.entries(resp).forEach(([
|
|
89
|
-
const item: AdminForthConfigMenuItem | undefined = findItemWithId(menu.value,
|
|
111
|
+
Object.entries(resp).forEach(([itemId, badge]: [string, string]) => {
|
|
112
|
+
const item: AdminForthConfigMenuItem | undefined = findItemWithId(menu.value, itemId);
|
|
90
113
|
if (item) {
|
|
91
114
|
item.badge = badge;
|
|
92
115
|
}
|
|
93
116
|
});
|
|
117
|
+
|
|
118
|
+
subscribeToMenuBadges();
|
|
119
|
+
|
|
94
120
|
}
|
|
95
121
|
|
|
96
122
|
|
|
@@ -301,6 +301,8 @@ export interface IAdminForth {
|
|
|
301
301
|
[key: string]: IAdminForthDataSourceConnectorBase;
|
|
302
302
|
};
|
|
303
303
|
|
|
304
|
+
tr(msg: string, category: string, lang: string, params: any): Promise<string>;
|
|
305
|
+
|
|
304
306
|
createResourceRecord(
|
|
305
307
|
params: { resource: AdminForthResource, record: any, adminUser: AdminUser, extra?: HttpExtra }
|
|
306
308
|
): Promise<{ error?: string, createdRecord?: any }>;
|
|
@@ -453,7 +455,16 @@ export type BeforeSaveFunction = (params: {
|
|
|
453
455
|
recordId: any,
|
|
454
456
|
adminUser: AdminUser,
|
|
455
457
|
record: any,
|
|
456
|
-
oldRecord
|
|
458
|
+
oldRecord: any,
|
|
459
|
+
adminforth: IAdminForth,
|
|
460
|
+
extra?: HttpExtra,
|
|
461
|
+
}) => Promise<{ok: boolean, error?: string}>;
|
|
462
|
+
|
|
463
|
+
export type BeforeCreateSaveFunction = (params: {
|
|
464
|
+
resource: AdminForthResource,
|
|
465
|
+
recordId: any,
|
|
466
|
+
adminUser: AdminUser,
|
|
467
|
+
record: any,
|
|
457
468
|
adminforth: IAdminForth,
|
|
458
469
|
extra?: HttpExtra,
|
|
459
470
|
}) => Promise<{ok: boolean, error?: string}>;
|
|
@@ -711,7 +722,7 @@ export interface AdminForthResourceInput extends Omit<AdminForthResourceInputCom
|
|
|
711
722
|
* - fill-in adminUser as creator of record
|
|
712
723
|
* - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
|
|
713
724
|
*/
|
|
714
|
-
beforeSave?:
|
|
725
|
+
beforeSave?: BeforeCreateSaveFunction | Array<BeforeCreateSaveFunction>,
|
|
715
726
|
|
|
716
727
|
/**
|
|
717
728
|
* Typical use-cases:
|
|
@@ -1137,7 +1148,7 @@ export interface AdminForthResource extends Omit<AdminForthResourceInput, 'optio
|
|
|
1137
1148
|
* - fill-in adminUser as creator of record
|
|
1138
1149
|
* - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
|
|
1139
1150
|
*/
|
|
1140
|
-
beforeSave?: Array<
|
|
1151
|
+
beforeSave?: Array<BeforeCreateSaveFunction>,
|
|
1141
1152
|
|
|
1142
1153
|
/**
|
|
1143
1154
|
* Typical use-cases:
|
|
@@ -1191,7 +1202,9 @@ export interface AdminForthBulkAction extends AdminForthBulkActionCommon {
|
|
|
1191
1202
|
* Callback which will be called on backend when user clicks on action button.
|
|
1192
1203
|
* It should return Promise which will be resolved when action is done.
|
|
1193
1204
|
*/
|
|
1194
|
-
action: ({ resource, selectedIds, adminUser
|
|
1205
|
+
action: ({ resource, selectedIds, adminUser, tr }: {
|
|
1206
|
+
resource: AdminForthResource, selectedIds: Array<any>, adminUser: AdminUser, tr: (key: string, category?: string, params?: any) => string
|
|
1207
|
+
}) => Promise<{ ok: boolean, error?: string, successMessage?: string }>,
|
|
1195
1208
|
|
|
1196
1209
|
/**
|
|
1197
1210
|
* Allowed callback called to check whether action is allowed for user.
|
|
@@ -1284,7 +1297,7 @@ export interface IWebSocketClient {
|
|
|
1284
1297
|
}
|
|
1285
1298
|
|
|
1286
1299
|
export interface IWebSocketBroker {
|
|
1287
|
-
publish: (topic: string, data: any) => void;
|
|
1300
|
+
publish: (topic: string, data: any, filterUsers?: (adminUser: AdminUser) => Promise<boolean>) => void;
|
|
1288
1301
|
|
|
1289
1302
|
registerWsClient: (client: IWebSocketClient) => void;
|
|
1290
1303
|
}
|
|
@@ -873,10 +873,17 @@ export interface AdminForthConfigMenuItem {
|
|
|
873
873
|
*/
|
|
874
874
|
badge?: string | ((user: AdminUser) => Promise<string>),
|
|
875
875
|
|
|
876
|
+
/**
|
|
877
|
+
* Tooltip shown on hover for badge
|
|
878
|
+
*/
|
|
879
|
+
badgeTooltip?: string,
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
|
|
876
883
|
/**
|
|
877
884
|
* Item id will be automatically generated from hashed resourceId+Path+label
|
|
878
885
|
*/
|
|
879
|
-
|
|
886
|
+
itemId?: string, // todo move to runtime type
|
|
880
887
|
}
|
|
881
888
|
|
|
882
889
|
|
package/dist/spa/src/utils.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { useCoreStore } from './stores/core';
|
|
|
8
8
|
import { useUserStore } from './stores/user';
|
|
9
9
|
import { Dropdown } from 'flowbite';
|
|
10
10
|
|
|
11
|
+
const LS_LANG_KEY = `afLanguage`;
|
|
11
12
|
|
|
12
13
|
export async function callApi({path, method, body=undefined}: {
|
|
13
14
|
path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
@@ -17,6 +18,7 @@ export async function callApi({path, method, body=undefined}: {
|
|
|
17
18
|
method,
|
|
18
19
|
headers: {
|
|
19
20
|
'Content-Type': 'application/json',
|
|
21
|
+
'accept-language': localStorage.getItem(LS_LANG_KEY) || 'en',
|
|
20
22
|
},
|
|
21
23
|
body: JSON.stringify(body),
|
|
22
24
|
};
|
|
@@ -43,10 +43,26 @@
|
|
|
43
43
|
}"
|
|
44
44
|
>
|
|
45
45
|
<component
|
|
46
|
-
v-if="action.icon"
|
|
46
|
+
v-if="action.icon && !bulkActionLoadingStates[action.id]"
|
|
47
47
|
:is="getIcon(action.icon)"
|
|
48
48
|
class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"></component>
|
|
49
|
-
|
|
49
|
+
<div v-if="bulkActionLoadingStates[action.id]">
|
|
50
|
+
<svg
|
|
51
|
+
aria-hidden="true"
|
|
52
|
+
class="w-5 h-5 animate-spin"
|
|
53
|
+
:class="{
|
|
54
|
+
'text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300': action.state !== 'danger',
|
|
55
|
+
'text-red-200 dark:text-red-800 fill-red-600 dark:fill-red-500': action.state === 'danger'
|
|
56
|
+
}"
|
|
57
|
+
viewBox="0 0 100 101"
|
|
58
|
+
fill="none"
|
|
59
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
60
|
+
>
|
|
61
|
+
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
|
62
|
+
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
|
63
|
+
</svg>
|
|
64
|
+
<span class="sr-only">Loading...</span>
|
|
65
|
+
</div>
|
|
50
66
|
{{ `${action.label} (${checkboxes.length})` }}
|
|
51
67
|
</button>
|
|
52
68
|
|
|
@@ -151,6 +167,7 @@ watch(() => sort, async (to, from) => {
|
|
|
151
167
|
const rows: Ref<any[]|null> = ref(null);
|
|
152
168
|
const totalRows = ref(0);
|
|
153
169
|
const checkboxes = ref([]);
|
|
170
|
+
const bulkActionLoadingStates = ref<{[key: string]: boolean}>({});
|
|
154
171
|
|
|
155
172
|
const DEFAULT_PAGE_SIZE = 10;
|
|
156
173
|
|
|
@@ -252,6 +269,7 @@ async function startBulkAction(actionId) {
|
|
|
252
269
|
return;
|
|
253
270
|
}
|
|
254
271
|
}
|
|
272
|
+
bulkActionLoadingStates.value[actionId] = true;
|
|
255
273
|
|
|
256
274
|
const data = await callAdminForthApi({
|
|
257
275
|
path: '/start_bulk_action',
|
|
@@ -264,6 +282,7 @@ async function startBulkAction(actionId) {
|
|
|
264
282
|
}
|
|
265
283
|
});
|
|
266
284
|
if (data?.ok) {
|
|
285
|
+
bulkActionLoadingStates.value[actionId] = false;
|
|
267
286
|
checkboxes.value = [];
|
|
268
287
|
await getList();
|
|
269
288
|
|
|
@@ -276,6 +295,7 @@ async function startBulkAction(actionId) {
|
|
|
276
295
|
|
|
277
296
|
}
|
|
278
297
|
if (data?.error) {
|
|
298
|
+
bulkActionLoadingStates.value[actionId] = false;
|
|
279
299
|
showErrorTost(data.error);
|
|
280
300
|
}
|
|
281
301
|
}
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
<div v-if="coreStore.config.rememberMeDays"
|
|
65
65
|
class="flex items-start mb-5"
|
|
66
|
-
:title="`Stay logged in for
|
|
66
|
+
:title="$t(`Stay logged in for {days} days`, {days: coreStore.config.rememberMeDays})"
|
|
67
67
|
>
|
|
68
68
|
<Checkbox v-model="rememberMeValue" class="mr-2">
|
|
69
69
|
{{ $t('Remember me') }}
|
package/dist/types/Adapters.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ export interface EmailAdapter {
|
|
|
3
3
|
}
|
|
4
4
|
export interface CompletionAdapter {
|
|
5
5
|
complete(content: string, stop: string[], maxTokens: number): Promise<{
|
|
6
|
-
content
|
|
7
|
-
finishReason
|
|
6
|
+
content?: string;
|
|
7
|
+
finishReason?: string;
|
|
8
|
+
error?: string;
|
|
8
9
|
}>;
|
|
9
10
|
}
|
|
10
11
|
//# sourceMappingURL=Adapters.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Adapters.d.ts","sourceRoot":"","sources":["../../types/Adapters.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,OACf;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CACN,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Adapters.d.ts","sourceRoot":"","sources":["../../types/Adapters.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,OACf;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CACN,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QACT,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ"}
|
package/dist/types/Back.d.ts
CHANGED
|
@@ -282,6 +282,7 @@ export interface IAdminForth {
|
|
|
282
282
|
connectors: {
|
|
283
283
|
[key: string]: IAdminForthDataSourceConnectorBase;
|
|
284
284
|
};
|
|
285
|
+
tr(msg: string, category: string, lang: string, params: any): Promise<string>;
|
|
285
286
|
createResourceRecord(params: {
|
|
286
287
|
resource: AdminForthResource;
|
|
287
288
|
record: any;
|
|
@@ -443,7 +444,18 @@ export type BeforeSaveFunction = (params: {
|
|
|
443
444
|
recordId: any;
|
|
444
445
|
adminUser: AdminUser;
|
|
445
446
|
record: any;
|
|
446
|
-
oldRecord
|
|
447
|
+
oldRecord: any;
|
|
448
|
+
adminforth: IAdminForth;
|
|
449
|
+
extra?: HttpExtra;
|
|
450
|
+
}) => Promise<{
|
|
451
|
+
ok: boolean;
|
|
452
|
+
error?: string;
|
|
453
|
+
}>;
|
|
454
|
+
export type BeforeCreateSaveFunction = (params: {
|
|
455
|
+
resource: AdminForthResource;
|
|
456
|
+
recordId: any;
|
|
457
|
+
adminUser: AdminUser;
|
|
458
|
+
record: any;
|
|
447
459
|
adminforth: IAdminForth;
|
|
448
460
|
extra?: HttpExtra;
|
|
449
461
|
}) => Promise<{
|
|
@@ -680,7 +692,7 @@ export interface AdminForthResourceInput extends Omit<AdminForthResourceInputCom
|
|
|
680
692
|
* - fill-in adminUser as creator of record
|
|
681
693
|
* - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
|
|
682
694
|
*/
|
|
683
|
-
beforeSave?:
|
|
695
|
+
beforeSave?: BeforeCreateSaveFunction | Array<BeforeCreateSaveFunction>;
|
|
684
696
|
/**
|
|
685
697
|
* Typical use-cases:
|
|
686
698
|
* - Initiate some trigger after record saved to database (e.g sync to another datasource)
|
|
@@ -1016,7 +1028,7 @@ export interface AdminForthResource extends Omit<AdminForthResourceInput, 'optio
|
|
|
1016
1028
|
* - fill-in adminUser as creator of record
|
|
1017
1029
|
* - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
|
|
1018
1030
|
*/
|
|
1019
|
-
beforeSave?: Array<
|
|
1031
|
+
beforeSave?: Array<BeforeCreateSaveFunction>;
|
|
1020
1032
|
/**
|
|
1021
1033
|
* Typical use-cases:
|
|
1022
1034
|
* - Initiate some trigger after record saved to database (e.g sync to another datasource)
|
|
@@ -1060,10 +1072,11 @@ export interface AdminForthBulkAction extends AdminForthBulkActionCommon {
|
|
|
1060
1072
|
* Callback which will be called on backend when user clicks on action button.
|
|
1061
1073
|
* It should return Promise which will be resolved when action is done.
|
|
1062
1074
|
*/
|
|
1063
|
-
action: ({ resource, selectedIds, adminUser }: {
|
|
1075
|
+
action: ({ resource, selectedIds, adminUser, tr }: {
|
|
1064
1076
|
resource: AdminForthResource;
|
|
1065
1077
|
selectedIds: Array<any>;
|
|
1066
1078
|
adminUser: AdminUser;
|
|
1079
|
+
tr: (key: string, category?: string, params?: any) => string;
|
|
1067
1080
|
}) => Promise<{
|
|
1068
1081
|
ok: boolean;
|
|
1069
1082
|
error?: string;
|
|
@@ -1151,7 +1164,7 @@ export interface IWebSocketClient {
|
|
|
1151
1164
|
onClose: (handler: () => void) => void;
|
|
1152
1165
|
}
|
|
1153
1166
|
export interface IWebSocketBroker {
|
|
1154
|
-
publish: (topic: string, data: any) => void;
|
|
1167
|
+
publish: (topic: string, data: any, filterUsers?: (adminUser: AdminUser) => Promise<boolean>) => void;
|
|
1155
1168
|
registerWsClient: (client: IWebSocketClient) => void;
|
|
1156
1169
|
}
|
|
1157
1170
|
export {};
|