@xen-orchestra/web-core 0.55.0 → 0.56.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.
Files changed (52) hide show
  1. package/lib/components/card-object-title/VtsCardObjectTitle.vue +36 -0
  2. package/lib/components/code-snippet/VtsCodeSnippet.vue +11 -5
  3. package/lib/components/drawer/VtsDrawer.vue +5 -1
  4. package/lib/components/drawer/VtsDrawerButton.vue +4 -3
  5. package/lib/components/drawer/VtsDrawerConfirmButton.vue +4 -2
  6. package/lib/components/icon/VtsIcon.vue +3 -1
  7. package/lib/components/layout/VtsContentSidePanel.vue +41 -0
  8. package/lib/components/menu/VtsActionsMenu.vue +37 -3
  9. package/lib/components/operation-card/VtsOperationCard.vue +2 -1
  10. package/lib/components/panel/VtsPanel.vue +80 -0
  11. package/lib/components/panel/VtsSidePanel.vue +140 -0
  12. package/lib/components/select/VtsSelect.vue +1 -0
  13. package/lib/components/state-hero/VtsStateHero.vue +9 -17
  14. package/lib/components/table/VtsTable.vue +2 -1
  15. package/lib/components/table/cells/VtsActionCell.vue +7 -0
  16. package/lib/components/table/cells/VtsLinkCell.vue +3 -1
  17. package/lib/components/tree/VtsTreeItem.vue +9 -3
  18. package/lib/components/tree/VtsTreeList.vue +6 -3
  19. package/lib/components/tree/VtsTreeLoadingItem.vue +3 -3
  20. package/lib/components/ui/head-bar/UiHeadBar.vue +1 -1
  21. package/lib/components/ui/input/UiInput.vue +3 -1
  22. package/lib/components/ui/link/UiLink.vue +3 -1
  23. package/lib/components/ui/panel/UiPanel.vue +7 -24
  24. package/lib/components/ui/tree-item-label/UiTreeItemLabel.vue +1 -1
  25. package/lib/composables/table-state.composable.ts +1 -1
  26. package/lib/icons/action-icons.ts +14 -0
  27. package/lib/locales/cs.json +56 -1
  28. package/lib/locales/de.json +3 -2
  29. package/lib/locales/en.json +22 -2
  30. package/lib/locales/es.json +11 -1
  31. package/lib/locales/fa.json +0 -1
  32. package/lib/locales/fr.json +22 -2
  33. package/lib/locales/it.json +0 -1
  34. package/lib/locales/ko.json +2 -0
  35. package/lib/locales/nl.json +2 -1
  36. package/lib/locales/pt-BR.json +0 -1
  37. package/lib/locales/pt.json +0 -1
  38. package/lib/locales/ru.json +0 -1
  39. package/lib/locales/sk.json +56 -1
  40. package/lib/locales/sv.json +3 -2
  41. package/lib/locales/uk.json +0 -1
  42. package/lib/locales/zh-Hans.json +79 -4
  43. package/lib/packages/remote-resource/define-remote-resource.ts +64 -29
  44. package/lib/packages/tree/types.ts +6 -0
  45. package/lib/packages/tree/use-tree.ts +38 -37
  46. package/lib/stores/panel.store.ts +56 -5
  47. package/lib/tables/column-definitions/action-column.ts +21 -9
  48. package/lib/tables/column-sets/vm-columns.ts +2 -3
  49. package/lib/types/state-hero.type.ts +19 -0
  50. package/lib/types/vue-virtual-scroller.d.ts +8 -2
  51. package/lib/utils/injection-keys.util.ts +1 -1
  52. package/package.json +1 -1
@@ -15,11 +15,14 @@
15
15
  "action:add-network-in-xo-5": "在XO5中添加网络",
16
16
  "action:add-sort": "添加排序条件",
17
17
  "action:add-vifs-in-xo-5": "在XO5中添加虚拟网卡",
18
+ "action:attach": "挂载",
19
+ "action:attach-vdi": "@:attach-vdi",
18
20
  "action:boot-vm": "启动虚拟机",
19
21
  "action:change-state": "修改状态",
20
22
  "action:close": "关闭",
21
23
  "action:connect": "连接",
22
24
  "action:connect-another-pool": "连接其他资源池",
25
+ "action:connect-n-vbds": "连接1个VBD | 连接{n}个VBD",
23
26
  "action:connect-n-vifs": "连接1个虚拟网卡 | 连接{n}个虚拟网卡",
24
27
  "action:connect-pool": "连接资源池",
25
28
  "action:copy": "复制",
@@ -31,6 +34,8 @@
31
34
  "action:create-bonded-network": "创建聚合网络",
32
35
  "action:create-internal-network": "创建内网",
33
36
  "action:create-network": "创建网络",
