@xen-orchestra/web-core 0.57.0 → 0.58.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.
@@ -39,6 +39,7 @@
39
39
  "action:create-network": "Vytvoriť sieť",
40
40
  "action:create-traffic-rule": "Vytvoriť pravidlo pre prevádzku",
41
41
  "action:create-vdi": "Vytvoriť obraz virt. disku",
42
+ "action:create-vif": "Vytvoriť virt. sieťové rozhranie",
42
43
  "action:delete": "Odstrániť",
43
44
  "action:delete-filter": "Odstrániť filter",
44
45
  "action:delete-group": "Odstrániť skupinu",
@@ -61,27 +62,34 @@
61
62
  "action:disconnect-n-vifs": "Odpojiť 1 virt. sieťové rozhranie | Odpojiť {n} virt. sieťové rozhrania | Odpojiť {n} virt. sieťových rozhraní",
62
63
  "action:download-bugtools-archive": "Stiahnuť archív s výstupom bugtools",
63
64
  "action:duplicate": "Zduplikovať",
65
+ "action:duplicate-n-vms": "Zduplikovať 1 virt. stroj | Zduplikovať {n} virt. stroje | Zduplikovať {n} virt. strojov",
64
66
  "action:edit": "Upraviť",
65
67
  "action:edit-config": "Upraviť konfiguráciu",
66
68
  "action:enable-light-mode": "Zapnúť svetlý režim",
67
69
  "action:export": "Export",
70
+ "action:export-content": "Exportovať obsah",
68
71
  "action:export-n-vms": "Exportovať 1 virt. stroj | Exportovať {n} virt. strojov",
69
72
  "action:export-n-vms-manually": "Exportovať 1 virt. stroj ručne | Exportovať {n} virt. strojov ručne",
70
73
  "action:export-table-to": "Exportovať tabuľku do {type}",
74
+ "action:export-vdi": "Exportovať obraz virt. disku",
71
75
  "action:export-vm": "Exportovať virt. stroj | Exportovať virt. stroje",
72
76
  "action:filter": "Filtrovať",
77
+ "action:force-migrate-on-sr": "Vynútiť presťahovanie na repozitár úložiska",
73
78
  "action:force-reboot": "Vynútiť reštart",
74
79
  "action:force-shutdown": "Vynútiť vypnutie",
75
80
  "action:forget": "Zabudnúť",
76
81
  "action:go-back": "Prejsť späť",
77
82
  "action:group": "Skupina",
78
83
  "action:hide": "Skryť",
84
+ "action:import": "Naimportovať",
85
+ "action:import-export": "Import/Export",
79
86
  "action:install-app": "Inštalovať {name}",
80
87
  "action:install-guest-tools": "Nainštalovať nástroje do hosťa",
81
88
  "action:load-now": "Načítať teraz",
82
89
  "action:log-out": "Odhlásiť sa",
83
90
  "action:migrate": "Migrovať",
84
91
  "action:migrate-n-vms": "Migrovať 1 virt. stroj | Migrovať {n} virt. strojov",
92
+ "action:migrate-vdi-on-sr": "Presťahovať na repozitár úložiska",
85
93
  "action:move-filters-to-parent-group": "Presunúť filtre do nadradenej skupiny",
86
94
  "action:open": "Otvoriť",
87
95
  "action:open-app": "@:action:open {name}",
@@ -152,6 +160,8 @@
152
160
  "allocated-space": "Pridelený priestor",
153
161
  "allow": "Umožniť",
154
162
  "allow-self-signed-ssl": "Je možné že bude potrebné vo vami využívanom prehliadači povoliť sami sebou podpísané SSL certifikáty",
163
+ "allowed-ips": "Umožnené IP adresy",
164
+ "allowed-ips-example": "Napríklad: 192.168.0.1; 2001:db8::1",
155
165
  "and": "A",
156
166
  "any-property": "Akákoľvek vlastnosť",
157
167
  "api-error-details": "Podrobnosti o chybe rozhrania API",
@@ -220,6 +230,7 @@
220
230
  "build-number": "Číslo zostavenia",
221
231
  "by": "Od",
222
232
  "bytes:gi": "GiB",
233
+ "bytes:kbs": "kB/s",
223
234
  "bytes:ki": "KiB",
224
235
  "bytes:mi": "MiB",
225
236
  "cancel": "Zrušiť",
@@ -240,6 +251,7 @@
240
251
  "community": "Komunita",
241
252
  "community-name": "{name} komunita",
242
253
  "compression": "Kompresia",
254
+ "compression-not-available": "Pre repozitárov úložiska z rovnakého fondu nie je kompresia k dispozícii.",
243
255
  "concurrency": "Súbežnosť",
244
256
  "configuration": "Konfigurácia",
245
257
  "configure-for-protected": "Aby ste zaistili ochranu svojich virt. strojov, zvážte nastavenie {backup-job}",
