Compare commits

..

324 Commits

Author SHA1 Message Date
Thierry
2243805d01 feat(lite): enhance human-format TypeScript Definition file 2023-03-22 16:58:05 +01:00
Mathieu
507e9a55c2 fix(lite/console): show console when restarting a VM (#6665) 2023-03-20 17:44:09 +01:00
rajaa-b
5bd0eb3362 feat(lite): NoData component (#6525) 2023-03-20 11:17:22 +01:00
Julien Fontanet
458496a09e feat(xo-server/api): use JSON schema defaults for params 2023-03-19 12:15:06 +01:00
Julien Fontanet
f13a98b6b8 chore(xo-cli/README): update usage 2023-03-18 20:59:26 +01:00
Julien Fontanet
dde32724b1 feat(xo-cli): --json flag
Fixes #6736
2023-03-18 20:59:02 +01:00
Julien Fontanet
63b76fdb50 fix(xo-cli): only use color when output is a TTY
Fixes #6736
2023-03-18 15:19:38 +01:00
Julien Fontanet
1b9cd56e9f feat(backups): implement speed limit at job level (#6728)
Related to #4119

The UI side is still missing.
2023-03-17 17:54:40 +01:00
Mathieu
784b0dded8 feat(lite): under construction page (#6673) 2023-03-17 16:35:06 +01:00
Julien Fontanet
4a658787de fix(xo-server): better normalization of VM networks
The normalization code was rewritten from scratch and now comes with tests.

- addresses for a specific device of a same protocol are deduplicated
- support any keys (instead of single digits)
2023-03-17 16:16:52 +01:00
Thierry Goettelmann
4beb49041d feat(lite/components): rework of CollectionFilterRow to be i18n-able (#6619) 2023-03-17 09:53:05 +01:00
Julien Fontanet
1f6e29084f feat: release 5.80.2 2023-03-16 17:44:24 +01:00
Julien Fontanet
7c6cb2454b feat(xo-web): 5.113.0 2023-03-16 17:31:46 +01:00
Julien Fontanet
96720d186c feat(xo-server-auth-oidc): 0.2.0 2023-03-16 17:31:27 +01:00
Julien Fontanet
a45fb88c48 fix(backups/cleanVm): hide misleading incorrect size warning
These warnings are usually false alarms and completely begnin, therefore they
are hidden until the root cause is found and fixed.
2023-03-16 17:12:30 +01:00
Thierry Goettelmann
b4b0a925af feat(lite): Component Stories implementation (#6614) 2023-03-16 16:04:36 +01:00
Mathieu
72822c9529 fix(xo-web/license): fix undefined expiration date (#6730)
Fix zammad#13319

The Pro support icon displayed in "Home/pool" considered that the licenses without expiration date had expired
2023-03-16 14:41:35 +01:00
Julien Fontanet
ca6cdbf9cf fix(xo-server/disk.exportContent): better HTTP properties handling 2023-03-16 14:31:46 +01:00
Julien Fontanet
74cd35f527 feat(xapi/VDI_exportContent): detect incompatible raw and baseRef 2023-03-16 14:29:26 +01:00
Julien Fontanet
010866a0ef fix(xo-server/vm.import): descriptionLabel can be empty
Fixes https://xcp-ng.org/forum/post/59968

Introduced by 65168c853
2023-03-16 14:10:38 +01:00
Julien Fontanet
5885df4ae9 fix(scripts/gen-deps-list.js): non-zero status code on invalid arg
Also, display the usage on stderr instead of stdout.
2023-03-16 11:21:53 +01:00
Julien Fontanet
89bc6da5f4 chore(xo-server/README): uniformize code blocks
Follow-up of 2a70ebf66
2023-03-16 11:19:54 +01:00
Florent BEAUCHAMP
d87f698512 fix(vmware-explorer): fix dcPath parameter (#6729) 2023-03-15 14:42:34 +01:00
rajaa-b
08dd871cb8 feat(xo-lite): add star icon near pool master (#6712) 2023-03-15 14:20:12 +01:00
Julien Fontanet
b5578eadf7 fix(xo-server/sr.{create,set}): name can be empty
Fixes https://xcp-ng.org/forum/post/59937

Introduced by 65168c853
2023-03-15 13:57:24 +01:00
Mathieu
aadc1bb84c feat(xo-web/home): icon grouping (#6655) 2023-03-15 10:59:06 +01:00
rajaa-b
2823af9441 feat(xo-web/render-xo-item/PIF): add VLAN number (#6714)
Fixes #4697
2023-03-15 10:45:08 +01:00
Julien Fontanet
d7da83359f fix(xo-web): don't send empty MTU while creating network (#6720)
Fixes #6717
2023-03-14 12:14:16 +01:00
Julien Fontanet
a143cd3427 fix(proxy): fix param for log/dedupe
Introduced by 05197b93e
2023-03-14 11:51:19 +01:00
Julien Fontanet
3c4dcde1d4 feat(xo-server-auth-oidc): make well-known suffix optional 2023-03-14 10:02:26 +01:00
Mathieu
7adfc195dc fix(lite): fix UiIcon import (#6726) 2023-03-13 16:17:48 +01:00
Julien Fontanet
5a2c315b20 feat(xo-server-auth-oidc): support email for username field (#6722)
Fixes https://xcp-ng.org/forum/post/59587
2023-03-13 15:26:03 +01:00
Julien Fontanet
299803f03c chore(xo-server-auth-oidc): add description and keywords 2023-03-13 14:16:38 +01:00
Julien Fontanet
1eac62a26e feat(xo-server): make plugins searchable by keywords 2023-03-13 14:13:30 +01:00
Julien Fontanet
f1b5416d0b chore(xo-server-auth-github): remove duplicate package keyword 2023-03-13 14:07:06 +01:00
Julien Fontanet
65168c8532 fix(xo-server-auth-iodc): fix empty usernames with default config
See https://xcp-ng.org/forum/post/59587
2023-03-13 12:28:37 +01:00
Julien Fontanet
35f6476d0f feat(xo-server/rest-api): expose backup logs (#6711) 2023-03-13 09:10:15 +01:00
Julien Fontanet
36fabe194f fix(xo-server/registerUser2): don't create user with invalid (empty) name
Related to https://xcp-ng.org/forum/post/59587
2023-03-13 00:42:05 +01:00
Julien Fontanet
921c700fab fix(xo-server/api): description params can be empty
Fixes #6721

Introduced by d6a3492e9
2023-03-13 00:13:41 +01:00
Gabriel Gunullu
2dbe35a31c fix(xo-web/cloud-config): update dead links (#6719) 2023-03-10 11:32:38 +01:00
Julien Fontanet
656d13d79b chore: update dev deps 2023-03-10 10:42:36 +01:00
Florent BEAUCHAMP
77b1adae37 feat(backups/writeVhd): check file has expected size (#6703) 2023-03-10 10:06:53 +01:00
Julien Fontanet
c18373bb0e feat(xo-server/rest-api): fields can be set to * to see all properties 2023-03-09 17:20:35 +01:00
Julien Fontanet
d4e7563272 feat(xo-server/getBackupNgLogsSorted): filter can be a function 2023-03-09 17:19:41 +01:00
Julien Fontanet
86d6052c89 chore(xo-server/getBackupNgLogsSorted): use Object.values() 2023-03-09 17:19:10 +01:00
Julien Fontanet
c5ae0dc4ca feat(xo-server/rest-api): allow trailing slashes for collections 2023-03-09 14:28:09 +01:00
Julien Fontanet
e979a2be9b fix(xo-server/rest-api/sendObjects): fix duplicate slashes 2023-03-09 14:27:32 +01:00
Julien Fontanet
586b84f434 feat(lint-staged): better branch commit message policy
Related to 56b9d22d4
2023-03-09 12:43:42 +01:00
Julien Fontanet
56b9d22d49 feat(lint-staged): validate commit message 2023-03-09 11:13:59 +01:00
Julien Fontanet
69aa241dc9 feat(lint-staged): validate packages list in CHANGELOG.unreleased.md 2023-03-09 10:22:01 +01:00
Mathieu
1335e12b97 feat(lite/dashboard): CPU provisioning (#6601) 2023-03-09 09:54:11 +01:00
Julien Fontanet
d1b1fa7ffd feat(xo-server/_getHostServerTimeShift): debounce for one minute (#6710)
Currently, each call to the API method `host.isHostServerTimeConsistent` triggers a call to the XAPI method `host.get_servertime` and a comparison with the local machine clock.

Numberof calls to this API method scales with the number of connected clients and xo-web appears to do it quite often on the Home/Hosts page.

As the result of this method is unlikely to change in time, it makes sense to add a small cache.
2023-03-08 16:58:24 +01:00
Julien Fontanet
d6a3492e90 feat(xo-server/api): make string params must be non-empty by default
Related to c71104db4
2023-03-08 16:41:03 +01:00
Florent BEAUCHAMP
4af57810d6 fix(xo-server): log handling when restoring a VM from a proxy (#6702) 2023-03-08 16:37:24 +01:00
Pierre Donias
6555cc4639 feat(lite/settings): color mode cards (#6693) 2023-03-08 16:08:58 +01:00
Florent BEAUCHAMP
3f57287d79 fix(backups): connect to NBD only when useVhdDirectory (#6707) 2023-03-08 15:09:19 +01:00
Thierry Goettelmann
1713e311f3 fix(lite): unused constant (#6704) 2023-03-07 16:35:55 +01:00
Julien Fontanet
8a14e78d2d feat: release 5.80.1 2023-03-07 14:38:11 +01:00
Julien Fontanet
1942e55f76 fix(scripts/bump-pkg): fix npm *Cannot set properties of null (setting 'parent')* error 2023-03-06 17:23:59 +01:00
Julien Fontanet
d83f41d0ff feat(xo-web): 5.112.1 2023-03-06 17:18:36 +01:00
Julien Fontanet
0f09240fb2 feat(xo-server): 5.110.1 2023-03-06 17:18:36 +01:00
Julien Fontanet
3731b49ea8 feat(@xen-orchestra/vmware-explorer): 0.2.0 2023-03-06 17:18:36 +01:00
Julien Fontanet
209223f77e feat(@xen-orchestra/proxy): 0.26.17 2023-03-06 17:18:36 +01:00
Julien Fontanet
7a5f5ee31d feat(@xen-orchestra/backups-cli): 1.0.2 2023-03-06 17:18:36 +01:00
Julien Fontanet
a148cb6c9b feat(@xen-orchestra/backups): 0.32.0 2023-03-06 17:18:33 +01:00
Julien Fontanet
e9ac049744 feat(@xen-orchestra/xapi): 2.0.0 2023-03-06 17:18:20 +01:00
Julien Fontanet
e06d4bd841 feat(xen-api): 1.2.7 2023-03-06 17:00:43 +01:00
Julien Fontanet
6cad4f5839 fix(fs/syncStackTraces): append sync stack instead of replacing original one
Same thing as 58e4f9b7b
2023-03-06 16:55:04 +01:00
Florent BEAUCHAMP
86f5f9eba3 feat(xo-server): use dcpath in ESXI import (#6694) 2023-03-06 16:25:46 +01:00
Julien Fontanet
473d091fa8 fix(xapi/parseDateTime): handle date objects (#6701)
Fixes zammad#12622 zammad#13106 zammad#13136 zammad#13162
2023-03-06 15:30:54 +01:00
Julien Fontanet
aec5ad4099 fix(xo-cli): better fallback logic for JSON-RPC transport
Logic:
- before: fallback on all network, HTTP or JSON-RPC formatting errors
- now: fallback only when response content-type is not `application/json`.
2023-03-06 15:07:32 +01:00
Julien Fontanet
f14f98f7c1 feat(xen-api): remove JSON in XML-RPC transport
This transport is never required, old hosts support XML-RPC and newer JSON-RPC.

This transport always contained bugs and now appears to be broken in recent XCP-ng/XenServer versions.
2023-03-06 15:07:32 +01:00
Julien Fontanet
e3d9a7ddf2 fix(xen-api/_call): ensure args always defined
Otherwise it might cause JSON-RPC issue on some XAPI versions (missing `params` field) or problems when augmenting errors with `call.params` (*TypeError: Cannot read properties of undefined (reading '0')*).
2023-03-06 15:07:32 +01:00
Julien Fontanet
58e4f9b7b4 fix(xen-api/syncStackTraces): append sync stack instead of replacing original one 2023-03-06 15:07:32 +01:00
Gabriel Gunullu
ef1f09cd4a chore(ci): no longer rely on Docker (#6687) 2023-03-06 13:10:27 +01:00
Julien Fontanet
617619eb31 fix(scripts/gen-deps-list): fix peer dependencies order
Introduced by 1f3255774
2023-03-06 11:05:23 +01:00
Julien Fontanet
00a135b00f chore(xo-web): move empty server label handling to xo-server
Related to c71104db4
2023-03-03 16:40:30 +01:00
Julien Fontanet
c71104db4f fix(xo-web): don't add servers with empty httpProxy
Fixes #6656

Introduced by 2412f8b1e

This commit also contains a change in `xo-server` to properly handle servers in database that have this problematic entries.
2023-03-03 16:39:13 +01:00
Julien Fontanet
eef7940fbc chore(xo-server): use @xen-orchestra/xapi/parseDateTime 2023-03-03 11:50:31 +01:00
Julien Fontanet
da4b3db17a feat(xo-server/patching): use HTTPS to fetch XenServer updates 2023-03-03 10:49:23 +01:00
rajaa-b
c0d20f04b6 fix(xo-web/import/vmware): fix invalid params (#6696)
Introduced by e6c95a0913
2023-03-02 17:13:47 +01:00
Mathieu
8fda8668b7 fix(lite): remove white bottom border (#6672) 2023-03-02 15:40:40 +01:00
Julien Fontanet
ea2c641604 fix(backups): fix remote timeout for metadata
Fixes https://xcp-ng.org/forum/post/59356

Introduced by 61b9a4cf2O
2023-03-01 17:23:52 +01:00
Pierre Donias
84e38505c5 feat(lite/vms): add "coming soon" labels on bulk actions (#6683)
And remove Backup action as it's not relevant in XO Lite
2023-02-28 15:46:47 +01:00
Julien Fontanet
6584eb0827 fix(CHANGELOG): 5.80 → 5.80.0 2023-02-28 15:08:45 +01:00
Gabriel Gunullu
467d897e05 feat: release 5.80 (#6692) 2023-02-28 15:07:53 +01:00
Julien Fontanet
2e6ea202cd feat(xo-server-transport-icinga2): 0.1.2 2023-02-28 14:14:59 +01:00
Julien Fontanet
27f17551ad feat(xo-server-perf-alert): 0.3.4 2023-02-28 14:14:33 +01:00
Julien Fontanet
48bfc4e3cd feat(xo-server-netbox): 0.3.7 2023-02-28 14:14:09 +01:00
Julien Fontanet
61b9a4cf28 feat(backups/Backup): error after waiting 5m for the remote
Related to zammad#12815
2023-02-28 09:26:14 +01:00
Julien Fontanet
c95448bf25 chore(backups/Backup): mutualize get remote error task 2023-02-28 09:26:14 +01:00
Julien Fontanet
f1ca60c182 feat(backups): add debug to backup worker 2023-02-28 09:26:14 +01:00
Julien Fontanet
52e79f78e5 feat(xo-server): refresh env.DEBUG for spawned processes (e.g. backup worker) 2023-02-28 09:26:10 +01:00
Julien Fontanet
586d6876f1 fix: release xo-server plugins impacted by http-request-plus@1
Introduced by ab96c549a
2023-02-27 19:41:28 +01:00
Gabriel Gunullu
25759ecf0a feat: technical release (#6691) 2023-02-27 18:06:47 +01:00
Florent BEAUCHAMP
1fbe870884 feat(xo-web/backup): show if NBD is used in the backup logs (#6685) 2023-02-27 14:14:01 +01:00
Florent BEAUCHAMP
9fcd497c42 feat(xo-web): improve esxi import (#6689)
- support thin import during esxi import
- support stop source during esxi import
- use toggle instead for checkboxes
- inverse logic for ssl verification and improve description
2023-02-27 11:30:01 +01:00
Julien Fontanet
63ee6b7f0e chore(prettify script): handle all file types 2023-02-27 10:42:12 +01:00
Gabriel Gunullu
73c0cd6934 feat(xo-web/metadata-backup): add pool selection for restoration (#6670)
See #6664
2023-02-27 10:21:49 +01:00
rajaa-b
e6c95a0913 feat(xo-web/import/esxi): import VM from ESXi (#6663)
See #6662
2023-02-27 10:12:03 +01:00
Gabriel Gunullu
af11cae29c feat(xo-server/metadataBackup.restore): new pool parameter (#6664) 2023-02-27 09:56:00 +01:00
Florent BEAUCHAMP
b984a9ff00 fix(fs): add missing dependency (#6688) 2023-02-27 09:41:49 +01:00
Florent Beauchamp
13837e0bf3 feat(xo-server): new API method esxi.listVms 2023-02-27 09:29:38 +01:00
Florent Beauchamp
f5d19fd28a fix(vmware-explorer/Esxi#search): handle more than 100 entries 2023-02-27 09:29:38 +01:00
Julien Fontanet
24ac3ea37d feat: release 5.79.3 2023-02-25 10:57:31 +01:00
Julien Fontanet
13cb33cc4a feat(xo-server/rest-api): basic VM actions (#6652) 2023-02-24 17:55:22 +01:00
Julien Fontanet
949a4697fe feat(xo-server-auth-oidc): OpenID Connect authentication plugin (#6684)
Fixes #6627
2023-02-24 17:45:41 +01:00
Florent BEAUCHAMP
3bbb828284 feat(backups): add Healthcheck to continuous replication (#6668) 2023-02-24 16:50:34 +01:00
Florent BEAUCHAMP
942b0f3dc9 fix(backups,xo-server): don't disable HA nor add CR tag on warm migration (#6679) 2023-02-24 16:50:03 +01:00
Florent BEAUCHAMP
208d8845c4 fix(backups): show signal when backup worker crashes (#6686) 2023-02-24 14:28:27 +01:00
Julien Fontanet
24cac9dcd5 fix(xen-api/getResource): fix redirection handling
Introduced by ab96c549a
2023-02-24 11:53:58 +01:00
Julien Fontanet
c8b29da677 feat(xo-cli): better output for returned values 2023-02-23 17:22:27 +01:00
Julien Fontanet
4f63d14529 fix(xo-cli --list-commands): close connection at the end 2023-02-23 16:57:55 +01:00
Julien Fontanet
cef6248650 chore: directly import lodash functions
This pass only concerns single imports.
2023-02-23 13:42:03 +01:00
Julien Fontanet
774e443a79 chore: remove unmaintained babel-plugin-lodash 2023-02-23 12:05:56 +01:00
Pierre Donias
1166807434 feat(xo-web/VM): add warning modal when enabling Windows update tools (#6681)
Fixes #6627
2023-02-22 19:01:24 +01:00
Pierre Donias
99cd502b65 fix(xo-web): show Suse icon when distro name is opensuse-leap (#6676)
See https://xcp-ng.org/forum/topic/6965
2023-02-22 17:24:13 +01:00
Julien Fontanet
d959e72a9c fix(xo-cli): handle EPIPE on stdout and stderr
Fixes #6680
2023-02-22 16:54:21 +01:00
Julien Fontanet
ee83788b43 chore: update dev deps 2023-02-21 18:32:02 +01:00
Julien Fontanet
62dd5f8ed7 feat(xo-cli): can register with existing token 2023-02-21 16:45:54 +01:00
Julien Fontanet
2de9984945 feat: release 7.79.2 2023-02-20 16:15:39 +01:00
Julien Fontanet
890b46b697 chore(read-chunk): add JSDoc 2023-02-20 14:03:31 +01:00
Julien Fontanet
5419957e06 chore(xen-api): log and ignore by default premature close errors
See #6677
2023-02-20 11:45:55 +01:00
Julien Fontanet
39d4667916 fix(xo-server/disk.import): handle stream end
Introduced by 61d5a964e

Fixes #6675
2023-02-18 11:10:59 +01:00
Julien Fontanet
083db67df9 feat: release 5.79.1 2023-02-17 14:00:54 +01:00
Julien Fontanet
8dceb6032b feat(xo-server): 5.109.2 2023-02-17 11:35:23 +01:00
Julien Fontanet
c300dad316 feat(@xen-orchestra/proxy): 0.26.12 2023-02-17 11:35:04 +01:00
Julien Fontanet
45b07f46f1 feat(xen-api): 1.2.4 2023-02-17 11:33:14 +01:00
Julien Fontanet
4023127c87 feat(xen-api/putResource): can ignore connection premature close
This is opt-in via the `ignorePrematureClose` option.
2023-02-17 10:51:28 +01:00
Julien Fontanet
ab96c549ae chore: use http-request-plus@1 2023-02-17 10:51:28 +01:00
Thierry Goettelmann
bc0afb589e feat(lite/stories): needed components for incoming Component Stories (#6611) 2023-02-17 10:42:35 +01:00
Mathieu
b42127f083 feat: technical release (#6674) 2023-02-16 14:35:04 +01:00
Florent BEAUCHAMP
61d5a964ee fix(xo-server): VMDK/OVA import (#6669) 2023-02-14 16:20:51 +01:00
Mathieu
f8fd6b78f5 fix(xo-web/home/pool): hide pro icon support for non XCP-ng pool (#6661)
Fixes #6653
2023-02-14 15:38:21 +01:00
Thierry Goettelmann
4546ef6619 feat(lite): Tasks (#6621) 2023-02-14 11:51:57 +01:00
Thierry Goettelmann
1f4457d9ca fix(lite/charts): fix Chart reactivity (#6618) 2023-02-14 11:42:29 +01:00
Thierry Goettelmann
65cbbf78bc fix(lite/dashboard): fix charts disappearing on dashboard (#6654) 2023-02-14 11:04:14 +01:00
Julien Fontanet
a73a24c1df chore(xo-cli): don't use exec-promise
May fix #6667

`exec-promise` called `process.exit()` at the end which may interfere with the output.
2023-02-11 21:51:16 +01:00
Julien Fontanet
31f850c19c fix(xo-server-transport-email): log async errors
Introduced by 711b722
2023-02-10 13:49:26 +01:00
Thierry Goettelmann
6d90d7bc82 feat(lite/UiIcon,UiSpinner): import and attributes order (#6609) 2023-02-09 19:25:41 +01:00
Julien Fontanet
d2a1c02b92 fix(backups,vhd-lib): don't dl whole VHD when using NBD
Related to zammad#12510
2023-02-09 17:22:10 +01:00
Florent BEAUCHAMP
6d96452ef8 fix(@vates/nbd-client): really disconnect from nbd server (#6660) 2023-02-09 17:04:51 +01:00
Pierre Donias
833589e6e7 feat(xo-web/intl): add missing French translation for S3 UI 2023-02-09 16:45:26 +01:00
Cécile MORANGE
8bb566e189 fix(xo-web/settings/remotes): placeholder for encryption key
Fixes #6658
2023-02-09 16:45:26 +01:00
Mathieu
38d2117752 fix(xo-web/pool/license): fix empty modal on license binding (#6666)
See zammad#12626
2023-02-09 16:10:42 +01:00
Julien Fontanet
914decd4f9 chore(backups/_forkStreamUnpipe): use native stream.finished 2023-02-09 11:30:03 +01:00
Julien Fontanet
873c38f9e1 chore(backups/_forkStreamUnpipe): rename variables
- `stream` → `source`
- `proxy` → `fork`
2023-02-09 11:29:13 +01:00
Julien Fontanet
a3e37eca62 fix(xo-server): disable broken requestTimeout
Fixes https://xcp-ng.org/forum/post/58146

Caused by nodejs/node#46574

It caused requests to timeout after 0-30 seconds, which broke all uploads.
2023-02-09 10:25:45 +01:00
Julien Fontanet
817911a41e fix(xen-api): fix task watchers when initially not watching events (2)
Introduced by 9f4fce9da
2023-02-08 17:11:27 +01:00
Julien Fontanet
9f4fce9daa fix(xen-api): fix task watchers when initially not watching events
Introduced by bc61dd85c
2023-02-08 12:21:56 +01:00
Julien Fontanet
9ff305d5db fix(xo-server/rest-api): fix VDI import
Introduced by ab0e411ac
2023-02-07 19:20:59 +01:00
Julien Fontanet
055c3e098f fix(xen-api/putResource): better error handling
Tested all combinaisons with the following conditions:

- success, cancelation and connection loss (ECONNRESET)
- with and without tasks watching
- with and without known length (i.e. content-length hack)
2023-02-07 17:38:01 +01:00
Julien Fontanet
bc61dd85c6 fix(xen-api): correctly handle not watching tasks 2023-02-07 17:23:57 +01:00
Julien Fontanet
db6f1405e9 chore(xo-server/rest-api): match export routes first 2023-02-07 16:36:05 +01:00
Gabriel Gunullu
3dc3376aec chore(test): replace vhd-util check (#6651) 2023-02-07 13:33:25 +01:00
Julien Fontanet
55920a58a3 feat(xo-server/recover-account): -s flag for xoa-support
Simpler process for xoa-support.

```console
$ xo-server-recover-account -s
The generated password is lXJMtCzWDGPOIg
user xoa-support has been successfully updated
```
2023-02-06 15:25:04 +01:00
Julien Fontanet
2a70ebf667 docs: uniformize code blocks
- add missing syntaxes
- don't put prompt if no command outputs to ease copy/paste and use `sh` syntax
- always use `$` as prompt and use `console` syntax
2023-02-06 11:25:12 +01:00
Julien Fontanet
2f65a86aa0 fix(xen-api/putResource): fix a number of issues
- hide `VDI_IO_ERROR` when using content-length hack
- avoid unhandled rejection in case upload fails
2023-02-06 10:40:42 +01:00
Julien Fontanet
4bf81ac33b docs(xapi): fix typo 2023-02-04 11:14:02 +01:00
Julien Fontanet
263c23ae8f docs(xapi): describe syncHookTimeout 2023-02-04 11:11:41 +01:00
Julien Fontanet
bf51b945c5 chore(vmware-explorer): fix lint issues
Introduced by 9fa15d9c8
2023-02-03 16:36:55 +01:00
Julien Fontanet
9d7a461550 feat(turbo): add dev and test tasks 2023-02-03 16:17:52 +01:00
Julien Fontanet
bbf60818eb chore: update dev deps 2023-02-03 16:17:31 +01:00
Julien Fontanet
103b22ebb2 fix(backups/importDeltaVm): resize cloned VDI if necessary
Fixes zammad#10996
2023-02-03 15:49:08 +01:00
Mathieu
cf4a1d7d40 fix(lite): update stacked ram usage message (#6650) 2023-02-02 11:50:10 +01:00
Julien Fontanet
e94f036aca chore(vmware-explorer): lower requirement to Node 14 2023-02-02 09:43:03 +01:00
Julien Fontanet
675405f7ac feat: release 5.79.0 2023-01-31 17:49:51 +01:00
Thierry Goettelmann
f8a3536a88 feat(lite): RelativeTime component (#6620) 2023-01-31 17:10:26 +01:00
Julien Fontanet
e527a13b50 feat(xo-server): 5.109.0 2023-01-31 17:04:31 +01:00
Julien Fontanet
3be03451f8 feat(@xen-orchestra/vmware-explorer): 0.0.3 2023-01-31 17:02:24 +01:00
Florent BEAUCHAMP
9fa15d9c84 feat(xo-server): import VM from ESXi (#6595) 2023-01-31 16:54:18 +01:00
Mathieu
9c3d39b4a7 feat: technical release (#6648) 2023-01-31 11:18:19 +01:00
Mathieu
28800f43ee fix(lite): use browser timestamps for stats (#6623) 2023-01-31 10:26:07 +01:00
Gabriel Gunullu
5c0b29c51f feat(xo-web/network): NBD option (#6646) 2023-01-30 17:34:21 +01:00
Gabriel Gunullu
62d9d0208b feat(xo-server/network.set): support (un)setting NBD (#6635) 2023-01-30 16:02:28 +01:00
Pierre Donias
4bf871e52f fix(lite): stats.memory is undefined (#6647)
Introduced by 4f31b7007a
2023-01-30 14:40:57 +01:00
Florent BEAUCHAMP
103972808c fix(xo-vmdk-to-vhd): better computation of overprovisioning of very sparse disks (#6639) 2023-01-30 14:15:44 +01:00
Julien Fontanet
dc65bb87b5 feat(upload-ova): special handling of invalid params error (#6626)
Fixes #6622

Similar to 036b30212 & 65daa39eb
2023-01-30 14:09:28 +01:00
Mathieu
bfa0282ecc feat: technical release (#6645) 2023-01-27 16:16:26 +01:00
Mathieu
aa66ec0ccd fix(changelog): fix release type on a package (#6644) 2023-01-27 15:09:10 +01:00
Mathieu
18fe19c680 fix(lite): fix getHostMemoryFunction error (#6643) 2023-01-27 14:43:11 +01:00
Julien Fontanet
ab0e411ac0 chore(xo-server/rest-api): improve code
- mutualize object fetching
- mutualize error handling
2023-01-27 13:01:21 +01:00
Pierre Donias
79671e6d61 fix(lite/build): "Big integer literals are not available in the configured target environment" (#6638)
Introduced by a281682f7a
2023-01-26 11:42:29 +01:00
Mathieu
71ad9773da feat(lite/vm): ability to change state of a VM (#6608) 2023-01-26 09:43:12 +01:00
Julien Fontanet
34ecc2bcbb feat(xo-server/rest-api): support setting name_label/name_description 2023-01-25 17:29:49 +01:00
Pierre Donias
53f4f265dc fix(xo-web/host/network): remove extra "mode" column (#6640)
Introduced by 7ede6bdbce
2023-01-25 17:19:03 +01:00
Florent BEAUCHAMP
97624ef836 fix(xo-vmdk-to-vhd): memory consumption during ova generation (#6637) 2023-01-25 10:23:50 +01:00
Julien Fontanet
fb8d0ed924 fix(xen-api/examples/import-vdi): fix tasks watching
Introduced by 3e351f852
2023-01-24 16:31:03 +01:00
Gabriel Gunullu
fedbdba13d feat(xo-web/recipes): static network config for k8s recipe (#6598) 2023-01-24 11:04:02 +01:00
Julien Fontanet
a281682f7a chore: update dev deps 2023-01-23 18:31:07 +01:00
Julien Fontanet
07e9f09692 docs(xo-server/rest-api): minor fix 2023-01-23 17:16:32 +01:00
Julien Fontanet
29d6e590de feat(xo-server/rest-api): support exporting VDI in raw format 2023-01-23 17:14:24 +01:00
Julien Fontanet
3e351f8529 feat(xen-api/examples/import-vdi): can create the VDI and various flags 2023-01-23 17:13:41 +01:00
Julien Fontanet
bfbfb9379a feat(xo-cli): improve no server message 2023-01-23 09:31:01 +01:00
rajaa-b
4f31b7007a feat(lite): RAM usage graph (#6604) 2023-01-20 11:44:54 +01:00
rajaa-b
fe0cc2ebb9 feat(lite): network throughput chart (#6610) 2023-01-19 16:10:36 +01:00
Mathieu
2fd6f521f8 feat(xo-web/licenses): make id and boundObjectId copyable (#6634) 2023-01-19 15:11:10 +01:00
Florent BEAUCHAMP
ec00728112 feat(xo-web): add toggle for viridian flag (#6631)
Fixes #6572
2023-01-19 09:33:36 +01:00
Julien Fontanet
7174c1edeb chore(xo-server/rest-api): doc fixes and changelog entry
Introduced by 7bd27e743
2023-01-18 23:43:57 +01:00
Julien Fontanet
7bd27e7437 feat(xo-server/rest-api): support to destroy VMs/VDIs 2023-01-18 23:35:49 +01:00
Florent BEAUCHAMP
0a28e30003 fix(xo-web): clarify windows update label (#6632)
Fix #6628
2023-01-18 17:31:28 +01:00
Mathieu
246c793c28 fix(xo-web/licenses): move message for XCP-ng license binding (#6630) 2023-01-18 17:11:21 +01:00
Florent BEAUCHAMP
5f0ea4d586 fix(xo-web): show bootable status for VM running pv_in_pvh virtualisation mode (#6629)
Fix #6432
2023-01-18 17:09:26 +01:00
Julien Fontanet
3c7d316b3c feat(xo-server): initial tasks infrastructure (#6625) 2023-01-17 16:12:04 +01:00
Julien Fontanet
645c8f32e3 chore(xo-server-perf-alert): use @xen-orchestra/log@0.5.0
Introduced by #6550
2023-01-17 15:42:38 +01:00
Gabriel Gunullu
adc5e7d0c0 test(vhd-cli): from Jest to test (#6605) 2023-01-17 10:39:41 +01:00
Thierry Goettelmann
b9b74ab1ac feat(lite/ui): first implementation of responsive UI (#6612) 2023-01-17 10:22:08 +01:00
Thierry Goettelmann
64298c04f2 feat(lite/ui): UiModal fix (#6617) 2023-01-17 09:25:29 +01:00
Gabriel Gunullu
3dfb7db039 chore(xo-server-perf-alert): print error (#6550) 2023-01-16 22:53:53 +01:00
Julien Fontanet
b64d8f5cbf fix(xo-server/rest-api): handle filter parsing errors 2023-01-16 17:34:23 +01:00
Julien Fontanet
c2e5225728 feat(xo-server): expose host.residentVms 2023-01-16 17:33:47 +01:00
Florent BEAUCHAMP
6c44a94bf4 fix(vhd-lib/parseVhdStream): also consume stream in NBD mode (#6613)
Consuming the stream is necessary for all writers including DeltaBackupWriter) otherwise other writers (e.g. DeltaBackupWriter i.e. CR) will stay stuck.
2023-01-16 10:54:45 +01:00
Florent BEAUCHAMP
a2d9310d0a fix(backups): fix size of NBD backups (#6599) 2023-01-16 10:43:29 +01:00
Julien Fontanet
05197b93ee feat(proxy): dedupe logs 2023-01-15 13:08:57 +01:00
Julien Fontanet
448d115d49 feat(xo-server): dedupe logs 2023-01-15 13:04:52 +01:00
Julien Fontanet
ae993dff45 feat(log/dedupe): helper to remove duplicated logs 2023-01-15 12:59:31 +01:00
Julien Fontanet
1bc4805f3d chore(log): move Log into own module 2023-01-15 12:59:31 +01:00
Julien Fontanet
98fe8f3955 chore(log): move createTransport into own module 2023-01-15 12:59:31 +01:00
Julien Fontanet
e902bcef67 chore(log): prefix internal modules by _ 2023-01-15 12:59:31 +01:00
Julien Fontanet
cb2a6e43a8 chore(log/utils.test.js): rename to _compileLogPattern.test.js 2023-01-15 12:59:31 +01:00
Julien Fontanet
b73a0992f8 feat(log): define public entry points
BREAKING CHANGE: Importing modules with extensions is now unsupported, i.e. use `@xen-orchestra/log/configure` instead of `@xen-orchestra/log/configure.js`.

Allows ESM modules to import modules without specifying extensions (just like CJS module) which will make migrating this lib to ESM painless in the future.
2023-01-15 12:58:35 +01:00
Julien Fontanet
d0b3d78639 feat(xo-server): round up host memory to nearest GiB
Fixes #5776

Improves the display of the value by ignoring the micro-kernel size (~50MiB), ie `128 GiB` instead of `127.96 GiB`.
2023-01-13 15:06:06 +01:00
Julien Fontanet
e6b8939772 fix(xapi/VM_snapshot): don't fail on NOBAK VDIs destruction failure 2023-01-12 15:25:09 +01:00
Julien Fontanet
bc372a982c fix(xapi/VM_checkpoint): remove unsupported ignoreNobakVdis 2023-01-12 15:20:40 +01:00
Florent Beauchamp
3ff8064f1b feat(backups): add more info about NBD backups in logs 2023-01-12 10:28:30 +01:00
Florent Beauchamp
834459186d fix(backups): useNbd must follow the config 2023-01-12 10:28:30 +01:00
Mathieu
12220ad4cf fix(lite/UsageBar): add color for dangerous cases (#6606) 2023-01-12 09:22:07 +01:00
Julien Fontanet
f6fd1db1ef feat(xo-server): increase HTTP server request timeout to 1 day
Fixes #6590
2023-01-11 22:07:35 +01:00
Julien Fontanet
a1050882ae docs(installation): explicit FreeBSD/OpenBSD not officially supported 2023-01-11 15:11:54 +01:00
Mathieu
687df5ead4 feat(lite/vm): change state button (#6571) 2023-01-11 10:51:16 +01:00
Mathieu
b057881ad0 fix(lite): fix type checking (#6607) 2023-01-10 16:16:32 +01:00
Julien Fontanet
2b23550996 chore(vhd-lib/createVhdStreamWithLength): use readChunkStrict
Related to zammad#10996

Not only it simplified the code a bit, but it also provides better error messages, especially on stream end.
2023-01-10 11:11:38 +01:00
Thierry Goettelmann
afeb20e589 fix(lite/Console): fix isReady condition (#6594) 2023-01-06 10:44:45 +01:00
Julien Fontanet
d7794518a2 chore: update to fs-extra@11 & parse-pairs@2 2023-01-05 11:33:09 +01:00
Julien Fontanet
fee61a43e3 chore: update to sinon@15 2023-01-05 11:16:03 +01:00
Julien Fontanet
b201afd192 chore: update dev deps 2023-01-05 10:21:06 +01:00
Florent Beauchamp
feef1f8b0a fix(backups/cleanVm): fix tests 2023-01-04 10:54:22 +01:00
Florent Beauchamp
1a5e2fde4f fix(vhd-lib/merge): require aliases for VHD directories 2023-01-04 10:54:22 +01:00
Julien Fontanet
609e957a55 fix: build script should build xo-server plugins
Introduced by 3bfd6c697
2023-01-04 10:53:55 +01:00
Thierry Goettelmann
5c18404174 feat(lite): update useCollectionFilter composable (#6538)
- Query String support must now be explicitly enabled with the `queryStringParam` option
- Added `initialFilters` option
- Added generic type support
- Updated documentation
2023-01-04 09:51:39 +01:00
Thierry Goettelmann
866a1dd8ae feat(lite): update useCollectionSorter composable (#6540)
- Query String support must now be explicitly enabled with the `queryStringParam` option
- Added `initialFilters` option
- Added generic type support
- Updated documentation
2023-01-04 09:42:51 +01:00
Julien Fontanet
3bfd6c6979 chore: use Turborepo to build
Why?

- ordering: build dependencies before dependents
- cache: don't rebuild if no changes in files or dependencies
- possibility to restrict to specific scopes

Changes:

- `yarn build` now only build `xo-server` and `xo-web` (and dependencies)
- `yarn build:xo-lite` build `@xen-orchestra/lite\ (and dependencies)
2023-01-03 11:39:20 +01:00
Florent BEAUCHAMP
06564e9091 feat(backups): remove merge limitations (#6591)
following #0635b3316ea077fccaa8b2d1e7a4d801eb701811
2023-01-03 11:07:07 +01:00
Thierry Goettelmann
1702783cfb feat(lite): Reactive chart theme (#6587) 2022-12-21 15:00:26 +01:00
rajaa-b
4ea0cbaa37 feat(xo-lite): Pool CPU usage chart (#6577) 2022-12-21 12:03:04 +01:00
Mathieu
2246e065f7 feat: release 5.78.0 (#6588) 2022-12-20 13:54:30 +01:00
Mathieu
29a38cdf1a feat: technical release (#6586) 2022-12-19 14:30:41 +01:00
Julien Fontanet
960c569e86 fix(CHANGELOG): add missing backups changes
Introduced by f95a20173
2022-12-19 11:40:06 +01:00
Julien Fontanet
fa183fc97e fix(CHANGELOG): add missing Kubernetes changes
Introduced by a1d63118c
2022-12-19 10:42:53 +01:00
Gabriel Gunullu
a1d63118c0 feat(xo-web/recipes/kubernetes): CIDR is no longer necessary (#6583)
Related to 6227349725
2022-12-19 09:42:56 +01:00
Julien Fontanet
f95a20173c fix(backups/{Delta,Full}BackupWriter}): fix this._vmBackupDir access
May fix #6584

Introduced by 45dcb914b.
2022-12-17 10:57:49 +01:00
Mathieu
b82d0fadc3 feat: technical release (#6585) 2022-12-16 16:13:07 +01:00
Julien Fontanet
0635b3316e feat(xo-server/backups): remove merge limitations
Since 30fe9764a, merging range of VHDs is supported via synthetic VHD which limits the perf impact.

It's no longer necessary to limit the number of VHDs per run to merge.
2022-12-16 14:42:05 +01:00
Thierry Goettelmann
113235aec3 feat(lite): new useArrayRemovedItemsHistory composable (#6546) 2022-12-16 11:43:50 +01:00
Mathieu
3921401e96 fix(lite): fix 'not connected to xapi' (#6522)
Introduced by 1c3cad9235
2022-12-16 09:54:43 +01:00
Julien Fontanet
2e514478a4 fix(xo-server/remotes): always remove handler from cache when forgetting 2022-12-15 17:58:14 +01:00
Julien Fontanet
b3d53b230e fix(fs/abstract): use standard naming for logger 2022-12-15 17:58:14 +01:00
Julien Fontanet
45dcb914ba chore(backups/{Mixin,Delta,Full}BackupWriter}): mutualize VM backup dir computation 2022-12-15 17:58:14 +01:00
Mathieu
711087b686 feat(lite): feedback on login page (#6464) 2022-12-15 15:00:46 +01:00
Julien Fontanet
b100a59d1d feat(xapi/VM_snapshot): use ignore_vdis param 2022-12-14 23:36:03 +01:00
Mathieu
109b2b0055 feat(lite): not found views (page/object) (#6410) 2022-12-14 16:47:40 +01:00
Julien Fontanet
9dda99eb20 fix(xo-server/_handleBackupLog): fix sendPassiveCheck condition
Introduced by ba782d269

Fixes https://xcp-ng.org/forum/post/56175
2022-12-14 16:26:43 +01:00
Thierry Goettelmann
fa0f75b474 feat(lite): New UiCardTitle component (#6558) 2022-12-12 15:19:43 +01:00
Julien Fontanet
2d93e0d4be feat(xapi/waitObjectState): better timeout error stacktrace
Create the error synchronously for better stacktrace and debuggability.
2022-12-12 15:11:10 +01:00
Julien Fontanet
fe6406336d feat: release 5.77.2 2022-12-12 11:49:55 +01:00
Julien Fontanet
1037d44089 feat(xo-server): 5.107.3 2022-12-12 11:27:18 +01:00
Julien Fontanet
a8c3669f43 feat(@xen-orchestra/proxy): 0.26.7 2022-12-12 11:26:55 +01:00
Julien Fontanet
d91753aa82 feat(@xen-orchestra/backups): 0.29.3 2022-12-12 11:26:26 +01:00
Julien Fontanet
b548514d44 fix(backups): wait for cache to be updated before running cleanVm (#6580)
Introduced by 191c12413
2022-12-12 09:30:08 +01:00
Julien Fontanet
ba782d2698 fix(xo-server/_handleBackupLog): bail instead of failing if Nagios plugin is not loaded
Introduced by ed34d9cbc
2022-12-08 17:17:31 +01:00
Julien Fontanet
0552dc23a5 chore(CHANGELOG.unreleased): clarify format description 2022-12-08 17:17:31 +01:00
Cécile Morange
574bbbf5ff docs(manage infrastructure): add how to remove a host from pool (#6574)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-12-08 15:38:02 +01:00
Julien Fontanet
df11a92cdb feat(scripts/gen-deps-list.js): add debug logs 2022-12-07 14:35:49 +01:00
Julien Fontanet
33ae59adf7 feat: release 5.77.1 2022-12-07 13:41:17 +01:00
Julien Fontanet
e0a115b41d feat(xo-server): 5.107.2 2022-12-07 13:19:15 +01:00
Julien Fontanet
f838d6c179 feat(@xen-orchestra/proxy): 0.26.6 2022-12-07 13:16:51 +01:00
Julien Fontanet
6c3229f517 feat(@xen-orchestra/backups): 0.29.2 2022-12-07 13:16:50 +01:00
Julien Fontanet
6973928b1a feat(backups/cleanVm): detect and fix cache inconsistencies (#6575) 2022-12-07 13:06:03 +01:00
Julien Fontanet
a5daba2a4d fix: work-around VuePress issues #2 2022-12-06 14:43:15 +01:00
Julien Fontanet
40ef83416e fix: work-around VuePress issues 2022-12-06 14:35:00 +01:00
Julien Fontanet
8518146455 fix: force classic Yarn 2022-12-06 10:53:35 +01:00
Florent BEAUCHAMP
d58f563de5 fix(xo-server/vm.warmMigration): fix start/delete params handling (#6570) 2022-12-06 10:42:51 +01:00
Thierry Goettelmann
ad2454adab feat(lite): replace ProgressBar with UiProgressBar & update UsageBar (#6544)
`ProgressBar` component handled too much logic (a progress bar + a circle icon + a label + a badge)

Since at various places we need a simple progress bar, all the additional logic should be handled by `UsageBar`.

- Move usage-specific logic from `ProgressBar` to `UsageBar`
- Removed `ProgressBar` component
- Created `ui/UiProgressBar` component containing only the bar itself
- Updated `UsageBar` to use `UiProgressBar` then adapting its style
2022-12-06 09:50:50 +01:00
Julien Fontanet
1f32557743 fix(scripts/gen-deps-list): fix packages order (#6564)
The release order computation is now uncoupled of the packages to release computation, and is now done for all packages so that transitive dependencies are still correctly ordered.
2022-11-30 14:52:46 +01:00
Julien Fontanet
e95aae2129 feat: release 5.77.0 2022-11-30 14:05:38 +01:00
Pierre Donias
9176171f20 feat: technical release (#6566) 2022-11-30 11:18:33 +01:00
Florent BEAUCHAMP
d4f2249a4d fix(xo-server/vm.warmMigration): use same job id in subsequent run (#6565)
Introduced by 72c69d7
2022-11-30 11:00:42 +01:00
Julien Fontanet
e0b4069c17 fix(scripts/bump-pkg): don't call git add --patch twice 2022-11-29 18:56:03 +01:00
Julien Fontanet
6b25a21151 feat(scripts/bump-pkg): ignore yarn.lock changes 2022-11-29 18:56:03 +01:00
Julien Fontanet
716dc45d85 chore(CHANGELOG): integrate released changes 2022-11-29 18:56:03 +01:00
Julien Fontanet
57850230c8 feat(xo-web): 5.108.0 2022-11-29 18:47:33 +01:00
Julien Fontanet
362d597031 feat(xo-server-web-hooks): 0.3.2 2022-11-29 18:47:14 +01:00
Julien Fontanet
e89b84b37b feat(xo-server-usage-report): 0.10.2 2022-11-29 18:46:54 +01:00
Julien Fontanet
ae6f6bf536 feat(xo-server-transport-nagios): 1.0.0 2022-11-29 18:46:27 +01:00
Julien Fontanet
6f765bdd6f feat(xo-server-sdn-controller): 1.0.7 2022-11-29 18:45:50 +01:00
Julien Fontanet
1982c6e6e6 feat(xo-server-netbox): 0.3.5 2022-11-29 18:45:30 +01:00
Julien Fontanet
527dceb43f feat(xo-server-load-balancer): 0.7.2 2022-11-29 18:44:12 +01:00
Julien Fontanet
f5a3d68d07 feat(xo-server-backup-reports): 0.17.2 2022-11-29 18:43:50 +01:00
Julien Fontanet
6c904fbc96 feat(xo-server-auth-ldap): 0.10.6 2022-11-29 18:43:22 +01:00
Julien Fontanet
295036a1e3 feat(xo-server-audit): 0.10.2 2022-11-29 18:42:30 +01:00
Julien Fontanet
5601d61b49 feat(xo-server): 5.107.0 2022-11-29 18:32:04 +01:00
Julien Fontanet
1c35c1a61a feat(xo-cli): 0.14.2 2022-11-29 18:31:24 +01:00
Julien Fontanet
4143014466 feat(xo-vmdk-to-vhd): 2.5.0 2022-11-29 18:29:33 +01:00
Julien Fontanet
90fea69b7e feat(@xen-orchestra/proxy): 0.26.5 2022-11-29 18:21:01 +01:00
Julien Fontanet
625663d619 feat(@xen-orchestra/xapi): 1.5.3 2022-11-29 18:18:09 +01:00
Julien Fontanet
403afc7aaf feat(@xen-orchestra/mixins): 0.8.2 2022-11-29 17:50:43 +01:00
Julien Fontanet
d295524c3c feat(@xen-orchestra/backups-cli): 1.0.0 2022-11-29 17:48:21 +01:00
Julien Fontanet
5eb4294e70 feat(@xen-orchestra/backups): 0.29.1 2022-11-29 17:48:21 +01:00
Julien Fontanet
90598522a6 feat(@xen-orchestra/audit-core): 0.2.2 2022-11-29 17:48:21 +01:00
Julien Fontanet
519fa1bcf8 feat(vhd-lib): 4.2.0 2022-11-29 17:48:21 +01:00
Julien Fontanet
7b0e5afe37 feat(@xen-orchestra/fs): 3.3.0 2022-11-29 17:48:21 +01:00
Julien Fontanet
0b6b3a47a2 feat(@vates/disposable): 0.1.3 2022-11-29 17:48:21 +01:00
Julien Fontanet
75db810508 feat(@xen-orchestra/log): 0.5.0 2022-11-29 17:48:21 +01:00
Julien Fontanet
2f52c564f5 chore(backups-cli): format package.json 2022-11-29 17:48:21 +01:00
Florent Beauchamp
011d582b80 fix(vhd-lib/merge): delete old data AFTER the alias has been overwritten 2022-11-29 16:42:57 +01:00
Julien Fontanet
32d21b2308 chore: use caret range for @vates/async-each
Introduced by 08298d328
2022-11-29 16:31:41 +01:00
Pierre Donias
45971ca622 fix(xo-web): remove duplicated imports (#6562) 2022-11-29 16:17:40 +01:00
Mathieu
f3a09f2dad feat(xo-web/VM/advanced): add button for warm migration (#6533)
See #6549
2022-11-29 15:14:41 +01:00
Mathieu
552a9c7b9f feat(xo-web/proxy): register an existing proxy (#6556) 2022-11-29 14:44:51 +01:00
Gabriel Gunullu
ed34d9cbc0 feat(xo-server-transport-nagios): make host and service configurable (#6560) 2022-11-29 14:34:41 +01:00
Julien Fontanet
187ee99931 fix(xo-server/plugin.configure): don't save injected defaults
Default values injected by Ajv from the configuration schema should not be saved.
2022-11-29 12:43:17 +01:00
Cécile Morange
ff78dd8f7c feat(xo-web/i18n): "XenServer" → "XCP-ng" (#6462)
See #6439
2022-11-29 11:47:16 +01:00
Julien Fontanet
b0eadb8ea4 fix: remove concurrency limit for dev script
Introduced by 9d5bc8af6

Limited concurrency (which is the default) is not compatible with never-ending commands.
2022-11-29 11:35:01 +01:00
Julien Fontanet
a95754715a fix: use --verbose for dev script
Introduced by 9d5bc8af6

Silent mode is not compatible (i.e. does not show a meaningful output) with never-ending commands.
2022-11-29 11:14:44 +01:00
534 changed files with 16668 additions and 5377 deletions

1
.commitlintrc.json Normal file
View File

@@ -0,0 +1 @@
{ "extends": ["@commitlint/config-conventional"] }

32
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Continous Integration
on: push
jobs:
CI:
runs-on: ubuntu-latest
steps:
# https://github.com/actions/checkout
- uses: actions/checkout@v3
- name: Install packages
run: |
sudo apt-get update
sudo apt-get install -y curl qemu-utils python3-vmdkstream git libxml2-utils libfuse2 nbdkit
- name: Cache Turbo
# https://github.com/actions/cache
uses: actions/cache@v3
with:
path: '**/node_modules/.cache/turbo'
key: ${{ runner.os }}-turbo-cache
- name: Setup Node environment
# https://github.com/actions/setup-node
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'yarn'
- name: Install project dependencies
run: yarn
- name: Build the project
run: yarn build
- name: Lint tests
run: yarn test-lint
- name: Integration tests
run: sudo yarn test-integration

View File

@@ -1,12 +0,0 @@
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build docker image
run: docker-compose -f docker/docker-compose.dev.yml build
- name: Create the container and start the tests
run: docker-compose -f docker/docker-compose.dev.yml up --exit-code-from xo

1
.gitignore vendored
View File

@@ -34,3 +34,4 @@ yarn-error.log.*
# code coverage
.nyc_output/
coverage/
.turbo/

11
.husky/commit-msg Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Only check commit message if commit on master or first commit on another
# branch to avoid bothering fix commits after reviews
#
# FIXME: does not properly run with git commit --amend
if [ "$(git rev-parse --abbrev-ref HEAD)" = master ] || [ "$(git rev-list --count master..)" -eq 0 ]
then
npx --no -- commitlint --edit "$1"
fi

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/async-each):
```
> npm install --save @vates/async-each
```sh
npm install --save @vates/async-each
```
## Usage

View File

@@ -33,7 +33,7 @@
"test": "node--test"
},
"devDependencies": {
"sinon": "^14.0.1",
"sinon": "^15.0.1",
"tap": "^16.3.0",
"test": "^3.2.1"
}

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/cached-dns.lookup):
```
> npm install --save @vates/cached-dns.lookup
```sh
npm install --save @vates/cached-dns.lookup
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/coalesce-calls):
```
> npm install --save @vates/coalesce-calls
```sh
npm install --save @vates/coalesce-calls
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/compose):
```
> npm install --save @vates/compose
```sh
npm install --save @vates/compose
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/decorate-with):
```
> npm install --save @vates/decorate-with
```sh
npm install --save @vates/decorate-with
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/disposable):
```
> npm install --save @vates/disposable
```sh
npm install --save @vates/disposable
```
## Usage

View File

@@ -14,7 +14,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.1.2",
"version": "0.1.4",
"engines": {
"node": ">=8.10"
},
@@ -25,11 +25,11 @@
"dependencies": {
"@vates/multi-key-map": "^0.1.0",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/log": "^0.4.0",
"@xen-orchestra/log": "^0.6.0",
"ensure-array": "^1.0.0"
},
"devDependencies": {
"sinon": "^14.0.1",
"sinon": "^15.0.1",
"test": "^3.2.1"
}
}

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/event-listeners-manager):
```
> npm install --save @vates/event-listeners-manager
```sh
npm install --save @vates/event-listeners-manager
```
## Usage

View File

@@ -21,7 +21,7 @@
"fuse-native": "^2.2.6",
"lru-cache": "^7.14.0",
"promise-toolbox": "^0.21.0",
"vhd-lib": "^4.1.1"
"vhd-lib": "^4.2.1"
},
"scripts": {
"postversion": "npm publish --access public"

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/multi-key-map):
```
> npm install --save @vates/multi-key-map
```sh
npm install --save @vates/multi-key-map
```
## Usage

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/nbd-client):
```
> npm install --save @vates/nbd-client
```sh
npm install --save @vates/nbd-client
```
## Usage

View File

@@ -16,6 +16,7 @@ const {
NBD_REPLY_MAGIC,
NBD_REQUEST_MAGIC,
OPTS_MAGIC,
NBD_CMD_DISC,
} = require('./constants.js')
const { fromCallback } = require('promise-toolbox')
const { readChunkStrict } = require('@vates/read-chunk')
@@ -89,6 +90,11 @@ module.exports = class NbdClient {
}
async disconnect() {
const buffer = Buffer.alloc(28)
buffer.writeInt32BE(NBD_REQUEST_MAGIC, 0) // it is a nbd request
buffer.writeInt16BE(0, 4) // no command flags for a disconnect
buffer.writeInt16BE(NBD_CMD_DISC, 6) // we want to disconnect from nbd server
await this.#write(buffer)
await this.#serverSocket.destroy()
}

View File

@@ -13,7 +13,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.0",
"version": "1.0.1",
"engines": {
"node": ">=14.0"
},
@@ -22,7 +22,7 @@
"@vates/read-chunk": "^1.0.1",
"@xen-orchestra/async-map": "^0.1.2",
"promise-toolbox": "^0.21.0",
"xen-api": "^1.2.2"
"xen-api": "^1.2.7"
},
"devDependencies": {
"tap": "^16.3.0",

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/otp):
```
> npm install --save @vates/otp
```sh
npm install --save @vates/otp
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/parse-duration):
```
> npm install --save @vates/parse-duration
```sh
npm install --save @vates/parse-duration
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/predicates):
```
> npm install --save @vates/predicates
```sh
npm install --save @vates/predicates
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/read-chunk):
```
> npm install --save @vates/read-chunk
```sh
npm install --save @vates/read-chunk
```
## Usage

View File

@@ -1,5 +1,12 @@
'use strict'
/**
* Read a chunk of data from a stream.
*
* @param {Readable} stream - A readable stream to read from.
* @param {number} size - The number of bytes to read.
* @returns {Promise<Buffer|null>} - A Promise that resolves to a Buffer of up to size bytes if available, or null if end of stream is reached. The Promise is rejected if there is an error while reading from the stream.
*/
const readChunk = (stream, size) =>
stream.closed || stream.readableEnded
? Promise.resolve(null)
@@ -33,6 +40,13 @@ const readChunk = (stream, size) =>
})
exports.readChunk = readChunk
/**
* Read a chunk of data from a stream.
*
* @param {Readable} stream - A readable stream to read from.
* @param {number} size - The number of bytes to read.
* @returns {Promise<Buffer>} - A Promise that resolves to a Buffer of size bytes. The Promise is rejected if there is an error while reading from the stream.
*/
exports.readChunkStrict = async function readChunkStrict(stream, size) {
const chunk = await readChunk(stream, size)
if (chunk === null) {

54
@vates/task/.USAGE.md Normal file
View File

@@ -0,0 +1,54 @@
```js
import { Task } from '@vates/task'
const task = new Task({
name: 'my task',
// if defined, a new detached task is created
//
// if not defined and created inside an existing task, the new task is considered a subtask
onProgress(event) {
// this function is called each time this task or one of it's subtasks change state
const { id, timestamp, type } = event
if (type === 'start') {
const { name, parentId } = event
} else if (type === 'end') {
const { result, status } = event
} else if (type === 'info' || type === 'warning') {
const { data, message } = event
} else if (type === 'property') {
const { name, value } = event
}
},
})
// this field is settable once before being observed
task.id
task.status
await task.abort()
// if fn rejects, the task will be marked as failed
const result = await task.runInside(fn)
// if fn rejects, the task will be marked as failed
// if fn resolves, the task will be marked as succeeded
const result = await task.run(fn)
// the abort signal of the current task if any, otherwise is `undefined`
Task.abortSignal
// sends an info on the current task if any, otherwise does nothing
Task.info(message, data)
// sends an info on the current task if any, otherwise does nothing
Task.warning(message, data)
// attaches a property to the current task if any, otherwise does nothing
//
// the latest value takes precedence
//
// examples:
// - progress
Task.set(property, value)
```

1
@vates/task/.npmignore Symbolic link
View File

@@ -0,0 +1 @@
../../scripts/npmignore

85
@vates/task/README.md Normal file
View File

@@ -0,0 +1,85 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/task
[![Package Version](https://badgen.net/npm/v/@vates/task)](https://npmjs.org/package/@vates/task) ![License](https://badgen.net/npm/license/@vates/task) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/task)](https://bundlephobia.com/result?p=@vates/task) [![Node compatibility](https://badgen.net/npm/node/@vates/task)](https://npmjs.org/package/@vates/task)
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/task):
```sh
npm install --save @vates/task
```
## Usage
```js
import { Task } from '@vates/task'
const task = new Task({
name: 'my task',
// if defined, a new detached task is created
//
// if not defined and created inside an existing task, the new task is considered a subtask
onProgress(event) {
// this function is called each time this task or one of it's subtasks change state
const { id, timestamp, type } = event
if (type === 'start') {
const { name, parentId } = event
} else if (type === 'end') {
const { result, status } = event
} else if (type === 'info' || type === 'warning') {
const { data, message } = event
} else if (type === 'property') {
const { name, value } = event
}
},
})
// this field is settable once before being observed
task.id
task.status
await task.abort()
// if fn rejects, the task will be marked as failed
const result = await task.runInside(fn)
// if fn rejects, the task will be marked as failed
// if fn resolves, the task will be marked as succeeded
const result = await task.run(fn)
// the abort signal of the current task if any, otherwise is `undefined`
Task.abortSignal
// sends an info on the current task if any, otherwise does nothing
Task.info(message, data)
// sends an info on the current task if any, otherwise does nothing
Task.warning(message, data)
// attaches a property to the current task if any, otherwise does nothing
//
// the latest value takes precedence
//
// examples:
// - progress
Task.set(property, value)
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

184
@vates/task/index.js Normal file
View File

@@ -0,0 +1,184 @@
'use strict'
const assert = require('node:assert').strict
const { AsyncLocalStorage } = require('node:async_hooks')
// define a read-only, non-enumerable, non-configurable property
function define(object, property, value) {
Object.defineProperty(object, property, { value })
}
const noop = Function.prototype
const ABORTED = 'aborted'
const ABORTING = 'aborting'
const FAILURE = 'failure'
const PENDING = 'pending'
const SUCCESS = 'success'
exports.STATUS = { ABORTED, ABORTING, FAILURE, PENDING, SUCCESS }
const asyncStorage = new AsyncLocalStorage()
const getTask = () => asyncStorage.getStore()
exports.Task = class Task {
static get abortSignal() {
const task = getTask()
if (task !== undefined) {
return task.#abortController.signal
}
}
static info(message, data) {
const task = getTask()
if (task !== undefined) {
task.#emit('info', { data, message })
}
}
static run(opts, fn) {
return new this(opts).run(fn)
}
static set(name, value) {
const task = getTask()
if (task !== undefined) {
task.#emit('property', { name, value })
}
}
static warning(message, data) {
const task = getTask()
if (task !== undefined) {
task.#emit('warning', { data, message })
}
}
static wrap(opts, fn) {
// compatibility with @decorateWith
if (typeof fn !== 'function') {
;[fn, opts] = [opts, fn]
}
return function taskRun() {
return Task.run(typeof opts === 'function' ? opts.apply(this, arguments) : opts, () => fn.apply(this, arguments))
}
}
#abortController = new AbortController()
#onProgress
#parent
get id() {
return (this.id = Math.random().toString(36).slice(2))
}
set id(value) {
define(this, 'id', value)
}
#startData
#status = PENDING
get status() {
return this.#status
}
constructor({ name, onProgress }) {
this.#startData = { name }
if (onProgress !== undefined) {
this.#onProgress = onProgress
} else {
const parent = getTask()
if (parent !== undefined) {
this.#parent = parent
const { signal } = parent.#abortController
signal.addEventListener('abort', () => {
this.#abortController.abort(signal.reason)
})
this.#onProgress = parent.#onProgress
this.#startData.parentId = parent.id
} else {
this.#onProgress = noop
}
}
const { signal } = this.#abortController
signal.addEventListener('abort', () => {
if (this.status === PENDING) {
this.#status = this.#running ? ABORTING : ABORTED
}
})
}
abort(reason) {
this.#abortController.abort(reason)
}
#emit(type, data) {
data.id = this.id
data.timestamp = Date.now()
data.type = type
this.#onProgress(data)
}
#handleMaybeAbortion(result) {
if (this.status === ABORTING) {
this.#status = ABORTED
this.#emit('end', { status: ABORTED, result })
return true
}
return false;
}
async run(fn) {
const result = await this.runInside(fn)
if (this.status === PENDING) {
this.#status = SUCCESS
this.#emit('end', { status: SUCCESS, result })
}
return result
}
#running = false
async runInside(fn) {
assert.equal(this.status, PENDING)
assert.equal(this.#running, false)
this.#running = true
const startData = this.#startData
if (startData !== undefined) {
this.#startData = undefined
this.#emit('start', startData)
}
try {
const result = await asyncStorage.run(this, fn)
this.#handleMaybeAbortion(result)
this.#running = false
return result
} catch (result) {
if (!this.#handleMaybeAbortion(result)) {
this.#status = FAILURE
this.#emit('end', { status: FAILURE, result })
}
throw result
}
}
wrap(fn) {
const task = this
return function taskRun() {
return task.run(() => fn.apply(this, arguments))
}
}
wrapInside(fn) {
const task = this
return function taskRunInside() {
return task.runInside(() => fn.apply(this, arguments))
}
}
}

23
@vates/task/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"private": false,
"name": "@vates/task",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/task",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/task",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.0.1",
"engines": {
"node": ">=14"
},
"scripts": {
"postversion": "npm publish --access public"
}
}

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@vates/toggle-scripts):
```
> npm install --save @vates/toggle-scripts
```sh
npm install --save @vates/toggle-scripts
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/async-map):
```
> npm install --save @xen-orchestra/async-map
```sh
npm install --save @xen-orchestra/async-map
```
## Usage

View File

@@ -35,7 +35,7 @@
"test": "node--test"
},
"devDependencies": {
"sinon": "^14.0.1",
"sinon": "^15.0.1",
"test": "^3.2.1"
}
}

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/audit-core):
```
> npm install --save @xen-orchestra/audit-core
```sh
npm install --save @xen-orchestra/audit-core
```
## Contributions

View File

@@ -7,7 +7,7 @@
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"version": "0.2.1",
"version": "0.2.3",
"engines": {
"node": ">=14"
},
@@ -17,7 +17,7 @@
},
"dependencies": {
"@vates/decorate-with": "^2.0.0",
"@xen-orchestra/log": "^0.4.0",
"@xen-orchestra/log": "^0.6.0",
"golike-defer": "^0.5.1",
"object-hash": "^2.0.1"
},

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/backups-cli):
```
> npm install --global @xen-orchestra/backups-cli
```sh
npm install --global @xen-orchestra/backups-cli
```
## Usage

View File

@@ -7,12 +7,12 @@
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"dependencies": {
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/backups": "^0.29.0",
"@xen-orchestra/fs": "^3.2.0",
"@xen-orchestra/backups": "^0.32.0",
"@xen-orchestra/fs": "^3.3.2",
"filenamify": "^4.1.0",
"getopts": "^2.2.5",
"lodash": "^4.17.15",
"promise-toolbox":"^0.21.0"
"promise-toolbox": "^0.21.0"
},
"engines": {
"node": ">=14"
@@ -27,7 +27,7 @@
"scripts": {
"postversion": "npm publish --access public"
},
"version": "0.7.8",
"version": "1.0.2",
"license": "AGPL-3.0-or-later",
"author": {
"name": "Vates SAS",

View File

@@ -3,6 +3,7 @@
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
const Disposable = require('promise-toolbox/Disposable')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const pTimeout = require('promise-toolbox/timeout')
const { compileTemplate } = require('@xen-orchestra/template')
const { limitConcurrency } = require('limit-concurrency-decorator')
@@ -11,6 +12,7 @@ const { PoolMetadataBackup } = require('./_PoolMetadataBackup.js')
const { Task } = require('./Task.js')
const { VmBackup } = require('./_VmBackup.js')
const { XoMetadataBackup } = require('./_XoMetadataBackup.js')
const createStreamThrottle = require('./_createStreamThrottle.js')
const noop = Function.prototype
@@ -25,6 +27,7 @@ const getAdaptersByRemote = adapters => {
const runTask = (...args) => Task.run(...args).catch(noop) // errors are handled by logs
const DEFAULT_SETTINGS = {
getRemoteTimeout: 300e3,
reportWhen: 'failure',
}
@@ -38,7 +41,8 @@ const DEFAULT_VM_SETTINGS = {
fullInterval: 0,
healthCheckSr: undefined,
healthCheckVmsWithTags: [],
maxMergedDeltasPerRun: 2,
maxExportRate: 0,
maxMergedDeltasPerRun: Infinity,
offlineBackup: false,
offlineSnapshot: false,
snapshotRetention: 0,
@@ -53,6 +57,13 @@ const DEFAULT_METADATA_SETTINGS = {
retentionXoMetadata: 0,
}
class RemoteTimeoutError extends Error {
constructor(remoteId) {
super('timeout while getting the remote ' + remoteId)
this.remoteId = remoteId
}
}
exports.Backup = class Backup {
constructor({ config, getAdapter, getConnectedRecord, job, schedule }) {
this._config = config
@@ -60,13 +71,6 @@ exports.Backup = class Backup {
this._job = job
this._schedule = schedule
this._getAdapter = Disposable.factory(function* (remoteId) {
return {
adapter: yield getAdapter(remoteId),
remoteId,
}
})
this._getSnapshotNameLabel = compileTemplate(config.snapshotNameLabelTpl, {
'{job.name}': job.name,
'{vm.name_label}': vm => vm.name_label,
@@ -87,6 +91,27 @@ exports.Backup = class Backup {
this._baseSettings = baseSettings
this._settings = { ...baseSettings, ...job.settings[schedule.id] }
const { getRemoteTimeout } = this._settings
this._getAdapter = async function (remoteId) {
try {
const disposable = await pTimeout.call(getAdapter(remoteId), getRemoteTimeout, new RemoteTimeoutError(remoteId))
return new Disposable(() => disposable.dispose(), {
adapter: disposable.value,
remoteId,
})
} catch (error) {
// See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
runTask(
{
name: 'get remote adapter',
data: { type: 'remote', id: remoteId },
},
() => Promise.reject(error)
)
}
}
}
async _runMetadataBackup() {
@@ -132,20 +157,7 @@ exports.Backup = class Backup {
})
)
),
Disposable.all(
remoteIds.map(id =>
this._getAdapter(id).catch(error => {
// See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
runTask(
{
name: 'get remote adapter',
data: { type: 'remote', id },
},
() => Promise.reject(error)
)
})
)
),
Disposable.all(remoteIds.map(id => this._getAdapter(id))),
async (pools, remoteAdapters) => {
// remove adapters that failed (already handled)
remoteAdapters = remoteAdapters.filter(_ => _ !== undefined)
@@ -216,9 +228,11 @@ exports.Backup = class Backup {
// FIXME: proper SimpleIdPattern handling
const getSnapshotNameLabel = this._getSnapshotNameLabel
const schedule = this._schedule
const settings = this._settings
const throttleStream = createStreamThrottle(settings.maxExportRate)
const config = this._config
const settings = this._settings
await Disposable.use(
Disposable.all(
extractIdsFromSimplePattern(job.srs).map(id =>
@@ -233,19 +247,7 @@ exports.Backup = class Backup {
})
)
),
Disposable.all(
extractIdsFromSimplePattern(job.remotes).map(id =>
this._getAdapter(id).catch(error => {
runTask(
{
name: 'get remote adapter',
data: { type: 'remote', id },
},
() => Promise.reject(error)
)
})
)
),
Disposable.all(extractIdsFromSimplePattern(job.remotes).map(id => this._getAdapter(id))),
() => (settings.healthCheckSr !== undefined ? this._getRecord('SR', settings.healthCheckSr) : undefined),
async (srs, remoteAdapters, healthCheckSr) => {
// remove adapters that failed (already handled)
@@ -280,6 +282,7 @@ exports.Backup = class Backup {
schedule,
settings: { ...settings, ...allSettings[vm.uuid] },
srs,
throttleStream,
vm,
}).run()
)

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/backups):
```
> npm install --save @xen-orchestra/backups
```sh
npm install --save @xen-orchestra/backups
```
## Contributions

View File

@@ -10,7 +10,14 @@ const groupBy = require('lodash/groupBy.js')
const pickBy = require('lodash/pickBy.js')
const { dirname, join, normalize, resolve } = require('path')
const { createLogger } = require('@xen-orchestra/log')
const { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
const {
createVhdDirectoryFromStream,
createVhdStreamWithLength,
openVhd,
VhdAbstract,
VhdDirectory,
VhdSynthetic,
} = require('vhd-lib')
const { deduped } = require('@vates/disposable/deduped.js')
const { decorateMethodsWith } = require('@vates/decorate-with')
const { compose } = require('@vates/compose')
@@ -28,9 +35,11 @@ const { isMetadataFile } = require('./_backupType.js')
const { isValidXva } = require('./_isValidXva.js')
const { listPartitions, LVM_PARTITION_TYPE } = require('./_listPartitions.js')
const { lvs, pvs } = require('./_lvm.js')
const { watchStreamSize } = require('./_watchStreamSize')
// @todo : this import is marked extraneous , sould be fixed when lib is published
const { mount } = require('@vates/fuse-vhd')
const { asyncEach } = require('@vates/async-each')
const { strictEqual } = require('assert')
const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
exports.DIR_XO_CONFIG_BACKUPS = DIR_XO_CONFIG_BACKUPS
@@ -208,8 +217,8 @@ class RemoteAdapter {
const isVhdDirectory = vhd instanceof VhdDirectory
return isVhdDirectory
? this.#useVhdDirectory() && this.#getCompressionType() === vhd.compressionType
: !this.#useVhdDirectory()
? this.useVhdDirectory() && this.#getCompressionType() === vhd.compressionType
: !this.useVhdDirectory()
})
}
@@ -232,21 +241,23 @@ class RemoteAdapter {
return promise
}
#removeVmBackupsFromCache(backups) {
for (const [dir, filenames] of Object.entries(
groupBy(
backups.map(_ => _._filename),
dirname
)
)) {
// detached async action, will not reject
this._updateCache(dir + '/cache.json.gz', backups => {
for (const filename of filenames) {
debug('removing cache entry', { entry: filename })
delete backups[filename]
}
})
}
async #removeVmBackupsFromCache(backups) {
await asyncEach(
Object.entries(
groupBy(
backups.map(_ => _._filename),
dirname
)
),
([dir, filenames]) =>
// will not reject
this._updateCache(dir + '/cache.json.gz', backups => {
for (const filename of filenames) {
debug('removing cache entry', { entry: filename })
delete backups[filename]
}
})
)
}
async deleteDeltaVmBackups(backups) {
@@ -255,7 +266,7 @@ class RemoteAdapter {
// this will delete the json, unused VHDs will be detected by `cleanVm`
await asyncMapSettled(backups, ({ _filename }) => handler.unlink(_filename))
this.#removeVmBackupsFromCache(backups)
await this.#removeVmBackupsFromCache(backups)
}
async deleteMetadataBackup(backupId) {
@@ -284,7 +295,7 @@ class RemoteAdapter {
Promise.all([handler.unlink(_filename), handler.unlink(resolveRelativeFromFile(_filename, xva))])
)
this.#removeVmBackupsFromCache(backups)
await this.#removeVmBackupsFromCache(backups)
}
deleteVmBackup(file) {
@@ -318,12 +329,12 @@ class RemoteAdapter {
return this._vhdDirectoryCompression
}
#useVhdDirectory() {
useVhdDirectory() {
return this.handler.useVhdDirectory()
}
#useAlias() {
return this.#useVhdDirectory()
return this.useVhdDirectory()
}
async *#getDiskLegacy(diskId) {
@@ -508,7 +519,7 @@ class RemoteAdapter {
return `${BACKUP_DIR}/${vmUuid}/cache.json.gz`
}
async #readCache(path) {
async _readCache(path) {
try {
return JSON.parse(await fromCallback(zlib.gunzip, await this.handler.readFile(path)))
} catch (error) {
@@ -521,15 +532,15 @@ class RemoteAdapter {
_updateCache = synchronized.withKey()(this._updateCache)
// eslint-disable-next-line no-dupe-class-members
async _updateCache(path, fn) {
const cache = await this.#readCache(path)
const cache = await this._readCache(path)
if (cache !== undefined) {
fn(cache)
await this.#writeCache(path, cache)
await this._writeCache(path, cache)
}
}
async #writeCache(path, data) {
async _writeCache(path, data) {
try {
await this.handler.writeFile(path, await fromCallback(zlib.gzip, JSON.stringify(data)), { flags: 'w' })
} catch (error) {
@@ -577,7 +588,7 @@ class RemoteAdapter {
async _readCacheListVmBackups(vmUuid) {
const path = this.#getVmBackupsCache(vmUuid)
const cache = await this.#readCache(path)
const cache = await this._readCache(path)
if (cache !== undefined) {
debug('found VM backups cache, using it', { path })
return cache
@@ -590,7 +601,7 @@ class RemoteAdapter {
}
// detached async action, will not reject
this.#writeCache(path, backups)
this._writeCache(path, backups)
return backups
}
@@ -641,7 +652,7 @@ class RemoteAdapter {
})
// will not throw
this._updateCache(this.#getVmBackupsCache(vmUuid), backups => {
await this._updateCache(this.#getVmBackupsCache(vmUuid), backups => {
debug('adding cache entry', { entry: path })
backups[path] = {
...metadata,
@@ -657,9 +668,9 @@ class RemoteAdapter {
async writeVhd(path, input, { checksum = true, validator = noop, writeBlockConcurrency, nbdClient } = {}) {
const handler = this._handler
if (this.#useVhdDirectory()) {
if (this.useVhdDirectory()) {
const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
await createVhdDirectoryFromStream(handler, dataPath, input, {
const size = await createVhdDirectoryFromStream(handler, dataPath, input, {
concurrency: writeBlockConcurrency,
compression: this.#getCompressionType(),
async validator() {
@@ -669,20 +680,43 @@ class RemoteAdapter {
nbdClient,
})
await VhdAbstract.createAlias(handler, path, dataPath)
return size
} else {
await this.outputStream(path, input, { checksum, validator })
const inputWithSize = await createVhdStreamWithLength(input)
return this.outputStream(path, inputWithSize, { checksum, validator, expectedSize: inputWithSize.length })
}
}
async outputStream(path, input, { checksum = true, validator = noop } = {}) {
async outputStream(path, input, { checksum = true, validator = noop, expectedSize } = {}) {
const container = watchStreamSize(input)
await this._handler.outputStream(path, input, {
checksum,
dirMode: this._dirMode,
async validator() {
await input.task
if (expectedSize !== undefined) {
// check that we read all the stream
strictEqual(
container.size,
expectedSize,
`transferred size ${container.size}, expected file size : ${expectedSize}`
)
}
let size
try {
size = await this._handler.getSize(path)
} catch (err) {
// can fail is the remote is encrypted
}
if (size !== undefined) {
// check that everything is written to disk
strictEqual(size, container.size, `written size ${size}, transfered size : ${container.size}`)
}
return validator.apply(this, arguments)
},
})
return container.size
}
// open the hierarchy of ancestors until we find a full one

View File

@@ -100,7 +100,7 @@ class Task {
* In case of error, the task will be failed.
*
* @typedef Result
* @param {() => Result)} fn
* @param {() => Result} fn
* @param {boolean} last - Whether the task should succeed if there is no error
* @returns Result
*/

View File

@@ -55,6 +55,7 @@ class VmBackup {
schedule,
settings,
srs,
throttleStream,
vm,
}) {
if (vm.other_config['xo:backup:job'] === job.id && 'start' in vm.blocked_operations) {
@@ -82,6 +83,7 @@ class VmBackup {
this._healthCheckSr = healthCheckSr
this._jobId = job.id
this._jobSnapshots = undefined
this._throttleStream = throttleStream
this._xapi = vm.$xapi
// Base VM for the export
@@ -244,6 +246,7 @@ class VmBackup {
fullVdisRequired,
})
const sizeContainers = mapValues(deltaExport.streams, stream => watchStreamSize(stream))
deltaExport.streams = mapValues(deltaExport.streams, this._throttleStream)
const timestamp = Date.now()
@@ -285,10 +288,12 @@ class VmBackup {
async _copyFull() {
const { compression } = this.job
const stream = await this._xapi.VM_export(this.exportedVm.$ref, {
compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
useSnapshot: false,
})
const stream = this._throttleStream(
await this._xapi.VM_export(this.exportedVm.$ref, {
compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
useSnapshot: false,
})
)
const sizeContainer = watchStreamSize(stream)
const timestamp = Date.now()

View File

@@ -1,8 +1,8 @@
'use strict'
require('@xen-orchestra/log/configure.js').catchGlobalErrors(
require('@xen-orchestra/log').createLogger('xo:backups:worker')
)
const logger = require('@xen-orchestra/log').createLogger('xo:backups:worker')
require('@xen-orchestra/log/configure').catchGlobalErrors(logger)
require('@vates/cached-dns.lookup').createCachedLookup().patchGlobal()
@@ -20,6 +20,8 @@ const { Backup } = require('./Backup.js')
const { RemoteAdapter } = require('./RemoteAdapter.js')
const { Task } = require('./Task.js')
const { debug } = logger
class BackupWorker {
#config
#job
@@ -122,6 +124,11 @@ decorateMethodsWith(BackupWorker, {
]),
})
const emitMessage = message => {
debug('message emitted', { message })
process.send(message)
}
// Received message:
//
// Message {
@@ -139,6 +146,8 @@ decorateMethodsWith(BackupWorker, {
// result?: any
// }
process.on('message', async message => {
debug('message received', { message })
if (message.action === 'run') {
const backupWorker = new BackupWorker(message.data)
try {
@@ -147,7 +156,7 @@ process.on('message', async message => {
{
name: 'backup run',
onLog: data =>
process.send({
emitMessage({
data,
type: 'log',
}),
@@ -156,13 +165,13 @@ process.on('message', async message => {
)
: await backupWorker.run()
process.send({
emitMessage({
type: 'result',
result,
status: 'success',
})
} catch (error) {
process.send({
emitMessage({
type: 'result',
result: error,
status: 'failure',

View File

@@ -311,7 +311,6 @@ exports.cleanVm = async function cleanVm(
}
const jsons = new Set()
let mustInvalidateCache = false
const xvas = new Set()
const xvaSums = []
const entries = await handler.list(vmDir, {
@@ -327,6 +326,20 @@ exports.cleanVm = async function cleanVm(
}
})
const cachePath = vmDir + '/cache.json.gz'
let mustRegenerateCache
{
const cache = await this._readCache(cachePath)
const actual = cache === undefined ? 0 : Object.keys(cache).length
const expected = jsons.size
mustRegenerateCache = actual !== expected
if (mustRegenerateCache) {
logWarn('unexpected number of entries in backup cache', { path: cachePath, actual, expected })
}
}
await asyncMap(xvas, async path => {
// check is not good enough to delete the file, the best we can do is report
// it
@@ -338,6 +351,8 @@ exports.cleanVm = async function cleanVm(
const unusedVhds = new Set(vhds)
const unusedXvas = new Set(xvas)
const backups = new Map()
// compile the list of unused XVAs and VHDs, and remove backup metadata which
// reference a missing XVA/VHD
await asyncMap(jsons, async json => {
@@ -350,19 +365,16 @@ exports.cleanVm = async function cleanVm(
return
}
let isBackupComplete
const { mode } = metadata
if (mode === 'full') {
const linkedXva = resolve('/', vmDir, metadata.xva)
if (xvas.has(linkedXva)) {
isBackupComplete = xvas.has(linkedXva)
if (isBackupComplete) {
unusedXvas.delete(linkedXva)
} else {
logWarn('the XVA linked to the backup is missing', { backup: json, xva: linkedXva })
if (remove) {
logInfo('deleting incomplete backup', { path: json })
jsons.delete(json)
mustInvalidateCache = true
await handler.unlink(json)
}
}
} else if (mode === 'delta') {
const linkedVhds = (() => {
@@ -371,22 +383,28 @@ exports.cleanVm = async function cleanVm(
})()
const missingVhds = linkedVhds.filter(_ => !vhds.has(_))
isBackupComplete = missingVhds.length === 0
// FIXME: find better approach by keeping as much of the backup as
// possible (existing disks) even if one disk is missing
if (missingVhds.length === 0) {
if (isBackupComplete) {
linkedVhds.forEach(_ => unusedVhds.delete(_))
linkedVhds.forEach(path => {
vhdsToJSons[path] = json
})
} else {
logWarn('some VHDs linked to the backup are missing', { backup: json, missingVhds })
if (remove) {
logInfo('deleting incomplete backup', { path: json })
mustInvalidateCache = true
jsons.delete(json)
await handler.unlink(json)
}
}
}
if (isBackupComplete) {
backups.set(json, metadata)
} else {
jsons.delete(json)
if (remove) {
logInfo('deleting incomplete backup', { backup: json })
mustRegenerateCache = true
await handler.unlink(json)
}
}
})
@@ -496,7 +514,7 @@ exports.cleanVm = async function cleanVm(
// check for the other that the size is the same as the real file size
await asyncMap(jsons, async metadataPath => {
const metadata = JSON.parse(await handler.readFile(metadataPath))
const metadata = backups.get(metadataPath)
let fileSystemSize
const merged = metadataWithMergedVhd[metadataPath] !== undefined
@@ -523,7 +541,8 @@ exports.cleanVm = async function cleanVm(
// don't warn if the size has changed after a merge
if (!merged && fileSystemSize !== size) {
logWarn('incorrect backup size in metadata', {
// FIXME: figure out why it occurs so often and, once fixed, log the real problems with `logWarn`
console.warn('cleanVm: incorrect backup size in metadata', {
path: metadataPath,
actual: size ?? 'none',
expected: fileSystemSize,
@@ -538,6 +557,7 @@ exports.cleanVm = async function cleanVm(
// systematically update size after a merge
if ((merged || fixMetadata) && size !== fileSystemSize) {
metadata.size = fileSystemSize
mustRegenerateCache = true
try {
await handler.writeFile(metadataPath, JSON.stringify(metadata), { flags: 'w' })
} catch (error) {
@@ -546,9 +566,16 @@ exports.cleanVm = async function cleanVm(
}
})
// purge cache if a metadata file has been deleted
if (mustInvalidateCache) {
await handler.unlink(vmDir + '/cache.json.gz')
if (mustRegenerateCache) {
const cache = {}
for (const [path, content] of backups.entries()) {
cache[path] = {
_filename: path,
id: path,
...content,
}
}
await this._writeCache(cachePath, cache)
}
return {

View File

@@ -31,7 +31,7 @@ beforeEach(async () => {
})
afterEach(async () => {
await pFromCallback(cb => rimraf(tempDir, cb))
await rimraf(tempDir)
await handler.forget()
})
@@ -221,7 +221,7 @@ test('it merges delta of non destroyed chain', async () => {
loggued.push(message)
}
await adapter.cleanVm(rootPath, { remove: true, logInfo, logWarn: logInfo, lock: false })
assert.equal(loggued[0], `incorrect backup size in metadata`)
assert.equal(loggued[0], `unexpected number of entries in backup cache`)
loggued = []
await adapter.cleanVm(rootPath, { remove: true, merge: true, logInfo, logWarn: () => {}, lock: false })
@@ -378,7 +378,19 @@ describe('tests multiple combination ', () => {
],
})
)
if (!useAlias && vhdMode === 'directory') {
try {
await adapter.cleanVm(rootPath, { remove: true, merge: true, logWarn: () => {}, lock: false })
} catch (err) {
assert.strictEqual(
err.code,
'NOT_SUPPORTED',
'Merging directory without alias should raise a not supported error'
)
return
}
assert.strictEqual(true, false, 'Merging directory without alias should raise an error')
}
await adapter.cleanVm(rootPath, { remove: true, merge: true, logWarn: () => {}, lock: false })
const metadata = JSON.parse(await handler.readFile(`${rootPath}/metadata.json`))

View File

@@ -0,0 +1,17 @@
'use strict'
const { pipeline } = require('node:stream')
const { ThrottleGroup } = require('@kldzj/stream-throttle')
const identity = require('lodash/identity.js')
const noop = Function.prototype
module.exports = function createStreamThrottle(rate) {
if (rate === 0) {
return identity
}
const group = new ThrottleGroup({ rate })
return function throttleStream(stream) {
return pipeline(stream, group.createThrottle(), noop)
}
}

View File

@@ -12,7 +12,7 @@ const { defer } = require('golike-defer')
const { cancelableMap } = require('./_cancelableMap.js')
const { Task } = require('./Task.js')
const { pick } = require('lodash')
const pick = require('lodash/pick.js')
const TAG_BASE_DELTA = 'xo:base_delta'
exports.TAG_BASE_DELTA = TAG_BASE_DELTA
@@ -258,6 +258,9 @@ exports.importDeltaVm = defer(async function importDeltaVm(
$defer.onFailure(() => newVdi.$destroy())
await newVdi.update_other_config(TAG_COPY_SRC, vdi.uuid)
if (vdi.virtual_size > newVdi.virtual_size) {
await newVdi.$callAsync('resize', vdi.virtual_size)
}
} else if (vdiRef === vmRecord.suspend_VDI) {
// suspendVDI has already created
newVdi = suspendVdi

View File

@@ -1,7 +1,6 @@
'use strict'
const eos = require('end-of-stream')
const { PassThrough } = require('stream')
const { finished, PassThrough } = require('node:stream')
const { debug } = require('@xen-orchestra/log').createLogger('xo:backups:forkStreamUnpipe')
@@ -9,29 +8,29 @@ const { debug } = require('@xen-orchestra/log').createLogger('xo:backups:forkStr
//
// in case of error in the new readable stream, it will simply be unpiped
// from the original one
exports.forkStreamUnpipe = function forkStreamUnpipe(stream) {
const { forks = 0 } = stream
stream.forks = forks + 1
exports.forkStreamUnpipe = function forkStreamUnpipe(source) {
const { forks = 0 } = source
source.forks = forks + 1
debug('forking', { forks: stream.forks })
debug('forking', { forks: source.forks })
const proxy = new PassThrough()
stream.pipe(proxy)
eos(stream, error => {
const fork = new PassThrough()
source.pipe(fork)
finished(source, { writable: false }, error => {
if (error !== undefined) {
debug('error on original stream, destroying fork', { error })
proxy.destroy(error)
fork.destroy(error)
}
})
eos(proxy, error => {
debug('end of stream, unpiping', { error, forks: --stream.forks })
finished(fork, { readable: false }, error => {
debug('end of stream, unpiping', { error, forks: --source.forks })
stream.unpipe(proxy)
source.unpipe(fork)
if (stream.forks === 0) {
if (source.forks === 0) {
debug('no more forks, destroying original stream')
stream.destroy(new Error('no more consumers for this stream'))
source.destroy(new Error('no more consumers for this stream'))
}
})
return proxy
return fork
}

View File

@@ -4,7 +4,7 @@
'use strict'
const { catchGlobalErrors } = require('@xen-orchestra/log/configure.js')
const { catchGlobalErrors } = require('@xen-orchestra/log/configure')
const { createLogger } = require('@xen-orchestra/log')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { join } = require('path')

View File

@@ -8,7 +8,7 @@
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"version": "0.29.0",
"version": "0.32.0",
"engines": {
"node": ">=14.6"
},
@@ -17,42 +17,42 @@
"test": "node--test"
},
"dependencies": {
"@kldzj/stream-throttle": "^1.1.1",
"@vates/async-each": "^1.0.0",
"@vates/cached-dns.lookup": "^1.0.0",
"@vates/compose": "^2.1.0",
"@vates/decorate-with": "^2.0.0",
"@vates/disposable": "^0.1.2",
"@vates/disposable": "^0.1.4",
"@vates/fuse-vhd": "^1.0.0",
"@vates/nbd-client": "*",
"@vates/nbd-client": "^1.0.1",
"@vates/parse-duration": "^0.1.1",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/fs": "^3.2.0",
"@xen-orchestra/log": "^0.4.0",
"@xen-orchestra/fs": "^3.3.2",
"@xen-orchestra/log": "^0.6.0",
"@xen-orchestra/template": "^0.1.0",
"compare-versions": "^5.0.1",
"d3-time-format": "^3.0.0",
"decorator-synchronized": "^0.6.0",
"end-of-stream": "^1.4.4",
"fs-extra": "^10.0.0",
"fs-extra": "^11.1.0",
"golike-defer": "^0.5.1",
"limit-concurrency-decorator": "^0.5.0",
"lodash": "^4.17.20",
"node-zone": "^0.4.0",
"parse-pairs": "^1.1.0",
"parse-pairs": "^2.0.0",
"promise-toolbox": "^0.21.0",
"proper-lockfile": "^4.1.2",
"uuid": "^9.0.0",
"vhd-lib": "^4.1.1",
"vhd-lib": "^4.2.1",
"yazl": "^2.5.1"
},
"devDependencies": {
"rimraf": "^3.0.2",
"sinon": "^14.0.1",
"rimraf": "^4.1.1",
"sinon": "^15.0.1",
"test": "^3.2.1",
"tmp": "^0.2.1"
},
"peerDependencies": {
"@xen-orchestra/xapi": "^1.5.2"
"@xen-orchestra/xapi": "^2.0.0"
},
"license": "AGPL-3.0-or-later",
"author": {

View File

@@ -12,7 +12,7 @@ exports.runBackupWorker = function runBackupWorker(params, onLog) {
return new Promise((resolve, reject) => {
const worker = fork(PATH)
worker.on('exit', code => reject(new Error(`worker exited with code ${code}`)))
worker.on('exit', (code, signal) => reject(new Error(`worker exited with code ${code} and signal ${signal}`)))
worker.on('error', reject)
worker.on('message', message => {

View File

@@ -7,11 +7,12 @@ const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { asyncMap } = require('@xen-orchestra/async-map')
const { chainVhd, checkVhdChain, openVhd, VhdAbstract } = require('vhd-lib')
const { createLogger } = require('@xen-orchestra/log')
const { decorateClass } = require('@vates/decorate-with')
const { defer } = require('golike-defer')
const { dirname } = require('path')
const { formatFilenameDate } = require('../_filenameDate.js')
const { getOldEntries } = require('../_getOldEntries.js')
const { getVmBackupDir } = require('../_getVmBackupDir.js')
const { Task } = require('../Task.js')
const { MixinBackupWriter } = require('./_MixinBackupWriter.js')
@@ -21,16 +22,15 @@ const { packUuid } = require('./_packUuid.js')
const { Disposable } = require('promise-toolbox')
const NbdClient = require('@vates/nbd-client')
const { debug, warn } = createLogger('xo:backups:DeltaBackupWriter')
const { debug, warn, info } = createLogger('xo:backups:DeltaBackupWriter')
exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
async checkBaseVdis(baseUuidToSrcVdi) {
const { handler } = this._adapter
const backup = this._backup
const adapter = this._adapter
const backupDir = getVmBackupDir(backup.vm.uuid)
const vdisDir = `${backupDir}/vdis/${backup.job.id}`
const vdisDir = `${this._vmBackupDir}/vdis/${backup.job.id}`
await asyncMap(baseUuidToSrcVdi, async ([baseUuid, srcVdi]) => {
let found = false
@@ -135,7 +135,7 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
}
}
async _transfer({ timestamp, deltaExport, sizeContainers }) {
async _transfer($defer, { timestamp, deltaExport }) {
const adapter = this._adapter
const backup = this._backup
@@ -143,7 +143,6 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
const jobId = job.id
const handler = adapter.handler
const backupDir = getVmBackupDir(vm.uuid)
// TODO: clean VM backup directory
@@ -175,9 +174,10 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
}
const { size } = await Task.run({ name: 'transfer' }, async () => {
let transferSize = 0
await Promise.all(
map(deltaExport.vdis, async (vdi, id) => {
const path = `${backupDir}/${vhds[id]}`
const path = `${this._vmBackupDir}/${vhds[id]}`
const isDelta = vdi.other_config['xo:base_delta'] !== undefined
let parentPath
@@ -203,21 +203,32 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
const vdiRef = vm.$xapi.getObject(vdi.uuid).$ref
let nbdClient
if (!this._backup.config.useNbd) {
if (this._backup.config.useNbd && adapter.useVhdDirectory()) {
debug('useNbd is enabled', { vdi: id, path })
// get nbd if possible
try {
// this will always take the first host in the list
const [nbdInfo] = await vm.$xapi.call('VDI.get_nbd_info', vdiRef)
debug('got NBD info', { nbdInfo, vdi: id, path })
nbdClient = new NbdClient(nbdInfo)
await nbdClient.connect()
debug(`got nbd connection `, { vdi: vdi.uuid })
// this will inform the xapi that we don't need this anymore
// and will detach the vdi from dom0
$defer(() => nbdClient.disconnect())
info('NBD client ready', { vdi: id, path })
Task.info('NBD used')
} catch (error) {
Task.warning('NBD configured but unusable', { error })
nbdClient = undefined
debug(`can't connect to nbd server or no server available`, { error })
warn('error connecting to NBD server', { error, vdi: id, path })
}
} else {
debug('useNbd is disabled', { vdi: id, path })
}
await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
transferSize += await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
// no checksum for VHDs, because they will be invalidated by
// merges and chainings
checksum: false,
@@ -238,9 +249,7 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
})
})
)
return {
size: Object.values(sizeContainers).reduce((sum, { size }) => sum + size, 0),
}
return { size: transferSize }
})
metadataContent.size = size
this._metadataFileName = await adapter.writeVmBackupMetadata(vm.uuid, metadataContent)
@@ -248,3 +257,6 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
// TODO: run cleanup?
}
}
exports.DeltaBackupWriter = decorateClass(DeltaBackupWriter, {
_transfer: defer,
})

View File

@@ -80,6 +80,7 @@ exports.DeltaReplicationWriter = class DeltaReplicationWriter extends MixinRepli
}
async _transfer({ timestamp, deltaExport, sizeContainers }) {
const { _warmMigration } = this._settings
const sr = this._sr
const { job, scheduleId, vm } = this._backup
@@ -92,7 +93,7 @@ exports.DeltaReplicationWriter = class DeltaReplicationWriter extends MixinRepli
__proto__: deltaExport,
vm: {
...deltaExport.vm,
tags: [...deltaExport.vm.tags, 'Continuous Replication'],
tags: _warmMigration ? deltaExport.vm.tags : [...deltaExport.vm.tags, 'Continuous Replication'],
},
},
sr
@@ -101,11 +102,13 @@ exports.DeltaReplicationWriter = class DeltaReplicationWriter extends MixinRepli
size: Object.values(sizeContainers).reduce((sum, { size }) => sum + size, 0),
}
})
this._targetVmRef = targetVmRef
const targetVm = await xapi.getRecord('VM', targetVmRef)
await Promise.all([
targetVm.ha_restart_priority !== '' &&
// warm migration does not disable HA , since the goal is to start the new VM in production
!_warmMigration &&
targetVm.ha_restart_priority !== '' &&
Promise.all([targetVm.set_ha_restart_priority(''), targetVm.add_tags('HA disabled')]),
targetVm.set_name_label(`${vm.name_label} - ${job.name} - (${formatFilenameDate(timestamp)})`),
asyncMap(['start', 'start_on'], op =>

View File

@@ -2,7 +2,6 @@
const { formatFilenameDate } = require('../_filenameDate.js')
const { getOldEntries } = require('../_getOldEntries.js')
const { getVmBackupDir } = require('../_getVmBackupDir.js')
const { Task } = require('../Task.js')
const { MixinBackupWriter } = require('./_MixinBackupWriter.js')
@@ -34,7 +33,6 @@ exports.FullBackupWriter = class FullBackupWriter extends MixinBackupWriter(Abst
const { job, scheduleId, vm } = backup
const adapter = this._adapter
const backupDir = getVmBackupDir(vm.uuid)
// TODO: clean VM backup directory
@@ -47,7 +45,7 @@ exports.FullBackupWriter = class FullBackupWriter extends MixinBackupWriter(Abst
const basename = formatFilenameDate(timestamp)
const dataBasename = basename + '.xva'
const dataFilename = backupDir + '/' + dataBasename
const dataFilename = this._vmBackupDir + '/' + dataBasename
const metadata = {
jobId: job.id,

View File

@@ -46,7 +46,7 @@ exports.FullReplicationWriter = class FullReplicationWriter extends MixinReplica
const oldVms = getOldEntries(settings.copyRetention - 1, listReplicatedVms(xapi, scheduleId, srUuid, vm.uuid))
const deleteOldBackups = () => asyncMapSettled(oldVms, vm => xapi.VM_destroy(vm.$ref))
const { deleteFirst } = settings
const { deleteFirst, _warmMigration } = settings
if (deleteFirst) {
await deleteOldBackups()
}
@@ -55,14 +55,18 @@ exports.FullReplicationWriter = class FullReplicationWriter extends MixinReplica
await Task.run({ name: 'transfer' }, async () => {
targetVmRef = await xapi.VM_import(stream, sr.$ref, vm =>
Promise.all([
vm.add_tags('Disaster Recovery'),
vm.ha_restart_priority !== '' && Promise.all([vm.set_ha_restart_priority(''), vm.add_tags('HA disabled')]),
!_warmMigration && vm.add_tags('Disaster Recovery'),
// warm migration does not disable HA , since the goal is to start the new VM in production
!_warmMigration &&
vm.ha_restart_priority !== '' &&
Promise.all([vm.set_ha_restart_priority(''), vm.add_tags('HA disabled')]),
vm.set_name_label(`${vm.name_label} - ${job.name} - (${formatFilenameDate(timestamp)})`),
])
)
return { size: sizeContainer.size }
})
this._targetVmRef = targetVmRef
const targetVm = await xapi.getRecord('VM', targetVmRef)
await Promise.all([

View File

@@ -16,7 +16,6 @@ const { info, warn } = createLogger('xo:backups:MixinBackupWriter')
exports.MixinBackupWriter = (BaseClass = Object) =>
class MixinBackupWriter extends BaseClass {
#lock
#vmBackupDir
constructor({ remoteId, ...rest }) {
super(rest)
@@ -24,13 +23,13 @@ exports.MixinBackupWriter = (BaseClass = Object) =>
this._adapter = rest.backup.remoteAdapters[remoteId]
this._remoteId = remoteId
this.#vmBackupDir = getVmBackupDir(this._backup.vm.uuid)
this._vmBackupDir = getVmBackupDir(this._backup.vm.uuid)
}
async _cleanVm(options) {
try {
return await Task.run({ name: 'clean-vm' }, () => {
return this._adapter.cleanVm(this.#vmBackupDir, {
return this._adapter.cleanVm(this._vmBackupDir, {
...options,
fixMetadata: true,
logInfo: info,
@@ -50,7 +49,7 @@ exports.MixinBackupWriter = (BaseClass = Object) =>
async beforeBackup() {
const { handler } = this._adapter
const vmBackupDir = this.#vmBackupDir
const vmBackupDir = this._vmBackupDir
await handler.mktree(vmBackupDir)
this.#lock = await handler.lock(vmBackupDir)
}

View File

@@ -1,5 +1,17 @@
'use strict'
const { Task } = require('../Task')
const assert = require('node:assert/strict')
const { HealthCheckVmBackup } = require('../HealthCheckVmBackup')
function extractOpaqueRef(str) {
const OPAQUE_REF_RE = /OpaqueRef:[0-9a-z-]+/
const matches = OPAQUE_REF_RE.exec(str)
if (!matches) {
throw new Error('no opaque ref found')
}
return matches[0]
}
exports.MixinReplicationWriter = (BaseClass = Object) =>
class MixinReplicationWriter extends BaseClass {
constructor({ sr, ...rest }) {
@@ -7,4 +19,32 @@ exports.MixinReplicationWriter = (BaseClass = Object) =>
this._sr = sr
}
healthCheck(sr) {
assert.notEqual(this._targetVmRef, undefined, 'A vm should have been transfered to be health checked')
// copy VM
return Task.run(
{
name: 'health check',
},
async () => {
const { $xapi: xapi } = sr
let clonedVm
try {
const baseVm = xapi.getObject(this._targetVmRef) ?? (await xapi.waitObject(this._targetVmRef))
const clonedRef = await xapi
.callAsync('VM.clone', this._targetVmRef, `Health Check - ${baseVm.name_label}`)
.then(extractOpaqueRef)
clonedVm = xapi.getObject(clonedRef) ?? (await xapi.waitObject(clonedRef))
await new HealthCheckVmBackup({
restoredVm: clonedVm,
xapi,
}).run()
} finally {
clonedVm && (await xapi.VM_destroy(clonedVm.$ref))
}
}
)
}
}

View File

@@ -8,8 +8,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/cr-seed-cli):
```
> npm install --global @xen-orchestra/cr-seed-cli
```sh
npm install --global @xen-orchestra/cr-seed-cli
```
## Contributions

View File

@@ -18,7 +18,7 @@
"preferGlobal": true,
"dependencies": {
"golike-defer": "^0.5.1",
"xen-api": "^1.2.2"
"xen-api": "^1.2.7"
},
"scripts": {
"postversion": "npm publish"

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/cron):
```
> npm install --save @xen-orchestra/cron
```sh
npm install --save @xen-orchestra/cron
```
## Usage

View File

@@ -42,7 +42,7 @@
"test": "node--test"
},
"devDependencies": {
"sinon": "^14.0.1",
"sinon": "^15.0.1",
"test": "^3.2.1"
}
}

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/defined):
```
> npm install --save @xen-orchestra/defined
```sh
npm install --save @xen-orchestra/defined
```
## Contributions

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/emit-async):
```
> npm install --save @xen-orchestra/emit-async
```sh
npm install --save @xen-orchestra/emit-async
```
## Usage

View File

@@ -10,8 +10,8 @@
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/fs):
```
> npm install --global @xen-orchestra/fs
```sh
npm install --global @xen-orchestra/fs
```
## Contributions

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "@xen-orchestra/fs",
"version": "3.2.0",
"version": "3.3.2",
"license": "AGPL-3.0-or-later",
"description": "The File System for Xen Orchestra backups.",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/fs",
@@ -20,6 +20,7 @@
"node": ">=14.13"
},
"dependencies": {
"@aws-sdk/abort-controller": "^3.272.0",
"@aws-sdk/client-s3": "^3.54.0",
"@aws-sdk/lib-storage": "^3.54.0",
"@aws-sdk/middleware-apply-body-checksum": "^3.58.0",
@@ -30,11 +31,11 @@
"@vates/decorate-with": "^2.0.0",
"@vates/read-chunk": "^1.0.1",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/log": "^0.4.0",
"@xen-orchestra/log": "^0.6.0",
"bind-property-descriptor": "^2.0.0",
"decorator-synchronized": "^0.6.0",
"execa": "^5.0.0",
"fs-extra": "^10.0.0",
"fs-extra": "^11.1.0",
"get-stream": "^6.0.0",
"limit-concurrency-decorator": "^0.5.0",
"lodash": "^4.17.4",
@@ -50,10 +51,9 @@
"@babel/plugin-proposal-decorators": "^7.1.6",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/preset-env": "^7.8.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^7.0.2",
"dotenv": "^16.0.0",
"rimraf": "^3.0.0",
"rimraf": "^4.1.1",
"tmp": "^0.2.1"
},
"scripts": {

View File

@@ -14,7 +14,7 @@ import { basename, dirname, normalize as normalizePath } from './path'
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
import { DEFAULT_ENCRYPTION_ALGORITHM, _getEncryptor } from './_encryptor'
const { info, warn } = createLogger('@xen-orchestra:fs')
const { info, warn } = createLogger('xo:fs:abstract')
const checksumFile = file => file + '.checksum'
const computeRate = (hrtime, size) => {

View File

@@ -116,7 +116,7 @@ describe('encryption', () => {
dir = await pFromCallback(cb => tmp.dir(cb))
})
afterAll(async () => {
await pFromCallback(cb => rimraf(dir, cb))
await rimraf(dir)
})
it('sync should NOT create metadata if missing (not encrypted)', async () => {

View File

@@ -1,7 +1,7 @@
import through2 from 'through2'
import { createHash } from 'crypto'
import { defer, fromEvent } from 'promise-toolbox'
import { invert } from 'lodash'
import invert from 'lodash/invert.js'
// Format: $<algorithm>$<salt>$<encrypted>
//

View File

@@ -19,7 +19,12 @@ async function addSyncStackTrace(fn, ...args) {
try {
return await fn.apply(this, args)
} catch (error) {
error.syncStack = stackContainer.stack
let { stack } = stackContainer
// remove first line which does not contain stack information, simply `Error`
stack = stack.slice(stack.indexOf('\n') + 1)
error.stack = [error.stack, 'From:', stack].join('\n')
throw error
}
}

View File

@@ -6,6 +6,15 @@
- Settings page (PR [#6418](https://github.com/vatesfr/xen-orchestra/pull/6418))
- Uncollapse hosts in the tree by default (PR [#6428](https://github.com/vatesfr/xen-orchestra/pull/6428))
- Display RAM usage in pool dashboard (PR [#6419](https://github.com/vatesfr/xen-orchestra/pull/6419))
- Implement not found page (PR [#6410](https://github.com/vatesfr/xen-orchestra/pull/6410))
- Display CPU usage chart in pool dashboard (PR [#6577](https://github.com/vatesfr/xen-orchestra/pull/6577))
- Display network throughput chart in pool dashboard (PR [#6610](https://github.com/vatesfr/xen-orchestra/pull/6610))
- Display RAM usage chart in pool dashboard (PR [#6604](https://github.com/vatesfr/xen-orchestra/pull/6604))
- Ability to change the state of a VM (PRs [#6571](https://github.com/vatesfr/xen-orchestra/pull/6571) [#6608](https://github.com/vatesfr/xen-orchestra/pull/6608))
- Display CPU provisioning in pool dashboard (PR [#6601](https://github.com/vatesfr/xen-orchestra/pull/6601))
- Add a star icon near the pool master (PR [#6712](https://github.com/vatesfr/xen-orchestra/pull/6712))
- Display an error message if the data cannot be fetched (PR [#6525](https://github.com/vatesfr/xen-orchestra/pull/6525))
- Add "Under Construction" views (PR [#6673](https://github.com/vatesfr/xen-orchestra/pull/6673))
## **0.1.0**

View File

@@ -105,7 +105,7 @@ Use the `busy` prop to display a loader icon.
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/UiIcon.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { faDisplay } from "@fortawesome/free-solid-svg-icons";
</script>
```

View File

@@ -0,0 +1,406 @@
<!-- TOC -->
- [Component Stories](#component-stories)
- [How to create a story](#how-to-create-a-story)
- [How to write a story](#how-to-write-a-story)
_ [Example](#example)
_ [Props](#props)
_ [Required prop](#required-prop)
_ [Prop type](#prop-type)
_ [String](#string)
_ [Number](#number)
_ [Boolean](#boolean)
_ [Array](#array)
_ [Object](#object)
_ [Enum](#enum)
_ [Any](#any)
_ [Custom type](#custom-type)
_ [Prop widget](#prop-widget)
_ [Text](#text)
_ [Number](#number-1)
_ [Object](#object-1)
_ [Choice](#choice)
_ [Boolean](#boolean-1)
_ [Prop default](#prop-default)
_ [Prop preset](#prop-preset)
_ [Prop help](#prop-help)
_ [Events](#events)
_ [Event with no arguments](#event-with-no-arguments)
_ [Event with arguments](#event-with-arguments)
_ [Custom function](#custom-function)
_ [Event type](#event-type)
_ [Models](#models)
_ [Default model](#default-model)
_ [Custom model](#custom-model)
_ [Configure the underlying prop and event](#configure-the-underlying-prop-and-event)
_ [Model type](#model-type)
_ [Model help](#model-help)
_ [Slots](#slots)
_ [Default slot](#default-slot)
_ [Named slot](#named-slot)
_ [Scoped slot (slot with props)](#scoped-slot--slot-with-props-)
_ [Slot help](#slot-help)
_ [Settings](#settings)
<!-- TOC -->
# Component Stories
The `ComponentStory` component allows you to document your components and their props, events and slots.
It takes a `params` prop which is an array of configuration items.
You can configure props, events, models, slots and settings.
Props, Events and Models will be added to the `properties` slot prop.
Slots are only for documentation purpose.
Settings will be added to the `settings` slot prop.
## How to create a story
1. Create a new story component in the `src/stories` directory (ie. `my-component.story.vue`).
2. To document your component, create the same file with the `.md` extension (ie. `my-component.story.md`).
## How to write a story
In your `.story.vue` file, import and use the `ComponentStory` component.
```vue
<template>
<ComponentStory
v-slot="{ properties, settings }"
:params="[
prop(...),
event(...),
model(...),
slot(...),
setting(...),
]"
>
<MyComponent v-bind="properties">
{{ settings.label }}
</MyComponent>
</ComponentStory>
</template>
<script lang="ts" setup>
import MyComponent from "@/components/MyComponent.vue";
import ComponentStory from "@/components/component-story/ComponentStory.vue";
import { prop, event, model, slot, setting } from "@/libs/story/story-param";
</script>
```
### Example
Let's take this Vue component:
```vue
<template>
<div>
<div>Required string prop: {{ imString }}</div>
<div>Required number prop: {{ imNumber }}</div>
<div v-if="imOptional">Optional prop: {{ imOptional }}</div>
<div>Optional prop with default: {{ imOptionalWithDefault }}</div>
<button @click="handleClick">Click me</button>
<button @click="handleClickWithArg('some-id')">Click me with an id</button>
<div>
<slot />
</div>
<div>
<slot name="named-slot" />
</div>
<div>
<slot :moon-distance="moonDistance" name="named-scoped-slot" />
</div>
</div>
</template>
<script lang="ts" setup>
withDefaults(
defineProps<{
imString: string;
imNumber: number;
imOptional?: string;
imOptionalWithDefault?: string;
modelValue?: string;
customModel?: number;
}>(),
{ imOptionalWithDefault: "Hi World" }
);
const emit = defineEmits<{
(event: "click"): void;
(event: "clickWithArg", id: string): void;
(event: "update:modelValue", value: string): void;
(event: "update:customModel", value: number): void;
}>();
const moonDistance = 384400;
const handleClick = () => emit("click");
const handleClickWithArg = (id: string) => emit("clickWithArg", id);
</script>
```
Here is how to document it with a Component Story:
```vue
<template>
<ComponentStory
v-slot="{ properties, settings }"
:params="[
prop('imString')
.str()
.required()
.preset('Example')
.widget()
.help('This is a required string prop'),
prop('imNumber')
.num()
.required()
.preset(42)
.widget()
.help('This is a required number prop'),
prop('imOptional').str().widget().help('This is an optional string prop'),
prop('imOptionalWithDefault')
.str()
.default('Hi World')
.widget()
.default('My default value'),
model().prop((p) => p.str()),
model('customModel').prop((p) => p.num()),
event('click').help('Emitted when the user clicks the first button'),
event('clickWithArg')
.args({ id: 'string' })
.help('Emitted when the user clicks the second button'),
slot().help('This is the default slot'),
slot('namedSlot').help('This is a named slot'),
slot('namedScopedSlot')
.prop('moon-distance', 'number')
.help('This is a named slot'),
setting('contentExample').widget(text()).preset('Some content'),
]"
>
<MyComponent v-bind="properties">
{{ settings.contentExample }}
<template #named-slot>Named slot content</template>
<template #named-scoped-slot="{ moonDistance }">
Moon distance is {{ moonDistance }} meters.
</template>
</MyComponent>
</ComponentStory>
</template>
<script lang="ts" setup>
import ComponentStory from "@/components/component-story/ComponentStory.vue";
import MyComponent from "@/components/MyComponent.vue";
import { event, model, prop, setting, slot } from "@/libs/story/story-param";
import { text } from "@/libs/story/story-widget";
</script>
```
### Props
Use the `prop(name: string)` function to document a prop.
It will appear on the **Props** tab.
#### Required prop
If the prop is required, use the `required()` function.
`prop('title').required()`
#### Prop type
You can set the type of the prop with the `str()`, `num()`, `bool()`, `arr()`, `obj()`, `enum()` and `any()` functions.
The type can also be detected automatically if a [preset](#prop-preset) value is defined.
##### String
`prop('title').str()`: `string`
##### Number
`prop('count').num()`: `number`
##### Boolean
`prop('disabled').bool()`: `boolean`
##### Array
`prop('items').arr()`: `any[]`
`prop('items').arr('string')`: `string[]`
##### Object
`prop('user').obj()`: `object`
`prop('user').obj('{ name: string, age: number }')`: `{ name: string; age: number; }`
##### Enum
`prop('color').enum('red', 'green', 'blue')`: `"red" | "green" | "blue"`
##### Any
`prop('color').any()`: `any`
##### Custom type
`prop('user').type('User')`: `User`
#### Prop widget
When the prop type is defined, the widget is automatically detected.
`prop('title').str().widget()`
But you can also define the widget manually.
##### Text
`prop('...').widget(text())`
##### Number
`prop('...').widget(number())`
##### Object
`prop('...').widget(object())`
##### Choice
`prop('...').widget(choice('red', 'green', 'blue'))`
##### Boolean
`prop('title').widget(boolean())`
#### Prop default
This documents the default value of the prop, which is applied when the prop is not defined.
`prop('color').default('blue')`
#### Prop preset
This allows to preset a prop value for this story.
`prop('color').preset('red')`
#### Prop help
This allows to add a help text for this prop.
`prop('color').help('This is the component text color')`
### Events
Use the `event(name: string)` function to document an event.
It will appear in the **Events** tab.
When triggered, this event will be logged to the `Logs` card.
#### Event with no arguments
`event('edit')`: `() => void`
#### Event with arguments
`event('delete').args({ id: 'string' })`: `(id: string) => void`
#### Custom function
If needed, thanks to the `preset` method, you can attach a custom function to your event.
`const debug = (id: string) => console.log(id);`
`event('my-event').args({ id: 'string' }).preset(debug)`
#### Event type
The event type is automatically generated from the arguments.
You can override it with the `type()` method.
#### Event help
This allows to add a help text for this event.
`event('close').help('Called when user clicks the close icon or on the background')`
### Models
Use the `model(name = "model-value")` function to document a model.
Calling `model("foo")` is kind of equivalent to calling `prop("foo")` + `event("update:foo")`.
#### Default model
`model()` with no argument will create a `model-value` prop and a `update:model-value` event.
#### Custom model
`model('foo')` will create a `foo` prop and a `update:foo` event.
#### Configure the underlying prop and event
You can use `.prop((p) => ...)` and `.event((e) => ...)` methods to access the underlying prop and event respectively
then use any of the [prop](#props) and [event](#events) methods.
`model().event((e) => e.help('Help for update:modelValue event'))`
#### Model type
`.type(type: string)` function is a shortcut for `.prop((p) => p.type(...))`
#### Model help
Using `.help(text: string)` function is a shortcut for `.prop((p) => p.help(...))`
### Slots
Use the `slot(name = "default")` function to document a slot.
#### Default slot
`slot()`
=> `<slot />`
#### Named slot
`slot('header')`
=> `<slot name="header" />`
#### Scoped slot (slot with props)
`slot('footer').prop('color', 'string').prop('count', 'number')`
#### Slot help
`slot('footer').help('This is the footer slot')`
### Settings
Use the `setting(name: string)` to configure your Story with arbitrary settings.
They will not be passed automatically to your component, but you can access them in your template with the `settings` variable.
For example:
```vue
<template>
<ComponentStory v-slot="{ settings }" :params="[setting('label').widget()]">
<button>{{ settings.label }}</button>
</ComponentStory>
</template>
```

View File

@@ -1,5 +1,6 @@
/// <reference types="vite/client" />
/// <reference types="json-rpc-2.0/dist" />
/// <reference types="vite-plugin-pages/client" />
declare const XO_LITE_VERSION: string;
declare const XO_LITE_GIT_HEAD: string;

View File

@@ -24,12 +24,14 @@
"d3-time-format": "^4.1.0",
"decorator-synchronized": "^0.6.0",
"echarts": "^5.3.3",
"highlight.js": "^11.6.0",
"human-format": "^1.0.0",
"json-rpc-2.0": "^1.3.0",
"json5": "^2.2.1",
"limit-concurrency-decorator": "^0.5.0",
"lodash-es": "^4.17.21",
"make-error": "^1.3.6",
"markdown-it": "^13.0.1",
"pinia": "^2.0.14",
"placement.js": "^1.0.0-beta.5",
"vue": "^3.2.37",
@@ -49,9 +51,11 @@
"eslint-plugin-vue": "^9.0.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.19",
"postcss-custom-media": "^9.0.1",
"postcss-nested": "^6.0.0",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vite-plugin-pages": "^0.27.1",
"vue-tsc": "^1.0.9"
},
"private": true,

View File

@@ -1,5 +1,6 @@
module.exports = {
plugins: {
"postcss-nested": {},
"postcss-custom-media": {},
},
};

View File

@@ -1,8 +1,8 @@
<template>
<UiModal
v-if="isSslModalOpen"
color="error"
:icon="faServer"
color="error"
@close="clearUnreachableHostsUrls"
>
<template #title>{{ $t("unreachable-hosts") }}</template>
@@ -10,19 +10,23 @@
<p>{{ $t("allow-self-signed-ssl") }}</p>
<ul>
<li v-for="url in unreachableHostsUrls" :key="url.hostname">
<a :href="url.href" target="_blank" rel="noopener">{{ url.href }}</a>
<a :href="url.href" rel="noopener" target="_blank">{{ url.href }}</a>
</li>
</ul>
<template #buttons>
<UiButton color="success" @click="reload">
{{ $t("unreachable-hosts-reload-page") }}
</UiButton>
<UiButton @click="clearUnreachableHostsUrls">{{ $t("cancel") }}</UiButton>
</template>
</UiModal>
<div v-if="!xenApiStore.isConnected">
<div v-if="!$route.meta.hasStoryNav && !xenApiStore.isConnected">
<AppLogin />
</div>
<div v-else>
<AppHeader />
<div style="display: flex">
<nav class="nav">
<InfraPoolList />
</nav>
<AppNavigation />
<main class="main">
<RouterView />
</main>
@@ -32,26 +36,30 @@
</template>
<script lang="ts" setup>
import favicon from "@/assets/favicon.svg";
import AppHeader from "@/components/AppHeader.vue";
import AppLogin from "@/components/AppLogin.vue";
import AppNavigation from "@/components/AppNavigation.vue";
import AppTooltips from "@/components/AppTooltips.vue";
import UiButton from "@/components/ui/UiButton.vue";
import UiModal from "@/components/ui/UiModal.vue";
import { useChartTheme } from "@/composables/chart-theme.composable";
import { useHostStore } from "@/stores/host.store";
import { useUiStore } from "@/stores/ui.store";
import { useXenApiStore } from "@/stores/xen-api.store";
import { faServer } from "@fortawesome/free-solid-svg-icons";
import { useActiveElement, useMagicKeys, whenever } from "@vueuse/core";
import { logicAnd } from "@vueuse/math";
import { difference } from "lodash";
import { computed, ref, watch, watchEffect } from "vue";
import favicon from "@/assets/favicon.svg";
import { faServer } from "@fortawesome/free-solid-svg-icons";
import AppHeader from "@/components/AppHeader.vue";
import AppLogin from "@/components/AppLogin.vue";
import AppTooltips from "@/components/AppTooltips.vue";
import InfraPoolList from "@/components/infra/InfraPoolList.vue";
import UiModal from "@/components/ui/UiModal.vue";
import { useChartTheme } from "@/composables/chart-theme.composable";
import { useHostStore } from "@/stores/host.store";
import { useXenApiStore } from "@/stores/xen-api.store";
import { useRoute } from "vue-router";
const unreachableHostsUrls = ref<URL[]>([]);
const clearUnreachableHostsUrls = () => (unreachableHostsUrls.value = []);
let link: HTMLLinkElement | null = document.querySelector("link[rel~='icon']");
let link = document.querySelector(
"link[rel~='icon']"
) as HTMLLinkElement | null;
if (link == null) {
link = document.createElement("link");
link.rel = "icon";
@@ -84,7 +92,13 @@ if (import.meta.env.DEV) {
);
}
const route = useRoute();
watchEffect(() => {
if (route.meta.hasStoryNav) {
return;
}
if (xenApiStore.isConnected) {
xenApiStore.init();
}
@@ -105,25 +119,18 @@ watch(
);
const isSslModalOpen = computed(() => unreachableHostsUrls.value.length > 0);
const reload = () => window.location.reload();
</script>
<style lang="postcss">
@import "@/assets/base.css";
</style>
.nav {
overflow: auto;
width: 37rem;
max-width: 37rem;
height: calc(100vh - 9rem);
padding: 0.5rem;
border-right: 1px solid var(--color-blue-scale-400);
background-color: var(--background-color-primary);
}
<style lang="postcss" scoped>
.main {
overflow: auto;
flex: 1;
height: calc(100vh - 9rem);
height: calc(100vh - 8rem);
background-color: var(--background-color-secondary);
}
</style>

View File

@@ -0,0 +1,2 @@
@custom-media --mobile (max-width: 1023px);
@custom-media --desktop (min-width: 1024px);

View File

@@ -16,8 +16,10 @@ a {
color: var(--color-extra-blue-base);
}
code {
font-family: monospace;
code,
code * {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
}
.card-view {
@@ -25,3 +27,17 @@ code {
display: flex;
gap: 2rem;
}
.link {
text-decoration: underline;
color: var(--color-extra-blue-base);
cursor: pointer;
}
.link:hover {
color: var(--color-extra-blue-d20);
}
.link:active {
color: var(--color-extra-blue-d40);
}

View File

@@ -0,0 +1,136 @@
<svg width="200" height="200" viewBox="3 2 200 200" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_ddd_1994_118844)">
<g clip-path="url(#clip0_1994_118844)">
<g filter="url(#filter1_ddd_1994_118844)">
<g clip-path="url(#clip1_1994_118844)">
<rect x="3" y="2" width="200" height="200" rx="8" fill="#F6F6F7"/>
<rect width="200" height="29" transform="translate(3 2)" fill="#F6F6F7"/>
<rect x="13" y="13" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect width="68" height="171" transform="translate(3 31)" fill="white"/>
<rect x="13" y="41" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="41" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="58" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="58" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="75" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="75" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="92" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="92" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="109" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="109" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="126" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="126" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="143" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="143" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="160" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="160" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="177" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="177" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="194" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="194" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="76" y="36" width="122" height="44" rx="4" fill="white"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="76" y="90" width="122" height="44" rx="4" fill="white"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="76" y="144" width="122" height="44" rx="4" fill="white"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="76" y="198" width="122" height="44" rx="4" fill="white"/>
</g>
</g>
</g>
<g clip-path="url(#clip2_1994_118844)">
<g filter="url(#filter2_ddd_1994_118844)">
<g clip-path="url(#clip3_1994_118844)">
<rect x="3" y="2" width="200" height="200" rx="8" fill="#17182B"/>
<rect width="200" height="29" transform="translate(3 2)" fill="#17182B"/>
<rect x="76" y="36" width="122" height="44" rx="4" fill="#14141E"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="76" y="90" width="122" height="44" rx="4" fill="#14141E"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="76" y="144" width="122" height="44" rx="4" fill="#14141E"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="76" y="198" width="122" height="44" rx="4" fill="#14141E"/>
</g>
</g>
</g>
</g>
<defs>
<filter id="filter0_ddd_1994_118844" x="0" y="0" width="206" height="206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.08 0"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.06 0"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.1 0"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1994_118844" result="shape"/>
</filter>
<filter id="filter1_ddd_1994_118844" x="0" y="0" width="206" height="206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.08 0"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.06 0"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.1 0"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1994_118844" result="shape"/>
</filter>
<filter id="filter2_ddd_1994_118844" x="0" y="0" width="206" height="206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1994_118844"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1994_118844" result="effect2_dropShadow_1994_118844"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1994_118844" result="shape"/>
</filter>
<clipPath id="clip0_1994_118844">
<rect width="100" height="200" fill="white" transform="translate(3 2)"/>
</clipPath>
<clipPath id="clip1_1994_118844">
<rect x="3" y="2" width="200" height="200" rx="8" fill="white"/>
</clipPath>
<clipPath id="clip2_1994_118844">
<rect width="100" height="200" fill="white" transform="translate(103 2)"/>
</clipPath>
<clipPath id="clip3_1994_118844">
<rect x="3" y="2" width="200" height="200" rx="8" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,70 @@
<svg width="200" height="200" viewBox="3 2 200 200" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_ddd_1994_118694)">
<g clip-path="url(#clip0_1994_118694)">
<rect x="3" y="2" width="200" height="200" rx="8" fill="#17182B"/>
<rect width="200" height="29" transform="translate(3 2)" fill="#17182B"/>
<rect x="13" y="13" width="36" height="7" rx="3" fill="#595A6F"/>
<rect width="68" height="171" transform="translate(3 31)" fill="#14141E"/>
<rect x="13" y="41" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="41" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="58" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="58" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="75" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="75" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="92" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="92" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="109" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="109" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="126" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="126" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="143" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="143" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="160" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="160" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="177" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="177" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="13" y="194" width="7" height="7" rx="3" fill="#595A6F"/>
<rect x="25" y="194" width="36" height="7" rx="3" fill="#595A6F"/>
<rect x="76" y="36" width="122" height="44" rx="4" fill="#14141E"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="76" y="90" width="122" height="44" rx="4" fill="#14141E"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="76" y="144" width="122" height="44" rx="4" fill="#14141E"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="black" fill-opacity="0.4"/>
<rect x="76" y="198" width="122" height="44" rx="4" fill="#14141E"/>
</g>
</g>
<defs>
<filter id="filter0_ddd_1994_118694" x="0" y="0" width="206" height="206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1994_118694"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1994_118694" result="effect2_dropShadow_1994_118694"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1994_118694" result="shape"/>
</filter>
<clipPath id="clip0_1994_118694">
<rect x="3" y="2" width="200" height="200" rx="8" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,70 @@
<svg width="200" height="200" viewBox="3 2 200 200" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_ddd_1994_118640)">
<g clip-path="url(#clip0_1994_118640)">
<rect x="3" y="2" width="200" height="200" rx="8" fill="#F6F6F7"/>
<rect width="200" height="29" transform="translate(3 2)" fill="#F6F6F7"/>
<rect x="13" y="13" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect width="68" height="171" transform="translate(3 31)" fill="white"/>
<rect x="13" y="41" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="41" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="58" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="58" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="75" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="75" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="92" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="92" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="109" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="109" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="126" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="126" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="143" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="143" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="160" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="160" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="177" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="177" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="13" y="194" width="7" height="7" rx="3" fill="#E5E5E7"/>
<rect x="25" y="194" width="36" height="7" rx="3" fill="#E5E5E7"/>
<rect x="76" y="36" width="122" height="44" rx="4" fill="white"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="46" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="63" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="76" y="90" width="122" height="44" rx="4" fill="white"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="100" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="117" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="76" y="144" width="122" height="44" rx="4" fill="white"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="154" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="#8F84FF"/>
<rect x="86" y="171" width="102" height="7" rx="3" fill="white" fill-opacity="0.6"/>
<rect x="76" y="198" width="122" height="44" rx="4" fill="white"/>
</g>
</g>
<defs>
<filter id="filter0_ddd_1994_118640" x="0" y="0" width="206" height="206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.08 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1994_118640"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1994_118640" result="effect2_dropShadow_1994_118640"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.101961 0 0 0 0 0.105882 0 0 0 0 0.219608 0 0 0 0.1 0"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1994_118640" result="shape"/>
</filter>
<clipPath id="clip0_1994_118640">
<rect x="3" y="2" width="200" height="200" rx="8" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,79 @@
<svg width="262" height="164" viewBox="0 0 262 164" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_814_56662)">
<path d="M249.387 164.001H28.7878C26.826 164.001 24.9446 163.222 23.5575 161.835C22.1703 160.448 21.391 158.567 21.391 156.606C101.358 147.314 179.822 147.314 256.784 156.606C256.784 158.567 256.005 160.448 254.617 161.835C253.23 163.222 251.349 164.001 249.387 164.001Z" fill="#CCCCCC"/>
<path d="M256.784 157.04L21.391 156.605L48.6722 110.711L48.8027 110.493V9.34843C48.8026 8.12075 49.0444 6.90507 49.5143 5.77082C49.9841 4.63657 50.6729 3.60597 51.5412 2.73787C52.4095 1.86976 53.4403 1.18117 54.5748 0.7114C55.7093 0.241634 56.9252 -0.000102555 58.1532 3.2638e-08H218.716C219.944 -0.000102555 221.16 0.241634 222.295 0.7114C223.429 1.18117 224.46 1.86976 225.328 2.73787C226.197 3.60597 226.885 4.63657 227.355 5.77082C227.825 6.90507 228.067 8.12075 228.067 9.34843V111.189L256.784 157.04Z" fill="#E6E6E6"/>
<path d="M57.94 6.52344C57.1325 6.52434 56.3584 6.84545 55.7874 7.41632C55.2164 7.98719 54.8952 8.7612 54.8943 9.56853V101.791C54.8952 102.599 55.2164 103.373 55.7874 103.944C56.3584 104.514 57.1325 104.835 57.94 104.836H220.235C221.042 104.835 221.817 104.514 222.387 103.943C222.958 103.373 223.28 102.599 223.281 101.791V9.56853C223.28 8.76121 222.959 7.98722 222.388 7.41636C221.817 6.8455 221.042 6.52438 220.235 6.52344H57.94Z" fill="white"/>
<path d="M59.0067 117.02C58.7572 117.02 58.5129 117.092 58.3028 117.226C58.0927 117.361 57.9255 117.553 57.8208 117.779L49.4146 136.05C49.3231 136.248 49.283 136.467 49.2982 136.685C49.3133 136.904 49.3831 137.115 49.5012 137.299C49.6193 137.483 49.7819 137.635 49.9739 137.74C50.166 137.845 50.3814 137.9 50.6003 137.9H227.378C227.6 137.9 227.819 137.843 228.014 137.735C228.208 137.627 228.371 137.47 228.488 137.281C228.605 137.092 228.672 136.876 228.682 136.654C228.692 136.431 228.645 136.21 228.545 136.011L219.408 117.741C219.3 117.524 219.134 117.341 218.927 117.214C218.721 117.086 218.483 117.019 218.241 117.02L59.0067 117.02Z" fill="#CCCCCC"/>
<path d="M138.435 5.00106C139.516 5.00106 140.393 4.12463 140.393 3.0435C140.393 1.96237 139.516 1.08594 138.435 1.08594C137.353 1.08594 136.477 1.96237 136.477 3.0435C136.477 4.12463 137.353 5.00106 138.435 5.00106Z" fill="white"/>
<path d="M121.059 141.379C120.772 141.379 120.493 141.474 120.265 141.649C120.038 141.823 119.874 142.068 119.799 142.345L117.69 150.175C117.638 150.368 117.631 150.571 117.67 150.768C117.709 150.964 117.792 151.149 117.914 151.308C118.036 151.467 118.193 151.596 118.373 151.684C118.552 151.773 118.75 151.819 118.95 151.819H159.051C159.259 151.819 159.465 151.769 159.649 151.674C159.834 151.579 159.994 151.441 160.115 151.271C160.235 151.102 160.314 150.906 160.343 150.7C160.373 150.494 160.353 150.284 160.284 150.087L157.574 142.257C157.485 142.001 157.318 141.778 157.097 141.621C156.876 141.464 156.611 141.379 156.34 141.379H121.059Z" fill="#CCCCCC"/>
<path d="M228.067 108.969V110.709H48.6722L48.8071 110.491V108.969H228.067Z" fill="#CCCCCC"/>
<path d="M205.924 112.957C205.854 112.957 205.786 112.98 205.731 113.023C205.675 113.065 205.635 113.125 205.617 113.192L205.104 115.099C205.091 115.146 205.089 115.195 205.099 115.243C205.108 115.291 205.129 115.336 205.158 115.375C205.188 115.414 205.226 115.445 205.27 115.467C205.314 115.488 205.362 115.499 205.411 115.499H215.176C215.226 115.499 215.276 115.487 215.321 115.464C215.366 115.441 215.405 115.407 215.435 115.366C215.464 115.325 215.483 115.277 215.49 115.227C215.498 115.177 215.493 115.126 215.476 115.078L214.816 113.171C214.794 113.108 214.754 113.054 214.7 113.016C214.646 112.978 214.582 112.957 214.516 112.957H205.924Z" fill="#CCCCCC"/>
<path d="M191.666 112.957C191.596 112.957 191.528 112.98 191.472 113.023C191.417 113.065 191.377 113.125 191.359 113.192L190.845 115.099C190.833 115.146 190.831 115.195 190.84 115.243C190.85 115.291 190.87 115.336 190.9 115.375C190.93 115.414 190.968 115.445 191.012 115.467C191.055 115.488 191.103 115.499 191.152 115.499H200.917C200.968 115.499 201.018 115.487 201.063 115.464C201.108 115.441 201.147 115.407 201.176 115.366C201.206 115.325 201.225 115.277 201.232 115.227C201.239 115.177 201.234 115.126 201.218 115.078L200.557 113.171C200.536 113.108 200.495 113.054 200.441 113.016C200.388 112.978 200.323 112.957 200.257 112.957H191.666Z" fill="#CCCCCC"/>
<path d="M150.356 125.071H151.121L149.591 22.793H148.825L148.856 24.8334H131.702L131.733 22.793H130.967L129.437 125.071H130.202L130.301 118.44H150.257L150.356 125.071ZM149.253 51.3595H131.305L131.492 38.8617H149.066L149.253 51.3595ZM149.264 52.1247L149.451 64.6226H131.107L131.294 52.1247H149.264ZM149.463 65.3877L149.65 77.8856H130.908L131.095 65.3877H149.463ZM149.661 78.6508L149.848 91.1486H130.71L130.897 78.6508H149.661ZM149.86 91.9138L150.047 104.412H130.511L130.698 91.9138H149.86ZM148.867 25.5986L149.054 38.0965H131.504L131.691 25.5986H148.867ZM130.313 117.675L130.5 105.177H150.058L150.245 117.675H130.313Z" fill="#3F3D56"/>
<path d="M136.488 58.0205C143.38 58.0205 148.967 52.4344 148.967 45.5435C148.967 38.6526 143.38 33.0664 136.488 33.0664C129.595 33.0664 124.008 38.6526 124.008 45.5435C124.008 52.4344 129.595 58.0205 136.488 58.0205Z" fill="#2F2E41"/>
<path d="M143.828 54.1685L140.098 54.8438L141.308 61.5265L145.038 60.8512L143.828 54.1685Z" fill="#2F2E41"/>
<path d="M136.367 55.5201L132.636 56.1953L133.846 62.878L137.577 62.2028L136.367 55.5201Z" fill="#2F2E41"/>
<path d="M135.827 64.2821C137.158 63.1546 137.895 61.836 137.472 61.3367C137.049 60.8375 135.627 61.3468 134.296 62.4742C132.964 63.6017 132.227 64.9203 132.65 65.4196C133.073 65.9188 134.495 65.4095 135.827 64.2821Z" fill="#2F2E41"/>
<path d="M143.133 62.9618C144.464 61.8343 145.201 60.5156 144.778 60.0164C144.355 59.5172 142.933 60.0264 141.601 61.1539C140.27 62.2813 139.533 63.6 139.956 64.0992C140.379 64.5985 141.801 64.0892 143.133 62.9618Z" fill="#2F2E41"/>
<path d="M135.614 46.7591C137.97 46.7591 139.879 44.8499 139.879 42.4948C139.879 40.1397 137.97 38.2305 135.614 38.2305C133.258 38.2305 131.349 40.1397 131.349 42.4948C131.349 44.8499 133.258 46.7591 135.614 46.7591Z" fill="white"/>
<path d="M133.861 42.3858C134.646 42.3858 135.283 41.7494 135.283 40.9644C135.283 40.1794 134.646 39.543 133.861 39.543C133.076 39.543 132.439 40.1794 132.439 40.9644C132.439 41.7494 133.076 42.3858 133.861 42.3858Z" fill="#3F3D56"/>
<path d="M146.333 31.97C146.52 27.3508 142.538 23.4383 137.439 23.2312C132.339 23.0241 128.053 26.6008 127.865 31.22C127.677 35.8392 131.222 36.8887 136.322 37.0958C141.422 37.303 146.145 36.5892 146.333 31.97Z" fill="#8F84FF"/>
<path d="M131.047 44.6638C131.288 43.6096 128.761 42.1308 125.402 41.3611C122.043 40.5913 119.124 40.8219 118.882 41.8762C118.641 42.9305 121.168 44.4092 124.527 45.179C127.886 45.9488 130.805 45.7181 131.047 44.6638Z" fill="#2F2E41"/>
<path d="M155.544 48.7381C155.786 47.6838 153.258 46.2051 149.899 45.4353C146.54 44.6655 143.621 44.8961 143.38 45.9504C143.138 47.0047 145.665 48.4834 149.024 49.2532C152.383 50.023 155.302 49.7924 155.544 48.7381Z" fill="#2F2E41"/>
<path d="M139.915 50.4628C139.983 50.8302 139.977 51.2073 139.898 51.5724C139.82 51.9376 139.67 52.2837 139.457 52.591C139.245 52.8982 138.974 53.1606 138.66 53.3631C138.346 53.5656 137.995 53.7043 137.628 53.7712C137.26 53.8381 136.883 53.8319 136.518 53.753C136.152 53.6741 135.806 53.524 135.499 53.3113C135.192 53.0986 134.93 52.8275 134.728 52.5135C134.525 52.1994 134.387 51.8486 134.32 51.4811L134.319 51.4756C134.04 49.9306 135.234 49.3839 136.78 49.1042C138.325 48.8245 139.636 48.9178 139.915 50.4628Z" fill="white"/>
<path d="M94.2194 87.9208C95.4807 84.6216 95.6634 81.6262 94.6275 81.2303C93.5915 80.8344 91.7293 83.188 90.468 86.4872C89.2067 89.7864 89.024 92.7819 90.0599 93.1777C91.0959 93.5736 92.9581 91.22 94.2194 87.9208Z" fill="#2F2E41"/>
<path d="M93.147 98.9138C94.2787 91.941 89.5425 85.3712 82.5682 84.2397C75.594 83.1082 69.0228 87.8434 67.891 94.8162C66.7593 101.789 71.4955 108.359 78.4698 109.49C85.444 110.622 92.0152 105.886 93.147 98.9138Z" fill="#2F2E41"/>
<path d="M78.5757 106.902H74.6892V113.864H78.5757V106.902Z" fill="#2F2E41"/>
<path d="M86.3487 106.902H82.4622V113.864H86.3487V106.902Z" fill="#2F2E41"/>
<path d="M77.928 115.159C79.7167 115.159 81.1668 114.615 81.1668 113.945C81.1668 113.274 79.7167 112.73 77.928 112.73C76.1393 112.73 74.6892 113.274 74.6892 113.945C74.6892 114.615 76.1393 115.159 77.928 115.159Z" fill="#2F2E41"/>
<path d="M85.7009 114.995C87.4897 114.995 88.9397 114.451 88.9397 113.781C88.9397 113.11 87.4897 112.566 85.7009 112.566C83.9122 112.566 82.4622 113.11 82.4622 113.781C82.4622 114.451 83.9122 114.995 85.7009 114.995Z" fill="#2F2E41"/>
<path d="M68.1092 84.9687C67.0762 80.3435 70.3782 75.6699 75.4845 74.53C80.5908 73.39 85.5676 76.2154 86.6006 80.8406C87.6336 85.4658 84.2495 87.1716 79.1432 88.3116C74.037 89.4515 69.1422 89.5939 68.1092 84.9687Z" fill="#8F84FF"/>
<path d="M71.8891 93.671C72.3643 92.6692 70.162 90.6301 66.9701 89.1165C63.7782 87.603 60.8054 87.1881 60.3301 88.19C59.8549 89.1918 62.0572 91.2309 65.2491 92.7444C68.441 94.258 71.4138 94.6728 71.8891 93.671Z" fill="#2F2E41"/>
<path d="M25.1257 122.604C32.1911 122.604 37.9188 116.878 37.9188 109.814C37.9188 102.75 32.1911 97.0234 25.1257 97.0234C18.0602 97.0234 12.3325 102.75 12.3325 109.814C12.3325 116.878 18.0602 122.604 25.1257 122.604Z" fill="#2F2E41"/>
<path d="M23.1824 119.852H19.2959V126.813H23.1824V119.852Z" fill="#2F2E41"/>
<path d="M30.9554 119.852H27.0688V126.813H30.9554V119.852Z" fill="#2F2E41"/>
<path d="M22.5347 128.108C24.3234 128.108 25.7735 127.565 25.7735 126.894C25.7735 126.223 24.3234 125.68 22.5347 125.68C20.7459 125.68 19.2959 126.223 19.2959 126.894C19.2959 127.565 20.7459 128.108 22.5347 128.108Z" fill="#2F2E41"/>
<path d="M30.3076 127.948C32.0963 127.948 33.5464 127.404 33.5464 126.734C33.5464 126.063 32.0963 125.52 30.3076 125.52C28.5189 125.52 27.0688 126.063 27.0688 126.734C27.0688 127.404 28.5189 127.948 30.3076 127.948Z" fill="#2F2E41"/>
<path d="M25.4495 110.946C27.8643 110.946 29.8218 108.989 29.8218 106.575C29.8218 104.16 27.8643 102.203 25.4495 102.203C23.0347 102.203 21.0771 104.16 21.0771 106.575C21.0771 108.989 23.0347 110.946 25.4495 110.946Z" fill="white"/>
<path d="M25.4496 108.035C26.2546 108.035 26.9071 107.383 26.9071 106.578C26.9071 105.773 26.2546 105.121 25.4496 105.121C24.6447 105.121 23.9922 105.773 23.9922 106.578C23.9922 107.383 24.6447 108.035 25.4496 108.035Z" fill="#3F3D56"/>
<path d="M12.7159 97.9179C11.6829 93.2927 14.9849 88.6191 20.0912 87.4792C25.1975 86.3392 30.1743 89.1646 31.2073 93.7898C32.2403 98.415 28.8562 100.121 23.7499 101.261C18.6436 102.401 13.7489 102.543 12.7159 97.9179Z" fill="#E6E6E6"/>
<path d="M34.4538 64.12C35.7151 60.8208 35.8978 57.8254 34.8618 57.4295C33.8259 57.0336 31.9636 59.3872 30.7023 62.6864C29.4411 65.9856 29.2584 68.9811 30.2943 69.3769C31.3302 69.7728 33.1925 67.4192 34.4538 64.12Z" fill="#2F2E41"/>
<path d="M33.3813 75.113C34.5131 68.1402 29.7768 61.5704 22.8026 60.4389C15.8284 59.3074 9.25717 64.0427 8.12541 71.0154C6.99365 77.9881 11.7299 84.5579 18.7041 85.6894C25.6784 86.8209 32.2496 82.0857 33.3813 75.113Z" fill="#2F2E41"/>
<path d="M18.81 83.1016H14.9235V90.0634H18.81V83.1016Z" fill="#2F2E41"/>
<path d="M26.5831 83.1016H22.6965V90.0634H26.5831V83.1016Z" fill="#2F2E41"/>
<path d="M18.1622 91.3583C19.951 91.3583 21.401 90.8146 21.401 90.144C21.401 89.4733 19.951 88.9297 18.1622 88.9297C16.3735 88.9297 14.9235 89.4733 14.9235 90.144C14.9235 90.8146 16.3735 91.3583 18.1622 91.3583Z" fill="#2F2E41"/>
<path d="M25.9353 91.1942C27.724 91.1942 29.1741 90.6505 29.1741 89.9799C29.1741 89.3093 27.724 88.7656 25.9353 88.7656C24.1466 88.7656 22.6965 89.3093 22.6965 89.9799C22.6965 90.6505 24.1466 91.1942 25.9353 91.1942Z" fill="#2F2E41"/>
<path d="M21.0772 74.1959C23.492 74.1959 25.4495 72.2388 25.4495 69.8245C25.4495 67.4103 23.492 65.4531 21.0772 65.4531C18.6624 65.4531 16.7048 67.4103 16.7048 69.8245C16.7048 72.2388 18.6624 74.1959 21.0772 74.1959Z" fill="white"/>
<path d="M21.0772 71.2815C21.8821 71.2815 22.5347 70.6291 22.5347 69.8243C22.5347 69.0196 21.8821 68.3672 21.0772 68.3672C20.2723 68.3672 19.6198 69.0196 19.6198 69.8243C19.6198 70.6291 20.2723 71.2815 21.0772 71.2815Z" fill="#3F3D56"/>
<path d="M8.34355 61.1679C7.31057 56.5427 10.6126 51.8691 15.7189 50.7292C20.8252 49.5892 25.802 52.4146 26.835 57.0398C27.868 61.665 24.4839 63.3709 19.3776 64.5108C14.2713 65.6507 9.37653 65.7931 8.34355 61.1679Z" fill="#8F84FF"/>
<path d="M12.1235 69.8663C12.5987 68.8645 10.3964 66.8254 7.20451 65.3118C4.01259 63.7983 1.03975 63.3834 0.5645 64.3853C0.0892476 65.3871 2.29155 67.4262 5.48347 68.9398C8.67539 70.4533 11.6482 70.8681 12.1235 69.8663Z" fill="#2F2E41"/>
<path d="M14.4694 77.6771C14.4694 78.9289 17.6933 81.4009 21.2708 81.4009C24.8482 81.4009 28.2025 77.8765 28.2025 76.6247C28.2025 75.3729 24.8482 76.8676 21.2708 76.8676C17.6933 76.8676 14.4694 76.4252 14.4694 77.6771Z" fill="white"/>
<path d="M33.3311 24.2266C33.0948 24.2268 32.8683 24.3208 32.7012 24.4878C32.5342 24.6548 32.4402 24.8813 32.4399 25.1175V61.9438C32.4402 62.18 32.5342 62.4065 32.7012 62.5735C32.8683 62.7405 33.0948 62.8345 33.3311 62.8347H118.584C118.821 62.8345 119.047 62.7405 119.214 62.5735C119.381 62.4065 119.475 62.18 119.476 61.9438V25.1175C119.475 24.8813 119.381 24.6548 119.214 24.4878C119.047 24.3208 118.821 24.2268 118.584 24.2266H33.3311Z" fill="#8F84FF"/>
<path d="M56.3507 52.176C61.1282 52.176 65.0012 48.3039 65.0012 43.5275C65.0012 38.751 61.1282 34.8789 56.3507 34.8789C51.5732 34.8789 47.7003 38.751 47.7003 43.5275C47.7003 48.3039 51.5732 52.176 56.3507 52.176Z" fill="white"/>
<path d="M75.8746 42.379C75.6092 42.3834 75.3562 42.4919 75.1701 42.681C74.9839 42.8702 74.8796 43.1249 74.8796 43.3902C74.8796 43.6556 74.9839 43.9103 75.1701 44.0994C75.3562 44.2886 75.6092 44.3971 75.8746 44.4014H103.187C103.32 44.4037 103.452 44.3798 103.576 44.331C103.699 44.2823 103.812 44.2096 103.908 44.1173C104.003 44.0249 104.08 43.9146 104.132 43.7927C104.185 43.6708 104.214 43.5397 104.216 43.4068C104.218 43.274 104.194 43.142 104.145 43.0184C104.096 42.8949 104.024 42.7821 103.931 42.6867C103.839 42.5912 103.728 42.5149 103.606 42.4621C103.484 42.4094 103.353 42.3811 103.22 42.379C103.209 42.3789 103.198 42.3789 103.187 42.379H75.8746Z" fill="white"/>
<path d="M75.8746 36.5197C75.6092 36.524 75.3562 36.6325 75.1701 36.8217C74.9839 37.0108 74.8796 37.2655 74.8796 37.5309C74.8796 37.7962 74.9839 38.0509 75.1701 38.2401C75.3562 38.4292 75.6092 38.5377 75.8746 38.5421H89.5138C89.6467 38.5443 89.7787 38.5204 89.9023 38.4717C90.026 38.4229 90.1388 38.3503 90.2343 38.2579C90.3299 38.1656 90.4063 38.0553 90.4592 37.9334C90.5121 37.8115 90.5404 37.6803 90.5426 37.5475C90.5448 37.4146 90.5208 37.2826 90.4719 37.1591C90.423 37.0355 90.3503 36.9228 90.2578 36.8273C90.1654 36.7318 90.055 36.6555 89.933 36.6028C89.8111 36.55 89.6799 36.5217 89.547 36.5197C89.5359 36.5195 89.5248 36.5195 89.5138 36.5197H75.8746Z" fill="white"/>
<path d="M75.8746 48.5158C75.6092 48.5201 75.3562 48.6286 75.1701 48.8177C74.9839 49.0069 74.8796 49.2616 74.8796 49.527C74.8796 49.7923 74.9839 50.047 75.1701 50.2362C75.3562 50.4253 75.6092 50.5338 75.8746 50.5382H103.187C103.32 50.5404 103.452 50.5165 103.576 50.4678C103.699 50.419 103.812 50.3464 103.908 50.254C104.003 50.1617 104.08 50.0514 104.132 49.9295C104.185 49.8076 104.214 49.6764 104.216 49.5436C104.218 49.4107 104.194 49.2787 104.145 49.1552C104.096 49.0316 104.024 48.9189 103.931 48.8234C103.839 48.728 103.728 48.6517 103.606 48.5989C103.484 48.5461 103.353 48.5178 103.22 48.5158C103.209 48.5156 103.198 48.5156 103.187 48.5158H75.8746Z" fill="white"/>
<path d="M208.521 116.633C209.557 116.237 209.375 113.242 208.113 109.942C206.852 106.643 204.99 104.29 203.954 104.686C202.918 105.081 203.1 108.077 204.362 111.376C205.623 114.675 207.485 117.029 208.521 116.633Z" fill="#2F2E41"/>
<path d="M217.438 135.616C224.412 134.484 229.149 127.914 228.017 120.942C226.885 113.969 220.314 109.234 213.34 110.365C206.365 111.497 201.629 118.067 202.761 125.039C203.893 132.012 210.464 136.747 217.438 135.616Z" fill="#2F2E41"/>
<path d="M221.218 133.031H217.332L218.093 141.369L221.225 140.518L221.218 133.031Z" fill="#2F2E41"/>
<path d="M212.746 133.051H208.859L209.62 141.388L212.611 140.516L212.746 133.051Z" fill="#2F2E41"/>
<path d="M209.509 141.78C211.293 141.649 212.7 141.001 212.651 140.333C212.602 139.664 211.116 139.227 209.332 139.358C207.548 139.488 206.141 140.136 206.19 140.805C206.239 141.474 207.725 141.91 209.509 141.78Z" fill="#2F2E41"/>
<path d="M215.065 124.126C217.48 124.126 219.437 122.168 219.437 119.754C219.437 117.34 217.48 115.383 215.065 115.383C212.65 115.383 210.693 117.34 210.693 119.754C210.693 122.168 212.65 124.126 215.065 124.126Z" fill="white"/>
<path d="M215.065 121.211C215.87 121.211 216.522 120.559 216.522 119.754C216.522 118.949 215.87 118.297 215.065 118.297C214.26 118.297 213.607 118.949 213.607 119.754C213.607 120.559 214.26 121.211 215.065 121.211Z" fill="#3F3D56"/>
<path d="M230.522 115.281C232.621 112.439 233.599 109.602 232.707 108.944C231.815 108.285 229.391 110.054 227.292 112.895C225.193 115.736 224.215 118.573 225.107 119.232C225.999 119.891 228.423 118.122 230.522 115.281Z" fill="#2F2E41"/>
<path d="M209.147 129.585C209.147 128.553 211.804 126.516 214.753 126.516C217.701 126.516 220.466 129.42 220.466 130.452C220.466 131.484 217.701 130.252 214.753 130.252C211.804 130.252 209.147 130.616 209.147 129.585Z" fill="white"/>
<path d="M251.263 112.42L181.234 102.714C180.688 102.638 180.195 102.348 179.863 101.908C179.53 101.469 179.385 100.915 179.46 100.369L187.333 43.5939C187.409 43.0481 187.699 42.5548 188.138 42.2222C188.578 41.8897 189.132 41.7451 189.678 41.8202L259.706 51.526C260.252 51.6023 260.745 51.892 261.078 52.3316C261.411 52.7711 261.555 53.3246 261.48 53.8707L253.608 110.646C253.531 111.192 253.242 111.685 252.802 112.018C252.362 112.35 251.809 112.495 251.263 112.42Z" fill="#8F84FF"/>
<path d="M226.175 94.7045L190.424 89.7494C190.379 89.7438 190.335 89.734 190.292 89.7203L211.261 62.7818C211.389 62.6137 211.56 62.4828 211.756 62.4023C211.952 62.3219 212.165 62.2948 212.375 62.3238C212.584 62.3529 212.782 62.437 212.949 62.5677C213.115 62.6983 213.244 62.8708 213.322 63.0674L221.945 84.2907L222.358 85.3064L226.175 94.7045Z" fill="white"/>
<path opacity="0.2" d="M226.175 94.7026L213.969 93.011L221.168 85.1396L221.686 84.5727L221.945 84.2891L222.358 85.3047L226.175 94.7026Z" fill="black"/>
<path d="M246.081 97.4603L215.16 93.1745L222.358 85.3032L222.876 84.7361L232.257 74.4782C232.419 74.3241 232.612 74.2058 232.822 74.131C233.033 74.0561 233.257 74.0265 233.48 74.0439C233.703 74.0613 233.92 74.1255 234.117 74.2321C234.313 74.3388 234.485 74.4856 234.622 74.663C234.656 74.7129 234.687 74.765 234.715 74.8188L246.081 97.4603Z" fill="white"/>
<path d="M226.61 71.9259C229.563 71.9259 231.957 69.5325 231.957 66.5801C231.957 63.6277 229.563 61.2344 226.61 61.2344C223.657 61.2344 221.263 63.6277 221.263 66.5801C221.263 69.5325 223.657 71.9259 226.61 71.9259Z" fill="white"/>
<path d="M197.346 47.0847L190.872 46.1875L189.975 52.6593L196.448 53.5565L197.346 47.0847Z" fill="#3F3D56"/>
<path d="M227.799 111.098C228.832 106.472 225.529 101.799 220.423 100.659C215.317 99.5189 210.34 102.344 209.307 106.969C208.274 111.595 211.658 113.301 216.764 114.44C221.871 115.58 226.766 115.723 227.799 111.098Z" fill="#F2F2F2"/>
<path d="M218.032 141.76C219.816 141.63 221.223 140.982 221.174 140.313C221.125 139.644 219.639 139.208 217.855 139.338C216.071 139.469 214.665 140.116 214.714 140.785C214.762 141.454 216.248 141.891 218.032 141.76Z" fill="#2F2E41"/>
</g>
<defs>
<clipPath id="clip0_814_56662">
<rect width="261" height="164" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -34,7 +34,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import AppMenu from "@/components/menu/AppMenu.vue";
import MenuItem from "@/components/menu/MenuItem.vue";
import UiIcon from "@/components/ui/UiIcon.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { useXenApiStore } from "@/stores/xen-api.store";
const router = useRouter();

View File

@@ -1,5 +1,11 @@
<template>
<header class="app-header">
<UiIcon
v-if="isMobile"
ref="navigationTrigger"
:icon="faBars"
class="toggle-navigation"
/>
<RouterLink :to="{ name: 'home' }">
<img alt="XO Lite" src="../assets/logo.svg" />
</RouterLink>
@@ -12,6 +18,17 @@
<script lang="ts" setup>
import AccountButton from "@/components/AccountButton.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { useNavigationStore } from "@/stores/navigation.store";
import { useUiStore } from "@/stores/ui.store";
import { faBars } from "@fortawesome/free-solid-svg-icons";
import { storeToRefs } from "pinia";
const uiStore = useUiStore();
const { isMobile } = storeToRefs(uiStore);
const navigationStore = useNavigationStore();
const { trigger: navigationTrigger } = storeToRefs(navigationStore);
</script>
<style lang="postcss" scoped>

View File

@@ -2,15 +2,24 @@
<div class="app-login form-container">
<form @submit.prevent="handleSubmit">
<img alt="XO Lite" src="../assets/logo-title.svg" />
<input v-model="login" name="login" readonly type="text" />
<input
v-model="password"
:readonly="isConnecting"
name="password"
:placeholder="$t('password')"
type="password"
/>
<UiButton :busy="isConnecting" type="submit">
<FormInputWrapper>
<FormInput v-model="login" name="login" readonly type="text" />
</FormInputWrapper>
<FormInputWrapper :error="error">
<FormInput
name="password"
ref="passwordRef"
type="password"
v-model="password"
:placeholder="$t('password')"
:readonly="isConnecting"
/>
</FormInputWrapper>
<UiButton
type="submit"
:busy="isConnecting"
:disabled="password.trim().length < 1"
>
{{ $t("login") }}
</UiButton>
</form>
@@ -19,21 +28,47 @@
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue";
import { onMounted, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import FormInput from "@/components/form/FormInput.vue";
import FormInputWrapper from "@/components/form/FormInputWrapper.vue";
import UiButton from "@/components/ui/UiButton.vue";
import { useXenApiStore } from "@/stores/xen-api.store";
const { t } = useI18n();
const xenApiStore = useXenApiStore();
const { isConnecting } = storeToRefs(xenApiStore);
const login = ref("root");
const password = ref("");
const error = ref<string>();
const passwordRef = ref<InstanceType<typeof FormInput>>();
const isInvalidPassword = ref(false);
const focusPasswordInput = () => passwordRef.value?.focus();
onMounted(() => {
xenApiStore.reconnect();
focusPasswordInput();
});
watch(password, () => {
isInvalidPassword.value = false;
error.value = undefined;
});
async function handleSubmit() {
await xenApiStore.connect(login.value, password.value);
try {
await xenApiStore.connect(login.value, password.value);
} catch (err) {
if ((err as Error).message === "SESSION_AUTHENTICATION_FAILED") {
focusPasswordInput();
isInvalidPassword.value = true;
error.value = t("password-invalid");
} else {
error.value = t("error-occured");
console.error(err);
}
}
}
</script>
@@ -50,6 +85,7 @@ async function handleSubmit() {
form {
display: flex;
font-size: 2rem;
min-width: 30em;
max-width: 100%;
align-items: center;
@@ -72,12 +108,6 @@ img {
margin-bottom: 5rem;
}
label {
font-size: 120%;
font-weight: bold;
margin: 1.5rem 0 0.5rem 0;
}
input {
width: 45rem;
max-width: 100%;
@@ -89,6 +119,6 @@ input {
}
button {
margin-top: 3rem;
margin-top: 2rem;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div ref="rootElement" class="app-markdown" v-html="html" />
</template>
<script lang="ts" setup>
import { type Ref, computed, ref } from "vue";
import { useEventListener } from "@vueuse/core";
import "highlight.js/styles/github-dark.css";
import { markdown } from "@/libs/markdown";
const rootElement = ref() as Ref<HTMLElement>;
const props = defineProps<{
content: string;
}>();
const html = computed(() => markdown.render(props.content ?? ""));
useEventListener(
rootElement,
"click",
(event: MouseEvent) => {
const target = event.target as HTMLElement;
if (!target.classList.contains("copy-button")) {
return;
}
const copyable =
target.parentElement!.querySelector<HTMLElement>(".copyable");
if (copyable !== null) {
navigator.clipboard.writeText(copyable.innerText);
}
},
{ capture: true }
);
</script>
<style lang="postcss" scoped>
.app-markdown {
font-size: 1.6rem;
:deep() {
p,
h1,
h2,
h3,
h4,
h5,
h6,
pre {
margin: 1rem 0;
}
pre {
width: 100%;
padding: 0.8rem 1.4rem;
border-radius: 1rem;
}
code:not(.hljs-code) {
background-color: var(--background-color-extra-blue);
padding: 0.3rem 0.6rem;
border-radius: 0.6rem;
}
ul,
ol {
margin: revert;
padding-left: 2rem;
list-style-type: revert;
}
table {
border-spacing: 0;
th,
td {
padding: 0.5rem 1rem;
}
thead th {
border-bottom: 2px solid var(--color-blue-scale-400);
background-color: var(--background-color-secondary);
}
tbody td {
border-bottom: 1px solid var(--color-blue-scale-400);
}
}
.copy-button {
font-size: 1.6rem;
font-weight: 400;
position: absolute;
z-index: 1;
right: 1rem;
cursor: pointer;
color: white;
border: none;
background-color: transparent;
&:hover {
color: var(--color-extra-blue-base);
}
&:active {
color: var(--color-extra-blue-d20);
}
}
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<transition name="slide">
<nav
v-if="isDesktop || isOpen"
ref="navElement"
:class="{ collapsible: isMobile }"
class="app-navigation"
>
<StoryMenu v-if="$route.meta.hasStoryNav" />
<InfraPoolList v-else />
</nav>
</transition>
</template>
<script lang="ts" setup>
import StoryMenu from "@/components/component-story/StoryMenu.vue";
import InfraPoolList from "@/components/infra/InfraPoolList.vue";
import { useNavigationStore } from "@/stores/navigation.store";
import { useUiStore } from "@/stores/ui.store";
import { onClickOutside, whenever } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { ref } from "vue";
const uiStore = useUiStore();
const { isMobile, isDesktop } = storeToRefs(uiStore);
const navigationStore = useNavigationStore();
const { isOpen, trigger } = storeToRefs(navigationStore);
const navElement = ref();
whenever(isOpen, () => {
const unregisterEvent = onClickOutside(
navElement,
() => {
isOpen.value = false;
unregisterEvent?.();
},
{
ignore: [trigger],
}
);
});
</script>
<style lang="postcss" scoped>
.app-navigation {
overflow: auto;
width: 37rem;
max-width: 37rem;
height: calc(100vh - 8rem);
padding: 0.5rem;
border-right: 1px solid var(--color-blue-scale-400);
background-color: var(--background-color-primary);
&.collapsible {
position: fixed;
z-index: 1;
}
}
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter-from,
.slide-leave-to {
transform: translateX(-37rem);
}
</style>

View File

@@ -62,6 +62,7 @@ watchEffect(() => {
color: var(--color-blue-scale-500);
border-radius: 0.5em;
background-color: var(--color-blue-scale-100);
z-index: 2;
}
.triangle {

View File

@@ -0,0 +1,41 @@
<template>
<pre class="code-highlight hljs"><code v-html="codeAsHtml"></code></pre>
</template>
<script lang="ts" setup>
import HLJS from "highlight.js";
import { computed } from "vue";
import "highlight.js/styles/github-dark.css";
const props = withDefaults(
defineProps<{
code?: any;
lang?: string;
}>(),
{ lang: "typescript" }
);
const codeAsText = computed(() => {
switch (typeof props.code) {
case "string":
return props.code;
case "function":
return String(props.code);
default:
return JSON.stringify(props.code, undefined, 2);
}
});
const codeAsHtml = computed(
() => HLJS.highlight(codeAsText.value, { language: props.lang }).value
);
</script>
<style lang="postcss" scoped>
.code-highlight {
display: inline-block;
padding: 0.3rem 0.6rem;
text-align: left;
border-radius: 0.6rem;
}
</style>

View File

@@ -19,74 +19,59 @@
</option>
</select>
</FormWidget>
<template v-if="hasComparisonSelect">
<FormWidget v-if="currentFilter?.type === 'string'">
<select v-model="newFilter.builder.negate">
<option :value="false">does</option>
<option :value="true">does not</option>
</select>
</FormWidget>
<FormWidget v-if="hasComparisonSelect">
<select v-model="newFilter.builder.comparison">
<option
v-for="(label, type) in comparisons"
:key="type"
:value="type"
>
{{ label }}
</option>
</select>
</FormWidget>
</template>
<FormWidget v-if="hasComparisonSelect">
<select v-model="newFilter.builder.comparison">
<option
v-for="(label, type) in comparisons"
:key="type"
:value="type"
>
{{ label }}
</option>
</select>
</FormWidget>
<FormWidget v-if="currentFilter?.type === 'enum'">
<select v-model="newFilter.builder.value">
<option v-if="!newFilter.builder.value" value="" />
<option v-for="choice in enumChoices" :key="choice" :value="choice">
{{ choice }}
</option>
</select>
</FormWidget>
<FormWidget
v-if="hasValueInput"
v-else-if="hasValueInput"
:after="valueInputAfter"
:before="valueInputBefore"
>
<input v-model="newFilter.builder.value" />
</FormWidget>
<template v-else-if="currentFilter?.type === 'enum'">
<FormWidget>
<select v-model="newFilter.builder.negate">
<option :value="false">is</option>
<option :value="true">is not</option>
</select>
</FormWidget>
<FormWidget>
<select v-model="newFilter.builder.value">
<option v-if="!newFilter.builder.value" value="" />
<option v-for="choice in enumChoices" :key="choice" :value="choice">
{{ choice }}
</option>
</select>
</FormWidget>
</template>
</template>
<UiActionButton
v-if="!newFilter.isAdvanced"
@click="enableAdvancedMode"
:icon="faPencil"
@click="enableAdvancedMode"
/>
<UiActionButton @click="emit('remove', newFilter.id)" :icon="faRemove" />
<UiActionButton :icon="faRemove" @click="emit('remove', newFilter.id)" />
</div>
</template>
<script lang="ts" setup>
import { computed, watch } from "vue";
import type {
Filter,
FilterComparisonType,
FilterComparisons,
FilterType,
Filters,
NewFilter,
} from "@/types/filter";
import { faPencil, faRemove } from "@fortawesome/free-solid-svg-icons";
import { useVModel } from "@vueuse/core";
import FormWidget from "@/components/FormWidget.vue";
import UiActionButton from "@/components/ui/UiActionButton.vue";
import { buildComplexMatcherNode } from "@/libs/complex-matcher.utils";
import { getFilterIcon } from "@/libs/utils";
import type {
Filter,
FilterComparisons,
FilterComparisonType,
Filters,
FilterType,
NewFilter,
} from "@/types/filter";
import { faPencil, faRemove } from "@fortawesome/free-solid-svg-icons";
import { useVModel } from "@vueuse/core";
import { computed, type Ref, watch } from "vue";
import { useI18n } from "vue-i18n";
const props = defineProps<{
availableFilters: Filters;
@@ -98,14 +83,16 @@ const emit = defineEmits<{
(event: "remove", filterId: number): void;
}>();
const newFilter = useVModel(props, "modelValue", emit);
const { t } = useI18n();
const newFilter: Ref<NewFilter> = useVModel(props, "modelValue", emit);
const getDefaultComparisonType = () => {
const defaultTypes: { [key in FilterType]: FilterComparisonType } = {
string: "stringContains",
boolean: "booleanTrue",
number: "numberEquals",
enum: "stringEquals",
enum: "enumIs",
};
return defaultTypes[
@@ -118,7 +105,6 @@ watch(
() => {
newFilter.value.builder.comparison = getDefaultComparisonType();
newFilter.value.builder.value = "";
newFilter.value.builder.negate = false;
}
);
@@ -133,7 +119,7 @@ const hasValueInput = computed(() =>
);
const hasComparisonSelect = computed(
() => newFilter.value.builder.property && currentFilter.value?.type !== "enum"
() => newFilter.value.builder.property !== ""
);
const enumChoices = computed(() => {
@@ -164,8 +150,7 @@ const generatedFilter = computed(() => {
const node = buildComplexMatcherNode(
newFilter.value.builder.comparison,
newFilter.value.builder.property,
newFilter.value.builder.value,
newFilter.value.builder.negate
newFilter.value.builder.value
);
if (node) {
@@ -190,15 +175,20 @@ watch(generatedFilter, (value) => {
const comparisons = computed<FilterComparisons>(() => {
const comparisonsByType = {
string: {
stringContains: "contain",
stringEquals: "equal",
stringStartsWith: "start with",
stringEndsWith: "end with",
stringMatchesRegex: "match regex",
stringContains: t("filter.comparison.contains"),
stringEquals: t("filter.comparison.equals"),
stringStartsWith: t("filter.comparison.starts-with"),
stringEndsWith: t("filter.comparison.ends-with"),
stringMatchesRegex: t("filter.comparison.matches-regex"),
stringDoesNotContain: t("filter.comparison.not-contain"),
stringDoesNotEqual: t("filter.comparison.not-equal"),
stringDoesNotStartWith: t("filter.comparison.not-start-with"),
stringDoesNotEndWith: t("filter.comparison.not-end-with"),
stringDoesNotMatchRegex: t("filter.comparison.not-match-regex"),
},
boolean: {
booleanTrue: "is true",
booleanFalse: "is false",
booleanTrue: t("filter.comparison.is-true"),
booleanFalse: t("filter.comparison.is-false"),
},
number: {
numberLessThan: "<",
@@ -207,23 +197,22 @@ const comparisons = computed<FilterComparisons>(() => {
numberGreaterThanOrEquals: ">=",
numberGreaterThan: ">",
},
enum: {},
enum: {
enumIs: t("filter.comparison.is"),
enumIsNot: t("filter.comparison.is-not"),
},
};
return comparisonsByType[currentFilter.value.type];
});
const valueInputBefore = computed(() => {
return newFilter.value.builder.comparison === "stringMatchesRegex"
? "/"
: undefined;
});
const valueInputBefore = computed(() =>
newFilter.value.builder.comparison === "stringMatchesRegex" ? "/" : undefined
);
const valueInputAfter = computed(() => {
return newFilter.value.builder.comparison === "stringMatchesRegex"
? "/i"
: undefined;
});
const valueInputAfter = computed(() =>
newFilter.value.builder.comparison === "stringMatchesRegex" ? "/i" : undefined
);
</script>
<style lang="postcss" scoped>

View File

@@ -7,7 +7,7 @@
@remove="emit('removeSort', property)"
>
<span class="property">
<FontAwesomeIcon :icon="isAscending ? faCaretUp : faCaretDown" />
<UiIcon :icon="isAscending ? faCaretUp : faCaretDown" />
{{ property }}
</span>
</UiFilter>
@@ -17,7 +17,7 @@
</UiActionButton>
</UiFilterGroup>
<UiModal v-if="isOpen" @submit.prevent="handleSubmit" :icon="faSort">
<UiModal v-if="isOpen" :icon="faSort" @submit.prevent="handleSubmit">
<div class="form-widgets">
<FormWidget :label="$t('sort-by')">
<select v-model="newSortProperty">
@@ -48,7 +48,14 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import FormWidget from "@/components/FormWidget.vue";
import UiActionButton from "@/components/ui/UiActionButton.vue";
import UiButton from "@/components/ui/UiButton.vue";
import UiFilter from "@/components/ui/UiFilter.vue";
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import UiModal from "@/components/ui/UiModal.vue";
import useModal from "@/composables/modal.composable";
import type { ActiveSorts, Sorts } from "@/types/sort";
import {
faCaretDown,
@@ -56,17 +63,11 @@ import {
faPlus,
faSort,
} from "@fortawesome/free-solid-svg-icons";
import FormWidget from "@/components/FormWidget.vue";
import UiButton from "@/components/ui/UiButton.vue";
import UiActionButton from "@/components/ui/UiActionButton.vue";
import UiFilter from "@/components/ui/UiFilter.vue";
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
import UiModal from "@/components/ui/UiModal.vue";
import useModal from "@/composables/modal.composable";
import { ref } from "vue";
defineProps<{
availableSorts: Sorts;
activeSorts: ActiveSorts;
activeSorts: ActiveSorts<Record<string, any>>;
}>();
const emit = defineEmits<{

View File

@@ -66,9 +66,11 @@ const emit = defineEmits<{
const isSelectable = computed(() => props.modelValue !== undefined);
const { filters, addFilter, removeFilter, predicate } = useCollectionFilter();
const { filters, addFilter, removeFilter, predicate } = useCollectionFilter({
queryStringParam: "filter",
});
const { sorts, addSort, removeSort, toggleSortDirection, compareFn } =
useCollectionSorter();
useCollectionSorter<Record<string, any>>({ queryStringParam: "sort" });
const filteredCollection = useFilteredCollection(
toRef(props, "collection"),

View File

@@ -2,7 +2,7 @@
<th>
<div class="content">
<span class="label">
<FontAwesomeIcon v-if="icon" :icon="icon" />
<UiIcon :icon="icon" />
<slot />
</span>
</div>
@@ -10,6 +10,7 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{

View File

@@ -8,7 +8,7 @@
<span class="widget">
<span v-if="before || $slots.before" class="before">
<slot name="before">
<FontAwesomeIcon v-if="isIcon(before)" :icon="before" fixed-width />
<UiIcon v-if="isIcon(before)" :icon="before" fixed-width />
<template v-else>{{ before }}</template>
</slot>
</span>
@@ -17,7 +17,7 @@
</span>
<span v-if="after || $slots.after" class="after">
<slot name="after">
<FontAwesomeIcon v-if="isIcon(after)" :icon="after" fixed-width />
<UiIcon v-if="isIcon(after)" :icon="after" fixed-width />
<template v-else>{{ after }}</template>
</slot>
</span>
@@ -26,6 +26,7 @@
</template>
<script lang="ts" setup>
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
defineProps<{

View File

@@ -0,0 +1,30 @@
<template>
<div class="no-data">
<img alt="No data" class="img" src="@/assets/undraw-bug-fixing.svg" />
<p class="text-error">{{ $t("error-no-data") }}</p>
</div>
</template>
<style scoped lang="postcss">
.no-data {
text-align: center;
}
.img {
display: block;
margin-left: auto;
margin-right: auto;
margin-bottom: 1em;
width: 17em;
height: 13em;
}
.text-error {
margin: auto;
width: 8em;
font-style: normal;
font-weight: 500;
font-size: 1.25em;
line-height: 150%;
color: var(--color-red-vates-base);
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div class="wrapper-spinner" v-if="store.isLoading">
<UiSpinner class="spinner" />
</div>
<ObjectNotFoundView :id="id" v-else-if="isRecordNotFound" />
<slot v-else />
</template>
<script lang="ts" setup>
import ObjectNotFoundView from "@/views/ObjectNotFoundView.vue";
import UiSpinner from "@/components/ui/UiSpinner.vue";
import { useHostStore } from "@/stores/host.store";
import { useVmStore } from "@/stores/vm.store";
import { computed } from "vue";
import { useRouter } from "vue-router";
const storeByType = {
vm: useVmStore,
host: useHostStore,
};
const props = defineProps<{ objectType: "vm" | "host"; id?: string }>();
const store = storeByType[props.objectType]();
const { currentRoute } = useRouter();
const id = computed(
() => props.id ?? (currentRoute.value.params.uuid as string)
);
const isRecordNotFound = computed(
() => store.isReady && !store.hasRecordByUuid(id.value)
);
</script>
<style scoped>
.wrapper-spinner {
display: flex;
height: 100%;
}
.spinner {
color: var(--color-extra-blue-base);
display: flex;
margin: auto;
width: 10rem;
height: 10rem;
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<div class="page-under-construction">
<img alt="Under construction" src="@/assets/under-construction.svg" />
<p class="title">{{ $t("xo-lite-under-construction") }}</p>
<p class="subtitle">{{ $t("new-features-are-coming") }}</p>
<p class="contact">
{{ $t("do-you-have-needs") }}
<a
href="https://xcp-ng.org/forum/topic/5018/xo-lite-building-an-embedded-ui-in-xcp-ng"
target="_blank"
rel="noopener noreferrer"
>
{{ $t("here") }}
</a>
</p>
</div>
</template>
<style lang="postcss" scoped>
.page-under-construction {
width: 100%;
min-height: 76.5vh;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--color-extra-blue-base);
}
img {
margin-bottom: 40px;
width: 30%;
}
.title {
font-weight: 400;
font-size: 36px;
text-align: center;
}
.subtitle {
font-weight: 500;
font-size: 24px;
margin: 21px 0;
text-align: center;
}
.contact {
font-weight: 400;
font-size: 20px;
color: var(--color-blue-scale-100);
& a {
text-transform: lowercase;
}
}
</style>

View File

@@ -1,9 +1,10 @@
<template>
<FontAwesomeIcon class="power-state-icon" :class="className" :icon="icon" />
<UiIcon :class="className" :icon="icon" class="power-state-icon" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { PowerState } from "@/libs/xen-api";
import {
faMoon,
faPause,
@@ -11,7 +12,7 @@ import {
faQuestion,
faStop,
} from "@fortawesome/free-solid-svg-icons";
import type { PowerState } from "@/libs/xen-api";
import { computed } from "vue";
const props = defineProps<{
state: PowerState;
@@ -29,7 +30,7 @@ const icon = computed(() => icons[props.state] ?? faQuestion);
const className = computed(() => `state-${props.state.toLocaleLowerCase()}`);
</script>
<style scoped lang="postcss">
<style lang="postcss" scoped>
.power-state-icon {
color: var(--color-extra-blue-d60);

Some files were not shown because too many files have changed in this diff Show More