@xen-orchestra/web-core 0.33.0 → 0.35.0
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/lib/components/dropdown/{DropdownTitle.vue → VtsDropdownTitle.vue} +4 -4
- package/lib/components/input-wrapper/VtsInputWrapper.vue +1 -10
- package/lib/components/label-value-list/VtsLabelValueList.vue +46 -0
- package/lib/components/menu/MenuList.vue +1 -0
- package/lib/components/progress-bar/VtsProgressBar.vue +8 -3
- package/lib/components/size-progress-cell/VtsSizeProgressCell.vue +36 -0
- package/lib/components/space-card/VtsSpaceCard.vue +94 -0
- package/lib/components/ui/input/UiInput.vue +5 -7
- package/lib/components/ui/label/UiLabel.vue +4 -15
- package/lib/components/ui/tag/UiTag.vue +4 -1
- package/lib/components/ui/tag/UiTagsList.vue +11 -1
- package/lib/components/ui/text-area/UiTextarea.vue +1 -3
- package/lib/i18n.ts +4 -0
- package/lib/icons/fa-icons.ts +4 -0
- package/lib/icons/legacy-icons.ts +33 -8
- package/lib/locales/cs.json +12 -6
- package/lib/locales/da.json +261 -0
- package/lib/locales/de.json +3 -3
- package/lib/locales/en.json +57 -7
- package/lib/locales/es.json +1 -5
- package/lib/locales/fa.json +1 -1
- package/lib/locales/fr.json +61 -11
- package/lib/locales/it.json +9 -0
- package/lib/locales/nl.json +10 -5
- package/lib/locales/pt_BR.json +75 -16
- package/lib/locales/ru.json +1 -5
- package/lib/locales/sv.json +1 -5
- package/lib/locales/uk.json +2 -6
- package/lib/packages/remote-resource/README.md +32 -0
- package/lib/packages/remote-resource/define-remote-resource.ts +107 -17
- package/lib/packages/remote-resource/sse.store.ts +140 -0
- package/lib/types/utility.type.ts +6 -0
- package/lib/utils/progress.util.ts +7 -5
- package/package.json +1 -1
package/lib/locales/sv.json
CHANGED
|
@@ -47,12 +47,8 @@
|
|
|
47
47
|
"backup-repositories": "Säkerhetskopieringsförvaring",
|
|
48
48
|
"backup-repository": "Säkerhetskopieringsförvaring (lokalt, NFS, SMB)",
|
|
49
49
|
"backup-targets": "Säkerhetskopieringsmål",
|
|
50
|
-
"backup.continuous-replication": "Kontinuerlig replikering",
|
|
51
|
-
"backup.disaster-recovery": "Katastrofåterställning",
|
|
52
50
|
"backup.full": "Fullständig säkerhetskopiering",
|
|
53
51
|
"backup.incremental": "Inkrementell säkerhetskopiering",
|
|
54
|
-
"backup.metadata": "Säkerhetskopiering av metadata",
|
|
55
|
-
"backup.mirror": "Speglad säkerhetskopiering",
|
|
56
52
|
"backup.pool-metadata": "Pool-metadata",
|
|
57
53
|
"backup.rolling-snapshot": "Rullande ögonblicksbild",
|
|
58
54
|
"backup.xo-config": "XO-konfig",
|
|
@@ -75,7 +71,6 @@
|
|
|
75
71
|
"bond-devices": "Bindningsenheter",
|
|
76
72
|
"bond-status": "Bindnings-status",
|
|
77
73
|
"boot-firmware": "Boot-programvara",
|
|
78
|
-
"boot-firmware-bios": "Den här mallen innehåller redan BIOS-strängar",
|
|
79
74
|
"boot-firmware-uefi": "Boot-programvaran är UEFI",
|
|
80
75
|
"boot-vm": "Starta VM",
|
|
81
76
|
"build-number": "Versionsnummer",
|
|
@@ -557,6 +552,7 @@
|
|
|
557
552
|
"tasks.quick-view.failed": "Misslyckades",
|
|
558
553
|
"tasks.quick-view.in-progress": "Pågår",
|
|
559
554
|
"template": "Mall",
|
|
555
|
+
"template-has-bios-strings": "Den här mallen innehåller redan BIOS-strängar",
|
|
560
556
|
"theme-auto": "Auto",
|
|
561
557
|
"theme-dark": "Mörkt",
|
|
562
558
|
"theme-light": "Ljust",
|
package/lib/locales/uk.json
CHANGED
|
@@ -44,12 +44,8 @@
|
|
|
44
44
|
"backup-network": "Резервна мережа",
|
|
45
45
|
"backup-repository": "Сховище резервних копій (local, NFS, SMB)",
|
|
46
46
|
"backup-targets": "Призначення для резервної копії",
|
|
47
|
-
"backup.continuous-replication": "Постійна реплікація",
|
|
48
|
-
"backup.disaster-recovery": "Відновлення після критичних помилок",
|
|
49
47
|
"backup.full": "Повна резервна копія",
|
|
50
48
|
"backup.incremental": "Інкрементальна резервна копія",
|
|
51
|
-
"backup.metadata": "Резервна копія метаданих",
|
|
52
|
-
"backup.mirror": "Дзеркальна резервна копія",
|
|
53
49
|
"backup.pool-metadata": "Метадані пулу",
|
|
54
50
|
"backup.rolling-snapshot": "Ковзний знімок",
|
|
55
51
|
"backup.xo-config": "Конфігурація XO",
|
|
@@ -72,7 +68,6 @@
|
|
|
72
68
|
"bond-devices": "Пристрої Bond",
|
|
73
69
|
"bond-status": "Статус Bond",
|
|
74
70
|
"boot-firmware": "Завантажувальна прошивка",
|
|
75
|
-
"boot-firmware-bios": "Шаблон вже містить налаштування BIOS",
|
|
76
71
|
"boot-firmware-uefi": "Завантажувальна прошивка - UEFI",
|
|
77
72
|
"boot-vm": "Завантажувальна ВМ",
|
|
78
73
|
"build-number": "Номер збірки",
|
|
@@ -311,8 +306,9 @@
|
|
|
311
306
|
"ssh-password": "пароль SSH",
|
|
312
307
|
"ssh-password-confirm": "Підтвердіть пароль SSH",
|
|
313
308
|
"static-ip": "Статична IP-адреса",
|
|
309
|
+
"template-has-bios-strings": "Шаблон вже містить налаштування BIOS",
|
|
314
310
|
"uuid": "UUID",
|
|
315
|
-
"vcpus": "vCPU |
|
|
311
|
+
"vcpus": "vCPU | vCPU | vCPUs",
|
|
316
312
|
"vdi-throughput": "Пропускна здатність VDI",
|
|
317
313
|
"vdis": "VDI | VDI | VDIs",
|
|
318
314
|
"vif": "VIF",
|
|
@@ -113,3 +113,35 @@ const useMyResource = defineRemoteResource({
|
|
|
113
113
|
cacheDurationMs: 5 * 60_000, // Cache for 5 minutes
|
|
114
114
|
})
|
|
115
115
|
```
|
|
116
|
+
|
|
117
|
+
## Watching a collection
|
|
118
|
+
|
|
119
|
+
You can enable real-time synchronization with a remote collection by using the `watchCollection` option.
|
|
120
|
+
When this option is set, the resource will first fetch the complete dataset once, then listen for any changes on the collection (such as additions, updates, or removals) and automatically update the shared store accordingly.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const useMyResource = defineRemoteResource({
|
|
124
|
+
url: '/api/path/to/resource?fields=id,name,status',
|
|
125
|
+
watchCollection: {
|
|
126
|
+
type: 'resource',
|
|
127
|
+
fields: ['id', 'name', 'status'],
|
|
128
|
+
},
|
|
129
|
+
// Optional
|
|
130
|
+
onDataReceived: (currentData, receivedData) => {
|
|
131
|
+
// Called when new or updated data is received
|
|
132
|
+
mergeCollection(currentData.value, receivedData)
|
|
133
|
+
},
|
|
134
|
+
// Optional
|
|
135
|
+
onDataRemoved: (currentData, removedData) => {
|
|
136
|
+
// Called when data is removed from the collection
|
|
137
|
+
removeFromCollection(currentData.value, removedData)
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
When a collection is being watched:
|
|
143
|
+
|
|
144
|
+
- The initial fetch retrieves the entire dataset.
|
|
145
|
+
- Any subsequent changes (additions, updates, deletions) are automatically reflected in the resource’s data.
|
|
146
|
+
- You can customize how incoming or removed data is handled using the onDataReceived and onDataRemoved callbacks.
|
|
147
|
+
- The subscription to collection changes automatically stops when there are no more active component subscribers.
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useSseStore,
|
|
3
|
+
type THandleDelete,
|
|
4
|
+
type THandlePost,
|
|
5
|
+
type THandleWatching,
|
|
6
|
+
} from '@core/packages/remote-resource/sse.store'
|
|
1
7
|
import type { ResourceContext, UseRemoteResource } from '@core/packages/remote-resource/types.ts'
|
|
2
8
|
import type { VoidFunction } from '@core/types/utility.type.ts'
|
|
3
9
|
import { ifElse } from '@core/utils/if-else.utils.ts'
|
|
4
10
|
import { type MaybeRef, noop, useTimeoutPoll } from '@vueuse/core'
|
|
5
|
-
import { merge } from 'lodash-es'
|
|
11
|
+
import { merge, remove } from 'lodash-es'
|
|
6
12
|
import readNDJSONStream from 'ndjson-readablestream'
|
|
7
13
|
import {
|
|
8
14
|
computed,
|
|
@@ -19,7 +25,7 @@ import {
|
|
|
19
25
|
watch,
|
|
20
26
|
} from 'vue'
|
|
21
27
|
|
|
22
|
-
const
|
|
28
|
+
const DEFAULT_CACHE_EXPIRATION_MS = 10_000
|
|
23
29
|
|
|
24
30
|
const DEFAULT_POLLING_INTERVAL_MS = 30_000
|
|
25
31
|
|
|
@@ -32,8 +38,8 @@ export function defineRemoteResource<
|
|
|
32
38
|
initialData: () => TData
|
|
33
39
|
state?: (data: Ref<NoInfer<TData>>, context: ResourceContext<TArgs>) => TState
|
|
34
40
|
onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
|
35
|
-
|
|
36
|
-
pollingIntervalMs?: number
|
|
41
|
+
cacheExpirationMs?: number | false
|
|
42
|
+
pollingIntervalMs?: number | false
|
|
37
43
|
stream?: boolean
|
|
38
44
|
}): UseRemoteResource<TState, TArgs>
|
|
39
45
|
|
|
@@ -41,11 +47,33 @@ export function defineRemoteResource<TData, TState extends object, TArgs extends
|
|
|
41
47
|
url: string | ((...args: TArgs) => string)
|
|
42
48
|
state?: (data: Ref<TData | undefined>, context: ResourceContext<TArgs>) => TState
|
|
43
49
|
onDataReceived?: (data: Ref<TData | undefined>, receivedData: any) => void
|
|
44
|
-
|
|
45
|
-
pollingIntervalMs?: number
|
|
50
|
+
cacheExpirationMs?: number | false
|
|
51
|
+
pollingIntervalMs?: number | false
|
|
46
52
|
stream?: boolean
|
|
47
53
|
}): UseRemoteResource<TState, TArgs>
|
|
48
54
|
|
|
55
|
+
export function defineRemoteResource<
|
|
56
|
+
TData,
|
|
57
|
+
TState extends object = { data: Ref<TData> },
|
|
58
|
+
TArgs extends any[] = [],
|
|
59
|
+
>(config: {
|
|
60
|
+
url: string | ((...args: TArgs) => string)
|
|
61
|
+
initialData: () => TData
|
|
62
|
+
state?: (data: Ref<NoInfer<TData>>, context: ResourceContext<TArgs>) => TState
|
|
63
|
+
onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
|
64
|
+
onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
|
65
|
+
stream?: boolean
|
|
66
|
+
watchCollection: {
|
|
67
|
+
collectionId: string
|
|
68
|
+
resource: string // reactivity only on XAPI XO record for now
|
|
69
|
+
getIdentifier: (obj: unknown) => string
|
|
70
|
+
handleDelete: THandleDelete
|
|
71
|
+
handlePost: THandlePost
|
|
72
|
+
handleWatching: THandleWatching
|
|
73
|
+
predicate?: (receivedData: TData, context: ResourceContext<TArgs> | undefined) => boolean
|
|
74
|
+
}
|
|
75
|
+
}): UseRemoteResource<TState, TArgs>
|
|
76
|
+
|
|
49
77
|
export function defineRemoteResource<
|
|
50
78
|
TData,
|
|
51
79
|
TState extends object = { data: Ref<TData> },
|
|
@@ -55,9 +83,19 @@ export function defineRemoteResource<
|
|
|
55
83
|
initialData?: () => TData
|
|
56
84
|
state?: (data: Ref<TData>, context: ResourceContext<TArgs>) => TState
|
|
57
85
|
onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
|
87
|
+
cacheExpirationMs?: number | false
|
|
88
|
+
pollingIntervalMs?: number | false
|
|
60
89
|
stream?: boolean
|
|
90
|
+
watchCollection?: {
|
|
91
|
+
collectionId: string
|
|
92
|
+
resource: string // reactivity only on XAPI XO record for now
|
|
93
|
+
getIdentifier: (obj: unknown) => string
|
|
94
|
+
handleDelete: THandleDelete
|
|
95
|
+
handlePost: THandlePost
|
|
96
|
+
handleWatching: THandleWatching
|
|
97
|
+
predicate?: (receivedData: TData, context: ResourceContext<TArgs> | undefined) => boolean
|
|
98
|
+
}
|
|
61
99
|
}) {
|
|
62
100
|
const cache = new Map<
|
|
63
101
|
string,
|
|
@@ -79,26 +117,60 @@ export function defineRemoteResource<
|
|
|
79
117
|
|
|
80
118
|
const buildState = config.state ?? ((data: Ref<TData>) => ({ data }))
|
|
81
119
|
|
|
82
|
-
const
|
|
120
|
+
const cacheExpiration = config.cacheExpirationMs ?? DEFAULT_CACHE_EXPIRATION_MS
|
|
83
121
|
|
|
84
122
|
const pollingInterval = config.pollingIntervalMs ?? DEFAULT_POLLING_INTERVAL_MS
|
|
85
123
|
|
|
124
|
+
const removeData = (data: TData[], dataToRemove: any) => {
|
|
125
|
+
const getIdentifier = config.watchCollection?.getIdentifier ?? JSON.stringify
|
|
126
|
+
|
|
127
|
+
remove(data, item => {
|
|
128
|
+
if (typeof item === 'object') {
|
|
129
|
+
return getIdentifier(item) === getIdentifier(dataToRemove)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return item === dataToRemove
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
86
136
|
const onDataReceived =
|
|
87
137
|
config.onDataReceived ??
|
|
88
|
-
((data: Ref<TData>, receivedData: any) => {
|
|
89
|
-
|
|
138
|
+
((data: Ref<TData>, receivedData: any, args?: ResourceContext<TArgs>) => {
|
|
139
|
+
// allow to ignore some update (like for sub collection. E.g. vms/:id/vdis)
|
|
140
|
+
if (config.watchCollection?.predicate?.(receivedData, args) === false) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
if (data.value === undefined || (Array.isArray(data.value) && Array.isArray(receivedData))) {
|
|
90
144
|
data.value = receivedData
|
|
91
145
|
return
|
|
92
146
|
}
|
|
93
147
|
|
|
94
|
-
if (Array.isArray(data.value)
|
|
95
|
-
data.value
|
|
148
|
+
if (Array.isArray(data.value)) {
|
|
149
|
+
removeData(data.value, receivedData)
|
|
150
|
+
data.value.push(receivedData)
|
|
96
151
|
return
|
|
97
152
|
}
|
|
98
153
|
|
|
99
154
|
merge(data.value, receivedData)
|
|
100
155
|
})
|
|
101
156
|
|
|
157
|
+
const onDataRemoved =
|
|
158
|
+
config.onDataRemoved ??
|
|
159
|
+
((data: Ref<TData>, receivedData: any, args?: ResourceContext<TArgs>) => {
|
|
160
|
+
// allow to ignore some update (like for sub collection. E.g. vms/:id/vdis)
|
|
161
|
+
if (!config.watchCollection?.predicate?.(receivedData, args)) {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// for now only support `onDataRemoved` when watching XapiXoRecord collection
|
|
166
|
+
if (Array.isArray(data.value) && !Array.isArray(receivedData)) {
|
|
167
|
+
removeData(data.value, receivedData)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.warn('onDataRemoved received but unhandled for:', receivedData)
|
|
172
|
+
})
|
|
173
|
+
|
|
102
174
|
function subscribeToUrl(url: string) {
|
|
103
175
|
const entry = cache.get(url)
|
|
104
176
|
|
|
@@ -128,9 +200,11 @@ export function defineRemoteResource<
|
|
|
128
200
|
|
|
129
201
|
entry.pause()
|
|
130
202
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
203
|
+
if (cacheExpiration !== false) {
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
cache.delete(url)
|
|
206
|
+
}, cacheExpiration)
|
|
207
|
+
}
|
|
134
208
|
}
|
|
135
209
|
|
|
136
210
|
function registerUrl(url: string, context: ResourceContext<TArgs>) {
|
|
@@ -182,7 +256,23 @@ export function defineRemoteResource<
|
|
|
182
256
|
let pause: VoidFunction = noop
|
|
183
257
|
let resume: VoidFunction = execute
|
|
184
258
|
|
|
185
|
-
if (
|
|
259
|
+
if (config.watchCollection !== undefined) {
|
|
260
|
+
const { collectionId, resource, handleDelete, handlePost, handleWatching } = config.watchCollection
|
|
261
|
+
const { watch, unwatch } = useSseStore()
|
|
262
|
+
|
|
263
|
+
pause = () => unwatch({ collectionId, resource, handleDelete })
|
|
264
|
+
resume = async function () {
|
|
265
|
+
await execute()
|
|
266
|
+
await watch({
|
|
267
|
+
collectionId,
|
|
268
|
+
handleWatching,
|
|
269
|
+
handlePost,
|
|
270
|
+
resource,
|
|
271
|
+
onDataReceived: receivedData => onDataReceived(data, receivedData, context),
|
|
272
|
+
onDataRemoved: receivedData => onDataRemoved(data, receivedData, context),
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
} else if (pollingInterval !== false) {
|
|
186
276
|
const timeoutPoll = useTimeoutPoll(execute, pollingInterval, {
|
|
187
277
|
immediateCallback: true,
|
|
188
278
|
immediate: false,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref, watch as watchVue } from 'vue'
|
|
3
|
+
|
|
4
|
+
type EventFn = (object: unknown) => void
|
|
5
|
+
export type THandlePost = (sseId: string) => Promise<string>
|
|
6
|
+
export type THandleDelete = (sseId: string, subscriptionId: string) => Promise<void>
|
|
7
|
+
export type THandleWatching = (
|
|
8
|
+
updateSseId: (id: string) => void,
|
|
9
|
+
getConfigByResource: (resource: string) =>
|
|
10
|
+
| {
|
|
11
|
+
subscriptionId: string
|
|
12
|
+
configs: Record<
|
|
13
|
+
string,
|
|
14
|
+
{
|
|
15
|
+
add: (object: unknown) => void
|
|
16
|
+
update: (object: unknown) => void
|
|
17
|
+
remove: (object: unknown) => void
|
|
18
|
+
}
|
|
19
|
+
>
|
|
20
|
+
}
|
|
21
|
+
| undefined
|
|
22
|
+
) => void
|
|
23
|
+
|
|
24
|
+
export const useSseStore = defineStore('sse', () => {
|
|
25
|
+
const sse = ref<{ id?: string; isWatching: boolean }>({ isWatching: false })
|
|
26
|
+
const configsByResource: Map<
|
|
27
|
+
string,
|
|
28
|
+
{
|
|
29
|
+
subscriptionId: string
|
|
30
|
+
configs: Record<
|
|
31
|
+
string,
|
|
32
|
+
{
|
|
33
|
+
add: EventFn
|
|
34
|
+
update: EventFn
|
|
35
|
+
remove: EventFn
|
|
36
|
+
}
|
|
37
|
+
>
|
|
38
|
+
}
|
|
39
|
+
> = new Map()
|
|
40
|
+
|
|
41
|
+
function updateSseId(id: string) {
|
|
42
|
+
sse.value.id = id
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getConfigsByResource(resource: string) {
|
|
46
|
+
return configsByResource.get(resource)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function initializeWatcher(handleWatching: THandleWatching) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
if (sse.value.id !== undefined) {
|
|
52
|
+
return reject(new Error('SSE already initialized'))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
watchVue(
|
|
56
|
+
sse,
|
|
57
|
+
value => {
|
|
58
|
+
if (value.id !== undefined) {
|
|
59
|
+
resolve(undefined)
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{ deep: true }
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if (!sse.value.isWatching) {
|
|
66
|
+
sse.value.isWatching = true
|
|
67
|
+
handleWatching(updateSseId, getConfigsByResource)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function watch({
|
|
73
|
+
collectionId,
|
|
74
|
+
resource,
|
|
75
|
+
onDataReceived,
|
|
76
|
+
onDataRemoved,
|
|
77
|
+
handlePost,
|
|
78
|
+
handleWatching,
|
|
79
|
+
}: {
|
|
80
|
+
collectionId: string
|
|
81
|
+
resource: string
|
|
82
|
+
onDataReceived: EventFn
|
|
83
|
+
onDataRemoved: EventFn
|
|
84
|
+
handlePost: THandlePost
|
|
85
|
+
handleWatching: THandleWatching
|
|
86
|
+
}) {
|
|
87
|
+
if (sse.value.id === undefined) {
|
|
88
|
+
await initializeWatcher(handleWatching)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const id = await handlePost(sse.value.id!)
|
|
92
|
+
|
|
93
|
+
const { subscriptionId, configs } = configsByResource.get(resource) ?? {}
|
|
94
|
+
|
|
95
|
+
if (subscriptionId !== undefined && id !== subscriptionId) {
|
|
96
|
+
throw new Error(`Previous subscription ID: ${subscriptionId} and new one: ${id} are not the same`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
configsByResource.set(resource, {
|
|
100
|
+
subscriptionId: id,
|
|
101
|
+
configs: {
|
|
102
|
+
...configs,
|
|
103
|
+
[collectionId]: {
|
|
104
|
+
add: onDataReceived,
|
|
105
|
+
update: onDataReceived,
|
|
106
|
+
remove: onDataRemoved,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function unwatch({
|
|
113
|
+
collectionId,
|
|
114
|
+
resource,
|
|
115
|
+
handleDelete,
|
|
116
|
+
}: {
|
|
117
|
+
collectionId: string
|
|
118
|
+
resource: string
|
|
119
|
+
handleDelete: THandleDelete
|
|
120
|
+
}) {
|
|
121
|
+
if (sse.value.id === undefined || !configsByResource.has(resource)) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { configs, subscriptionId } = configsByResource.get(resource)!
|
|
126
|
+
delete configs[collectionId]
|
|
127
|
+
|
|
128
|
+
if (Object.keys(configs).length === 0) {
|
|
129
|
+
await handleDelete(sse.value.id, subscriptionId)
|
|
130
|
+
configsByResource.delete(resource)
|
|
131
|
+
} else {
|
|
132
|
+
configsByResource.set(resource, {
|
|
133
|
+
subscriptionId,
|
|
134
|
+
configs,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { watch, unwatch }
|
|
140
|
+
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { XapiXoRecord, XoAlarm } from '@vates/types/'
|
|
2
|
+
|
|
1
3
|
export type MaybeArray<T> = T | T[]
|
|
2
4
|
|
|
3
5
|
export type VoidFunction = () => void
|
|
@@ -18,3 +20,7 @@ export type KeyOfByValue<T, TValue> =
|
|
|
18
20
|
: never
|
|
19
21
|
|
|
20
22
|
export type ArrayFilterPredicate<T> = (value: T, index: number, array: T[]) => boolean
|
|
23
|
+
|
|
24
|
+
export type GetRecordByType<TType extends XapiXoRecord['type'] | 'alarm'> = TType extends 'alarm'
|
|
25
|
+
? XoAlarm
|
|
26
|
+
: Extract<XapiXoRecord, { type: TType }>
|
|
@@ -28,24 +28,26 @@ export function cpuProgressThresholds(tooltip?: string): ThresholdConfig<Progres
|
|
|
28
28
|
|
|
29
29
|
export function useProgressToLegend(
|
|
30
30
|
rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>
|
|
31
|
-
): (label: string, progress: Progress | Reactive<Progress>) => ProgressBarLegend | undefined
|
|
31
|
+
): (label: string | undefined, progress: Progress | Reactive<Progress>) => ProgressBarLegend | undefined
|
|
32
32
|
|
|
33
33
|
export function useProgressToLegend(
|
|
34
34
|
rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>,
|
|
35
|
-
|
|
35
|
+
rawLabel: MaybeRefOrGetter<string | undefined>,
|
|
36
36
|
progress: Progress | Reactive<Progress>
|
|
37
37
|
): ComputedRef<ProgressBarLegend | undefined>
|
|
38
38
|
|
|
39
39
|
export function useProgressToLegend(
|
|
40
40
|
rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>,
|
|
41
|
-
|
|
41
|
+
rawLabel?: MaybeRefOrGetter<string | undefined>,
|
|
42
42
|
progress?: Progress | Reactive<Progress>
|
|
43
43
|
) {
|
|
44
44
|
const { n } = useI18n()
|
|
45
45
|
|
|
46
46
|
const type = toComputed(rawType)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
const label = toComputed(rawLabel)
|
|
49
|
+
|
|
50
|
+
function toLegend(label: string = '', progress: Progress | Reactive<Progress>): ProgressBarLegend | undefined {
|
|
49
51
|
switch (type.value) {
|
|
50
52
|
case 'percent':
|
|
51
53
|
return { label, value: n(toValue(progress.percentage) / 100, { maximumFractionDigits: 1, style: 'percent' }) }
|
|
@@ -66,7 +68,7 @@ export function useProgressToLegend(
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
if (label && progress) {
|
|
69
|
-
return computed(() => toLegend(label, progress))
|
|
71
|
+
return computed(() => toLegend(label.value, progress))
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
return toLegend
|