@@ -282,6 +294,7 @@
282
294
  "creating-new-network": "Vytváranie novej siete",
283
295
  "creating-new-traffic-rule": "Vytvoriť nové pravidlo pre prevádzku",
284
296
  "creating-new-vdi": "Vytvára sa nový obraz virt. disku",
297
+ "creating-new-vif": "Vytváranie nového virt. sieťového rozhrania",
285
298
  "creation-date": "Dátum vytvorenia",
286
299
  "cron-pattern": "Vzorec plánovania (cron)",
287
300
  "current": "Súčasné",
@@ -306,6 +319,8 @@
306
319
  "deploy-xoa-status:waiting": "Čaká sa až XOA odpovie…",
307
320
  "descending": "zostupne",
308
321
  "description": "Popis",
322
+ "destination-sr": "Cieľový repozitár úložiska",
323
+ "destination-sr-mandatory": "Je potrebné určiť cieľový repozitár úložiska.",
309
324
  "device": "Zariadenie",
310
325
  "device-config": "Konfigurácia zariadenia",
311
326
  "dhcp": "DHCP",
@@ -332,6 +347,9 @@
332
347
  "documentation-name": "Dokumentácia pre {name}",
333
348
  "done": "Hotovo",
334
349
  "drop": "Zahodiť",
350
+ "duplicate-vm:error-message": "Pri duplikovaní virt. stroje došlo k chybe. Skontrolujte nastavenia (názov, repozitár úložiska, kompresiu) a skúste to znova.",
351
+ "duplicating-vm": "Duplikovanie virt. stroje",
352
+ "duplication-method": "Metóda duplikácie",
335
353
  "duration": "Trvanie",
336
354
  "empty": "Prázdne",
337
355
  "enabled": "Povolené",
@@ -352,6 +370,7 @@
352
370
  "failed": "Neúspešné",
353
371
  "failure": "Zlyhanie",
354
372
  "fast-clone": "Rýchly klon",
373
+ "fast-clone-available": "Rýchly klon je k dispozícii iba pre zastavené virt. stroje.",
355
374
  "fetching-fresh-data": "Získavanie aktuálnych údajov",
356
375
  "field:exceeds-max-characters": "Je potrebné, aby zadanie v kolónke bolo dlhé najviac {max} znakov.",
357
376
  "file": "Súbor",
@@ -369,6 +388,7 @@
369
388
  "free-space": "Voľné miesto",
370
389
  "free-space-on-sr": "Voľný priestor na repozitári úložiska",
371
390
  "from-vm": "Od virt. stroje",
391
+ "full-copy": "Úplná kópia",
372
392
  "fullscreen": "Celá obrazovka",
373
393
  "gateway": "Prehod",
374
394
  "general": "Všeobecné",
@@ -378,6 +398,7 @@
378
398
  "graphics-display": "Grafika a zobrazenie",
379
399
  "groups": "Skupiny",
380
400
  "guest-tools": "Nástroje pre hostí",
401
+ "gzip": "GZIP",
381
402
  "hardware": "Hardvér",
382
403
  "hardware-specifications": "Hardvérové špecifikácie",
383
404
  "heartbeat-storage-repository": "Repozitár úložiska kontroly stavu",
@@ -412,6 +433,9 @@
412
433
  "interfaces": "Rozhrania",
413
434
  "interrupted": "Prerušené",
414
435
  "invalid-field": "Neplatné pole",
436
+ "invalid-ip-addresses": "Jedna alebo viac neplatných IP adries",
437
+ "invalid-mac-address": "Neplatná MAC adresa",
438
+ "invalid-rate-limit": "Obmedzovanie rýchlosti nemôže byť záporné",
415
439
  "ip-address": "IP adresa",
416
440
  "ip-address-info": "Pravidlo je možné uplatniť na jednotlivú IP adresu alebo podsieť (v CIDR formáte). Príklady: 192.168.1.10 alebo 10.20.0.0/16",
417
441
  "ip-address-invalid": "Formát IP adresy nie je platný.",
@@ -455,6 +479,8 @@
455
479
  "job:create:in-progress": "Prebieha vytváranie…",
456
480
  "job:delete:in-progress": "Prebieha odstraňovanie…",
457
481
  "job:disconnect:in-progress": "Prebieha odpájanie…",
482
+ "job:export:in-progress": "Prebieha export…",
483
+ "job:migrate:in-progress": "Sťahovanie prebieha…",
458
484
  "job:network-delete:has-n-physical-pif-connected": "Sieť má pripojené jedno fyzické rozhranie | Sieť má pripojené {n} fyzické rozhrania | Sieť má pripojených {n} fyzických rozhraní",
459
485
  "job:network-delete:has-n-vif-attached": "Sieť má pripojené jedno virtuálne rozhranie | Sieť má pripojená {n} virtuálne rozhranie | Sieť má pripojených {n} virtuálnych rozhraní",