37
+ "action:create-traffic-rule": "创建流量规则",
38
+ "action:create-vdi": "创建VDI",
34
39
  "action:delete": "删除",
35
40
  "action:delete-filter": "删除筛选条件",
36
41
  "action:delete-group": "删除分组",
@@ -42,6 +47,7 @@
42
47
  "action:deploy": "部署",
43
48
  "action:deploy-xoa": "部署XOA",
44
49
  "action:disconnect": "断开连接",
50
+ "action:disconnect-n-vbds": "断开1个VBD | 断开{n}个VBD",
45
51
  "action:disconnect-n-vifs": "断开1个虚拟网卡 | 断开{n}个虚拟网卡",
46
52
  "action:download-bugtools-archive": "下载故障排查工具包",
47
53
  "action:duplicate": "克隆",
@@ -78,6 +84,8 @@
78
84
  "action:reboot": "重启",
79
85
  "action:reformat": "重新格式化",
80
86
  "action:resume": "恢复运行",
87
+ "action:revert-to-snapshot": "回滚至快照",
88
+ "action:revert-vm-here": "在此主机还原虚拟机快照",
81
89
  "action:save": "保存",
82
90
  "action:search": "搜索",
83
91
  "action:search-treeview": "树形列表搜索",
@@ -130,6 +138,7 @@
130
138
  "all-done!": "全部完成!",
131
139
  "all-good!": "一切正常!",
132
140
  "all-quiet-launchpad": "系统运行平稳",
141
+ "allocated-space": "已分配容量",
133
142
  "allow": "允许",
134
143
  "allow-self-signed-ssl": "请在浏览器中允许自签名SSL证书",
135
144
  "and": "并且",
@@ -140,6 +149,9 @@
140
149
  "appearance": "外观设置",
141
150
  "aria:breadcrumb:label": "面包屑导航",
142
151
  "ascending": "升序",
152
+ "attach-vdi": "挂载已有VDI",
153
+ "attach-vdi:error-message": "挂载VDI时发生错误。请检查存储库、VDI、读写模式配置,确认虚拟机状态后重试。",
154
+ "attaching-vdi": "正在挂载VDI",
143
155
  "auto-generated": "自动生成",
144
156
  "auto-power": "自动启停",
145
157
  "available": "可用",
@@ -183,7 +195,6 @@
183
195
  "backups:vms-protection:active-protected": "已加入有效任务并受保护",
184
196
  "backups:vms-protection:active-unprotected": "已加入有效任务但未受保护",
185
197
  "backups:vms-protection:no-active-job": "未加入任何有效任务",
186
- "backups:vms-protection:no-job": "未加入任何备份任务",
187
198
  "backups:vms-protection:protected": "已加入任务并受保护",
188
199
  "backups:vms-protection:tooltip": "虚拟机加入启用定时的备份任务且最近执行成功即为受保护",
189
200
  "backups:vms-protection:unprotected": "已加入任务但未受保护",
@@ -194,6 +205,7 @@
194
205
  "bond-mode": "聚合模式",
195
206
  "bond-status": "聚合状态",
196
207
  "boot-firmware": "启动固件",
208
+ "bootable": "可引导启动",
197
209
  "build-number": "编译版本号",
198
210
  "by": "依据",
199
211
  "bytes:gi": "吉字节",
@@ -202,10 +214,12 @@
202
214
  "cancel": "取消",
203
215
  "cbt-destroy-snapshot-data": "使用变更块追踪时清理快照数据",
204
216
  "change-block-tracking": "变更块追踪(CBT)",
205
- "character-limit": "已输入 {count}/{max} 个字符",
217
+ "character-limit": "{count}/{max}个字符 | {count}/{max}个字符",
206
218
  "check-errors:": "查看错误信息:",
207
219
  "check-summing": "发送端校验和",
208
220
  "checkpoint-snapshot": "检查点快照",
221
+ "choose-network": "选择网络",
222
+ "choose-vif": "选择VIF",
209
223
  "click-to-display-alarms:": "点击查看告警:",
210
224
  "click-to-return-default-pool": "点击返回默认资源池",
211
225
  "cloud-config": "云端配置",
@@ -221,6 +235,7 @@
221
235
  "configure-in-xo-5": "前往XO5配置",
222
236
  "confirm-cancel?": "确定取消当前操作吗?",
223
237
  "confirm-delete": "即将删除{0}",
238
+ "confirm-vm-revert": "确认还原虚拟机快照",
224
239
  "connect": "连接",
225
240
  "connected": "已连接",
226
241
  "connected-to-ip": "已连接至 {ip}",
@@ -250,9 +265,12 @@
250
265
  "cpu-weight": "CPU权重",
251
266
  "cpus": "处理器数量",
252
267
  "crash-dump-storage-repository": "故障转储存储仓库",
