adminforth 2.4.0-next.15 → 2.4.0-next.151
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/callTsProxy.js +14 -4
- package/commands/cli.js +12 -4
- package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
- package/commands/createApp/templates/index.ts.hbs +10 -2
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/utils.js +27 -2
- package/commands/createCustomComponent/configLoader.js +3 -0
- package/commands/createCustomComponent/main.js +1 -0
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +46 -15
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +15 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +44 -15
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +11 -0
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +11 -0
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +11 -0
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -9
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +22 -5
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +54 -3
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +147 -25
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.d.ts +457 -13
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +513 -31
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +1 -0
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +9 -0
- package/dist/modules/utils.js.map +1 -1
- package/dist/spa/index.html +1 -1
- package/dist/spa/src/App.vue +33 -15
- package/dist/spa/src/adminforth.ts +30 -10
- package/dist/spa/src/afcl/BarChart.vue +2 -2
- package/dist/spa/src/afcl/Button.vue +6 -6
- package/dist/spa/src/afcl/Checkbox.vue +21 -13
- package/dist/spa/src/afcl/CountryFlag.vue +4 -1
- package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
- package/dist/spa/src/afcl/Dialog.vue +44 -27
- package/dist/spa/src/afcl/Dropzone.vue +12 -12
- package/dist/spa/src/afcl/Input.vue +6 -6
- package/dist/spa/src/afcl/JsonViewer.vue +25 -0
- package/dist/spa/src/afcl/LinkButton.vue +1 -1
- package/dist/spa/src/afcl/PieChart.vue +5 -5
- package/dist/spa/src/afcl/ProgressBar.vue +7 -7
- package/dist/spa/src/afcl/Select.vue +68 -34
- package/dist/spa/src/afcl/Skeleton.vue +6 -6
- package/dist/spa/src/afcl/Table.vue +199 -71
- package/dist/spa/src/afcl/Textarea.vue +31 -0
- package/dist/spa/src/afcl/Toggle.vue +32 -0
- package/dist/spa/src/afcl/Tooltip.vue +1 -2
- package/dist/spa/src/afcl/VerticalTabs.vue +3 -3
- package/dist/spa/src/afcl/index.ts +4 -3
- package/dist/spa/src/components/AcceptModal.vue +7 -7
- package/dist/spa/src/components/Breadcrumbs.vue +5 -5
- package/dist/spa/src/components/ColumnValueInput.vue +38 -18
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
- package/dist/spa/src/components/CustomRangePicker.vue +37 -8
- package/dist/spa/src/components/ErrorMessage.vue +21 -0
- package/dist/spa/src/components/Filters.vue +83 -37
- package/dist/spa/src/components/GroupsTable.vue +9 -8
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +94 -51
- package/dist/spa/src/components/ResourceListTable.vue +78 -80
- package/dist/spa/src/components/ResourceListTableVirtual.vue +70 -72
- package/dist/spa/src/components/ShowTable.vue +17 -12
- package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
- package/dist/spa/src/components/SkeleteLoader.vue +3 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +73 -14
- package/dist/spa/src/components/Toast.vue +27 -9
- package/dist/spa/src/components/ValueRenderer.vue +43 -16
- package/dist/spa/src/controls/BoolToggle.vue +34 -0
- package/dist/spa/src/i18n.ts +1 -1
- package/dist/spa/src/shims-vue.d.ts +5 -0
- package/dist/spa/src/spa_types/core.ts +8 -1
- package/dist/spa/src/stores/core.ts +1 -1
- package/dist/spa/src/stores/modal.ts +6 -1
- package/dist/spa/src/stores/toast.ts +22 -3
- package/dist/spa/src/types/Back.ts +107 -21
- package/dist/spa/src/types/Common.ts +45 -31
- package/dist/spa/src/types/FrontendAPI.ts +15 -2
- package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
- package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
- package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
- package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
- package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
- package/dist/spa/src/types/adapters/index.ts +6 -0
- package/dist/spa/src/utils.ts +217 -7
- package/dist/spa/src/views/CreateView.vue +18 -19
- package/dist/spa/src/views/EditView.vue +25 -19
- package/dist/spa/src/views/ListView.vue +124 -79
- package/dist/spa/src/views/LoginView.vue +36 -44
- package/dist/spa/src/views/ResourceParent.vue +2 -2
- package/dist/spa/src/views/ShowView.vue +59 -39
- package/dist/spa/src/websocket.ts +6 -1
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/spa/vite.config.ts +45 -2
- package/dist/types/Back.d.ts +75 -14
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +15 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +38 -28
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +15 -1
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
- package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CompletionAdapter.js +2 -0
- package/dist/types/adapters/CompletionAdapter.js.map +1 -0
- package/dist/types/adapters/EmailAdapter.d.ts +20 -0
- package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
- package/dist/types/adapters/EmailAdapter.js +2 -0
- package/dist/types/adapters/EmailAdapter.js.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
- package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.js +2 -0
- package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
- package/dist/types/adapters/OAuth2Adapter.js +2 -0
- package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
- package/dist/types/adapters/StorageAdapter.d.ts +63 -0
- package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
- package/dist/types/adapters/StorageAdapter.js +2 -0
- package/dist/types/adapters/StorageAdapter.js.map +1 -0
- package/dist/types/adapters/index.d.ts +7 -0
- package/dist/types/adapters/index.d.ts.map +1 -0
- package/dist/types/adapters/index.js +2 -0
- package/dist/types/adapters/index.js.map +1 -0
- package/package.json +3 -2
- package/dist/spa/src/types/Adapters.ts +0 -213
- package/dist/types/Adapters.d.ts +0 -168
- package/dist/types/Adapters.d.ts.map +0 -1
- package/dist/types/Adapters.js +0 -2
- package/dist/types/Adapters.js.map +0 -1
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
<div class="flex items-center w-full p-4
|
|
4
|
+
<div class="flex items-center w-full p-4 rounded-lg shadow-lg dark:text-darkToastText dark:bg-darkToastBackground bg-lightToastBackground text-lightToastText"
|
|
5
5
|
role="alert"
|
|
6
|
-
:class="
|
|
7
|
-
{
|
|
8
|
-
'danger': 'bg-red-100',
|
|
9
|
-
}[toast.variant]
|
|
10
|
-
"
|
|
11
6
|
>
|
|
12
7
|
<div v-if="toast.variant == 'info'" class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity rounded-lg dark:bg-blue-800 dark:text-blue-200">
|
|
13
8
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
|
@@ -35,8 +30,19 @@
|
|
|
35
30
|
</div>
|
|
36
31
|
|
|
37
32
|
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
|
|
38
|
-
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>
|
|
39
|
-
|
|
33
|
+
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>
|
|
34
|
+
<div class="flex flex-col items-center justify-center">
|
|
35
|
+
{{toast.message}}
|
|
36
|
+
<div class="flex justify-center mt-2 gap-2">
|
|
37
|
+
<div v-for="button in toast.buttons" class="rounded-md bg-lightButtonsBackground hover:bg-lightButtonsHover text-lightButtonsText dark:bg-darkPrimary dark:hover:bg-darkButtonsBackground dark:text-darkButtonsText">
|
|
38
|
+
<button @click="onButtonClick(button.value)" class="px-2 py-1 rounded hover:bg-black/5 dark:hover:bg-white/10">
|
|
39
|
+
{{ button.label }}
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<button @click="closeToast" type="button" class="ms-auto -mx-1.5 -my-1.5 bg-lightToastCloseIconBackground text-lightToastCloseIcon hover:text-lightToastCloseIconHover rounded-lg focus:ring-2 focus:ring-lightToastCloseIconFocusRing p-1.5 hover:bg-lightToastCloseIconBackgroundHover inline-flex items-center justify-center h-8 w-8 dark:text-darkToastCloseIcon dark:hover:text-darkToastCloseIconHover dark:bg-darkToastCloseIconBackground dark:hover:bg-darkToastCloseIconBackgroundHover dark:focus:ring-darkToastCloseIconFocusRing" >
|
|
40
46
|
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
41
47
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
42
48
|
</svg>
|
|
@@ -58,16 +64,28 @@ const props = defineProps<{
|
|
|
58
64
|
variant: string;
|
|
59
65
|
id: string;
|
|
60
66
|
timeout?: number|'unlimited';
|
|
67
|
+
buttons?: { value: any; label: string }[];
|
|
61
68
|
}
|
|
62
69
|
}>();
|
|
63
70
|
function closeToast() {
|
|
71
|
+
// resolve with undefined on close (X button)
|
|
72
|
+
toastStore.resolveToast(props.toast.id);
|
|
73
|
+
emit('close');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onButtonClick(value: any) {
|
|
77
|
+
toastStore.resolveToast(props.toast.id, value);
|
|
64
78
|
emit('close');
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
onMounted(() => {
|
|
68
82
|
if (props.toast.timeout === 'unlimited') return;
|
|
69
83
|
else {
|
|
70
|
-
setTimeout(() => {
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
// resolve with undefined on auto-timeout
|
|
86
|
+
toastStore.resolveToast(props.toast.id);
|
|
87
|
+
emit('close');
|
|
88
|
+
}, (props.toast.timeout || 10) * 1e3 );
|
|
71
89
|
}
|
|
72
90
|
});
|
|
73
91
|
|
|
@@ -12,13 +12,40 @@
|
|
|
12
12
|
>
|
|
13
13
|
<RouterLink
|
|
14
14
|
class="font-medium text-lightSidebarText dark:text-darkSidebarText hover:brightness-110 whitespace-nowrap"
|
|
15
|
-
:to="{
|
|
15
|
+
:to="{
|
|
16
|
+
name: 'resource-show',
|
|
17
|
+
params: {
|
|
18
|
+
primaryKey: foreignResource.pk,
|
|
19
|
+
resourceId: column.foreignResource
|
|
20
|
+
? (
|
|
21
|
+
column.foreignResource.resourceId
|
|
22
|
+
|| column.foreignResource.polymorphicResources?.find(
|
|
23
|
+
(pr: any) => pr.whenValue === record[column.foreignResource?.polymorphicOn!]
|
|
24
|
+
)?.resourceId
|
|
25
|
+
)
|
|
26
|
+
: undefined
|
|
27
|
+
}
|
|
28
|
+
}"
|
|
16
29
|
>
|
|
17
30
|
{{ foreignResource.label }}
|
|
18
31
|
</RouterLink>
|
|
19
32
|
</span>
|
|
20
33
|
<RouterLink v-else-if="record[column.name]" class="font-medium text-lightPrimary dark:text-darkPrimary hover:brightness-110 whitespace-nowrap"
|
|
21
|
-
|
|
34
|
+
:to="{
|
|
35
|
+
name: 'resource-show',
|
|
36
|
+
params: {
|
|
37
|
+
primaryKey: record[column.name].pk,
|
|
38
|
+
resourceId: column.foreignResource
|
|
39
|
+
? (
|
|
40
|
+
column.foreignResource.resourceId
|
|
41
|
+
|| column.foreignResource.polymorphicResources?.find(
|
|
42
|
+
(pr: any) => pr.whenValue === record[column.foreignResource?.polymorphicOn!]
|
|
43
|
+
)?.resourceId
|
|
44
|
+
)
|
|
45
|
+
: undefined
|
|
46
|
+
}
|
|
47
|
+
}"
|
|
48
|
+
>
|
|
22
49
|
{{ record[column.name].label }}
|
|
23
50
|
</RouterLink>
|
|
24
51
|
<div v-else>
|
|
@@ -27,8 +54,8 @@
|
|
|
27
54
|
</span>
|
|
28
55
|
|
|
29
56
|
<span v-else-if="column.type === 'boolean'">
|
|
30
|
-
<span v-if="record[column.name] === true" class="bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">{{ $t('Yes') }}</span>
|
|
31
|
-
<span v-else-if="record[column.name] === false" class="bg-red-100 whitespace-nowrap text-red-
|
|
57
|
+
<span v-if="record[column.name] === true" class="af-true-value-icon bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">{{ $t('Yes') }}</span>
|
|
58
|
+
<span v-else-if="record[column.name] === false" class="af-false-value-icon bg-red-100 whitespace-nowrap 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">{{ $t('No') }}</span>
|
|
32
59
|
<span v-else class="bg-gray-100 whitespace-nowrap text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-gray-400 border border-gray-400">{{ $t('Unset') }}</span>
|
|
33
60
|
</span>
|
|
34
61
|
<span
|
|
@@ -38,14 +65,14 @@
|
|
|
38
65
|
<template v-for="(arrayItem, arrayItemIndex) in record[column.name]">
|
|
39
66
|
<span
|
|
40
67
|
v-if="column.isArray.itemType === 'boolean' && arrayItem"
|
|
41
|
-
:key="`${column.name}-${arrayItemIndex}`"
|
|
42
|
-
class="bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">
|
|
68
|
+
:key="`${column.name}-${arrayItemIndex}-true`"
|
|
69
|
+
class="af-true-value-icon bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">
|
|
43
70
|
{{ $t('Yes') }}
|
|
44
71
|
</span>
|
|
45
72
|
<span
|
|
46
73
|
v-else-if="column.isArray.itemType === 'boolean'"
|
|
47
|
-
:key="`${column.name}-${arrayItemIndex}`"
|
|
48
|
-
class="bg-red-100 whitespace-nowrap 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">
|
|
74
|
+
:key="`${column.name}-${arrayItemIndex}-false`"
|
|
75
|
+
class="af-false-value-icon bg-red-100 whitespace-nowrap 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">
|
|
49
76
|
{{ $t('No') }}
|
|
50
77
|
</span>
|
|
51
78
|
<span
|
|
@@ -53,30 +80,30 @@
|
|
|
53
80
|
:key="`${column.name}-${arrayItemIndex}`"
|
|
54
81
|
class="rounded-md m-0.5 bg-lightAnnouncementBG dark:bg-darkAnnouncementBG text-lightAnnouncementText dark:text-darkAnnouncementText py-0.5 px-2.5 text-sm"
|
|
55
82
|
>
|
|
56
|
-
{{ checkEmptyValues(getArrayItemDisplayValue(arrayItem, column), route.meta.type) }}
|
|
83
|
+
{{ checkEmptyValues(getArrayItemDisplayValue(arrayItem, column), route.meta.type as "show" | "list") }}
|
|
57
84
|
</span>
|
|
58
85
|
</template>
|
|
59
86
|
</span>
|
|
60
87
|
<span v-else-if="column.enum">
|
|
61
|
-
{{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label || record[column.name], route.meta.type) }}
|
|
88
|
+
{{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label || record[column.name], route.meta.type as "show" | "list") }}
|
|
62
89
|
</span>
|
|
63
90
|
<span v-else-if="column.type === 'datetime'" class="whitespace-nowrap">
|
|
64
|
-
{{ checkEmptyValues(formatDateTime(record[column.name]), route.meta.type) }}
|
|
91
|
+
{{ checkEmptyValues(formatDateTime(record[column.name]), route.meta.type as "show" | "list") }}
|
|
65
92
|
</span>
|
|
66
93
|
<span v-else-if="column.type === 'date'" class="whitespace-nowrap">
|
|
67
|
-
{{ checkEmptyValues(formatDate(record[column.name]), route.meta.type) }}
|
|
94
|
+
{{ checkEmptyValues(formatDate(record[column.name]), route.meta.type as "show" | "list") }}
|
|
68
95
|
</span>
|
|
69
96
|
<span v-else-if="column.type === 'time'" class="whitespace-nowrap">
|
|
70
|
-
{{ checkEmptyValues(formatTime(record[column.name]), route.meta.type) }}
|
|
97
|
+
{{ checkEmptyValues(formatTime(record[column.name]), route.meta.type as "show" | "list") }}
|
|
71
98
|
</span>
|
|
72
99
|
<span v-else-if="column.type === 'decimal'">
|
|
73
|
-
{{ checkEmptyValues(record[column.name] && parseFloat(record[column.name]), route.meta.type) }}
|
|
100
|
+
{{ checkEmptyValues(record[column.name] && parseFloat(record[column.name]), route.meta.type as "show" | "list") }}
|
|
74
101
|
</span>
|
|
75
102
|
<span v-else-if="column.type === 'json'">
|
|
76
103
|
<JsonViewer class="min-w-[6rem]" :value="record[column.name]" :expandDepth="column.extra?.jsonCollapsedLevel" copyable sort :theme="coreStore.theme"/>
|
|
77
104
|
</span>
|
|
78
105
|
<span v-else>
|
|
79
|
-
{{ checkEmptyValues(record[column.name],route.meta.type) }}
|
|
106
|
+
{{ checkEmptyValues(record[column.name], route.meta.type as "show" | "list") }}
|
|
80
107
|
</span>
|
|
81
108
|
</div>
|
|
82
109
|
</template>
|
|
@@ -122,7 +149,7 @@ function formatTime(time: string) {
|
|
|
122
149
|
return dayjs(`0000-00-00 ${time}`).format(coreStore.config?.timeFormat || 'HH:mm:ss');
|
|
123
150
|
}
|
|
124
151
|
|
|
125
|
-
function getArrayItemDisplayValue(value, column) {
|
|
152
|
+
function getArrayItemDisplayValue(value: any, column: AdminForthResourceColumnCommon) {
|
|
126
153
|
if (column.isArray?.itemType === 'datetime') {
|
|
127
154
|
return formatDateTime(value);
|
|
128
155
|
} else if (column.isArray?.itemType === 'date') {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Toggle
|
|
3
|
+
:disabled="readonly"
|
|
4
|
+
@update:modelValue="$emit('update:value', $event)"
|
|
5
|
+
:modelValue="valueFromRecord"
|
|
6
|
+
>
|
|
7
|
+
<p>{{text}}</p>
|
|
8
|
+
</Toggle>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import Toggle from '@/afcl/Toggle.vue';
|
|
13
|
+
import type {
|
|
14
|
+
AdminForthResourceColumnCommon,
|
|
15
|
+
AdminForthResourceCommon,
|
|
16
|
+
AdminUser,
|
|
17
|
+
} from "@/types/Common";
|
|
18
|
+
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
value: boolean,
|
|
21
|
+
text: string,
|
|
22
|
+
column: AdminForthResourceColumnCommon,
|
|
23
|
+
record: any,
|
|
24
|
+
meta: any,
|
|
25
|
+
resource: AdminForthResourceCommon,
|
|
26
|
+
adminUser: AdminUser,
|
|
27
|
+
readonly: boolean
|
|
28
|
+
}>();
|
|
29
|
+
console.log(JSON.stringify(props));
|
|
30
|
+
console.log("Current mode:", props.meta?.mode)
|
|
31
|
+
defineEmits(['update:value']);
|
|
32
|
+
const valueFromRecord = props.record[props.column.name]
|
|
33
|
+
const editReadOnly = props.column.editReadonly;
|
|
34
|
+
</script>
|
package/dist/spa/src/i18n.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createApp } from 'vue';
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// taken from here https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization
|
|
6
|
-
function slavicPluralRule(choice, choicesLength, orgRule) {
|
|
6
|
+
function slavicPluralRule(choice: number, choicesLength: number, orgRule: any) {
|
|
7
7
|
if (choice === 0) {
|
|
8
8
|
return 0
|
|
9
9
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AdminForthResource, AdminForthResourceColumn } from '../types/
|
|
1
|
+
import type { AdminForthResource, AdminForthResourceColumn } from '../types/Back.js';
|
|
2
2
|
|
|
3
3
|
export type resourceById = {
|
|
4
4
|
[key: string]: AdminForthResource;
|
|
@@ -21,6 +21,7 @@ export type ResourceColumns = {
|
|
|
21
21
|
|
|
22
22
|
export type CoreConfig = {
|
|
23
23
|
brandName: string,
|
|
24
|
+
singleTheme?: 'light' | 'dark',
|
|
24
25
|
brandLogo: string,
|
|
25
26
|
title: string,
|
|
26
27
|
datesFormat: string,
|
|
@@ -33,12 +34,18 @@ export type CoreConfig = {
|
|
|
33
34
|
passwordHashField: string,
|
|
34
35
|
loginBackgroundImage: string,
|
|
35
36
|
loginBackgroundPosition: string,
|
|
37
|
+
removeBackgroundBlendMode: boolean,
|
|
36
38
|
userFullnameField: string,
|
|
37
39
|
},
|
|
38
40
|
emptyFieldPlaceholder?: {
|
|
39
41
|
show?: string,
|
|
40
42
|
list?: string,
|
|
41
43
|
} | string,
|
|
44
|
+
|
|
45
|
+
customHeadItems?: {
|
|
46
|
+
tagName: string;
|
|
47
|
+
attributes: { [key: string]: string | boolean };
|
|
48
|
+
}[],
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
|
|
@@ -29,7 +29,12 @@ export const useModalStore = defineStore('modal', () => {
|
|
|
29
29
|
onCancelFunction.value = func;
|
|
30
30
|
}
|
|
31
31
|
function setModalContent(content: ModalContentType) {
|
|
32
|
-
modalContent.value =
|
|
32
|
+
modalContent.value = {
|
|
33
|
+
title: content.title || 'title',
|
|
34
|
+
content: content.content || 'content',
|
|
35
|
+
acceptText: content.acceptText || 'acceptText',
|
|
36
|
+
cancelText: content.cancelText || 'cancelText',
|
|
37
|
+
};
|
|
33
38
|
}
|
|
34
39
|
function resetmodalState() {
|
|
35
40
|
isOpened.value = false;
|
|
@@ -12,19 +12,38 @@ export const useToastStore = defineStore('toast', () => {
|
|
|
12
12
|
watch(route, () => {
|
|
13
13
|
// on route change clear all toasts older then 5 seconds
|
|
14
14
|
const now = +new Date();
|
|
15
|
-
toasts.value = toasts.value.filter((t) => now - t.createdAt < 5000);
|
|
15
|
+
toasts.value = toasts.value.filter((t) => t?.timeout === 'unlimited' || now - t.createdAt < 5000);
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
const addToast = (toast: {
|
|
18
|
+
const addToast = (toast: {
|
|
19
|
+
message?: string;
|
|
20
|
+
messageHtml?: string;
|
|
21
|
+
variant: string;
|
|
22
|
+
timeout?: number | 'unlimited';
|
|
23
|
+
buttons?: { value: any; label: string }[];
|
|
24
|
+
onResolve?: (value?: any) => void;
|
|
25
|
+
}): string => {
|
|
19
26
|
const toastId = uuid();
|
|
20
27
|
toasts.value.push({
|
|
21
28
|
...toast,
|
|
22
29
|
id: toastId,
|
|
23
30
|
createdAt: +new Date(),
|
|
24
31
|
});
|
|
32
|
+
return toastId;
|
|
25
33
|
};
|
|
26
34
|
const removeToast = (toast: { id: string }) => {
|
|
27
35
|
toasts.value = toasts.value.filter((t) => t.id !== toast.id);
|
|
28
36
|
};
|
|
29
|
-
|
|
37
|
+
|
|
38
|
+
const resolveToast = (toastId: string, value?: any) => {
|
|
39
|
+
const t = toasts.value.find((x) => x.id === toastId);
|
|
40
|
+
try {
|
|
41
|
+
t?.onResolve?.(value);
|
|
42
|
+
} catch {
|
|
43
|
+
// no-op
|
|
44
|
+
}
|
|
45
|
+
toasts.value = toasts.value.filter((x) => x.id !== toastId);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return { toasts, addToast, removeToast, resolveToast };
|
|
30
49
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Express } from 'express';
|
|
1
|
+
import type { Express, Request } from 'express';
|
|
2
2
|
import type { Writable } from 'stream';
|
|
3
3
|
|
|
4
4
|
import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum,
|
|
@@ -8,12 +8,12 @@ import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections,
|
|
|
8
8
|
type AdminForthBulkActionCommon,
|
|
9
9
|
type AdminForthForeignResourceCommon,
|
|
10
10
|
type AdminForthResourceColumnCommon,
|
|
11
|
-
AdminForthResourceInputCommon,
|
|
12
|
-
AdminForthComponentDeclarationFull,
|
|
13
|
-
AdminForthConfigMenuItem,
|
|
14
|
-
AnnouncementBadgeResponse,
|
|
11
|
+
type AdminForthResourceInputCommon,
|
|
12
|
+
type AdminForthComponentDeclarationFull,
|
|
13
|
+
type AdminForthConfigMenuItem,
|
|
14
|
+
type AnnouncementBadgeResponse,
|
|
15
15
|
AdminForthResourcePages,
|
|
16
|
-
AdminForthResourceColumnInputCommon,
|
|
16
|
+
type AdminForthResourceColumnInputCommon,
|
|
17
17
|
} from './Common.js';
|
|
18
18
|
|
|
19
19
|
export interface ICodeInjector {
|
|
@@ -92,19 +92,36 @@ export interface IExpressHttpServer extends IHttpServer {
|
|
|
92
92
|
* Adds adminUser to request object if user is authorized. Drops request with 401 status if user is not authorized.
|
|
93
93
|
* @param callable : Function which will be called if user is authorized.
|
|
94
94
|
*
|
|
95
|
-
* Example:
|
|
96
95
|
*
|
|
96
|
+
* @example
|
|
97
97
|
* ```ts
|
|
98
|
-
* expressApp.get('/myApi', authorize((req, res) =>
|
|
98
|
+
* expressApp.get('/myApi', authorize((req, res) => {
|
|
99
99
|
* console.log('User is authorized', req.adminUser);
|
|
100
|
-
* res.json(
|
|
101
|
-
*
|
|
102
|
-
*
|
|
100
|
+
* res.json({ message: 'Hello World' });
|
|
101
|
+
* }));
|
|
102
|
+
* ```
|
|
103
103
|
*
|
|
104
|
-
*/
|
|
104
|
+
*/
|
|
105
105
|
authorize(callable: Function): void;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
export interface ITranslateFunction {
|
|
109
|
+
(
|
|
110
|
+
msg: string,
|
|
111
|
+
category: string,
|
|
112
|
+
params: any,
|
|
113
|
+
pluralizationNumber?: number
|
|
114
|
+
): Promise<string>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Omit <Request, 'param'> is used to remove 'param' method from Request type for correct docs generation
|
|
118
|
+
export interface IAdminUserExpressRequest extends Omit<Request, 'protocol' | 'param' | 'unshift'> {
|
|
119
|
+
adminUser: AdminUser;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ITranslateExpressRequest extends Omit<Request, 'protocol' | 'param' | 'unshift'> {
|
|
123
|
+
tr: ITranslateFunction;
|
|
124
|
+
}
|
|
108
125
|
|
|
109
126
|
export interface IAdminForthSingleFilter {
|
|
110
127
|
field?: string;
|
|
@@ -113,7 +130,9 @@ export interface IAdminForthSingleFilter {
|
|
|
113
130
|
| AdminForthFilterOperators.LTE | AdminForthFilterOperators.LIKE | AdminForthFilterOperators.ILIKE
|
|
114
131
|
| AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN;
|
|
115
132
|
value?: any;
|
|
133
|
+
rightField?: string;
|
|
116
134
|
insecureRawSQL?: string;
|
|
135
|
+
insecureRawNoSQL?: any;
|
|
117
136
|
}
|
|
118
137
|
export interface IAdminForthAndOrFilter {
|
|
119
138
|
operator: AdminForthFilterOperators.AND | AdminForthFilterOperators.OR;
|
|
@@ -336,7 +355,7 @@ export interface IAdminForth {
|
|
|
336
355
|
|
|
337
356
|
createResourceRecord(
|
|
338
357
|
params: { resource: AdminForthResource, record: any, adminUser: AdminUser, extra?: HttpExtra }
|
|
339
|
-
): Promise<{ error?: string, createdRecord?: any }>;
|
|
358
|
+
): Promise<{ error?: string, createdRecord?: any, newRecordId?: any }>;
|
|
340
359
|
|
|
341
360
|
updateResourceRecord(
|
|
342
361
|
params: { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser, extra?: HttpExtra }
|
|
@@ -474,7 +493,7 @@ export type BeforeDataSourceRequestFunction = (params: {
|
|
|
474
493
|
requestUrl: string,
|
|
475
494
|
},
|
|
476
495
|
adminforth: IAdminForth,
|
|
477
|
-
}) => Promise<{ok: boolean, error?: string}>;
|
|
496
|
+
}) => Promise<{ok: boolean, error?: string, newRecordId?: string}>;
|
|
478
497
|
|
|
479
498
|
/**
|
|
480
499
|
* Modify response to change how data is returned after fetching from database.
|
|
@@ -525,7 +544,7 @@ export type BeforeEditSaveFunction = (params: {
|
|
|
525
544
|
oldRecord: any,
|
|
526
545
|
adminforth: IAdminForth,
|
|
527
546
|
extra?: HttpExtra,
|
|
528
|
-
}) => Promise<{ok: boolean, error?: string}>;
|
|
547
|
+
}) => Promise<{ok: boolean, error?: string | null}>;
|
|
529
548
|
|
|
530
549
|
|
|
531
550
|
|
|
@@ -535,7 +554,7 @@ export type BeforeCreateSaveFunction = (params: {
|
|
|
535
554
|
record: any,
|
|
536
555
|
adminforth: IAdminForth,
|
|
537
556
|
extra?: HttpExtra,
|
|
538
|
-
}) => Promise<{ok: boolean, error?: string}>;
|
|
557
|
+
}) => Promise<{ok: boolean, error?: string | null, newRecordId?: string}>;
|
|
539
558
|
|
|
540
559
|
export type AfterCreateSaveFunction = (params: {
|
|
541
560
|
resource: AdminForthResource,
|
|
@@ -619,12 +638,23 @@ interface AdminForthInputConfigCustomization {
|
|
|
619
638
|
*/
|
|
620
639
|
brandName?: string,
|
|
621
640
|
|
|
641
|
+
/**
|
|
642
|
+
* Whether to use single theme for the app
|
|
643
|
+
*/
|
|
644
|
+
singleTheme?: 'light' | 'dark',
|
|
645
|
+
|
|
622
646
|
/**
|
|
623
647
|
* Whether to show brand name in sidebar
|
|
624
648
|
* default is true
|
|
625
649
|
*/
|
|
626
650
|
showBrandNameInSidebar?: boolean,
|
|
627
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Whether to show brand logo in sidebar
|
|
654
|
+
* default is true
|
|
655
|
+
*/
|
|
656
|
+
showBrandLogoInSidebar?: boolean,
|
|
657
|
+
|
|
628
658
|
/**
|
|
629
659
|
* Path to your app logo
|
|
630
660
|
*
|
|
@@ -757,8 +787,19 @@ interface AdminForthInputConfigCustomization {
|
|
|
757
787
|
userMenu?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
758
788
|
header?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
759
789
|
sidebar?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
790
|
+
sidebarTop?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
760
791
|
everyPageBottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
761
792
|
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Allows adding custom elements (e.g., <link>, <script>, <meta>) to the <head> of the HTML document.
|
|
796
|
+
* Each item must include a tag name and a set of attributes.
|
|
797
|
+
*/
|
|
798
|
+
customHeadItems?: {
|
|
799
|
+
tagName: string;
|
|
800
|
+
attributes: Record<string, string | boolean>;
|
|
801
|
+
}[];
|
|
802
|
+
|
|
762
803
|
}
|
|
763
804
|
|
|
764
805
|
export interface AdminForthActionInput {
|
|
@@ -935,6 +976,13 @@ export interface AdminForthInputConfig {
|
|
|
935
976
|
*/
|
|
936
977
|
loginBackgroundPosition?: 'over' | '1/2' | '1/3' | '2/3' | '3/4' | '2/5' | '3/5',
|
|
937
978
|
|
|
979
|
+
/**
|
|
980
|
+
* If true, background blend mode will be removed from login background image when position is 'over'
|
|
981
|
+
*
|
|
982
|
+
* Default: false
|
|
983
|
+
*/
|
|
984
|
+
removeBackgroundBlendMode?: boolean,
|
|
985
|
+
|
|
938
986
|
/**
|
|
939
987
|
* Function or functions which will be called before user try to login.
|
|
940
988
|
* Each function will resive User object as an argument
|
|
@@ -956,7 +1004,7 @@ export interface AdminForthInputConfig {
|
|
|
956
1004
|
/**
|
|
957
1005
|
* Any prompt to show users on login. Supports HTML.
|
|
958
1006
|
*/
|
|
959
|
-
loginPromptHTML?: string
|
|
1007
|
+
loginPromptHTML?: string | (() => string | void | undefined | Promise<string | void | undefined>) | undefined
|
|
960
1008
|
|
|
961
1009
|
/**
|
|
962
1010
|
* Remember me days for "Remember Me" checkbox on login page.
|
|
@@ -1062,8 +1110,15 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
|
|
|
1062
1110
|
userMenu: Array<AdminForthComponentDeclarationFull>,
|
|
1063
1111
|
header: Array<AdminForthComponentDeclarationFull>,
|
|
1064
1112
|
sidebar: Array<AdminForthComponentDeclarationFull>,
|
|
1113
|
+
sidebarTop: Array<AdminForthComponentDeclarationFull>,
|
|
1065
1114
|
everyPageBottom: Array<AdminForthComponentDeclarationFull>,
|
|
1066
1115
|
},
|
|
1116
|
+
|
|
1117
|
+
customHeadItems?: {
|
|
1118
|
+
tagName: string;
|
|
1119
|
+
attributes: Record<string, string | boolean>;
|
|
1120
|
+
}[];
|
|
1121
|
+
|
|
1067
1122
|
}
|
|
1068
1123
|
|
|
1069
1124
|
export interface AdminForthConfig extends Omit<AdminForthInputConfig, 'customization' | 'resources'> {
|
|
@@ -1111,6 +1166,21 @@ export class Filters {
|
|
|
1111
1166
|
static LIKE(field: string, value: any): IAdminForthSingleFilter {
|
|
1112
1167
|
return { field, operator: AdminForthFilterOperators.LIKE, value };
|
|
1113
1168
|
}
|
|
1169
|
+
static ILIKE(field: string, value: any): IAdminForthSingleFilter {
|
|
1170
|
+
return { field, operator: AdminForthFilterOperators.ILIKE, value };
|
|
1171
|
+
}
|
|
1172
|
+
static GT_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
|
|
1173
|
+
return { field: leftField, operator: AdminForthFilterOperators.GT, rightField };
|
|
1174
|
+
}
|
|
1175
|
+
static GTE_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
|
|
1176
|
+
return { field: leftField, operator: AdminForthFilterOperators.GTE, rightField };
|
|
1177
|
+
}
|
|
1178
|
+
static LT_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
|
|
1179
|
+
return { field: leftField, operator: AdminForthFilterOperators.LT, rightField };
|
|
1180
|
+
}
|
|
1181
|
+
static LTE_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
|
|
1182
|
+
return { field: leftField, operator: AdminForthFilterOperators.LTE, rightField };
|
|
1183
|
+
}
|
|
1114
1184
|
static AND(
|
|
1115
1185
|
...args: (IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>)[]
|
|
1116
1186
|
): IAdminForthAndOrFilter {
|
|
@@ -1318,9 +1388,13 @@ export interface AdminForthResource extends Omit<AdminForthResourceInput, 'optio
|
|
|
1318
1388
|
},
|
|
1319
1389
|
create?: {
|
|
1320
1390
|
/**
|
|
1391
|
+
* Should return `ok: true` to continue saving pipeline and allow creating record in database, and `ok: false` to interrupt pipeline and prevent record creation.
|
|
1392
|
+
* If you need to show error on UI, set `error: \<error message\>` in response.
|
|
1393
|
+
*
|
|
1321
1394
|
* Typical use-cases:
|
|
1322
|
-
* -
|
|
1323
|
-
* -
|
|
1395
|
+
* - Create record by custom code (return `{ ok: false, newRecordId: <id of created record from custom code> }`)
|
|
1396
|
+
* - Validate record before saving to database and interrupt execution if validation failed (return `{ ok: false, error: <validation error> }`), though `allowedActions.create` should be preferred in most cases
|
|
1397
|
+
* - fill-in adminUser as creator of record (set `record.<some field> = x; return \{ ok: true \}`)
|
|
1324
1398
|
* - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
|
|
1325
1399
|
*/
|
|
1326
1400
|
beforeSave?: Array<BeforeCreateSaveFunction>,
|
|
@@ -1471,15 +1545,27 @@ export type ShowInInput = ShowInModernInput | ShowInLegacyInput;
|
|
|
1471
1545
|
export type ShowIn = {
|
|
1472
1546
|
[key in AdminForthResourcePages]: AllowedActionValue
|
|
1473
1547
|
}
|
|
1548
|
+
export type BackendOnlyInput =
|
|
1549
|
+
| boolean
|
|
1550
|
+
| ((p: {
|
|
1551
|
+
adminUser: AdminUser;
|
|
1552
|
+
resource: AdminForthResource;
|
|
1553
|
+
meta: any;
|
|
1554
|
+
source: ActionCheckSource;
|
|
1555
|
+
adminforth: IAdminForth;
|
|
1556
|
+
}) => boolean | Promise<boolean>);
|
|
1557
|
+
|
|
1474
1558
|
|
|
1475
|
-
export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn'> {
|
|
1559
|
+
export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn' | 'backendOnly'> {
|
|
1476
1560
|
showIn?: ShowInInput,
|
|
1477
1561
|
foreignResource?: AdminForthForeignResource,
|
|
1562
|
+
backendOnly?: BackendOnlyInput;
|
|
1478
1563
|
}
|
|
1479
1564
|
|
|
1480
|
-
export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn'> {
|
|
1565
|
+
export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn' | 'backendOnly'> {
|
|
1481
1566
|
showIn?: ShowIn,
|
|
1482
1567
|
foreignResource?: AdminForthForeignResource,
|
|
1568
|
+
backendOnly?: BackendOnlyInput;
|
|
1483
1569
|
}
|
|
1484
1570
|
|
|
1485
1571
|
export interface IWebSocketClient {
|