460
486
  "job:pbd-plug:missing-pbd": "Žiadne fyzické blokové zariadenia na pripojenie",
@@ -474,6 +500,12 @@
474
500
  "job:vbd-disconnect:vbd-disconnected": "Virt. blokové zariadenie je odpojené",
475
501
  "job:vdi-delete:missing-vdi": "Žiadne virt. disky na zmazanie",
476
502
  "job:vdi-delete:vbd-attached": "Nie je možné zmazať virt. disk, ku ktorému je pripojené virtuálne blokové zariadenie",
503
+ "job:vdi-detach:in-progress": "Prebieha rušenie priloženia…",
504
+ "job:vdi-export:format-required": "Vyžaduje sa formát exportu obrazu virt. disku",
505
+ "job:vdi-export:missing-vdi": "Žiadny obraz virt. disku na export",
506
+ "job:vdi-migrate:in-progress": "Prebieha sťahovanie…",
507
+ "job:vdi-migrate:missing-sr": "Je potrebný repozitár úložiska",
508
+ "job:vdi-migrate:missing-vdi": "Nevybraný žiadny obraz virt. disku",
477
509
  "job:vif-connect:missing-vif": "Žiadna virt. sieťové rozhrania na pripojenie",
478
510
  "job:vif-connect:vif-already-connected": "Virt. sieťové rozhranie už je pripojené",
479
511
  "job:vif-delete:blocked-operation": "Mazanie virt. sieťového rozhrania je blokované",
@@ -657,6 +689,8 @@
657
689
  "new-vdi:add": "Pridať nový obraz virt. disku",
658
690
  "new-vdi:error-message": "Pri pridávaní obrazu virt. disku došlo k chybe. Skontrolujte nastavenie (názov, repozitár úložiska, pridelený priestor) a skúste to znova.",
659
691
  "new-vif": "Nové VIF",
692
+ "new-vif:add": "Pridať nové virt. sieťové rozhranie",
693
+ "new-vif:error-message": "Došlo k chybe pri vytváraní nového virt. sieťového rozhrania. Skontrolujte nastavenia a skúste to znova.",
660
694
  "new-vm": "Nový VM",
661
695
  "new-vm:add": "Pridať nový virt. stroj",
662
696
  "new-vm:description": "Popis VM",
@@ -725,6 +759,7 @@
725
759
  "operation:clean-reboot": "Reštartovanie",
726
760
  "operation:clean-shutdown": "Vypínanie",
727
761
  "operation:destroy": "Odstraňovanie",
762
+ "operation:duplicate": "Duplikovanie",
728
763
  "operation:force-reboot": "Vynútené reštartovanie",
729
764
  "operation:force-shutdown": "Vynútené vypínanie",
730
765
  "operation:pause": "Pozastavenie",
@@ -741,6 +776,7 @@
741
776
  "other": "Iné",
742
777
  "other-properties": "Ostatné vlastnosti",
743
778
  "other-settings": "Ďalšie nastavenia",
779
+ "ova": "OVA",
744
780
  "page-not-found": "Táto stránka sa nedá nájsť…",
745
781
  "page-please-wait": "Drž sa pevne, astronaut! Stránka sa pripravuje na start.{newline}Práve nastavujeme trysky a kalibrujeme systémy. Už to dlho nepotrvá, zostaňte naladení!",
746
782
  "pagination:all": "@:all",
@@ -885,8 +921,9 @@
885
921
  "see-details": "Zobraziť podrobnosti",
886
922
  "select-compression": "Vyberte kompresiu",
887
923
  "select-destination-host": "Vyberte cieľového hostiteľa",
924
+ "select-format": "Vyberte formát",
888
925
  "select-host": "Vyberte hostiteľa",
889
- "select-to-see-details": "Ak chcete zobraziť podrobnosti, vyberte prvok",
926
+ "select-to-see-details": "Podrobnosti si zobrazíte kliknutím na {icon}",
890
927
  "selected-vms-in-execution": "Niektoré z vybraných virt. stroje sú spustené",
891
928
  "self-signed-certificates": "Samy sebou podpísané certifikáty",
892
929
  "send-us-feedback": "Pošlite nám spätnú väzbu",
@@ -927,6 +964,7 @@
927
964
  "sr-disconnect-info-pool-mixed": "Lokálne repozitára úložiska budú odpojené od svojich príslušných hostiteľov. Zdieľané repozitáre budú odpojené od všetkých hostiteľov v tomto fonde.",
928
965
  "sr-disconnect-info-pool-shared": "Repozitár úložiska bude odpojený od {hostsCount} hostiteľov v tomto fonde. | Každý zdieľaný repozitár úložiska bude odpojený od všetkých hostiteľov v tomto fonde.",
929
966
  "sr-disconnect-title": "Chystáte sa odpojiť 1 repozitár úložiska | Chystáte sa odpojiť {n} repozitára úložiska | Chystáte sa odpojiť {n} repozitárov úložiska",