268
+ "create-new-vdi": "新建VDI",
253
269
  "created-by": "创建人",
254
270
  "created-on": "创建时间",
255
271
  "creating-new-network": "正在创建新网络",
272
+ "creating-new-traffic-rule": "正在创建流量规则",
273
+ "creating-new-vdi": "正在创建VDI",
256
274
  "creation-date": "创建日期",
257
275
  "cron-pattern": "定时表达式",
258
276
  "current": "当前",
@@ -281,6 +299,13 @@
281
299
  "device-config": "设备配置",
282
300
  "dhcp": "自动获取IP",
283
301
  "direction": "传输方向",
302
+ "direction-and": "并且",
303
+ "direction-between": "双向",
304
+ "direction-from": "源地址",
305
+ "direction-from-and-to": "双向收发",
306
+ "direction-from-to": "源至目标单向",
307
+ "direction-on": "绑定端口",
308
+ "direction-to": "目标地址",
284
309
  "disabled": "已禁用",
285
310
  "disconnect-vifs-in-xo-5-to-delete-network": "删除网络前需断开所有关联虚拟网卡,可在{xo-5}中管理。",
286
311
  "disconnected": "已断开",
@@ -296,6 +321,7 @@
296
321
  "done": "完成",
297
322
  "drop": "丢弃",
298
323
  "duration": "时长",
324
+ "empty": "空",
299
325
  "enabled": "已启用",
300
326
  "end-date": "结束日期",
301
327
  "end-of-life": "生命周期终止",
@@ -316,16 +342,23 @@
316
342
  "fast-clone": "快速克隆",
317
343
  "fetching-fresh-data": "正在获取实时数据",
318
344
  "field:exceeds-max-characters": "输入内容不可超过{max}个字符。",
345
+ "file": "文件",
319
346
  "filter-actions": "筛选操作",
320
347
  "following-hosts-unreachable": "以下主机无法连通",
321
348
  "for-backup": "用于备份",
322
349
  "for-replication": "用于数据复制",
323
350
  "force-reboot-blocked": "禁止强制重启",
324
351
  "force-shutdown-blocked": "禁止强制关机",
352
+ "form:error:field-required": "{field}为必填项",
353
+ "form:error:integer": "必须填写整数",
354
+ "form:error:required": "此字段不能为空",
355
+ "form:warning:out-of-range": "数值范围应在{min}至{max}之间",
325
356
  "format": "格式化",
326
357
  "free-space": "剩余空间",
358
+ "from-vm": "来源虚拟机",
327
359
  "fullscreen": "全屏模式",
328
360
  "gateway": "网关",
361
+ "general": "常规",
329
362
  "general-information": "基本信息",
330
363
  "gpu": "显卡",
331
364
  "gpus": "显卡列表",
@@ -357,6 +390,7 @@
357
390
  "in-progress": "进行中",
358
391
  "included": "已包含",
359
392
  "info": "信息",
393
+ "info:vdi-attached-rw": "该VDI已以读写模式挂载至其他虚拟机。",
360
394
  "infos": "详情信息",
361
395
  "install-settings": "安装配置",
362
396
  "installed": "已安装",
@@ -365,6 +399,9 @@
365
399
  "interrupted": "任务中断",
366
400
  "invalid-field": "输入内容无效",
367
401
  "ip-address": "IP地址",
402
+ "ip-address-info": "规则支持单IP或CIDR子网格式。示例:192.168.1.10、10.20.0.0/16",
403
+ "ip-address-invalid": "IP地址格式错误。",
404
+ "ip-address-required": "IP地址为必填项。",
368
405
  "ip-addresses": "多组IP地址",
369
406
  "ip-mode": "IP获取方式",
370
407
  "ip-port-placeholder": "地址[:端口]",
@@ -378,18 +415,28 @@
378
415
  "iso-dvd": "ISO镜像/光驱",
379
416
  "job": "后台任务",
380
417
  "job-name": "任务名称",
418
+ "job:arg:allocated-space-required": "必须填写分配容量",
381
419
  "job:arg:bond-mode-required": "请选择聚合模式",
420
+ "job:arg:direction-required": "流量方向为必填项",
421
+ "job:arg:file-required": "文件路径为必填项",
382
422
  "job:arg:host-required": "请选择目标主机",
423
+ "job:arg:ip-range-required": "IP地址段为必填项",
383
424
  "job:arg:missing-payload": "缺少任务配置参数",
384
425
  "job:arg:name-required": "请填写名称",
385
426
  "job:arg:password-required": "请输入密码",
386
427
  "job:arg:pif-id-required": "请选择物理网卡",
387
428
  "job:arg:pif-ids-required": "至少选择一张物理网卡",
388
429
  "job:arg:pool-id-required": "请选择资源池",
