adminforth 2.70.0 → 2.71.1-next.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/dist/spa/src/afcl/Button.vue +7 -2
- package/dist/spa/src/afcl/Dialog.vue +22 -9
- package/dist/spa/src/afcl/Modal.vue +21 -8
- package/dist/spa/src/components/ListActionsThreeDots.vue +2 -2
- package/dist/spa/src/components/ResourceListTable.vue +1 -1
- package/dist/spa/src/components/ThreeDotsMenu.vue +2 -2
- package/dist/spa/src/views/ListView.vue +2 -2
- package/dist/spa/src/views/ShowView.vue +1 -1
- package/dist/spa/src/websocket.ts +44 -6
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
:class="{
|
|
8
8
|
'cursor-default opacity-50 pointer-events-none': props.disabled,
|
|
9
9
|
'active brightness-200 hover:brightness-150' : props.active,
|
|
10
|
-
'text-lightSecondaryContrast/70 bg-lightSecondary border-lightSecondaryContrast/30 dark:bg-darkSecondary hover:bg-lightSecondary/60 hover:border-lightSecondaryContrast/60 focus:ring-lightSecondary dark:focus:ring-darkSecondary/40 dark:text-darkSecondaryContrast dark:border-darkSecondaryContrast/40 dark:hover:bg-darkSecondary/60 dark:hover:border-white/60':
|
|
10
|
+
'text-lightSecondaryContrast/70 bg-lightSecondary border-lightSecondaryContrast/30 dark:bg-darkSecondary hover:bg-lightSecondary/60 hover:border-lightSecondaryContrast/60 focus:ring-lightSecondary dark:focus:ring-darkSecondary/40 dark:text-darkSecondaryContrast dark:border-darkSecondaryContrast/40 dark:hover:bg-darkSecondary/60 dark:hover:border-white/60': currentVariant === 'secondary',
|
|
11
11
|
}"
|
|
12
12
|
>
|
|
13
13
|
<Spinner v-if="props.loader" class="w-4 h-4 text-lightButtonsText dark:text-darkButtonsText fill-lightButtonsBackground dark:fill-darkPrimary" />
|
|
@@ -17,17 +17,22 @@
|
|
|
17
17
|
|
|
18
18
|
<script setup lang="ts">
|
|
19
19
|
import { Spinner } from '@/afcl';
|
|
20
|
+
import { computed } from 'vue';
|
|
20
21
|
|
|
21
22
|
const props = withDefaults(defineProps<{
|
|
22
23
|
loader?: boolean;
|
|
23
24
|
disabled?: boolean;
|
|
24
25
|
active?: boolean;
|
|
26
|
+
variant?: 'primary' | 'secondary';
|
|
27
|
+
/** @deprecated use variant instead of mode */
|
|
25
28
|
mode?: 'primary' | 'secondary';
|
|
26
29
|
}>(), {
|
|
27
30
|
loader: false,
|
|
28
31
|
disabled: false,
|
|
29
32
|
active: false,
|
|
30
|
-
mode: 'primary'
|
|
31
33
|
});
|
|
32
34
|
|
|
35
|
+
// mode is deprecated, but we still want to support it for backward compatibility,
|
|
36
|
+
// so we check both variant and mode props
|
|
37
|
+
const currentVariant = computed(() => props.variant ?? props.mode ?? 'primary');
|
|
33
38
|
</script>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
:closeByClickOutside="clickToCloseOutside || closeByClickOutside"
|
|
6
6
|
:closeByEsc="closeByEsc || closable"
|
|
7
7
|
:beforeCloseFunction="beforeCloseFunction"
|
|
8
|
+
:beforeCancelFunction="beforeCancelFunction"
|
|
8
9
|
:beforeOpenFunction="beforeOpenFunction"
|
|
9
10
|
:askForCloseConfirmation="askForCloseConfirmation"
|
|
10
11
|
:closeConfirmationText="closeConfirmationText"
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
v-if="headerCloseButton"
|
|
27
28
|
type="button"
|
|
28
29
|
class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
|
|
29
|
-
@click="
|
|
30
|
+
@click="tryToCancelModal"
|
|
30
31
|
>
|
|
31
32
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
32
33
|
<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"/>
|
|
@@ -118,10 +119,15 @@ function close() {
|
|
|
118
119
|
modalRef.value.close();
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
function tryToCancelModal() {
|
|
123
|
+
modalRef.value?.cancel();
|
|
124
|
+
}
|
|
125
|
+
|
|
121
126
|
defineExpose({
|
|
122
127
|
open: open,
|
|
123
128
|
close: close,
|
|
124
129
|
tryToHideModal: tryToHideModal,
|
|
130
|
+
tryToCancelModal: tryToCancelModal,
|
|
125
131
|
})
|
|
126
132
|
|
|
127
133
|
function tryToHideModal() {
|
|
@@ -171,6 +177,11 @@ interface DialogProps {
|
|
|
171
177
|
*/
|
|
172
178
|
beforeOpenFunction?: (() => void | Promise<void>) | null
|
|
173
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Function that will be called before the dialog is canceled.
|
|
182
|
+
*/
|
|
183
|
+
beforeCancelFunction?: (() => void | Promise<void | boolean>) | null
|
|
184
|
+
|
|
174
185
|
/**
|
|
175
186
|
* Disables close on Ecs button
|
|
176
187
|
*
|
|
@@ -201,19 +212,21 @@ interface DialogProps {
|
|
|
201
212
|
|
|
202
213
|
/********** for the backward compatibility ***************/
|
|
203
214
|
class Dialog implements IDialogInsideButtonClickHandler {
|
|
204
|
-
hide: () => void
|
|
205
|
-
constructor(
|
|
206
|
-
this.hide =
|
|
215
|
+
hide: (isCancel?: boolean) => void
|
|
216
|
+
constructor(hideFn: (isCancel?: boolean) => void) {
|
|
217
|
+
this.hide = hideFn;
|
|
207
218
|
}
|
|
208
219
|
}
|
|
209
220
|
const dialog: Ref<Dialog> = ref(
|
|
210
|
-
new Dialog(
|
|
211
|
-
()
|
|
212
|
-
if (
|
|
213
|
-
|
|
221
|
+
new Dialog((isCancel = false) => {
|
|
222
|
+
if (dialog.value) {
|
|
223
|
+
if (isCancel) {
|
|
224
|
+
modalRef.value?.cancel();
|
|
225
|
+
} else {
|
|
226
|
+
modalRef.value?.close();
|
|
214
227
|
}
|
|
215
228
|
}
|
|
216
|
-
)
|
|
229
|
+
})
|
|
217
230
|
);
|
|
218
231
|
/*************************************************************/
|
|
219
232
|
|
|
@@ -70,6 +70,7 @@ interface DialogProps {
|
|
|
70
70
|
closeByEsc?: boolean
|
|
71
71
|
beforeCloseFunction?: (() => void | Promise<void | boolean>) | null
|
|
72
72
|
beforeOpenFunction?: (() => void | Promise<void>) | null
|
|
73
|
+
beforeCancelFunction?: (() => void | Promise<void | boolean>) | null
|
|
73
74
|
askForCloseConfirmation?: boolean
|
|
74
75
|
closeConfirmationText?: string
|
|
75
76
|
removeFromDomOnClose?: boolean
|
|
@@ -82,6 +83,7 @@ const props = withDefaults(defineProps<DialogProps>(), {
|
|
|
82
83
|
closeByEsc: true,
|
|
83
84
|
beforeCloseFunction: null,
|
|
84
85
|
beforeOpenFunction: null,
|
|
86
|
+
beforeCancelFunction: null,
|
|
85
87
|
askForCloseConfirmation: false,
|
|
86
88
|
closeConfirmationText: 'Are you sure you want to close this dialog?',
|
|
87
89
|
removeFromDomOnClose: false,
|
|
@@ -109,6 +111,14 @@ async function close() {
|
|
|
109
111
|
isModalOpen.value = false;
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
async function cancel() {
|
|
115
|
+
if (props.beforeCancelFunction) {
|
|
116
|
+
const shouldCancel = await props.beforeCancelFunction?.();
|
|
117
|
+
if (shouldCancel === false) return;
|
|
118
|
+
}
|
|
119
|
+
isModalOpen.value = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
defineOptions({
|
|
113
123
|
inheritAttrs: false,
|
|
114
124
|
})
|
|
@@ -116,12 +126,17 @@ defineOptions({
|
|
|
116
126
|
defineExpose({
|
|
117
127
|
open: open,
|
|
118
128
|
close: close,
|
|
129
|
+
cancel: cancel,
|
|
119
130
|
tryToHideModal: tryToHideModal,
|
|
120
131
|
})
|
|
121
132
|
|
|
122
|
-
function tryToHideModal() {
|
|
123
|
-
if (!props.askForCloseConfirmation
|
|
124
|
-
|
|
133
|
+
function tryToHideModal(isCancelAction = false) {
|
|
134
|
+
if (!props.askForCloseConfirmation) {
|
|
135
|
+
if (isCancelAction) {
|
|
136
|
+
cancel();
|
|
137
|
+
} else {
|
|
138
|
+
close();
|
|
139
|
+
}
|
|
125
140
|
} else {
|
|
126
141
|
showConfirmationOnClose.value = true;
|
|
127
142
|
}
|
|
@@ -136,10 +151,8 @@ function toggleModal() {
|
|
|
136
151
|
}
|
|
137
152
|
|
|
138
153
|
function onEsc(event: KeyboardEvent) {
|
|
139
|
-
if (event.key === 'Escape') {
|
|
140
|
-
|
|
141
|
-
tryToHideModal();
|
|
142
|
-
}
|
|
154
|
+
if (event.key === 'Escape' && props.closeByEsc) {
|
|
155
|
+
tryToHideModal(true);
|
|
143
156
|
}
|
|
144
157
|
}
|
|
145
158
|
|
|
@@ -154,7 +167,7 @@ onUnmounted(() => {
|
|
|
154
167
|
|
|
155
168
|
function backdropClick(e: MouseEvent) {
|
|
156
169
|
if (props.closeByClickOutside && e.target === e.currentTarget) {
|
|
157
|
-
tryToHideModal();
|
|
170
|
+
tryToHideModal(true);
|
|
158
171
|
}
|
|
159
172
|
}
|
|
160
173
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
class="border border-gray-300 dark:border-gray-700 dark:border-opacity-0 border-opacity-0 hover:border-opacity-100 dark:hover:border-opacity-100 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
|
|
6
6
|
@click="toggleMenu"
|
|
7
7
|
>
|
|
8
|
-
<IconDotsHorizontalOutline class="w-6 h-6 text-lightPrimary dark:text-darkPrimary" />
|
|
8
|
+
<IconDotsHorizontalOutline class="w-6 h-6 text-lightPrimary dark:text-darkPrimary dark:brightness-150" />
|
|
9
9
|
</div>
|
|
10
10
|
<teleport to="body">
|
|
11
11
|
<div
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
<component
|
|
71
71
|
v-if="action.icon"
|
|
72
72
|
:is="getIcon(action.icon)"
|
|
73
|
-
class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"
|
|
73
|
+
class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary dark:brightness-200"
|
|
74
74
|
/>
|
|
75
75
|
{{ $t(action.name) }}
|
|
76
76
|
</component>
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
<component
|
|
217
217
|
v-if="action.icon && !actionLoadingStates[`${action.id}_${row._primaryKeyValue}`]"
|
|
218
218
|
:is="getIcon(action.icon)"
|
|
219
|
-
class="w-6 h-6 text-lightPrimary dark:text-darkPrimary"
|
|
219
|
+
class="w-6 h-6 text-lightPrimary dark:text-darkPrimary dark:brightness-150"
|
|
220
220
|
/>
|
|
221
221
|
<Spinner
|
|
222
222
|
v-if="actionLoadingStates[`${action.id}_${row._primaryKeyValue}`]"
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<component
|
|
60
60
|
v-if="action.icon && !actionLoadingStates[action.id!]"
|
|
61
61
|
:is="getIcon(action.icon)"
|
|
62
|
-
class="w-4 h-4 text-lightPrimary dark:text-darkPrimary"
|
|
62
|
+
class="w-4 h-4 text-lightPrimary dark:text-darkPrimary dark:brightness-200"
|
|
63
63
|
/>
|
|
64
64
|
<Spinner
|
|
65
65
|
v-if="actionLoadingStates[action.id!]"
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<component
|
|
84
84
|
v-if="action.icon"
|
|
85
85
|
:is="getIcon(action.icon)"
|
|
86
|
-
class="w-4 h-4 text-lightPrimary dark:text-darkPrimary"
|
|
86
|
+
class="w-4 h-4 text-lightPrimary dark:text-darkPrimary dark:brightness-200"
|
|
87
87
|
/>
|
|
88
88
|
{{ action.label }}
|
|
89
89
|
</div>
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
@click="()=>{checkboxes = []}"
|
|
36
36
|
v-if="checkboxes.length"
|
|
37
37
|
data-tooltip-target="tooltip-remove-all"
|
|
38
|
-
class="flex gap-1 items-center py-1 px-3
|
|
38
|
+
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText af-button-shadow
|
|
39
39
|
focus:outline-none bg-lightListViewButtonBackground rounded border border-lightListViewButtonBorder h-[2.125rem]
|
|
40
40
|
hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4
|
|
41
41
|
focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
</RouterLink>
|
|
126
126
|
|
|
127
127
|
<button
|
|
128
|
-
class="af-filter-button flex gap-1 items-center py-1 h-[2.125rem] px-3
|
|
128
|
+
class="af-filter-button flex gap-1 items-center py-1 h-[2.125rem] px-3 af-button-shadow text-sm font-medium
|
|
129
129
|
text-lightListViewButtonText transition-all focus:outline-none bg-lightListViewButtonBackground rounded border
|
|
130
130
|
border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover
|
|
131
131
|
focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<component
|
|
28
28
|
v-if="action.icon && !actionLoadingStates[action.id!]"
|
|
29
29
|
:is="getIcon(action.icon)"
|
|
30
|
-
class="w-4 h-4 me-2 text-lightPrimary dark:text-darkPrimary"
|
|
30
|
+
class="w-4 h-4 me-2 text-lightPrimary dark:text-darkPrimary dark:brightness-200"
|
|
31
31
|
/>
|
|
32
32
|
<Spinner
|
|
33
33
|
v-if="actionLoadingStates[action.id!]"
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
2
|
+
type WebsocketCallback = (data: any) => void;
|
|
3
|
+
type Unsubscribe = () => void;
|
|
4
|
+
type Subscription = {
|
|
5
|
+
id: number;
|
|
6
|
+
callback: WebsocketCallback;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const subscriptions: { [topic: string]: Subscription[] } = {};
|
|
10
|
+
let nextSubscriptionId = 1;
|
|
3
11
|
|
|
4
12
|
interface ExtendedWebSocket extends WebSocket {
|
|
5
13
|
connected?: boolean;
|
|
@@ -65,8 +73,8 @@ async function connect () {
|
|
|
65
73
|
const topic = message.topic;
|
|
66
74
|
const data = message.data;
|
|
67
75
|
if (subscriptions[topic]) {
|
|
68
|
-
for (const
|
|
69
|
-
callback(data);
|
|
76
|
+
for (const subscription of subscriptions[topic]) {
|
|
77
|
+
subscription.callback(data);
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
}
|
|
@@ -104,19 +112,49 @@ setInterval(() => {
|
|
|
104
112
|
}, 10_000);
|
|
105
113
|
|
|
106
114
|
|
|
115
|
+
function unsubscribeSubscription(topic: string, subscriptionId: number): void {
|
|
116
|
+
if (!subscriptions[topic]) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
subscriptions[topic] = subscriptions[topic].filter((subscription) => subscription.id !== subscriptionId);
|
|
121
|
+
if (subscriptions[topic].length > 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
delete subscriptions[topic];
|
|
126
|
+
if (state.status === 'connected') {
|
|
127
|
+
doPhysicalUnsubscribe(topic);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
107
131
|
export default {
|
|
108
|
-
subscribe(topic: string, callback:
|
|
132
|
+
subscribe(topic: string, callback: WebsocketCallback): Unsubscribe {
|
|
109
133
|
const isFirstSubscription = !subscriptions[topic];
|
|
110
134
|
if (!subscriptions[topic]) {
|
|
111
135
|
subscriptions[topic] = [];
|
|
112
136
|
}
|
|
113
|
-
|
|
137
|
+
const subscriptionId = nextSubscriptionId++;
|
|
138
|
+
subscriptions[topic].push({
|
|
139
|
+
id: subscriptionId,
|
|
140
|
+
callback,
|
|
141
|
+
});
|
|
114
142
|
if (isFirstSubscription && state.status === 'connected') {
|
|
115
143
|
doPhysicalSubscribe(topic);
|
|
116
144
|
}
|
|
145
|
+
|
|
146
|
+
let isSubscribed = true;
|
|
147
|
+
return () => {
|
|
148
|
+
if (!isSubscribed) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
isSubscribed = false;
|
|
152
|
+
unsubscribeSubscription(topic, subscriptionId);
|
|
153
|
+
};
|
|
117
154
|
},
|
|
118
155
|
|
|
119
156
|
unsubscribe(topic: string): void {
|
|
157
|
+
console.warn('This method is deprecated because it removes all subscriptions for the topic. Use the unsubscribe function returned by subscribe(), or use unsubscribeByPrefix() when you explicitly need to unsubscribe by topic prefix.');
|
|
120
158
|
if (!subscriptions[topic]) {
|
|
121
159
|
return;
|
|
122
160
|
}
|
|
@@ -146,4 +184,4 @@ export default {
|
|
|
146
184
|
});
|
|
147
185
|
}
|
|
148
186
|
|
|
149
|
-
}
|
|
187
|
+
}
|