967
+ "sr-required": "Je potrebný repozitár úložiska.",
930
968
  "ssh-account": "SSH účet",
931
969
  "ssh-key": "SSH kľúč",
932
970
  "ssh-login": "SSH prihlasovacie meno",
@@ -1022,6 +1060,8 @@
1022
1060
  "traffic-rule": "Pravidlo pre prevádzku",
1023
1061
  "traffic-rule-delete-warning": "Túto akciu nemožno vziať späť. Označené pravidlo pre prevádzku bude nevratne zmazané.",
1024
1062
  "traffic-rules": "Pravidlá pre prevádzku",
1063
+ "traffic-rules:format-warning": "Chyba nastavenia: Pravidlá pre prevádzku nie sú v očakávanom formáte. {check-doc} ohľadom toho, ako ich previesť.",
1064
+ "traffic-rules:format-warning:check-doc": "Pozrite sa do najaktuálnejšej dokumentácie",
1025
1065
  "traffic-rules:info-message": "Implementácia poradia pravidiel pre prevádzku ešte prebieha. Chovanie môže byť nebezpečné.",
1026
1066
  "traffic-rules:no-traffic-rules-detected": "Nezjištěná žiadna pravidla pre prevádzku",
1027
1067
  "transfer-size": "Objem prenosu",
@@ -1038,7 +1078,10 @@
1038
1078
  "unable-to-create-new-network": "Nebolo možné vytvoriť novú sieť",
1039
1079
  "unable-to-create-new-traffic-rule": "Nebolo možné vytvoriť nové pravidlo pre prevádzku",
1040
1080
  "unable-to-create-new-vdi": "Nebolo možné vytvoriť nový obraz virt. disku",
1081
+ "unable-to-create-new-vif": "Nebolo možné vytvoriť nové virt. sieťové rozhranie",
1041
1082
  "unable-to-delete-network": "Sieť nebolo možné zmazať",
1083
+ "unable-to-duplicate-vm": "Nebolo možné zduplikovať virt. stroj",
1084
+ "unable-to-migrate-vdi": "Nebolo možné presťahovať obraz virt. disku na repozitár úložiska",
1042
1085
  "unknown": "Neznámo",
1043
1086
  "unlocked": "Odomknuté",
1044
1087
  "unnamed": "Bez názvu",
@@ -1072,6 +1115,7 @@
1072
1115
  "vdi-in-use": "Obraz virt. disku už je využívaný",
1073
1116
  "vdi-name": "Názov obrazu virt. disku",
1074
1117
  "vdi-not-attached-to-vm": "Nepripojený žiadny virt. stroj",
1118
+ "vdi-on-different-sr-warning": "Tento repozitár úložiska je lokálny pre hostiteľa, ktorý nedrží ostatné disky virt. stroje. Virt. stroj nebude môcť naštartovať pokiaľ sa nebudú všetky jeho disky nachádzať na rovnakom hostiteľovi alebo na zdieľanom úložisku.",
1075
1119
  "vdi-snapshot-source": "Zdroj obrazu virt. disku",
1076
1120
  "vdi-snpashot-time": "Dátum zachytenia stavu",
1077
1121
  "vdi-throughput": "Objemy čítania/zápisov na virt. disky",
@@ -1102,6 +1146,8 @@
1102
1146
  "vm-is-running": "Virt. stroj je spustený",
1103
1147
  "vm-limit-topology": "Limit topológie virt. stroje",
1104
1148
  "vm-management": "Správa VM",
1149
+ "vm-name": "Názov virt. stroje",
1150
+ "vm-name-required": "Je potrebný názov virt. stroje.",
1105
1151
  "vm-not-running": "Virt. stroj nie je spustený",
1106
1152
  "vm-off": "Tento virtuálny stroj je vypnutý.",
1107
1153
  "vm-protected-deletion": "Tento virtuálny počítač je momentálne chránený pred vymazaním.",
@@ -1156,5 +1202,7 @@
1156
1202
  "xoa-password-confirm-different": "Heslo do XOA nebolo zadané dvakrát rovnako",
1157
1203
  "xoa-ssh-account": "Účet pre SSH prístup do XOA",
1158
1204
  "xoa-ssh-password-confirm-different": "Heslo pre SSH nebolo zadané dvakrát rovnako",
1159
- "you-are-currently-on": "V tejto chvíli sa nachádzate na: {0}"
1205
+ "xva": "XVA",
1206
+ "you-are-currently-on": "V tejto chvíli sa nachádzate na: {0}",
1207
+ "zstd": "ZSTD"
1160
1208
  }
@@ -163,12 +163,13 @@ An event that is _not_ declared in `useOverlay` can also be handled at open time
163
163
 
164
164
  ## Aborted overlays
165
165
 