430
+ "job:arg:port-required-for-tcp-udp": "TCP/UDP协议必须填写端口",
431
+ "job:arg:protocol-required": "通信协议为必填项",
432
+ "job:arg:sr-id-required": "存储库SR ID为必填项",
389
433
  "job:arg:sr-vdi-required": "请选择存储仓库与虚拟磁盘",
434
+ "job:arg:target-id-required": "目标对象ID为必填项",
390
435
  "job:arg:template-uuid-required": "请选择虚拟机模板",
391
436
  "job:arg:username-required": "请输入用户名",
437
+ "job:arg:vdi-id-required": "VDI ID为必填项",
392
438
  "job:arg:vlan-required": "请填写VLAN编号",
439
+ "job:arg:vm-id-required": "虚拟机ID为必填项",
393
440
  "job:connect:in-progress": "正在连接.…",
394
441
  "job:create:in-progress": "正在创建.…",
395
442
  "job:delete:in-progress": "正在删除.…",
@@ -450,6 +497,8 @@
450
497
  "job:vm-shutdown:blocked-operation": "禁止关闭虚拟机",
451
498
  "job:vm-shutdown:in-progress": "正在关闭虚拟机.…",
452
499
  "job:vm-shutdown:missing-vm": "暂无虚拟机可关机",
500
+ "job:vm-snapshot-revert:in-progress": "快照回滚执行中…",
501
+ "job:vm-snapshot-revert:missing-snapshot": "无可用快照用于回滚",
453
502
  "job:vm-snapshot:in-progress": "正在创建快照.…",
454
503
  "job:vm-snapshot:missing-vm": "暂无虚拟机可创建快照",
455
504
  "job:vm-start-on:bad-power-state": "虚拟机必须处于关机状态",
@@ -538,7 +587,7 @@
538
587
  "n-gb-left": "剩余{n}GB空间",
539
588
  "n-gb-required": "需要{n}GB存储空间",
540
589
  "n-hosts": "1台主机 | {n}台主机",
541
- "n-hosts-awaiting-patch": "{n}台主机等待安装该补丁",
590
+ "n-hosts-awaiting-patch": "{n}台主机等待安装此补丁 | {n}台主机等待安装此补丁",
542
591
  "n-internal-networks": "1个内网 | {n}个内网",
543
592
  "n-missing": "缺失{n}项",
544
593
  "n-networks": "1个网络 | {n}个网络",
@@ -559,8 +608,10 @@
559
608
  "network-config": "网络配置",
560
609
  "network-create:warning:mtu-out-of-range": "MTU取值范围:{min}-{max}",
561
610
  "network-create:warning:vlan-out-of-range": "VLAN取值范围:0-{max}",
611
+ "network-default": "默认网络",
562
612
  "network-download": "下载速率",
563
613
  "network-information": "网络详情",
614
+ "network-required": "必须选择网络。",
564
615
  "network-throughput": "网络吞吐量",
565
616
  "network-upload": "上传速率",
566
617
  "networking": "网络管理",
@@ -573,6 +624,11 @@
573
624
  "new-network:creation-in-progress-message": "网络创建任务已后台运行,可前往{tasksPageLink}查看。",
574
625
  "new-network:error-message": "网络创建失败,请核对配置并确认主机状态后重试。",
575
626
  "new-network:select-interface": "选择物理网卡",
627
+ "new-traffic-rule:add": "新建流量防火墙规则",
628
+ "new-traffic-rule:error-message": "创建流量规则失败,请核对配置参数后重试。",
629
+ "new-vdi:accepted-formats": "支持磁盘格式:{formats}",
630
+ "new-vdi:add": "新建虚拟磁盘VDI",
631
+ "new-vdi:error-message": "创建VDI失败,请核对磁盘名称、存储库、分配容量后重试。",
576
632
  "new-vif": "新增虚拟网卡",
577
633
  "new-vm": "新建虚拟机",
578
634
  "new-vm:add": "创建虚拟机",
@@ -660,7 +716,7 @@
660
716
  "other-settings": "其他配置",
661
717
  "page-not-found": "页面不存在.…",
662
718
  "page-please-wait": "页面正在初始化,请稍候!",
663
- "pagination:all": "全部",
719
+ "pagination:all": "@:all",
664
720
  "partially-connected": "部分已连接",
665
721
  "password": "密码",
666
722
  "password-invalid": "密码格式错误",
@@ -703,6 +759,8 @@
703
759
  "popup-first-connection-introduction": "欢迎使用Xen Orchestra 6正式版。",
704
760
  "popup-first-connection-upcoming-release": "后续版本将补齐用户管理、备份、批量操作等全部功能!",
705
761
  "port": "端口",
