adminforth 2.4.0 → 2.5.1
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 +1 -1
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/utils.js +27 -2
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +1 -8
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.js +2 -2
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/spa/src/App.vue +1 -1
- package/dist/spa/src/afcl/Button.vue +3 -3
- package/dist/spa/src/afcl/Input.vue +2 -2
- package/dist/spa/src/afcl/JsonViewer.vue +25 -0
- package/dist/spa/src/afcl/Select.vue +1 -2
- package/dist/spa/src/afcl/Table.vue +8 -8
- package/dist/spa/src/afcl/index.ts +1 -1
- package/dist/spa/src/components/AcceptModal.vue +4 -4
- package/dist/spa/src/components/Filters.vue +1 -1
- package/dist/spa/src/components/ResourceListTable.vue +15 -16
- package/dist/spa/src/components/ResourceListTableVirtual.vue +6 -6
- package/dist/spa/src/components/ShowTable.vue +7 -4
- package/dist/spa/src/components/ValueRenderer.vue +1 -1
- package/dist/spa/src/types/Back.ts +6 -6
- package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +29 -0
- package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -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 +5 -0
- package/dist/spa/src/views/CreateView.vue +2 -2
- package/dist/spa/src/views/ListView.vue +2 -2
- package/dist/spa/src/views/ShowView.vue +3 -3
- package/dist/types/Back.d.ts +5 -5
- 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 +21 -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/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 +6 -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 +2 -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,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
|
-
<div class="afcl-input flex z-0 relative" :class="{'opacity-50' : readonly}">
|
|
3
|
+
<div class="afcl-input-wrapper flex z-0 relative" :class="{'opacity-50' : readonly}">
|
|
4
4
|
<span
|
|
5
5
|
v-if="$slots.prefix || prefix"
|
|
6
6
|
class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border border-s-0 border-gray-300 rounded-s-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
@input="$emit('update:modelValue', $event.target?.value)"
|
|
16
16
|
:value="modelValue"
|
|
17
17
|
aria-describedby="helper-text-explanation"
|
|
18
|
-
class="inline-flex bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
|
|
18
|
+
class="afcl-input inline-flex bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
|
|
19
19
|
blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 dark:text-white translate-y-0"
|
|
20
20
|
:class="{'rounded-l-md': !$slots.prefix && !prefix, 'rounded-r-md': !$slots.suffix && !suffix, 'w-full': fullWidth}"
|
|
21
21
|
:disabled="readonly"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<JsonViewer
|
|
3
|
+
class="afcl-json-viewer min-w-[6rem]"
|
|
4
|
+
:value="value"
|
|
5
|
+
:expandDepth="expandDepth"
|
|
6
|
+
copyable
|
|
7
|
+
sort
|
|
8
|
+
:theme="currentTheme"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { computed } from 'vue'
|
|
14
|
+
import { JsonViewer } from 'vue3-json-viewer'
|
|
15
|
+
import { useCoreStore } from '@/stores/core'
|
|
16
|
+
|
|
17
|
+
defineProps<{
|
|
18
|
+
value: any
|
|
19
|
+
expandDepth?: number
|
|
20
|
+
}>()
|
|
21
|
+
|
|
22
|
+
const coreStore = useCoreStore()
|
|
23
|
+
|
|
24
|
+
const currentTheme = computed(() => (coreStore.theme === 'dark' ? 'dark' : 'light'))
|
|
25
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="afcl-select relative inline-block" ref="internalSelect"
|
|
2
|
+
<div class="afcl-select afcl-select-wrapper relative inline-block" ref="internalSelect"
|
|
3
3
|
:class="{'opacity-50': readonly}"
|
|
4
4
|
>
|
|
5
5
|
<div class="relative">
|
|
@@ -74,7 +74,6 @@
|
|
|
74
74
|
<div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-gray-400 dark:text-gray-300">
|
|
75
75
|
{{ options.length ? $t('No results found') : $t('No items here') }}
|
|
76
76
|
</div>
|
|
77
|
-
|
|
78
77
|
<div v-if="$slots['extra-item']" class="px-4 py-2 dark:text-gray-400">
|
|
79
78
|
<slot name="extra-item"></slot>
|
|
80
79
|
</div>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
|
-
<div class="relative overflow-x-auto shadow-md sm:rounded-lg">
|
|
4
|
-
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
5
|
-
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
|
3
|
+
<div class="afcl-table-container relative overflow-x-auto shadow-md sm:rounded-lg">
|
|
4
|
+
<table class="afcl-table w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
5
|
+
<thead class="afcl-table-thread text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
|
6
6
|
<tr>
|
|
7
7
|
<th scope="col" class="px-6 py-3"
|
|
8
8
|
v-for="column in columns"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<tr
|
|
20
20
|
v-for="(item, index) in dataPage"
|
|
21
21
|
:class="{
|
|
22
|
-
'odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800': evenHighlights,
|
|
22
|
+
'afcl-table-body odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800': evenHighlights,
|
|
23
23
|
'border-b dark:border-gray-700': index !== dataPage.length - 1 || totalPages > 1,
|
|
24
24
|
}"
|
|
25
25
|
>
|
|
@@ -38,24 +38,24 @@
|
|
|
38
38
|
</tr>
|
|
39
39
|
</tbody>
|
|
40
40
|
</table>
|
|
41
|
-
<nav class="flex items-center flex-column flex-wrap md:flex-row justify-between p-4"
|
|
41
|
+
<nav class="afcl-table-pagination-container flex items-center flex-column flex-wrap md:flex-row justify-between p-4"
|
|
42
42
|
v-if="totalPages > 1"
|
|
43
43
|
:aria-label="$t('Table navigation')">
|
|
44
44
|
<i18n-t
|
|
45
|
-
keypath="Showing {from} to {to} of {total}" tag="span" class="text-sm font-normal text-gray-500 dark:text-gray-400 mb-4 md:mb-0 block w-full md:inline md:w-auto"
|
|
45
|
+
keypath="Showing {from} to {to} of {total}" tag="span" class="afcl-table-pagination-text text-sm font-normal text-gray-500 dark:text-gray-400 mb-4 md:mb-0 block w-full md:inline md:w-auto"
|
|
46
46
|
>
|
|
47
47
|
<template #from><span class="font-semibold text-gray-900 dark:text-white">{{ Math.min((currentPage - 1) * props.pageSize + 1, props.data.length) }}</span></template>
|
|
48
48
|
<template #to><span class="font-semibold text-gray-900 dark:text-white">{{ Math.min(currentPage * props.pageSize, props.data.length) }}</span></template>
|
|
49
49
|
<template #total><span class="font-semibold text-gray-900 dark:text-white">{{ props.data.length }}</span></template>
|
|
50
50
|
</i18n-t>
|
|
51
51
|
|
|
52
|
-
<ul class="inline-flex -space-x-px rtl:space-x-reverse text-sm h-8">
|
|
52
|
+
<ul class="afcl-table-pagination-list inline-flex -space-x-px rtl:space-x-reverse text-sm h-8">
|
|
53
53
|
<li v-for="page in totalPages" :key="page">
|
|
54
54
|
<a href="#"
|
|
55
55
|
@click.prevent="switchPage(page)"
|
|
56
56
|
:aria-current="page === page ? 'page' : undefined"
|
|
57
57
|
:class='{
|
|
58
|
-
"text-blue-600 bg-lightPrimary text-lightPrimaryContrast dark:bg-darkPrimary dark:text-darkPrimaryContrast hover:opacity-90": page === currentPage,
|
|
58
|
+
"afcl-table-pagination-button text-blue-600 bg-lightPrimary text-lightPrimaryContrast dark:bg-darkPrimary dark:text-darkPrimaryContrast hover:opacity-90": page === currentPage,
|
|
59
59
|
"text-gray-500 border bg-white border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white": page !== currentPage,
|
|
60
60
|
"rounded-s-lg ms-0": page === 1,
|
|
61
61
|
"rounded-e-lg": page === totalPages,
|
|
@@ -19,6 +19,6 @@ export { default as Skeleton } from './Skeleton.vue';
|
|
|
19
19
|
export { default as Dialog } from './Dialog.vue';
|
|
20
20
|
export { default as MixedChart } from './MixedChart.vue';
|
|
21
21
|
export { default as CountryFlag } from './CountryFlag.vue';
|
|
22
|
-
|
|
22
|
+
export { default as JsonViewer } from './JsonViewer.vue';
|
|
23
23
|
|
|
24
24
|
|
|
@@ -8,7 +8,7 @@ const modalStore = useModalStore();
|
|
|
8
8
|
<Teleport to="body">
|
|
9
9
|
<div v-if="modalStore.isOpened" class="bg-gray-900/50 dark:bg-gray-900/80 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-full max-h-full">
|
|
10
10
|
<div class="relative p-4 w-full max-w-md max-h-full top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 " >
|
|
11
|
-
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black">
|
|
11
|
+
<div class="afcl-confirmation-container relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black">
|
|
12
12
|
<button type="button"@click="modalStore.togleModal" class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" >
|
|
13
13
|
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
14
14
|
<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"/>
|
|
@@ -19,11 +19,11 @@ const modalStore = useModalStore();
|
|
|
19
19
|
<svg class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
|
20
20
|
<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"/>
|
|
21
21
|
</svg>
|
|
22
|
-
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ modalStore?.modalContent?.content }}</h3>
|
|
23
|
-
<button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
|
|
22
|
+
<h3 class="afcl-confirmation-title mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ modalStore?.modalContent?.content }}</h3>
|
|
23
|
+
<button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="afcl-confirmation-accept-button text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
|
|
24
24
|
{{ modalStore?.modalContent?.acceptText }}
|
|
25
25
|
</button>
|
|
26
|
-
<button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">{{ modalStore?.modalContent?.cancelText }}</button>
|
|
26
|
+
<button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="afcl-confirmation-cancel-button py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">{{ modalStore?.modalContent?.cancelText }}</button>
|
|
27
27
|
</div>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<!-- drawer component -->
|
|
3
3
|
<div id="drawer-navigation"
|
|
4
4
|
|
|
5
|
-
class="fixed
|
|
5
|
+
class="af-filters-sidebar fixed right-0 z-50 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl dark:shadow-gray-900"
|
|
6
6
|
|
|
7
7
|
:class="show ? 'top-0 transform-none' : ''"
|
|
8
8
|
tabindex="-1" aria-labelledby="drawer-navigation-label"
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
<!-- skelet loader -->
|
|
7
7
|
<div role="status" v-if="!resource || !resource.columns"
|
|
8
8
|
class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded shadow animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700">
|
|
9
|
-
|
|
10
9
|
<div role="status" class="max-w-sm animate-pulse">
|
|
11
10
|
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
|
|
12
11
|
</div>
|
|
@@ -128,7 +127,7 @@
|
|
|
128
127
|
}"
|
|
129
128
|
|
|
130
129
|
>
|
|
131
|
-
<IconEyeSolid class="w-5 h-5 me-2"/>
|
|
130
|
+
<IconEyeSolid class="af-show-icon w-5 h-5 me-2"/>
|
|
132
131
|
</RouterLink>
|
|
133
132
|
|
|
134
133
|
<template v-slot:tooltip>
|
|
@@ -147,7 +146,7 @@
|
|
|
147
146
|
}
|
|
148
147
|
}"
|
|
149
148
|
>
|
|
150
|
-
<IconPenSolid class="w-5 h-5 me-2"/>
|
|
149
|
+
<IconPenSolid class="af-edit-icon w-5 h-5 me-2"/>
|
|
151
150
|
</RouterLink>
|
|
152
151
|
<template v-slot:tooltip>
|
|
153
152
|
{{ $t('Edit item') }}
|
|
@@ -159,7 +158,7 @@
|
|
|
159
158
|
v-if="resource.options?.allowedActions.delete"
|
|
160
159
|
@click="deleteRecord(row)"
|
|
161
160
|
>
|
|
162
|
-
<IconTrashBinSolid class="w-5 h-5 me-2"/>
|
|
161
|
+
<IconTrashBinSolid class="af-delete-icon w-5 h-5 me-2"/>
|
|
163
162
|
</button>
|
|
164
163
|
|
|
165
164
|
<template v-slot:tooltip>
|
|
@@ -199,14 +198,14 @@
|
|
|
199
198
|
<!-- pagination
|
|
200
199
|
totalRows in v-if is used to not hide page input during loading when user puts cursor into it and edit directly (rows gets null there during edit)
|
|
201
200
|
-->
|
|
202
|
-
<div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3"
|
|
203
|
-
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
204
|
-
>
|
|
201
|
+
<div class="af-pagination-container flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
|
|
205
202
|
|
|
206
|
-
<div class="inline-flex "
|
|
203
|
+
<div class="af-pagination-buttons-container inline-flex "
|
|
204
|
+
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
205
|
+
>
|
|
207
206
|
<!-- Buttons -->
|
|
208
207
|
<button
|
|
209
|
-
class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
208
|
+
class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
210
209
|
@click="page--; pageInput = page.toString();" :disabled="page <= 1">
|
|
211
210
|
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
212
211
|
viewBox="0 0 14 10">
|
|
@@ -218,14 +217,14 @@
|
|
|
218
217
|
</span>
|
|
219
218
|
</button>
|
|
220
219
|
<button
|
|
221
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
220
|
+
class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
222
221
|
@click="page = 1; pageInput = page.toString();" :disabled="page <= 1">
|
|
223
222
|
<!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
|
|
224
223
|
1
|
|
225
224
|
</button>
|
|
226
225
|
<div
|
|
227
226
|
contenteditable="true"
|
|
228
|
-
class="min-w-10 outline-none inline-block w-auto min-w-10 py-1.5 px-3 text-sm text-center text-gray-700 border border-gray-300 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 z-10"
|
|
227
|
+
class="af-pagination-input min-w-10 outline-none inline-block w-auto min-w-10 py-1.5 px-3 text-sm text-center text-gray-700 border border-gray-300 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 z-10"
|
|
229
228
|
@keydown="onPageKeydown($event)"
|
|
230
229
|
@input="onPageInput($event)"
|
|
231
230
|
@blur="validatePageInput()"
|
|
@@ -234,14 +233,14 @@
|
|
|
234
233
|
</div>
|
|
235
234
|
|
|
236
235
|
<button
|
|
237
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
236
|
+
class="af-pagination-last-page-button flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
238
237
|
@click="page = totalPages; pageInput = page.toString();" :disabled="page >= totalPages">
|
|
239
238
|
{{ totalPages }}
|
|
240
239
|
|
|
241
240
|
<!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
|
|
242
241
|
</button>
|
|
243
242
|
<button
|
|
244
|
-
class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
243
|
+
class="af-pagination-next-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
245
244
|
@click="page++; pageInput = page.toString();" :disabled="page >= totalPages">
|
|
246
245
|
<span class="hidden sm:inline">{{ $t('Next') }}</span>
|
|
247
246
|
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
@@ -254,10 +253,10 @@
|
|
|
254
253
|
|
|
255
254
|
<!-- Help text -->
|
|
256
255
|
<span class="text-sm text-gray-700 dark:text-gray-400">
|
|
257
|
-
<span v-if="((page || 1) - 1) * pageSize + 1 > totalRows">{{ $t('Wrong Page') }} </span>
|
|
258
|
-
<template v-else>
|
|
256
|
+
<span v-if="((((page || 1) - 1) * pageSize + 1 > totalRows) && totalRows > 0)">{{ $t('Wrong Page') }} </span>
|
|
257
|
+
<template v-else-if="resource && totalRows > 0">
|
|
259
258
|
|
|
260
|
-
<span class="hidden sm:inline">
|
|
259
|
+
<span class="af-pagination-info hidden sm:inline">
|
|
261
260
|
<i18n-t keypath="Showing {from} to {to} of {total} Entries" tag="p" >
|
|
262
261
|
<template v-slot:from>
|
|
263
262
|
<strong>{{ from }}</strong>
|
|
@@ -216,11 +216,11 @@
|
|
|
216
216
|
<!-- pagination
|
|
217
217
|
totalRows in v-if is used to not hide page input during loading when user puts cursor into it and edit directly (rows gets null there during edit)
|
|
218
218
|
-->
|
|
219
|
-
<div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3"
|
|
220
|
-
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
221
|
-
>
|
|
219
|
+
<div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
|
|
222
220
|
|
|
223
|
-
<div class="inline-flex "
|
|
221
|
+
<div class="inline-flex "
|
|
222
|
+
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
223
|
+
>
|
|
224
224
|
<!-- Buttons -->
|
|
225
225
|
<button
|
|
226
226
|
class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
@@ -271,8 +271,8 @@
|
|
|
271
271
|
|
|
272
272
|
<!-- Help text -->
|
|
273
273
|
<span class="text-sm text-gray-700 dark:text-gray-400">
|
|
274
|
-
<span v-if="((page || 1) - 1) * pageSize + 1 > totalRows">{{ $t('Wrong Page') }} </span>
|
|
275
|
-
<template v-else>
|
|
274
|
+
<span v-if="((((page || 1) - 1) * pageSize + 1 > totalRows) && totalRows > 0)">{{ $t('Wrong Page') }} </span>
|
|
275
|
+
<template v-else-if="resource && totalRows > 0">
|
|
276
276
|
|
|
277
277
|
<span class="hidden sm:inline">
|
|
278
278
|
<i18n-t keypath="Showing {from} to {to} of {total} Entries" tag="p" >
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
2
|
+
<div :class="`overflow-x-auto ${isRounded ? 'rounded-default' : ''} shadow-resourseFormShadow dark:shadow-darkResourseFormShadow`">
|
|
3
3
|
<div v-if="groupName && !noTitle" class="text-md font-semibold px-6 py-3 flex flex-1 items-center dark:border-gray-600 text-gray-700 bg-lightFormHeading dark:bg-gray-700 dark:text-gray-400 rounded-t-lg">
|
|
4
4
|
{{ groupName }}
|
|
5
5
|
</div>
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
import { getCustomComponent } from '@/utils';
|
|
61
61
|
import { useCoreStore } from '@/stores/core';
|
|
62
62
|
import { computed } from 'vue';
|
|
63
|
-
const props = defineProps<{
|
|
63
|
+
const props = withDefaults(defineProps<{
|
|
64
64
|
columns: Array<{
|
|
65
65
|
name: string;
|
|
66
66
|
label: string;
|
|
@@ -80,8 +80,11 @@
|
|
|
80
80
|
noTitle?: boolean;
|
|
81
81
|
resource: Record<string, any>;
|
|
82
82
|
record: Record<string, any>;
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
isRounded?: boolean;
|
|
84
|
+
}>(), {
|
|
85
|
+
isRounded: true
|
|
86
|
+
});
|
|
87
|
+
|
|
85
88
|
const coreStore = useCoreStore();
|
|
86
89
|
const allColumnsHaveCustomComponent = computed(() => {
|
|
87
90
|
return props.columns.every(column => {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
<span v-else-if="column.type === 'boolean'">
|
|
30
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-
|
|
31
|
+
<span v-else-if="record[column.name] === false" 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">{{ $t('No') }}</span>
|
|
32
32
|
<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
33
|
</span>
|
|
34
34
|
<span
|
|
@@ -92,16 +92,16 @@ 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
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface CompletionAdapter {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This method is called to validate the configuration of the adapter
|
|
5
|
+
* and should throw a clear user-readbale error if the configuration is invalid.
|
|
6
|
+
*/
|
|
7
|
+
validate(): void;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This method should return a text completion based on the provided content and stop sequence.
|
|
11
|
+
* @param content - The input text to complete
|
|
12
|
+
* @param stop - An array of stop sequences to indicate where to stop the completion
|
|
13
|
+
* @param maxTokens - The maximum number of tokens to generate
|
|
14
|
+
* @returns A promise that resolves to an object containing the completed text and other metadata
|
|
15
|
+
*/
|
|
16
|
+
complete(
|
|
17
|
+
content: string,
|
|
18
|
+
stop: string[],
|
|
19
|
+
maxTokens: number,
|
|
20
|
+
): Promise<{
|
|
21
|
+
content?: string;
|
|
22
|
+
finishReason?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface EmailAdapter {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This method is called to validate the configuration of the adapter
|
|
5
|
+
* and should throw a clear user-readbale error if the configuration is invalid.
|
|
6
|
+
*/
|
|
7
|
+
validate(): Promise<void>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This method should send an email using the adapter
|
|
11
|
+
* @param from - The sender's email address
|
|
12
|
+
* @param to - The recipient's email address
|
|
13
|
+
* @param text - The plain text version of the email
|
|
14
|
+
* @param html - The HTML version of the email
|
|
15
|
+
* @param subject - The subject of the email
|
|
16
|
+
*/
|
|
17
|
+
sendEmail(
|
|
18
|
+
from: string,
|
|
19
|
+
to: string,
|
|
20
|
+
text: string,
|
|
21
|
+
html: string,
|
|
22
|
+
subject: string
|
|
23
|
+
): Promise<{
|
|
24
|
+
error?: string;
|
|
25
|
+
ok?: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { EmailAdapter };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface ImageGenerationAdapter {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This method is called to validate the configuration of the adapter
|
|
5
|
+
* and should throw a clear user-readbale error if the configuration is invalid.
|
|
6
|
+
*/
|
|
7
|
+
validate(): void;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Return max number of images which model can generate in one request
|
|
11
|
+
*/
|
|
12
|
+
outputImagesMaxCountSupported(): number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Return the list of supported dimensions in format ["100x500", "200x200"]
|
|
16
|
+
*/
|
|
17
|
+
outputDimensionsSupported(): string[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Input file extension supported
|
|
21
|
+
*/
|
|
22
|
+
inputFileExtensionSupported(): string[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This method should generate an image based on the provided prompt and input files.
|
|
26
|
+
* @param prompt - The prompt to generate the image
|
|
27
|
+
* @param inputFiles - An array of input file paths (optional)
|
|
28
|
+
* @param n - The number of images to generate (default is 1)
|
|
29
|
+
* @param size - The size of the generated image (default is the lowest dimension supported)
|
|
30
|
+
* @returns A promise that resolves to an object containing the generated image URLs and any error message
|
|
31
|
+
*/
|
|
32
|
+
generate({
|
|
33
|
+
prompt,
|
|
34
|
+
inputFiles,
|
|
35
|
+
n,
|
|
36
|
+
size,
|
|
37
|
+
}: {
|
|
38
|
+
prompt: string,
|
|
39
|
+
inputFiles: string[],
|
|
40
|
+
|
|
41
|
+
// default = lowest dimension supported
|
|
42
|
+
size?: string,
|
|
43
|
+
|
|
44
|
+
// one by default
|
|
45
|
+
n?: number
|
|
46
|
+
}): Promise<{
|
|
47
|
+
imageURLs?: string[];
|
|
48
|
+
error?: string;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* This interface is used to implement OAuth2 authentication adapters.
|
|
4
|
+
*/
|
|
5
|
+
export interface OAuth2Adapter {
|
|
6
|
+
/**
|
|
7
|
+
* This method should return navigatable URL to the OAuth2 provider authentication page.
|
|
8
|
+
*/
|
|
9
|
+
getAuthUrl(): string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This method should return the token from the OAuth2 provider using the provided code and redirect URI.
|
|
13
|
+
* @param code - The authorization code received from the OAuth2 provider
|
|
14
|
+
* @param redirect_uri - The redirect URI used in the authentication request
|
|
15
|
+
* @returns A promise that resolves to an object containing the email address of the authenticated user
|
|
16
|
+
*/
|
|
17
|
+
getTokenFromCode(code: string, redirect_uri: string): Promise<{ email: string }>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This method should return text (content) of SVG icon which will be used in the UI.
|
|
21
|
+
* Use official SVG icons with simplest possible conent, omit icons which have base64 encoded raster images inside.
|
|
22
|
+
*/
|
|
23
|
+
getIcon(): string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This method should return the text to be displayed on the button in the UI
|
|
27
|
+
*/
|
|
28
|
+
getButtonText?(): string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* This method should return the name of the adapter
|
|
32
|
+
*/
|
|
33
|
+
getName?(): string;
|
|
34
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Each storage adapter should support two ways of storing files:
|
|
4
|
+
* - publically (public URL) - the file can be accessed by anyone by HTTP GET / HEAD request with plain URL
|
|
5
|
+
* - privately (presigned URL) - the file can be accessed by anyone by HTTP GET / HEAD request only with presigned URLs, limited by expiration time
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
export interface StorageAdapter {
|
|
9
|
+
/**
|
|
10
|
+
* This method should return the presigned URL for the given key capable of upload (adapter user will call PUT multipart form data to this URL within expiresIn seconds after link generation).
|
|
11
|
+
* By default file which will be uploaded on PUT should be marked for deletion. So if during 24h it is not marked for not deletion, it adapter should delete it forever.
|
|
12
|
+
* The PUT method should fail if the file already exists.
|
|
13
|
+
*
|
|
14
|
+
* Adapter user will always pass next parameters to the method:
|
|
15
|
+
* @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
|
|
16
|
+
* @param expiresIn - The expiration time in seconds for the presigned URL
|
|
17
|
+
* @param contentType - The content type of the file to be uploaded, e.g. "image/png"
|
|
18
|
+
*
|
|
19
|
+
* @returns A promise that resolves to an object containing the upload URL and any extra parameters which should be sent with PUT multipart form data
|
|
20
|
+
*/
|
|
21
|
+
getUploadSignedUrl(key: string, contentType: string, expiresIn?: number): Promise<{
|
|
22
|
+
uploadUrl: string;
|
|
23
|
+
uploadExtraParams?: Record<string, string>;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* This method should return the URL for the given key capable of download (200 GET request with response body or 200 HEAD request without response body).
|
|
28
|
+
* If adapter configured to store objects publically, this method should return the public URL of the file.
|
|
29
|
+
* If adapter configured to no allow public storing of images, this method should return the presigned URL for the file.
|
|
30
|
+
*
|
|
31
|
+
* @param key - The key of the file to be downloaded e.g. "uploads/file.txt"
|
|
32
|
+
* @param expiresIn - The expiration time in seconds for the presigned URL
|
|
33
|
+
*/
|
|
34
|
+
getDownloadUrl(key: string, expiresIn?: number): Promise<string>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* This method should mark the file for deletion.
|
|
38
|
+
* If file is marked for delation and exists more then 24h (since creation date) it should be deleted.
|
|
39
|
+
* This method should work even if the file does not exist yet (e.g. only presigned URL was generated).
|
|
40
|
+
* @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
|
|
41
|
+
*/
|
|
42
|
+
markKeyForDeletation(key: string): Promise<void>;
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* This method should mark the file to not be deleted.
|
|
47
|
+
* This method should be used to cancel the deletion of the file if it was marked for deletion.
|
|
48
|
+
* @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
|
|
49
|
+
*/
|
|
50
|
+
markKeyForNotDeletation(key: string): Promise<void>;
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* This method can start needed schedullers, cron jobs, etc. to clean up the storage.
|
|
55
|
+
* @param adapterUserUniqueRepresentation - The unique representation of the plugin instance which
|
|
56
|
+
* wil use this adapter. Might be handy if you need to distinguish between different instances of the same adapter.
|
|
57
|
+
*/
|
|
58
|
+
setupLifecycle(adapterUserUniqueRepresentation: string): Promise<void>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* If adapter is configured to publically, this method should return true.
|
|
62
|
+
*/
|
|
63
|
+
objectCanBeAccesedPublicly(): Promise<boolean>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* This method should return the key as a data URL (base64 encoded string).
|
|
67
|
+
* @param key - The key of the file to be converted to a data URL
|
|
68
|
+
* @returns A promise that resolves to a string containing the data URL
|
|
69
|
+
*/
|
|
70
|
+
getKeyAsDataURL(key: string): Promise<string>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { EmailAdapter } from './EmailAdapter.js';
|
|
2
|
+
export type { CompletionAdapter } from './CompletionAdapter.js';
|
|
3
|
+
export type { ImageGenerationAdapter } from './ImageGenerationAdapter.js';
|
|
4
|
+
export type { OAuth2Adapter } from './OAuth2Adapter.js';
|
|
5
|
+
export type { StorageAdapter } from './StorageAdapter.js';
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
<BreadcrumbsWithButtons>
|
|
14
14
|
<!-- save and cancle -->
|
|
15
15
|
<button @click="$router.back()"
|
|
16
|
-
class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
16
|
+
class="af-cancel-button flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
17
17
|
>
|
|
18
18
|
{{ $t('Cancel') }}
|
|
19
19
|
</button>
|
|
20
20
|
|
|
21
21
|
<button
|
|
22
22
|
@click="saveRecord"
|
|
23
|
-
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
23
|
+
class="af-save-button flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
24
24
|
:disabled="saving || (validating && !isValid)"
|
|
25
25
|
>
|
|
26
26
|
<svg v-if="saving"
|
|
@@ -74,14 +74,14 @@
|
|
|
74
74
|
|
|
75
75
|
<RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
|
|
76
76
|
:to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
|
|
77
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
77
|
+
class="af-create-button flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
78
78
|
>
|
|
79
79
|
<IconPlusOutline class="w-4 h-4 me-2"/>
|
|
80
80
|
{{ $t('Create') }}
|
|
81
81
|
</RouterLink>
|
|
82
82
|
|
|
83
83
|
<button
|
|
84
|
-
class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
84
|
+
class="af-filter-button flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
85
85
|
@click="()=>{filtersShow = !filtersShow}"
|
|
86
86
|
v-if="coreStore.resource?.options?.allowedActions?.filter"
|
|
87
87
|
>
|