166
- An overlay can be torn down without a user decision:
166
+ An overlay is not tied to the lifecycle of the component that opened it: it stays open until the user makes a decision, or until it is aborted. An overlay can be aborted in three ways:
167
167
 
168
- - the component that created it (where `useOverlay` was called) is unmounted (its scope is disposed),
169
- - or `open()` is called again on the same `useOverlay` instance while the overlay is still open (the previous one is replaced).
168
+ - `open()` is called again on the same `useOverlay` instance while the overlay is still open (the previous one is replaced),
169
+ - the `abort()` function returned by `useOverlay` is called,
170
+ - or the store's `abortAll()` is called (see below).
170
171
 
171
- In both cases the promise resolves with `OVERLAY_ABORT_EVENT` (a symbol) as its `event`, and no handler runs:
172
+ In all cases the promise resolves with `OVERLAY_ABORT_EVENT` (a symbol) as its `event`, and no handler runs:
172
173
 
173
174
  ```ts
174
175
  const response = await open()
@@ -180,6 +181,10 @@ if (response.event === OVERLAY_ABORT_EVENT) {
180
181
 
181
182
  The `OVERLAY_ABORT_EVENT` case is always part of the response union, so exhaustive `event` checks will remind you it exists.
182
183
 
184
+ Aborting an overlay whose decision is already being resolved is a no-op: the promise resolves with the actual response.
185
+
186
+ An overlay can also be aborted while one of its handlers is still awaiting (for example, navigating away while a save is in progress). In that case the overlay closes right away with `OVERLAY_ABORT_EVENT`. The handler's async work keeps running — it cannot be cancelled — but the value it eventually returns is ignored, and the caller sees an abort.
187
+
183
188
  ## Busy and disabled triggers
184
189
 
185
190
  Inside the overlay component, use `useOverlayTrigger()` to wire the controls that emit events (buttons, typically) to the overlay's state:
@@ -283,3 +288,13 @@ Opened overlays live in a Pinia store (`useOverlayStore`). The app must render t
283
288
  ```
284
289
 
285
290
  Styling and animations are up to you. The store also exposes `isCurrent(key)` to style stacked overlays (e.g. dim the ones below the topmost), and each `overlay` carries its current `status`.
291
+
292
+ Since overlays are not tied to the component that opened them, dismissing them on navigation is the app's decision. If that's the behavior you want, wire it once on the router:
293
+
294
+ ```ts
295
+ router.isReady().then(() => {
296
+ router.afterEach(() => useOverlayStore().abortAll())
297
+ })
298
+ ```
299
+
300
+ Register the guard only once the router is ready: the initial navigation also triggers `afterEach`, which would abort any overlay opened while the app was starting up.
@@ -1,7 +1,6 @@
1
1
  import { isThenable } from '@core/packages/overlay/is-thenable.ts'
2
2
  import { KEEP_OVERLAY_OPEN } from '@core/packages/overlay/symbols.ts'
3
- import type { Overlay, OverlayEventHandler, OverlayResponse } from '@core/packages/overlay/types.ts'
4
- import { nextTick } from 'vue'
3
+ import type { Overlay, OverlayEventHandler, OverlayResponse, OverlayStatus } from '@core/packages/overlay/types.ts'
5
4
 