762
+ "port-invalid": "端口格式填写错误。",
763
+ "port-required": "端口为必填项。",
706
764
  "power-on-host-for-console": "开启主机后即可访问控制台",
707
765
  "power-on-mode": "开机模式",
708
766
  "power-on-vm-for-console": "开启虚拟机后即可访问控制台",
@@ -724,6 +782,7 @@
724
782
  "public-key": "公钥",
725
783
  "public-key-already-exists": "公钥已存在",
726
784
  "public-key-mandatory": "必须填写公钥",
785
+ "pv-vms-only": "仅半虚拟化(PV)虚拟机可用。",
727
786
  "pxe": "网络引导启动",
728
787
  "query-builder": "查询编辑器",
729
788
  "query-builder:label": "智能搜索器",
@@ -754,6 +813,7 @@
754
813
  "ram": "运行内存",
755
814
  "ram-provisioning": "内存资源分配",
756
815
  "ram-usage": "内存使用率",
816
+ "rate-limit": "速率限制",
757
817
  "read": "读取",
758
818
  "read-only": "只读模式",
759
819
  "reboot-blocked": "禁止重启",
@@ -782,6 +842,7 @@
782
842
  "rest-api": "后端接口",
783
843
  "retry": "重试",
784
844
  "root-by-default": "默认账号root。",
845
+ "rule-type": "规则类型",
785
846
  "run": "执行",
786
847
  "running-vm": "运行中虚拟机",
787
848
  "runs": "执行记录",
@@ -813,10 +874,13 @@
813
874
  "snapshot-created-on": "快照创建时间",
814
875
  "snapshot-delete-warning": "删除 1 个快照将造成不可逆数据丢失,该操作永久生效且无法撤销。| 批量删除 {n} 个快照将造成不可逆数据丢失,该操作永久生效且无法撤销。",
815
876
  "snapshot-mode": "快照存储模式",
877
+ "snapshot-revert-snapshot-before": "回滚前先对虚拟机创建快照",
878
+ "snapshot-revert-warning": "此操作会将虚拟机恢复至所选快照状态,快照之后所有的数据、配置与磁盘变更都会丢失,且该操作无法撤销。",
816
879
  "snapshots": "快照管理",
817
880
  "sockets-with-cores-per-socket": "{nSockets}插槽 × {nCores}核心/插槽",
818
881
  "software": "软件服务",
819
882
  "software-tooling": "软件与运维工具",
883
+ "source": "源",
820
884
  "source-backup-repository": "源端备份仓库",
821
885
  "space": "存储空间",
822
886
  "speed": "传输速率",
@@ -922,12 +986,15 @@
922
986
  "translation-tool": "词条翻译工具",
923
987
  "trigger": "触发条件",
924
988
  "type": "资源类型",
989
+ "unable-to-attach-vdi": "无法挂载虚拟磁盘(VDI)",
925
990
  "unable-to-connect-to": "无法连接至 {ip}",
926
991
  "unable-to-connect-to-the-pool": "资源池连接失败",
927
992
  "unable-to-connect-to-xo-server": "无法连接XO服务端。",
928
993
  "unable-to-create-new-bonded-network": "聚合网络创建失败",
929
994
  "unable-to-create-new-internal-network": "内网创建失败",
930
995
  "unable-to-create-new-network": "网络创建失败",
996
+ "unable-to-create-new-traffic-rule": "无法新建流量规则",
997
+ "unable-to-create-new-vdi": "无法新建虚拟磁盘(VDI)",
931
998
  "unable-to-delete-network": "网络删除失败",
932
999
  "unknown": "未知",
933
1000
  "unlocked": "已解锁",
@@ -937,6 +1004,7 @@
937
1004
  "untitled": "无标题",
938
1005
  "untranslated-text-helper": "未完成翻译的内容默认显示英文。",
939
1006
  "up-to-date": "已是最新版本",
1007
+ "url": "地址链接 (URL)",
940
1008
  "used": "已使用",
941
1009
  "used-for-backup": "用于备份存储",
942
1010
  "used-space": "已用空间",
@@ -949,6 +1017,9 @@
949
1017
  "vcpus-assigned": "已分配虚拟CPU数量",
950
1018
  "vdi": "虚拟磁盘",
951
1019
  "vdi-delete-warning": "此操作将永久删除磁盘及全部数据,无法恢复。",
1020
+ "vdi-description": "虚拟磁盘描述(VDI描述)",
1021
+ "vdi-in-use": "虚拟磁盘正在占用(VDI已被使用)",
1022
+ "vdi-name": "虚拟磁盘名称(VDI名称)",
952
1023
  "vdi-throughput": "虚拟磁盘读写吞吐量",
953
1024
  "vdis": "虚拟磁盘列表",
954
1025
  "version": "版本",
@@ -961,6 +1032,7 @@
961
1032
  "vif-disconnect-info": "虚拟机断开网络访问,网卡配置保留,可重新连接。",
