adminforth 2.49.3 → 2.50.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/commands/createApp/templates/.agents/skills/adminforth/SKILL.md.hbs +42 -0
- package/commands/createApp/templates/.agents/skills/adminforth-custom-vue/SKILL.md.hbs +340 -0
- package/commands/createApp/templates/.agents/skills/adminforth-hooks/SKILL.md.hbs +70 -0
- package/commands/createApp/templates/.agents/skills/adminforth-permissions/SKILL.md.hbs +59 -0
- package/commands/createApp/templates/AGENTS.md.hbs +71 -0
- package/commands/createApp/templates/CLAUDE.md.hbs +5 -0
- package/commands/createApp/templates/Dockerfile.hbs +5 -16
- package/commands/createApp/templates/package.json.hbs +6 -18
- package/commands/createApp/templates/readme.md.hbs +5 -24
- package/commands/createApp/utils.js +52 -5
- 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/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +2 -1
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +3 -2
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +9 -1
- package/dist/modules/restApi.js.map +1 -1
- package/dist/servers/express.d.ts +2 -0
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +34 -7
- package/dist/servers/express.js.map +1 -1
- package/dist/servers/openapi.d.ts.map +1 -1
- package/dist/servers/openapi.js +2 -0
- package/dist/servers/openapi.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/adminforth.ts +5 -3
- package/dist/spa/src/afcl/Link.vue +24 -4
- 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/ResourceForm.vue +7 -0
- package/dist/spa/src/components/ResourceListTable.vue +6 -3
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/stores/modal.ts +5 -1
- package/dist/spa/src/types/Back.ts +10 -1
- package/dist/spa/src/types/Common.ts +1 -1
- package/dist/spa/src/types/FrontendAPI.ts +10 -0
- package/dist/spa/src/types/adapters/AudioAdapter.ts +73 -0
- package/dist/spa/src/types/adapters/index.ts +10 -0
- package/dist/spa/src/utils/listUtils.ts +4 -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 +2 -1
- package/dist/types/Back.d.ts +14 -1
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +1 -1
- package/dist/types/Common.d.ts.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/dist/types/adapters/AudioAdapter.d.ts +52 -0
- package/dist/types/adapters/AudioAdapter.d.ts.map +1 -0
- package/dist/types/adapters/AudioAdapter.js +2 -0
- package/dist/types/adapters/AudioAdapter.js.map +1 -0
- package/dist/types/adapters/index.d.ts +1 -0
- package/dist/types/adapters/index.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -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
|
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<a
|
|
3
|
+
v-if="isExternal"
|
|
4
|
+
v-bind="$attrs"
|
|
5
|
+
:href="to"
|
|
6
|
+
:target="target"
|
|
7
|
+
rel="noopener noreferrer"
|
|
8
|
+
:class="linkClasses"
|
|
9
|
+
>
|
|
10
|
+
<slot></slot>
|
|
11
|
+
</a>
|
|
12
|
+
|
|
2
13
|
<router-link
|
|
14
|
+
v-else
|
|
3
15
|
v-bind="$attrs"
|
|
4
16
|
:to="to"
|
|
5
|
-
|
|
6
|
-
|
|
17
|
+
:target="target"
|
|
18
|
+
:class="linkClasses"
|
|
7
19
|
>
|
|
8
20
|
<slot></slot>
|
|
9
21
|
</router-link>
|
|
10
22
|
</template>
|
|
11
23
|
|
|
12
24
|
<script setup lang="ts">
|
|
25
|
+
import { computed } from 'vue';
|
|
13
26
|
|
|
14
|
-
defineProps<{
|
|
27
|
+
const props = defineProps<{
|
|
15
28
|
to: string,
|
|
16
|
-
|
|
29
|
+
target?: 'blank' | 'self' | 'parent' | 'top'
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
const isExternal = computed(() => {
|
|
33
|
+
return typeof props.to === 'string' && props.to.startsWith('http');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const linkClasses = "afcl-link text-lightPrimary underline dark:text-darkPrimary hover:no-underline hover:brightness-110 cursor-pointer";
|
|
17
37
|
</script>
|
|
@@ -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>
|
|
@@ -490,6 +490,13 @@ async function validateUsingUserValidationFunction(editableColumnsInner: AdminFo
|
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
+
watch(customComponentsInValidity, () => {
|
|
494
|
+
editableColumns.value?.forEach(column => {
|
|
495
|
+
checkIfColumnHasError(column);
|
|
496
|
+
});
|
|
497
|
+
isValid.value = checkIfAnyColumnHasErrors();
|
|
498
|
+
});
|
|
499
|
+
|
|
493
500
|
defineExpose({
|
|
494
501
|
columnError,
|
|
495
502
|
editableColumns,
|
|
@@ -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,8 @@ 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?'),
|
|
588
|
+
message: t(`This process is irreversible.`),
|
|
586
589
|
yes: t('Delete'),
|
|
587
590
|
no: t('Cancel'),
|
|
588
591
|
});
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<img v-if="coreStore.config?.iconOnlySidebar?.logo" :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
|
|
33
33
|
<span
|
|
34
34
|
v-if="coreStore.config?.showBrandNameInSidebar && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))"
|
|
35
|
-
class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
|
|
35
|
+
class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText truncate"
|
|
36
36
|
>
|
|
37
37
|
{{ coreStore.config?.brandName }}
|
|
38
38
|
</span>
|
|
@@ -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',
|
|
@@ -62,6 +62,8 @@ export interface IAdminForthEndpointOptions {
|
|
|
62
62
|
request_schema?: AnySchemaObject,
|
|
63
63
|
response_schema?: AnySchemaObject,
|
|
64
64
|
responce_schema?: AnySchemaObject,
|
|
65
|
+
meta?: Record<string, unknown>,
|
|
66
|
+
target?: 'json' | 'upload',
|
|
65
67
|
handler: (input: IAdminForthEndpointHandlerInput) => void | Promise<any>,
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -82,14 +84,21 @@ export interface IAdminForthExpressRouteSchema {
|
|
|
82
84
|
* JSON schema or Zod schema describing the JSON response body for a custom Express route.
|
|
83
85
|
*/
|
|
84
86
|
response?: AdminForthExpressSchemaInput;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Internal metadata for AdminForth integrations. This is not rendered in the OpenAPI document.
|
|
90
|
+
*/
|
|
91
|
+
meta?: Record<string, unknown>;
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
export interface IRegisteredApiSchema {
|
|
88
95
|
method: string;
|
|
89
96
|
path: string;
|
|
90
97
|
description?: string;
|
|
98
|
+
meta?: Record<string, unknown>;
|
|
91
99
|
request_schema?: AnySchemaObject;
|
|
92
100
|
response_schema?: AnySchemaObject;
|
|
101
|
+
handler?: (input: IAdminForthEndpointHandlerInput) => void | Promise<any>;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
export interface IAdminForthApiValidationError {
|
|
@@ -1416,7 +1425,7 @@ export interface AdminForthActionInput {
|
|
|
1416
1425
|
adminUser: AdminUser;
|
|
1417
1426
|
standardAllowedActions: AllowedActions;
|
|
1418
1427
|
}) => boolean | Promise<boolean>);
|
|
1419
|
-
url?: string;
|
|
1428
|
+
url?: string | ((params: { adminUser: AdminUser; resource: AdminForthResource; recordId: string, record: any }) => string);
|
|
1420
1429
|
bulkHandler?: (params: {
|
|
1421
1430
|
adminforth: IAdminForth;
|
|
1422
1431
|
resource: AdminForthResource;
|
|
@@ -66,7 +66,7 @@ export enum ActionCheckSource {
|
|
|
66
66
|
EditRequest = 'editRequest',
|
|
67
67
|
CreateRequest = 'createRequest',
|
|
68
68
|
DeleteRequest = 'deleteRequest',
|
|
69
|
-
BulkActionRequest = 'bulkActionRequest',
|
|
69
|
+
BulkActionRequest = 'bulkActionRequest', // @deprecated beacuse whole bulk action is deprecated in favor of custom actions, never use this value in new code
|
|
70
70
|
CustomActionRequest = 'customActionRequest',
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -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
|
*/
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type SpeechToTextInput = {
|
|
2
|
+
buffer: Buffer;
|
|
3
|
+
filename: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
language?: string;
|
|
6
|
+
prompt?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type SpeechToTextResult = {
|
|
10
|
+
text: string;
|
|
11
|
+
language?: string;
|
|
12
|
+
raw?: unknown;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface SpeechToTextAdapter {
|
|
16
|
+
name: string;
|
|
17
|
+
|
|
18
|
+
validate(): void;
|
|
19
|
+
|
|
20
|
+
transcribe(input: SpeechToTextInput): Promise<SpeechToTextResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TtsAudioFormat =
|
|
24
|
+
| "mp3"
|
|
25
|
+
| "opus"
|
|
26
|
+
| "aac"
|
|
27
|
+
| "flac"
|
|
28
|
+
| "wav"
|
|
29
|
+
| "pcm";
|
|
30
|
+
|
|
31
|
+
export type TextToSpeechInput<Voice extends string = string> = {
|
|
32
|
+
text: string;
|
|
33
|
+
voice?: Voice;
|
|
34
|
+
format?: TtsAudioFormat;
|
|
35
|
+
speed?: number;
|
|
36
|
+
instructions?: string;
|
|
37
|
+
stream?: false;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type TextToSpeechResult = {
|
|
41
|
+
audio: Buffer;
|
|
42
|
+
mimeType: string;
|
|
43
|
+
format: TtsAudioFormat;
|
|
44
|
+
raw?: unknown;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type TtsStreamFormat = "audio" | "sse";
|
|
48
|
+
|
|
49
|
+
export type TextToSpeechStreamInput<Voice extends string = string> =
|
|
50
|
+
Omit<TextToSpeechInput<Voice>, "stream"> & {
|
|
51
|
+
stream: true;
|
|
52
|
+
streamFormat?: TtsStreamFormat;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type TextToSpeechStreamResult = {
|
|
56
|
+
audioStream: ReadableStream<Uint8Array>;
|
|
57
|
+
mimeType: string;
|
|
58
|
+
format: TtsAudioFormat;
|
|
59
|
+
streamFormat: TtsStreamFormat;
|
|
60
|
+
raw?: unknown;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export interface TextToSpeechAdapter<Voice extends string = string> {
|
|
64
|
+
name: string;
|
|
65
|
+
|
|
66
|
+
validate(): void;
|
|
67
|
+
|
|
68
|
+
synthesize(input: TextToSpeechStreamInput<Voice>): Promise<TextToSpeechStreamResult>;
|
|
69
|
+
synthesize(input: TextToSpeechInput<Voice>): Promise<TextToSpeechResult>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type AudioAdapter<Voice extends string = string> =
|
|
73
|
+
SpeechToTextAdapter & TextToSpeechAdapter<Voice>;
|
|
@@ -15,3 +15,13 @@ export type { ImageVisionAdapter } from './ImageVisionAdapter.js';
|
|
|
15
15
|
export type { OAuth2Adapter } from './OAuth2Adapter.js';
|
|
16
16
|
export type { StorageAdapter } from './StorageAdapter.js';
|
|
17
17
|
export type { CaptchaAdapter } from './CaptchaAdapter.js';
|
|
18
|
+
export type {
|
|
19
|
+
AudioAdapter,
|
|
20
|
+
SpeechToTextAdapter,
|
|
21
|
+
SpeechToTextInput,
|
|
22
|
+
SpeechToTextResult,
|
|
23
|
+
TextToSpeechAdapter,
|
|
24
|
+
TextToSpeechInput,
|
|
25
|
+
TextToSpeechResult,
|
|
26
|
+
TtsAudioFormat,
|
|
27
|
+
} from './AudioAdapter.js';
|
|
@@ -3,6 +3,7 @@ import { callAdminForthApi } from '@/utils';
|
|
|
3
3
|
import { type AdminForthResourceFrontend } from '../types/Common';
|
|
4
4
|
import { useAdminforth } from '@/adminforth';
|
|
5
5
|
import { showErrorTost } from '@/composables/useFrontendApi'
|
|
6
|
+
import { useI18n } from 'vue-i18n';
|
|
6
7
|
|
|
7
8
|
let getResourceDataLastAbortController: AbortController | null = null;
|
|
8
9
|
export async function getList(resource: AdminForthResourceFrontend, isPageLoaded: boolean, page: number | null , pageSize: number, sort: any, checkboxes:{ value: any[] }, filters: any = [] ) {
|
|
@@ -57,10 +58,12 @@ export async function startBulkAction(actionId: string, resource: AdminForthReso
|
|
|
57
58
|
bulkActionLoadingStates: {value: Record<string, boolean>}, getListInner: () => Promise<any>) {
|
|
58
59
|
const action = resource?.options?.bulkActions?.find(a => a.id === actionId);
|
|
59
60
|
const { confirm, alert } = useAdminforth();
|
|
61
|
+
const { t } = useI18n();
|
|
60
62
|
|
|
61
63
|
if (action?.confirm) {
|
|
62
64
|
const confirmed = await confirm({
|
|
63
|
-
|
|
65
|
+
title: action.confirm,
|
|
66
|
+
message: t(`Deleting ${checkboxes.value.length} ${checkboxes.value.length === 1 ? 'item' : 'items'}. This process is irreversible.`),
|
|
64
67
|
});
|
|
65
68
|
if (!confirmed) {
|
|
66
69
|
return;
|