6
5
  export function createEventHandler({
7
6
  overlay,
@@ -12,7 +11,7 @@ export function createEventHandler({
12
11
  overlay: Overlay
13
12
  definitionEvents: Record<string, true | OverlayEventHandler>
14
13
  openEvents: Record<string, OverlayEventHandler>
15
- settle: (response: OverlayResponse<PropertyKey, unknown>) => void
14
+ settle: (response: OverlayResponse<PropertyKey, unknown>) => Promise<void>
16
15
  }) {
17
16
  const shouldStop = (payload: unknown) => payload === KEEP_OVERLAY_OPEN || overlay.status === 'settled'
18
17
 
@@ -32,7 +31,7 @@ export function createEventHandler({
32
31
  }
33
32
 
34
33
  try {
35
- overlay.status = 'locked'
34
+ overlay.status = 'locked' as OverlayStatus
36
35
 
37
36
  // The definition handler receives the raw emit arguments, then the open
38
37
  // handler receives the payload it produced. An event handled at open
@@ -62,13 +61,7 @@ export function createEventHandler({
62
61
  }
63
62
  }
64
63
 
65
- overlay.status = 'settled'
66
-
67
- // The leave transition freezes the DOM in its last painted state,
68
- // so paint a non-busy frame before removing the overlay
69
- await nextTick()
70
-
71
- settle({ event: eventName, payload })
64
+ await settle({ event: eventName, payload })
72
65
  } finally {
73
66
  if (overlay.status !== 'settled') {
74
67
  overlay.status = 'idle'
@@ -62,6 +62,7 @@ export interface Overlay {
62
62
  props: Record<string, unknown>
63
63
  status: OverlayStatus
64
64
  lastTrigger: symbol | undefined
65
+ abort: () => void
65
66
  }
66
67
 
67
68
  export type ExtractEventName<TName> = TName extends `onVnode${string}`
@@ -15,6 +15,10 @@ export const useOverlayStore = defineStore('overlay', () => {
15
15
  }
16
16
  }
17
17
 
18
+ function abortAll() {
19
+ registry.value.forEach(overlay => overlay.abort())
20
+ }
21
+
18
22
  function isCurrent(key: symbol): boolean {
19
23
  return Array.from(registry.value.keys()).at(-1) === key
20
24
  }
@@ -27,6 +31,7 @@ export const useOverlayStore = defineStore('overlay', () => {
27
31
  overlays: computed(() => Array.from(registry.value.values())),
28
32
  register,
29
33
  unregister,
34
+ abortAll,
30
35
  isCurrent,
31
36
  get,
32
37
  }
@@ -13,7 +13,7 @@ import type {
13
13
  } from '@core/packages/overlay/types.ts'
14
14
  import { useOverlayStore } from '@core/packages/overlay/use-overlay-store.ts'
15
15
  import { reactiveComputed } from '@vueuse/core'
16
- import { defineAsyncComponent, markRaw, onScopeDispose, reactive, type AsyncComponentLoader, type Component } from 'vue'
16
+ import { defineAsyncComponent, markRaw, nextTick, reactive, type AsyncComponentLoader, type Component } from 'vue'
17
17
  import type { ComponentProps } from 'vue-component-type-helpers'
18
18
 
19
19
  export function useOverlay<TComponent extends Component, const TEvents extends OverlayEvents<TComponent>>({
@@ -29,9 +29,11 @@ export function useOverlay<TComponent extends Component, const TEvents extends O
29
29
 
30
30
  const component = defineAsyncComponent(componentLoader)
31
31
 
32
- let abortCurrent: (() => void) | undefined
32
+ let currentOverlay: Overlay | undefined
33
33
 
34
- onScopeDispose(() => abortCurrent?.(), true)
34
+ function abort() {
35
+ currentOverlay?.abort()
36
+ }
35
37
 
36
38
  function open<TOpenEvents extends OpenEvents<TComponent, TEvents>>(
37
39
  ...args: MaybeOptionalArgs<
@@ -45,8 +47,7 @@ export function useOverlay<TComponent extends Component, const TEvents extends O
45
47
  ) {
46
48
  const { events: openEvents, props } = args[0] ?? {}
47
49
 
48
- // Reopening while the previous overlay is still open replaces it
49
- abortCurrent?.()
50
+ abort()
50
51
 
51
52
  return new Promise<ExtractOverlayResponse<TComponent, TEvents, TOpenEvents>>(resolve => {
52
53
  const definitionEvents = events as Record<string, true | OverlayEventHandler>
@@ -60,16 +61,30 @@ export function useOverlay<TComponent extends Component, const TEvents extends O
60
61
  props: {},
61
62
  status: 'idle',
62
63
  lastTrigger: undefined,
64
+ abort: () => settle({ event: OVERLAY_ABORT_EVENT, payload: undefined }),
63
65
  })
64
66
 
65
- function settle(response: ExtractOverlayResponse<TComponent, TEvents, TOpenEvents>) {
67
+ currentOverlay = overlay
68
+
69
+ async function settle(response: ExtractOverlayResponse<TComponent, TEvents, TOpenEvents>) {
70
+ if (overlay.status === 'settled') {
71
+ return
72
+ }
73
+
66
74
  overlay.status = 'settled'
67
- abortCurrent = undefined
75
+
76
+ // The `leave` transition freezes the DOM in its last painted state,
77
+ // so paint a non-busy frame before removing the overlay
78
+ await nextTick()
79
+
68
80
  resolve(response)
81
+
69
82
  overlayStore.unregister(overlay)
70
- }
71
83
 
72
- abortCurrent = () => settle({ event: OVERLAY_ABORT_EVENT, payload: undefined })
84
+ if (currentOverlay === overlay) {
85
+ currentOverlay = undefined
86
+ }
87
+ }
73
88
 
74
89
  const handleEvent = createEventHandler({
75
90
  overlay,
@@ -95,5 +110,5 @@ export function useOverlay<TComponent extends Component, const TEvents extends O
95
110
  })
96
111
  }
97
112
 
98
- return { open }
113
+ return { open, abort }
99
114
  }
@@ -46,7 +46,7 @@ export function defineRemoteResource<
46
46
  receivedData: any,
47
47
  calledFrom: 'execute' | 'update',
48
48
  context?: ResourceContext<TArgs>
49
- ) => void
49
+ ) => Promise<void>
50
50
  cacheExpirationMs?: number | false
51
51
  pollingIntervalMs?: number | false
52
52
  stream?: boolean
@@ -60,7 +60,7 @@ export function defineRemoteResource<TData, TState extends object, TArgs extends
60
60
  receivedData: any,
61
61
  calledFrom: 'execute' | 'update',
62
62
  context?: ResourceContext<TArgs>
63
- ) => void
63
+ ) => Promise<void>
64
64
  cacheExpirationMs?: number | false
65
65
  pollingIntervalMs?: number | false
66
66
  stream?: boolean
@@ -79,8 +79,8 @@ export function defineRemoteResource<
79
79
  receivedData: any,
80
80
  calledFrom: 'execute' | 'update',
81
81
  context?: ResourceContext<TArgs>
82
- ) => void
83
- onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
82
+ ) => Promise<void>
83
+ onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => Promise<void>
84
84
  stream?: boolean
85
85
  initWatchCollection: () => {
86
86
  collectionId: string
@@ -89,7 +89,7 @@ export function defineRemoteResource<
89
89
  handleDelete: THandleDelete
90
90
  handlePost: THandlePost
91
91
  handleWatching: THandleWatching
92
- predicate?: (receivedData: TData, context: ResourceContext<TArgs> | undefined) => boolean
92
+ predicate?: (receivedData: TData, context: ResourceContext<TArgs> | undefined) => Promise<boolean> | boolean
93
93
  }
94
94
  }): UseRemoteResource<TState, TArgs>