962
1033
  "vif-disconnect-title": "即将断开1个虚拟网卡 | 即将断开{n}个虚拟网卡",
963
1034
  "vif-network-info": "虚拟网卡网络信息",
1035
+ "vif-required": "必须配置虚拟网卡(VIF)。",
964
1036
  "vif-status": "虚拟网卡状态",
965
1037
  "vifs": "虚拟网卡列表",
966
1038
  "viridian": "Windows虚拟化兼容模式",
@@ -985,6 +1057,7 @@
985
1057
  "vm-protected-reboot": "该虚拟机禁止重启。",
986
1058
  "vm-protected-shutdown": "该虚拟机禁止关机。",
987
1059
  "vm-protected-suspend": "该虚拟机禁止休眠。",
1060
+ "vm-required": "虚拟机为必填项。",
988
1061
  "vm-running": "虚拟机运行中",
989
1062
  "vm-shutdown": "虚拟机已关机",
990
1063
  "vm-tools-missing": "未安装虚拟机配套工具",
@@ -1001,6 +1074,8 @@
1001
1074
  "vms-treeview": "虚拟机树形列表",
1002
1075
  "vtpm": "可信虚拟芯片",
1003
1076
  "warning": "警告",
1077
+ "warning:vdi-already-attached": "所选虚拟磁盘已挂载至该虚拟机。",
1078
+ "warning:vdi-sr": "此存储库(SR)与其他虚拟磁盘所属存储库不兼容,该存储库需为共享存储,或与其他虚拟磁盘存储库部署在同一主机。",
1004
1079
  "warnings": "警告提示",
1005
1080
  "weblate": "在线翻译平台",
1006
1081
  "welcome-to-xo6!": "欢迎使用XO6新版管理平台!",
@@ -14,19 +14,19 @@ import {
14
14
  computed,
15
15
  type ComputedRef,
16
16
  type EffectScope,
17
+ effectScope,
17
18
  getCurrentScope,
19
+ type MaybeRef,
18
20
  type MaybeRefOrGetter,
19
21
  onScopeDispose,
20
22
  reactive,
21
23
  type Ref,
22
24
  ref,
23
25
  shallowRef,
24
- triggerRef,
25
26
  toRef,
26
27
  toValue,
28
+ triggerRef,
27
29
  watch,
28
- effectScope,
29
- type MaybeRef,
30
30
  } from 'vue'
31
31
 
32
32
  const DEFAULT_CACHE_EXPIRATION_MS = 10_000
@@ -41,7 +41,12 @@ export function defineRemoteResource<
41
41
  url: string | ((...args: TArgs) => string)
42
42
  initialData: () => TData
43
43
  state?: (data: Ref<NoInfer<TData>>, context: ResourceContext<TArgs>) => TState
44
- onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
44
+ onDataReceived?: (
45
+ data: Ref<NoInfer<TData>>,
46
+ receivedData: any,
47
+ calledFrom: 'execute' | 'update',
48
+ context?: ResourceContext<TArgs>
49
+ ) => void
45
50
  cacheExpirationMs?: number | false
46
51
  pollingIntervalMs?: number | false
47
52
  stream?: boolean
