adminforth 2.44.2-next.1 → 2.45.0-next.10
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/dist/dataConnectors/clickhouse.d.ts +16 -2
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +65 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts +12 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +87 -0
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts +12 -1
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +126 -1
- package/dist/dataConnectors/mysql.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 +8 -0
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/package-lock.json +103 -10
- package/dist/spa/package.json +2 -1
- package/dist/spa/pnpm-lock.yaml +298 -282
- package/dist/spa/src/App.vue +1 -0
- package/dist/spa/src/adminforth.ts +5 -3
- package/dist/spa/src/afcl/Modal.vue +4 -0
- package/dist/spa/src/components/AcceptModal.vue +51 -16
- package/dist/spa/src/components/CustomRangePicker.vue +47 -73
- package/dist/spa/src/components/ResourceListTable.vue +5 -3
- package/dist/spa/src/stores/modal.ts +5 -1
- package/dist/spa/src/types/Back.ts +6 -1
- package/dist/spa/src/types/FrontendAPI.ts +10 -0
- package/dist/spa/src/utils/listUtils.ts +1 -1
- package/dist/spa/src/utils/utils.ts +33 -23
- package/dist/spa/src/views/ListView.vue +1 -1
- package/dist/spa/src/views/ShowView.vue +1 -1
- package/dist/types/Back.d.ts +4 -0
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +8 -0
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/package.json +1 -1
package/dist/spa/src/App.vue
CHANGED
|
@@ -118,11 +118,13 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
118
118
|
|
|
119
119
|
confirm(params: Parameters<FrontendAPIInterface['confirm']>[0]): ReturnType<FrontendAPIInterface['confirm']> {
|
|
120
120
|
return new Promise((resolve, reject) => {
|
|
121
|
-
this.modalStore.setModalContent({
|
|
122
|
-
|
|
121
|
+
this.modalStore.setModalContent({
|
|
122
|
+
title: params.title,
|
|
123
|
+
guardMessage: params.guardMessage,
|
|
124
|
+
content: params.message,
|
|
123
125
|
contentHTML: params.messageHtml,
|
|
124
126
|
acceptText: params.yes || 'Yes',
|
|
125
|
-
cancelText: params.no || 'Cancel'
|
|
127
|
+
cancelText: params.no || 'Cancel'
|
|
126
128
|
})
|
|
127
129
|
this.modalStore.onAcceptFunction = resolve
|
|
128
130
|
this.modalStore.onCancelFunction = reject
|
|
@@ -4,26 +4,59 @@
|
|
|
4
4
|
ref="modalRef"
|
|
5
5
|
:beforeCloseFunction="()=>{modalStore.onAcceptFunction(false);modalStore.isOpened=false}"
|
|
6
6
|
backgroundCustomClasses="z-[998]"
|
|
7
|
-
modalCustomClasses="z-[999]"
|
|
7
|
+
modalCustomClasses="z-[999] flex items-center justify-center"
|
|
8
8
|
>
|
|
9
|
-
<div class="relative p-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<div class="relative p-6 sm:p-8 w-[440px] bg-white rounded-lg shadow-xl dark:bg-gray-800" >
|
|
10
|
+
|
|
11
|
+
<button type="button" @click="modalStore.togleModal()" class="absolute top-4 right-4 text-gray-400 hover:text-gray-900 transition-colors dark:hover:text-white" >
|
|
12
|
+
<svg class="w-3.5 h-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
12
13
|
<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"/>
|
|
13
14
|
</svg>
|
|
14
|
-
<span class="sr-only">{{ $t('Close modal') }}</span>
|
|
15
15
|
</button>
|
|
16
|
-
<div class="p-4 md:p-5 text-center">
|
|
17
|
-
<svg class="mx-auto mb-4 text-lightAcceptModalWarningIcon w-12 h-12 dark:text-darkAcceptModalWarningIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
|
18
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
|
19
|
-
</svg>
|
|
20
|
-
<h3 class="afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText">{{ modalStore?.modalContent?.content }}</h3>
|
|
21
|
-
<h3 class=" afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText" v-html="modalStore?.modalContent?.contentHTML"></h3>
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
<div class="text-center flex flex-col">
|
|
18
|
+
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-red-50 mb-4 relative dark:bg-red-900/20">
|
|
19
|
+
<IconClipboardDocumentSolid class="w-10 h-10 text-red-500" />
|
|
20
|
+
<div class="absolute bottom-1 right-1 bg-red-500 rounded-full w-[18px] h-[18px] flex items-center justify-center border-2 border-white dark:border-gray-800">
|
|
21
|
+
<span class="text-white text-[10px] font-medium">!</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="flex flex-col gap-3">
|
|
26
|
+
<h3 v-if="modalStore?.modalContent?.title" class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
27
|
+
{{ modalStore.modalContent.title }}
|
|
28
|
+
</h3>
|
|
29
|
+
|
|
30
|
+
<div class="text-[15px] text-gray-600 dark:text-gray-300">
|
|
31
|
+
<div v-if="modalStore?.modalContent?.contentHTML" class="font-medium" v-html="modalStore.modalContent.contentHTML"></div>
|
|
32
|
+
<p v-else-if="modalStore?.modalContent?.content" class="font-medium">{{ modalStore.modalContent.content }}</p>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<hr class="border-t-2 border-gray-300 dark:border-gray-700 my-6">
|
|
37
|
+
|
|
38
|
+
<div class="flex justify-center gap-4 w-full">
|
|
39
|
+
<button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-700 bg-white rounded-lg border border-gray-200 hover:bg-gray-50 focus:ring-4 focus:ring-gray-100 transition-all dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700">
|
|
40
|
+
{{ modalStore?.modalContent?.cancelText }}
|
|
41
|
+
</button>
|
|
42
|
+
|
|
43
|
+
<button
|
|
44
|
+
@click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}"
|
|
45
|
+
type="button"
|
|
46
|
+
class="flex-1 flex items-center justify-center py-2.5 px-4 text-sm font-medium transition-all focus:outline-none
|
|
47
|
+
text-white bg-red-600 hover:bg-red-700
|
|
48
|
+
dark:bg-red-500 dark:hover:bg-red-600
|
|
49
|
+
border-none rounded-lg shadow-sm focus:z-10 focus:ring-4
|
|
50
|
+
focus:ring-red-100 dark:focus:ring-red-900 gap-1">
|
|
51
|
+
{{ modalStore?.modalContent?.acceptText }}
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div v-if="modalStore?.modalContent?.guardMessage" class="flex items-center justify-center mt-6 text-xs text-gray-400 gap-1.5 font-medium">
|
|
56
|
+
<IconShieldCheck class="w-4 h-4" />
|
|
57
|
+
<span> {{ modalStore?.modalContent?.guardMessage }}</span>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
27
60
|
</div>
|
|
28
61
|
</div>
|
|
29
62
|
</Modal>
|
|
@@ -31,9 +64,11 @@
|
|
|
31
64
|
</template>
|
|
32
65
|
|
|
33
66
|
<script setup lang="ts">
|
|
34
|
-
import { watch,
|
|
67
|
+
import { watch, ref } from 'vue';
|
|
35
68
|
import { useModalStore } from '@/stores/modal';
|
|
36
69
|
import { Modal } from '@/afcl';
|
|
70
|
+
import { IconClipboardDocumentSolid, IconShieldCheck } from '@iconify-prerendered/vue-heroicons';
|
|
71
|
+
|
|
37
72
|
|
|
38
73
|
const modalRef = ref();
|
|
39
74
|
const modalStore = useModalStore();
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
v-model="end"
|
|
19
19
|
>
|
|
20
20
|
|
|
21
|
-
<div v-if="min && max" class="w-full px-2.5">
|
|
21
|
+
<div v-if="min !== undefined && max !== undefined" class="w-full px-2.5">
|
|
22
22
|
<RangePicker
|
|
23
23
|
:dot-size="20"
|
|
24
24
|
height="7.99px"
|
|
@@ -31,96 +31,70 @@
|
|
|
31
31
|
</div>
|
|
32
32
|
</template>
|
|
33
33
|
<script setup lang="ts">
|
|
34
|
-
import {computed,
|
|
34
|
+
import { computed, ref, watch } from "vue";
|
|
35
35
|
import debounce from 'debounce'
|
|
36
36
|
import RangePicker from './RangePicker.vue';
|
|
37
37
|
|
|
38
38
|
const props = defineProps<{
|
|
39
|
-
valueStart: number | null,
|
|
40
|
-
valueEnd: number | null,
|
|
41
|
-
min: number,
|
|
42
|
-
max: number,
|
|
39
|
+
valueStart: number | null | string,
|
|
40
|
+
valueEnd: number | null | string,
|
|
41
|
+
min: number | string | undefined,
|
|
42
|
+
max: number | string | undefined,
|
|
43
43
|
}>()
|
|
44
44
|
|
|
45
45
|
const emit = defineEmits(['update:valueStart', 'update:valueEnd']);
|
|
46
46
|
|
|
47
|
-
const minFormatted = computed(() =>
|
|
48
|
-
const
|
|
47
|
+
const minFormatted = computed(() => {
|
|
48
|
+
const v = Number(props.min);
|
|
49
|
+
return isNaN(v) ? 0 : Math.floor(v);
|
|
50
|
+
});
|
|
49
51
|
|
|
52
|
+
const maxFormatted = computed(() => {
|
|
53
|
+
const v = Number(props.max);
|
|
54
|
+
return isNaN(v) ? 100 : Math.ceil(v);
|
|
55
|
+
});
|
|
50
56
|
|
|
51
|
-
const
|
|
52
|
-
|
|
57
|
+
const normalize = (val: any) => {
|
|
58
|
+
if (val === "" || val === null || val === undefined) return null;
|
|
59
|
+
const numericValue = Number(val);
|
|
60
|
+
return isNaN(numericValue) ? null : numericValue;
|
|
61
|
+
};
|
|
53
62
|
|
|
54
|
-
const
|
|
63
|
+
const start = ref<number | null>(normalize(props.valueStart));
|
|
64
|
+
const end = ref<number | null>(normalize(props.valueEnd));
|
|
55
65
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
setSliderValues(start.value, maxFormatted.value);
|
|
61
|
-
} else if ( !start.value && !end.value ) {
|
|
62
|
-
setSliderValues(minFormatted.value, maxFormatted.value);
|
|
63
|
-
} else {
|
|
64
|
-
setSliderValues(start.value, end.value);
|
|
65
|
-
}
|
|
66
|
-
})
|
|
66
|
+
const sliderValue = ref<[number, number]>([
|
|
67
|
+
start.value ?? minFormatted.value,
|
|
68
|
+
end.value ?? maxFormatted.value
|
|
69
|
+
]);
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end.value = value[1] === maxFormatted.value ? null : value[1];
|
|
72
|
-
}, 500);
|
|
71
|
+
function setSliderValues(s: number | null, e: number | null) {
|
|
72
|
+
sliderValue.value = [s ?? minFormatted.value, e ?? maxFormatted.value];
|
|
73
|
+
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
watch([start, end], () => {
|
|
76
|
+
setSliderValues(start.value, end.value);
|
|
77
|
+
}, { immediate: true });
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
const updateFromSlider = debounce((value: [number, number]) => {
|
|
80
|
+
start.value = value[0] === minFormatted.value ? null : value[0];
|
|
81
|
+
end.value = value[1] === maxFormatted.value ? null : value[1];
|
|
82
|
+
}, 500);
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
84
|
+
watch(() => props.valueStart, (newVal) => {
|
|
85
|
+
const v = normalize(newVal);
|
|
86
|
+
if (v !== start.value) start.value = v;
|
|
87
|
+
});
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
start.value = props.valueStart;
|
|
92
|
-
setSliderValues(start.value, end.value)
|
|
93
|
-
}
|
|
89
|
+
watch(() => props.valueEnd, (newVal) => {
|
|
90
|
+
const v = normalize(newVal);
|
|
91
|
+
if (v !== end.value) end.value = v;
|
|
92
|
+
});
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
end.value = props.valueEnd;
|
|
100
|
-
setSliderValues(start.value, end.value)
|
|
101
|
-
}
|
|
94
|
+
watch(start, (newVal) => emit('update:valueStart', newVal));
|
|
95
|
+
watch(end, (newVal) => emit('update:valueEnd', newVal));
|
|
102
96
|
|
|
103
|
-
watch(
|
|
104
|
-
|
|
97
|
+
watch([minFormatted, maxFormatted], () => {
|
|
98
|
+
setSliderValues(start.value, end.value);
|
|
105
99
|
})
|
|
106
|
-
|
|
107
|
-
watch(end, () => {
|
|
108
|
-
emit('update:valueEnd', end.value);
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
watch([minFormatted,maxFormatted], () => {
|
|
112
|
-
if ( !start.value && end.value ) {
|
|
113
|
-
setSliderValues(minFormatted.value, end.value);
|
|
114
|
-
} else if ( start.value && !end.value ) {
|
|
115
|
-
setSliderValues(start.value, maxFormatted.value);
|
|
116
|
-
} else if ( !start.value && !end.value ) {
|
|
117
|
-
setSliderValues(minFormatted.value, maxFormatted.value);
|
|
118
|
-
} else {
|
|
119
|
-
setSliderValues(start.value, end.value);
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
function setSliderValues(start: any, end: any) {
|
|
124
|
-
sliderValue.value = [start || minFormatted.value, end || maxFormatted.value];
|
|
125
|
-
}
|
|
126
100
|
</script>
|
|
@@ -372,7 +372,7 @@ import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
|
372
372
|
const coreStore = useCoreStore();
|
|
373
373
|
const { t } = useI18n();
|
|
374
374
|
const { alert, confirm } = useAdminforth();
|
|
375
|
-
const props = defineProps<{
|
|
375
|
+
const props = withDefaults(defineProps<{
|
|
376
376
|
page: number,
|
|
377
377
|
resource: AdminForthResourceFrontend | null,
|
|
378
378
|
rows: any[] | null,
|
|
@@ -389,7 +389,9 @@ const props = defineProps<{
|
|
|
389
389
|
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration[]
|
|
390
390
|
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
391
391
|
isVirtualScrollEnabled: boolean
|
|
392
|
-
}>()
|
|
392
|
+
}>(), {
|
|
393
|
+
sort: () => []
|
|
394
|
+
});
|
|
393
395
|
|
|
394
396
|
//select between all rows or rows, that should be rendered in virtual scroll
|
|
395
397
|
const rowsToRender = computed(() => {
|
|
@@ -582,7 +584,7 @@ async function onClick(e: any, row: any) {
|
|
|
582
584
|
|
|
583
585
|
async function deleteRecord(row: any) {
|
|
584
586
|
const data = await confirm({
|
|
585
|
-
|
|
587
|
+
title: t('Are you sure you want to delete this item?'),
|
|
586
588
|
yes: t('Delete'),
|
|
587
589
|
no: t('Cancel'),
|
|
588
590
|
});
|
|
@@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
|
|
|
3
3
|
|
|
4
4
|
type ModalContentType = {
|
|
5
5
|
title?: string;
|
|
6
|
+
guardMessage?: string;
|
|
6
7
|
content?: string;
|
|
7
8
|
contentHTML?: string;
|
|
8
9
|
acceptText?: string;
|
|
@@ -13,6 +14,7 @@ import { defineStore } from 'pinia'
|
|
|
13
14
|
export const useModalStore = defineStore('modal', () => {
|
|
14
15
|
const modalContent = ref({
|
|
15
16
|
title: 'title',
|
|
17
|
+
guardMessage: 'guardMessage',
|
|
16
18
|
content: '',
|
|
17
19
|
contentHTML: '',
|
|
18
20
|
acceptText: 'acceptText',
|
|
@@ -32,7 +34,8 @@ export const useModalStore = defineStore('modal', () => {
|
|
|
32
34
|
}
|
|
33
35
|
function setModalContent(content: ModalContentType) {
|
|
34
36
|
modalContent.value = {
|
|
35
|
-
title: content.title || '
|
|
37
|
+
title: content.title || '',
|
|
38
|
+
guardMessage: content.guardMessage || '',
|
|
36
39
|
content: content.content || '',
|
|
37
40
|
contentHTML: content.contentHTML || '',
|
|
38
41
|
acceptText: content.acceptText || 'acceptText',
|
|
@@ -43,6 +46,7 @@ export const useModalStore = defineStore('modal', () => {
|
|
|
43
46
|
isOpened.value = false;
|
|
44
47
|
modalContent.value = {
|
|
45
48
|
title: 'title',
|
|
49
|
+
guardMessage: 'guardMessage',
|
|
46
50
|
content: 'content',
|
|
47
51
|
contentHTML: '',
|
|
48
52
|
acceptText: 'acceptText',
|
|
@@ -153,6 +153,11 @@ export interface IExpressHttpServer extends IHttpServer {
|
|
|
153
153
|
listen(port: number, callback: Function): void;
|
|
154
154
|
listen(port: number, host: string, callback: Function): void;
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Returns an internal HTTP origin for same-process/server API calls.
|
|
158
|
+
*/
|
|
159
|
+
getInternalApiOrigin(): string | undefined;
|
|
160
|
+
|
|
156
161
|
/**
|
|
157
162
|
* Method (middleware) to wrap express endpoints with authorization check.
|
|
158
163
|
* Adds adminUser to request object if user is authorized. Drops request with 401 status if user is not authorized.
|
|
@@ -2328,4 +2333,4 @@ export interface PluginsCommonOptions {
|
|
|
2328
2333
|
* It is required if you want to log custom actions, otherwise it is optional.
|
|
2329
2334
|
*/
|
|
2330
2335
|
id?: string;
|
|
2331
|
-
}
|
|
2336
|
+
}
|
|
@@ -160,6 +160,16 @@ export interface FrontendAPIInterface {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
export type ConfirmParams = {
|
|
163
|
+
/**
|
|
164
|
+
* The title to display in the dialog
|
|
165
|
+
*/
|
|
166
|
+
title?: string;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The message to display in the dialog as a warning that action is irreversible
|
|
170
|
+
*/
|
|
171
|
+
guardMessage?: string;
|
|
172
|
+
|
|
163
173
|
/**
|
|
164
174
|
* The message to display in the dialog
|
|
165
175
|
*/
|
|
@@ -6,7 +6,7 @@ import { useCoreStore } from '../stores/core';
|
|
|
6
6
|
import { useUserStore } from '../stores/user';
|
|
7
7
|
import { Dropdown } from 'flowbite';
|
|
8
8
|
import adminforth, { useAdminforth } from '../adminforth';
|
|
9
|
-
import
|
|
9
|
+
import DOMPurify from 'dompurify'
|
|
10
10
|
import debounce from 'debounce';
|
|
11
11
|
import type { AdminForthActionFront, AdminForthResourceColumnInputCommon, AdminForthResourceFrontend, Predicate } from '@/types/Common';
|
|
12
12
|
import { i18nInstance } from '../i18n'
|
|
@@ -337,26 +337,25 @@ export function humanifySize(size: number) {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
export function protectAgainstXSS(value: string) {
|
|
340
|
-
return
|
|
341
|
-
|
|
342
|
-
"address",
|
|
343
|
-
"h5",
|
|
344
|
-
"dl",
|
|
345
|
-
"ul",
|
|
346
|
-
"em",
|
|
347
|
-
"small",
|
|
348
|
-
"col",
|
|
340
|
+
return DOMPurify.sanitize(value, {
|
|
341
|
+
ALLOWED_TAGS: [
|
|
342
|
+
"address","article","aside","footer","header","h1","h2","h3","h4",
|
|
343
|
+
"h5","h6","hgroup","main","nav","section","blockquote","dd","div",
|
|
344
|
+
"dl","dt","figcaption","figure","hr","li","ol","p","pre",
|
|
345
|
+
"ul","a","abbr","b","bdi","bdo","br","cite","code","data","dfn",
|
|
346
|
+
"em","i","kbd","mark","q","rb","rp","rt","rtc","ruby","s","samp",
|
|
347
|
+
"small","span","strong","sub","sup","time","u","var","wbr","caption",
|
|
348
|
+
"col","colgroup","table","tbody","td","tfoot","th","thead","tr",
|
|
349
|
+
"img","video","source"
|
|
349
350
|
],
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
'*': [ 'data-*', 'aria-*', 'style' ]
|
|
359
|
-
},
|
|
351
|
+
ALLOWED_ATTR: [
|
|
352
|
+
"data-list",
|
|
353
|
+
"src","srcset","alt","title","width","height","loading",
|
|
354
|
+
"controls","autoplay","loop","muted","poster","playsinline","type",
|
|
355
|
+
"class","style"
|
|
356
|
+
],
|
|
357
|
+
ALLOW_DATA_ATTR: true,
|
|
358
|
+
ALLOW_ARIA_ATTR: true
|
|
360
359
|
});
|
|
361
360
|
}
|
|
362
361
|
|
|
@@ -707,7 +706,16 @@ export function generateMessageHtmlForRecordChange(changedFields: Record<string,
|
|
|
707
706
|
}).join('');
|
|
708
707
|
|
|
709
708
|
const listHtml = items ? `<ul class="mt-2 list-disc list-inside">${items}</ul>` : '';
|
|
710
|
-
const messageHtml =
|
|
709
|
+
const messageHtml = `
|
|
710
|
+
<div class="flex flex-col gap-y-2 text-center">
|
|
711
|
+
<div class="text-gray-500 dark:text-gray-400">
|
|
712
|
+
${listHtml}
|
|
713
|
+
</div>
|
|
714
|
+
<p class="font-medium text-gray-900 dark:text-white mt-4">
|
|
715
|
+
${escapeHtml(t('Are you sure you want to leave this page?'))}
|
|
716
|
+
</p>
|
|
717
|
+
</div>
|
|
718
|
+
`;
|
|
711
719
|
return messageHtml;
|
|
712
720
|
}
|
|
713
721
|
|
|
@@ -772,9 +780,11 @@ export async function onBeforeRouteLeaveCreateEditViewGuard(initialValues: any,
|
|
|
772
780
|
generateMessageHtmlForRecordChange(changedFields, t);
|
|
773
781
|
|
|
774
782
|
const answer = await confirm({
|
|
783
|
+
title: t('There are unsaved changes'),
|
|
784
|
+
guardMessage: t('Your changes will not be saved'),
|
|
775
785
|
messageHtml,
|
|
776
|
-
yes: t('
|
|
777
|
-
no: t('
|
|
786
|
+
yes: t('Leave without saving'),
|
|
787
|
+
no: t('Stay and continue'),
|
|
778
788
|
});
|
|
779
789
|
|
|
780
790
|
return answer;
|
|
@@ -224,7 +224,7 @@ import { getCustomComponent, initThreeDotsDropdown, getList, startBulkAction } f
|
|
|
224
224
|
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
225
225
|
import { Tooltip, Spinner } from '@/afcl'
|
|
226
226
|
import type { AdminForthComponentDeclaration, AdminForthComponentDeclarationFull, AdminForthFilterOperators, AdminForthResourceColumnCommon } from '@/types/Common';
|
|
227
|
-
|
|
227
|
+
import { useI18n } from 'vue-i18n';
|
|
228
228
|
|
|
229
229
|
import {
|
|
230
230
|
IconBanOutline,
|
|
@@ -310,7 +310,7 @@ const otherColumns = computed(() => {
|
|
|
310
310
|
|
|
311
311
|
async function deleteRecord() {
|
|
312
312
|
const data = await confirm({
|
|
313
|
-
|
|
313
|
+
title: t('Are you sure you want to delete this item?'),
|
|
314
314
|
yes: t('Delete'),
|
|
315
315
|
no: t('Cancel'),
|
|
316
316
|
});
|
package/dist/types/Back.d.ts
CHANGED
|
@@ -124,6 +124,10 @@ export interface IExpressHttpServer extends IHttpServer {
|
|
|
124
124
|
*/
|
|
125
125
|
listen(port: number, callback: Function): void;
|
|
126
126
|
listen(port: number, host: string, callback: Function): void;
|
|
127
|
+
/**
|
|
128
|
+
* Returns an internal HTTP origin for same-process/server API calls.
|
|
129
|
+
*/
|
|
130
|
+
getInternalApiOrigin(): string | undefined;
|
|
127
131
|
/**
|
|
128
132
|
* Method (middleware) to wrap express endpoints with authorization check.
|
|
129
133
|
* Adds adminUser to request object if user is authorized. Drops request with 401 status if user is not authorized.
|