95
95
 
@@ -106,8 +106,8 @@ export function defineRemoteResource<
106
106
  receivedData: any,
107
107
  calledFrom: 'execute' | 'update',
108
108
  context?: ResourceContext<TArgs>
109
- ) => void
110
- onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
109
+ ) => Promise<void>
110
+ onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => Promise<void>
111
111
  cacheExpirationMs?: number | false
112
112
  pollingIntervalMs?: number | false
113
113
  stream?: boolean
@@ -118,15 +118,18 @@ export function defineRemoteResource<
118
118
  handleDelete: THandleDelete
119
119
  handlePost: THandlePost
120
120
  handleWatching: THandleWatching
121
- predicate?: (receivedData: TData, context: ResourceContext<TArgs> | undefined) => boolean
121
+ predicate?: (receivedData: TData, context: ResourceContext<TArgs> | undefined) => Promise<boolean> | boolean
122
122
  }
123
123
  }) {
124
124
  const cache = new Map<
125
125
  string,
126
126
  {
127
127
  count: number
128
+ evictionTimeout?: NodeJS.Timeout // TODO : Fixme , the actual type in browser is number, but NodeJS.Timeout in nodejs.
129
+ // Due to transitive types of vates/types, the type is NodeJS.Timeout.
128
130
  pause: VoidFunction
129
131
  resume: VoidFunction
132
+ isPaused: boolean
130
133
  state: object
131
134
  stateScope: EffectScope
132
135
  isReady: Ref<boolean>
@@ -181,9 +184,14 @@ export function defineRemoteResource<
181
184
 
182
185
  const onDataReceived =
183
186
  config.onDataReceived ??
184
- ((data: Ref<TData>, receivedData: any, calledFrom: 'execute' | 'update', context?: ResourceContext<TArgs>) => {
187
+ (async (
188
+ data: Ref<TData>,
189
+ receivedData: any,
190
+ calledFrom: 'execute' | 'update',
191
+ context?: ResourceContext<TArgs>
192
+ ) => {
185
193
  // allow to ignore some update (like for sub collection. E.g. vms/:id/vdis)
186
- if (watchCollection?.predicate?.(receivedData, context) === false) {
194
+ if ((await watchCollection?.predicate?.(receivedData, context)) === false) {
187
195
  return
188
196
  }
189
197
 
@@ -217,9 +225,9 @@ export function defineRemoteResource<
217
225
 
218
226
  const onDataRemoved =
219
227
  config.onDataRemoved ??
220
- ((data: Ref<TData>, receivedData: any, context?: ResourceContext<TArgs>) => {
228
+ (async (data: Ref<TData>, receivedData: any, context?: ResourceContext<TArgs>) => {
221
229
  // allow to ignore some update (like for sub collection. E.g. vms/:id/vdis)
222
- if (watchCollection?.predicate?.(receivedData, context) === false) {
230
+ if ((await watchCollection?.predicate?.(receivedData, context)) === false) {
223
231
  return
224
232
  }
225
233
 
@@ -246,7 +254,12 @@ export function defineRemoteResource<
246
254
  entry.count += 1
247
255
 
248
256
  if (entry.count === 1) {
249
- entry.resume()
257
+ clearTimeout(entry.evictionTimeout)
258
+ entry.evictionTimeout = undefined
259
+ if (entry.isPaused) {
260
+ entry.resume()
261
+ entry.isPaused = false
262
+ }
250
263
  }
251
264
  }
252
265
 
@@ -263,13 +276,21 @@ export function defineRemoteResource<
263
276
  return
264
277
  }
265
278
 
266
- entry.pause()
279
+ function cleanUp() {
280
+ if (entry === undefined) {
281
+ return
282
+ }
283
+
284
+ entry.pause()
285
+ entry.isPaused = true
286
+ entry.stateScope.stop()
287
+ cache.delete(url)
288
+ }
267
289
 
268
290
  if (cacheExpiration !== false) {
269
- setTimeout(() => {
270
- cache.get(url)?.stateScope.stop()
271
- cache.delete(url)
272
- }, cacheExpiration)
291
+ entry.evictionTimeout = setTimeout(cleanUp, cacheExpiration)
292
+ } else {
293
+ cleanUp()
273
294
  }
274
295
  }
275
296
 
@@ -285,6 +306,8 @@ export function defineRemoteResource<
285
306
  const lastError = ref<Error>()
286
307
 
287
308
  const hasError = computed(() => lastError.value !== undefined)
309
+ const stateScope = effectScope(true)
310
+ const sharedContext = { ...context, scope: stateScope }
288
311
 
289
312
  const data = shallowRef(buildData()) as Ref<TData>
290
313
  // trigger reactivity on data when no more updates since 100ms or after 500ms
@@ -322,7 +345,7 @@ export function defineRemoteResource<
322
345
  }
323
346
 
324
347
  for await (const event of readNDJSONStream(response.body)) {
325
- onDataReceived(data, event, 'execute', context)
348
+ await onDataReceived(data, event, 'execute', sharedContext)
326
349
  void flushData()
327
350
  }
328
351
 
@@ -332,7 +355,7 @@ export function defineRemoteResource<
332
355
  void flushData()
333
356
  }
334
357
  } else {
335
- onDataReceived(data, await response.json(), 'execute', context)
358
+ await onDataReceived(data, await response.json(), 'execute', sharedContext)
336
359
  void flushData()
337
360
  }
338
361
 
@@ -358,12 +381,12 @@ export function defineRemoteResource<
358
381
  handleWatching,
359
382
  handlePost,
360
383
  resource,
361
- onDataReceived: receivedData => {
362
- onDataReceived(data, receivedData, 'update', context)
384
+ onDataReceived: async receivedData => {
385
+ await onDataReceived(data, receivedData, 'update', sharedContext)
363
386
  void flushData()
364
387
  },
365
- onDataRemoved: receivedData => {
366
- onDataRemoved(data, receivedData, context)
388
+ onDataRemoved: async receivedData => {
389
+ await onDataRemoved(data, receivedData, sharedContext)
367
390
  void flushData()
368
391
  },
369
392
  })
@@ -379,13 +402,13 @@ export function defineRemoteResource<
379
402
  resume = timeoutPoll.resume
380
403
  }
381
404
 
382
- const stateScope = effectScope(true)
383
- const state = stateScope.run(() => buildState(data, context))!
405
+ const state = stateScope.run(() => buildState(data, sharedContext))!
384
406
 
385
407
  cache.set(url, {
386
408
  count: 0,
387
409
  pause,
388
410
  resume,
411
+ isPaused: true,
389
412
  state,
390
413
  stateScope,
391
414
  isReady,
@@ -423,6 +446,12 @@ export function defineRemoteResource<
423
446
  throw new Error('No effect scope found. Please provide a scope or use this function within a Vue component.')
424
447
  }
425
448
 
449
+ if (!scope.active) {
450
+ throw new Error(
451
+ 'useRemoteResource() was called with an inactive effect scope. This usually means a stale scope/context, (e.g. captured from an unmounted component) is being reused'
452
+ )
453
+ }
454
+
426
455
  const isEnabled = toRef(optionsOrParentContext?.isEnabled ?? true)
427
456
 
428
457
  return scope.run(() => {
@@ -2,7 +2,7 @@ import { useNow } from '@vueuse/core'
2
2
  import { defineStore } from 'pinia'
3
3
  import { computed, ref, watch as watchVue } from 'vue'
4
4
 
5
- type EventFn = (object: unknown) => void
5
+ type EventFn = (object: unknown) => Promise<void>
6
6
  export type THandlePost = (sseId: string) => Promise<string>
7
7
  export type THandleDelete = (sseId: string, subscriptionId: string) => Promise<void>
8
8
  export type THandleWatching = (
@@ -13,9 +13,9 @@ export type THandleWatching = (
13
13
  configs: Record<
14
14
  string,
15
15
  {
16
- add: (object: unknown) => void
17
- update: (object: unknown) => void
18
- remove: (object: unknown) => void
16
+ add: EventFn
17
+ update: EventFn
18
+ remove: EventFn
19
19
  }
20
20
  >
21
21
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xen-orchestra/web-core",
3
3
  "type": "module",
4
- "version": "0.57.0",
4
+ "version": "0.58.0",
5
5
  "private": false,
6
6
  "exports": {
7
7
  "./*": {