@@ -50,7 +55,12 @@ export function defineRemoteResource<
50
55
  export function defineRemoteResource<TData, TState extends object, TArgs extends any[] = []>(config: {
51
56
  url: string | ((...args: TArgs) => string)
52
57
  state?: (data: Ref<TData | undefined>, context: ResourceContext<TArgs>) => TState
53
- onDataReceived?: (data: Ref<TData | undefined>, receivedData: any) => void
58
+ onDataReceived?: (
59
+ data: Ref<TData | undefined>,
60
+ receivedData: any,
61
+ calledFrom: 'execute' | 'update',
62
+ context?: ResourceContext<TArgs>
63
+ ) => void
54
64
  cacheExpirationMs?: number | false
55
65
  pollingIntervalMs?: number | false
56
66
  stream?: boolean
@@ -64,7 +74,12 @@ export function defineRemoteResource<
64
74
  url: string | ((...args: TArgs) => string)
65
75
  initialData: () => TData
66
76
  state?: (data: Ref<NoInfer<TData>>, context: ResourceContext<TArgs>) => TState
67
- onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
77
+ onDataReceived?: (
78
+ data: Ref<NoInfer<TData>>,
79
+ receivedData: any,
80
+ calledFrom: 'execute' | 'update',
81
+ context?: ResourceContext<TArgs>
82
+ ) => void
68
83
  onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
69
84
  stream?: boolean
70
85
  initWatchCollection: () => {
@@ -86,7 +101,12 @@ export function defineRemoteResource<
86
101
  url: string | ((...args: TArgs) => string)
87
102
  initialData?: () => TData
88
103
  state?: (data: Ref<TData>, context: ResourceContext<TArgs>) => TState
89
- onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
104
+ onDataReceived?: (
105
+ data: Ref<NoInfer<TData>>,
106
+ receivedData: any,
107
+ calledFrom: 'execute' | 'update',
108
+ context?: ResourceContext<TArgs>
109
+ ) => void
90
110
  onDataRemoved?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
91
111
  cacheExpirationMs?: number | false
92
112
  pollingIntervalMs?: number | false
@@ -161,33 +181,38 @@ export function defineRemoteResource<
161
181
 
162
182
  const onDataReceived =
163
183
  config.onDataReceived ??
164
- ((data: Ref<TData>, receivedData: any, context?: ResourceContext<TArgs>) => {
184
+ ((data: Ref<TData>, receivedData: any, calledFrom: 'execute' | 'update', context?: ResourceContext<TArgs>) => {
165
185
  // allow to ignore some update (like for sub collection. E.g. vms/:id/vdis)
166
186
  if (watchCollection?.predicate?.(receivedData, context) === false) {
167
187
  return
168
188
  }
169
189
 
170
- if (data.value === undefined || (Array.isArray(data.value) && Array.isArray(receivedData))) {
171
- data.value = receivedData
172
-
173
- if (watchCollection !== undefined && Array.isArray(data.value)) {
174
- handleBuffer(data as Ref<TData[]>)
175
- isBufferEventsProcessed = true
190
+ if (!Array.isArray(data.value)) {
191
+ if (data.value === undefined) {
192
+ data.value = receivedData
193
+ } else {
194
+ merge(data.value, receivedData)
176
195
  }
196
+
177
197
  return
178
198
  }
179
199
 
180
- if (Array.isArray(data.value)) {
181
- if (!isBufferEventsProcessed) {
182
- bufferedEvents.push(['update', receivedData])
183
- } else {
184
- removeData(data.value, receivedData)
185
- data.value.push(receivedData)
186
- }
200
+ const store = data.value as TData[]
201
+
202
+ if (Array.isArray(receivedData)) {
203
+ data.value = receivedData as TData
204
+
187
205
  return
188
206
  }
189
207
 
190
- merge(data.value, receivedData)
208
+ if (calledFrom === 'execute') {
209
+ store.push(receivedData)
210
+ } else if (!isBufferEventsProcessed) {
211
+ bufferedEvents.push(['update', receivedData])
212
+ } else {
213
+ removeData(store, receivedData)
214
+ store.push(receivedData)
215
+ }
191
216
  })
192
217
 
193
218
  const onDataRemoved =
@@ -292,13 +317,23 @@ export function defineRemoteResource<
292
317
  }
293
318
 
294
319
  if (config.stream) {
320
+ if (Array.isArray(data.value)) {
321
+ data.value = [] as TData
322
+ }
323
+
295
324
  for await (const event of readNDJSONStream(response.body)) {
296
- onDataReceived(data, event)
297
- flushData()
325
+ onDataReceived(data, event, 'execute', context)
326
+ void flushData()
327
+ }
328
+
329
+ if (watchCollection !== undefined && Array.isArray(data.value)) {
330
+ handleBuffer(data as Ref<TData[]>)
331
+ isBufferEventsProcessed = true
332
+ void flushData()
298
333
  }
299
334
  } else {
300
- onDataReceived(data, await response.json())
301
- flushData()
335
+ onDataReceived(data, await response.json(), 'execute', context)
336
+ void flushData()
302
337
  }
303
338
 
304
339
  isReady.value = true
@@ -324,12 +359,12 @@ export function defineRemoteResource<
324
359
  handlePost,
325
360
  resource,
326
361
  onDataReceived: receivedData => {
327
- onDataReceived(data, receivedData, context)
328
- flushData()
362
+ onDataReceived(data, receivedData, 'update', context)
363
+ void flushData()
329
364
  },
330
365
  onDataRemoved: receivedData => {
331
366
  onDataRemoved(data, receivedData, context)
332
- flushData()
367
+ void flushData()
333
368
  },
334
369
  })
335
370
  await execute()
@@ -24,6 +24,12 @@ export type TreeNode<
24
24
  TDiscriminator = any,
25
25
  > = Leaf<TData, TDiscriminator> | Branch<TData, TChildNode, TDiscriminator>
26
26
 
27
+ export type FlatTreeNode<TNode extends TreeNode = TreeNode> = {
28
+ id: TreeNodeId
29
+ node: TNode
30
+ depth: number
31
+ }
32
+
27
33
  export type BaseTreeNodeOptions<TData extends object, TDiscriminator> = {
28
34
  discriminator?: TDiscriminator
29
35
  predicate?: (node: TreeNodeBase<TData, TDiscriminator>) => boolean | undefined
@@ -1,14 +1,14 @@
1
1
  import { buildNodes } from '@core/packages/tree/build-nodes'
2
2
  import type {
3
3
  DefinitionToTreeNode,
4
+ FlatTreeNode,
4
5
  TreeContext,
5
6
  TreeNode,
6
7
  TreeNodeDefinition,
7
8
  TreeNodeId,
8
9
  UseTreeOptions,
9
10
  } from '@core/packages/tree/types'
10
- import { useTimeoutFn } from '@vueuse/core'
11
- import { computed, type MaybeRefOrGetter, nextTick, reactive, ref, toValue } from 'vue'
11
+ import { computed, type MaybeRefOrGetter, reactive, ref, toValue } from 'vue'
12
12
 
13
13
  export function useTree<
14
14
  TDefinition extends TreeNodeDefinition,
@@ -53,6 +53,32 @@ export function useTree<
53
53
 
54
54
  const visibleNodes = computed(() => nodes.value.filter(node => !node.isExcluded))
55
55
 
56
+ const flatNodes = computed<FlatTreeNode[]>(() => {
57
+ const result: FlatTreeNode[] = []
58
+
59
+ const flatten = (list: TreeNode[]) => {
60
+ for (const node of list) {
61
+ result.push({ id: node.id, node, depth: node.depth })
62
+ if (node.isBranch && !node.isCollapsed) {
63
+ flatten(node.children)
64
+ }
65
+ }
66
+ }
67
+
68
+ flatten(visibleNodes.value)
69
+
70
+ return result
71
+ })
72
+
73
+ const flatNodeIndexById = computed(() => {
74
+ const index = new Map<TreeNodeId, number>()
75
+ const list = flatNodes.value
76
+ for (let i = 0; i < list.length; i++) {
77
+ index.set(list[i].id, i)
78
+ }
79
+ return index
80
+ })
81
+
56
82
  const getNode = (id: TreeNodeId | undefined) => (id !== undefined ? nodesMap.value.get(id) : undefined)
57
83
  const getNodes = (ids: TreeNodeId[]) => ids.map(getNode).filter(node => node !== undefined) as TreeNode[]
58
84
 
@@ -80,46 +106,19 @@ export function useTree<
80
106
  return undefined
81
107
  }
82
108
 
83
- const scrollToNodeElement = async (id: string | number, options?: ScrollIntoViewOptions) => {
84
- const node = findNodeByObjectId(nodes.value, id)
109
+ const expandToNode = (objectId: string | number): TreeNode | undefined => {
110
+ const node = findNodeByObjectId(nodes.value, objectId)
85
111
  if (!node) {
86
- if (id) {
87
- useTimeoutFn(async () => {
88
- await nextTick()
89
- await scrollToNodeElement(id, options)
90
- }, 200)
91
- }
92
- return
112
+ return undefined
93
113
  }
94
114
 
95
- getHierarchy(node).forEach(node => {
96
- if (node.isBranch) {
97
- node.toggleCollapse(false)
115
+ getHierarchy(node).forEach(ancestor => {
116
+ if (ancestor.isBranch) {
117
+ ancestor.toggleCollapse(false)
98
118
  }
99
119
  })
100
120
 
101
- const nodeElement = document.querySelector<HTMLElement>(`[data-node-id="${id}"]`)
102
-
103
- if (!nodeElement) {
104
- useTimeoutFn(async () => {
105
- await nextTick()
106
- await scrollToNodeElement(id, options)
107
- }, 200)
108
- return
109
- }
110
-
111
- const hasChildren = node.isBranch && node.hasChildren
112
- const cfg: ScrollIntoViewOptions = options ?? { block: hasChildren ? 'start' : 'center', behavior: 'smooth' }
113
-
114
- if (hasChildren) {
115
- nodeElement.style.scrollMarginTop = '0.8rem'
116
-
117
- useTimeoutFn(async () => {
118
- nodeElement.style.scrollMarginTop = ''
119
- }, 1000)
120
- }
121
-
122
- nodeElement.scrollIntoView(cfg)
121
+ return node
123
122
  }
124
123
 
125
124
  const selectedNodes = computed(() => getNodes(Array.from(selectedIds.value.values())))
@@ -140,6 +139,8 @@ export function useTree<
140
139
 
141
140
  return {
142
141
  nodes: visibleNodes,
142
+ flatNodes,
143
+ flatNodeIndexById,
143
144
  activeId,
144
145
  activeNode,
145
146
  selectedIds,
@@ -148,6 +149,6 @@ export function useTree<
148
149
  collapsedIds,
149
150
  expandedIds,
150
151
  options,
151
- scrollToNodeElement,
152
+ expandToNode,
152
153
  }
153
154
  }