Compare commits
4561 Commits
v5.2.0
...
xen-api-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81e6372070 | ||
|
|
dbfbd42d29 | ||
|
|
e0d34b1747 | ||
|
|
9a8f9dd1d7 | ||
|
|
75521f8757 | ||
|
|
11d4cb2f04 | ||
|
|
d90cb09b56 | ||
|
|
a02d393457 | ||
|
|
01a5963947 | ||
|
|
7ef314d9f4 | ||
|
|
2ff25d1f61 | ||
|
|
ede12b6732 | ||
|
|
8a010f62fd | ||
|
|
51da4a7e70 | ||
|
|
fd2580f5da | ||
|
|
c5fdab7d47 | ||
|
|
ae094438b1 | ||
|
|
3e5af9e894 | ||
|
|
10093afb91 | ||
|
|
58032738b9 | ||
|
|
89cbbaeeea | ||
|
|
5ca08eb400 | ||
|
|
fad049d2ac | ||
|
|
87466cb5bd | ||
|
|
bb69ad8019 | ||
|
|
d5373b85c7 | ||
|
|
d71e699582 | ||
|
|
f464751752 | ||
|
|
d69f355206 | ||
|
|
769c32a1b1 | ||
|
|
44075d2ad5 | ||
|
|
2b4ae3f1c2 | ||
|
|
8689b48c55 | ||
|
|
443882f4be | ||
|
|
9c3adaedf0 | ||
|
|
4ba4d383f0 | ||
|
|
947febcdaa | ||
|
|
b0a152612e | ||
|
|
c9991655cf | ||
|
|
8971d218bb | ||
|
|
8b05486945 | ||
|
|
42cd5d8e58 | ||
|
|
31f420937e | ||
|
|
952d086c5e | ||
|
|
422a04795f | ||
|
|
28a8807cfb | ||
|
|
c6327a953b | ||
|
|
cc71f2e4a3 | ||
|
|
701086ec26 | ||
|
|
20acaebb68 | ||
|
|
22997cd903 | ||
|
|
7cb720b11f | ||
|
|
8bf35b2a63 | ||
|
|
8b72690c17 | ||
|
|
a0cc768af9 | ||
|
|
9fc73e03a2 | ||
|
|
edd57f7205 | ||
|
|
8b155b62dc | ||
|
|
bf5daa1a9b | ||
|
|
4f154e33bc | ||
|
|
395bcade67 | ||
|
|
985a038482 | ||
|
|
2da04c5bb1 | ||
|
|
1baf8cfaa6 | ||
|
|
956a9a7dde | ||
|
|
d7bb457fd0 | ||
|
|
563ff38c25 | ||
|
|
05caaa0814 | ||
|
|
1f283d285a | ||
|
|
adaa7ca413 | ||
|
|
247e797d2b | ||
|
|
9c7c030b00 | ||
|
|
867c9ea66c | ||
|
|
59985adc5d | ||
|
|
fd4504f0ee | ||
|
|
6c6aacc64e | ||
|
|
678adc0726 | ||
|
|
be3b684866 | ||
|
|
179aadafa4 | ||
|
|
f9dc129601 | ||
|
|
6beea2d208 | ||
|
|
57ef84ad3b | ||
|
|
9d9fdcff1e | ||
|
|
83e82e4789 | ||
|
|
4a12e0b6a0 | ||
|
|
642118959e | ||
|
|
600011be17 | ||
|
|
6765251332 | ||
|
|
afa5b046cb | ||
|
|
138d5627b5 | ||
|
|
682d9e88ec | ||
|
|
191c531bad | ||
|
|
f026e51c54 | ||
|
|
693afc52ff | ||
|
|
b22914e88a | ||
|
|
50b081608f | ||
|
|
214528a880 | ||
|
|
4f13b32f9a | ||
|
|
8ce8f5763c | ||
|
|
1e5b2f54c3 | ||
|
|
348bc16d6d | ||
|
|
ff2c69102d | ||
|
|
9479b7dc74 | ||
|
|
39f4e13915 | ||
|
|
5ea9f26ec4 | ||
|
|
c1d9cc7e2a | ||
|
|
6b80124a35 | ||
|
|
83a3754fe5 | ||
|
|
bf9b53c4d9 | ||
|
|
2b5f7dc8de | ||
|
|
4349911076 | ||
|
|
152f68624c | ||
|
|
3770194598 | ||
|
|
2b4a7c40e1 | ||
|
|
62a9158074 | ||
|
|
354fd88028 | ||
|
|
1b191c2bc9 | ||
|
|
f94212729a | ||
|
|
43e5398d99 | ||
|
|
920107e13f | ||
|
|
d52bed9618 | ||
|
|
5ecc31b977 | ||
|
|
ed3ecc6f4e | ||
|
|
add10ea556 | ||
|
|
d627fad299 | ||
|
|
6f8b31eaa2 | ||
|
|
8d47c83117 | ||
|
|
75ddf2681e | ||
|
|
4ad72b2ef0 | ||
|
|
a7334061ef | ||
|
|
c89c7dab60 | ||
|
|
0e0e4aae69 | ||
|
|
971c039086 | ||
|
|
5e8e1968e1 | ||
|
|
76a1c827d1 | ||
|
|
a4ab028ad6 | ||
|
|
d2fdf0586c | ||
|
|
0825806093 | ||
|
|
44b1bee080 | ||
|
|
d87f54d4a4 | ||
|
|
dc12896b19 | ||
|
|
23e992de85 | ||
|
|
6f6b047537 | ||
|
|
9fc6013934 | ||
|
|
8a02d557f4 | ||
|
|
1406f1f8e5 | ||
|
|
d5f6b65861 | ||
|
|
8d2bbe86d5 | ||
|
|
1a2f553094 | ||
|
|
4d69866532 | ||
|
|
bdddea2e29 | ||
|
|
be4ca0eede | ||
|
|
495c97b44b | ||
|
|
e817b3254e | ||
|
|
dd6987efe9 | ||
|
|
d7f8d12d88 | ||
|
|
504895a730 | ||
|
|
cde92836f3 | ||
|
|
9158e33546 | ||
|
|
c787988b06 | ||
|
|
316d71b34d | ||
|
|
1b344b7d9c | ||
|
|
3df01cd0cf | ||
|
|
d2331bb62f | ||
|
|
51504cc6a3 | ||
|
|
cd03ebe49d | ||
|
|
86c7ae3693 | ||
|
|
6e3b85e764 | ||
|
|
b3bc9a2edd | ||
|
|
90369acb63 | ||
|
|
94e84ce55f | ||
|
|
68cef1aa5d | ||
|
|
2f9ecb8fa8 | ||
|
|
6281374cf3 | ||
|
|
13580923ea | ||
|
|
898434b267 | ||
|
|
6f6e760a25 | ||
|
|
38fb549709 | ||
|
|
6e44c65a07 | ||
|
|
736b00b4e2 | ||
|
|
b5a139619a | ||
|
|
03028bca50 | ||
|
|
b835c5bd40 | ||
|
|
a65b1325fc | ||
|
|
cfcd302294 | ||
|
|
a601154fc7 | ||
|
|
c8669dc88f | ||
|
|
82240979c2 | ||
|
|
db5d495105 | ||
|
|
6e8dfe8833 | ||
|
|
242d9e20c4 | ||
|
|
d4e46a0696 | ||
|
|
d5deaf72f7 | ||
|
|
f9c942d526 | ||
|
|
e446eb0cd0 | ||
|
|
b63efe579a | ||
|
|
f3410f1491 | ||
|
|
b27ac11d56 | ||
|
|
a55d73614e | ||
|
|
25cd1957c7 | ||
|
|
abd97abc24 | ||
|
|
6ddfd909f0 | ||
|
|
e054eec555 | ||
|
|
e253657770 | ||
|
|
041c32a4a7 | ||
|
|
102e629e16 | ||
|
|
2d0e3b93cc | ||
|
|
668d572549 | ||
|
|
e023809c8c | ||
|
|
0bcabb9f0d | ||
|
|
594a9d5399 | ||
|
|
242a02836c | ||
|
|
b144382831 | ||
|
|
6936f223f3 | ||
|
|
eb8dfc86ca | ||
|
|
02c715e1cc | ||
|
|
7e64e1a11e | ||
|
|
54772fad33 | ||
|
|
8cb53b0c4e | ||
|
|
892139908b | ||
|
|
ba6393165d | ||
|
|
2174a9cec7 | ||
|
|
629f68ffd7 | ||
|
|
5fe03f2934 | ||
|
|
09e7f04794 | ||
|
|
feecd51770 | ||
|
|
b5a855b2ad | ||
|
|
0b6bb65c17 | ||
|
|
d3691313e6 | ||
|
|
79ea50c829 | ||
|
|
8532d563de | ||
|
|
2e945e8349 | ||
|
|
22e3037e85 | ||
|
|
9aed4f6fba | ||
|
|
a505ded8a1 | ||
|
|
bea7b90eb2 | ||
|
|
e1eb948c54 | ||
|
|
1b42523e2d | ||
|
|
ed7a40bc73 | ||
|
|
c8dc786008 | ||
|
|
a6869638f5 | ||
|
|
3ef08d3d5e | ||
|
|
ef17cb1c6c | ||
|
|
ecc086f15d | ||
|
|
be9eb8ce91 | ||
|
|
5b7a9fe969 | ||
|
|
97d54cab11 | ||
|
|
980a1cc205 | ||
|
|
9517686e22 | ||
|
|
4905a0310a | ||
|
|
d2f88d0a02 | ||
|
|
76000d2563 | ||
|
|
bc02d51882 | ||
|
|
aada9e4a33 | ||
|
|
cf47a80676 | ||
|
|
c190067181 | ||
|
|
4490a2508b | ||
|
|
938e7704e6 | ||
|
|
18ca6b935c | ||
|
|
b3769019e5 | ||
|
|
506a6b0cf4 | ||
|
|
a18df93c4f | ||
|
|
d93eda34c9 | ||
|
|
684269321b | ||
|
|
b84ad98fd0 | ||
|
|
abf526f91a | ||
|
|
c06020745e | ||
|
|
431b85c98f | ||
|
|
299fdc19d6 | ||
|
|
7531a25588 | ||
|
|
d8f89191e8 | ||
|
|
d9ad53d954 | ||
|
|
043fc5cdfc | ||
|
|
d7eeeca268 | ||
|
|
4c21175ca7 | ||
|
|
377efcd054 | ||
|
|
072401f600 | ||
|
|
1ce7d94261 | ||
|
|
8178de8a6b | ||
|
|
eb37c7d7d8 | ||
|
|
9465459ef9 | ||
|
|
bf3f9b4ac2 | ||
|
|
52a71cec91 | ||
|
|
4c91667d2c | ||
|
|
a3fe9aff94 | ||
|
|
ed45888d7f | ||
|
|
dbadc487ae | ||
|
|
a5b80655da | ||
|
|
99d4789049 | ||
|
|
2de4163553 | ||
|
|
0fc0be19b2 | ||
|
|
3c271ffffd | ||
|
|
155422bcb5 | ||
|
|
68b6dc5985 | ||
|
|
1aa793886b | ||
|
|
46cd2f10a5 | ||
|
|
337abe2a2b | ||
|
|
0441103f0c | ||
|
|
b5829e2484 | ||
|
|
2e1b32fadc | ||
|
|
0c8f3ea824 | ||
|
|
c23be2185a | ||
|
|
c43ea11017 | ||
|
|
bd18a9270e | ||
|
|
e24cba9684 | ||
|
|
084e96be0e | ||
|
|
5dfefe8d35 | ||
|
|
bf45cdd2b8 | ||
|
|
4a6ba998d4 | ||
|
|
7f953c62af | ||
|
|
c9412b02af | ||
|
|
536a9d6e50 | ||
|
|
4ee0944037 | ||
|
|
98f5db9fbd | ||
|
|
fd47403ec9 | ||
|
|
bbefa3a009 | ||
|
|
575fe300fa | ||
|
|
137b8e7f7f | ||
|
|
4749e452c4 | ||
|
|
0881b66ab6 | ||
|
|
4a8c5a980a | ||
|
|
233aca7911 | ||
|
|
50966dce5d | ||
|
|
0e6bcefee6 | ||
|
|
41ec831df0 | ||
|
|
0178ce0a91 | ||
|
|
142233453a | ||
|
|
b12a804fb3 | ||
|
|
3fe5efbfab | ||
|
|
f33ea6c47c | ||
|
|
f419ec5702 | ||
|
|
24b7dc1c30 | ||
|
|
4619e387ad | ||
|
|
d7d2425435 | ||
|
|
2c99a4db0f | ||
|
|
d71323a67d | ||
|
|
8dd5428ef6 | ||
|
|
88d9111196 | ||
|
|
8ec30ec76d | ||
|
|
eb46711e34 | ||
|
|
748b09d8fa | ||
|
|
17e9885586 | ||
|
|
826628b18f | ||
|
|
e6a32b53fc | ||
|
|
7ce9e8a959 | ||
|
|
a49c9d646c | ||
|
|
d7e2690430 | ||
|
|
71ab2dedb9 | ||
|
|
b817cb86d0 | ||
|
|
3e1b2119c4 | ||
|
|
bfa31be3b7 | ||
|
|
9e9027d4c6 | ||
|
|
584da2f56a | ||
|
|
a43dfc62c2 | ||
|
|
6a071942a5 | ||
|
|
ac8787e930 | ||
|
|
ab60bc46cf | ||
|
|
c84f61d388 | ||
|
|
11b1ff478e | ||
|
|
ea7c44b544 | ||
|
|
b67310ae75 | ||
|
|
56cf6fba2f | ||
|
|
020618554a | ||
|
|
38ec7ac34f | ||
|
|
ef9f75514f | ||
|
|
bd4644dd4f | ||
|
|
f419cd6ac8 | ||
|
|
e714303f96 | ||
|
|
a78151c93e | ||
|
|
285b1fb36e | ||
|
|
50581c539c | ||
|
|
16e8c87cc6 | ||
|
|
6eed3f2fc7 | ||
|
|
7c5000d588 | ||
|
|
93ffb77e81 | ||
|
|
ec0dd0b174 | ||
|
|
88f6d77047 | ||
|
|
3ff63927f3 | ||
|
|
a5f55c5f92 | ||
|
|
7412a6c01b | ||
|
|
d396593d99 | ||
|
|
62b64ad0b6 | ||
|
|
5983ea32fd | ||
|
|
6f33a79644 | ||
|
|
5716130e6f | ||
|
|
67f31407d7 | ||
|
|
37f0bffcaa | ||
|
|
0e6a460332 | ||
|
|
f05d2d0063 | ||
|
|
28172607c6 | ||
|
|
682deb4b56 | ||
|
|
64fac454b5 | ||
|
|
cdd9eed3d8 | ||
|
|
e9c60bc958 | ||
|
|
7a8c0831bd | ||
|
|
0ca1af8606 | ||
|
|
34ef2700f2 | ||
|
|
e81f88e676 | ||
|
|
e96a8af9ef | ||
|
|
d8393d8500 | ||
|
|
38742d0668 | ||
|
|
0c9f6e0923 | ||
|
|
56d6347209 | ||
|
|
44b74e6135 | ||
|
|
f31417a85b | ||
|
|
1c6967594c | ||
|
|
2db582339a | ||
|
|
7f6386dd8c | ||
|
|
92e33efc04 | ||
|
|
805409cb48 | ||
|
|
93400352ca | ||
|
|
585b943a52 | ||
|
|
a0bf1bca8b | ||
|
|
738e3a0fbe | ||
|
|
05f3171910 | ||
|
|
f0771e4fed | ||
|
|
a3cd3b3fb1 | ||
|
|
323577f1ca | ||
|
|
d22619adc7 | ||
|
|
59f8a58b21 | ||
|
|
4d1f647a89 | ||
|
|
86e5206b4d | ||
|
|
d9ab8e83dc | ||
|
|
d5e783e7e6 | ||
|
|
105ede5b1d | ||
|
|
bb8a25cc9d | ||
|
|
54c3d843be | ||
|
|
4a1407786c | ||
|
|
f20604d3ad | ||
|
|
6b17e80e28 | ||
|
|
f5e3aef86c | ||
|
|
37c8a7c2b2 | ||
|
|
1a788fae7e | ||
|
|
8efc083a70 | ||
|
|
f196a9ebc4 | ||
|
|
06704ce467 | ||
|
|
8524db2903 | ||
|
|
c996b61eae | ||
|
|
60df3bc633 | ||
|
|
025cb6c6ca | ||
|
|
5014b95206 | ||
|
|
a2464fa968 | ||
|
|
033153c8b9 | ||
|
|
1715dadf8f | ||
|
|
a74a857ffe | ||
|
|
f0fe369cfd | ||
|
|
457ba5f24c | ||
|
|
d41b04313a | ||
|
|
34be34e7b3 | ||
|
|
70d75d1c69 | ||
|
|
dbc9fdcfa6 | ||
|
|
76b20f0fb6 | ||
|
|
80ca2052c2 | ||
|
|
3e5d8be507 | ||
|
|
1b76ad4252 | ||
|
|
22772a5fac | ||
|
|
3bd7798eda | ||
|
|
a6c2622f6b | ||
|
|
454412d9d4 | ||
|
|
114e5e1fa0 | ||
|
|
c38d4e275b | ||
|
|
8cc9dea9aa | ||
|
|
1037083ed2 | ||
|
|
7f87a03830 | ||
|
|
d3dcf6d305 | ||
|
|
4815af35cf | ||
|
|
f5fc17c10e | ||
|
|
06ad1dcb02 | ||
|
|
cb4276a2ee | ||
|
|
cf5f9d4080 | ||
|
|
02439bd23d | ||
|
|
f5febef10f | ||
|
|
f89d8d22a0 | ||
|
|
a9eb1f3d27 | ||
|
|
757a1e2353 | ||
|
|
9a0544c4aa | ||
|
|
31c365313b | ||
|
|
b44017ca95 | ||
|
|
08d7c189eb | ||
|
|
289112af27 | ||
|
|
4d2dc4eece | ||
|
|
5e11e87a0c | ||
|
|
712101d8d6 | ||
|
|
828ba5d448 | ||
|
|
03a2ff8e8c | ||
|
|
84d7ba5758 | ||
|
|
75487203cf | ||
|
|
6ad751f079 | ||
|
|
78ddad839e | ||
|
|
812cecdcc4 | ||
|
|
4b49da7d8f | ||
|
|
fc8c37d66c | ||
|
|
0d618e6477 | ||
|
|
d7e2b12d3d | ||
|
|
2ae4ed3999 | ||
|
|
eaaf70e52e | ||
|
|
bb4ebcd198 | ||
|
|
083f3df29d | ||
|
|
429fa1553b | ||
|
|
15809333c1 | ||
|
|
815855964e | ||
|
|
5649a10c44 | ||
|
|
a8b7431a02 | ||
|
|
a5ec70f7fa | ||
|
|
75dfdd4854 | ||
|
|
3f29dd129f | ||
|
|
075f94d438 | ||
|
|
a2b06972d0 | ||
|
|
5bb27131a3 | ||
|
|
d06aacbe25 | ||
|
|
7b19341406 | ||
|
|
838ad58946 | ||
|
|
5488a2d60a | ||
|
|
851182a92e | ||
|
|
f263097008 | ||
|
|
21fee6122f | ||
|
|
68a94da44c | ||
|
|
1ded2f8101 | ||
|
|
e3d436a019 | ||
|
|
ec4a7325da | ||
|
|
d9a5764dda | ||
|
|
9859afe8b7 | ||
|
|
efbd588c9e | ||
|
|
76357fb918 | ||
|
|
e535e064fa | ||
|
|
448068178b | ||
|
|
2a6c476189 | ||
|
|
7132a68a73 | ||
|
|
7308d9ca96 | ||
|
|
b9e2a28915 | ||
|
|
e642f54815 | ||
|
|
465c25e71c | ||
|
|
c6851cd31e | ||
|
|
ebb6cb17ea | ||
|
|
08a0aa9f98 | ||
|
|
8a933c98e3 | ||
|
|
026e4a8982 | ||
|
|
e5bd024de2 | ||
|
|
f05117b6a4 | ||
|
|
4932f417b6 | ||
|
|
363b22bffe | ||
|
|
6d0dcce5e3 | ||
|
|
1194f7e1b3 | ||
|
|
a4087530c5 | ||
|
|
d5fae68b98 | ||
|
|
457483f373 | ||
|
|
7f706bdbe9 | ||
|
|
79a85659aa | ||
|
|
9f7c01599e | ||
|
|
b462f57060 | ||
|
|
28faaf32d2 | ||
|
|
9a0090570c | ||
|
|
5ef22f9921 | ||
|
|
1239f80018 | ||
|
|
49d1d060d6 | ||
|
|
c21c6f3a55 | ||
|
|
eca145e113 | ||
|
|
1b161843ed | ||
|
|
084650e8ba | ||
|
|
dd5c32f889 | ||
|
|
04b75fb3b3 | ||
|
|
582e220a02 | ||
|
|
2e87abefc4 | ||
|
|
2cfa31609e | ||
|
|
64101c8337 | ||
|
|
241fed25a2 | ||
|
|
5ea19ee56f | ||
|
|
209b34e451 | ||
|
|
2ee733399e | ||
|
|
73f228c719 | ||
|
|
ba79673715 | ||
|
|
86b0962063 | ||
|
|
e2a482a6ca | ||
|
|
8924444cc1 | ||
|
|
8e83b0ffd2 | ||
|
|
451384bcdc | ||
|
|
b733164c50 | ||
|
|
72d9d8ba86 | ||
|
|
b73add61c6 | ||
|
|
7c7646c65c | ||
|
|
b1c087451e | ||
|
|
46067a4e56 | ||
|
|
6089ebae2a | ||
|
|
58ec9f4676 | ||
|
|
f0f72f3bdd | ||
|
|
0ab3267541 | ||
|
|
995e76d323 | ||
|
|
d4f8d98d2b | ||
|
|
9df1716480 | ||
|
|
62a9f805c2 | ||
|
|
9c34e64d0e | ||
|
|
84ea95a641 | ||
|
|
5e50ed15bd | ||
|
|
b0bff55dac | ||
|
|
81cad34170 | ||
|
|
c38379e229 | ||
|
|
49ae88ff98 | ||
|
|
33548c95aa | ||
|
|
1c2748c04b | ||
|
|
40041cd727 | ||
|
|
cfc6374995 | ||
|
|
e3f1545cdb | ||
|
|
bc853da09a | ||
|
|
7bcece7f76 | ||
|
|
168f48eea9 | ||
|
|
fd81f24030 | ||
|
|
d851a8147d | ||
|
|
e0e4099750 | ||
|
|
adc54c8c51 | ||
|
|
0350838438 | ||
|
|
5f10d9af5e | ||
|
|
07945ef8a6 | ||
|
|
35d7fa4a85 | ||
|
|
316de42cd9 | ||
|
|
ad3a0d4c39 | ||
|
|
c6ca365e26 | ||
|
|
589d21bac0 | ||
|
|
0eb7bcd013 | ||
|
|
87f694f698 | ||
|
|
fbb6870310 | ||
|
|
81efe25fbc | ||
|
|
63d440677b | ||
|
|
2e0df47581 | ||
|
|
a84ced70a6 | ||
|
|
4a9498e25e | ||
|
|
1978e218f4 | ||
|
|
2ab912c4f4 | ||
|
|
ca329fa2ff | ||
|
|
bc2256fc86 | ||
|
|
db122ed8c5 | ||
|
|
28d7f106b2 | ||
|
|
d6e46adfde | ||
|
|
93109fe9b8 | ||
|
|
439992d96f | ||
|
|
ff1653383b | ||
|
|
f94fe0b325 | ||
|
|
cc543ae33a | ||
|
|
5df1e62d53 | ||
|
|
1dff6f81fc | ||
|
|
f0d85f4c4e | ||
|
|
1801f9cb06 | ||
|
|
4be018ad15 | ||
|
|
5dcf060975 | ||
|
|
de7672bdfb | ||
|
|
975da2c057 | ||
|
|
59f6b1f0c8 | ||
|
|
ae38a85b19 | ||
|
|
324dbbcfc8 | ||
|
|
ecf2d95318 | ||
|
|
a1af272180 | ||
|
|
72f0793aeb | ||
|
|
9770b77df4 | ||
|
|
0f91de389a | ||
|
|
e150be6a3e | ||
|
|
d71b13f2a2 | ||
|
|
66c88b37a4 | ||
|
|
a7198d2e83 | ||
|
|
d3584b5820 | ||
|
|
e2e6267dd7 | ||
|
|
23ce94fdae | ||
|
|
aa5baecdc3 | ||
|
|
601b2aa6bb | ||
|
|
d09ba38dbd | ||
|
|
b847c2b7b5 | ||
|
|
30e44cb533 | ||
|
|
d43b83081b | ||
|
|
699363c548 | ||
|
|
6ea3eb4ba6 | ||
|
|
4aa20b6f6a | ||
|
|
9c8ea27238 | ||
|
|
22c515b0e7 | ||
|
|
7f5a623b37 | ||
|
|
c7cf73ff05 | ||
|
|
4aab425cef | ||
|
|
a4ab8e712d | ||
|
|
fd75326bb8 | ||
|
|
51e09ecfcc | ||
|
|
0d9666639f | ||
|
|
6c26c09685 | ||
|
|
819f650b48 | ||
|
|
353eba6365 | ||
|
|
063302b91d | ||
|
|
562b51bc2f | ||
|
|
e33a6f9a05 | ||
|
|
b9db4e7704 | ||
|
|
3270d9c3a7 | ||
|
|
6d7399f96c | ||
|
|
886ef87bc5 | ||
|
|
1e5dc9efe7 | ||
|
|
72a53c0c09 | ||
|
|
05e75c9a26 | ||
|
|
8c7d254244 | ||
|
|
28ec66bf3b | ||
|
|
9a5a0d7a2b | ||
|
|
a18d88a3f1 | ||
|
|
c49d68e457 | ||
|
|
13fd265e49 | ||
|
|
ea7b03a00c | ||
|
|
1336920a1a | ||
|
|
024d14d529 | ||
|
|
61089c1128 | ||
|
|
8a74210049 | ||
|
|
a45a1b19b6 | ||
|
|
9199784a23 | ||
|
|
f3d3c62809 | ||
|
|
7e2bd52f25 | ||
|
|
e65dd15edc | ||
|
|
c7e447db6f | ||
|
|
fec976a494 | ||
|
|
f81615f8b6 | ||
|
|
c18b49a4a1 | ||
|
|
12caceb02b | ||
|
|
872eabefba | ||
|
|
30f71ab444 | ||
|
|
fe04481ca3 | ||
|
|
7766e8edcd | ||
|
|
31d417c9d3 | ||
|
|
feda929f0b | ||
|
|
5ed29197cf | ||
|
|
ff5f3e12d3 | ||
|
|
6718193dee | ||
|
|
d63ac7df1c | ||
|
|
e462ff3f4b | ||
|
|
8f84b767d2 | ||
|
|
240180405c | ||
|
|
edca6495fc | ||
|
|
b7d746ee09 | ||
|
|
8a9b753b01 | ||
|
|
83f3fcd913 | ||
|
|
445fc696c9 | ||
|
|
781ffa5574 | ||
|
|
8fe3b1a368 | ||
|
|
039075736c | ||
|
|
492e2362be | ||
|
|
1acee209be | ||
|
|
1bead03151 | ||
|
|
e64dc51a17 | ||
|
|
7f4df49933 | ||
|
|
2bc77ee0cd | ||
|
|
b25adf7f57 | ||
|
|
6785c48709 | ||
|
|
808e674503 | ||
|
|
6b2650282d | ||
|
|
475be2ee30 | ||
|
|
12e1da4ef2 | ||
|
|
780d072bb7 | ||
|
|
bd0c2385e2 | ||
|
|
689720b4b0 | ||
|
|
69c48e2770 | ||
|
|
aa934ad725 | ||
|
|
8596ca607d | ||
|
|
191a14e6c0 | ||
|
|
9c5df507ae | ||
|
|
643ea9e523 | ||
|
|
90be44738d | ||
|
|
dbbf6d8c1f | ||
|
|
ce2a08373e | ||
|
|
d9e2229335 | ||
|
|
61454e2030 | ||
|
|
6f79e8145e | ||
|
|
a39e6846fe | ||
|
|
f7e5a5cf92 | ||
|
|
3574c8de5c | ||
|
|
b09ab4d403 | ||
|
|
9d251b5876 | ||
|
|
a3ecb520cb | ||
|
|
60c4673992 | ||
|
|
7c597539ff | ||
|
|
8b80c12aa4 | ||
|
|
9c802c7b0c | ||
|
|
00c0ba9863 | ||
|
|
ca11656844 | ||
|
|
9d27730a4a | ||
|
|
4ce0ca702c | ||
|
|
242e28ddb9 | ||
|
|
40d20b0f9c | ||
|
|
1997f4af51 | ||
|
|
347cd063a3 | ||
|
|
d157a94f89 | ||
|
|
12e4186972 | ||
|
|
46dbd320d3 | ||
|
|
07c70361da | ||
|
|
b92611f4e0 | ||
|
|
9f922b1c4e | ||
|
|
c0cf6b2551 | ||
|
|
eff3221f7d | ||
|
|
74a4519a33 | ||
|
|
20acf7cfb2 | ||
|
|
214f04d7a2 | ||
|
|
be57004257 | ||
|
|
99bc34b2da | ||
|
|
f65b5e3ddd | ||
|
|
dc10492b84 | ||
|
|
6f7c10537b | ||
|
|
7f503cfc21 | ||
|
|
9dbef0c20a | ||
|
|
923166b4e3 | ||
|
|
b420128e40 | ||
|
|
8875640eb6 | ||
|
|
f29c3a4c49 | ||
|
|
2b3dff46aa | ||
|
|
7776a6ce23 | ||
|
|
f52218748f | ||
|
|
8db949734a | ||
|
|
dc37009737 | ||
|
|
bb5bdfb9b2 | ||
|
|
d7ce5eb651 | ||
|
|
730ed8bc69 | ||
|
|
32b8076c82 | ||
|
|
8b1f9a1b0e | ||
|
|
0d49361f50 | ||
|
|
26f630d9d6 | ||
|
|
7481874ba2 | ||
|
|
9fac3ecd81 | ||
|
|
52a5ce95b6 | ||
|
|
8a84cc2627 | ||
|
|
f361c22fce | ||
|
|
61179ec67d | ||
|
|
59fc5955ba | ||
|
|
a348585e76 | ||
|
|
6e1155adc6 | ||
|
|
e853ba6244 | ||
|
|
7a4f62a793 | ||
|
|
4b9e15bbbc | ||
|
|
fb40ae7264 | ||
|
|
f629047be2 | ||
|
|
278d8adf1b | ||
|
|
87087d55aa | ||
|
|
cf72467510 | ||
|
|
3a0f40633a | ||
|
|
12a6b75bfb | ||
|
|
db8e7817a3 | ||
|
|
8d135bc1fc | ||
|
|
2c00f0ffab | ||
|
|
9adce54987 | ||
|
|
46e95fe7eb | ||
|
|
090c9ea4d7 | ||
|
|
264611bb71 | ||
|
|
d51d147b92 | ||
|
|
5e1d627834 | ||
|
|
37b23bdc47 | ||
|
|
9e1f97811e | ||
|
|
647eb7299e | ||
|
|
027652e80a | ||
|
|
185d380c36 | ||
|
|
a0a944310f | ||
|
|
61c6c98fca | ||
|
|
188b8d2d1a | ||
|
|
9008b5c4e7 | ||
|
|
f5ad59803e | ||
|
|
c6b0e38fec | ||
|
|
92e69570e1 | ||
|
|
81d1d7ba13 | ||
|
|
62f7a0c54b | ||
|
|
3328e71805 | ||
|
|
665fc2b7f9 | ||
|
|
dfd17bc96c | ||
|
|
d7e3dbac26 | ||
|
|
905182bf2e | ||
|
|
a0146290ee | ||
|
|
173aa22432 | ||
|
|
9e5b871ebe | ||
|
|
8824ce55ec | ||
|
|
155edf5533 | ||
|
|
6d06e1f89d | ||
|
|
6d1e2c47d3 | ||
|
|
8b9b0346cb | ||
|
|
41924f7e66 | ||
|
|
2340045dd1 | ||
|
|
0d11817e3f | ||
|
|
a8cb209717 | ||
|
|
ddfbf1d6c4 | ||
|
|
cf45ffddf1 | ||
|
|
2e0ea51c30 | ||
|
|
8639faeffc | ||
|
|
0f7f8c7330 | ||
|
|
808f72409f | ||
|
|
f8e2d29372 | ||
|
|
22dec27c65 | ||
|
|
89b3806a7a | ||
|
|
b6bedf9253 | ||
|
|
0d4983043b | ||
|
|
f9ff3fe168 | ||
|
|
4a25c5323f | ||
|
|
9b4e2d3bb8 | ||
|
|
3915efcf92 | ||
|
|
4591ff8522 | ||
|
|
e3491797f3 | ||
|
|
6eee167675 | ||
|
|
16b965b28a | ||
|
|
5125410efd | ||
|
|
abf73193fd | ||
|
|
1a4da2a8de | ||
|
|
991fbaec86 | ||
|
|
fb399278b3 | ||
|
|
ff1b643cf4 | ||
|
|
b868092365 | ||
|
|
80fdc6849f | ||
|
|
a73acedc4d | ||
|
|
7ffeb44e77 | ||
|
|
25ffcb952b | ||
|
|
5ae1488864 | ||
|
|
fb3ff165db | ||
|
|
cdf59bb877 | ||
|
|
b6371dde23 | ||
|
|
3989bfa832 | ||
|
|
083ac1e2d6 | ||
|
|
5a4b553a60 | ||
|
|
b1135ef566 | ||
|
|
1928d1e00f | ||
|
|
a369f7f387 | ||
|
|
33d9801dfe | ||
|
|
8c7a031cca | ||
|
|
9484d87e76 | ||
|
|
4b6822d6e5 | ||
|
|
7241a0529b | ||
|
|
66083b4e50 | ||
|
|
740e1ccd5b | ||
|
|
bc03f2ed17 | ||
|
|
4b5cb86d87 | ||
|
|
b877d6088b | ||
|
|
26cfd439e1 | ||
|
|
f631b3cc64 | ||
|
|
8f4a43de75 | ||
|
|
e2d4a41d9a | ||
|
|
bb58d9b4d6 | ||
|
|
93ebff1055 | ||
|
|
08aec1c09a | ||
|
|
8ca98a56fe | ||
|
|
705f53e3e5 | ||
|
|
adaf069d20 | ||
|
|
d7be7d8660 | ||
|
|
faddee86b6 | ||
|
|
c4fcc65d16 | ||
|
|
890631d33b | ||
|
|
8e8145bb48 | ||
|
|
d73d6719a5 | ||
|
|
eafcf66f4d | ||
|
|
3419bee198 | ||
|
|
a8fad8193b | ||
|
|
4368fad393 | ||
|
|
ca695c38cd | ||
|
|
ab93fdbf10 | ||
|
|
8fd7697a45 | ||
|
|
6e0f98a809 | ||
|
|
1121a60912 | ||
|
|
d2af0152d0 | ||
|
|
e7b4bd2fe4 | ||
|
|
fcd8bdd1b3 | ||
|
|
e6f140f575 | ||
|
|
37f825ab6d | ||
|
|
bfe4c45fcf | ||
|
|
8901d3ce64 | ||
|
|
f95370124b | ||
|
|
2564343816 | ||
|
|
03734eb761 | ||
|
|
92104f4ab7 | ||
|
|
408a2498d0 | ||
|
|
2641966285 | ||
|
|
29d63a9fdd | ||
|
|
ca94b236a8 | ||
|
|
fa1ec30ba5 | ||
|
|
8d473001aa | ||
|
|
ceaf0bd745 | ||
|
|
a96849bc89 | ||
|
|
2b1423aebe | ||
|
|
09dc2265fe | ||
|
|
a1fa139ef1 | ||
|
|
373332141f | ||
|
|
ecf2cf15b5 | ||
|
|
4ee0831d93 | ||
|
|
7df2a88c13 | ||
|
|
3d52556c67 | ||
|
|
df26a500c4 | ||
|
|
437b160a3f | ||
|
|
5c87b82e0c | ||
|
|
182b7ae84d | ||
|
|
e6e254bd86 | ||
|
|
3b4e7a0bfe | ||
|
|
a0887bb5b3 | ||
|
|
5c6a592aba | ||
|
|
7f2bc79d5f | ||
|
|
837a61acf3 | ||
|
|
a465218ba3 | ||
|
|
5971eed72a | ||
|
|
1ead6eb916 | ||
|
|
c8c0b24420 | ||
|
|
0da5d4376d | ||
|
|
1b8224030b | ||
|
|
ed3ec3fa8b | ||
|
|
44fe1b31ba | ||
|
|
0856d3d5c9 | ||
|
|
f6b74ea836 | ||
|
|
aa98ca49e5 | ||
|
|
44d35c2351 | ||
|
|
df8eb7a000 | ||
|
|
ac061c8750 | ||
|
|
656d3e55ac | ||
|
|
e0cef71700 | ||
|
|
47f3788f55 | ||
|
|
19d1f70458 | ||
|
|
840a236e61 | ||
|
|
50641287f8 | ||
|
|
0bc072aa65 | ||
|
|
9d7d665520 | ||
|
|
819ea94e7b | ||
|
|
40753568df | ||
|
|
b07b998304 | ||
|
|
8793aed561 | ||
|
|
377a50bc09 | ||
|
|
fe5a43fbdf | ||
|
|
43c103e436 | ||
|
|
7f44220220 | ||
|
|
0df1610ca9 | ||
|
|
24c8b9e02d | ||
|
|
01b311f2ba | ||
|
|
a2bb3182f4 | ||
|
|
c86e15a310 | ||
|
|
6ff4f096a3 | ||
|
|
862e5a95e7 | ||
|
|
73e2c7e849 | ||
|
|
0b0937e233 | ||
|
|
6bf114859f | ||
|
|
db6d67eeb7 | ||
|
|
e820b13f9f | ||
|
|
d8d82441c3 | ||
|
|
63be8afd35 | ||
|
|
1166db7834 | ||
|
|
4f489e1854 | ||
|
|
3662f4f256 | ||
|
|
a345d89aac | ||
|
|
e8f8ebb112 | ||
|
|
9ab275df5d | ||
|
|
66c1754eb8 | ||
|
|
b03335eb94 | ||
|
|
31e3677111 | ||
|
|
e67bab1f5c | ||
|
|
ceb6667112 | ||
|
|
cafba0b361 | ||
|
|
9bc1a51def | ||
|
|
d215885dab | ||
|
|
1dad5b5c3a | ||
|
|
5cc5ee4e87 | ||
|
|
68a1c93fbb | ||
|
|
9c6302fa50 | ||
|
|
d60440e512 | ||
|
|
957b9319cb | ||
|
|
9a28b9d1f1 | ||
|
|
543d061251 | ||
|
|
acba90b293 | ||
|
|
8992e9988f | ||
|
|
3ce87b9cb3 | ||
|
|
cbe7b3046c | ||
|
|
62344cb814 | ||
|
|
72d45a5fd2 | ||
|
|
c4e4dd501d | ||
|
|
e8d2b32a14 | ||
|
|
f492909e42 | ||
|
|
3e4efcf297 | ||
|
|
4ccadac148 | ||
|
|
6e148c18b0 | ||
|
|
75f849982e | ||
|
|
8722ef45ac | ||
|
|
3d96765b1f | ||
|
|
7ea17750a1 | ||
|
|
02d052c071 | ||
|
|
4c52fe11d3 | ||
|
|
663e1f1a4b | ||
|
|
079310c67e | ||
|
|
5cf7f1f886 | ||
|
|
b347c78a8c | ||
|
|
c70d92cf09 | ||
|
|
4590bed56e | ||
|
|
7c00d118f3 | ||
|
|
9f64af859e | ||
|
|
007aa776cb | ||
|
|
66bc092edd | ||
|
|
140a88ee12 | ||
|
|
88ae24855a | ||
|
|
356884ea53 | ||
|
|
68e37fff79 | ||
|
|
6958e71efd | ||
|
|
d0618182d1 | ||
|
|
e8891e27dd | ||
|
|
adb80933ee | ||
|
|
23b942c7da | ||
|
|
7eef7d4636 | ||
|
|
51fba21dd6 | ||
|
|
f42758938d | ||
|
|
e19fd81536 | ||
|
|
73835ded96 | ||
|
|
1ec1a8bd94 | ||
|
|
f0b6d57ba8 | ||
|
|
6aa5d608bf | ||
|
|
21ad2c5744 | ||
|
|
dea1163159 | ||
|
|
c4c2e8cf74 | ||
|
|
5883c35cf3 | ||
|
|
4d2719a424 | ||
|
|
7cf2d0d01c | ||
|
|
5a08b512a7 | ||
|
|
aa6ff6cd64 | ||
|
|
72baa1a786 | ||
|
|
4d72569030 | ||
|
|
f9a3ad14d1 | ||
|
|
89421d292c | ||
|
|
55c6515bac | ||
|
|
1b86f533f7 | ||
|
|
5db6f6a58c | ||
|
|
eeedf6ab28 | ||
|
|
3758cd207b | ||
|
|
c15ede6239 | ||
|
|
e54e31e059 | ||
|
|
8c573aa8e4 | ||
|
|
654559086c | ||
|
|
b0d9679568 | ||
|
|
87fdaf7fa7 | ||
|
|
bb3a365166 | ||
|
|
2be183d980 | ||
|
|
c6dc846838 | ||
|
|
1142f1d59a | ||
|
|
126c470979 | ||
|
|
d679dc3e8b | ||
|
|
d5422dfe89 | ||
|
|
d64237b4f2 | ||
|
|
7b7e4942f2 | ||
|
|
e4c343a587 | ||
|
|
1a8ae21478 | ||
|
|
dd37a5b584 | ||
|
|
eec340e6c0 | ||
|
|
c2fb5ba1f0 | ||
|
|
36d7e17b86 | ||
|
|
5a1dc49428 | ||
|
|
47caf54772 | ||
|
|
6af50a8c44 | ||
|
|
1b27407970 | ||
|
|
4da6306c67 | ||
|
|
f950b7a725 | ||
|
|
930cf9ed04 | ||
|
|
744016e752 | ||
|
|
2fb4e907df | ||
|
|
ef2a815e52 | ||
|
|
33a81d4f3c | ||
|
|
4911df0bf9 | ||
|
|
2b612b5db7 | ||
|
|
bfe81b52ef | ||
|
|
26f6a4beb9 | ||
|
|
532a368606 | ||
|
|
52e9c3053a | ||
|
|
908d1f1ec8 | ||
|
|
6a1120f95b | ||
|
|
2a2780c25c | ||
|
|
7d4152197b | ||
|
|
9c742600ff | ||
|
|
6c4178e107 | ||
|
|
a035bf132a | ||
|
|
05a68030b6 | ||
|
|
b989d157a0 | ||
|
|
46416fb026 | ||
|
|
c5f3228ba7 | ||
|
|
54ed37c95d | ||
|
|
fd79b47d9e | ||
|
|
be8333824b | ||
|
|
db3728f9e4 | ||
|
|
ca2d3f5b48 | ||
|
|
f3a992a55f | ||
|
|
b5d1e7c459 | ||
|
|
eecd89772f | ||
|
|
261587511b | ||
|
|
ff798801fb | ||
|
|
9b4aab0d19 | ||
|
|
02d1fb436d | ||
|
|
1c97bf9019 | ||
|
|
bfc4bb1f4c | ||
|
|
ccdce32562 | ||
|
|
87fdcf14e5 | ||
|
|
a0570f41fb | ||
|
|
6e42cf9952 | ||
|
|
4c3a8ca312 | ||
|
|
a63eb48f03 | ||
|
|
d0214f805e | ||
|
|
d736bd6501 | ||
|
|
5fd42bf216 | ||
|
|
2ce4a11e0a | ||
|
|
55daffc791 | ||
|
|
e5ab8fe3e4 | ||
|
|
3e55d8d9df | ||
|
|
d9f4a196d0 | ||
|
|
768ef8a316 | ||
|
|
a478d02e5d | ||
|
|
b98a7de3ae | ||
|
|
375baf7fe5 | ||
|
|
815e74c93c | ||
|
|
547d6fbc93 | ||
|
|
657b74a084 | ||
|
|
dfee98b66b | ||
|
|
b45a4b9e6c | ||
|
|
3436d0256a | ||
|
|
f65b9f695e | ||
|
|
4056385cd3 | ||
|
|
2627cfd426 | ||
|
|
96d56d43bc | ||
|
|
34b18c00a1 | ||
|
|
2e31ff42ed | ||
|
|
9a3b8f2b10 | ||
|
|
079790bce6 | ||
|
|
d99c4c6d14 | ||
|
|
6839b4d8a4 | ||
|
|
4c47cc6ea8 | ||
|
|
de81649a98 | ||
|
|
d31eab673e | ||
|
|
31189be2c8 | ||
|
|
45dd2c6519 | ||
|
|
5176d2000e | ||
|
|
6f606761e4 | ||
|
|
cfabadffe4 | ||
|
|
eba8f95e58 | ||
|
|
e13af7f5f0 | ||
|
|
7e2da1ff93 | ||
|
|
ca08613292 | ||
|
|
4ab63591a0 | ||
|
|
b7b7e81468 | ||
|
|
5b4f98b03b | ||
|
|
0c7768f5d2 | ||
|
|
8fe6a56dfc | ||
|
|
f396d61633 | ||
|
|
7523cb3489 | ||
|
|
505a9d7c70 | ||
|
|
7b9dae980d | ||
|
|
9f8c0c8cdf | ||
|
|
198777ffab | ||
|
|
b59ba6b7bb | ||
|
|
29c5ca1132 | ||
|
|
05d6f3d1ed | ||
|
|
8cdee4d173 | ||
|
|
c9ed5fbe00 | ||
|
|
536e82de3d | ||
|
|
c59be7c315 | ||
|
|
e698e89968 | ||
|
|
9581764cc8 | ||
|
|
b327bb5bd0 | ||
|
|
02f198d42c | ||
|
|
f03493a252 | ||
|
|
61d2d0263b | ||
|
|
ed477e99a8 | ||
|
|
22f2a05c8a | ||
|
|
a703ecc7e1 | ||
|
|
c90a687179 | ||
|
|
e08689ff0e | ||
|
|
80ffd811c9 | ||
|
|
56b583fc99 | ||
|
|
37a4e108be | ||
|
|
9ca531541d | ||
|
|
e304c554d0 | ||
|
|
a7dfa7a381 | ||
|
|
1449be8d66 | ||
|
|
090d48b636 | ||
|
|
55567bf666 | ||
|
|
5867d84eaa | ||
|
|
4d8f1ab169 | ||
|
|
6a2963be41 | ||
|
|
b10f7b35ee | ||
|
|
dc1bb8992f | ||
|
|
5eb7119821 | ||
|
|
3fa8fc19dc | ||
|
|
a3103587f5 | ||
|
|
1bb11b574f | ||
|
|
405efe6a31 | ||
|
|
341d98e00d | ||
|
|
7eee0f4341 | ||
|
|
0b71b7862a | ||
|
|
e2893a0eba | ||
|
|
0c39a4e193 | ||
|
|
066072b22d | ||
|
|
73663c3703 | ||
|
|
d548503590 | ||
|
|
3a02fc99a2 | ||
|
|
d8ae943d8a | ||
|
|
63dae9ed70 | ||
|
|
97f57f1f2b | ||
|
|
060ba6423e | ||
|
|
f31bbcdaab | ||
|
|
2a4bae54ab | ||
|
|
95df2f66a3 | ||
|
|
c0c63f49b1 | ||
|
|
4e1bef9537 | ||
|
|
8b78727d20 | ||
|
|
4cd7025be4 | ||
|
|
015ce2690d | ||
|
|
3e7f552a63 | ||
|
|
28902d8747 | ||
|
|
421ee7125b | ||
|
|
1a6166b63c | ||
|
|
3828e75b7d | ||
|
|
154da142c7 | ||
|
|
d534592479 | ||
|
|
b2f6ea9116 | ||
|
|
312cd60dd1 | ||
|
|
8bf38bb29b | ||
|
|
6bf522f72f | ||
|
|
9c6a78b678 | ||
|
|
128b169ae2 | ||
|
|
fdd33abe91 | ||
|
|
22882b1ff2 | ||
|
|
213898987d | ||
|
|
a844f8d459 | ||
|
|
850199d7fc | ||
|
|
4282928960 | ||
|
|
8ee206174b | ||
|
|
1a08e24a5c | ||
|
|
086cd0e038 | ||
|
|
356dd89d9f | ||
|
|
42d123318c | ||
|
|
89f160317c | ||
|
|
9ccd1a0362 | ||
|
|
d116d014bc | ||
|
|
7956cabcf4 | ||
|
|
7dd2391e5a | ||
|
|
36c61ad357 | ||
|
|
e0093f236a | ||
|
|
25d60360d5 | ||
|
|
1e5579e3ad | ||
|
|
8c5c32268a | ||
|
|
77d43b2280 | ||
|
|
33e8929e8b | ||
|
|
b79fa9cb9f | ||
|
|
a2812a85bd | ||
|
|
b61ccc1af1 | ||
|
|
e8ff46a8ba | ||
|
|
7caf0e40f4 | ||
|
|
a16508db10 | ||
|
|
351c01d642 | ||
|
|
044c9bed4c | ||
|
|
e333b1d083 | ||
|
|
81bff342b9 | ||
|
|
5ad49de642 | ||
|
|
b45bb5c144 | ||
|
|
49d41a76a0 | ||
|
|
b1732b3298 | ||
|
|
9402596f69 | ||
|
|
096687ae2c | ||
|
|
9372cdb6c7 | ||
|
|
81062638eb | ||
|
|
1d8e54b83e | ||
|
|
30c5600271 | ||
|
|
9f7e5c3a9a | ||
|
|
37c9342717 | ||
|
|
210b5de992 | ||
|
|
8827f8e940 | ||
|
|
58334bf4a1 | ||
|
|
b898a6702c | ||
|
|
6d78a810b9 | ||
|
|
f742fdbf1b | ||
|
|
8c0028055a | ||
|
|
e7026c522d | ||
|
|
c21fc4beda | ||
|
|
8fc4eb8cdf | ||
|
|
edf6fe782e | ||
|
|
3cbb6c4a98 | ||
|
|
b3fac0c56f | ||
|
|
0b063b1f5e | ||
|
|
480f05e676 | ||
|
|
1ac8af34ec | ||
|
|
74f060b309 | ||
|
|
12de0ca463 | ||
|
|
72ed59b65b | ||
|
|
fe369bfa18 | ||
|
|
568a50acc5 | ||
|
|
fbcb756cef | ||
|
|
81eb4ba4f9 | ||
|
|
0cc14d2ab8 | ||
|
|
116661d958 | ||
|
|
0dd70c57cd | ||
|
|
df0aa1c46d | ||
|
|
9b93a47e45 | ||
|
|
06f6912fb9 | ||
|
|
6aedadc982 | ||
|
|
a8d10dab3c | ||
|
|
1ff6ff1d7a | ||
|
|
c708fd65d7 | ||
|
|
8afe4a85dc | ||
|
|
0c497900a2 | ||
|
|
34ff8b0f02 | ||
|
|
77c3684e28 | ||
|
|
93038ea838 | ||
|
|
46348f7cba | ||
|
|
ccc0e45daf | ||
|
|
46ca03b017 | ||
|
|
1bfe3197a5 | ||
|
|
4d2617fe68 | ||
|
|
92e289f9da | ||
|
|
c57fbdce63 | ||
|
|
a8c7558a77 | ||
|
|
c756e7ecbe | ||
|
|
1998c56e84 | ||
|
|
0b92ceec90 | ||
|
|
bdc0278fd1 | ||
|
|
731bcc68ef | ||
|
|
7e5131c4d2 | ||
|
|
f0554fc77c | ||
|
|
c7814b1f04 | ||
|
|
d585b47360 | ||
|
|
4530d95f48 | ||
|
|
c3ac8d0587 | ||
|
|
2ed55b1616 | ||
|
|
0c8d456fd3 | ||
|
|
c7e6e72ce8 | ||
|
|
f3a5e1e97c | ||
|
|
919aa5fc43 | ||
|
|
416c98ffd2 | ||
|
|
013d4b9411 | ||
|
|
8094447183 | ||
|
|
575375d3e0 | ||
|
|
4296ae02dc | ||
|
|
0e40af0515 | ||
|
|
5d3a0e7a41 | ||
|
|
9e4924caf6 | ||
|
|
7f391a5860 | ||
|
|
8ae2aae37a | ||
|
|
5c7249c8fc | ||
|
|
0a8fed1950 | ||
|
|
6dc4b4dc1b | ||
|
|
83b3cf406a | ||
|
|
959b8863eb | ||
|
|
1643ced4e0 | ||
|
|
b2a1840da7 | ||
|
|
b9f20d1e80 | ||
|
|
932d00133d | ||
|
|
0c77781be8 | ||
|
|
83245af1e2 | ||
|
|
7db806a461 | ||
|
|
32a371bf13 | ||
|
|
b25f418411 | ||
|
|
985a4780f5 | ||
|
|
92b15fb1e2 | ||
|
|
92a1f2c6d5 | ||
|
|
7b5182111c | ||
|
|
82b1b81999 | ||
|
|
81b9348e50 | ||
|
|
f0a430f350 | ||
|
|
90f95b7270 | ||
|
|
15e6a93fac | ||
|
|
04e7d54620 | ||
|
|
729dbe16c0 | ||
|
|
974650bc56 | ||
|
|
0899a16333 | ||
|
|
8c9ed833c3 | ||
|
|
5e86e64e18 | ||
|
|
235c789c5e | ||
|
|
3f69a22229 | ||
|
|
ab48d06967 | ||
|
|
18ca950cd2 | ||
|
|
82489b36c8 | ||
|
|
a67b6130f8 | ||
|
|
01541a2577 | ||
|
|
8c70bc0a17 | ||
|
|
9d96074604 | ||
|
|
114a4028f4 | ||
|
|
eab007db6e | ||
|
|
5d0622d2cf | ||
|
|
b342a4ba17 | ||
|
|
9ab9155bf0 | ||
|
|
889eae276e | ||
|
|
2b2a72252b | ||
|
|
13e4568d3b | ||
|
|
86a1ed6d46 | ||
|
|
b3c9936d74 | ||
|
|
92c4dda801 | ||
|
|
3e59ba4563 | ||
|
|
99c95626df | ||
|
|
20a9fc2497 | ||
|
|
27e0621ad8 | ||
|
|
abecff222c | ||
|
|
3f0d91101a | ||
|
|
252be59ac3 | ||
|
|
1987a8c68a | ||
|
|
5088fd0e82 | ||
|
|
2de4d36a0f | ||
|
|
21b4d7cf11 | ||
|
|
4ec07f9ff8 | ||
|
|
fcbf037619 | ||
|
|
a8e4ab433d | ||
|
|
6613ba02ab | ||
|
|
2af7fde83f | ||
|
|
19a0d4bc98 | ||
|
|
9ed49b1f27 | ||
|
|
fb32aeeeb6 | ||
|
|
76cb4037d4 | ||
|
|
b7c89d6f64 | ||
|
|
d56df30a22 | ||
|
|
64908068d9 | ||
|
|
af1530db36 | ||
|
|
9d6560aece | ||
|
|
fe69d59aeb | ||
|
|
caba246e0b | ||
|
|
0eb168ec70 | ||
|
|
731e2dc4c4 | ||
|
|
815bdf3454 | ||
|
|
b36ef9fdb1 | ||
|
|
b65e737f84 | ||
|
|
bd274fdc3c | ||
|
|
fee3f7a716 | ||
|
|
8ac1a66e93 | ||
|
|
ac19249c63 | ||
|
|
301da3662a | ||
|
|
2abff1fec8 | ||
|
|
e474946cb7 | ||
|
|
dd00d6581f | ||
|
|
9a0ca1ebb2 | ||
|
|
f4fc7acf4d | ||
|
|
f1a6cfae0d | ||
|
|
e43e90ed3c | ||
|
|
0ee88fe0dc | ||
|
|
5b8cdf06b3 | ||
|
|
682315e672 | ||
|
|
07e7f2e14d | ||
|
|
366ab95a2f | ||
|
|
520f7b2a77 | ||
|
|
ca723068a1 | ||
|
|
e424a105b3 | ||
|
|
c0b3b3aab8 | ||
|
|
32d2f92413 | ||
|
|
d499332ce3 | ||
|
|
19ce06e0bb | ||
|
|
898e2ff010 | ||
|
|
e33c7fd1c1 | ||
|
|
9b3668423e | ||
|
|
7227af9aac | ||
|
|
7c8194307e | ||
|
|
ea6ff4224e | ||
|
|
36c38063f2 | ||
|
|
9b5fac9e2b | ||
|
|
871d1f8632 | ||
|
|
47991b7d1a | ||
|
|
dfa5e76870 | ||
|
|
c93dd12fae | ||
|
|
dbb1b1e582 | ||
|
|
76388ee160 | ||
|
|
a5ea24311a | ||
|
|
77ce2ff6d1 | ||
|
|
3c11f0acda | ||
|
|
5ec2eee69a | ||
|
|
31875a36fe | ||
|
|
6383104796 | ||
|
|
c50598b78e | ||
|
|
2f0c81d9ad | ||
|
|
c22f89c6bb | ||
|
|
568a23cd35 | ||
|
|
4a5222fa3b | ||
|
|
b99b4159c8 | ||
|
|
4294dfd8fe | ||
|
|
8bedb1f3b9 | ||
|
|
eb7c4c131d | ||
|
|
dc85804a27 | ||
|
|
f0664cd2c7 | ||
|
|
570eb7bc89 | ||
|
|
42a31e512a | ||
|
|
1ee91b4925 | ||
|
|
2be7388696 | ||
|
|
e7b739bb3b | ||
|
|
baf5f7491d | ||
|
|
a2afe2fa1a | ||
|
|
8496a9bebd | ||
|
|
bc5b00781b | ||
|
|
69fee37f00 | ||
|
|
313e2b3de6 | ||
|
|
49be66ae69 | ||
|
|
a0efe6895c | ||
|
|
8ef07e917d | ||
|
|
0bbd002060 | ||
|
|
5e785266a5 | ||
|
|
d3995b7bab | ||
|
|
c353e71ce7 | ||
|
|
5870769e7d | ||
|
|
0d6b7d6f04 | ||
|
|
59d6a51635 | ||
|
|
b4693019f7 | ||
|
|
320d7a89ca | ||
|
|
a3570a1c9f | ||
|
|
79b80dcd07 | ||
|
|
1016f5f26f | ||
|
|
6f6e547e6c | ||
|
|
9284aee3fa | ||
|
|
32726efe88 | ||
|
|
87daf0271c | ||
|
|
70e73a5a65 | ||
|
|
c593c98e6d | ||
|
|
1642798aa6 | ||
|
|
a4b5b532f2 | ||
|
|
588c369615 | ||
|
|
9aa9d4452c | ||
|
|
d7fe25c4fc | ||
|
|
990c5e9f08 | ||
|
|
352c9357df | ||
|
|
1ba4641641 | ||
|
|
6357f23aeb | ||
|
|
60e0047285 | ||
|
|
01d9b3bd0e | ||
|
|
7e2f2f6102 | ||
|
|
51def6535f | ||
|
|
6b428f7587 | ||
|
|
14156b0911 | ||
|
|
e2ba1fa7f8 | ||
|
|
36b589c2db | ||
|
|
f829aa76d7 | ||
|
|
cdf1a5fe47 | ||
|
|
abf146707f | ||
|
|
a72051e96f | ||
|
|
235e7c143c | ||
|
|
797622ba66 | ||
|
|
522d6eed92 | ||
|
|
9d1d6ea4c5 | ||
|
|
0afd506a41 | ||
|
|
39342cd662 | ||
|
|
051a3ac122 | ||
|
|
f842a321ba | ||
|
|
3cd2dd65d3 | ||
|
|
9dfb837e3f | ||
|
|
4ab63b569f | ||
|
|
8d390d256d | ||
|
|
5ce7e0b108 | ||
|
|
4eec5e06fc | ||
|
|
71c2058cc8 | ||
|
|
f200d39d23 | ||
|
|
7932845ac5 | ||
|
|
94bda6ac9e | ||
|
|
7a65f80406 | ||
|
|
36ab58dad9 | ||
|
|
e9be9e3761 | ||
|
|
b54645c86c | ||
|
|
e4063b1ba8 | ||
|
|
c24a4009c8 | ||
|
|
7b928c4d41 | ||
|
|
4d50eae3c9 | ||
|
|
c3a5e0592d | ||
|
|
207aef7cb3 | ||
|
|
fbbd9ae249 | ||
|
|
7aa591ffbd | ||
|
|
1fd91de50d | ||
|
|
ab77d8430c | ||
|
|
0c3227cf8e | ||
|
|
8492ceee09 | ||
|
|
1f710b9b78 | ||
|
|
7bed200bf5 | ||
|
|
c6f683b532 | ||
|
|
a2604f5156 | ||
|
|
5ae7f683d6 | ||
|
|
f953c89979 | ||
|
|
bb8aab02ea | ||
|
|
b87ad2df54 | ||
|
|
af0c03ff6a | ||
|
|
8859900537 | ||
|
|
4f763e2109 | ||
|
|
c9e2f94daf | ||
|
|
130852ab85 | ||
|
|
65fa8f96b4 | ||
|
|
0a84e9e363 | ||
|
|
163c69454b | ||
|
|
49d3fde0f3 | ||
|
|
75167fb65b | ||
|
|
675588f780 | ||
|
|
2d6f94edd8 | ||
|
|
bb67e2254e | ||
|
|
52774c7d6d | ||
|
|
6d2abc4e74 | ||
|
|
247c66ef4b | ||
|
|
01686b8e60 | ||
|
|
0317d6a862 | ||
|
|
2a316b1ffa | ||
|
|
4875450053 | ||
|
|
19184ca8a0 | ||
|
|
654c3d324b | ||
|
|
c5b4811f16 | ||
|
|
7a9dc4fd59 | ||
|
|
9b9f0e5607 | ||
|
|
590f6cb7b3 | ||
|
|
e79096626a | ||
|
|
1076fac40f | ||
|
|
14a4a415a2 | ||
|
|
524355b59c | ||
|
|
332d074d32 | ||
|
|
b120146cdc | ||
|
|
36fe49f3f5 | ||
|
|
7587458f99 | ||
|
|
e511ecd76e | ||
|
|
d1e69f5957 | ||
|
|
bcfbd5eba9 | ||
|
|
2c35ee11b3 | ||
|
|
bd0c747d98 | ||
|
|
9fa3db395b | ||
|
|
52a41ceb04 | ||
|
|
f5fb066975 | ||
|
|
cd6a0fa678 | ||
|
|
e735420dd8 | ||
|
|
bc2f17c840 | ||
|
|
8c459cac0f | ||
|
|
878d2d9260 | ||
|
|
e65d67266d | ||
|
|
0d1045821c | ||
|
|
45d526dda2 | ||
|
|
e52f998e78 | ||
|
|
42ed3b9355 | ||
|
|
563b4cb1ec | ||
|
|
45bad231cf | ||
|
|
d76bd2484b | ||
|
|
445b60bb63 | ||
|
|
3214e0e41e | ||
|
|
c61230e145 | ||
|
|
fac6a29226 | ||
|
|
7a8f414748 | ||
|
|
9f450d282e | ||
|
|
31787067e3 | ||
|
|
c0c0af9b14 | ||
|
|
1a769b23e2 | ||
|
|
ae002abafc | ||
|
|
d1e472d482 | ||
|
|
c80e43ad0d | ||
|
|
fdd395e2b6 | ||
|
|
31a25d9c16 | ||
|
|
356295c361 | ||
|
|
d10681b6d1 | ||
|
|
0602410aa8 | ||
|
|
e094437168 | ||
|
|
14bca4bbf7 | ||
|
|
ea55c10c4d | ||
|
|
75cde40b0e | ||
|
|
5434b4987f | ||
|
|
2ee0be7466 | ||
|
|
1112768adc | ||
|
|
2784a7cc92 | ||
|
|
86b599df89 | ||
|
|
b09f998d6c | ||
|
|
88f7661172 | ||
|
|
29c96c0119 | ||
|
|
d8c6e54c68 | ||
|
|
df053eb016 | ||
|
|
d1715f7711 | ||
|
|
240282c72d | ||
|
|
9e8dd6ea21 | ||
|
|
32806a20c9 | ||
|
|
bdeb5895f6 | ||
|
|
3944b8aaee | ||
|
|
34dcfbbf49 | ||
|
|
91fec43866 | ||
|
|
aa2d196a79 | ||
|
|
6e66cffb92 | ||
|
|
54befb2814 | ||
|
|
74fba76b85 | ||
|
|
2b8996e965 | ||
|
|
a9aad547b2 | ||
|
|
4673af6fd8 | ||
|
|
33ae81ce05 | ||
|
|
57092ee788 | ||
|
|
180ca458ad | ||
|
|
70e9e1c706 | ||
|
|
075c43ce0e | ||
|
|
10fddc51bb | ||
|
|
b32b319198 | ||
|
|
2d14fde671 | ||
|
|
9662b8fbee | ||
|
|
98cd2746ef | ||
|
|
aa881c60e7 | ||
|
|
ea18e4129c | ||
|
|
6980e2b959 | ||
|
|
7e7ec83c12 | ||
|
|
b12fd45df1 | ||
|
|
a8340c24c3 | ||
|
|
9f66421ae7 | ||
|
|
50584c2e50 | ||
|
|
5b6966042d | ||
|
|
dc859da0cd | ||
|
|
151eb6cbd6 | ||
|
|
7be4e1901a | ||
|
|
16db591bbf | ||
|
|
05a55e5eb2 | ||
|
|
f49f3fb2a6 | ||
|
|
3c9ef8d199 | ||
|
|
dcd84b2b8f | ||
|
|
4a89119f0a | ||
|
|
bc1c30a7bf | ||
|
|
33cffbf28b | ||
|
|
a18b68116c | ||
|
|
d5acf15bca | ||
|
|
84f970af68 | ||
|
|
969f636bb7 | ||
|
|
6939aee20a | ||
|
|
ab2a02a555 | ||
|
|
70038e0764 | ||
|
|
e730ef5e11 | ||
|
|
b47146de45 | ||
|
|
835ad5aaf1 | ||
|
|
ac645c8617 | ||
|
|
97b229b2c7 | ||
|
|
669c24ebd5 | ||
|
|
b801fdbab2 | ||
|
|
bf495953e2 | ||
|
|
45b165deec | ||
|
|
09169578e8 | ||
|
|
43b2366927 | ||
|
|
6bb5bb9403 | ||
|
|
f015a69eec | ||
|
|
99568508dd | ||
|
|
e8515344dd | ||
|
|
edc873a570 | ||
|
|
1a03e96ab2 | ||
|
|
89e0bb4f0a | ||
|
|
7d0fd60908 | ||
|
|
6b20523df4 | ||
|
|
8c4b8271d8 | ||
|
|
e9a612647e | ||
|
|
69291c0574 | ||
|
|
28404ef149 | ||
|
|
a5f8230def | ||
|
|
2dc073dcd6 | ||
|
|
1894cb35d2 | ||
|
|
39171de5de | ||
|
|
5aa5a0acbc | ||
|
|
a4518e630a | ||
|
|
94975f5ea6 | ||
|
|
7e98838d96 | ||
|
|
cd37420b07 | ||
|
|
16dde5c772 | ||
|
|
55cb6b39db | ||
|
|
89d13b2285 | ||
|
|
e8c9c196ff | ||
|
|
db314a238f | ||
|
|
2c85a6d4ab | ||
|
|
b683e14e80 | ||
|
|
1b64b0468a | ||
|
|
085fb83294 | ||
|
|
ba45095fa8 | ||
|
|
b8e5ffa9f7 | ||
|
|
b4bff9e032 | ||
|
|
0c461bc4e2 | ||
|
|
edd606563f | ||
|
|
a33b2a5294 | ||
|
|
298e1c4471 | ||
|
|
1c70cdc10b | ||
|
|
160e4bb530 | ||
|
|
e69ba8dd96 | ||
|
|
fb804e99f0 | ||
|
|
1707cbcb54 | ||
|
|
e55f4c3eb2 | ||
|
|
6d6a630c31 | ||
|
|
ff2990e8e5 | ||
|
|
d679aff0fb | ||
|
|
1a3272b980 | ||
|
|
7bed5e025a | ||
|
|
603a444905 | ||
|
|
a002958448 | ||
|
|
cb4bc37424 | ||
|
|
29d22c0598 | ||
|
|
a38c7c34ac | ||
|
|
8d690ce4ff | ||
|
|
2569568a03 | ||
|
|
0fc6f917e6 | ||
|
|
ec0d012b24 | ||
|
|
2c6ff6b5b8 | ||
|
|
1257f01027 | ||
|
|
2cd4b171a1 | ||
|
|
fad6830863 | ||
|
|
0cb6906c4d | ||
|
|
4c19b93c30 | ||
|
|
66262bb20b | ||
|
|
4abb0754c7 | ||
|
|
78c53bf3ad | ||
|
|
810d666d84 | ||
|
|
67699f0bb6 | ||
|
|
46274948c0 | ||
|
|
6165f1b405 | ||
|
|
28e3a842ef | ||
|
|
6d90f1d45d | ||
|
|
09642c347d | ||
|
|
37a4221e43 | ||
|
|
9831b222b5 | ||
|
|
7b6f44fb74 | ||
|
|
2d0e06f785 | ||
|
|
399f4d0ea3 | ||
|
|
26a668a875 | ||
|
|
a5bc8497cf | ||
|
|
bf96262b6e | ||
|
|
96635a98f5 | ||
|
|
1155fa1fe9 | ||
|
|
1875d31731 | ||
|
|
6f855fd14e | ||
|
|
08e392bb46 | ||
|
|
66d63e0546 | ||
|
|
7ee56fe8bc | ||
|
|
669d04ee48 | ||
|
|
cb1b37326e | ||
|
|
7bb73bee67 | ||
|
|
7286ddc338 | ||
|
|
54ef65ced9 | ||
|
|
4fb6bef04c | ||
|
|
7d1f9e33fe | ||
|
|
63c676ebfe | ||
|
|
7d5e7f5c73 | ||
|
|
15a8b97410 | ||
|
|
874162df87 | ||
|
|
0c8b9d8539 | ||
|
|
7c6579b264 | ||
|
|
fcaf6b7923 | ||
|
|
e3473721f6 | ||
|
|
28799c62a9 | ||
|
|
224a79840d | ||
|
|
c21fef7c72 | ||
|
|
99ae3e0f7f | ||
|
|
02a4161ecb | ||
|
|
1a68c3947d | ||
|
|
4ac58b3f44 | ||
|
|
a9758e6997 | ||
|
|
bb546c6bb7 | ||
|
|
d877a6f1ad | ||
|
|
ef7a563d97 | ||
|
|
df33772eba | ||
|
|
97c1cc8ed9 | ||
|
|
9f347a170a | ||
|
|
2f7cd4426d | ||
|
|
854f256470 | ||
|
|
5d0b40f752 | ||
|
|
27a2853ee8 | ||
|
|
67f6b80312 | ||
|
|
016037adc1 | ||
|
|
70d5c1034d | ||
|
|
ed6fb8754f | ||
|
|
6d08a9b11c | ||
|
|
cf6aa7cf79 | ||
|
|
b7f34b9da6 | ||
|
|
f82a6efda1 | ||
|
|
f869c02395 | ||
|
|
43e0217008 | ||
|
|
6c4e57aae0 | ||
|
|
f89538c6f7 | ||
|
|
1ee0085c22 | ||
|
|
d56590c6e6 | ||
|
|
d08a04959c | ||
|
|
2762f74ce5 | ||
|
|
556ca71c3a | ||
|
|
42be2d5031 | ||
|
|
6ebcf6eec5 | ||
|
|
25b78fb7e1 | ||
|
|
876e22b092 | ||
|
|
f9f9c16cb0 | ||
|
|
f63f5d0ac6 | ||
|
|
44dbbeb2a3 | ||
|
|
a881090e65 | ||
|
|
670dd2dd96 | ||
|
|
3d9fce02a4 | ||
|
|
dea401fc7c | ||
|
|
cca21fcd32 | ||
|
|
ab6b1e2c32 | ||
|
|
1baf04f786 | ||
|
|
59d9b3c6b4 | ||
|
|
f7b6fcf684 | ||
|
|
df3ec9a629 | ||
|
|
ce05b7a041 | ||
|
|
6bc4bf308b | ||
|
|
2ddb84f457 | ||
|
|
6b82cc7510 | ||
|
|
2764f4d455 | ||
|
|
28fecc428f | ||
|
|
290cc146c8 | ||
|
|
db4d46a584 | ||
|
|
8ed2e51dde | ||
|
|
33702c09a6 | ||
|
|
45aeca3753 | ||
|
|
deae7dfb4d | ||
|
|
8851a661c0 | ||
|
|
2d327961da | ||
|
|
2af043ebdd | ||
|
|
ac6d14113a | ||
|
|
398db72b44 | ||
|
|
32777e17c4 | ||
|
|
a3da3299fa | ||
|
|
3a41efbea8 | ||
|
|
e763db7102 | ||
|
|
e121295735 | ||
|
|
7c1c405a64 | ||
|
|
2b504ce5ab | ||
|
|
5d7c95a34d | ||
|
|
504c934fc9 | ||
|
|
81b0223f73 | ||
|
|
6d1e410bfd | ||
|
|
26c5c6152d | ||
|
|
d83bf0ebaf | ||
|
|
5adfe9a552 | ||
|
|
883f461dc7 | ||
|
|
8595ebc258 | ||
|
|
2bd31f4560 | ||
|
|
6df85ecadd | ||
|
|
07829918e4 | ||
|
|
b0d400b6eb | ||
|
|
706cb895ad | ||
|
|
45bf539b3c | ||
|
|
0923981f8d | ||
|
|
b0ac14363d | ||
|
|
5d346aba37 | ||
|
|
124cb15ebe | ||
|
|
a244ab898d | ||
|
|
3c551590eb | ||
|
|
10e30cccbc | ||
|
|
806a6b86a2 | ||
|
|
9719fdf5cc | ||
|
|
6d8764f8cb | ||
|
|
d9fd9cb408 | ||
|
|
39a16f9a7f | ||
|
|
f3ea8d012f | ||
|
|
7710ec0aba | ||
|
|
c97bd78cd0 | ||
|
|
728c5aa86e | ||
|
|
83d68ca293 | ||
|
|
47d7561db4 | ||
|
|
7d993e8319 | ||
|
|
1d1a597b22 | ||
|
|
23082f9300 | ||
|
|
ea1a7f9376 | ||
|
|
1b9aa63096 | ||
|
|
45b8fd0100 | ||
|
|
11bde53069 | ||
|
|
f115ee18c4 | ||
|
|
162a56232c | ||
|
|
1796c7bab8 | ||
|
|
65ad76479a | ||
|
|
422db04ec8 | ||
|
|
d12f60fe37 | ||
|
|
194c1c991c | ||
|
|
de6f0ef8eb | ||
|
|
3e8e2222c1 | ||
|
|
1620327a33 | ||
|
|
b1131e3667 | ||
|
|
cd8a92c30b | ||
|
|
10030c4959 | ||
|
|
db0250ac08 | ||
|
|
0a6b605760 | ||
|
|
81ac2375e5 | ||
|
|
6bcaca6cd7 | ||
|
|
ec8375252e | ||
|
|
766aa1762f | ||
|
|
5165e0a54c | ||
|
|
a2f7ad627e | ||
|
|
1176c162d4 | ||
|
|
a4880cd017 | ||
|
|
ffc155c341 | ||
|
|
42ea76eb2a | ||
|
|
be503f1341 | ||
|
|
6f4509c260 | ||
|
|
383bdce416 | ||
|
|
b2331084d1 | ||
|
|
ab3a594884 | ||
|
|
592adcf42e | ||
|
|
978c881ab7 | ||
|
|
7cc300dd83 | ||
|
|
687809db9d | ||
|
|
1127ec3a90 | ||
|
|
a797edfae9 | ||
|
|
938e106252 | ||
|
|
a0eb9caaa2 | ||
|
|
442f53d45e | ||
|
|
68de1ca248 | ||
|
|
e16061141e | ||
|
|
64cbe3d209 | ||
|
|
ebdc6376d8 | ||
|
|
68335123a1 | ||
|
|
25b18f4ef8 | ||
|
|
99727447ef | ||
|
|
e02cb56ee0 | ||
|
|
9ad615b0ff | ||
|
|
12eaceb032 | ||
|
|
3263511b72 | ||
|
|
75cae8c647 | ||
|
|
9991ef624c | ||
|
|
489e9fce27 | ||
|
|
0655628073 | ||
|
|
9460822529 | ||
|
|
d02358ac0d | ||
|
|
366237a625 | ||
|
|
2f2da18994 | ||
|
|
ecd30db215 | ||
|
|
1980854f6f | ||
|
|
7d4f006c25 | ||
|
|
b697be2383 | ||
|
|
143e53c43f | ||
|
|
02f75fb2e1 | ||
|
|
6cced719dc | ||
|
|
45ec0b9b01 | ||
|
|
2451ac3ade | ||
|
|
45a94fe73d | ||
|
|
6dde1ade01 | ||
|
|
d4de391ac5 | ||
|
|
af15f4bc6a | ||
|
|
e1173e6565 | ||
|
|
46f6911ef8 | ||
|
|
7629bf5be2 | ||
|
|
fb17de7988 | ||
|
|
32f4d42e59 | ||
|
|
d4ace24caa | ||
|
|
0031c2a9b7 | ||
|
|
509e99913a | ||
|
|
c639cfcfd9 | ||
|
|
b140a2ca3e | ||
|
|
c5ab47fa66 | ||
|
|
d60051b629 | ||
|
|
aa448e7a41 | ||
|
|
220750f887 | ||
|
|
808cc5d8d0 | ||
|
|
22ff330ee7 | ||
|
|
dd62bef66d | ||
|
|
e7feb99f8d | ||
|
|
6358accece | ||
|
|
9ce8a24eea | ||
|
|
4d0673f489 | ||
|
|
02e1a32fae | ||
|
|
d1690bda81 | ||
|
|
fbe1e6a7d5 | ||
|
|
4ed02ca501 | ||
|
|
af245ed9fe | ||
|
|
fc86a3e882 | ||
|
|
f9109edcf1 | ||
|
|
ec100e1a91 | ||
|
|
746c5f4a79 | ||
|
|
b2611728a1 | ||
|
|
fc6cc4234d | ||
|
|
7706c1cb63 | ||
|
|
4d7a07220c | ||
|
|
436875f7dc | ||
|
|
21c6f53ecc | ||
|
|
5472be8b72 | ||
|
|
d22542fcf3 | ||
|
|
1d8341eb27 | ||
|
|
1897a7ada3 | ||
|
|
a048698c66 | ||
|
|
f891e57f4a | ||
|
|
fcc590e48a | ||
|
|
9a02a2a65b | ||
|
|
536a6c5c60 | ||
|
|
86a6871ee8 | ||
|
|
6046045151 | ||
|
|
9c3ddd4ba4 | ||
|
|
155f2fc36c | ||
|
|
6c9f55c1d7 | ||
|
|
5bec3d7dcd | ||
|
|
a4c309efe8 | ||
|
|
4e22a208dd | ||
|
|
ff9e77118e | ||
|
|
6c6dfa9ac4 | ||
|
|
d60d5207d8 | ||
|
|
c3dc136de4 | ||
|
|
4cbc5c4e2f | ||
|
|
68820aaf59 | ||
|
|
cd65bc7683 | ||
|
|
8c0ae892f5 | ||
|
|
f570492a11 | ||
|
|
4d4a2897a5 | ||
|
|
cd73c8f82f | ||
|
|
934e67d146 | ||
|
|
4c96e44b9b | ||
|
|
189900549a | ||
|
|
6d18420a5d | ||
|
|
ab3d307393 | ||
|
|
89b2156f61 | ||
|
|
2f95da1892 | ||
|
|
6e0956f09f | ||
|
|
1191f0ba93 | ||
|
|
6534ffea26 | ||
|
|
cc447304f5 | ||
|
|
f9173c41d1 | ||
|
|
a2faedcacb | ||
|
|
cb56b3b9d0 | ||
|
|
a61b50548c | ||
|
|
75ad588e0b | ||
|
|
306d5d8fc7 | ||
|
|
8f8c6366e3 | ||
|
|
3b13bcb098 | ||
|
|
5553d5fefa | ||
|
|
9328518bbc | ||
|
|
df60784b51 | ||
|
|
6564edcc32 | ||
|
|
75bb7d5a2d | ||
|
|
ceab4e37cd | ||
|
|
afb6974cc0 | ||
|
|
bae3122bb5 | ||
|
|
0770aef4bf | ||
|
|
c198350bfa | ||
|
|
a4dc965c23 | ||
|
|
8b65c280a8 | ||
|
|
a2ed388777 | ||
|
|
f6670c699a | ||
|
|
5fa4c95480 | ||
|
|
5b8608c186 | ||
|
|
bb75d42ede | ||
|
|
b4b6def07a | ||
|
|
b305700987 | ||
|
|
40232b7eb1 | ||
|
|
67ff666db4 | ||
|
|
5960fd4fe0 | ||
|
|
f8b28c519c | ||
|
|
c46c0018ea | ||
|
|
4ab24d2fe5 | ||
|
|
c70ca2ff64 | ||
|
|
649ab26da8 | ||
|
|
c921ea6eb7 | ||
|
|
58aed76aa3 | ||
|
|
e286c57ce4 | ||
|
|
840f0b6379 | ||
|
|
538025edd5 | ||
|
|
ee1105b6dd | ||
|
|
4778274c97 | ||
|
|
d7ecb32238 | ||
|
|
744306fc50 | ||
|
|
11bbb8ed4d | ||
|
|
b5092a4444 | ||
|
|
e2442c07a9 | ||
|
|
35e8dcc3be | ||
|
|
d1600fd058 | ||
|
|
6f924d4e83 | ||
|
|
faf1508914 | ||
|
|
7eb8152835 | ||
|
|
8f45905831 | ||
|
|
4ba2ffce5b | ||
|
|
1416fb0c71 | ||
|
|
2975db247d | ||
|
|
ffb3659ef5 | ||
|
|
6dec07d562 | ||
|
|
afb22f3279 | ||
|
|
f2f369db64 | ||
|
|
03eaa652ce | ||
|
|
635c76db93 | ||
|
|
5f50f1928d | ||
|
|
32c9ed1dc2 | ||
|
|
0536926a1f | ||
|
|
3959c98479 | ||
|
|
1e7d1b1628 | ||
|
|
2ce5735676 | ||
|
|
defd42f74e | ||
|
|
71741e144e | ||
|
|
f2e64cdd5e | ||
|
|
afaa5d5e9e | ||
|
|
d82861727d | ||
|
|
90f0795416 | ||
|
|
aa54ab6e51 | ||
|
|
f0c28c74d8 | ||
|
|
3e285d6131 | ||
|
|
9efbe7771c | ||
|
|
a75caac13d | ||
|
|
279d0d20ea | ||
|
|
332ba96d34 | ||
|
|
3f6e5b7606 | ||
|
|
94703492fd | ||
|
|
df78117617 | ||
|
|
909b9480e4 | ||
|
|
21762ac1aa | ||
|
|
412bc175b4 | ||
|
|
dc0eb76e88 | ||
|
|
c96d94329e | ||
|
|
2695941a3c | ||
|
|
3506be1a70 | ||
|
|
cbf4786b39 | ||
|
|
8dbf334208 | ||
|
|
60ba5fbc72 | ||
|
|
c3ace0c44f | ||
|
|
8eceb90e63 | ||
|
|
4754e19e83 | ||
|
|
627227f2f9 | ||
|
|
42cef0da88 | ||
|
|
06f60b7d92 | ||
|
|
3ddb4d2b23 | ||
|
|
5a825bd459 | ||
|
|
ab1f08f687 | ||
|
|
2f89e3658a | ||
|
|
a0559d0dc9 | ||
|
|
8d03ce19b0 | ||
|
|
2470d851e9 | ||
|
|
df99f5c0a5 | ||
|
|
36f5084c52 | ||
|
|
b77d3f123d | ||
|
|
3c14405155 | ||
|
|
c10b0afaa8 | ||
|
|
3f7a2d6bfb | ||
|
|
f2a0d56e01 | ||
|
|
eac29993d3 | ||
|
|
0736cc8414 | ||
|
|
53240d40a0 | ||
|
|
4137dd7cc8 | ||
|
|
8907290d27 | ||
|
|
401dc1cb10 | ||
|
|
a6b5d26f56 | ||
|
|
eb55cba34a | ||
|
|
b0b41d984e | ||
|
|
947f64e32d | ||
|
|
24ccbfa9b6 | ||
|
|
8110acb795 | ||
|
|
7473aede60 | ||
|
|
6f204f721b | ||
|
|
7b0e08094a | ||
|
|
322e1a75b9 | ||
|
|
f08ab729bd | ||
|
|
052c974369 | ||
|
|
e760e868c1 | ||
|
|
5e3831a1a4 | ||
|
|
99e046ddea | ||
|
|
12e0759711 | ||
|
|
da0c1cec22 | ||
|
|
a0806d98a1 | ||
|
|
182897d971 | ||
|
|
f90a639fcc | ||
|
|
d95d7208a2 | ||
|
|
bbac8ffe64 | ||
|
|
801a649fb1 | ||
|
|
d23df2ab15 | ||
|
|
dbe828097c | ||
|
|
48a0623ded | ||
|
|
7c09ceecfd | ||
|
|
8c4954fb9b | ||
|
|
fbe892105b | ||
|
|
584e1bb847 | ||
|
|
c437ab282e | ||
|
|
42a100d138 | ||
|
|
65807bf35d | ||
|
|
2995f48ede | ||
|
|
3527b86ec5 | ||
|
|
fe7a9104a8 | ||
|
|
cbfb94afcb | ||
|
|
74d8eff6d8 | ||
|
|
d452702aef | ||
|
|
d7ed9ab64e | ||
|
|
3b6c5898fe | ||
|
|
ae22adc920 | ||
|
|
f8ed9c7357 | ||
|
|
5dacf9c3f5 | ||
|
|
9143120177 | ||
|
|
fd3b1bee92 | ||
|
|
bff42954d1 | ||
|
|
9129bfa284 | ||
|
|
96190c21d6 | ||
|
|
af2a9225b8 | ||
|
|
aa117a0ee3 | ||
|
|
39b0ea381b | ||
|
|
021cea0b34 | ||
|
|
eaad41fe55 | ||
|
|
e25d58d70a | ||
|
|
6b74fd6a02 | ||
|
|
9c0967170a | ||
|
|
0547cebfe2 | ||
|
|
caefdf4300 | ||
|
|
a59df15994 | ||
|
|
33304eb8d9 | ||
|
|
eb21a1bfb3 | ||
|
|
ce0333b0a7 | ||
|
|
25a1b53a91 | ||
|
|
6aba73f970 | ||
|
|
6406bb7fb6 | ||
|
|
abd89df365 | ||
|
|
2458107903 | ||
|
|
628f9bd9b5 | ||
|
|
2d791571d5 | ||
|
|
ed57127a79 | ||
|
|
6d9bcff8e1 | ||
|
|
8126cd1879 | ||
|
|
ab34c2261c | ||
|
|
651e4bb775 | ||
|
|
6953f65970 | ||
|
|
52073e79fa | ||
|
|
8e3484bb17 | ||
|
|
7110da8a36 | ||
|
|
7f06d6e68c | ||
|
|
7ffd6ded51 | ||
|
|
5e04547ecf | ||
|
|
7cbe5f64ce | ||
|
|
47ed78031a | ||
|
|
fd3d24b834 | ||
|
|
c2f607b452 | ||
|
|
b1328bb6e2 | ||
|
|
2a02583e27 | ||
|
|
cfb49f9136 | ||
|
|
5f20091f24 | ||
|
|
e5146f7def | ||
|
|
a37b8e35a1 | ||
|
|
d9bf7c7d12 | ||
|
|
84c980c3ea | ||
|
|
5823057b41 | ||
|
|
024a9b1763 | ||
|
|
0425780cd3 | ||
|
|
20734dc7f3 | ||
|
|
0574c58f16 | ||
|
|
31e3117190 | ||
|
|
b0bf18e235 | ||
|
|
f780ba2c5a | ||
|
|
f001b2c68f | ||
|
|
f125b593bf | ||
|
|
baee4e185d | ||
|
|
ca8476d466 | ||
|
|
a24de7fe3f | ||
|
|
757bf82a78 | ||
|
|
644887f727 | ||
|
|
563b643461 | ||
|
|
296141ad3d | ||
|
|
7abba0a69b | ||
|
|
0e4a6fd2e1 | ||
|
|
d452bf1f1c | ||
|
|
dee7767427 | ||
|
|
126828a813 | ||
|
|
03dc6fb73a | ||
|
|
087a71367d | ||
|
|
3653e89714 | ||
|
|
318dd14e42 | ||
|
|
2d13844b5d | ||
|
|
bec2e3b4a0 | ||
|
|
b777b7432a | ||
|
|
6f91c225c2 | ||
|
|
274884ef4d | ||
|
|
d3f52cdd1a | ||
|
|
c355e9ca4a | ||
|
|
4514ea8123 | ||
|
|
a9a1472cb7 | ||
|
|
250b0eee28 | ||
|
|
5cd7527937 | ||
|
|
57ebd5bb7a | ||
|
|
cb97e37c15 | ||
|
|
c18a697d6b | ||
|
|
ad40b72508 | ||
|
|
3a72e5910d | ||
|
|
8f3eb65a05 | ||
|
|
3013fa86b6 | ||
|
|
2593743746 | ||
|
|
700cd83ff5 | ||
|
|
0c27881eaf | ||
|
|
ab6bd56006 | ||
|
|
f7fdc6acd2 | ||
|
|
2c5f844edc | ||
|
|
a253de43c5 | ||
|
|
dbaf67a986 | ||
|
|
5175d06e37 | ||
|
|
651a27b558 | ||
|
|
fd41f8def6 | ||
|
|
208ea04fd5 | ||
|
|
5ee83a1af9 | ||
|
|
901c7704f4 | ||
|
|
c6f7290f92 | ||
|
|
5368eda98b | ||
|
|
7b9be209c8 | ||
|
|
cee05fea7c | ||
|
|
b87acb47e2 | ||
|
|
cb192bf9ea | ||
|
|
e02b8522d6 | ||
|
|
8cf74c88ce | ||
|
|
4095d6dcf6 | ||
|
|
cd4221e4f2 | ||
|
|
e4e65e4576 | ||
|
|
72e93786df | ||
|
|
16351ba7f3 | ||
|
|
96ba128942 | ||
|
|
76c8d4af25 | ||
|
|
3ea2b3cc00 | ||
|
|
0df0936022 | ||
|
|
4fc11a7fd3 | ||
|
|
8c509271a6 | ||
|
|
67d5b63ef9 | ||
|
|
4f999511a6 | ||
|
|
cfbf239175 | ||
|
|
1aedf9bb07 | ||
|
|
c2d4423720 | ||
|
|
c2f7a2620c | ||
|
|
6f0cda34b4 | ||
|
|
1a472fdf1f | ||
|
|
0551f61228 | ||
|
|
b900adfddd | ||
|
|
0e339daef5 | ||
|
|
5f5733e8b9 | ||
|
|
1372050a7b | ||
|
|
1960951c5e | ||
|
|
bc070407c7 | ||
|
|
0172ee0b6b | ||
|
|
2953bc6bb8 | ||
|
|
c0ed3a9e3c | ||
|
|
5456e4fe75 | ||
|
|
548754821e | ||
|
|
77f85579e3 | ||
|
|
867a1e960e | ||
|
|
48dc68c3fe | ||
|
|
2c719f326b | ||
|
|
201f92eb93 | ||
|
|
46f055b216 | ||
|
|
08305e679b | ||
|
|
e9e0b70199 | ||
|
|
441d784027 | ||
|
|
558956bf55 | ||
|
|
0d8250a3ac | ||
|
|
dc1f5826f8 | ||
|
|
06fb06829b | ||
|
|
bbf52d2611 | ||
|
|
f55a6617e9 | ||
|
|
e3ec03794a | ||
|
|
05c325d686 | ||
|
|
3bd273fbdd | ||
|
|
cf9472eb56 | ||
|
|
1b64a543f1 | ||
|
|
bb2085436e | ||
|
|
d882a4249a | ||
|
|
a2420e4288 | ||
|
|
3687a230e1 | ||
|
|
c274486057 | ||
|
|
94e9bbfd63 | ||
|
|
adb71ad174 | ||
|
|
4a95f5cd9d | ||
|
|
f3b368fae4 | ||
|
|
20919a8a39 | ||
|
|
f2e86efc4d | ||
|
|
98b27d647e | ||
|
|
97b07f7d42 | ||
|
|
7a884d1f87 | ||
|
|
d21f5f427c | ||
|
|
ebb472b8f6 | ||
|
|
1a2ef6479e | ||
|
|
876c63fe80 | ||
|
|
32236962f5 | ||
|
|
9ba2a6628d | ||
|
|
ba66af922f | ||
|
|
739641b857 | ||
|
|
d02a110f99 | ||
|
|
28b9bbe54f | ||
|
|
bf6bd7cbdc | ||
|
|
ddcb2468a6 | ||
|
|
f048b58935 | ||
|
|
09f6200c2e | ||
|
|
354692fb06 | ||
|
|
2c5858c2e0 | ||
|
|
1f41fd0436 | ||
|
|
e0bbefdfae | ||
|
|
bc6fbb2797 | ||
|
|
b579cf8128 | ||
|
|
a94ed014b7 | ||
|
|
9120db2dc6 | ||
|
|
0db991b668 | ||
|
|
347ced6942 | ||
|
|
5d7a775b2b | ||
|
|
df732ab4bf | ||
|
|
31cd3953d6 | ||
|
|
4666b13892 | ||
|
|
bf586d0837 | ||
|
|
37d7ddb4b0 | ||
|
|
ed45a9b156 | ||
|
|
3abbaeb44b | ||
|
|
f4ea39b602 | ||
|
|
847ea49042 | ||
|
|
779068c2ee | ||
|
|
140cd6882d | ||
|
|
a6dca12bad | ||
|
|
e40411ef91 | ||
|
|
fd20c21243 | ||
|
|
2e295c2391 | ||
|
|
5c0474ef96 | ||
|
|
73d1de75f9 | ||
|
|
596b0995f4 | ||
|
|
8ea6989ea4 | ||
|
|
b61fe97893 | ||
|
|
209aa2ebe6 | ||
|
|
c03a0e857e | ||
|
|
2854d698e6 | ||
|
|
944163be0e | ||
|
|
269a9eaff0 | ||
|
|
7f9c49cbc4 | ||
|
|
2b6bfeeb15 | ||
|
|
fa9742bc92 | ||
|
|
472e419abc | ||
|
|
169d11387b | ||
|
|
e59ac6d947 | ||
|
|
e193b45562 | ||
|
|
1ac34f810e | ||
|
|
e65e5c6e5f | ||
|
|
16a9f44d4d | ||
|
|
af6365c76a | ||
|
|
8c672b23b5 | ||
|
|
3b53f5ac11 | ||
|
|
ccdc744748 | ||
|
|
261f0b4bf0 | ||
|
|
495b59c2e5 | ||
|
|
f26fc9a0f0 | ||
|
|
3fd637b3c7 | ||
|
|
d6e1c13c39 | ||
|
|
ea605383d5 | ||
|
|
a4fa670dc5 | ||
|
|
56f99b2129 | ||
|
|
ccb508705b | ||
|
|
6f8fa96150 | ||
|
|
72e1632499 | ||
|
|
f7f13b9e07 | ||
|
|
62564d747f | ||
|
|
1d5d59c4c0 | ||
|
|
e8380b8a12 | ||
|
|
c304d9cc62 | ||
|
|
aad4ebf287 | ||
|
|
7d7cc56527 | ||
|
|
85d0271b86 | ||
|
|
749d5e22bb | ||
|
|
6c2f48181c | ||
|
|
480b6ff7d6 | ||
|
|
4bdd6f972c | ||
|
|
61c61adea1 | ||
|
|
6674d8456a | ||
|
|
d1478ff694 | ||
|
|
cb20d46b74 | ||
|
|
9dd2538043 | ||
|
|
f25136a512 | ||
|
|
03eb56ad2a | ||
|
|
2508840701 | ||
|
|
6e098f5a4f | ||
|
|
31b33406fd | ||
|
|
7ab7c763ed | ||
|
|
06258e757a | ||
|
|
5919b43a21 | ||
|
|
7d4b9521e7 | ||
|
|
f9d2fd7997 | ||
|
|
bdbc20c3c6 | ||
|
|
69d6d03714 | ||
|
|
f40e1e55b0 | ||
|
|
b9082ed838 | ||
|
|
4edfefa9a2 | ||
|
|
0f98ee5407 | ||
|
|
7fdf119873 | ||
|
|
3c054e6ea1 | ||
|
|
98899ece72 | ||
|
|
2061a006d0 | ||
|
|
5496c2d7fd | ||
|
|
d6b862a4a9 | ||
|
|
d581f8a852 | ||
|
|
3a593ee35a | ||
|
|
415d34fdaa | ||
|
|
7d28191bb5 | ||
|
|
e2c7693370 | ||
|
|
f17ff02f4d | ||
|
|
225043e01d | ||
|
|
56f78349f8 | ||
|
|
8839d4f55a | ||
|
|
2562aec1d2 | ||
|
|
db2361be84 | ||
|
|
d08fcbfef3 | ||
|
|
7601b93e65 | ||
|
|
1103ec40e0 | ||
|
|
af32c7e3db | ||
|
|
170918eb3b | ||
|
|
a91e615a8d | ||
|
|
cc92c26fe3 | ||
|
|
937135db32 | ||
|
|
01366558b4 | ||
|
|
b0dbd54ea4 | ||
|
|
f113915307 | ||
|
|
0a3c3d9bb1 | ||
|
|
ba2e005c3e | ||
|
|
b9ea52d65f | ||
|
|
f1e328d333 | ||
|
|
23f1965398 | ||
|
|
fc82f185cb | ||
|
|
56b25f373f | ||
|
|
1ac6add122 | ||
|
|
91b1a903f9 | ||
|
|
a8d6654ef5 | ||
|
|
63093b1be6 | ||
|
|
60abe8f37e | ||
|
|
7ba3909aa1 | ||
|
|
eecdba2d05 | ||
|
|
7bdc005aa7 | ||
|
|
d46703fdc4 | ||
|
|
e4aa85f603 | ||
|
|
233124ef50 | ||
|
|
36a3012de2 | ||
|
|
2b4ee96ed7 | ||
|
|
85a2afd55c | ||
|
|
6cd0d8456a | ||
|
|
7750a0a773 | ||
|
|
a5364b9257 | ||
|
|
e0e7b1406d | ||
|
|
38b67a0002 | ||
|
|
18dd4f8a52 | ||
|
|
879f9b4ea9 | ||
|
|
3db0dda67a | ||
|
|
ed9ee15b90 | ||
|
|
44ff85e8e9 | ||
|
|
cb07e9ba11 | ||
|
|
bfe05ce5fc | ||
|
|
64ee23cec0 | ||
|
|
c022d3c4a4 | ||
|
|
69c764301f | ||
|
|
2f777daef6 | ||
|
|
a10bf7330e | ||
|
|
782bb5967d | ||
|
|
aeb2f55f0d | ||
|
|
ae68749b1b | ||
|
|
a3c25d56a0 | ||
|
|
c8a6fd19a7 | ||
|
|
0b143b580a | ||
|
|
d2b9cc8df9 | ||
|
|
2027daa75c | ||
|
|
f3493a08bd | ||
|
|
ed69fedc0a | ||
|
|
8098464f58 | ||
|
|
ea0db57388 | ||
|
|
6fcc148105 | ||
|
|
3485cb4ec4 | ||
|
|
f3963269ae | ||
|
|
ae2212c245 | ||
|
|
3a19ac4c93 | ||
|
|
666f546cf0 | ||
|
|
e6ef6ccccf | ||
|
|
4826e14cad | ||
|
|
5515f90147 | ||
|
|
2193c26acb | ||
|
|
1974a2c0e4 | ||
|
|
84fbe9ee06 | ||
|
|
b8e2cfc47f | ||
|
|
464f57d7da | ||
|
|
2a192f33a1 | ||
|
|
553fc6f5d9 | ||
|
|
f5d790b264 | ||
|
|
9ca2674261 | ||
|
|
641e13496e | ||
|
|
a6e18819d4 | ||
|
|
faf5ff6aa4 | ||
|
|
ae20ca5558 | ||
|
|
22bd87c965 | ||
|
|
2129645f39 | ||
|
|
93a07b6207 | ||
|
|
24bc91dc0c | ||
|
|
cf2d5b502f | ||
|
|
61450ef602 | ||
|
|
78f1d1738e | ||
|
|
9f595cf5f7 | ||
|
|
25b8e49975 | ||
|
|
d40086cd13 | ||
|
|
8f9d8d93b9 | ||
|
|
1080c10004 | ||
|
|
866aeca220 | ||
|
|
121b3afc61 | ||
|
|
e8406b04b4 | ||
|
|
8e7fe81806 | ||
|
|
852807b5d7 | ||
|
|
9928d47fa2 | ||
|
|
412a1bd62a | ||
|
|
b290520951 | ||
|
|
dde677b6d3 | ||
|
|
75030847bd | ||
|
|
e7b9cb76bc | ||
|
|
e96c4c0dd3 | ||
|
|
b553b3fa50 | ||
|
|
c6fb924b8f | ||
|
|
b13844c4a6 | ||
|
|
ab6c83a3fc | ||
|
|
b2a51bd658 | ||
|
|
e5ab1dc154 | ||
|
|
7e0a97973f | ||
|
|
6274969635 | ||
|
|
069c430346 | ||
|
|
6a8a79bba5 | ||
|
|
4a0c58c50a | ||
|
|
cbcc4dd21d | ||
|
|
eb0c963332 | ||
|
|
023fe82932 | ||
|
|
2e1a06c7bf | ||
|
|
8b6961d40c | ||
|
|
53351877da | ||
|
|
522445894e | ||
|
|
550351bb16 | ||
|
|
328adbb56f | ||
|
|
b4cdf4d277 | ||
|
|
716d7bfcf6 | ||
|
|
b45a169a2f | ||
|
|
44a36bbba3 | ||
|
|
4cc4adeda6 | ||
|
|
c14e6f2a63 | ||
|
|
cfcb2d54d8 | ||
|
|
010d60e504 | ||
|
|
eabde07ff6 | ||
|
|
be19ad5f2a | ||
|
|
d1d0816961 | ||
|
|
7be7170504 | ||
|
|
478272f515 | ||
|
|
09af6958c8 | ||
|
|
adb3a2b64e | ||
|
|
1ee7e842dc | ||
|
|
b080a57406 | ||
|
|
7c017e345a | ||
|
|
4b91343155 | ||
|
|
02a3df8ad0 | ||
|
|
6a7080f4ee | ||
|
|
4547042577 | ||
|
|
0e39eea7f8 | ||
|
|
1e5aefea63 | ||
|
|
02c4f333b0 | ||
|
|
1e8fc4020b | ||
|
|
f969701ac1 | ||
|
|
b236243857 | ||
|
|
39edc64922 | ||
|
|
f22ece403f | ||
|
|
f5423bb314 | ||
|
|
b1e5945ebe | ||
|
|
d98a457271 | ||
|
|
ef3a0d881f | ||
|
|
669a9e5cc3 | ||
|
|
5eb58ae6cc | ||
|
|
7fa1923400 | ||
|
|
4c165bd620 | ||
|
|
7c04a455b4 | ||
|
|
06b6061518 | ||
|
|
3821ee3dcd | ||
|
|
76b5be8171 | ||
|
|
03a33646d6 | ||
|
|
791183553e | ||
|
|
de6ef49043 | ||
|
|
804bca2041 | ||
|
|
720b9ef999 | ||
|
|
9b9e4dddfc | ||
|
|
10602b47b4 | ||
|
|
8d7c522596 | ||
|
|
3ac455c5a7 | ||
|
|
2b19a459df | ||
|
|
41ba2d9bf6 | ||
|
|
a7b5eb69d3 | ||
|
|
67c209bb5e | ||
|
|
a6d436d9ea | ||
|
|
652c784e13 | ||
|
|
7434e0352f | ||
|
|
26d61af902 | ||
|
|
5bd12c5f9e | ||
|
|
e07fae4290 | ||
|
|
a0a3b7a158 | ||
|
|
28bf7ee90b | ||
|
|
4d1ca7ede4 | ||
|
|
f3b46515c5 | ||
|
|
0aa5e7ba63 | ||
|
|
8d8bf43b46 | ||
|
|
789f51bd2a | ||
|
|
9983407c8b | ||
|
|
2471ad4215 | ||
|
|
f266982560 | ||
|
|
c059a416f7 | ||
|
|
82dc0c0593 | ||
|
|
5faad3db92 | ||
|
|
099db6792a | ||
|
|
c2f1a74f96 | ||
|
|
a9ed7a3f3b | ||
|
|
74a31f3301 | ||
|
|
f88c0b9b67 | ||
|
|
61ef313b1c | ||
|
|
b348e88a5f | ||
|
|
1615395866 | ||
|
|
e483abcad0 | ||
|
|
12b6760f6e | ||
|
|
048cea19b7 | ||
|
|
3e77c76c34 | ||
|
|
9b6c5d2ea3 | ||
|
|
6fde6d7eac | ||
|
|
a7ef891217 | ||
|
|
2fa081a4ba | ||
|
|
8f22dfe87b | ||
|
|
2dc7fab39a | ||
|
|
5b9a3ca1cb | ||
|
|
bf7c56a269 | ||
|
|
d33af742dd | ||
|
|
823879e9f9 | ||
|
|
98eb285e14 | ||
|
|
74cb2e3c63 | ||
|
|
37fd2e1103 | ||
|
|
56db5dc341 | ||
|
|
d48fa235b1 | ||
|
|
06a111495b | ||
|
|
f3fb0797bf | ||
|
|
561b8f4eed | ||
|
|
6e763a58f1 | ||
|
|
8cfc6f0b1d | ||
|
|
7e04f26f78 | ||
|
|
348c30b61e | ||
|
|
ad1e60e137 | ||
|
|
97e0a983f1 | ||
|
|
a8e72ed410 | ||
|
|
fcdfd5f936 | ||
|
|
f1faa463c1 | ||
|
|
a0f4952b54 | ||
|
|
bd82ded07d | ||
|
|
016e17dedb | ||
|
|
5cd3e1b368 | ||
|
|
b2b39458da | ||
|
|
556bbe394d | ||
|
|
07288b3f26 | ||
|
|
45681e645b | ||
|
|
a4e9f1a683 | ||
|
|
f8c74daef5 | ||
|
|
b3a593afd7 | ||
|
|
90f79b7708 | ||
|
|
e220786a20 | ||
|
|
f16b993294 | ||
|
|
d45461bc47 | ||
|
|
c241bea3bf | ||
|
|
084654cd3c | ||
|
|
d21742afb6 | ||
|
|
b5259384e8 | ||
|
|
bf78ad9fbe | ||
|
|
ab3577c369 | ||
|
|
6efb90c94e | ||
|
|
cbcc400eb4 | ||
|
|
15aec7da7e | ||
|
|
46535e4f56 | ||
|
|
e3f945c079 | ||
|
|
04239c57fe | ||
|
|
58aa2b6a49 | ||
|
|
ad4439ed55 | ||
|
|
fb06905c86 | ||
|
|
9fe3ef430f | ||
|
|
ff30773097 | ||
|
|
f7531d1e18 | ||
|
|
4a2911557d | ||
|
|
99caa5dddc | ||
|
|
86b42a3716 | ||
|
|
12c4680501 | ||
|
|
b7e05c236f | ||
|
|
658008ab64 | ||
|
|
b089d63112 | ||
|
|
ee9b1b7f57 | ||
|
|
cd0fc8176f | ||
|
|
8e291e3e46 | ||
|
|
e3024076cd | ||
|
|
6105874abc | ||
|
|
1855f7829d | ||
|
|
456e8bd9c0 | ||
|
|
d5f2efac26 | ||
|
|
21e692623c | ||
|
|
80e9589af5 | ||
|
|
b2b9ae0677 | ||
|
|
63122905e6 | ||
|
|
f99b6f4646 | ||
|
|
39090c2a22 | ||
|
|
76baa8c791 | ||
|
|
74e4b9d6d2 | ||
|
|
bbfc5039f7 | ||
|
|
b2fd694483 | ||
|
|
b03f38ff22 | ||
|
|
fe48811047 | ||
|
|
bd9396b031 | ||
|
|
f0497ec16d | ||
|
|
7e9e179fa7 | ||
|
|
de62464ad8 | ||
|
|
f6911ca195 | ||
|
|
aec09ed8d2 | ||
|
|
51a983e460 | ||
|
|
0eb46e29c7 | ||
|
|
5ee11c7b6b | ||
|
|
b55accd76f | ||
|
|
fef2be1bc7 | ||
|
|
0b3858f91d | ||
|
|
d07ea1b337 | ||
|
|
7e2dbc7358 | ||
|
|
c676f08a7c | ||
|
|
92f24b5728 | ||
|
|
0254e71435 | ||
|
|
2972fc5814 | ||
|
|
975c96217c | ||
|
|
c30c1848bc | ||
|
|
94615d3b36 | ||
|
|
37a00f0d16 | ||
|
|
0dbe70f5af | ||
|
|
7584374b0b | ||
|
|
71ca51dc1a | ||
|
|
aa81e72e45 | ||
|
|
9954bb9c15 | ||
|
|
e304395179 | ||
|
|
6b83130853 | ||
|
|
9565718699 | ||
|
|
e5c0250423 | ||
|
|
135799ed5e | ||
|
|
22c3b57960 | ||
|
|
7054dd74a4 | ||
|
|
d4f1e52ef6 | ||
|
|
76a44459cf | ||
|
|
a5590b090c | ||
|
|
74c9a57070 | ||
|
|
06e283e070 | ||
|
|
8ab2ca3f24 | ||
|
|
0eb949ba39 | ||
|
|
be35693814 | ||
|
|
1e5f13795c | ||
|
|
cca2265633 | ||
|
|
0f0d1ac370 | ||
|
|
d52d4ac183 | ||
|
|
841220fd01 | ||
|
|
ca5e10784b | ||
|
|
712319974b | ||
|
|
067a6d01bc | ||
|
|
27825f9e2e | ||
|
|
425eb115dc | ||
|
|
0a5ce55e2b | ||
|
|
dbc8ed9d4c | ||
|
|
e31e990684 | ||
|
|
8618f56481 | ||
|
|
a39fc4667e | ||
|
|
4c369e240b | ||
|
|
4e291d01d4 | ||
|
|
0f03208aa1 | ||
|
|
b32fce46cb | ||
|
|
d1fcd45aac | ||
|
|
ebdb92c708 | ||
|
|
112909d35b | ||
|
|
c6e2c559f1 | ||
|
|
cf8613886a | ||
|
|
39c25c2001 | ||
|
|
9c9721ade5 | ||
|
|
4b8abe4ce8 | ||
|
|
33dfaba276 | ||
|
|
dd8bbcf358 | ||
|
|
cc3e4369ed | ||
|
|
548355fce6 | ||
|
|
a4e0e6544b | ||
|
|
62067e0801 | ||
|
|
5eb40d2299 | ||
|
|
d58add18fc | ||
|
|
53990a531b | ||
|
|
d799aea3c4 | ||
|
|
9af86cbba2 | ||
|
|
6fbfece4ff | ||
|
|
3a0413d8bb | ||
|
|
9122f9b291 | ||
|
|
d279db2a0e | ||
|
|
d56cca7873 | ||
|
|
fa1096a6ba | ||
|
|
16ff721331 | ||
|
|
798ee9dc46 | ||
|
|
bc17c60305 | ||
|
|
432688d577 | ||
|
|
da8d4b47f1 | ||
|
|
c6657b9619 | ||
|
|
ed1e2e2449 | ||
|
|
b9744b4688 | ||
|
|
80d8388eb6 | ||
|
|
b1ee4bdc09 | ||
|
|
2f328f8f37 | ||
|
|
ffefd7e50b | ||
|
|
c2445f8a7c | ||
|
|
54d44079cc | ||
|
|
87f089a12c | ||
|
|
aa2172e4db | ||
|
|
0a33e94e79 | ||
|
|
fada54abae | ||
|
|
802641b719 | ||
|
|
1da93829d4 | ||
|
|
9e7acbc49a | ||
|
|
318765d40b | ||
|
|
ac11885379 | ||
|
|
277669a13c | ||
|
|
fcbc476462 | ||
|
|
94ba20dfa1 | ||
|
|
4944b415c7 | ||
|
|
5da7312d2d | ||
|
|
954d19fe50 | ||
|
|
addd86f5d2 | ||
|
|
2ad3dc4a32 | ||
|
|
eef940dd7c | ||
|
|
1b90223210 | ||
|
|
95989ff63b | ||
|
|
1b5fc12ac1 | ||
|
|
c1c7b8dfcd | ||
|
|
d4510c2afe | ||
|
|
799f758dce | ||
|
|
f241f073a3 | ||
|
|
26a6c72611 | ||
|
|
51cee7804b | ||
|
|
52228430f1 | ||
|
|
4fcd45d8a4 | ||
|
|
ea5736947d | ||
|
|
6b5f36fb7e | ||
|
|
b328d6d95f | ||
|
|
2e06921bf8 | ||
|
|
fd95e2d711 | ||
|
|
490e253b79 | ||
|
|
8a10f5cd52 | ||
|
|
5cebcc2424 | ||
|
|
7663b89289 | ||
|
|
d0d1f9e3c0 | ||
|
|
5b782993fd | ||
|
|
138e60e77c | ||
|
|
9771402c54 | ||
|
|
30dcb4d8d2 | ||
|
|
4433760e37 | ||
|
|
c418c766d8 | ||
|
|
334d843955 | ||
|
|
2e5169eb46 | ||
|
|
3cdc1c03c3 | ||
|
|
733c619b1f | ||
|
|
2021b644c0 | ||
|
|
b63fcd2254 | ||
|
|
eb2aa352ef | ||
|
|
f55ed13bd2 | ||
|
|
14b7072b5b | ||
|
|
98395abc17 | ||
|
|
5db5c4e52c | ||
|
|
ec95f198ec | ||
|
|
f166e480f1 | ||
|
|
b31f55063d | ||
|
|
71e77ad45a | ||
|
|
25873e0e02 | ||
|
|
22638a8147 | ||
|
|
2051f0486c | ||
|
|
f33fc5d730 | ||
|
|
1603df3503 | ||
|
|
6055ac182b | ||
|
|
4678f60adf | ||
|
|
e075f1c08b | ||
|
|
b48521950e | ||
|
|
45da6fb39f | ||
|
|
1604d327da | ||
|
|
98b2b325a1 | ||
|
|
55b3def630 | ||
|
|
7e0aa719b4 | ||
|
|
ce7bc9f438 | ||
|
|
43a362d0eb | ||
|
|
7d7e6e10b9 | ||
|
|
73821b0f12 | ||
|
|
2071a7d308 | ||
|
|
c439daadad | ||
|
|
083f325076 | ||
|
|
ee53433dcc | ||
|
|
ad10d13a75 | ||
|
|
1b5f6bd3eb | ||
|
|
3472b85345 | ||
|
|
1c6ae53656 | ||
|
|
a84a56f611 | ||
|
|
69751aa415 | ||
|
|
9ef0337807 | ||
|
|
a3fdd274c3 | ||
|
|
f069c02153 | ||
|
|
ad0d14c517 | ||
|
|
a72da79743 | ||
|
|
4fd9639457 | ||
|
|
2f2ee1f431 | ||
|
|
6bc35217d9 | ||
|
|
c526e9ac8f | ||
|
|
3dfebcadc1 | ||
|
|
ad06c8147f | ||
|
|
693af532a2 | ||
|
|
4bac57fcd1 | ||
|
|
a2f5f1cb0e | ||
|
|
1fbe7d92eb | ||
|
|
760974c7c7 | ||
|
|
e1587d11b1 | ||
|
|
0595360808 | ||
|
|
1a8149e456 | ||
|
|
59291fcac2 | ||
|
|
1f21b202a5 | ||
|
|
b3ca02a166 | ||
|
|
2462f8f87e | ||
|
|
18268acc1f | ||
|
|
fd6f92f6b5 | ||
|
|
ddf7226ba8 | ||
|
|
a1cd95752a | ||
|
|
d131a26a41 | ||
|
|
10a7c75001 | ||
|
|
1f454ababf | ||
|
|
44f4423a9d | ||
|
|
37250a6f48 | ||
|
|
4ee352fdb2 | ||
|
|
fd97541417 | ||
|
|
dba3e30d02 | ||
|
|
96ea3ded4a | ||
|
|
8bbc6e9ff5 | ||
|
|
5962283ad3 | ||
|
|
cdd705831b | ||
|
|
a1a7c5e4bb | ||
|
|
c1e9061568 | ||
|
|
90ee04de57 | ||
|
|
cd24cfbe5c | ||
|
|
af7029812c | ||
|
|
5b05668a30 | ||
|
|
a2eca9589f | ||
|
|
a2adbb19bd | ||
|
|
012e5c09ed | ||
|
|
84c9532456 | ||
|
|
aa7c9bca46 | ||
|
|
3d00b4ffbe | ||
|
|
5f33aa7fd4 | ||
|
|
7ea2204dfd | ||
|
|
be8cadf5ac | ||
|
|
fc7192c748 | ||
|
|
c517b59138 | ||
|
|
5c6b6b3919 | ||
|
|
87553ac685 | ||
|
|
384f396bbb | ||
|
|
d40560bd49 | ||
|
|
0304d6079d | ||
|
|
ae41e64999 | ||
|
|
8c9f32c927 | ||
|
|
5485e8a322 | ||
|
|
67952ca246 | ||
|
|
b4c612ff6d | ||
|
|
e708268067 | ||
|
|
4ddbefb147 | ||
|
|
edabc17ac9 | ||
|
|
c7e8211fae | ||
|
|
dc3f07dd52 | ||
|
|
fae6a5cfb4 | ||
|
|
cafd225797 | ||
|
|
bf77d2973e | ||
|
|
dfd8133431 | ||
|
|
e9d78b8990 | ||
|
|
13532cdffb | ||
|
|
c17620efb7 | ||
|
|
d3b5b1080f | ||
|
|
e4b0c23bb9 | ||
|
|
e61873f335 | ||
|
|
6c24fb6d79 | ||
|
|
bd93551c13 | ||
|
|
6c3719b9b8 | ||
|
|
dc0799a56b | ||
|
|
8c9ea7885a | ||
|
|
e1fca1520b | ||
|
|
c72f3b8efb | ||
|
|
afcd172a94 | ||
|
|
5957700699 | ||
|
|
451d023e81 | ||
|
|
64d166cebf | ||
|
|
2540ac34b3 | ||
|
|
76e5d41a34 | ||
|
|
51b067def7 | ||
|
|
de25e8dd6f | ||
|
|
39ac291ba2 | ||
|
|
722d26c5b9 | ||
|
|
0346760a75 | ||
|
|
a2015143f8 | ||
|
|
071d99a7b4 | ||
|
|
2c32a4e912 | ||
|
|
c66f7235b6 | ||
|
|
e6d8e063c9 | ||
|
|
29526144c9 | ||
|
|
b88bcafddd | ||
|
|
78d844d693 | ||
|
|
49310b53e9 | ||
|
|
4684487ff8 | ||
|
|
89250205dc | ||
|
|
9039919b03 | ||
|
|
df16803f11 | ||
|
|
48327cf5f8 | ||
|
|
2ead125b9d | ||
|
|
23807f11f9 | ||
|
|
cad666d07f | ||
|
|
a0b11051e1 | ||
|
|
b6e6fb9de2 | ||
|
|
4172fd8aff | ||
|
|
d60df6ca69 | ||
|
|
f9ba7377eb | ||
|
|
5d3c47b8ae | ||
|
|
ef9cc860a9 | ||
|
|
5a1ec8aa74 | ||
|
|
ac435f3699 | ||
|
|
20cbf0c710 | ||
|
|
0374dec79c | ||
|
|
41b6bf38c4 | ||
|
|
ddba251449 | ||
|
|
bf76e47648 | ||
|
|
5a3ad35bdd | ||
|
|
7da18274ad | ||
|
|
9b66ae05eb | ||
|
|
d407964625 | ||
|
|
bc254d5a8d | ||
|
|
1d9128a650 | ||
|
|
a1f2e9e4e2 | ||
|
|
8c8188c24f | ||
|
|
e42eaf73b8 | ||
|
|
5444381f7d | ||
|
|
fbd8552c0b | ||
|
|
daa26b2e1e | ||
|
|
dc44679031 | ||
|
|
18473e6819 | ||
|
|
2cbd17b745 | ||
|
|
a51d6b9a74 | ||
|
|
706871c12a | ||
|
|
16c49e965c | ||
|
|
f29e867441 | ||
|
|
4a5f6d8393 | ||
|
|
39e3025077 | ||
|
|
8de2066634 | ||
|
|
dfc312c092 | ||
|
|
ce15dbf31b | ||
|
|
9ee293816d | ||
|
|
b3c1397753 | ||
|
|
93d63538bb | ||
|
|
09c28d2311 | ||
|
|
5b8ed496db | ||
|
|
27b4cf743e | ||
|
|
95938a65b6 | ||
|
|
70780fc4ef | ||
|
|
21d265d12f | ||
|
|
788526d6eb | ||
|
|
a7203e9a17 | ||
|
|
eff50daae4 | ||
|
|
7612db15e0 | ||
|
|
0731ee473d | ||
|
|
3a6a76fe19 | ||
|
|
edb0509194 | ||
|
|
1806be1fd7 | ||
|
|
7769f25869 | ||
|
|
f6e5743f20 | ||
|
|
9ef13696d8 | ||
|
|
c3f635fd12 | ||
|
|
e3d1380435 | ||
|
|
1ef6196de6 | ||
|
|
f83737b538 | ||
|
|
bb1ea4e4d0 | ||
|
|
9cb4de2ea8 | ||
|
|
048cbf60ec | ||
|
|
36f40b4188 | ||
|
|
ea1afb260a | ||
|
|
75ceee23ec | ||
|
|
38f2372ee8 | ||
|
|
b58826da6e | ||
|
|
a3bba92063 | ||
|
|
ebcc6c9341 | ||
|
|
95f765055e | ||
|
|
49aa5ffccc | ||
|
|
2c05e606c7 | ||
|
|
92f1c080c4 | ||
|
|
8f79699e84 | ||
|
|
e7b3e28f76 | ||
|
|
123677b6c6 | ||
|
|
f393c7173c | ||
|
|
0abfb9f1e4 | ||
|
|
379771a5c1 | ||
|
|
2f4a76b3fa | ||
|
|
7d82e199b7 | ||
|
|
de693a08ad | ||
|
|
923091ca62 | ||
|
|
cb65ddedb6 | ||
|
|
7aa75539c9 | ||
|
|
a1a7cf59b3 | ||
|
|
f572cb5f3e | ||
|
|
8f1a31ad13 | ||
|
|
dcf63e3da2 | ||
|
|
153dca8137 | ||
|
|
db97787b15 | ||
|
|
7148fec6e1 | ||
|
|
db6d48f8ca | ||
|
|
6d87a1a604 | ||
|
|
d09d3fa80b | ||
|
|
4c8cd50643 | ||
|
|
eee72f4f27 | ||
|
|
5c414fc7b4 | ||
|
|
127f4446ae | ||
|
|
45f6a7cb4d | ||
|
|
8866bd8663 | ||
|
|
3f9c515f1d | ||
|
|
e9e0fdae37 | ||
|
|
124f7f43ab | ||
|
|
27df44bf44 | ||
|
|
b934a7de6a | ||
|
|
d521c75085 | ||
|
|
5e18b6b878 | ||
|
|
3183ca02b3 | ||
|
|
60a278490f | ||
|
|
b78e74cdf6 | ||
|
|
f61a16074b | ||
|
|
82766d1645 | ||
|
|
49a49e2a2c | ||
|
|
8b9389b468 | ||
|
|
ee17336d64 | ||
|
|
2b768d2fb5 | ||
|
|
4fc63010d4 | ||
|
|
3311ded843 | ||
|
|
135c2e7a71 | ||
|
|
a3a8f600b6 | ||
|
|
23643b99e2 | ||
|
|
764a334952 | ||
|
|
6f163f2d01 | ||
|
|
aae65f8235 | ||
|
|
764ea571eb | ||
|
|
aef719b574 | ||
|
|
73e52e625b | ||
|
|
a967588fe1 | ||
|
|
72e530f062 | ||
|
|
a0b422f99e | ||
|
|
6b92f30324 | ||
|
|
464cfe701c | ||
|
|
070fa03108 | ||
|
|
51abeabb33 | ||
|
|
74b0697da1 | ||
|
|
0ca409bb22 | ||
|
|
527cf25d1b | ||
|
|
a49594e6a5 | ||
|
|
5cd22b41d6 | ||
|
|
934949c514 | ||
|
|
cc61e8d334 | ||
|
|
81be499c49 | ||
|
|
082aa55566 | ||
|
|
c783039557 | ||
|
|
206dfeb879 | ||
|
|
f839e76f4b | ||
|
|
c67151f922 | ||
|
|
aedec5de18 | ||
|
|
3df973a1ea | ||
|
|
0253031d7f | ||
|
|
f4445d4681 | ||
|
|
e399dfa7e6 | ||
|
|
787f95ac3a | ||
|
|
d2f35d46d2 | ||
|
|
b21d078c5d | ||
|
|
032c3fb856 | ||
|
|
bbd79379ce | ||
|
|
d01d544a0a | ||
|
|
01ecd76976 | ||
|
|
26e8ae4bf3 | ||
|
|
3befdbc93d | ||
|
|
c91a890d42 | ||
|
|
3369ab601a | ||
|
|
bfe5b71f19 | ||
|
|
eb25cf65dd | ||
|
|
aa1ca3be64 | ||
|
|
a4e03daeee | ||
|
|
cbd0b9db1d | ||
|
|
621e8e89a5 | ||
|
|
c9eca5ec7e | ||
|
|
05063b76eb | ||
|
|
7d1d34d1eb | ||
|
|
725f471a6a | ||
|
|
0b01a79d9d | ||
|
|
2653ff6536 | ||
|
|
0f30cc8e59 | ||
|
|
e3879cd4d1 | ||
|
|
7a4cdf8688 | ||
|
|
40423a0547 | ||
|
|
682804b1ad | ||
|
|
790e67866c | ||
|
|
8399edb4dc | ||
|
|
55a1c27d6d | ||
|
|
35bf7dc484 | ||
|
|
4d3dfa1dca | ||
|
|
ac4686125f | ||
|
|
fb2ca3bb19 | ||
|
|
c0122aace7 | ||
|
|
c141e92cc4 | ||
|
|
c92567d4fa | ||
|
|
1839bf938a | ||
|
|
5236441be0 | ||
|
|
44d4096a79 | ||
|
|
41280c9d38 | ||
|
|
94620748ab | ||
|
|
da70c03845 | ||
|
|
337eb0f27b | ||
|
|
26a63c4baf | ||
|
|
8b03890f2a | ||
|
|
df3c76fa72 | ||
|
|
cea4157402 | ||
|
|
61731e2c2e | ||
|
|
5c1611c484 | ||
|
|
a502965d19 | ||
|
|
b55764db56 | ||
|
|
510897f672 | ||
|
|
2689fd17d0 | ||
|
|
75e3949cec | ||
|
|
a7bb4b7104 | ||
|
|
9bcb2ac094 | ||
|
|
9ab110277a | ||
|
|
d1506bcdae | ||
|
|
00c38c96cd | ||
|
|
9798d4ff6a | ||
|
|
5801b29ede | ||
|
|
22ed022787 | ||
|
|
f7e7ecf5ae | ||
|
|
c116d3f453 | ||
|
|
d9b3d263ae | ||
|
|
7c54adec9d | ||
|
|
68abd91fc2 | ||
|
|
4d2e42d244 | ||
|
|
5a87a6c502 | ||
|
|
9a265a0437 | ||
|
|
d8ca15ceb3 | ||
|
|
a17f718517 | ||
|
|
3589dda8ee | ||
|
|
21f8e4d55b | ||
|
|
811e0123c9 | ||
|
|
47c4516060 | ||
|
|
16450e2133 | ||
|
|
5678742810 | ||
|
|
29ce3bd05e | ||
|
|
b3d58f4f0c | ||
|
|
76d551a238 | ||
|
|
5467c4b1b8 | ||
|
|
e48d277440 | ||
|
|
13913334b6 | ||
|
|
7f60725c88 | ||
|
|
d55fb36182 | ||
|
|
41205aef20 | ||
|
|
aeadbc1d58 | ||
|
|
bd12ade426 | ||
|
|
f9c26089cd | ||
|
|
7ddb57078c | ||
|
|
3e7f1275d8 | ||
|
|
e963938016 | ||
|
|
9d1da81557 | ||
|
|
91f557ac9e | ||
|
|
452826bd61 | ||
|
|
84564bb7fb | ||
|
|
bf7647c737 | ||
|
|
1f98d7e5ec | ||
|
|
e4486f4c17 | ||
|
|
65daa23a74 | ||
|
|
523a30afb4 | ||
|
|
1d0de4584e | ||
|
|
7aac124407 | ||
|
|
03e8b664ac | ||
|
|
312fcea5f1 | ||
|
|
66883ae37c | ||
|
|
9d05653f5b | ||
|
|
644ebd0a4f | ||
|
|
1033bfcfe5 | ||
|
|
2a075d929a | ||
|
|
d93d234c71 | ||
|
|
7fe9ae8a04 | ||
|
|
87cf1ed7cb | ||
|
|
a0ba5c8a57 | ||
|
|
d7208a15d9 | ||
|
|
debde0c67a | ||
|
|
97db55156a | ||
|
|
9d3477d465 | ||
|
|
41147483d8 | ||
|
|
ca517784ed | ||
|
|
4dd3be1568 | ||
|
|
7412d97bf3 | ||
|
|
ab7b2da83b | ||
|
|
19e26729a8 | ||
|
|
0101365ebc | ||
|
|
1f56e63f9c | ||
|
|
883a30c7ad | ||
|
|
6e151a9f8b | ||
|
|
321bb299b1 | ||
|
|
2ca18340c7 | ||
|
|
74d4237913 | ||
|
|
a4f9b9208d | ||
|
|
8fd65b7365 | ||
|
|
4f4d0bf6aa | ||
|
|
873e2aed94 | ||
|
|
89d485e188 | ||
|
|
2e9870014f | ||
|
|
528529c0d1 | ||
|
|
9bddec2dfd | ||
|
|
f986487df9 | ||
|
|
031af000e6 | ||
|
|
0512fac3aa | ||
|
|
99461a70e6 | ||
|
|
adbbb15a92 | ||
|
|
d85a4c9ad4 | ||
|
|
41baea780a | ||
|
|
a165884bcb | ||
|
|
456adc5d0b | ||
|
|
cfc42906b9 | ||
|
|
738d657c8e | ||
|
|
a51452ee7c | ||
|
|
5d2a41082a | ||
|
|
f9dd00b79b | ||
|
|
898244d04d | ||
|
|
33334830cc | ||
|
|
8503350bfd | ||
|
|
a4bb2aaf12 | ||
|
|
da443045bf | ||
|
|
b9927cd48d | ||
|
|
4272e8196a | ||
|
|
140f9d05df | ||
|
|
9222733243 | ||
|
|
5838c56c4e | ||
|
|
7af3f7e881 | ||
|
|
ee81febc89 | ||
|
|
8146bee846 | ||
|
|
53e94378ae | ||
|
|
8592ead0e3 | ||
|
|
67699372f2 | ||
|
|
95a8ced558 | ||
|
|
ae437be6e7 | ||
|
|
1441d9f4ee | ||
|
|
1814e0a260 | ||
|
|
711c5781e6 | ||
|
|
adeb5c2344 | ||
|
|
f0b0277b9d | ||
|
|
a688310b95 | ||
|
|
6fb5fb63e7 | ||
|
|
5330cc5ae9 | ||
|
|
bcc2244fdb | ||
|
|
ad2de95f32 | ||
|
|
e3ffc8784e | ||
|
|
bb35fc3801 | ||
|
|
c804630576 | ||
|
|
e5f3ca1623 | ||
|
|
453dee33ba | ||
|
|
0880787d68 | ||
|
|
cd582e2e3a | ||
|
|
aebd9319f5 | ||
|
|
de6cbb0f45 | ||
|
|
e14dcd0184 | ||
|
|
17ef653903 | ||
|
|
f5d5b5efc0 | ||
|
|
59dbee8f28 | ||
|
|
13f36b3f79 | ||
|
|
719b63ee02 | ||
|
|
38a5698f90 | ||
|
|
4db6971cc4 | ||
|
|
71482bd06c | ||
|
|
c3acf8341b | ||
|
|
a05b60f48e | ||
|
|
8694ecd417 | ||
|
|
1bc48fbf96 | ||
|
|
d45348c167 | ||
|
|
22caa0ee66 | ||
|
|
e6e8ccc855 | ||
|
|
d78522f5e1 | ||
|
|
3da2a618b9 | ||
|
|
100d007271 | ||
|
|
a1a764d807 | ||
|
|
047fa5b2db | ||
|
|
6cb30adf5d | ||
|
|
9eb939e38f | ||
|
|
3e26060979 | ||
|
|
cc60aa7b84 | ||
|
|
c763794ef3 | ||
|
|
bdfdafaec0 | ||
|
|
7737dc6b6c | ||
|
|
1a89465201 | ||
|
|
7a4dcd52c4 | ||
|
|
e8e7a92131 | ||
|
|
7e8c2211d8 | ||
|
|
99694161e1 | ||
|
|
fe3ce45b8e | ||
|
|
7af0883f08 | ||
|
|
f48c21b124 | ||
|
|
abc2e74f2c | ||
|
|
6130c49b83 | ||
|
|
433b58511c | ||
|
|
f0858b7d93 | ||
|
|
3af6c28ab0 | ||
|
|
5c31c7f14c | ||
|
|
b04111c79b | ||
|
|
06ca0079b3 | ||
|
|
ff53c6b49d | ||
|
|
da58458fb7 | ||
|
|
1a21989ad1 | ||
|
|
2610a9c777 | ||
|
|
58cf611497 | ||
|
|
76d54b8914 | ||
|
|
00f944f3f4 | ||
|
|
1269411771 | ||
|
|
d4d8ea6cf2 | ||
|
|
160522c520 | ||
|
|
7024b5ec1b | ||
|
|
5b020035d6 | ||
|
|
fcea7fd4bf | ||
|
|
d22d64f68c | ||
|
|
580ae005f4 | ||
|
|
75ab9d2e8c | ||
|
|
37e5bcad61 | ||
|
|
20679a62fd | ||
|
|
bb5a5bf2ed | ||
|
|
c1db993b92 | ||
|
|
61631e405b | ||
|
|
c19916ff1c | ||
|
|
6fa2e79c1c | ||
|
|
185e0849b1 | ||
|
|
f48b9d364b | ||
|
|
e4f1a7d4c1 | ||
|
|
e02f19ff67 | ||
|
|
72a2110845 | ||
|
|
9baa415249 | ||
|
|
22b840af14 | ||
|
|
61f32d89ca | ||
|
|
3c7da93dfc | ||
|
|
5831616fac | ||
|
|
d7b6d9f124 | ||
|
|
6e7588e9fc | ||
|
|
03cc8248bc | ||
|
|
068df6f2b1 | ||
|
|
0966ba909b | ||
|
|
6c246768e9 | ||
|
|
245978e2b3 | ||
|
|
3aae60bde9 | ||
|
|
7941a24d51 | ||
|
|
bc75bc199b | ||
|
|
91d36122eb | ||
|
|
e004ba63f8 | ||
|
|
f234b6a540 | ||
|
|
36c1e2cc73 | ||
|
|
4a0a09ba3e | ||
|
|
bff8bfea7b | ||
|
|
48bf0d1942 | ||
|
|
04bbb84845 | ||
|
|
1f30a19566 | ||
|
|
51f4578a41 | ||
|
|
311f8cd00f | ||
|
|
ed0ab78048 | ||
|
|
bd3954a5f1 | ||
|
|
94967add7c | ||
|
|
783ab0b611 | ||
|
|
0eec1c1f61 | ||
|
|
b4a3b832dc | ||
|
|
04b44cff2b | ||
|
|
8309755ee3 | ||
|
|
8e7830dd7d | ||
|
|
41a75d404c | ||
|
|
8eb63de201 | ||
|
|
1dec134a6b | ||
|
|
6f6a8e4bb1 | ||
|
|
d32a18d965 | ||
|
|
7d00d47cb6 | ||
|
|
b0853eb119 | ||
|
|
25d29fb389 | ||
|
|
87d216c578 | ||
|
|
bb0ee61870 | ||
|
|
653a9526f5 | ||
|
|
34ac4b25af | ||
|
|
c7b7f9236f | ||
|
|
881779744f | ||
|
|
ddf3356b5d | ||
|
|
ed241ede9d | ||
|
|
94e8f3e2c1 | ||
|
|
2c4a7f48d1 | ||
|
|
af47c23ca1 | ||
|
|
326a001c57 | ||
|
|
fe9e548e89 | ||
|
|
e58ef1f436 | ||
|
|
f249fa6f65 | ||
|
|
68c6b63c9c | ||
|
|
a249597225 | ||
|
|
4906c677af | ||
|
|
2534a9f14a | ||
|
|
5488c2bdeb | ||
|
|
e072ff2d77 | ||
|
|
41dfbc2709 | ||
|
|
04a27d5778 | ||
|
|
964e461597 | ||
|
|
ef2eec4c4a | ||
|
|
bf1d76d853 | ||
|
|
0682cbd554 | ||
|
|
f5191cdd42 | ||
|
|
b1c73208c5 | ||
|
|
ab221a465b | ||
|
|
4ecfa0477d | ||
|
|
bab2de36ad | ||
|
|
70a2067a06 | ||
|
|
4a13c01817 | ||
|
|
151c2b573c | ||
|
|
4cdb3f4c6a | ||
|
|
3cf0384bc5 | ||
|
|
ad2f6ebe93 | ||
|
|
178a429f26 | ||
|
|
f479e914bb | ||
|
|
45441653f6 | ||
|
|
0303558ae1 | ||
|
|
b70e0e3e2b | ||
|
|
5e0c4d7b7a | ||
|
|
6c83308451 | ||
|
|
eeb898179e | ||
|
|
0ea662d8fe | ||
|
|
ea3219fa10 | ||
|
|
fb9203d396 | ||
|
|
23e7542871 | ||
|
|
a40832dffd | ||
|
|
5ba7493613 | ||
|
|
e7b406c127 | ||
|
|
dd1d16f91c | ||
|
|
82e2a19749 | ||
|
|
b73126e6c1 | ||
|
|
cbd93f450e | ||
|
|
35c64be3d7 | ||
|
|
71194d5b4e | ||
|
|
771c530b85 | ||
|
|
96ea70c027 | ||
|
|
ea277d0579 | ||
|
|
10d7cd1520 | ||
|
|
9051322338 | ||
|
|
d26b6103b5 | ||
|
|
f2e7963e1f | ||
|
|
78a6b622fb | ||
|
|
0177bbebe0 | ||
|
|
a3d7e541d3 | ||
|
|
8deed4a9cd | ||
|
|
4617025bd4 | ||
|
|
0693e19605 | ||
|
|
62618acfed | ||
|
|
1feaa43d2e | ||
|
|
92d7d61926 | ||
|
|
265d77d776 | ||
|
|
60b2576ce8 | ||
|
|
90cc58a8fe | ||
|
|
d79f750e30 | ||
|
|
9f9ab01508 | ||
|
|
4dc89c9082 | ||
|
|
48d9fde3b6 | ||
|
|
500349a8bd | ||
|
|
e299f3e510 | ||
|
|
a9ea1a02ed | ||
|
|
19cd5c8881 | ||
|
|
cdb9c661bd | ||
|
|
c3a01c240b | ||
|
|
e2f748e63d | ||
|
|
6114f4644f | ||
|
|
eb664404e1 | ||
|
|
370f645cf0 | ||
|
|
bd5b18a163 | ||
|
|
c51b0c6a41 | ||
|
|
d56f6e75f9 | ||
|
|
c743348872 | ||
|
|
578049bfb6 | ||
|
|
564cd37628 | ||
|
|
60ecf91935 | ||
|
|
7b452e93b2 | ||
|
|
0bc1f7bf8c | ||
|
|
62c2421d85 | ||
|
|
f2edf56d02 | ||
|
|
376e5aeb45 | ||
|
|
3d1c7e0bc1 | ||
|
|
a2603f882d | ||
|
|
0c1dcafc35 | ||
|
|
d5108f8007 | ||
|
|
ebeca9aa04 | ||
|
|
7307d9f7f1 | ||
|
|
eac7cdae1c | ||
|
|
7e548cb133 | ||
|
|
0a61512fc7 | ||
|
|
52fa4f11ac | ||
|
|
479f2010a9 | ||
|
|
55796932c4 | ||
|
|
bfcabd30c5 | ||
|
|
095ea470a1 | ||
|
|
c9b502c72b | ||
|
|
9390eacb7c | ||
|
|
f193ce87bf | ||
|
|
9267adf79a | ||
|
|
cc2f86cb06 | ||
|
|
14c6895135 | ||
|
|
83f6647352 | ||
|
|
f6b3f898de | ||
|
|
792ecee399 | ||
|
|
93a1ef6bdb | ||
|
|
62607b16f8 | ||
|
|
ca60376447 | ||
|
|
3cc4b5db79 | ||
|
|
f8179c83e7 | ||
|
|
b2233f61e4 | ||
|
|
a7e2f776e4 | ||
|
|
de5ba5d0d3 | ||
|
|
50d672892c | ||
|
|
e6154db6e5 | ||
|
|
34e8f57f7d | ||
|
|
4a8c089fa9 | ||
|
|
b3aa5ee247 | ||
|
|
64bf98a7d3 | ||
|
|
e8e38beeb8 | ||
|
|
de96a96ac6 | ||
|
|
ee3ad17163 | ||
|
|
fafd8a5d51 | ||
|
|
c5879f17f8 | ||
|
|
999cbd314c | ||
|
|
a9d34a223a | ||
|
|
3381030ed5 | ||
|
|
2bacc6cfe8 | ||
|
|
973c936514 | ||
|
|
c5121a7fc5 | ||
|
|
37bf0f6b53 | ||
|
|
203d51cdbf | ||
|
|
9669d8eb8b | ||
|
|
1b7571be5b | ||
|
|
614ff2a30e | ||
|
|
5b11671997 | ||
|
|
5158e08901 | ||
|
|
2cb9c7211e | ||
|
|
24e26c95ff | ||
|
|
b8286af8fa | ||
|
|
c539dd5570 | ||
|
|
de76afea99 | ||
|
|
1d3616ae71 | ||
|
|
d76cb440f9 | ||
|
|
7c89d658f7 | ||
|
|
735279c27c | ||
|
|
292c929117 | ||
|
|
d75be22d1f | ||
|
|
40f1b1c665 | ||
|
|
daf42b63c8 | ||
|
|
07da03618f | ||
|
|
d076c875ed | ||
|
|
771c7fe863 | ||
|
|
dda51f2801 | ||
|
|
25472bcfa6 | ||
|
|
369454c12a | ||
|
|
1784eacf58 | ||
|
|
e86f5b3b7c | ||
|
|
80ff6cda04 | ||
|
|
dff96cfd95 | ||
|
|
31d244ef78 | ||
|
|
8325a84ab2 | ||
|
|
793839c7d5 | ||
|
|
23cf87dbc0 | ||
|
|
7171de336d | ||
|
|
e206cfe6d6 | ||
|
|
1a71cc9223 | ||
|
|
ed6fcf5ae7 | ||
|
|
bb31693a4d | ||
|
|
d15c8b16f3 | ||
|
|
8b9c932b80 | ||
|
|
c10e8f5f9a | ||
|
|
cd27e43994 | ||
|
|
20614cf64b | ||
|
|
c69c02bcb3 | ||
|
|
21b177cbb4 | ||
|
|
31a7e48768 | ||
|
|
fd3d60ed7d | ||
|
|
e2e369a463 | ||
|
|
0c3304f041 | ||
|
|
0d4b9b4bce | ||
|
|
1192bf6a87 | ||
|
|
2366a91e8d | ||
|
|
6ff17d16f0 | ||
|
|
06b7116692 | ||
|
|
3c3ea0f3e1 | ||
|
|
db4d6511d6 | ||
|
|
6e42a67268 | ||
|
|
fd066e5eef | ||
|
|
3dd0c44410 | ||
|
|
12b42854e4 | ||
|
|
2fcb6d0c7c | ||
|
|
68e863723a | ||
|
|
d0b37d0f9a | ||
|
|
a0a1353445 | ||
|
|
2c4e46c630 | ||
|
|
a989e296b0 | ||
|
|
26648dbcd2 | ||
|
|
147d759d35 | ||
|
|
f6b07c5609 | ||
|
|
29fa7a053f | ||
|
|
f380f245c6 | ||
|
|
1824a30cde | ||
|
|
8ab86fd6bb | ||
|
|
2c7bdc54c1 | ||
|
|
39b57da42b | ||
|
|
a98a9fd97a | ||
|
|
093a5c1019 | ||
|
|
ba25acaab9 | ||
|
|
9783802370 | ||
|
|
47bb02ac08 | ||
|
|
1f952d81aa | ||
|
|
0632019e44 | ||
|
|
bc14d0f580 | ||
|
|
4bc3998010 | ||
|
|
cd9c2d1988 | ||
|
|
5d597c22bf | ||
|
|
574144f9b1 | ||
|
|
b26bee3524 | ||
|
|
c835cf7829 | ||
|
|
a1d819dcb6 | ||
|
|
2234cc9334 | ||
|
|
cabd1506b7 | ||
|
|
a7c2e321bf | ||
|
|
31b75179bd | ||
|
|
06152f3131 | ||
|
|
c82f8c997f | ||
|
|
6725cc6f61 | ||
|
|
7e9639052b | ||
|
|
21bd5ba376 | ||
|
|
f06840f4b8 | ||
|
|
11c3d6d056 | ||
|
|
814a566845 | ||
|
|
28d5fb1822 | ||
|
|
cd3c031df1 | ||
|
|
a6db0f6fd9 | ||
|
|
c80f6e8285 | ||
|
|
c6d779853a | ||
|
|
1b720a504c | ||
|
|
72f8854a7a | ||
|
|
097d195f00 | ||
|
|
807da8f696 | ||
|
|
a8ca6b6fcb | ||
|
|
85e2e14c81 | ||
|
|
f3bcaf2710 | ||
|
|
f9dba9266f | ||
|
|
00f7df3982 | ||
|
|
5ae45ddd55 | ||
|
|
4e0a3da01e | ||
|
|
541a99bbc5 | ||
|
|
f62008aba4 | ||
|
|
74f7415f84 | ||
|
|
1e1e079b65 | ||
|
|
0b4f808b2d | ||
|
|
ae4af99c59 | ||
|
|
0b17556fa4 | ||
|
|
e6ebc347e5 | ||
|
|
f2323a9d19 | ||
|
|
ce53fe5e31 | ||
|
|
f1d359b3e7 | ||
|
|
da99f3bc2a | ||
|
|
fec8dd74af | ||
|
|
f598e0d0d5 | ||
|
|
656d2494b0 | ||
|
|
6d07d58f37 | ||
|
|
b873a77409 | ||
|
|
dd5270f620 | ||
|
|
216f895953 | ||
|
|
6acb87b7ea | ||
|
|
acdccd697c | ||
|
|
cecc4b1f6d | ||
|
|
f93f115e13 | ||
|
|
debcf086b5 | ||
|
|
c6db974962 | ||
|
|
e9b0b0c42e | ||
|
|
ebee1a02fd | ||
|
|
cd5a26398a | ||
|
|
5e8a614d82 | ||
|
|
bad601edb1 | ||
|
|
58297219a8 | ||
|
|
087b191e5b | ||
|
|
de4468a15a | ||
|
|
b73de087d2 | ||
|
|
39fd092055 | ||
|
|
dec88bd601 | ||
|
|
bc9975baa1 | ||
|
|
d2d401883e | ||
|
|
3b3ac1688a | ||
|
|
6bb0ca22d0 | ||
|
|
e157bc7b97 | ||
|
|
578da0f1a7 | ||
|
|
8f0dd0b0c6 | ||
|
|
9e05bc4fad | ||
|
|
4cee341ce5 | ||
|
|
c40192aa46 | ||
|
|
651748cd4e | ||
|
|
9e8f8357b1 | ||
|
|
3cc4c07fa1 | ||
|
|
8ed52af203 | ||
|
|
8e9a941b5d | ||
|
|
4538a4d33a | ||
|
|
a8469456ce | ||
|
|
6ca25f913a | ||
|
|
da74c7df8a | ||
|
|
278aa87753 | ||
|
|
0f77294718 | ||
|
|
38df160a5e | ||
|
|
dc61a3307c | ||
|
|
2724f8d3c5 | ||
|
|
36e1c1eff0 | ||
|
|
81f830fe23 | ||
|
|
6a42482b92 | ||
|
|
8be83c278b | ||
|
|
eb82980cbc | ||
|
|
08a6d28868 | ||
|
|
37afbc8e9d | ||
|
|
12546b3f17 | ||
|
|
70821c5d26 | ||
|
|
2f7725c5a9 | ||
|
|
a96d26c3bd | ||
|
|
a9f37c9238 | ||
|
|
de05139dfc | ||
|
|
239ebcdcb8 | ||
|
|
9e6e62c5e8 | ||
|
|
3a0e9f422e | ||
|
|
ef8beb9310 | ||
|
|
260094a666 | ||
|
|
659f9a8f18 | ||
|
|
cf2e3c8018 | ||
|
|
e88d0579b0 | ||
|
|
a05901e792 | ||
|
|
0928ec5c4c | ||
|
|
ef843d02c0 | ||
|
|
07cdc6bf2f | ||
|
|
5564e4daa2 | ||
|
|
4742cd4a03 | ||
|
|
832c1ef83c | ||
|
|
f5d0fc8672 | ||
|
|
99985f4fab | ||
|
|
4c0c917fb5 | ||
|
|
9e5ac261e2 | ||
|
|
42bb3b5aca | ||
|
|
06abfc4337 | ||
|
|
70bedaf8dd | ||
|
|
c9c54200aa | ||
|
|
c5f948099d | ||
|
|
a78cb59bc3 | ||
|
|
fb5ff24747 | ||
|
|
38683a7fea | ||
|
|
ae1a68500c | ||
|
|
578f842eed | ||
|
|
acb2ede658 | ||
|
|
a7dea95f90 | ||
|
|
1080562d96 | ||
|
|
b8cf5a2347 | ||
|
|
e9706d605a | ||
|
|
4b338c56b5 | ||
|
|
05b1bffeef | ||
|
|
b330b55054 | ||
|
|
9a67e63e9b | ||
|
|
0d629a5385 | ||
|
|
4e6a214581 | ||
|
|
333c591771 | ||
|
|
e6645101ad | ||
|
|
48788986cb | ||
|
|
d400beffb6 | ||
|
|
7762135cb1 | ||
|
|
d0d594df69 | ||
|
|
ed97f2d786 | ||
|
|
f3a724237f | ||
|
|
aeb3a308a8 | ||
|
|
62cc97bc44 | ||
|
|
20928c7a7f | ||
|
|
6f4a865604 | ||
|
|
e7919416d5 | ||
|
|
cf8a512af4 | ||
|
|
3efef7bbbb | ||
|
|
65d8e01529 | ||
|
|
e0dd7a9b4b | ||
|
|
3a32d220dd | ||
|
|
5efaf7b23e | ||
|
|
91375a5447 | ||
|
|
f6574d346c | ||
|
|
b9b29b11d4 | ||
|
|
54d062156d | ||
|
|
4dc24e48d8 | ||
|
|
3a24464c90 | ||
|
|
f2ad9395fc | ||
|
|
61539416e5 | ||
|
|
ecf7c1f3f3 | ||
|
|
d18886ed7c | ||
|
|
07cc529f6e | ||
|
|
01b87bfbfd | ||
|
|
b126f0cb79 | ||
|
|
10c6901a04 | ||
|
|
c26efb111e | ||
|
|
f07cbe0087 | ||
|
|
7d72e196e0 | ||
|
|
9bc935da96 | ||
|
|
d2e3b317ef | ||
|
|
0edd39805b | ||
|
|
15189014d0 | ||
|
|
46ab3ed1ea | ||
|
|
edfbbe8c5a | ||
|
|
9d35295b47 | ||
|
|
b59d07fb5f | ||
|
|
9c0fda2b52 | ||
|
|
2974a96e96 | ||
|
|
ab7fd3d019 | ||
|
|
54c5659496 | ||
|
|
8c0811885f | ||
|
|
291570dfd7 | ||
|
|
f6b9b4cc19 | ||
|
|
a06403ab7c | ||
|
|
e64a95d1d7 | ||
|
|
6939e49643 | ||
|
|
bb18586484 | ||
|
|
09b39a4bf9 | ||
|
|
1d7d639654 | ||
|
|
42ec509574 | ||
|
|
dcef864c1c | ||
|
|
e8cb4f90f4 | ||
|
|
4e0b2e4f77 | ||
|
|
9ab1a980a7 | ||
|
|
a2bcadeb7c | ||
|
|
84a4242e27 | ||
|
|
6373d667d9 | ||
|
|
07d91b8f50 | ||
|
|
5645921a8e | ||
|
|
7840d601e1 | ||
|
|
1893061c0d | ||
|
|
0289cc3ee5 | ||
|
|
9c17ac1c33 | ||
|
|
a35c9d2d9e | ||
|
|
7f739a1371 | ||
|
|
62df78f329 | ||
|
|
a80968aaa7 | ||
|
|
2589a15e0a | ||
|
|
950efde3b8 | ||
|
|
de8d5e4489 | ||
|
|
d6eefa5185 | ||
|
|
5ad0cffbb3 | ||
|
|
89bd00c65e | ||
|
|
be4511d95b | ||
|
|
b17e6058d1 | ||
|
|
99c28a184f | ||
|
|
7ed9adaf49 | ||
|
|
c3ef051657 | ||
|
|
ba149efa4a | ||
|
|
e30a7b3849 | ||
|
|
97ad0483ec | ||
|
|
3981c772a2 | ||
|
|
e762002560 | ||
|
|
061d8dc94f | ||
|
|
b7e7a0df94 | ||
|
|
84e6228f90 | ||
|
|
e3f02fc4d6 | ||
|
|
81bece9dc4 | ||
|
|
db4b05e9d2 | ||
|
|
0b9029c7b5 | ||
|
|
7fd9a65ce5 | ||
|
|
8cd2d52b3d | ||
|
|
d8e8c5504a | ||
|
|
7535f6af51 | ||
|
|
347effcf70 | ||
|
|
2b2176ba24 | ||
|
|
a0b2d9384d | ||
|
|
58cc047ffa | ||
|
|
cfacd90815 | ||
|
|
f46dca910b | ||
|
|
d362dfc359 | ||
|
|
4dff1ead06 | ||
|
|
ec8aa28aea | ||
|
|
e49abd909d | ||
|
|
ce2c700f1d | ||
|
|
dcb9f90a8c | ||
|
|
c936d4f5a3 | ||
|
|
44f9292bfd | ||
|
|
259fae0134 | ||
|
|
04c28824b2 | ||
|
|
c5bf4d1723 | ||
|
|
b72a7f2c22 | ||
|
|
71cef03a4b | ||
|
|
13abde0b0f | ||
|
|
e4850e0a48 | ||
|
|
1c225ff1ec | ||
|
|
1b4e80858d | ||
|
|
4f683edcb9 | ||
|
|
301cc22985 | ||
|
|
834fe6da45 | ||
|
|
35678661d1 | ||
|
|
29398b9869 | ||
|
|
1ef6b0b5f9 | ||
|
|
d1cf246600 | ||
|
|
8b65a75235 | ||
|
|
093e09c732 | ||
|
|
313f7e8173 | ||
|
|
d9e615e696 | ||
|
|
b526067eeb | ||
|
|
5c25a82a2b | ||
|
|
2f224fc26e | ||
|
|
2969f8da36 | ||
|
|
8959d12746 | ||
|
|
6e6574a235 | ||
|
|
bf3eea4a41 | ||
|
|
044594c614 | ||
|
|
aeba346a4e | ||
|
|
779a3d2d70 | ||
|
|
6a8667cc4b | ||
|
|
29972fe54e | ||
|
|
cf9e9e4fcf | ||
|
|
ddd14999d7 | ||
|
|
cfc9c60794 | ||
|
|
71f206446c | ||
|
|
e368465591 | ||
|
|
2f4314257c | ||
|
|
204d32b9fd | ||
|
|
a66f522e77 | ||
|
|
9016b7be90 | ||
|
|
545da61855 | ||
|
|
4c22f2525c | ||
|
|
2e5c4bb5ee | ||
|
|
6a85db1d5d | ||
|
|
2b16b961f4 | ||
|
|
a2531b3073 | ||
|
|
88a2baf19e | ||
|
|
f0766f753c | ||
|
|
8d67aed95a | ||
|
|
678dbd76cb | ||
|
|
ae9269d1b1 | ||
|
|
971c238313 | ||
|
|
e713befd59 | ||
|
|
eb95c75c29 | ||
|
|
a522e2bcbd | ||
|
|
b1a8902e33 | ||
|
|
8c706d2393 | ||
|
|
1b55746213 | ||
|
|
3f5a7809f0 | ||
|
|
ecfd05f788 | ||
|
|
41585f1467 | ||
|
|
3c6a3674a5 | ||
|
|
e4bc31357f | ||
|
|
a9436f9668 | ||
|
|
f90c236fa4 | ||
|
|
c5a2d6a1eb | ||
|
|
9bf41edb88 | ||
|
|
4c5f27301e | ||
|
|
99c9ad69ac | ||
|
|
6866267cf5 | ||
|
|
92307013a7 | ||
|
|
ed81261dc2 | ||
|
|
1409ae026d | ||
|
|
566ca24833 | ||
|
|
844a10911a | ||
|
|
015f6d2536 | ||
|
|
c1fd149118 | ||
|
|
887533018c | ||
|
|
7dfbb6753a | ||
|
|
5c2f69c820 | ||
|
|
0abed8f132 | ||
|
|
158d0ea65e | ||
|
|
cdb31a18f5 | ||
|
|
17f75bbd77 | ||
|
|
d1b54c4195 | ||
|
|
a3e18c95f5 | ||
|
|
693fd6d6f3 | ||
|
|
b3029514c7 | ||
|
|
fd6dc6090e | ||
|
|
a94bc50e25 | ||
|
|
13d5ecb79f | ||
|
|
de0b365bc7 | ||
|
|
b287406c94 | ||
|
|
320865c262 | ||
|
|
e550ea2141 | ||
|
|
19e47a2742 | ||
|
|
ca64db3e54 | ||
|
|
ea57b40d8c | ||
|
|
7c4e7eb225 | ||
|
|
028e950c81 | ||
|
|
d5e118d292 | ||
|
|
5b50f3d294 | ||
|
|
ef5d97194f | ||
|
|
d4f34cbdb6 | ||
|
|
0152b7e534 | ||
|
|
ace5c873a3 | ||
|
|
eb7b76bed3 | ||
|
|
d72a9a3907 | ||
|
|
cfeb9f7cd4 | ||
|
|
a690982a09 | ||
|
|
2949a830d9 | ||
|
|
a757db7a44 | ||
|
|
e73899d083 | ||
|
|
05c45e1b6b | ||
|
|
00027434ad | ||
|
|
60c46d529c | ||
|
|
5b91e6aa59 | ||
|
|
615fb3f387 | ||
|
|
24751675c5 | ||
|
|
0e2e54778d | ||
|
|
f336832395 | ||
|
|
44826da3ee | ||
|
|
e492f4fdc9 | ||
|
|
b786d4ac06 | ||
|
|
15abc40bd0 | ||
|
|
e5ac1bc629 | ||
|
|
d67a99b8f8 | ||
|
|
6816f93ecb | ||
|
|
30ecab05cf | ||
|
|
ff13998205 | ||
|
|
1e1e2166b9 | ||
|
|
fcfa5bcb8d | ||
|
|
284599d249 | ||
|
|
caf2ffc167 | ||
|
|
e73ac0af22 | ||
|
|
53dff59c6a | ||
|
|
1d8c8ea1b0 | ||
|
|
b18acfac83 | ||
|
|
fb1f9c675b | ||
|
|
02cef37128 | ||
|
|
08a84df7d8 | ||
|
|
084ba95b6a | ||
|
|
37c11bfcb2 | ||
|
|
5bba2b7bf0 | ||
|
|
9e5c8a650e | ||
|
|
44dc8210bc | ||
|
|
86d40b774e | ||
|
|
e8ff2f8229 | ||
|
|
1ca7faf7c6 | ||
|
|
eeba0940b6 | ||
|
|
c81f7cebf7 | ||
|
|
4affdd91a7 | ||
|
|
d8153d1263 | ||
|
|
3f7ec9e0c2 | ||
|
|
d0ef14ccd8 | ||
|
|
f6f35f3e49 | ||
|
|
ec0302644f | ||
|
|
0e5f7f8d1b | ||
|
|
3498f3fd2f | ||
|
|
b68cfc8f72 | ||
|
|
af11b7cd26 | ||
|
|
6aad4a4a98 | ||
|
|
d7a4395557 | ||
|
|
4df8b90bfe | ||
|
|
0ded953527 | ||
|
|
5a345ce4c9 | ||
|
|
8f8dc390ca | ||
|
|
52fb5641ce | ||
|
|
f47eea03cc | ||
|
|
432864c323 | ||
|
|
4608216e28 | ||
|
|
befdd2e7ad | ||
|
|
13b73acee6 | ||
|
|
d1800fe86a | ||
|
|
13b7b72865 | ||
|
|
0894114aac | ||
|
|
50ae55d74f | ||
|
|
9e8ed150a3 | ||
|
|
7e682eac22 | ||
|
|
172fbb51a4 | ||
|
|
f4bee77131 | ||
|
|
1dcce05f98 | ||
|
|
58239efa84 | ||
|
|
d42121a426 | ||
|
|
4a031d70a5 | ||
|
|
3a5c512a62 | ||
|
|
1de8f9a92a | ||
|
|
6a6dddfef1 | ||
|
|
8180075d8f | ||
|
|
466fd49ad0 | ||
|
|
8ae8e99b25 | ||
|
|
3dc0066e06 | ||
|
|
826add7b43 | ||
|
|
3bc345d391 | ||
|
|
91b0acd4e4 | ||
|
|
774b4051bc | ||
|
|
ad5e68091f | ||
|
|
280b3275b4 | ||
|
|
a3a87f54df | ||
|
|
8ed912913d | ||
|
|
99068ca605 | ||
|
|
514109fa4e | ||
|
|
f979092f59 | ||
|
|
1d9d1ca7dc | ||
|
|
4df7235b09 | ||
|
|
ee72e5ea91 | ||
|
|
6575891546 | ||
|
|
30c7fbddaa | ||
|
|
e1c97ac393 | ||
|
|
c443422b10 | ||
|
|
2ef2f9bbc3 | ||
|
|
1c98a05b4f | ||
|
|
f637cab5ec | ||
|
|
0e232b4c49 | ||
|
|
3d37260d5c | ||
|
|
ce56f072d4 | ||
|
|
3fc5aba3bc | ||
|
|
92f450cda0 | ||
|
|
76b26a7ee6 | ||
|
|
cdc9eff2fb | ||
|
|
7fb3b19cb8 | ||
|
|
76b9c3c0e4 | ||
|
|
109864c3bf | ||
|
|
522fc51c3a | ||
|
|
3dd66e40f8 | ||
|
|
54dc3e5a71 | ||
|
|
23311c0235 | ||
|
|
6e75a3829a | ||
|
|
8b3373a7cc | ||
|
|
7fc9ca96b2 | ||
|
|
7c3dcf586c | ||
|
|
a1ef1353da | ||
|
|
9ce2c55cc4 | ||
|
|
49fcefbf2d | ||
|
|
7eb0c36a8e | ||
|
|
95dc2d430c | ||
|
|
a3d04fa810 | ||
|
|
26aaf5f53c | ||
|
|
5208d3d091 | ||
|
|
177f7af7d7 | ||
|
|
047f8a7888 | ||
|
|
e302b33d3b | ||
|
|
0c5c76b5cc | ||
|
|
54a7de62e0 | ||
|
|
5b056f563e | ||
|
|
a38972bd2e | ||
|
|
0f3c3920a1 | ||
|
|
f23905f5ee | ||
|
|
e69db958b2 | ||
|
|
ac39e3b98e | ||
|
|
c22f642418 | ||
|
|
c8a47d7b99 | ||
|
|
302d9cb321 | ||
|
|
95a4dc94a4 | ||
|
|
3ca027fa69 | ||
|
|
02ae845bab | ||
|
|
0472c22b08 | ||
|
|
424c6403e1 | ||
|
|
e8b1be6568 | ||
|
|
c759df855d | ||
|
|
e105ce8c65 | ||
|
|
9a813adf0e | ||
|
|
e703824198 | ||
|
|
9eb3973131 | ||
|
|
1f09d9e8b9 | ||
|
|
10edf58bf9 | ||
|
|
426dbf4fff | ||
|
|
e68f1d64a3 | ||
|
|
3f25d10e6f | ||
|
|
537e3b6f1f | ||
|
|
6988a3250b | ||
|
|
4c902c5da5 | ||
|
|
f7c793ce8c | ||
|
|
ac69349822 | ||
|
|
937345f0e0 | ||
|
|
34a77d4a11 | ||
|
|
e3519f571e | ||
|
|
dd4a40ccab | ||
|
|
2d0e8d371a | ||
|
|
a6f391dd68 | ||
|
|
6dff423206 | ||
|
|
87b880d869 | ||
|
|
e53742e554 | ||
|
|
94b88d8f8a | ||
|
|
0b9f8b8340 | ||
|
|
e384c8f5a7 | ||
|
|
8ae74ee4fe | ||
|
|
35a06a4364 | ||
|
|
f9055952a4 | ||
|
|
20e675a080 | ||
|
|
199d107914 | ||
|
|
7812266dea | ||
|
|
53af1f5fdd | ||
|
|
f619d3f9a7 | ||
|
|
64e2dc3529 | ||
|
|
cbd0b91d6b | ||
|
|
f4d7ce72c2 | ||
|
|
3c52c56315 | ||
|
|
c3119d1b29 | ||
|
|
4b5d13fd19 | ||
|
|
0fdceb2281 | ||
|
|
9b2c56815b | ||
|
|
c650a92a1d | ||
|
|
c611cccc06 | ||
|
|
4ea335b974 | ||
|
|
c140216fc3 | ||
|
|
d01d89767a | ||
|
|
9adfec28cf | ||
|
|
bcc400c7d7 | ||
|
|
d80ff58bb8 | ||
|
|
93bb0e9097 | ||
|
|
1c61ec3bd3 | ||
|
|
d3ec9a0669 | ||
|
|
00f0fee211 | ||
|
|
c9cd1ebee2 | ||
|
|
16667e0d40 | ||
|
|
2134e3b4d1 | ||
|
|
a51d7663c6 | ||
|
|
09d71aff49 | ||
|
|
d3361a3f1d | ||
|
|
085400bd17 | ||
|
|
479474d10a | ||
|
|
92fb359108 | ||
|
|
a4b58b1fd7 | ||
|
|
54019d7843 | ||
|
|
a39a0a9d84 | ||
|
|
ddf259f8b7 | ||
|
|
22ce37c0cd | ||
|
|
fd05a1a9cf | ||
|
|
ae091cdb8c | ||
|
|
acce572374 | ||
|
|
6cc3241eed | ||
|
|
1a7e9730e7 | ||
|
|
8f46aa2242 | ||
|
|
ee1d1f3852 | ||
|
|
5a32a55900 | ||
|
|
4327137cb0 | ||
|
|
332aa1d2ef | ||
|
|
89596591e9 | ||
|
|
bbe4f8c6ef | ||
|
|
3c990f6d07 | ||
|
|
4d608c5e7a | ||
|
|
50647fd9b7 | ||
|
|
5f26cd7342 | ||
|
|
2c0913637b | ||
|
|
1065fbc422 | ||
|
|
ce1f58956a | ||
|
|
25d00f2fcb | ||
|
|
27178cfd09 | ||
|
|
029a9293f0 | ||
|
|
1203149cb7 | ||
|
|
0ab5c33ddf | ||
|
|
1209143de7 | ||
|
|
bc2806bd05 | ||
|
|
65614921b5 | ||
|
|
9ee3208cff | ||
|
|
abb2f50e10 |
@@ -11,7 +11,7 @@ root = true
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespaces = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# CoffeeScript
|
||||
#
|
||||
@@ -28,12 +28,12 @@ indent_style = space
|
||||
# Package.json
|
||||
#
|
||||
# This indentation style is the one used by npm.
|
||||
[/package.json]
|
||||
[package.json]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
# Jade
|
||||
[*.jade]
|
||||
# Pug (Jade)
|
||||
[*.{jade,pug}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
@@ -41,7 +41,7 @@ indent_style = space
|
||||
#
|
||||
# Two spaces seems to be the standard most common style, at least in
|
||||
# Node.js (http://nodeguide.com/style.html#tabs-vs-spaces).
|
||||
[*.js]
|
||||
[*.{js,jsx,ts,tsx}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
|
||||
14
.eslintrc.js
Normal file
14
.eslintrc.js
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
extends: ['standard', 'standard-jsx'],
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
},
|
||||
parser: 'babel-eslint',
|
||||
rules: {
|
||||
'comma-dangle': ['error', 'always-multiline'],
|
||||
'no-var': 'error',
|
||||
'node/no-extraneous-import': 'error',
|
||||
'node/no-extraneous-require': 'error',
|
||||
'prefer-const': 'error',
|
||||
},
|
||||
}
|
||||
14
.flowconfig
Normal file
14
.flowconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
[ignore]
|
||||
<PROJECT_ROOT>/node_modules/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
include_warnings=true
|
||||
module.use_strict=true
|
||||
|
||||
[strict]
|
||||
31
.gitignore
vendored
31
.gitignore
vendored
@@ -1,9 +1,28 @@
|
||||
/.nyc_output/
|
||||
/bower_components/
|
||||
/dist/
|
||||
/coverage/
|
||||
/node_modules/
|
||||
/lerna-debug.log
|
||||
/lerna-debug.log.*
|
||||
|
||||
/@xen-orchestra/*/dist/
|
||||
/@xen-orchestra/*/node_modules/
|
||||
/packages/*/dist/
|
||||
/packages/*/node_modules/
|
||||
|
||||
/packages/xen-api/plot.dat
|
||||
|
||||
/packages/xo-server/.xo-server.*
|
||||
/packages/xo-server/src/api/index.js
|
||||
/packages/xo-server/src/xapi/mixins/index.js
|
||||
/packages/xo-server/src/xo-mixins/index.js
|
||||
|
||||
/packages/xo-server-auth-ldap/ldap.cache.conf
|
||||
|
||||
/packages/xo-web/src/common/intl/locales/index.js
|
||||
/packages/xo-web/src/common/themes/index.js
|
||||
|
||||
npm-debug.log
|
||||
npm-debug.log.*
|
||||
|
||||
!node_modules/*
|
||||
node_modules/*/
|
||||
pnpm-debug.log
|
||||
pnpm-debug.log.*
|
||||
yarn-error.log
|
||||
yarn-error.log.*
|
||||
|
||||
4
.prettierrc.js
Normal file
4
.prettierrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
}
|
||||
16
.travis.yml
16
.travis.yml
@@ -1,12 +1,16 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 'stable'
|
||||
#- '4' # Disabled for now because npm 2 cannot properly handled broken peer dependencies.
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- stable
|
||||
- 8
|
||||
- 6
|
||||
|
||||
# Use containers.
|
||||
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
sudo: false
|
||||
|
||||
before_install:
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
|
||||
47
@xen-orchestra/cron/.babelrc.js
Normal file
47
@xen-orchestra/cron/.babelrc.js
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict'
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
const __PROD__ = NODE_ENV === 'production'
|
||||
const __TEST__ = NODE_ENV === 'test'
|
||||
|
||||
const pkg = require('./package')
|
||||
|
||||
const plugins = {
|
||||
lodash: {},
|
||||
}
|
||||
|
||||
const presets = {
|
||||
'@babel/preset-env': {
|
||||
debug: !__TEST__,
|
||||
loose: true,
|
||||
shippedProposals: true,
|
||||
targets: __PROD__
|
||||
? (() => {
|
||||
let node = (pkg.engines || {}).node
|
||||
if (node !== undefined) {
|
||||
const trimChars = '^=>~'
|
||||
while (trimChars.includes(node[0])) {
|
||||
node = node.slice(1)
|
||||
}
|
||||
return { node: node }
|
||||
}
|
||||
})()
|
||||
: { browsers: '', node: 'current' },
|
||||
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
|
||||
},
|
||||
}
|
||||
|
||||
Object.keys(pkg.devDependencies || {}).forEach(name => {
|
||||
if (!(name in presets) && /@babel\/plugin-.+/.test(name)) {
|
||||
plugins[name] = {}
|
||||
} else if (!(name in presets) && /@babel\/preset-.+/.test(name)) {
|
||||
presets[name] = {}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
comments: !__PROD__,
|
||||
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
|
||||
plugins: Object.keys(plugins).map(plugin => [plugin, plugins[plugin]]),
|
||||
presets: Object.keys(presets).map(preset => [preset, presets[preset]]),
|
||||
}
|
||||
24
@xen-orchestra/cron/.npmignore
Normal file
24
@xen-orchestra/cron/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
145
@xen-orchestra/cron/README.md
Normal file
145
@xen-orchestra/cron/README.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# @xen-orchestra/cron [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> Focused, well maintained, cron parser/scheduler
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/cron):
|
||||
|
||||
```
|
||||
> npm install --save @xen-orchestra/cron
|
||||
```
|
||||
|
||||
### Pattern syntax
|
||||
|
||||
```
|
||||
<minute> <hour> <day of month> <month> <day of week>
|
||||
```
|
||||
|
||||
|
||||
Each entry can be:
|
||||
|
||||
- a single value
|
||||
- a range (`0-23` or `*/2`)
|
||||
- a list of values/ranges (`1,8-12`)
|
||||
|
||||
A wildcard (`*`) can be used as a shortcut for the whole range
|
||||
(`first-last`).
|
||||
|
||||
Step values can be used in conjunctions with ranges. For instance,
|
||||
`1-7/2` is the same as `1,3,5,7`.
|
||||
|
||||
| Field | Allowed values |
|
||||
|------------------|----------------|
|
||||
| minute | 0-59 |
|
||||
| hour | 0-23 |
|
||||
| day of the month | 1-31 or 3-letter names (`jan`, `feb`, …) |
|
||||
| month | 0-11 |
|
||||
| day of week | 0-7 (0 and 7 both mean Sunday) or 3-letter names (`mon`, `tue`, …) |
|
||||
|
||||
> Note: the month range is 0-11 to be compatible with
|
||||
> [cron](https://github.com/kelektiv/node-cron), it does not appear to
|
||||
> be very standard though.
|
||||
|
||||
### API
|
||||
|
||||
`createSchedule(pattern: string, zone: string = 'utc'): Schedule`
|
||||
|
||||
> Create a new schedule.
|
||||
|
||||
- `pattern`: the pattern to use, see [the syntax](#pattern-syntax)
|
||||
- `zone`: the timezone to use, use `'local'` for the local timezone
|
||||
|
||||
```js
|
||||
import { createSchedule } from '@xen-orchestra/cron'
|
||||
|
||||
const schedule = createSchedule('0 0 * * sun', 'America/New_York')
|
||||
```
|
||||
|
||||
`Schedule#createJob(fn: Function): Job`
|
||||
|
||||
> Create a new job from this schedule.
|
||||
|
||||
- `fn`: function to execute, if it returns a promise, it will be
|
||||
awaited before scheduling the next run.
|
||||
|
||||
```js
|
||||
const job = schedule.createJob(() => {
|
||||
console.log(new Date())
|
||||
})
|
||||
```
|
||||
|
||||
`Schedule#next(n: number): Array<Date>`
|
||||
|
||||
> Returns the next dates matching this schedule.
|
||||
|
||||
- `n`: number of dates to return
|
||||
|
||||
```js
|
||||
schedule.next(2)
|
||||
// [ 2018-02-11T05:00:00.000Z, 2018-02-18T05:00:00.000Z ]
|
||||
```
|
||||
|
||||
`Schedule#startJob(fn: Function): () => void`
|
||||
|
||||
> Start a new job from this schedule and return a function to stop it.
|
||||
|
||||
- `fn`: function to execute, if it returns a promise, it will be
|
||||
awaited before scheduling the next run.
|
||||
|
||||
```js
|
||||
const stopJob = schedule.startJob(() => {
|
||||
console.log(new Date())
|
||||
})
|
||||
stopJob()
|
||||
```
|
||||
|
||||
`Job#start(): void`
|
||||
|
||||
> Start this job.
|
||||
|
||||
```js
|
||||
job.start()
|
||||
```
|
||||
|
||||
`Job#stop(): void`
|
||||
|
||||
> Stop this job.
|
||||
|
||||
```js
|
||||
job.stop()
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> yarn
|
||||
|
||||
# Run the tests
|
||||
> yarn test
|
||||
|
||||
# Continuously compile
|
||||
> yarn dev
|
||||
|
||||
# Continuously run the tests
|
||||
> yarn dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> yarn build
|
||||
```
|
||||
|
||||
## 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 © [Vates SAS](https://vates.fr)
|
||||
59
@xen-orchestra/cron/package.json
Normal file
59
@xen-orchestra/cron/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@xen-orchestra/cron",
|
||||
"version": "1.0.2",
|
||||
"license": "ISC",
|
||||
"description": "Focused, well maintained, cron parser/scheduler",
|
||||
"keywords": [
|
||||
"cron",
|
||||
"cronjob",
|
||||
"crontab",
|
||||
"job",
|
||||
"parser",
|
||||
"pattern",
|
||||
"schedule",
|
||||
"scheduling",
|
||||
"task"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/@xen-orchestra/cron",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@isonoe.net"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"browserslist": [
|
||||
">2%"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.4",
|
||||
"moment-timezone": "^0.5.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.0.0-beta.40",
|
||||
"@babel/core": "7.0.0-beta.40",
|
||||
"@babel/preset-env": "7.0.0-beta.40",
|
||||
"@babel/preset-flow": "7.0.0-beta.40",
|
||||
"cross-env": "^5.1.3",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"clean": "rimraf dist/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "yarn run clean",
|
||||
"predev": "yarn run clean",
|
||||
"prepublishOnly": "yarn run build"
|
||||
}
|
||||
}
|
||||
76
@xen-orchestra/cron/src/index.js
Normal file
76
@xen-orchestra/cron/src/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import moment from 'moment-timezone'
|
||||
|
||||
import next from './next'
|
||||
import parse from './parse'
|
||||
|
||||
const MAX_DELAY = 2 ** 31 - 1
|
||||
|
||||
class Job {
|
||||
constructor (schedule, fn) {
|
||||
const wrapper = () => {
|
||||
const result = fn()
|
||||
let then
|
||||
if (result != null && typeof (then = result.then) === 'function') {
|
||||
then.call(result, scheduleNext, scheduleNext)
|
||||
} else {
|
||||
scheduleNext()
|
||||
}
|
||||
}
|
||||
const scheduleNext = () => {
|
||||
const delay = schedule._nextDelay()
|
||||
this._timeout =
|
||||
delay < MAX_DELAY
|
||||
? setTimeout(wrapper, delay)
|
||||
: setTimeout(scheduleNext, MAX_DELAY)
|
||||
}
|
||||
|
||||
this._scheduleNext = scheduleNext
|
||||
this._timeout = undefined
|
||||
}
|
||||
|
||||
start () {
|
||||
this.stop()
|
||||
this._scheduleNext()
|
||||
}
|
||||
|
||||
stop () {
|
||||
clearTimeout(this._timeout)
|
||||
}
|
||||
}
|
||||
|
||||
class Schedule {
|
||||
constructor (pattern, zone = 'utc') {
|
||||
this._schedule = parse(pattern)
|
||||
this._createDate =
|
||||
zone.toLowerCase() === 'utc'
|
||||
? moment.utc
|
||||
: zone === 'local' ? moment : () => moment.tz(zone)
|
||||
}
|
||||
|
||||
createJob (fn) {
|
||||
return new Job(this, fn)
|
||||
}
|
||||
|
||||
next (n) {
|
||||
const dates = new Array(n)
|
||||
const schedule = this._schedule
|
||||
let date = this._createDate()
|
||||
for (let i = 0; i < n; ++i) {
|
||||
dates[i] = (date = next(schedule, date)).toDate()
|
||||
}
|
||||
return dates
|
||||
}
|
||||
|
||||
_nextDelay () {
|
||||
const now = this._createDate()
|
||||
return next(this._schedule, now) - now
|
||||
}
|
||||
|
||||
startJob (fn) {
|
||||
const job = this.createJob(fn)
|
||||
job.start()
|
||||
return job.stop.bind(job)
|
||||
}
|
||||
}
|
||||
|
||||
export const createSchedule = (...args) => new Schedule(...args)
|
||||
89
@xen-orchestra/cron/src/next.js
Normal file
89
@xen-orchestra/cron/src/next.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import moment from 'moment-timezone'
|
||||
import sortedIndex from 'lodash/sortedIndex'
|
||||
|
||||
const NEXT_MAPPING = {
|
||||
month: { year: 1 },
|
||||
date: { month: 1 },
|
||||
day: { week: 1 },
|
||||
hour: { day: 1 },
|
||||
minute: { hour: 1 },
|
||||
}
|
||||
|
||||
const getFirst = values => (values !== undefined ? values[0] : 0)
|
||||
|
||||
const setFirstAvailable = (date, unit, values) => {
|
||||
if (values === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const curr = date.get(unit)
|
||||
const next = values[sortedIndex(values, curr) % values.length]
|
||||
if (curr === next) {
|
||||
return
|
||||
}
|
||||
|
||||
const timestamp = +date
|
||||
date.set(unit, next)
|
||||
if (timestamp > +date) {
|
||||
date.add(NEXT_MAPPING[unit])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// returns the next run, after the passed date
|
||||
export default (schedule, fromDate) => {
|
||||
let date = moment(fromDate)
|
||||
.set({
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
})
|
||||
.add({ minute: 1 })
|
||||
|
||||
const { minute, hour, dayOfMonth, month, dayOfWeek } = schedule
|
||||
setFirstAvailable(date, 'minute', minute)
|
||||
|
||||
if (setFirstAvailable(date, 'hour', hour)) {
|
||||
date.set('minute', getFirst(minute))
|
||||
}
|
||||
|
||||
let loop
|
||||
let i = 0
|
||||
do {
|
||||
loop = false
|
||||
|
||||
if (setFirstAvailable(date, 'month', month)) {
|
||||
date.set({
|
||||
date: 1,
|
||||
hour: getFirst(hour),
|
||||
minute: getFirst(minute),
|
||||
})
|
||||
}
|
||||
|
||||
let newDate = date.clone()
|
||||
if (dayOfMonth === undefined) {
|
||||
if (dayOfWeek !== undefined) {
|
||||
setFirstAvailable(newDate, 'day', dayOfWeek)
|
||||
}
|
||||
} else if (dayOfWeek === undefined) {
|
||||
setFirstAvailable(newDate, 'date', dayOfMonth)
|
||||
} else {
|
||||
const dateDay = newDate.clone()
|
||||
setFirstAvailable(dateDay, 'date', dayOfMonth)
|
||||
setFirstAvailable(newDate, 'day', dayOfWeek)
|
||||
newDate = moment.min(dateDay, newDate)
|
||||
}
|
||||
if (+date !== +newDate) {
|
||||
loop = date.month() !== newDate.month()
|
||||
date = newDate.set({
|
||||
hour: getFirst(hour),
|
||||
minute: getFirst(minute),
|
||||
})
|
||||
}
|
||||
} while (loop && ++i < 5)
|
||||
|
||||
if (loop) {
|
||||
throw new Error('no solutions found for this schedule')
|
||||
}
|
||||
|
||||
return date
|
||||
}
|
||||
44
@xen-orchestra/cron/src/next.spec.js
Normal file
44
@xen-orchestra/cron/src/next.spec.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import mapValues from 'lodash/mapValues'
|
||||
import moment from 'moment-timezone'
|
||||
|
||||
import next from './next'
|
||||
import parse from './parse'
|
||||
|
||||
const N = (pattern, fromDate = '2018-04-09T06:25') => {
|
||||
const iso = next(parse(pattern), moment.utc(fromDate)).toISOString()
|
||||
return iso.slice(0, iso.lastIndexOf(':'))
|
||||
}
|
||||
|
||||
describe('next()', () => {
|
||||
mapValues(
|
||||
{
|
||||
minutely: ['* * * * *', '2018-04-09T06:26'],
|
||||
hourly: ['@hourly', '2018-04-09T07:00'],
|
||||
daily: ['@daily', '2018-04-10T00:00'],
|
||||
monthly: ['@monthly', '2018-05-01T00:00'],
|
||||
yearly: ['@yearly', '2019-01-01T00:00'],
|
||||
weekly: ['@weekly', '2018-04-15T00:00'],
|
||||
},
|
||||
([pattern, result], title) =>
|
||||
it(title, () => {
|
||||
expect(N(pattern)).toBe(result)
|
||||
})
|
||||
)
|
||||
|
||||
it('select first between month-day and week-day', () => {
|
||||
expect(N('* * 10 * wen')).toBe('2018-04-10T00:00')
|
||||
expect(N('* * 12 * wen')).toBe('2018-04-11T00:00')
|
||||
})
|
||||
|
||||
it('select the last available day of a month', () => {
|
||||
expect(N('* * 29 feb *')).toBe('2020-02-29T00:00')
|
||||
})
|
||||
|
||||
it('fails when no solutions has been found', () => {
|
||||
expect(() => N('0 0 30 feb *')).toThrow(
|
||||
'no solutions found for this schedule'
|
||||
)
|
||||
})
|
||||
})
|
||||
193
@xen-orchestra/cron/src/parse.js
Normal file
193
@xen-orchestra/cron/src/parse.js
Normal file
@@ -0,0 +1,193 @@
|
||||
const compareNumbers = (a, b) => a - b
|
||||
|
||||
const createParser = ({ fields: [...fields], presets: { ...presets } }) => {
|
||||
const m = fields.length
|
||||
|
||||
for (let j = 0; j < m; ++j) {
|
||||
const field = fields[j]
|
||||
let { aliases } = field
|
||||
if (aliases !== undefined) {
|
||||
let symbols = aliases
|
||||
|
||||
if (Array.isArray(aliases)) {
|
||||
aliases = {}
|
||||
const [start] = field.range
|
||||
symbols.forEach((alias, i) => {
|
||||
aliases[alias] = start + i
|
||||
})
|
||||
} else {
|
||||
symbols = Object.keys(aliases)
|
||||
}
|
||||
|
||||
fields[j] = {
|
||||
...field,
|
||||
aliases,
|
||||
aliasesRegExp: new RegExp(symbols.join('|'), 'y'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let field, i, n, pattern, schedule, values
|
||||
|
||||
const isDigit = c => c >= '0' && c <= '9'
|
||||
const match = c => pattern[i] === c && (++i, true)
|
||||
|
||||
const consumeWhitespaces = () => {
|
||||
let c
|
||||
while ((c = pattern[i]) === ' ' || c === '\t') {
|
||||
++i
|
||||
}
|
||||
}
|
||||
|
||||
const parseInteger = () => {
|
||||
let c
|
||||
const digits = []
|
||||
while (isDigit((c = pattern[i]))) {
|
||||
++i
|
||||
digits.push(c)
|
||||
}
|
||||
if (digits.length === 0) {
|
||||
throw new SyntaxError(`${field.name}: missing integer at character ${i}`)
|
||||
}
|
||||
return Number.parseInt(digits.join(''), 10)
|
||||
}
|
||||
|
||||
const parseValue = () => {
|
||||
let value
|
||||
|
||||
const { aliasesRegExp } = field
|
||||
if (aliasesRegExp === undefined || isDigit(pattern[i])) {
|
||||
value = parseInteger()
|
||||
const { post } = field
|
||||
if (post !== undefined) {
|
||||
value = post(value)
|
||||
}
|
||||
} else {
|
||||
aliasesRegExp.lastIndex = i
|
||||
const matches = aliasesRegExp.exec(pattern)
|
||||
if (matches === null) {
|
||||
throw new SyntaxError(
|
||||
`${field.name}: missing alias or integer at character ${i}`
|
||||
)
|
||||
}
|
||||
const [alias] = matches
|
||||
i += alias.length
|
||||
value = field.aliases[alias]
|
||||
}
|
||||
|
||||
const { range } = field
|
||||
if (value < range[0] || value > range[1]) {
|
||||
throw new SyntaxError(
|
||||
`${field.name}: ${value} is not between ${range[0]} and ${range[1]}`
|
||||
)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const parseRange = () => {
|
||||
let end, start, step
|
||||
if (match('*')) {
|
||||
if (!match('/')) {
|
||||
return
|
||||
}
|
||||
;[start, end] = field.range
|
||||
step = parseInteger()
|
||||
} else {
|
||||
start = parseValue()
|
||||
if (!match('-')) {
|
||||
values.add(start)
|
||||
return
|
||||
}
|
||||
end = parseValue()
|
||||
step = match('/') ? parseInteger() : 1
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i += step) {
|
||||
values.add(i)
|
||||
}
|
||||
}
|
||||
|
||||
const parseSequence = () => {
|
||||
do {
|
||||
parseRange()
|
||||
} while (match(','))
|
||||
}
|
||||
|
||||
const parse = p => {
|
||||
{
|
||||
const schedule = presets[p]
|
||||
if (schedule !== undefined) {
|
||||
return typeof schedule === 'string'
|
||||
? (presets[p] = parse(schedule))
|
||||
: schedule
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
i = 0
|
||||
n = p.length
|
||||
pattern = p
|
||||
schedule = {}
|
||||
|
||||
for (let j = 0; j < m; ++j) {
|
||||
consumeWhitespaces()
|
||||
|
||||
field = fields[j]
|
||||
values = new Set()
|
||||
parseSequence()
|
||||
if (values.size !== 0) {
|
||||
schedule[field.name] = Array.from(values).sort(compareNumbers)
|
||||
}
|
||||
}
|
||||
|
||||
consumeWhitespaces()
|
||||
if (i !== n) {
|
||||
throw new SyntaxError(
|
||||
`unexpected character at offset ${i}, expected end`
|
||||
)
|
||||
}
|
||||
|
||||
return schedule
|
||||
} finally {
|
||||
field = pattern = schedule = values = undefined
|
||||
}
|
||||
}
|
||||
|
||||
return parse
|
||||
}
|
||||
|
||||
export default createParser({
|
||||
fields: [
|
||||
{
|
||||
name: 'minute',
|
||||
range: [0, 59],
|
||||
},
|
||||
{
|
||||
name: 'hour',
|
||||
range: [0, 23],
|
||||
},
|
||||
{
|
||||
name: 'dayOfMonth',
|
||||
range: [1, 31],
|
||||
},
|
||||
{
|
||||
aliases: 'jan feb mar apr may jun jul aug sep oct nov dec'.split(' '),
|
||||
name: 'month',
|
||||
range: [0, 11],
|
||||
},
|
||||
{
|
||||
aliases: 'mon tue wen thu fri sat sun'.split(' '),
|
||||
name: 'dayOfWeek',
|
||||
post: value => (value === 0 ? 7 : value),
|
||||
range: [1, 7],
|
||||
},
|
||||
],
|
||||
presets: {
|
||||
'@annually': '0 0 1 jan *',
|
||||
'@daily': '0 0 * * *',
|
||||
'@hourly': '0 * * * *',
|
||||
'@monthly': '0 0 1 * *',
|
||||
'@weekly': '0 0 * * sun',
|
||||
'@yearly': '0 0 1 jan *',
|
||||
},
|
||||
})
|
||||
49
@xen-orchestra/cron/src/parse.spec.js
Normal file
49
@xen-orchestra/cron/src/parse.spec.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import parse from './parse'
|
||||
|
||||
describe('parse()', () => {
|
||||
it('works', () => {
|
||||
expect(parse('0 0-10 */10 jan,2,4-11/3 *')).toEqual({
|
||||
minute: [0],
|
||||
hour: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
dayOfMonth: [1, 11, 21, 31],
|
||||
month: [0, 2, 4, 7, 10],
|
||||
})
|
||||
})
|
||||
|
||||
it('correctly parse months', () => {
|
||||
expect(parse('* * * 0,11 *')).toEqual({
|
||||
month: [0, 11],
|
||||
})
|
||||
expect(parse('* * * jan,dec *')).toEqual({
|
||||
month: [0, 11],
|
||||
})
|
||||
})
|
||||
|
||||
it('correctly parse days', () => {
|
||||
expect(parse('* * * * mon,sun')).toEqual({
|
||||
dayOfWeek: [1, 7],
|
||||
})
|
||||
})
|
||||
|
||||
it('reports missing integer', () => {
|
||||
expect(() => parse('*/a')).toThrow('minute: missing integer at character 2')
|
||||
expect(() => parse('*')).toThrow('hour: missing integer at character 1')
|
||||
})
|
||||
|
||||
it('reports invalid aliases', () => {
|
||||
expect(() => parse('* * * jan-foo *')).toThrow(
|
||||
'month: missing alias or integer at character 10'
|
||||
)
|
||||
})
|
||||
|
||||
it('dayOfWeek: 0 and 7 bind to sunday', () => {
|
||||
expect(parse('* * * * 0')).toEqual({
|
||||
dayOfWeek: [7],
|
||||
})
|
||||
expect(parse('* * * * 7')).toEqual({
|
||||
dayOfWeek: [7],
|
||||
})
|
||||
})
|
||||
})
|
||||
960
CHANGELOG.md
960
CHANGELOG.md
@@ -1,960 +0,0 @@
|
||||
# ChangeLog
|
||||
|
||||
## **5.2.0** (2016-09-09)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- IP management [\#1350](https://github.com/vatesfr/xo-web/issues/1350), [\#988](https://github.com/vatesfr/xo-web/issues/988), [\#1427](https://github.com/vatesfr/xo-web/issues/1427) and [\#240](https://github.com/vatesfr/xo-web/issues/240)
|
||||
- Update reverse proxy example [\#1474](https://github.com/vatesfr/xo-web/issues/1474)
|
||||
- Improve log view [\#1467](https://github.com/vatesfr/xo-web/issues/1467)
|
||||
- Backup Reports: e-mail subject [\#1463](https://github.com/vatesfr/xo-web/issues/1463)
|
||||
- Backup Reports: report the error [\#1462](https://github.com/vatesfr/xo-web/issues/1462)
|
||||
- Vif selector: select management network by default [\#1425](https://github.com/vatesfr/xo-web/issues/1425)
|
||||
- Display when browser disconnected to server [\#1417](https://github.com/vatesfr/xo-web/issues/1417)
|
||||
- Tooltip on OS icon in VM view [\#1416](https://github.com/vatesfr/xo-web/issues/1416)
|
||||
- Display pool master [\#1407](https://github.com/vatesfr/xo-web/issues/1407)
|
||||
- Missing tooltips in VM creation view [\#1402](https://github.com/vatesfr/xo-web/issues/1402)
|
||||
- Handle VDB disconnect and connect [\#1397](https://github.com/vatesfr/xo-web/issues/1397)
|
||||
- Eject host from a pool [\#1395](https://github.com/vatesfr/xo-web/issues/1395)
|
||||
- Improve pool general view [\#1393](https://github.com/vatesfr/xo-web/issues/1393)
|
||||
- Improve patching system [\#1392](https://github.com/vatesfr/xo-web/issues/1392)
|
||||
- Pool name modification [\#1390](https://github.com/vatesfr/xo-web/issues/1390)
|
||||
- Confirmation dialog before destroying VDIs [\#1388](https://github.com/vatesfr/xo-web/issues/1388)
|
||||
- Tooltips for meter object [\#1387](https://github.com/vatesfr/xo-web/issues/1387)
|
||||
- New Host assistant [\#1374](https://github.com/vatesfr/xo-web/issues/1374)
|
||||
- New VM assistant [\#1373](https://github.com/vatesfr/xo-web/issues/1373)
|
||||
- New SR assistant [\#1372](https://github.com/vatesfr/xo-web/issues/1372)
|
||||
- Direct access to VDI listing from dashboard's SR usage breakdown [\#1371](https://github.com/vatesfr/xo-web/issues/1371)
|
||||
- Can't set a network name at pool level [\#1368](https://github.com/vatesfr/xo-web/issues/1368)
|
||||
- Change a few mouse over descriptions [\#1363](https://github.com/vatesfr/xo-web/issues/1363)
|
||||
- Hide network install in VM create if template is HVM [\#1362](https://github.com/vatesfr/xo-web/issues/1362)
|
||||
- SR space left during VM creation [\#1358](https://github.com/vatesfr/xo-web/issues/1358)
|
||||
- Add destination SR on migration modal in VM view [\#1357](https://github.com/vatesfr/xo-web/issues/1357)
|
||||
- Ability to create a new VM from a snapshot [\#1353](https://github.com/vatesfr/xo-web/issues/1353)
|
||||
- Missing explanation/confirmation on Snapshot Page [\#1349](https://github.com/vatesfr/xo-web/issues/1349)
|
||||
- Log view: expose API errors in the web UI [\#1344](https://github.com/vatesfr/xo-web/issues/1344)
|
||||
- Registration on update page [\#1341](https://github.com/vatesfr/xo-web/issues/1341)
|
||||
- Add export snapshot button [\#1336](https://github.com/vatesfr/xo-web/issues/1336)
|
||||
- Use saved SSH keys in VM create CloudConfig [\#1319](https://github.com/vatesfr/xo-web/issues/1319)
|
||||
- Collapse header in console view [\#1268](https://github.com/vatesfr/xo-web/issues/1268)
|
||||
- Two max concurrent jobs in parallel [\#915](https://github.com/vatesfr/xo-web/issues/915)
|
||||
- Handle OVA import via the web UI [\#709](https://github.com/vatesfr/xo-web/issues/709)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Bug on VM console when header is hidden [\#1485](https://github.com/vatesfr/xo-web/issues/1485)
|
||||
- Disks not removed when deleting multiple VMs [\#1484](https://github.com/vatesfr/xo-web/issues/1484)
|
||||
- Do not display VDI disconnect button when a VM is not running [\#1470](https://github.com/vatesfr/xo-web/issues/1470)
|
||||
- Do not display VIF disconnect button when a VM is not running [\#1468](https://github.com/vatesfr/xo-web/issues/1468)
|
||||
- Error on migration if no default SR \(even when not used\) [\#1466](https://github.com/vatesfr/xo-web/issues/1466)
|
||||
- DR issue while rotating old backup [\#1464](https://github.com/vatesfr/xo-web/issues/1464)
|
||||
- Giving resource set to end-user ends with error [\#1448](https://github.com/vatesfr/xo-web/issues/1448)
|
||||
- Error thrown when cancelling out of Delete User confirmation dialog [\#1439](https://github.com/vatesfr/xo-web/issues/1439)
|
||||
- Wrong month label shown in Backup and Job scheduler [\#1438](https://github.com/vatesfr/xo-web/issues/1438)
|
||||
- Bug on Self service creation/edition [\#1438](https://github.com/vatesfr/xo-web/issues/1428)
|
||||
- ISO selection during VM create is not mounted after [\#1415](https://github.com/vatesfr/xo-web/issues/1415)
|
||||
- Hosts general view: bad link for storage [\#1408](https://github.com/vatesfr/xo-web/issues/1408)
|
||||
- Backup Schedule - "Month" and "Day of Week" display error [\#1404](https://github.com/vatesfr/xo-web/issues/1404)
|
||||
- Migrate dialog doesn't present all available VIF's in new UI interface [\#1403](https://github.com/vatesfr/xo-web/issues/1403)
|
||||
- NFS mount issues [\#1396](https://github.com/vatesfr/xo-web/issues/1396)
|
||||
- Select component color [\#1391](https://github.com/vatesfr/xo-web/issues/1391)
|
||||
- SR created with local path shouldn't be shared [\#1389](https://github.com/vatesfr/xo-web/issues/1389)
|
||||
- Disks (VBD) are attached to VM in RO mode instead of RW even if RO is unchecked [\#1386](https://github.com/vatesfr/xo-web/issues/1386)
|
||||
- Re-connection issues between server and XS hosts [\#1384](https://github.com/vatesfr/xo-web/issues/1384)
|
||||
- Meter object style with Chrome 52 [\#1383](https://github.com/vatesfr/xo-web/issues/1383)
|
||||
- Editing a rolling snapshot job seems to fail [\#1376](https://github.com/vatesfr/xo-web/issues/1376)
|
||||
- Dashboard SR usage and total inverted [\#1370](https://github.com/vatesfr/xo-web/issues/1370)
|
||||
- XenServer connection issue with host while using VGPUs [\#1369](https://github.com/vatesfr/xo-web/issues/1369)
|
||||
- Job created with v4 are not correctly displayed in v5 [\#1366](https://github.com/vatesfr/xo-web/issues/1366)
|
||||
- CPU accounting in resource set [\#1365](https://github.com/vatesfr/xo-web/issues/1365)
|
||||
- Tooltip stay displayed when a button change state [\#1360](https://github.com/vatesfr/xo-web/issues/1360)
|
||||
- Failure on host reboot [\#1351](https://github.com/vatesfr/xo-web/issues/1351)
|
||||
- Editing Backup Jobs Without Compression, Slider Always Set To On [\#1339](https://github.com/vatesfr/xo-web/issues/1339)
|
||||
- Month Selection on Backup Screen Wrong [\#1338](https://github.com/vatesfr/xo-web/issues/1338)
|
||||
- Delta backup fail when removed VDIs [\#1333](https://github.com/vatesfr/xo-web/issues/1333)
|
||||
|
||||
## **5.1.0** (2016-07-26)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Improve backups timezone UI [\#1314](https://github.com/vatesfr/xo-web/issues/1314)
|
||||
- HOME view submenus [\#1306](https://github.com/vatesfr/xo-web/issues/1306)
|
||||
- Ability for a user to save SSH keys [\#1299](https://github.com/vatesfr/xo-web/issues/1299)
|
||||
- \[ACLs\] Ability to select all hosts/VMs [\#1296](https://github.com/vatesfr/xo-web/issues/1296)
|
||||
- Improve scheduling UI [\#1295](https://github.com/vatesfr/xo-web/issues/1295)
|
||||
- Plugins: Predefined configurations [\#1289](https://github.com/vatesfr/xo-web/issues/1289)
|
||||
- Button to recompute resource sets limits [\#1287](https://github.com/vatesfr/xo-web/issues/1287)
|
||||
- Credit scheduler CAP and weight configuration [\#1283](https://github.com/vatesfr/xo-web/issues/1283)
|
||||
- Migration form problem on the /v5/vms/\_\_UUID\_\_ page when doing xenmotion inside a pool [\#1254](https://github.com/vatesfr/xo-web/issues/1254)
|
||||
- /v5/\#/pools/\_\_UUID\_\_: patch table improvement [\#1246](https://github.com/vatesfr/xo-web/issues/1246)
|
||||
- /v5/\#/hosts/\_\_UUID\_\_: patch list improvements ? [\#1245](https://github.com/vatesfr/xo-web/issues/1245)
|
||||
- F\*cking patches, how do they work? [\#1236](https://github.com/vatesfr/xo-web/issues/1236)
|
||||
- Change Default Filter [\#1235](https://github.com/vatesfr/xo-web/issues/1235)
|
||||
- Add a property on jobs to know their state [\#1232](https://github.com/vatesfr/xo-web/issues/1232)
|
||||
- Spanish translation [\#1231](https://github.com/vatesfr/xo-web/issues/1231)
|
||||
- Home: "Filter" input and keyboard focus [\#1228](https://github.com/vatesfr/xo-web/issues/1228)
|
||||
- Display xenserver version [\#1225](https://github.com/vatesfr/xo-web/issues/1225)
|
||||
- Plugin config: presets & defaults [\#1222](https://github.com/vatesfr/xo-web/issues/1222)
|
||||
- Allow halted VM migration [\#1216](https://github.com/vatesfr/xo-web/issues/1216)
|
||||
- Missing confirm dialog on critical button [\#1211](https://github.com/vatesfr/xo-web/issues/1211)
|
||||
- Backup logs are not sortable [\#1196](https://github.com/vatesfr/xo-web/issues/1196)
|
||||
- Page title with the name of current object [\#1185](https://github.com/vatesfr/xo-web/issues/1185)
|
||||
- Existing VIF management [\#1176](https://github.com/vatesfr/xo-web/issues/1176)
|
||||
- Do not display fast clone option is there isn't template disks [\#1172](https://github.com/vatesfr/xo-web/issues/1172)
|
||||
- UI issue when adding a user [\#1159](https://github.com/vatesfr/xo-web/issues/1159)
|
||||
- Combined values on stats [\#1158](https://github.com/vatesfr/xo-web/issues/1158)
|
||||
- Parallel coordinates graph [\#1157](https://github.com/vatesfr/xo-web/issues/1157)
|
||||
- VM creation on self-service as user [\#1155](https://github.com/vatesfr/xo-web/issues/1155)
|
||||
- VM copy bulk action on home view [\#1154](https://github.com/vatesfr/xo-web/issues/1154)
|
||||
- Better VDI map [\#1151](https://github.com/vatesfr/xo-web/issues/1151)
|
||||
- Missing tooltips on buttons [\#1150](https://github.com/vatesfr/xo-web/issues/1150)
|
||||
- Patching from pool view [\#1149](https://github.com/vatesfr/xo-web/issues/1149)
|
||||
- Missing patches in dashboard [\#1148](https://github.com/vatesfr/xo-web/issues/1148)
|
||||
- Improve tasks view [\#1147](https://github.com/vatesfr/xo-web/issues/1147)
|
||||
- Home bulk VM migration [\#1146](https://github.com/vatesfr/xo-web/issues/1146)
|
||||
- LDAP plugin clear password field [\#1145](https://github.com/vatesfr/xo-web/issues/1145)
|
||||
- Cron default behavior [\#1144](https://github.com/vatesfr/xo-web/issues/1144)
|
||||
- Modal for migrate on home [\#1143](https://github.com/vatesfr/xo-web/issues/1143)
|
||||
- /v5/\#/srs/\_\_UUID\_\_: UI improvements [\#1142](https://github.com/vatesfr/xo-web/issues/1142)
|
||||
- /v5/\#/pools/: some name should be links [\#1141](https://github.com/vatesfr/xo-web/issues/1141)
|
||||
- create the page /v5/\#/pools/ [\#1140](https://github.com/vatesfr/xo-web/issues/1140)
|
||||
- Dashboard: add links to different part of XOA [\#1139](https://github.com/vatesfr/xo-web/issues/1139)
|
||||
- /v5/\#/dashboard/overview: add link on the "Top 5 SR Usage" graph [\#1135](https://github.com/vatesfr/xo-web/issues/1135)
|
||||
- /v5/\#/backup/overview: display the error when there is one returned by xenserver on failed job. [\#1134](https://github.com/vatesfr/xo-web/issues/1134)
|
||||
- /v5/: add an option to set the number of element displayed in tables [\#1133](https://github.com/vatesfr/xo-web/issues/1133)
|
||||
- Updater refresh page after update [\#1131](https://github.com/vatesfr/xo-web/issues/1131)
|
||||
- /v5/\#/settings/plugins [\#1130](https://github.com/vatesfr/xo-web/issues/1130)
|
||||
- /v5/\#/new/sr: layout issue [\#1129](https://github.com/vatesfr/xo-web/issues/1129)
|
||||
- v5 /v5/\#/vms/new: layout issue [\#1128](https://github.com/vatesfr/xo-web/issues/1128)
|
||||
- v5 user page missing style [\#1127](https://github.com/vatesfr/xo-web/issues/1127)
|
||||
- Remote helper/tester [\#1075](https://github.com/vatesfr/xo-web/issues/1075)
|
||||
- Generate uiSchema from custom schema properties [\#951](https://github.com/vatesfr/xo-web/issues/951)
|
||||
- Customizing VM names generation during batch creation [\#949](https://github.com/vatesfr/xo-web/issues/949)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Plugins: Don't use `default` attributes in presets list [\#1288](https://github.com/vatesfr/xo-web/issues/1288)
|
||||
- CPU weight must be an integer [\#1286](https://github.com/vatesfr/xo-web/issues/1286)
|
||||
- Overview of self service is always empty [\#1282](https://github.com/vatesfr/xo-web/issues/1282)
|
||||
- SR attach/creation issue [\#1281](https://github.com/vatesfr/xo-web/issues/1281)
|
||||
- Self service resources not modified after a VM deletion [\#1276](https://github.com/vatesfr/xo-web/issues/1276)
|
||||
- Scheduled jobs seems use GMT since 5.0 [\#1258](https://github.com/vatesfr/xo-web/issues/1258)
|
||||
- Can't create a VM with disks on 2 different SRs [\#1257](https://github.com/vatesfr/xo-web/issues/1257)
|
||||
- Graph display bug [\#1247](https://github.com/vatesfr/xo-web/issues/1247)
|
||||
- /v5/#/hosts/__UUID__: Patch list not limited to the current pool [\#1244](https://github.com/vatesfr/xo-web/issues/1244)
|
||||
- Replication issues [\#1233](https://github.com/vatesfr/xo-web/issues/1233)
|
||||
- VM creation install method disabled fields [\#1198](https://github.com/vatesfr/xo-web/issues/1198)
|
||||
- Update icon shouldn't be displayed when menu is collapsed [\#1188](https://github.com/vatesfr/xo-web/issues/1188)
|
||||
- /v5/ : Load average graph axis issue [\#1167](https://github.com/vatesfr/xo-web/issues/1167)
|
||||
- Some remote can't be opened [\#1164](https://github.com/vatesfr/xo-web/issues/1164)
|
||||
- Bulk action for hosts in home and pool view [\#1153](https://github.com/vatesfr/xo-web/issues/1153)
|
||||
- New Vif [\#1138](https://github.com/vatesfr/xo-web/issues/1138)
|
||||
- Missing SRs [\#1123](https://github.com/vatesfr/xo-web/issues/1123)
|
||||
- Continuous replication email alert does not obey per job setting [\#1121](https://github.com/vatesfr/xo-web/issues/1121)
|
||||
- Safari XO5 issue [\#1120](https://github.com/vatesfr/xo-web/issues/1120)
|
||||
- ACLs shoud be available in Enterprise Edition [\#1118](https://github.com/vatesfr/xo-web/issues/1118)
|
||||
- SR edit name or description doesn't work [\#1116](https://github.com/vatesfr/xo-web/issues/1116)
|
||||
- Bad RRD parsing for VIFs [\#969](https://github.com/vatesfr/xo-web/issues/969)
|
||||
|
||||
## **5.0.0** (2016-06-24)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Handle failed quiesce in snapshots [\#1088](https://github.com/vatesfr/xo-web/issues/1088)
|
||||
- Sparklines stats [\#1061](https://github.com/vatesfr/xo-web/issues/1061)
|
||||
- Task view [\#1060](https://github.com/vatesfr/xo-web/issues/1060)
|
||||
- Improved import system [\#1048](https://github.com/vatesfr/xo-web/issues/1048)
|
||||
- Backup restore view improvements [\#1021](https://github.com/vatesfr/xo-web/issues/1021)
|
||||
- Restore VM - Wrong VLAN on the VMs interface [\#1016](https://github.com/vatesfr/xo-web/issues/1016)
|
||||
- Fast Disk Cloning [\#960](https://github.com/vatesfr/xo-web/issues/960)
|
||||
- Disaster recovery job should target SRs, not pools [\#955](https://github.com/vatesfr/xo-web/issues/955)
|
||||
- Improve Header/Content interaction in a page [\#926](https://github.com/vatesfr/xo-web/issues/926)
|
||||
- New default view [\#912](https://github.com/vatesfr/xo-web/issues/912)
|
||||
- Xen Patching - Restart Pending [\#883](https://github.com/vatesfr/xo-web/issues/883)
|
||||
- Hide About page for user that are not admin [\#877](https://github.com/vatesfr/xo-web/issues/877)
|
||||
- ACL: Ability to view/sort/group by User/Group, Objects or Role [\#875](https://github.com/vatesfr/xo-web/issues/875)
|
||||
- ACL: Ability to select multiple users & group when creating a rule [\#874](https://github.com/vatesfr/xo-web/issues/874)
|
||||
- Translation [\#839](https://github.com/vatesfr/xo-web/issues/839)
|
||||
- XO offer useless network interfaces for XenMontion [\#833](https://github.com/vatesfr/xo-web/issues/833)
|
||||
- Show HVM, PVM, PVHVM modes in guest details [\#806](https://github.com/vatesfr/xo-web/issues/806)
|
||||
- Tree view: display cpu available/total for each host [\#696](https://github.com/vatesfr/xo-web/issues/696)
|
||||
- Greenkeeper integration [\#667](https://github.com/vatesfr/xo-web/issues/667)
|
||||
- Clarify vCPUs and RAM editor [\#658](https://github.com/vatesfr/xo-web/issues/658)
|
||||
- Backup LZ4 compression [\#647](https://github.com/vatesfr/xo-web/issues/647)
|
||||
- Support enum in plugins configuration [\#638](https://github.com/vatesfr/xo-web/issues/638)
|
||||
- Add configuration option to disable xoa-updater [\#535](https://github.com/vatesfr/xo-web/issues/535)
|
||||
- Use cursors to add more context to actions [\#523](https://github.com/vatesfr/xo-web/issues/523)
|
||||
- Review UI for flat view [\#354](https://github.com/vatesfr/xo-web/issues/354)
|
||||
- Review UI for the tree view [\#353](https://github.com/vatesfr/xo-web/issues/353)
|
||||
- Tag filtering [\#233](https://github.com/vatesfr/xo-web/issues/233)
|
||||
- GUI review [\#230](https://github.com/vatesfr/xo-web/issues/230)
|
||||
- Review UI for VM creation [\#214](https://github.com/vatesfr/xo-web/issues/214)
|
||||
- Ability to collapse pools/hosts in main view [\#173](https://github.com/vatesfr/xo-web/issues/173)
|
||||
- Issue importing .xva VM via xo-web [\#1022](https://github.com/vatesfr/xo-web/issues/1022)
|
||||
- Enhancement Proposal - Cancel In Progress Backups [\#1003](https://github.com/vatesfr/xo-web/issues/1003)
|
||||
- Can't create VM with CloudConfigDrive [\#917](https://github.com/vatesfr/xo-web/issues/917)
|
||||
- Auth: LDAP User causes problems [\#893](https://github.com/vatesfr/xo-web/issues/893)
|
||||
- No tags in Continuous Replication [\#838](https://github.com/vatesfr/xo-web/issues/838)
|
||||
- Delta backup Depth not working [\#802](https://github.com/vatesfr/xo-web/issues/802)
|
||||
- Update Section - Running version info missing - gui enhancement [\#795](https://github.com/vatesfr/xo-web/issues/795)
|
||||
- On reboot, vnc console wrongly scaled [\#722](https://github.com/vatesfr/xo-web/issues/722)
|
||||
- Make the object name \(title\) "sticky" at the top of the page [\#705](https://github.com/vatesfr/xo-web/issues/705)
|
||||
- pool view: display Local SR from hosts in the current pool [\#692](https://github.com/vatesfr/xo-web/issues/692)
|
||||
- tree view: display all IPs [\#689](https://github.com/vatesfr/xo-web/issues/689)
|
||||
- XO5 parallel distribution [\#462](https://github.com/vatesfr/xo-web/issues/462)
|
||||
- Load balancing with XO [\#423](https://github.com/vatesfr/xo-web/issues/423)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- vCPUs number when no tools installed [\#1089](https://github.com/vatesfr/xo-web/issues/1089)
|
||||
- Config Drive textbox disappears when content is deleted [\#1012](https://github.com/vatesfr/xo-web/issues/1012)
|
||||
- storage status not changed in host view page after disconnect/connect [\#1009](https://github.com/vatesfr/xo-web/issues/1009)
|
||||
- Cannot Delete Logs From Backup Overview [\#1004](https://github.com/vatesfr/xo-web/issues/1004)
|
||||
- \[v5.x\] Plugins configuration: optional non-used objects are sent [\#1000](https://github.com/vatesfr/xo-web/issues/1000)
|
||||
- "@" char in remote password break the remote view [\#997](https://github.com/vatesfr/xo-web/issues/997)
|
||||
- Handle MEMORY\_CONSTRAINT\_VIOLATION correctly [\#970](https://github.com/vatesfr/xo-web/issues/970)
|
||||
- VM creation error on XenServer Dundee [\#964](https://github.com/vatesfr/xo-web/issues/964)
|
||||
- Copy VMs doesn't display all SRs [\#945](https://github.com/vatesfr/xo-web/issues/945)
|
||||
- Autopower\_on wrong value [\#937](https://github.com/vatesfr/xo-web/issues/937)
|
||||
- Correctly handle unknown users in group view [\#900](https://github.com/vatesfr/xo-web/issues/900)
|
||||
- Importing into Dundee [\#887](https://github.com/vatesfr/xo-web/issues/887)
|
||||
- update status - gui resize issue [\#803](https://github.com/vatesfr/xo-web/issues/803)
|
||||
- Backup Remote Stores Problem [\#751](https://github.com/vatesfr/xo-web/issues/751)
|
||||
- VM view is broken when changing a disk SR twice [\#670](https://github.com/vatesfr/xo-web/issues/670)
|
||||
- console mouse sync [\#280](https://github.com/vatesfr/xo-web/issues/280)
|
||||
|
||||
## **4.16.0** (2016-04-29)
|
||||
|
||||
Maintenance release
|
||||
|
||||
### Enhancements
|
||||
|
||||
- TOO\_MANY\_PENDING\_TASKS [\#861](https://github.com/vatesfr/xo-web/issues/861)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Incorrect VM target name with continuous replication [\#904](https://github.com/vatesfr/xo-web/issues/904)
|
||||
- Error while deleting users [\#901](https://github.com/vatesfr/xo-web/issues/901)
|
||||
- Use an available path to the SR to create a config drive [\#882](https://github.com/vatesfr/xo-web/issues/882)
|
||||
- VM autoboot don't set the right pool parameter [\#879](https://github.com/vatesfr/xo-web/issues/879)
|
||||
- BUG: ACL with NFS ISO Library not working! [\#870](https://github.com/vatesfr/xo-web/issues/870)
|
||||
- Broken paths in backups in SMB [\#865](https://github.com/vatesfr/xo-web/issues/865)
|
||||
- Plugins page loads users/groups multiple times [\#829](https://github.com/vatesfr/xo-web/issues/829)
|
||||
- "Ghost" VM remains after migration [\#769](https://github.com/vatesfr/xo-web/issues/769)
|
||||
|
||||
## **4.15.0** (2016-03-21)
|
||||
|
||||
Load balancing, SMB delta support, advanced network operations...
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Add the job name inside the backup email report [\#819](https://github.com/vatesfr/xo-web/issues/819)
|
||||
- Delta backup with quiesce [\#812](https://github.com/vatesfr/xo-web/issues/812)
|
||||
- Hosts: No user feedback when error occurs with SR connect / disconnect [\#810](https://github.com/vatesfr/xo-web/issues/810)
|
||||
- Expose components versions [\#807](https://github.com/vatesfr/xo-web/issues/807)
|
||||
- Rework networks/PIFs management [\#805](https://github.com/vatesfr/xo-web/issues/805)
|
||||
- Displaying all SRs and a list of available hosts for creating VM from a pool [\#790](https://github.com/vatesfr/xo-web/issues/790)
|
||||
- Add "Source network" on "VM migration" screen [\#785](https://github.com/vatesfr/xo-web/issues/785)
|
||||
- Migration queue [\#783](https://github.com/vatesfr/xo-web/issues/783)
|
||||
- Match network names for VM migration [\#782](https://github.com/vatesfr/xo-web/issues/782)
|
||||
- Disk names [\#780](https://github.com/vatesfr/xo-web/issues/780)
|
||||
- Self service: should the user be able to set the CPU weight? [\#767](https://github.com/vatesfr/xo-web/issues/767)
|
||||
- host & pool Citrix license status [\#763](https://github.com/vatesfr/xo-web/issues/763)
|
||||
- pool view: Provide "updates" section [\#762](https://github.com/vatesfr/xo-web/issues/762)
|
||||
- XOA ISO image: ambigious root disk label [\#761](https://github.com/vatesfr/xo-web/issues/761)
|
||||
- Host info: provide system serial number [\#760](https://github.com/vatesfr/xo-web/issues/760)
|
||||
- CIFS ISO SR Creation [\#731](https://github.com/vatesfr/xo-web/issues/731)
|
||||
- MAC address not preserved on VM restore [\#707](https://github.com/vatesfr/xo-web/issues/707)
|
||||
- Failing replication job should send reports [\#659](https://github.com/vatesfr/xo-web/issues/659)
|
||||
- Display networks in the Pool view [\#226](https://github.com/vatesfr/xo-web/issues/226)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Broken link to backup remote [\#821](https://github.com/vatesfr/xo-web/issues/821)
|
||||
- Issue with self-signed cert for email plugin [\#817](https://github.com/vatesfr/xo-web/issues/817)
|
||||
- Plugins view, reset form and errors [\#815](https://github.com/vatesfr/xo-web/issues/815)
|
||||
- HVM recovery mode is broken [\#794](https://github.com/vatesfr/xo-web/issues/794)
|
||||
- Disk bug when creating vm from template [\#778](https://github.com/vatesfr/xo-web/issues/778)
|
||||
- Can't mount NFS shares in remote stores [\#775](https://github.com/vatesfr/xo-web/issues/775)
|
||||
- VM disk name and description not passed during creation [\#774](https://github.com/vatesfr/xo-web/issues/774)
|
||||
- NFS mount problem for Windows share [\#771](https://github.com/vatesfr/xo-web/issues/771)
|
||||
- lodash.pluck not installed [\#757](https://github.com/vatesfr/xo-web/issues/757)
|
||||
- this.\_getAuthenticationTokensForUser is not a function [\#755](https://github.com/vatesfr/xo-web/issues/755)
|
||||
- CentOS 6.x 64bit template creates a VM that won't boot [\#733](https://github.com/vatesfr/xo-web/issues/733)
|
||||
- Lot of xo:perf leading to XO crash [\#575](https://github.com/vatesfr/xo-web/issues/575)
|
||||
- New collection checklist [\#262](https://github.com/vatesfr/xo-web/issues/262)
|
||||
|
||||
## **4.14.0** (2016-02-23)
|
||||
|
||||
Self service, custom CloudInit...
|
||||
|
||||
### Enhancements
|
||||
|
||||
- VM creation self service with quotas [\#285](https://github.com/vatesfr/xo-web/issues/285)
|
||||
- Cloud config custom user data [\#706](https://github.com/vatesfr/xo-web/issues/706)
|
||||
- Patches behind a proxy [\#737](https://github.com/vatesfr/xo-web/issues/737)
|
||||
- Remote store status indicator [\#728](https://github.com/vatesfr/xo-web/issues/728)
|
||||
- Patch list order [\#724](https://github.com/vatesfr/xo-web/issues/724)
|
||||
- Enable reporting on additional backup types [\#717](https://github.com/vatesfr/xo-web/issues/717)
|
||||
- Tooltip name for cancel [\#703](https://github.com/vatesfr/xo-web/issues/703)
|
||||
- Portable VHD merging [\#646](https://github.com/vatesfr/xo-web/issues/646)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid merge between two delta vdi backups [\#702](https://github.com/vatesfr/xo-web/issues/702)
|
||||
- Text in table is not cut anymore [\#713](https://github.com/vatesfr/xo-web/issues/713)
|
||||
- Disk size edition issue with float numbers [\#719](https://github.com/vatesfr/xo-web/issues/719)
|
||||
- Create vm, summary is not refreshed [\#721](https://github.com/vatesfr/xo-web/issues/721)
|
||||
- Boot order problem [\#726](https://github.com/vatesfr/xo-web/issues/726)
|
||||
|
||||
## **4.13.0** (2016-02-05)
|
||||
|
||||
Backup checksum, SMB remotes...
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Add SMB mount for remote [\#338](https://github.com/vatesfr/xo-web/issues/338)
|
||||
- Centralize Perm in a lib [\#345](https://github.com/vatesfr/xo-web/issues/345)
|
||||
- Expose interpool migration details [\#567](https://github.com/vatesfr/xo-web/issues/567)
|
||||
- Add checksum for delta backup [\#617](https://github.com/vatesfr/xo-web/issues/617)
|
||||
- Redirect from HTTP to HTTPS [\#626](https://github.com/vatesfr/xo-web/issues/626)
|
||||
- Expose vCPU weight [\#633](https://github.com/vatesfr/xo-web/issues/633)
|
||||
- Avoid metadata in delta backup [\#651](https://github.com/vatesfr/xo-web/issues/651)
|
||||
- Button to clear logs [\#661](https://github.com/vatesfr/xo-web/issues/661)
|
||||
- Units for RAM and disks [\#666](https://github.com/vatesfr/xo-web/issues/666)
|
||||
- Remove multiple VDIs at once [\#676](https://github.com/vatesfr/xo-web/issues/676)
|
||||
- Find orphaned VDI snapshots [\#679](https://github.com/vatesfr/xo-web/issues/679)
|
||||
- New health view in Dashboard [\#680](https://github.com/vatesfr/xo-web/issues/680)
|
||||
- Use physical usage for VDI and SR [\#682](https://github.com/vatesfr/xo-web/issues/682)
|
||||
- TLS configuration [\#685](https://github.com/vatesfr/xo-web/issues/685)
|
||||
- Better VM info on tree view [\#688](https://github.com/vatesfr/xo-web/issues/688)
|
||||
- Absolute values in tooltips for tree view [\#690](https://github.com/vatesfr/xo-web/issues/690)
|
||||
- Absolute values for host memory [\#691](https://github.com/vatesfr/xo-web/issues/691)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Issues on host console screen [\#672](https://github.com/vatesfr/xo-web/issues/672)
|
||||
- NFS remote mount fails in particular case [\#665](https://github.com/vatesfr/xo-web/issues/665)
|
||||
- Unresponsive pages [\#662](https://github.com/vatesfr/xo-web/issues/662)
|
||||
- Live migration fail in the same pool with local SR fails [\#655](https://github.com/vatesfr/xo-web/issues/655)
|
||||
|
||||
## **4.12.0** (2016-01-18)
|
||||
|
||||
Continuous Replication, Continuous Delta backup...
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Continuous VM replication [\#582](https://github.com/vatesfr/xo-web/issues/582)
|
||||
- Continuous Delta Backup [\#576](https://github.com/vatesfr/xo-web/issues/576)
|
||||
- Scheduler should not run job again if previous instance is not finished [\#642](https://github.com/vatesfr/xo-web/issues/642)
|
||||
- Boot VM automatically after creation [\#635](https://github.com/vatesfr/xo-web/issues/635)
|
||||
- Manage existing VIFs in templates [\#630](https://github.com/vatesfr/xo-web/issues/630)
|
||||
- Support templates with existing install repository [\#627](https://github.com/vatesfr/xo-web/issues/627)
|
||||
- Remove running VMs [\#616](https://github.com/vatesfr/xo-web/issues/616)
|
||||
- Prevent a VM to start before delta import is finished [\#613](https://github.com/vatesfr/xo-web/issues/613)
|
||||
- Spawn multiple VMs at once [\#606](https://github.com/vatesfr/xo-web/issues/606)
|
||||
- Fixed `suspendVM` in tree view. [\#619](https://github.com/vatesfr/xo-web/pull/619) ([pdonias](https://github.com/pdonias))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- User defined MAC address is not fetch in VM install [\#643](https://github.com/vatesfr/xo-web/issues/643)
|
||||
- CoreOsCloudConfig is not shown with CoreOS [\#639](https://github.com/vatesfr/xo-web/issues/639)
|
||||
- Plugin activation/deactivation in web UI seems broken [\#637](https://github.com/vatesfr/xo-web/issues/637)
|
||||
- Issue when creating CloudConfig drive [\#636](https://github.com/vatesfr/xo-web/issues/636)
|
||||
- CloudConfig hostname shouldn't have space [\#634](https://github.com/vatesfr/xo-web/issues/634)
|
||||
- Cloned VIFs are not properly deleted on VM creation [\#632](https://github.com/vatesfr/xo-web/issues/632)
|
||||
- Default PV args missing during VM creation [\#628](https://github.com/vatesfr/xo-web/issues/628)
|
||||
- VM creation problems from custom templates [\#625](https://github.com/vatesfr/xo-web/issues/625)
|
||||
- Emergency shutdown race condition [\#622](https://github.com/vatesfr/xo-web/issues/622)
|
||||
- `vm.delete\(\)` should not delete VDIs attached to other VMs [\#621](https://github.com/vatesfr/xo-web/issues/621)
|
||||
- VM creation error from template with a disk [\#581](https://github.com/vatesfr/xo-web/issues/581)
|
||||
- Only delete VDI exports when VM backup is successful [\#644](https://github.com/vatesfr/xo-web/issues/644)
|
||||
- Change the name of an imported VM during the import process [\#641](https://github.com/vatesfr/xo-web/issues/641)
|
||||
- Creating a new VIF in view is partially broken [\#652](https://github.com/vatesfr/xo-web/issues/652)
|
||||
- Grey out the "create button" during VM creation [\#654](https://github.com/vatesfr/xo-web/issues/654)
|
||||
|
||||
## **4.11.0** (2015-12-22)
|
||||
|
||||
Delta backup, CloudInit...
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Visible list of SR inside a VM [\#601](https://github.com/vatesfr/xo-web/issues/601)
|
||||
- VDI move [\#591](https://github.com/vatesfr/xo-web/issues/591)
|
||||
- Edit pre-existing disk configuration during VM creation [\#589](https://github.com/vatesfr/xo-web/issues/589)
|
||||
- Allow disk size edition [\#587](https://github.com/vatesfr/xo-web/issues/587)
|
||||
- Better VDI resize support [\#585](https://github.com/vatesfr/xo-web/issues/585)
|
||||
- Remove manual VM export metadata in UI [\#580](https://github.com/vatesfr/xo-web/issues/580)
|
||||
- Support import VM metadata [\#579](https://github.com/vatesfr/xo-web/issues/579)
|
||||
- Set a default pool SR [\#572](https://github.com/vatesfr/xo-web/issues/572)
|
||||
- ISOs should be sorted by name [\#565](https://github.com/vatesfr/xo-web/issues/565)
|
||||
- Button to boot a VM from a disc once [\#564](https://github.com/vatesfr/xo-web/issues/564)
|
||||
- Ability to boot a PV VM from a disc [\#563](https://github.com/vatesfr/xo-web/issues/563)
|
||||
- Add an option to manually run backup jobs [\#562](https://github.com/vatesfr/xo-web/issues/562)
|
||||
- backups to unmounted storage [\#561](https://github.com/vatesfr/xo-web/issues/561)
|
||||
- Root integer properties cannot be edited in plugins configuration form [\#550](https://github.com/vatesfr/xo-web/issues/550)
|
||||
- Generic CloudConfig drive [\#549](https://github.com/vatesfr/xo-web/issues/549)
|
||||
- Auto-discovery of installed xo-server plugins [\#546](https://github.com/vatesfr/xo-web/issues/546)
|
||||
- Hide info on flat view [\#545](https://github.com/vatesfr/xo-web/issues/545)
|
||||
- Config plugin boolean properties must have a default value \(undefined prohibited\) [\#543](https://github.com/vatesfr/xo-web/issues/543)
|
||||
- Present detailed errors on plugin configuration failures [\#530](https://github.com/vatesfr/xo-web/issues/530)
|
||||
- Do not reset form on failures in plugins configuration [\#529](https://github.com/vatesfr/xo-web/issues/529)
|
||||
- XMPP alert plugin [\#518](https://github.com/vatesfr/xo-web/issues/518)
|
||||
- Hide tag adders depending on ACLs [\#516](https://github.com/vatesfr/xo-web/issues/516)
|
||||
- Choosing a framework for xo-web 5 [\#514](https://github.com/vatesfr/xo-web/issues/514)
|
||||
- Prevent adding a host in an existing XAPI connection [\#466](https://github.com/vatesfr/xo-web/issues/466)
|
||||
- Read only connection to Xen servers/pools [\#439](https://github.com/vatesfr/xo-web/issues/439)
|
||||
- generic notification system [\#391](https://github.com/vatesfr/xo-web/issues/391)
|
||||
- Data architecture review [\#384](https://github.com/vatesfr/xo-web/issues/384)
|
||||
- Make filtering easier to understand/add some "default" filters [\#207](https://github.com/vatesfr/xo-web/issues/207)
|
||||
- Improve performance [\#148](https://github.com/vatesfr/xo-web/issues/148)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- VM metadata export should not require a snapshot [\#615](https://github.com/vatesfr/xo-web/issues/615)
|
||||
- Missing patch for all hosts is continuously refreshed [\#609](https://github.com/vatesfr/xo-web/issues/609)
|
||||
- Backup import memory issue [\#608](https://github.com/vatesfr/xo-web/issues/608)
|
||||
- Host list missing patch is buggy [\#604](https://github.com/vatesfr/xo-web/issues/604)
|
||||
- Servers infos should not been refreshed while a field is being edited [\#595](https://github.com/vatesfr/xo-web/issues/595)
|
||||
- Servers list should not been re-order while a field is being edited [\#594](https://github.com/vatesfr/xo-web/issues/594)
|
||||
- Correctly display size in interface \(binary scale\) [\#592](https://github.com/vatesfr/xo-web/issues/592)
|
||||
- Display failures on VM boot order modification [\#560](https://github.com/vatesfr/xo-web/issues/560)
|
||||
- `vm.setBootOrder\(\)` should throw errors on failures \(non-HVM VMs\) [\#559](https://github.com/vatesfr/xo-web/issues/559)
|
||||
- Hide boot order form for non-HVM VMs [\#558](https://github.com/vatesfr/xo-web/issues/558)
|
||||
- Allow editing PV args even when empty \(but only for PV VMs\) [\#557](https://github.com/vatesfr/xo-web/issues/557)
|
||||
- Crashes when using legacy event system [\#556](https://github.com/vatesfr/xo-web/issues/556)
|
||||
- XenServer patches check error for 6.1 [\#555](https://github.com/vatesfr/xo-web/issues/555)
|
||||
- activation plugin xo-server-transport-email [\#553](https://github.com/vatesfr/xo-web/issues/553)
|
||||
- Server error with JSON on 32 bits Dom0 [\#552](https://github.com/vatesfr/xo-web/issues/552)
|
||||
- Cloud Config drive shouldn't be created on default SR [\#548](https://github.com/vatesfr/xo-web/issues/548)
|
||||
- Deep properties cannot be edited in plugins configuration form [\#521](https://github.com/vatesfr/xo-web/issues/521)
|
||||
- Aborted VM export should cancel the operation [\#490](https://github.com/vatesfr/xo-web/issues/490)
|
||||
- VM missing with same UUID after an inter-pool migration [\#284](https://github.com/vatesfr/xo-web/issues/284)
|
||||
|
||||
## **4.10.0** (2015-11-27)
|
||||
|
||||
Job management, email notifications, CoreOS/Docker, Quiesce snapshots...
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Job management ([xo-web#487](https://github.com/vatesfr/xo-web/issues/487))
|
||||
- Patch upload on all connected servers ([xo-web#168](https://github.com/vatesfr/xo-web/issues/168))
|
||||
- Emergency shutdown ([xo-web#185](https://github.com/vatesfr/xo-web/issues/185))
|
||||
- CoreOS/docker template install ([xo-web#246](https://github.com/vatesfr/xo-web/issues/246))
|
||||
- Email for backups ([xo-web#308](https://github.com/vatesfr/xo-web/issues/308))
|
||||
- Console Clipboard ([xo-web#408](https://github.com/vatesfr/xo-web/issues/408))
|
||||
- Logs from CLI ([xo-web#486](https://github.com/vatesfr/xo-web/issues/486))
|
||||
- Save disconnected servers ([xo-web#489](https://github.com/vatesfr/xo-web/issues/489))
|
||||
- Snapshot with quiesce ([xo-web#491](https://github.com/vatesfr/xo-web/issues/491))
|
||||
- Start VM in reovery mode ([xo-web#495](https://github.com/vatesfr/xo-web/issues/495))
|
||||
- Username in logs ([xo-web#498](https://github.com/vatesfr/xo-web/issues/498))
|
||||
- Delete associated tokens with user ([xo-web#500](https://github.com/vatesfr/xo-web/issues/500))
|
||||
- Validate plugin configuration ([xo-web#503](https://github.com/vatesfr/xo-web/issues/503))
|
||||
- Avoid non configured plugins to be loaded ([xo-web#504](https://github.com/vatesfr/xo-web/issues/504))
|
||||
- Verbose API logs if configured ([xo-web#505](https://github.com/vatesfr/xo-web/issues/505))
|
||||
- Better backup overview ([xo-web#512](https://github.com/vatesfr/xo-web/issues/512))
|
||||
- VM auto power on ([xo-web#519](https://github.com/vatesfr/xo-web/issues/519))
|
||||
- Title property supported in config schema ([xo-web#522](https://github.com/vatesfr/xo-web/issues/522))
|
||||
- Start VM export only when necessary ([xo-web#534](https://github.com/vatesfr/xo-web/issues/534))
|
||||
- Input type should be number ([xo-web#538](https://github.com/vatesfr/xo-web/issues/538))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Numbers/int support in plugins config ([xo-web#531](https://github.com/vatesfr/xo-web/issues/531))
|
||||
- Boolean support in plugins config ([xo-web#528](https://github.com/vatesfr/xo-web/issues/528))
|
||||
- Keyboard unusable outside console ([xo-web#513](https://github.com/vatesfr/xo-web/issues/513))
|
||||
- UsernameField for SAML ([xo-web#513](https://github.com/vatesfr/xo-web/issues/513))
|
||||
- Wrong display of "no plugin found" ([xo-web#508](https://github.com/vatesfr/xo-web/issues/508))
|
||||
- Bower build error ([xo-web#488](https://github.com/vatesfr/xo-web/issues/488))
|
||||
- VM cloning should require SR permission ([xo-web#472](https://github.com/vatesfr/xo-web/issues/472))
|
||||
- Xen tools status ([xo-web#471](https://github.com/vatesfr/xo-web/issues/471))
|
||||
- Can't delete ghost user ([xo-web#464](https://github.com/vatesfr/xo-web/issues/464))
|
||||
- Stats with old versions of Node ([xo-web#463](https://github.com/vatesfr/xo-web/issues/463))
|
||||
|
||||
## **4.9.0** (2015-11-13)
|
||||
|
||||
Automated DR, restore backup, VM copy
|
||||
|
||||
### Enhancements
|
||||
|
||||
- DR: schedule VM export on other host ([xo-web#447](https://github.com/vatesfr/xo-web/issues/447))
|
||||
- Scheduler logs ([xo-web#390](https://github.com/vatesfr/xo-web/issues/390) and [xo-web#477](https://github.com/vatesfr/xo-web/issues/477))
|
||||
- Restore backups ([xo-web#450](https://github.com/vatesfr/xo-web/issues/350))
|
||||
- Disable backup compression ([xo-web#467](https://github.com/vatesfr/xo-web/issues/467))
|
||||
- Copy VM to another SR (even remote) ([xo-web#475](https://github.com/vatesfr/xo-web/issues/475))
|
||||
- VM stats without time sync ([xo-web#460](https://github.com/vatesfr/xo-web/issues/460))
|
||||
- Stats perfs for high CPU numbers ([xo-web#461](https://github.com/vatesfr/xo-web/issues/461))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Rolling backup bug ([xo-web#484](https://github.com/vatesfr/xo-web/issues/484))
|
||||
- vCPUs/CPUs inversion in dashboard ([xo-web#481](https://github.com/vatesfr/xo-web/issues/481))
|
||||
- Machine to template ([xo-web#459](https://github.com/vatesfr/xo-web/issues/459))
|
||||
|
||||
### Misc
|
||||
|
||||
- Console fix in XenServer ([xo-web#406](https://github.com/vatesfr/xo-web/issues/406))
|
||||
|
||||
## **4.8.0** (2015-10-29)
|
||||
|
||||
Fully automated patch system, ACLs inheritance, stats performance improved.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- ACLs inheritance ([xo-web#279](https://github.com/vatesfr/xo-web/issues/279))
|
||||
- Patch automatically all missing updates ([xo-web#281](https://github.com/vatesfr/xo-web/issues/281))
|
||||
- Intelligent stats polling ([xo-web#432](https://github.com/vatesfr/xo-web/issues/432))
|
||||
- Cache latest result of stats request ([xo-web#431](https://github.com/vatesfr/xo-web/issues/431))
|
||||
- Improve stats polling on multiple objects ([xo-web#433](https://github.com/vatesfr/xo-web/issues/433))
|
||||
- Patch upload task should display the patch name ([xo-web#449](https://github.com/vatesfr/xo-web/issues/449))
|
||||
- Backup filename for Windows ([xo-web#448](https://github.com/vatesfr/xo-web/issues/448))
|
||||
- Specific distro icons ([xo-web#446](https://github.com/vatesfr/xo-web/issues/446))
|
||||
- PXE boot for HVM ([xo-web#436](https://github.com/vatesfr/xo-web/issues/436))
|
||||
- Favicon display before sign in ([xo-web#428](https://github.com/vatesfr/xo-web/issues/428))
|
||||
- Registration renewal ([xo-web#424](https://github.com/vatesfr/xo-web/issues/424))
|
||||
- Reconnect to the host if pool merge fails ([xo-web#403](https://github.com/vatesfr/xo-web/issues/403))
|
||||
- Avoid brute force login ([xo-web#339](https://github.com/vatesfr/xo-web/issues/339))
|
||||
- Missing FreeBSD icon ([xo-web#136](https://github.com/vatesfr/xo-web/issues/136))
|
||||
- Hide halted objects in the Health view ([xo-web#457](https://github.com/vatesfr/xo-web/issues/457))
|
||||
- Click on "Remember me" label ([xo-web#438](https://github.com/vatesfr/xo-web/issues/438))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Pool patches in multiple pools not displayed ([xo-web#442](https://github.com/vatesfr/xo-web/issues/442))
|
||||
- VM Import crashes with Chrome ([xo-web#427](https://github.com/vatesfr/xo-web/issues/427))
|
||||
- Cannot open a direct link ([xo-web#371](https://github.com/vatesfr/xo-web/issues/371))
|
||||
- Patch display edge case ([xo-web#309](https://github.com/vatesfr/xo-web/issues/309))
|
||||
- VM snapshot should require user permission on SR ([xo-web#429](https://github.com/vatesfr/xo-web/issues/429))
|
||||
|
||||
## **4.7.0** (2015-10-12)
|
||||
|
||||
Plugin config management and browser notifications.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Plugin management in the web interface ([xo-web#352](https://github.com/vatesfr/xo-web/issues/352))
|
||||
- Browser notifications ([xo-web#402](https://github.com/vatesfr/xo-web/issues/402))
|
||||
- Graph selector ([xo-web#400](https://github.com/vatesfr/xo-web/issues/400))
|
||||
- Circle packing visualization ([xo-web#374](https://github.com/vatesfr/xo-web/issues/374))
|
||||
- Password generation ([xo-web#397](https://github.com/vatesfr/xo-web/issues/397))
|
||||
- Password reveal during user creation ([xo-web#396](https://github.com/vatesfr/xo-web/issues/396))
|
||||
- Add host to a pool ([xo-web#62](https://github.com/vatesfr/xo-web/issues/62))
|
||||
- Better modal when removing a host from a pool ([xo-web#405](https://github.com/vatesfr/xo-web/issues/405))
|
||||
- Drop focus on CD/ISO selector ([xo-web#290](https://github.com/vatesfr/xo-web/issues/290))
|
||||
- Allow non persistent session ([xo-web#243](https://github.com/vatesfr/xo-web/issues/243))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- VM export permission corrected ([xo-web#410](https://github.com/vatesfr/xo-web/issues/410))
|
||||
- Proper host removal in a pool ([xo-web#402](https://github.com/vatesfr/xo-web/issues/402))
|
||||
- Sub-optimal tooltip placement ([xo-web#421](https://github.com/vatesfr/xo-web/issues/421))
|
||||
- VM migrate host incorrect target ([xo-web#419](https://github.com/vatesfr/xo-web/issues/419))
|
||||
- Alone host can't leave its pool ([xo-web#414](https://github.com/vatesfr/xo-web/issues/414))
|
||||
|
||||
## **4.6.0** (2015-09-25)
|
||||
|
||||
Tags management and new visualization.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Multigraph for correlation ([xo-web#358](https://github.com/vatesfr/xo-web/issues/358))
|
||||
- Tags management ([xo-web#367](https://github.com/vatesfr/xo-web/issues/367))
|
||||
- Google Provider for authentication ([xo-web#363](https://github.com/vatesfr/xo-web/issues/363))
|
||||
- Password change for users ([xo-web#362](https://github.com/vatesfr/xo-web/issues/362))
|
||||
- Better live migration process ([xo-web#237](https://github.com/vatesfr/xo-web/issues/237))
|
||||
- VDI search filter in SR view ([xo-web#222](https://github.com/vatesfr/xo-web/issues/222))
|
||||
- PV args during VM creation ([xo-web#112](https://github.com/vatesfr/xo-web/issues/330))
|
||||
- PV args management ([xo-web#394](https://github.com/vatesfr/xo-web/issues/394))
|
||||
- Confirmation dialog on important actions ([xo-web#350](https://github.com/vatesfr/xo-web/issues/350))
|
||||
- New favicon ([xo-web#369](https://github.com/vatesfr/xo-web/issues/369))
|
||||
- Filename of VM for exports ([xo-web#370](https://github.com/vatesfr/xo-web/issues/370))
|
||||
- ACLs rights edited on the fly ([xo-web#323](https://github.com/vatesfr/xo-web/issues/323))
|
||||
- Heatmap values now human readable ([xo-web#342](https://github.com/vatesfr/xo-web/issues/342))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Export backup fails if no tags specified ([xo-web#383](https://github.com/vatesfr/xo-web/issues/383))
|
||||
- Wrong login give an obscure error message ([xo-web#373](https://github.com/vatesfr/xo-web/issues/373))
|
||||
- Update view is broken during updates ([xo-web#356](https://github.com/vatesfr/xo-web/issues/356))
|
||||
- Settings/dashboard menu incorrect display ([xo-web#357](https://github.com/vatesfr/xo-web/issues/357))
|
||||
- Console View Not refreshing if the VM restart ([xo-web#107](https://github.com/vatesfr/xo-web/issues/107))
|
||||
|
||||
## **4.5.1** (2015-09-16)
|
||||
|
||||
An issue in `xo-web` with the VM view.
|
||||
|
||||
### Bug fix
|
||||
|
||||
- Attach disk/new disk/create interface is broken ([xo-web#378](https://github.com/vatesfr/xo-web/issues/378))
|
||||
|
||||
## **4.5.0** (2015-09-11)
|
||||
|
||||
A new dataviz (parallel coord), a new provider (GitHub) and faster consoles.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Parallel coordinates view ([xo-web#333](https://github.com/vatesfr/xo-web/issues/333))
|
||||
- Faster consoles ([xo-web#337](https://github.com/vatesfr/xo-web/issues/337))
|
||||
- Disable/hide button ([xo-web#268](https://github.com/vatesfr/xo-web/issues/268))
|
||||
- More details on missing-guest-tools ([xo-web#304](https://github.com/vatesfr/xo-web/issues/304))
|
||||
- Scheduler meta data export ([xo-web#315](https://github.com/vatesfr/xo-web/issues/315))
|
||||
- Better heatmap ([xo-web#330](https://github.com/vatesfr/xo-web/issues/330))
|
||||
- Faster dashboard ([xo-web#331](https://github.com/vatesfr/xo-web/issues/331))
|
||||
- Faster sunburst ([xo-web#332](https://github.com/vatesfr/xo-web/issues/332))
|
||||
- GitHub provider for auth ([xo-web#334](https://github.com/vatesfr/xo-web/issues/334))
|
||||
- Filter networks for users ([xo-web#347](https://github.com/vatesfr/xo-web/issues/347))
|
||||
- Add networks in ACLs ([xo-web#348](https://github.com/vatesfr/xo-web/issues/348))
|
||||
- Better looking login page ([xo-web#341](https://github.com/vatesfr/xo-web/issues/341))
|
||||
- Real time dataviz (dashboard) ([xo-web#349](https://github.com/vatesfr/xo-web/issues/349))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Typo in dashboard ([xo-web#355](https://github.com/vatesfr/xo-web/issues/355))
|
||||
- Global RAM usage fix ([xo-web#356](https://github.com/vatesfr/xo-web/issues/356))
|
||||
- Re-allowing XO behind a reverse proxy ([xo-web#361](https://github.com/vatesfr/xo-web/issues/361))
|
||||
|
||||
## **4.4.0** (2015-08-28)
|
||||
|
||||
SSO and Dataviz are the main features for this release.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Dataviz storage usage ([xo-web#311](https://github.com/vatesfr/xo-web/issues/311))
|
||||
- Heatmap in health view ([xo-web#329](https://github.com/vatesfr/xo-web/issues/329))
|
||||
- SSO for SAML and other providers ([xo-web#327](https://github.com/vatesfr/xo-web/issues/327))
|
||||
- Better UI for ACL objects attribution ([xo-web#320](https://github.com/vatesfr/xo-web/issues/320))
|
||||
- Refresh the browser after an update ([xo-web#318](https://github.com/vatesfr/xo-web/issues/318))
|
||||
- Clean CSS and Flexbox usage ([xo-web#239](https://github.com/vatesfr/xo-web/issues/239))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Admin only accessible views ([xo-web#328](https://github.com/vatesfr/xo-web/issues/328))
|
||||
- Hide "base copy" VDIs ([xo-web#324](https://github.com/vatesfr/xo-web/issues/324))
|
||||
- ACLs on VIFs for non-admins ([xo-web#322](https://github.com/vatesfr/xo-web/issues/322))
|
||||
- Updater display problems ([xo-web#313](https://github.com/vatesfr/xo-web/issues/313))
|
||||
|
||||
## **4.3.0** (2015-07-22)
|
||||
|
||||
Scheduler for rolling backups
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Rolling backup scheduler ([xo-web#278](https://github.com/vatesfr/xo-web/issues/278))
|
||||
- Clean snapshots of removed VMs ([xo-web#301](https://github.com/vatesfr/xo-web/issues/301))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- VM export ([xo-web#307](https://github.com/vatesfr/xo-web/issues/307))
|
||||
- Remove VM VDIs ([xo-web#303](https://github.com/vatesfr/xo-web/issues/303))
|
||||
- Pagination fails ([xo-web#302](https://github.com/vatesfr/xo-web/issues/302))
|
||||
|
||||
## **4.2.0** (2015-06-29)
|
||||
|
||||
Huge performance boost, scheduler for rolling snapshots and backward compatibility for XS 5.x series
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Rolling snapshots scheduler ([xo-web#176](https://github.com/vatesfr/xo-web/issues/176))
|
||||
- Huge perf boost ([xen-api#1](https://github.com/julien-f/js-xen-api/issues/1))
|
||||
- Backward compatibility ([xo-web#296](https://github.com/vatesfr/xo-web/issues/296))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- VDI attached on a VM missing in SR view ([xo-web#294](https://github.com/vatesfr/xo-web/issues/294))
|
||||
- Better VM creation process ([xo-web#292](https://github.com/vatesfr/xo-web/issues/292))
|
||||
|
||||
## **4.1.0** (2015-06-10)
|
||||
|
||||
Add the drag'n drop support from VM live migration, better ACLs groups UI.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Drag'n drop VM in tree view for live migration ([xo-web#277](https://github.com/vatesfr/xo-web/issues/277))
|
||||
- Better group view with objects ACLs ([xo-web#276](https://github.com/vatesfr/xo-web/issues/276))
|
||||
- Hide non-visible objects ([xo-web#272](https://github.com/vatesfr/xo-web/issues/272))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Convert to template displayed when the VM is not halted ([xo-web#286](https://github.com/vatesfr/xo-web/issues/286))
|
||||
- Lost some data when refresh some views ([xo-web#271](https://github.com/vatesfr/xo-web/issues/271))
|
||||
- Suspend button don't trigger any permission message ([xo-web#270](https://github.com/vatesfr/xo-web/issues/270))
|
||||
- Create network interfaces shouldn't call xoApi directly ([xo-web#269](https://github.com/vatesfr/xo-web/issues/269))
|
||||
- Don't plug automatically a disk or a VIF if the VM is not running ([xo-web#287](https://github.com/vatesfr/xo-web/issues/287))
|
||||
|
||||
## **4.0.2** (2015-06-01)
|
||||
|
||||
An issue in `xo-server` with the password of default admin account and also a UI fix.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Cannot modify admin account ([xo-web#265](https://github.com/vatesfr/xo-web/issues/265))
|
||||
- Password field seems to keep empty/reset itself after 1-2 seconds ([xo-web#264](https://github.com/vatesfr/xo-web/issues/264))
|
||||
|
||||
## **4.0.1** (2015-05-30)
|
||||
|
||||
An issue with the updater in HTTPS was left in the *4.0.0*. This patch release fixed
|
||||
it.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- allow updater to work in HTTPS ([xo-web#266](https://github.com/vatesfr/xo-web/issues/266))
|
||||
|
||||
## **4.0.0** (2015-05-29)
|
||||
|
||||
[Blog post of this release](https://xen-orchestra.com/blog/xen-orchestra-4-0).
|
||||
|
||||
### Enhancements
|
||||
|
||||
- advanced ACLs ([xo-web#209](https://github.com/vatesfr/xo-web/issues/209))
|
||||
- xenserver update management ([xo-web#174](https://github.com/vatesfr/xo-web/issues/174) & [xo-web#259](https://github.com/vatesfr/xo-web/issues/259))
|
||||
- docker control ([xo-web#211](https://github.com/vatesfr/xo-web/issues/211))
|
||||
- better responsive design ([xo-web#252](https://github.com/vatesfr/xo-web/issues/252))
|
||||
- host stats ([xo-web#255](https://github.com/vatesfr/xo-web/issues/255))
|
||||
- pagination ([xo-web#221](https://github.com/vatesfr/xo-web/issues/221))
|
||||
- web updater
|
||||
- better VM creation process([xo-web#256](https://github.com/vatesfr/xo-web/issues/256))
|
||||
- VM boot order([xo-web#251](https://github.com/vatesfr/xo-web/issues/251))
|
||||
- new mapped collection([xo-server#47](https://github.com/vatesfr/xo-server/issues/47))
|
||||
- resource location in ACL view ([xo-web#245](https://github.com/vatesfr/xo-web/issues/245))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- wrong calulation of RAM amounts ([xo-web#51](https://github.com/vatesfr/xo-web/issues/51))
|
||||
- checkbox not aligned ([xo-web#253](https://github.com/vatesfr/xo-web/issues/253))
|
||||
- VM stats behavior more robust ([xo-web#250](https://github.com/vatesfr/xo-web/issues/250))
|
||||
- XO not on the root of domain ([xo-web#254](https://github.com/vatesfr/xo-web/issues/254))
|
||||
|
||||
|
||||
## **3.9.1** (2015-04-21)
|
||||
|
||||
A few bugs hve made their way into *3.9.0*, this minor release fixes
|
||||
them.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- correctly keep the VM guest metrics up to date ([xo-web#172](https://github.com/vatesfr/xo-web/issues/172))
|
||||
- fix edition of a VM snapshot ([b04111c](https://github.com/vatesfr/xo-server/commit/b04111c79ba8937778b84cb861bb7c2431162c11))
|
||||
- do not fetch stats if the VM state is transitioning ([a5c9880](https://github.com/vatesfr/xo-web/commit/a5c98803182792d2fe5ceb840ae1e23a8b767923))
|
||||
- fix broken Angular due to new version of Babel which breaks ngAnnotate ([0a9c868](https://github.com/vatesfr/xo-web/commit/0a9c868678d239e5b3e54b4d2bc3bd14b5400120))
|
||||
- feedback when connecting/disconnecting a server ([027d1e8](https://github.com/vatesfr/xo-web/commit/027d1e8cb1f2431e67042e1eec51690b2bc54ad7))
|
||||
- clearer error message if a server is unreachable ([06ca007](https://github.com/vatesfr/xo-server/commit/06ca0079b321e757aaa4112caa6f92a43193e35d))
|
||||
|
||||
## **3.9.0** (2015-04-20)
|
||||
|
||||
[Blog post of this release](https://xen-orchestra.com/blog/xen-orchestra-3-9).
|
||||
|
||||
### Enhancements
|
||||
|
||||
- ability to manually connect/disconnect a server ([xo-web#88](https://github.com/vatesfr/xo-web/issues/88) & [xo-web#234](https://github.com/vatesfr/xo-web/issues/234))
|
||||
- display the connection status of a server ([xo-web#103](https://github.com/vatesfr/xo-web/issues/103))
|
||||
- better feedback when connecting to a server ([xo-web#210](https://github.com/vatesfr/xo-web/issues/210))
|
||||
- ability to add a local LVM SR ([xo-web#219](https://github.com/vatesfr/xo-web/issues/219))
|
||||
- display virtual GPUs in VM view ([xo-web#223](https://github.com/vatesfr/xo-web/issues/223))
|
||||
- ability to automatically start a VM with its host ([xo-web#224](https://github.com/vatesfr/xo-web/issues/224))
|
||||
- ability to create networks ([xo-web#225](https://github.com/vatesfr/xo-web/issues/225))
|
||||
- live charts for a VM CPU/disk/network & RAM ([xo-web#228](https://github.com/vatesfr/xo-web/issues/228) & [xo-server#51](https://github.com/vatesfr/xo-server/issues/51))
|
||||
- remove VM import progress notifications (redundant with the tasks list) ([xo-web#235](https://github.com/vatesfr/xo-web/issues/235))
|
||||
- XO-Server sources are compiled to JS prior distribution: less bugs & faster startups ([xo-server#50](https://github.com/vatesfr/xo-server/issues/50))
|
||||
- use XAPI `event.from()` instead of `event.next()` which leads to faster connection ([xo-server#52](https://github.com/vatesfr/xo-server/issues/52))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- removed servers are properly disconnected ([xo-web#61](https://github.com/vatesfr/xo-web/issues/61))
|
||||
- fix VM creation with multiple interfaces ([xo-wb#229](https://github.com/vatesfr/xo-wb/issues/229))
|
||||
- disconnected servers are properly removed from the interface ([xo-web#234](https://github.com/vatesfr/xo-web/issues/234))
|
||||
|
||||
## **3.8.0** (2015-03-27)
|
||||
|
||||
[Blog post of this release](https://xen-orchestra.com/blog/xen-orchestra-3-8).
|
||||
|
||||
### Enhancements
|
||||
|
||||
- initial plugin system ([xo-server#37](https://github.com/vatesfr/xo-server/issues/37))
|
||||
- new authentication system based on providers ([xo-server#39](https://github.com/vatesfr/xo-server/issues/39))
|
||||
- LDAP authentication plugin for XO-Server ([xo-server#40](https://github.com/vatesfr/xo-server/issues/40))
|
||||
- disk creation on the VM page ([xo-web#215](https://github.com/vatesfr/xo-web/issues/215))
|
||||
- network creation on the VM page ([xo-web#216](https://github.com/vatesfr/xo-web/issues/216))
|
||||
- charts on the host and SR pages ([xo-web#217](https://github.com/vatesfr/xo-web/issues/217))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- fix *Invalid parameter(s)* message on the settings page ([xo-server#49](https://github.com/vatesfr/xo-server/issues/49))
|
||||
- fix mouse clicks in console ([xo-web#205](https://github.com/vatesfr/xo-web/issues/205))
|
||||
- fix user editing on the settings page ([xo-web#206](https://github.com/vatesfr/xo-web/issues/206))
|
||||
|
||||
## **3.7.0** (2015-03-06)
|
||||
|
||||
*Highlights in this release are the [initial ACLs implementation](https://xen-orchestra.com/blog/xen-orchestra-3-7-is-out-acls-in-early-access), [live migration for VDIs](https://xen-orchestra.com/blog/moving-vdi-in-live) and the ability to [create a new storage repository](https://xen-orchestra.com/blog/create-a-storage-repository-with-xen-orchestra/).*
|
||||
|
||||
### Enhancements
|
||||
|
||||
- ability to live migrate a VM between hosts with different CPUs ([xo-web#126](https://github.com/vatesfr/xo-web/issues/126))
|
||||
- ability to live migrate a VDI ([xo-web#177](https://github.com/vatesfr/xo-web/issues/177))
|
||||
- display a notification on VM creation ([xo-web#178](https://github.com/vatesfr/xo-web/issues/178))
|
||||
- ability to create/attach a iSCSI/NFS/ISO SR ([xo-web#179](https://github.com/vatesfr/xo-web/issues/179))
|
||||
- display SR available space on VM creation ([xo-web#180](https://github.com/vatesfr/xo-web/issues/180))
|
||||
- ability to enable and disable host on the tree view ([xo-web#181](https://github.com/vatesfr/xo-web/issues/181) & [xo-web#182](https://github.com/vatesfr/xo-web/issues/182))
|
||||
- ability to suspend/resume a VM ([xo-web#186](https://github.com/vatesfr/xo-web/issues/186))
|
||||
- display Linux icon for SUSE Linux Enterprise Server distribution ([xo-web#187](https://github.com/vatesfr/xo-web/issues/187))
|
||||
- correctly handle incorrectly formated token in cookies ([xo-web#192](https://github.com/vatesfr/xo-web/issues/192))
|
||||
- display host manufacturer in host view ([xo-web#195](https://github.com/vatesfr/xo-web/issues/195))
|
||||
- only display task concerning authorized objects ([xo-web#197](https://github.com/vatesfr/xo-web/issues/197))
|
||||
- better welcome message ([xo-web#199](https://github.com/vatesfr/xo-web/issues/199))
|
||||
- initial ACLs ([xo-web#202](https://github.com/vatesfr/xo-web/issues/202))
|
||||
- display an action panel to rescan, remove, attach and forget a SR ([xo-web#203](https://github.com/vatesfr/xo-web/issues/203))
|
||||
- display current active tasks in navbar ([xo-web#204](https://github.com/vatesfr/xo-web/issues/204))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- implements a proxy which fixes consoles over HTTPs ([xo#14](https://github.com/vatesfr/xo/issues/14))
|
||||
- fix tasks listing in host view ([xo-server#43](https://github.com/vatesfr/xo-server/issues/43))
|
||||
- fix console view on IE ([xo-web#184](https://github.com/vatesfr/xo-web/issues/184))
|
||||
- fix out of sync objects in XO-Web ([xo-web#142](https://github.com/vatesfr/xo-web/issues/142))
|
||||
- fix incorrect connection status displayed in login view ([xo-web#193](https://github.com/vatesfr/xo-web/issues/193))
|
||||
- fix *flickering* tree view ([xo-web#194](https://github.com/vatesfr/xo-web/issues/194))
|
||||
- single host pools should not have a dropdown menu in tree view ([xo-web#198](https://github.com/vatesfr/xo-web/issues/198))
|
||||
|
||||
## **3.6.0** (2014-11-28)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- upload and apply patches to hosts/pools ([xo-web#150](https://github.com/vatesfr/xo-web/issues/150))
|
||||
- import VMs ([xo-web#151](https://github.com/vatesfr/xo-web/issues/151))
|
||||
- export VMs ([xo-web#152](https://github.com/vatesfr/xo-web/issues/152))
|
||||
- migrate VMs to another pool ([xo-web#153](https://github.com/vatesfr/xo-web/issues/153))
|
||||
- display pool even for single host ([xo-web#155](https://github.com/vatesfr/xo-web/issues/155))
|
||||
- start halted hosts with wake-on-LAN ([xo-web#154](https://github.com/vatesfr/xo-web/issues/154))
|
||||
- list of uploaded/applied patches ([xo-web#156](https://github.com/vatesfr/xo-web/issues/156))
|
||||
- use Angular 1.3 from npm ([xo-web#157](https://github.com/vatesfr/xo-web/issues/157) & [xo-web#160](https://github.com/vatesfr/xo-web/issues/160))
|
||||
- more feedbacks on actions ([xo-web#165](https://github.com/vatesfr/xo-web/issues/165))
|
||||
- only buttons compatible with VM states are displayed ([xo-web#166](https://github.com/vatesfr/xo-web/issues/166))
|
||||
- export VM snapshot ([xo-web#167](https://github.com/vatesfr/xo-web/issues/167))
|
||||
- plug/unplug a SR to a host ([xo-web#169](https://github.com/vatesfr/xo-web/issues/169))
|
||||
- plug a SR to all available hosts ([xo-web#170](https://github.com/vatesfr/xo-web/issues/170))
|
||||
- disks editing on SR page ([xo-web#171](https://github.com/vatesfr/xo-web/issues/171))
|
||||
- export of running VMs ([xo-server#36](https://github.com/vatesfr/xo-server/issues/36))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- disks editing on VM page should work ([xo-web#86](https://github.com/vatesfr/xo-web/issues/86))
|
||||
- dropdown menus should close after selecting an item ([xo-web#140](https://github.com/vatesfr/xo-web/issues/140))
|
||||
- user creation should require a password ([xo-web#143](https://github.com/vatesfr/xo-web/issues/143))
|
||||
- server connection should require a user and a password ([xo-web#144](https://github.com/vatesfr/xo-web/issues/144))
|
||||
- snapshot deletion should work ([xo-server#147](https://github.com/vatesfr/xo-server/issues/147))
|
||||
- VM console should work in Chrome ([xo-web#149](https://github.com/vatesfr/xo-web/issues/149))
|
||||
- tooltips should work ([xo-web#163](https://github.com/vatesfr/xo-web/issues/163))
|
||||
- disk plugged status should be automatically refreshed ([xo-web#164](https://github.com/vatesfr/xo-web/issues/164))
|
||||
- deleting users without tokens should not trigger an error ([xo-server#34](https://github.com/vatesfr/xo-server/issues/34))
|
||||
- live pool master change should work ([xo-server#20](https://github.com/vatesfr/xo-server/issues/20))
|
||||
|
||||
## **3.5.1** (2014-08-19)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- pool view works again ([#139](https://github.com/vatesfr/xo-web/issues/139))
|
||||
|
||||
## **3.5.0** (2014-08-14)
|
||||
|
||||
*[XO-Web](https://www.npmjs.org/package/xo-web) and [XO-Server](https://www.npmjs.org/package/xo-server) are now available as npm packages!*
|
||||
|
||||
### Enhancements
|
||||
|
||||
- XO-Server published on npm ([#26](https://github.com/vatesfr/xo-server/issues/26))
|
||||
- XO-Server config is now in `/etc/xo-server/config.yaml` ([#33](https://github.com/vatesfr/xo-server/issues/33))
|
||||
- paths in XO-Server's config are now relative to the config file ([#19](https://github.com/vatesfr/xo-server/issues/19))
|
||||
- use the Linux icon for Fedora ([#131](https://github.com/vatesfr/xo-web/issues/131))
|
||||
- start/stop/reboot buttons on console page ([#121](https://github.com/vatesfr/xo-web/issues/121))
|
||||
- settings page now only accessible to admin ([#77](https://github.com/vatesfr/xo-web/issues/77))
|
||||
- redirection to the home page when a VM is deleted from its own page ([#56](https://github.com/vatesfr/xo-web/issues/56))
|
||||
- XO-Web published on npm ([#123](https://github.com/vatesfr/xo-web/issues/123))
|
||||
- buid process now use Browserify (([#125](https://github.com/vatesfr/xo-web/issues/125), [#135](https://github.com/vatesfr/xo-web/issues/135)))
|
||||
- view are now written in Jade instead of HTML ([#124](https://github.com/vatesfr/xo-web/issues/124))
|
||||
- CSS autoprefixer to improve compatibility ([#132](https://github.com/vatesfr/xo-web/issues/132), [#137](https://github.com/vatesfr/xo-web/issues/137))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- force shutdown does not attempt a clean shutdown first anymore ([#29](https://github.com/vatesfr/xo-server/issues/29))
|
||||
- shutdown hosts are now correctly reported as such ([#31](https://github.com/vatesfr/xo-web/issues/31))
|
||||
- incorrect VM metrics ([#54](https://github.com/vatesfr/xo-web/issues/54), [#68](https://github.com/vatesfr/xo-web/issues/68), [#108](https://github.com/vatesfr/xo-web/issues/108))
|
||||
- an user cannot delete itself ([#104](https://github.com/vatesfr/xo-web/issues/104))
|
||||
- in VM creation, required fields are now marked as such ([#113](https://github.com/vatesfr/xo-web/issues/113), [#114](https://github.com/vatesfr/xo-web/issues/114))
|
||||
|
||||
## **3.4.0** (2014-05-22)
|
||||
|
||||
*Highlight in this release is the new events system between XO-Web
|
||||
and XO-Server which results in less bandwidth consumption as well as
|
||||
better performance and reactivity.*
|
||||
|
||||
### Enhancements
|
||||
|
||||
- events system between XO-Web and XO-Server ([#52](https://github.com/vatesfr/xo-web/issues/52))
|
||||
- ability to clone/copy a VM ([#116](https://github.com/vatesfr/xo-web/issues/116))
|
||||
- mandatory log in page ([#120](https://github.com/vatesfr/xo-web/issues/120))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- failure in VM creation ([#111](https://github.com/vatesfr/xo-web/issues/111))
|
||||
|
||||
## **3.3.1** (2014-03-28)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- console view is now prettier ([#92](https://github.com/vatesfr/xo-web/issues/92))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- VM creation fails to incorrect dependencies ([xo-server/#24](https://github.com/vatesfr/xo-server/issues/24))
|
||||
- VDIs list in SR view is blinking ([#109](https://github.com/vatesfr/xo-web/issues/109))
|
||||
|
||||
## **3.3.0** (2014-03-07)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [Grunt](http://gruntjs.com/) has been replaced by [gulp.js](http://gulpjs.com/) ([#91](https://github.com/vatesfr/xo-web/issues/91))
|
||||
- a host can be detached from a pool ([#98](https://github.com/vatesfr/xo-web/issues/98))
|
||||
- snapshots management in VM view ([#99](https://github.com/vatesfr/xo-web/issues/99))
|
||||
- log deletion in VM view ([#100](https://github.com/vatesfr/xo-web/issues/100))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- *Snapshot* not working in VM view ([#95](https://github.com/vatesfr/xo-web/issues/95))
|
||||
- Host *Reboot*/*Restart toolstack*/*Shutdown* not working in main view ([#97](https://github.com/vatesfr/xo-web/issues/97))
|
||||
- Bower cannot install `angular` automatically due to a version conflict ([#101](https://github.com/vatesfr/xo-web/issues/101))
|
||||
- Bower installs an incorrect version of `angular-animate` ([#102](https://github.com/vatesfr/xo-web/issues/102))
|
||||
|
||||
## **3.2.0** (2014-02-21)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- dependencies' versions should be fixed to ease deployment ([#93](https://github.com/vatesfr/xo-web/issues/93))
|
||||
- badges added to the README to see whether dependencies are up to date ([#90](https://github.com/vatesfr/xo-web/issues/90))
|
||||
- an error notification has been added when the connection to XO-Server failed ([#89](https://github.com/vatesfr/xo-web/issues/89))
|
||||
- in host view, there is now a link to the host console ([#87](https://github.com/vatesfr/xo-web/issues/87))
|
||||
- in VM view, deleting a disk requires a confirmation ([#85](https://github.com/vatesfr/xo-web/issues/85))
|
||||
- the VM and console icons are now different ([#80](https://github.com/vatesfr/xo-web/issues/80))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- consoles now work in Google Chrome \o/ ([#46](https://github.com/vatesfr/xo-web/issues/46))
|
||||
- in host view, many buttons were not working ([#79](https://github.com/vatesfr/xo-web/issues/79))
|
||||
- in main view, incorrect icons were fixes ([#81](https://github.com/vatesfr/xo-web/issues/81))
|
||||
- MAC addresses should not be ignored during VM creation ([#94](https://github.com/vatesfr/xo-web/issues/94))
|
||||
|
||||
## **3.1.0** (2014-02-14)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- in VM view, interfaces' network should be displayed ([#64](https://github.com/vatesfr/xo-web/issues/64))
|
||||
- middle-click or `Ctrl`+click should open new windows (even on pseudo-links) ([#66](https://github.com/vatesfr/xo-web/issues/66))
|
||||
- lists should use natural sorting (e.g. *VM 2* before *VM 10*) ([#69](https://github.com/vatesfr/xo-web/issues/69))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- consoles are not implemented for hosts ([#57](https://github.com/vatesfr/xo-web/issues/57))
|
||||
- it makes no sense to remove a stand-alone host from a pool (58)
|
||||
- in VM view, the migrate button is not working ([#59](https://github.com/vatesfr/xo-web/issues/59))
|
||||
- pool and host names overflow their box in the main view ([#63](https://github.com/vatesfr/xo-web/issues/63))
|
||||
- in host view, interfaces incorrectly named *networks* and VLAN not shown ([#70](https://github.com/vatesfr/xo-web/issues/70))
|
||||
- VM suspended state is not properly handled ([#71](https://github.com/vatesfr/xo-web/issues/71))
|
||||
- unauthenticated users should not be able to access to consoles ([#73](https://github.com/vatesfr/xo-web/issues/73))
|
||||
- incorrect scroll (under the navbar) when the view changes ([#74](https://github.com/vatesfr/xo-web/issues/74))
|
||||
@@ -6,18 +6,17 @@ Here you can:
|
||||
- propose an enhancement
|
||||
- ask a question
|
||||
|
||||
The template below is only a proposition for your ticket, feel free to
|
||||
change it as appropriate :)
|
||||
Please, respect this template as much as possible, it helps us sort
|
||||
the issues :)
|
||||
-->
|
||||
|
||||
### Context
|
||||
|
||||
- **XO version**: XO appliance / `stable` branch / `next-release` branch
|
||||
|
||||
If from the sources:
|
||||
|
||||
- **Component**: xo-web / xo-server / *unknown*
|
||||
- **Node/npm version**: *just execute `npm version`*
|
||||
- **XO origin**: the sources / XO Appliance
|
||||
- **Versions**:
|
||||
- Node: **FILL HERE**
|
||||
- xo-web: **FILL HERE**
|
||||
- xo-server: **FILL HERE**
|
||||
|
||||
### Expected behavior
|
||||
|
||||
|
||||
84
README.md
84
README.md
@@ -1,90 +1,10 @@
|
||||
# Xen Orchestra Web [](https://travis-ci.org/vatesfr/xo-web)
|
||||
# Xen Orchestra [](https://go.crisp.im/chat/embed/?website_id=-JzqzzwddSV7bKGtEyAQ) [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||

|
||||
|
||||
XO-Web is part of [Xen Orchestra](https://github.com/vatesfr/xo), a web interface for XenServer or XAPI enabled hosts.
|
||||
|
||||
It is a web client for [XO-Server](https://github.com/vatesfr/xo-server).
|
||||
|
||||
___
|
||||
|
||||
## Installation
|
||||
|
||||
XOA or manual install procedure is [available here](https://github.com/vatesfr/xo/blob/master/doc/installation/README.md)
|
||||
|
||||
## Compilation
|
||||
|
||||
Production build:
|
||||
|
||||
```
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
Development build:
|
||||
|
||||
```
|
||||
$ npm run dev
|
||||
```
|
||||
|
||||
### Environment
|
||||
|
||||
#### `NODE_ENV`
|
||||
|
||||
Set to *production* it disables many checks which result in increased
|
||||
performance.
|
||||
|
||||
#### `XOA_PLAN`
|
||||
|
||||
- 1: Free
|
||||
- 2: Starter
|
||||
- 3: Enterprise
|
||||
- 4: Premium
|
||||
- 5: Sources
|
||||
|
||||
```js
|
||||
if (process.env.XOA_PLAN < 5) {
|
||||
console.log('included only in XOA')
|
||||
}
|
||||
|
||||
if (process.env.XOA_PLAN > 3) {
|
||||
console.log('included only in Premium and Sources')
|
||||
}
|
||||
```
|
||||
|
||||
## How to report a bug?
|
||||
|
||||
If you are certain the bug is exclusively related to XO-Web, you may use the [bugtracker of this repository](https://github.com/vatesfr/xo-web/issues).
|
||||
|
||||
Otherwise, please consider using the [bugtracker of the general repository](https://github.com/vatesfr/xo/issues).
|
||||
|
||||
## Process for new release
|
||||
|
||||
```bash
|
||||
# Switch to the stable branch.
|
||||
git checkout stable
|
||||
|
||||
# Fetches latest changes.
|
||||
git pull --ff-only
|
||||
|
||||
# Merge changes of the next-release branch.
|
||||
git merge next-release
|
||||
|
||||
# Increment the version (patch, minor or major).
|
||||
npm version minor
|
||||
|
||||
# Go back to the next-release branch.
|
||||
git checkout next-release
|
||||
|
||||
# Fetches the last changes (the merge and version bump) from stable to
|
||||
# next-release.
|
||||
git merge --ff-only stable
|
||||
|
||||
# Push the changes on git.
|
||||
git push --follow-tags origin stable next-release
|
||||
|
||||
# Publish this release to npm.
|
||||
npm publish
|
||||
```
|
||||
XOA or manual install procedure is [available here](https://xen-orchestra.com/docs/installation.html)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
227
package.json
227
package.json
@@ -1,180 +1,65 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-web",
|
||||
"version": "5.2.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
"xen",
|
||||
"orchestra",
|
||||
"xen-orchestra",
|
||||
"web"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xo-web",
|
||||
"bugs": "https://github.com/vatesfr/xo-web/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xo-web"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@vates.fr"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4",
|
||||
"npm": ">=3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ansi_up": "^1.3.0",
|
||||
"asap": "^2.0.4",
|
||||
"ava": "^0.16.0",
|
||||
"babel-eslint": "^6.0.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-plugin-transform-react-constant-elements": "^6.5.0",
|
||||
"babel-plugin-transform-react-inline-elements": "^6.6.5",
|
||||
"babel-plugin-transform-runtime": "^6.6.0",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"babel-runtime": "^6.6.1",
|
||||
"babelify": "^7.2.0",
|
||||
"benchmark": "^2.1.0",
|
||||
"bootstrap": "github:twbs/bootstrap#v4-dev",
|
||||
"browserify": "^13.0.0",
|
||||
"bundle-collapser": "^1.2.1",
|
||||
"chartist-plugin-legend": "^0.5.0",
|
||||
"chartist-plugin-tooltip": "0.0.11",
|
||||
"classnames": "^2.2.3",
|
||||
"connect": "^3.4.1",
|
||||
"cookies-js": "^1.2.2",
|
||||
"d3": "^4.0.0-alpha.50",
|
||||
"dependency-check": "^2.5.1",
|
||||
"font-awesome": "^4.5.0",
|
||||
"font-mfizz": "github:fizzed/font-mfizz",
|
||||
"get-stream": "^2.3.0",
|
||||
"ghooks": "^1.1.1",
|
||||
"globby": "^6.0.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-autoprefixer": "^3.1.0",
|
||||
"gulp-csso": "^2.0.0",
|
||||
"gulp-embedlr": "^0.5.2",
|
||||
"gulp-jade": "^1.1.0",
|
||||
"gulp-plumber": "^1.1.0",
|
||||
"gulp-refresh": "^1.1.0",
|
||||
"gulp-sass": "^2.2.0",
|
||||
"gulp-uglify": "^2.0.0",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"human-format": "^0.6.0",
|
||||
"is-ip": "^1.0.0",
|
||||
"jsonrpc-websocket-client": "0.0.1-5",
|
||||
"later": "^1.2.0",
|
||||
"lodash": "^4.6.1",
|
||||
"loose-envify": "^1.1.0",
|
||||
"marked": "^0.3.5",
|
||||
"modular-css": "^0.27.1",
|
||||
"moment": "^2.13.0",
|
||||
"moment-timezone": "^0.5.4",
|
||||
"notifyjs": "^2.0.1",
|
||||
"novnc-node": "^0.5.3",
|
||||
"promise-toolbox": "^0.5.0",
|
||||
"random-password": "^0.1.2",
|
||||
"react": "^15.0.0",
|
||||
"react-addons-shallow-compare": "^15.1.0",
|
||||
"react-bootstrap-4": "^0.29.1",
|
||||
"react-chartist": "^0.10.1",
|
||||
"react-copy-to-clipboard": "^4.0.2",
|
||||
"react-debounce-input": "^2.4.0",
|
||||
"react-dnd": "^2.1.4",
|
||||
"react-dnd-html5-backend": "^2.1.2",
|
||||
"react-document-title": "^2.0.2",
|
||||
"react-dom": "^15.0.0",
|
||||
"react-dropzone": "^3.5.0",
|
||||
"react-intl": "^2.0.1",
|
||||
"react-key-handler": "^0.2.0",
|
||||
"react-notify": "^2.0.1",
|
||||
"react-redux": "^4.4.0",
|
||||
"react-router": "^3.0.0-alpha.1",
|
||||
"react-select": "^1.0.0-beta13",
|
||||
"react-sparklines": "^1.5.0",
|
||||
"react-virtualized": "^7.4.0",
|
||||
"readable-stream": "^2.0.6",
|
||||
"redux": "^3.3.1",
|
||||
"redux-devtools": "^3.1.1",
|
||||
"redux-devtools-dock-monitor": "^1.1.0",
|
||||
"redux-devtools-log-monitor": "^1.0.5",
|
||||
"redux-thunk": "^2.0.1",
|
||||
"reselect": "^2.2.1",
|
||||
"serve-static": "^1.10.2",
|
||||
"standard": "^8.0.0",
|
||||
"superagent": "^2.0.0",
|
||||
"tar-stream": "^1.5.2",
|
||||
"vinyl": "^1.1.1",
|
||||
"watchify": "^3.7.0",
|
||||
"xml2js": "^0.4.17",
|
||||
"xo-acl-resolver": "^0.2.1",
|
||||
"xo-lib": "^0.8.0",
|
||||
"xo-remote-parser": "^0.3"
|
||||
"@babel/register": "^7.0.0-beta.40",
|
||||
"babel-7-jest": "^21.3.2",
|
||||
"babel-eslint": "^8.1.2",
|
||||
"benchmark": "^2.1.4",
|
||||
"eslint": "^4.14.0",
|
||||
"eslint-config-standard": "^11.0.0-beta.0",
|
||||
"eslint-config-standard-jsx": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-node": "^6.0.0",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"eslint-plugin-react": "^7.6.1",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"exec-promise": "^0.7.0",
|
||||
"flow-bin": "^0.66.0",
|
||||
"globby": "^8.0.0",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^22.0.4",
|
||||
"lodash": "^4.17.4",
|
||||
"prettier": "^1.10.2",
|
||||
"promise-toolbox": "^0.9.5",
|
||||
"sorted-object": "^2.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"benchmarks": "./tools/run-benchmarks.js 'src/**/*.bench.js'",
|
||||
"build": "npm run build-indexes && NODE_ENV=production gulp build",
|
||||
"build-indexes": "./tools/generate-index src/common/intl/locales",
|
||||
"dev": "npm run build-indexes && gulp build server",
|
||||
"dev-test": "ava --watch",
|
||||
"lint": "standard",
|
||||
"posttest": "npm run lint",
|
||||
"prepublish": "npm run build",
|
||||
"test": "ava"
|
||||
"engines": {
|
||||
"yarn": "^1.2.1"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"babelify",
|
||||
"loose-envify"
|
||||
]
|
||||
},
|
||||
"ava": {
|
||||
"babel": "inherit",
|
||||
"files": [
|
||||
"src/**/*.spec.js"
|
||||
"jest": {
|
||||
"collectCoverage": true,
|
||||
"projects": [
|
||||
"<rootDir>",
|
||||
"<rootDir>/packages/xo-web"
|
||||
],
|
||||
"require": [
|
||||
"babel-register"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"env": {
|
||||
"production": {
|
||||
"plugins": [
|
||||
"transform-react-constant-elements",
|
||||
"transform-react-inline-elements"
|
||||
]
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"transform-decorators-legacy",
|
||||
"transform-runtime"
|
||||
"testEnvironment": "node",
|
||||
"testPathIgnorePatterns": [
|
||||
"/dist/",
|
||||
"/xo-vmdk-to-vhd/",
|
||||
"/xo-web/"
|
||||
],
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react",
|
||||
"stage-0"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"ghooks": {
|
||||
"commit-msg": "npm test"
|
||||
"testRegex": "\\.spec\\.js$",
|
||||
"transform": {
|
||||
"/@xen-orchestra/cron/.+\\.jsx?$": "babel-7-jest",
|
||||
"/packages/complex-matcher/.+\\.jsx?$": "babel-7-jest",
|
||||
"/packages/value-matcher/.+\\.jsx?$": "babel-7-jest",
|
||||
"/packages/xo-cli/.+\\.jsx?$": "babel-7-jest",
|
||||
"\\.jsx?$": "babel-jest"
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"dist"
|
||||
],
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "scripts/run-script --parallel build",
|
||||
"clean": "scripts/run-script --parallel clean",
|
||||
"dev": "scripts/run-script --parallel dev",
|
||||
"dev-test": "jest --bail --watch",
|
||||
"posttest": "scripts/run-script test",
|
||||
"precommit": "scripts/lint-staged",
|
||||
"prepare": "scripts/run-script prepare",
|
||||
"pretest": "eslint --ignore-path .gitignore .",
|
||||
"test": "jest && flow status"
|
||||
},
|
||||
"workspaces": [
|
||||
"@xen-orchestra/*",
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
|
||||
47
packages/complex-matcher/.babelrc.js
Normal file
47
packages/complex-matcher/.babelrc.js
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict'
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
const __PROD__ = NODE_ENV === 'production'
|
||||
const __TEST__ = NODE_ENV === 'test'
|
||||
|
||||
const pkg = require('./package')
|
||||
|
||||
const plugins = {
|
||||
lodash: {},
|
||||
}
|
||||
|
||||
const presets = {
|
||||
'@babel/preset-env': {
|
||||
debug: !__TEST__,
|
||||
loose: true,
|
||||
shippedProposals: true,
|
||||
targets: __PROD__
|
||||
? (() => {
|
||||
let node = (pkg.engines || {}).node
|
||||
if (node !== undefined) {
|
||||
const trimChars = '^=>~'
|
||||
while (trimChars.includes(node[0])) {
|
||||
node = node.slice(1)
|
||||
}
|
||||
return { node: node }
|
||||
}
|
||||
})()
|
||||
: { browsers: '', node: 'current' },
|
||||
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
|
||||
},
|
||||
}
|
||||
|
||||
Object.keys(pkg.devDependencies || {}).forEach(name => {
|
||||
if (!(name in presets) && /@babel\/plugin-.+/.test(name)) {
|
||||
plugins[name] = {}
|
||||
} else if (!(name in presets) && /@babel\/preset-.+/.test(name)) {
|
||||
presets[name] = {}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
comments: !__PROD__,
|
||||
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
|
||||
plugins: Object.keys(plugins).map(plugin => [plugin, plugins[plugin]]),
|
||||
presets: Object.keys(presets).map(preset => [preset, presets[preset]]),
|
||||
}
|
||||
24
packages/complex-matcher/.npmignore
Normal file
24
packages/complex-matcher/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
66
packages/complex-matcher/README.md
Normal file
66
packages/complex-matcher/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# complex-matcher [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> ${pkg.description}
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/complex-matcher):
|
||||
|
||||
```
|
||||
> npm install --save complex-matcher
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import * as CM from 'complex-matcher'
|
||||
|
||||
const characters = [
|
||||
{ name: 'Catwoman', costumeColor: 'black' },
|
||||
{ name: 'Superman', costumeColor: 'blue', hasCape: true },
|
||||
{ name: 'Wonder Woman', costumeColor: 'blue' },
|
||||
]
|
||||
|
||||
const predicate = CM.parse('costumeColor:blue hasCape?').createPredicate()
|
||||
|
||||
characters.filter(predicate)
|
||||
// [
|
||||
// { name: 'Superman', costumeColor: 'blue', hasCape: true },
|
||||
// ]
|
||||
|
||||
new CM.String('foo').createPredicate()
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> yarn
|
||||
|
||||
# Run the tests
|
||||
> yarn test
|
||||
|
||||
# Continuously compile
|
||||
> yarn dev
|
||||
|
||||
# Continuously run the tests
|
||||
> yarn dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> yarn build
|
||||
```
|
||||
|
||||
## 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 © [Vates SAS](https://vates.fr)
|
||||
48
packages/complex-matcher/package.json
Normal file
48
packages/complex-matcher/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "complex-matcher",
|
||||
"version": "0.2.1",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/complex-matcher",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@isonoe.net"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"browserslist": [
|
||||
">2%"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.0.0-beta.40",
|
||||
"@babel/core": "7.0.0-beta.40",
|
||||
"@babel/preset-env": "7.0.0-beta.40",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^5.1.1",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"clean": "rimraf dist/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "yarn run clean",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
parse,
|
||||
toString
|
||||
} from './'
|
||||
import {
|
||||
ast,
|
||||
pattern
|
||||
} from './index.fixtures'
|
||||
import { parse } from './'
|
||||
import { ast, pattern } from './index.fixtures'
|
||||
|
||||
export default ({ benchmark }) => {
|
||||
benchmark('parse', () => {
|
||||
@@ -13,6 +7,6 @@ export default ({ benchmark }) => {
|
||||
})
|
||||
|
||||
benchmark('toString', () => {
|
||||
ast::toString()
|
||||
ast.toString()
|
||||
})
|
||||
}
|
||||
15
packages/complex-matcher/src/index.fixtures.js
Normal file
15
packages/complex-matcher/src/index.fixtures.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as CM from './'
|
||||
|
||||
export const pattern =
|
||||
'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape? age:32'
|
||||
|
||||
export const ast = new CM.And([
|
||||
new CM.String('foo'),
|
||||
new CM.Not(new CM.String('\\ "')),
|
||||
new CM.Property(
|
||||
'name',
|
||||
new CM.Or([new CM.String('wonderwoman'), new CM.String('batman')])
|
||||
),
|
||||
new CM.TruthyProperty('hasCape'),
|
||||
new CM.Property('age', new CM.Number(32)),
|
||||
])
|
||||
501
packages/complex-matcher/src/index.js
Normal file
501
packages/complex-matcher/src/index.js
Normal file
@@ -0,0 +1,501 @@
|
||||
import { isPlainObject, some } from 'lodash'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const RAW_STRING_CHARS = (() => {
|
||||
const chars = { __proto__: null }
|
||||
const add = (a, b = a) => {
|
||||
let i = a.charCodeAt(0)
|
||||
const j = b.charCodeAt(0)
|
||||
while (i <= j) {
|
||||
chars[String.fromCharCode(i++)] = true
|
||||
}
|
||||
}
|
||||
add('$')
|
||||
add('-')
|
||||
add('.')
|
||||
add('0', '9')
|
||||
add('_')
|
||||
add('A', 'Z')
|
||||
add('a', 'z')
|
||||
return chars
|
||||
})()
|
||||
const isRawString = string => {
|
||||
const { length } = string
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (!(string[i] in RAW_STRING_CHARS)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
class Node {
|
||||
createPredicate () {
|
||||
return value => this.match(value)
|
||||
}
|
||||
}
|
||||
|
||||
export class Null extends Node {
|
||||
match () {
|
||||
return true
|
||||
}
|
||||
|
||||
toString () {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const formatTerms = terms => terms.map(term => term.toString(true)).join(' ')
|
||||
|
||||
export class And extends Node {
|
||||
constructor (children) {
|
||||
super()
|
||||
|
||||
if (children.length === 1) {
|
||||
return children[0]
|
||||
}
|
||||
this.children = children
|
||||
}
|
||||
|
||||
match (value) {
|
||||
return this.children.every(child => child.match(value))
|
||||
}
|
||||
|
||||
toString (isNested) {
|
||||
const terms = formatTerms(this.children)
|
||||
return isNested ? `(${terms})` : terms
|
||||
}
|
||||
}
|
||||
|
||||
export class Or extends Node {
|
||||
constructor (children) {
|
||||
super()
|
||||
|
||||
if (children.length === 1) {
|
||||
return children[0]
|
||||
}
|
||||
this.children = children
|
||||
}
|
||||
|
||||
match (value) {
|
||||
return this.children.some(child => child.match(value))
|
||||
}
|
||||
|
||||
toString () {
|
||||
return `|(${formatTerms(this.children)})`
|
||||
}
|
||||
}
|
||||
|
||||
export class Not extends Node {
|
||||
constructor (child) {
|
||||
super()
|
||||
|
||||
this.child = child
|
||||
}
|
||||
|
||||
match (value) {
|
||||
return !this.child.match(value)
|
||||
}
|
||||
|
||||
toString () {
|
||||
return '!' + this.child.toString(true)
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberNode extends Node {
|
||||
constructor (value) {
|
||||
super()
|
||||
|
||||
this.value = value
|
||||
|
||||
// should not be enumerable for the tests
|
||||
Object.defineProperty(this, 'match', {
|
||||
value: this.match.bind(this),
|
||||
})
|
||||
}
|
||||
|
||||
match (value) {
|
||||
return (
|
||||
value === this.value ||
|
||||
(value !== null && typeof value === 'object' && some(value, this.match))
|
||||
)
|
||||
}
|
||||
|
||||
toString () {
|
||||
return String(this.value)
|
||||
}
|
||||
}
|
||||
export { NumberNode as Number }
|
||||
|
||||
export class Property extends Node {
|
||||
constructor (name, child) {
|
||||
super()
|
||||
|
||||
this.name = name
|
||||
this.child = child
|
||||
}
|
||||
|
||||
match (value) {
|
||||
return value != null && this.child.match(value[this.name])
|
||||
}
|
||||
|
||||
toString () {
|
||||
return `${formatString(this.name)}:${this.child.toString(true)}`
|
||||
}
|
||||
}
|
||||
|
||||
const escapeChar = char => '\\' + char
|
||||
const formatString = value =>
|
||||
Number.isNaN(+value)
|
||||
? isRawString(value) ? value : `"${value.replace(/\\|"/g, escapeChar)}"`
|
||||
: `"${value}"`
|
||||
|
||||
export class StringNode extends Node {
|
||||
constructor (value) {
|
||||
super()
|
||||
|
||||
this.lcValue = value.toLowerCase()
|
||||
this.value = value
|
||||
|
||||
// should not be enumerable for the tests
|
||||
Object.defineProperty(this, 'match', {
|
||||
value: this.match.bind(this),
|
||||
})
|
||||
}
|
||||
|
||||
match (value) {
|
||||
if (typeof value === 'string') {
|
||||
return value.toLowerCase().indexOf(this.lcValue) !== -1
|
||||
}
|
||||
|
||||
if (Array.isArray(value) || isPlainObject(value)) {
|
||||
return some(value, this.match)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
toString () {
|
||||
return formatString(this.value)
|
||||
}
|
||||
}
|
||||
export { StringNode as String }
|
||||
|
||||
export class TruthyProperty extends Node {
|
||||
constructor (name) {
|
||||
super()
|
||||
|
||||
this.name = name
|
||||
}
|
||||
|
||||
match (value) {
|
||||
return value != null && !!value[this.name]
|
||||
}
|
||||
|
||||
toString () {
|
||||
return formatString(this.name) + '?'
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// https://gist.github.com/yelouafi/556e5159e869952335e01f6b473c4ec1
|
||||
|
||||
class Failure {
|
||||
constructor (pos, expected) {
|
||||
this.expected = expected
|
||||
this.pos = pos
|
||||
}
|
||||
|
||||
get value () {
|
||||
throw new Error(
|
||||
`parse error: expected ${this.expected} at position ${this.pos}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Success {
|
||||
constructor (pos, value) {
|
||||
this.pos = pos
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
class P {
|
||||
static alt (...parsers) {
|
||||
const { length } = parsers
|
||||
return new P((input, pos, end) => {
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const result = parsers[i]._parse(input, pos, end)
|
||||
if (result instanceof Success) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return new Failure(pos, 'alt')
|
||||
})
|
||||
}
|
||||
|
||||
static grammar (rules) {
|
||||
const grammar = {}
|
||||
Object.keys(rules).forEach(k => {
|
||||
const rule = rules[k]
|
||||
grammar[k] = rule instanceof P ? rule : P.lazy(rule, grammar)
|
||||
})
|
||||
return grammar
|
||||
}
|
||||
|
||||
static lazy (parserCreator, arg) {
|
||||
const parser = new P((input, pos, end) =>
|
||||
(parser._parse = parserCreator(arg)._parse)(input, pos, end)
|
||||
)
|
||||
return parser
|
||||
}
|
||||
|
||||
static regex (regex) {
|
||||
regex = new RegExp(regex.source, 'y')
|
||||
return new P((input, pos) => {
|
||||
regex.lastIndex = pos
|
||||
const matches = regex.exec(input)
|
||||
return matches !== null
|
||||
? new Success(regex.lastIndex, matches[0])
|
||||
: new Failure(pos, regex)
|
||||
})
|
||||
}
|
||||
|
||||
static seq (...parsers) {
|
||||
const { length } = parsers
|
||||
return new P((input, pos, end) => {
|
||||
const values = new Array(length)
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const result = parsers[i]._parse(input, pos, end)
|
||||
if (result instanceof Failure) {
|
||||
return result
|
||||
}
|
||||
pos = result.pos
|
||||
values[i] = result.value
|
||||
}
|
||||
return new Success(pos, values)
|
||||
})
|
||||
}
|
||||
|
||||
static text (text) {
|
||||
const { length } = text
|
||||
return new P(
|
||||
(input, pos) =>
|
||||
input.startsWith(text, pos)
|
||||
? new Success(pos + length, text)
|
||||
: new Failure(pos, `'${text}'`)
|
||||
)
|
||||
}
|
||||
|
||||
constructor (parse) {
|
||||
this._parse = parse
|
||||
}
|
||||
|
||||
map (fn) {
|
||||
return new P((input, pos, end) => {
|
||||
const result = this._parse(input, pos, end)
|
||||
if (result instanceof Success) {
|
||||
result.value = fn(result.value)
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
parse (input, pos = 0, end = input.length) {
|
||||
return this._parse(input, pos, end).value
|
||||
}
|
||||
|
||||
repeat (min = 0, max = Infinity) {
|
||||
return new P((input, pos, end) => {
|
||||
const value = []
|
||||
let result
|
||||
let i = 0
|
||||
while (i < min) {
|
||||
++i
|
||||
result = this._parse(input, pos, end)
|
||||
if (result instanceof Failure) {
|
||||
return result
|
||||
}
|
||||
value.push(result.value)
|
||||
pos = result.pos
|
||||
}
|
||||
while (
|
||||
i < max &&
|
||||
(result = this._parse(input, pos, end)) instanceof Success
|
||||
) {
|
||||
++i
|
||||
value.push(result.value)
|
||||
pos = result.pos
|
||||
}
|
||||
return new Success(pos, value)
|
||||
})
|
||||
}
|
||||
|
||||
skip (otherParser) {
|
||||
return new P((input, pos, end) => {
|
||||
const result = this._parse(input, pos, end)
|
||||
if (result instanceof Failure) {
|
||||
return result
|
||||
}
|
||||
const otherResult = otherParser._parse(input, result.pos, end)
|
||||
if (otherResult instanceof Failure) {
|
||||
return otherResult
|
||||
}
|
||||
result.pos = otherResult.pos
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
P.eof = new P(
|
||||
(input, pos, end) =>
|
||||
pos < end ? new Failure(pos, 'end of input') : new Success(pos)
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const parser = P.grammar({
|
||||
default: r =>
|
||||
P.seq(r.ws, r.term.repeat(), P.eof).map(
|
||||
([, terms]) => (terms.length === 0 ? new Null() : new And(terms))
|
||||
),
|
||||
quotedString: new P((input, pos, end) => {
|
||||
if (input[pos] !== '"') {
|
||||
return new Failure(pos, '"')
|
||||
}
|
||||
++pos
|
||||
|
||||
const value = []
|
||||
let char
|
||||
while (pos < end && (char = input[pos++]) !== '"') {
|
||||
if (char === '\\') {
|
||||
char = input[pos++]
|
||||
}
|
||||
value.push(char)
|
||||
}
|
||||
|
||||
return new Success(pos, value.join(''))
|
||||
}),
|
||||
rawString: new P((input, pos, end) => {
|
||||
let value = ''
|
||||
let c
|
||||
while (pos < end && RAW_STRING_CHARS[(c = input[pos])]) {
|
||||
++pos
|
||||
value += c
|
||||
}
|
||||
return value.length === 0
|
||||
? new Failure(pos, 'a raw string')
|
||||
: new Success(pos, value)
|
||||
}),
|
||||
string: r => P.alt(r.quotedString, r.rawString),
|
||||
term: r =>
|
||||
P.alt(
|
||||
P.seq(P.text('('), r.ws, r.term.repeat(1), P.text(')')).map(
|
||||
_ => new And(_[2])
|
||||
),
|
||||
P.seq(
|
||||
P.text('|'),
|
||||
r.ws,
|
||||
P.text('('),
|
||||
r.ws,
|
||||
r.term.repeat(1),
|
||||
P.text(')')
|
||||
).map(_ => new Or(_[4])),
|
||||
P.seq(P.text('!'), r.ws, r.term).map(_ => new Not(_[2])),
|
||||
P.seq(r.string, r.ws, P.text(':'), r.ws, r.term).map(
|
||||
_ => new Property(_[0], _[4])
|
||||
),
|
||||
P.seq(r.string, P.text('?')).map(_ => new TruthyProperty(_[0])),
|
||||
P.alt(
|
||||
r.quotedString.map(_ => new StringNode(_)),
|
||||
r.rawString.map(str => {
|
||||
const asNum = +str
|
||||
return Number.isNaN(asNum)
|
||||
? new StringNode(str)
|
||||
: new NumberNode(asNum)
|
||||
})
|
||||
)
|
||||
).skip(r.ws),
|
||||
ws: P.regex(/\s*/),
|
||||
}).default
|
||||
export const parse = parser.parse.bind(parser)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const _getPropertyClauseStrings = ({ child }) => {
|
||||
if (child instanceof Or) {
|
||||
const strings = []
|
||||
child.children.forEach(child => {
|
||||
if (child instanceof StringNode) {
|
||||
strings.push(child.value)
|
||||
}
|
||||
})
|
||||
return strings
|
||||
}
|
||||
|
||||
if (child instanceof StringNode) {
|
||||
return [child.value]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// Find possible values for property clauses in a and clause.
|
||||
export const getPropertyClausesStrings = node => {
|
||||
if (!node) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (node instanceof Property) {
|
||||
return {
|
||||
[node.name]: _getPropertyClauseStrings(node),
|
||||
}
|
||||
}
|
||||
|
||||
if (node instanceof And) {
|
||||
const strings = {}
|
||||
node.children.forEach(node => {
|
||||
if (node instanceof Property) {
|
||||
const { name } = node
|
||||
const values = strings[name]
|
||||
if (values) {
|
||||
values.push.apply(values, _getPropertyClauseStrings(node))
|
||||
} else {
|
||||
strings[name] = _getPropertyClauseStrings(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
return strings
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const setPropertyClause = (node, name, child) => {
|
||||
const property =
|
||||
child &&
|
||||
new Property(
|
||||
name,
|
||||
typeof child === 'string' ? new StringNode(child) : child
|
||||
)
|
||||
|
||||
if (node === undefined) {
|
||||
return property
|
||||
}
|
||||
|
||||
const children = (node instanceof And ? node.children : [node]).filter(
|
||||
child => !(child instanceof Property && child.name === name)
|
||||
)
|
||||
if (property !== undefined) {
|
||||
children.push(property)
|
||||
}
|
||||
return new And(children)
|
||||
}
|
||||
86
packages/complex-matcher/src/index.spec.js
Normal file
86
packages/complex-matcher/src/index.spec.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import { ast, pattern } from './index.fixtures'
|
||||
import {
|
||||
getPropertyClausesStrings,
|
||||
Null,
|
||||
NumberNode,
|
||||
parse,
|
||||
setPropertyClause,
|
||||
} from './'
|
||||
|
||||
it('getPropertyClausesStrings', () => {
|
||||
const tmp = getPropertyClausesStrings(parse('foo bar:baz baz:|(foo bar)'))
|
||||
expect(tmp).toEqual({
|
||||
bar: ['baz'],
|
||||
baz: ['foo', 'bar'],
|
||||
})
|
||||
})
|
||||
|
||||
describe('parse', () => {
|
||||
it('analyses a string and returns a node/tree', () => {
|
||||
expect(parse(pattern)).toEqual(ast)
|
||||
})
|
||||
|
||||
it('supports an empty string', () => {
|
||||
expect(parse('')).toEqual(new Null())
|
||||
})
|
||||
|
||||
it('differentiate between numbers and numbers in strings', () => {
|
||||
let node
|
||||
|
||||
node = parse('32')
|
||||
expect(node.match(32)).toBe(true)
|
||||
expect(node.match('32')).toBe(false)
|
||||
expect(node.toString()).toBe('32')
|
||||
|
||||
node = parse('"32"')
|
||||
expect(node.match(32)).toBe(false)
|
||||
expect(node.match('32')).toBe(true)
|
||||
expect(node.toString()).toBe('"32"')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Number', () => {
|
||||
it('match a number recursively', () => {
|
||||
expect(new NumberNode(3).match([{ foo: 3 }])).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setPropertyClause', () => {
|
||||
it('creates a node if none passed', () => {
|
||||
expect(setPropertyClause(undefined, 'foo', 'bar').toString()).toBe(
|
||||
'foo:bar'
|
||||
)
|
||||
})
|
||||
|
||||
it('adds a property clause if there was none', () => {
|
||||
expect(setPropertyClause(parse('baz'), 'foo', 'bar').toString()).toBe(
|
||||
'baz foo:bar'
|
||||
)
|
||||
})
|
||||
|
||||
it('replaces the property clause if there was one', () => {
|
||||
expect(
|
||||
setPropertyClause(parse('plip foo:baz plop'), 'foo', 'bar').toString()
|
||||
).toBe('plip plop foo:bar')
|
||||
|
||||
expect(
|
||||
setPropertyClause(parse('foo:|(baz plop)'), 'foo', 'bar').toString()
|
||||
).toBe('foo:bar')
|
||||
})
|
||||
|
||||
it('removes the property clause if no chid is passed', () => {
|
||||
expect(
|
||||
setPropertyClause(parse('foo bar:baz qux'), 'bar', undefined).toString()
|
||||
).toBe('foo qux')
|
||||
|
||||
expect(
|
||||
setPropertyClause(parse('foo bar:baz qux'), 'baz', undefined).toString()
|
||||
).toBe('foo bar:baz qux')
|
||||
})
|
||||
})
|
||||
|
||||
it('toString', () => {
|
||||
expect(pattern).toBe(ast.toString())
|
||||
})
|
||||
47
packages/value-matcher/.babelrc.js
Normal file
47
packages/value-matcher/.babelrc.js
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict'
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
const __PROD__ = NODE_ENV === 'production'
|
||||
const __TEST__ = NODE_ENV === 'test'
|
||||
|
||||
const pkg = require('./package')
|
||||
|
||||
const plugins = {
|
||||
lodash: {},
|
||||
}
|
||||
|
||||
const presets = {
|
||||
'@babel/preset-env': {
|
||||
debug: !__TEST__,
|
||||
loose: true,
|
||||
shippedProposals: true,
|
||||
targets: __PROD__
|
||||
? (() => {
|
||||
let node = (pkg.engines || {}).node
|
||||
if (node !== undefined) {
|
||||
const trimChars = '^=>~'
|
||||
while (trimChars.includes(node[0])) {
|
||||
node = node.slice(1)
|
||||
}
|
||||
return { node: node }
|
||||
}
|
||||
})()
|
||||
: { browsers: '', node: 'current' },
|
||||
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
|
||||
},
|
||||
}
|
||||
|
||||
Object.keys(pkg.devDependencies || {}).forEach(name => {
|
||||
if (!(name in presets) && /@babel\/plugin-.+/.test(name)) {
|
||||
plugins[name] = {}
|
||||
} else if (!(name in presets) && /@babel\/preset-.+/.test(name)) {
|
||||
presets[name] = {}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
comments: !__PROD__,
|
||||
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
|
||||
plugins: Object.keys(plugins).map(plugin => [plugin, plugins[plugin]]),
|
||||
presets: Object.keys(presets).map(preset => [preset, presets[preset]]),
|
||||
}
|
||||
24
packages/value-matcher/.npmignore
Normal file
24
packages/value-matcher/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
66
packages/value-matcher/README.md
Normal file
66
packages/value-matcher/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# value-matcher [](https://travis-ci.org/vatefr/xen-orchestra)
|
||||
|
||||
> ${pkg.description}
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/value-matcher):
|
||||
|
||||
```
|
||||
> npm install --save value-matcher
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { createPredicate } from 'value-matcher'
|
||||
|
||||
[
|
||||
{ user: 'sam', age: 65, active: false },
|
||||
{ user: 'barney', age: 36, active: true },
|
||||
{ user: 'fred', age: 40, active: false },
|
||||
].filter(createPredicate({
|
||||
__or: [
|
||||
{ user: 'sam' },
|
||||
{ active: true },
|
||||
],
|
||||
}))
|
||||
// [
|
||||
// { user: 'sam', age: 65, active: false },
|
||||
// { user: 'barney', age: 36, active: true },
|
||||
// ]
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> yarn
|
||||
|
||||
# Run the tests
|
||||
> yarn test
|
||||
|
||||
# Continuously compile
|
||||
> yarn dev
|
||||
|
||||
# Continuously run the tests
|
||||
> yarn dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> yarn build
|
||||
```
|
||||
|
||||
## 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 © [Vates SAS](https://vates.fr)
|
||||
47
packages/value-matcher/package.json
Normal file
47
packages/value-matcher/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "value-matcher",
|
||||
"version": "0.2.0",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/value-matcher",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@isonoe.net"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"browserslist": [
|
||||
">2%"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.0.0-beta.40",
|
||||
"@babel/core": "7.0.0-beta.40",
|
||||
"@babel/preset-env": "7.0.0-beta.40",
|
||||
"@babel/preset-flow": "7.0.0-beta.40",
|
||||
"cross-env": "^5.1.3",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"clean": "rimraf dist/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "yarn run clean",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepare": "yarn run build",
|
||||
"prepublishOnly": "yarn run build"
|
||||
}
|
||||
}
|
||||
81
packages/value-matcher/src/index.js
Normal file
81
packages/value-matcher/src/index.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// @flow
|
||||
|
||||
/* eslint-disable no-use-before-define */
|
||||
export type Pattern =
|
||||
| AndPattern
|
||||
| OrPattern
|
||||
| NotPattern
|
||||
| ObjectPattern
|
||||
| ArrayPattern
|
||||
| ValuePattern
|
||||
/* eslint-enable no-use-before-define */
|
||||
|
||||
// all patterns must match
|
||||
type AndPattern = {| __and: Array<Pattern> |}
|
||||
|
||||
// one of the pattern must match
|
||||
type OrPattern = {| __or: Array<Pattern> |}
|
||||
|
||||
// the pattern must not match
|
||||
type NotPattern = {| __not: Pattern |}
|
||||
|
||||
// value is an object with properties matching the patterns
|
||||
type ObjectPattern = { [string]: Pattern }
|
||||
|
||||
// value is an array and each patterns must match a different item
|
||||
type ArrayPattern = Array<Pattern>
|
||||
|
||||
// value equals the pattern
|
||||
type ValuePattern = boolean | number | string
|
||||
|
||||
const match = (pattern: Pattern, value: any) => {
|
||||
if (Array.isArray(pattern)) {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
pattern.every((subpattern, i) =>
|
||||
// FIXME: subpatterns should match different subvalues
|
||||
value.some(subvalue => match(subpattern, subvalue))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (pattern !== null && typeof pattern === 'object') {
|
||||
const keys = Object.keys(pattern)
|
||||
const { length } = keys
|
||||
|
||||
if (length === 1) {
|
||||
const [key] = keys
|
||||
if (key === '__and') {
|
||||
const andPattern: AndPattern = (pattern: any)
|
||||
return andPattern.__and.every(subpattern => match(subpattern, value))
|
||||
}
|
||||
if (key === '__or') {
|
||||
const orPattern: OrPattern = (pattern: any)
|
||||
return orPattern.__or.some(subpattern => match(subpattern, value))
|
||||
}
|
||||
if (key === '__not') {
|
||||
const notPattern: NotPattern = (pattern: any)
|
||||
return !match(notPattern.__not, value)
|
||||
}
|
||||
}
|
||||
|
||||
if (value === null || typeof value !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
const objectPattern: ObjectPattern = (pattern: any)
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const key = keys[i]
|
||||
const subvalue = value[key]
|
||||
if (subvalue === undefined || !match(objectPattern[key], subvalue)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return pattern === value
|
||||
}
|
||||
|
||||
export const createPredicate = (pattern: Pattern) => (value: any) =>
|
||||
match(pattern, value)
|
||||
24
packages/vhd-cli/.npmignore
Normal file
24
packages/vhd-cli/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
51
packages/vhd-cli/README.md
Normal file
51
packages/vhd-cli/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# vhd-cli [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> ${pkg.description}
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/vhd-cli):
|
||||
|
||||
```
|
||||
> npm install --global vhd-cli
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
> vhd-cli <VHD file>
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> npm install
|
||||
|
||||
# Run the tests
|
||||
> npm test
|
||||
|
||||
# Continuously compile
|
||||
> npm run dev
|
||||
|
||||
# Continuously run the tests
|
||||
> npm run dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> npm run build
|
||||
```
|
||||
|
||||
## 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 © [Vates SAS](https://vates.fr)
|
||||
67
packages/vhd-cli/package.json
Normal file
67
packages/vhd-cli/package.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "vhd-cli",
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/vhd-cli",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@isonoe.net"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {
|
||||
"vhd-cli": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nraynaud/struct-fu": "^1.0.1",
|
||||
"@nraynaud/xo-fs": "^0.0.5",
|
||||
"babel-runtime": "^6.22.0",
|
||||
"exec-promise": "^0.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.5.2",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.1.3",
|
||||
"rimraf": "^2.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"babel": {
|
||||
"plugins": [
|
||||
"lodash",
|
||||
"transform-runtime"
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
||||
}
|
||||
19
packages/vhd-cli/src/index.js
Executable file
19
packages/vhd-cli/src/index.js
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import execPromise from 'exec-promise'
|
||||
import { RemoteHandlerLocal } from '@nraynaud/xo-fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
import Vhd from './vhd'
|
||||
|
||||
execPromise(async args => {
|
||||
const vhd = new Vhd(
|
||||
new RemoteHandlerLocal({ url: 'file:///' }),
|
||||
resolve(args[0])
|
||||
)
|
||||
|
||||
await vhd.readHeaderAndFooter()
|
||||
|
||||
console.log(vhd._header)
|
||||
console.log(vhd._footer)
|
||||
})
|
||||
461
packages/vhd-cli/src/vhd.js
Normal file
461
packages/vhd-cli/src/vhd.js
Normal file
@@ -0,0 +1,461 @@
|
||||
import assert from 'assert'
|
||||
import fu from '@nraynaud/struct-fu'
|
||||
import { dirname } from 'path'
|
||||
|
||||
// ===================================================================
|
||||
//
|
||||
// Spec:
|
||||
// https://www.microsoft.com/en-us/download/details.aspx?id=23850
|
||||
//
|
||||
// C implementation:
|
||||
// https://github.com/rubiojr/vhd-util-convert
|
||||
//
|
||||
// ===================================================================
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
const HARD_DISK_TYPE_DIFFERENCING = 4
|
||||
const HARD_DISK_TYPE_DYNAMIC = 3
|
||||
const HARD_DISK_TYPE_FIXED = 2
|
||||
const PLATFORM_CODE_NONE = 0
|
||||
export const SECTOR_SIZE = 512
|
||||
|
||||
/* eslint-enable no-unused vars */
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const fuFooter = fu.struct([
|
||||
fu.char('cookie', 8), // 0
|
||||
fu.uint32('features'), // 8
|
||||
fu.uint32('fileFormatVersion'), // 12
|
||||
fu.struct('dataOffset', [
|
||||
fu.uint32('high'), // 16
|
||||
fu.uint32('low'), // 20
|
||||
]),
|
||||
fu.uint32('timestamp'), // 24
|
||||
fu.char('creatorApplication', 4), // 28
|
||||
fu.uint32('creatorVersion'), // 32
|
||||
fu.uint32('creatorHostOs'), // 36
|
||||
fu.struct('originalSize', [
|
||||
// At the creation, current size of the hard disk.
|
||||
fu.uint32('high'), // 40
|
||||
fu.uint32('low'), // 44
|
||||
]),
|
||||
fu.struct('currentSize', [
|
||||
// Current size of the virtual disk. At the creation: currentSize = originalSize.
|
||||
fu.uint32('high'), // 48
|
||||
fu.uint32('low'), // 52
|
||||
]),
|
||||
fu.struct('diskGeometry', [
|
||||
fu.uint16('cylinders'), // 56
|
||||
fu.uint8('heads'), // 58
|
||||
fu.uint8('sectorsPerTrackCylinder'), // 59
|
||||
]),
|
||||
fu.uint32('diskType'), // 60 Disk type, must be equal to HARD_DISK_TYPE_DYNAMIC/HARD_DISK_TYPE_DIFFERENCING.
|
||||
fu.uint32('checksum'), // 64
|
||||
fu.uint8('uuid', 16), // 68
|
||||
fu.char('saved'), // 84
|
||||
fu.char('hidden'), // 85
|
||||
fu.byte('reserved', 426), // 86
|
||||
])
|
||||
const FOOTER_SIZE = fuFooter.size
|
||||
|
||||
const fuHeader = fu.struct([
|
||||
fu.char('cookie', 8),
|
||||
fu.struct('dataOffset', [fu.uint32('high'), fu.uint32('low')]),
|
||||
fu.struct('tableOffset', [
|
||||
// Absolute byte offset of the Block Allocation Table.
|
||||
fu.uint32('high'),
|
||||
fu.uint32('low'),
|
||||
]),
|
||||
fu.uint32('headerVersion'),
|
||||
fu.uint32('maxTableEntries'), // Max entries in the Block Allocation Table.
|
||||
fu.uint32('blockSize'), // Block size (without bitmap) in bytes.
|
||||
fu.uint32('checksum'),
|
||||
fu.uint8('parentUuid', 16),
|
||||
fu.uint32('parentTimestamp'),
|
||||
fu.byte('reserved1', 4),
|
||||
fu.char16be('parentUnicodeName', 512),
|
||||
fu.struct(
|
||||
'parentLocatorEntry',
|
||||
[
|
||||
fu.uint32('platformCode'),
|
||||
fu.uint32('platformDataSpace'),
|
||||
fu.uint32('platformDataLength'),
|
||||
fu.uint32('reserved'),
|
||||
fu.struct('platformDataOffset', [
|
||||
// Absolute byte offset of the locator data.
|
||||
fu.uint32('high'),
|
||||
fu.uint32('low'),
|
||||
]),
|
||||
],
|
||||
8
|
||||
),
|
||||
fu.byte('reserved2', 256),
|
||||
])
|
||||
const HEADER_SIZE = fuHeader.size
|
||||
|
||||
// ===================================================================
|
||||
// Helpers
|
||||
// ===================================================================
|
||||
|
||||
const SIZE_OF_32_BITS = Math.pow(2, 32)
|
||||
const uint32ToUint64 = fu => fu.high * SIZE_OF_32_BITS + fu.low
|
||||
|
||||
// Returns a 32 bits integer corresponding to a Vhd version.
|
||||
const getVhdVersion = (major, minor) => (major << 16) | (minor & 0x0000ffff)
|
||||
|
||||
// bytes[] bit manipulation
|
||||
const testBit = (map, bit) => map[bit >> 3] & (1 << (bit & 7))
|
||||
const setBit = (map, bit) => {
|
||||
map[bit >> 3] |= 1 << (bit & 7)
|
||||
}
|
||||
const unsetBit = (map, bit) => {
|
||||
map[bit >> 3] &= ~(1 << (bit & 7))
|
||||
}
|
||||
|
||||
const addOffsets = (...offsets) =>
|
||||
offsets.reduce(
|
||||
(a, b) =>
|
||||
b == null
|
||||
? a
|
||||
: typeof b === 'object'
|
||||
? { bytes: a.bytes + b.bytes, bits: a.bits + b.bits }
|
||||
: { bytes: a.bytes + b, bits: a.bits },
|
||||
{ bytes: 0, bits: 0 }
|
||||
)
|
||||
|
||||
const pack = (field, value, buf, offset) => {
|
||||
field.pack(value, buf, addOffsets(field.offset, offset))
|
||||
}
|
||||
|
||||
const unpack = (field, buf, offset) =>
|
||||
field.unpack(buf, addOffsets(field.offset, offset))
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const streamToNewBuffer = stream =>
|
||||
new Promise((resolve, reject) => {
|
||||
const chunks = []
|
||||
let length = 0
|
||||
|
||||
const onData = chunk => {
|
||||
chunks.push(chunk)
|
||||
length += chunk.length
|
||||
}
|
||||
stream.on('data', onData)
|
||||
|
||||
const clean = () => {
|
||||
stream.removeListener('data', onData)
|
||||
stream.removeListener('end', onEnd)
|
||||
stream.removeListener('error', onError)
|
||||
}
|
||||
const onEnd = () => {
|
||||
resolve(Buffer.concat(chunks, length))
|
||||
clean()
|
||||
}
|
||||
stream.on('end', onEnd)
|
||||
const onError = error => {
|
||||
reject(error)
|
||||
clean()
|
||||
}
|
||||
stream.on('error', onError)
|
||||
})
|
||||
|
||||
const streamToExistingBuffer = (
|
||||
stream,
|
||||
buffer,
|
||||
offset = 0,
|
||||
end = buffer.length
|
||||
) =>
|
||||
new Promise((resolve, reject) => {
|
||||
assert(offset >= 0)
|
||||
assert(end > offset)
|
||||
assert(end <= buffer.length)
|
||||
|
||||
let i = offset
|
||||
|
||||
const onData = chunk => {
|
||||
const prev = i
|
||||
i += chunk.length
|
||||
|
||||
if (i > end) {
|
||||
return onError(new Error('too much data'))
|
||||
}
|
||||
|
||||
chunk.copy(buffer, prev)
|
||||
}
|
||||
stream.on('data', onData)
|
||||
|
||||
const clean = () => {
|
||||
stream.removeListener('data', onData)
|
||||
stream.removeListener('end', onEnd)
|
||||
stream.removeListener('error', onError)
|
||||
}
|
||||
const onEnd = () => {
|
||||
resolve(i - offset)
|
||||
clean()
|
||||
}
|
||||
stream.on('end', onEnd)
|
||||
const onError = error => {
|
||||
reject(error)
|
||||
clean()
|
||||
}
|
||||
stream.on('error', onError)
|
||||
})
|
||||
|
||||
// ===================================================================
|
||||
|
||||
// Returns the checksum of a raw struct.
|
||||
const computeChecksum = (struct, buf, offset = 0) => {
|
||||
let sum = 0
|
||||
|
||||
// Do not use the stored checksum to compute the new checksum.
|
||||
const checksumField = struct.fields.checksum
|
||||
const checksumOffset = offset + checksumField.offset
|
||||
for (let i = offset, n = checksumOffset; i < n; ++i) {
|
||||
sum += buf[i]
|
||||
}
|
||||
for (
|
||||
let i = checksumOffset + checksumField.size, n = offset + struct.size;
|
||||
i < n;
|
||||
++i
|
||||
) {
|
||||
sum += buf[i]
|
||||
}
|
||||
|
||||
return ~sum >>> 0
|
||||
}
|
||||
|
||||
const verifyChecksum = (struct, buf, offset) =>
|
||||
unpack(struct.fields.checksum, buf, offset) ===
|
||||
computeChecksum(struct, buf, offset)
|
||||
|
||||
const getParentLocatorSize = parentLocatorEntry => {
|
||||
const { platformDataSpace } = parentLocatorEntry
|
||||
|
||||
if (platformDataSpace < SECTOR_SIZE) {
|
||||
return platformDataSpace * SECTOR_SIZE
|
||||
}
|
||||
|
||||
return platformDataSpace % SECTOR_SIZE === 0 ? platformDataSpace : 0
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
// Euclidean division, returns the quotient and the remainder of a / b.
|
||||
const div = (a, b) => [Math.floor(a / b), a % b]
|
||||
|
||||
export default class Vhd {
|
||||
constructor (handler, path) {
|
||||
this._handler = handler
|
||||
this._path = path
|
||||
|
||||
this._blockAllocationTable = null
|
||||
this._blockBitmapSize = null
|
||||
this._footer = null
|
||||
this._header = null
|
||||
this._parent = null
|
||||
this._sectorsPerBlock = null
|
||||
}
|
||||
|
||||
// Read `length` bytes starting from `begin`.
|
||||
//
|
||||
// - if `buffer`: it is filled starting from `offset`, and the
|
||||
// number of written bytes is returned;
|
||||
// - otherwise: a new buffer is allocated and returned.
|
||||
_read (begin, length, buf, offset) {
|
||||
assert(begin >= 0)
|
||||
assert(length > 0)
|
||||
|
||||
return this._handler
|
||||
.createReadStream(this._path, {
|
||||
end: begin + length - 1,
|
||||
start: begin,
|
||||
})
|
||||
.then(
|
||||
buf
|
||||
? stream =>
|
||||
streamToExistingBuffer(
|
||||
stream,
|
||||
buf,
|
||||
offset,
|
||||
(offset || 0) + length
|
||||
)
|
||||
: streamToNewBuffer
|
||||
)
|
||||
}
|
||||
|
||||
// - if `buffer`: it is filled with 0 starting from `offset`, and
|
||||
// the number of written bytes is returned;
|
||||
// - otherwise: a new buffer is allocated and returned.
|
||||
_zeroes (length, buf, offset = 0) {
|
||||
if (buf) {
|
||||
assert(offset >= 0)
|
||||
assert(length > 0)
|
||||
|
||||
const end = offset + length
|
||||
assert(end <= buf.length)
|
||||
|
||||
buf.fill(0, offset, end)
|
||||
return Promise.resolve(length)
|
||||
}
|
||||
|
||||
return Promise.resolve(Buffer.alloc(length))
|
||||
}
|
||||
|
||||
// Return the position of a block in the VHD or undefined if not found.
|
||||
_getBlockAddress (block) {
|
||||
assert(block >= 0)
|
||||
assert(block < this._header.maxTableEntries)
|
||||
|
||||
const blockAddr = this._blockAllocationTable[block]
|
||||
if (blockAddr !== 0xffffffff) {
|
||||
return blockAddr * SECTOR_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
async readHeaderAndFooter () {
|
||||
const buf = await this._read(0, FOOTER_SIZE + HEADER_SIZE)
|
||||
|
||||
if (!verifyChecksum(fuFooter, buf)) {
|
||||
throw new Error('footer checksum does not match')
|
||||
}
|
||||
|
||||
if (!verifyChecksum(fuHeader, buf, FOOTER_SIZE)) {
|
||||
throw new Error('header checksum does not match')
|
||||
}
|
||||
|
||||
return this._initMetadata(
|
||||
unpack(fuHeader, buf, FOOTER_SIZE),
|
||||
unpack(fuFooter, buf)
|
||||
)
|
||||
}
|
||||
|
||||
async _initMetadata (header, footer) {
|
||||
const sectorsPerBlock = header.blockSize / SECTOR_SIZE
|
||||
assert(sectorsPerBlock % 1 === 0)
|
||||
|
||||
// 1 bit per sector, rounded up to full sectors
|
||||
this._blockBitmapSize =
|
||||
Math.ceil(sectorsPerBlock / 8 / SECTOR_SIZE) * SECTOR_SIZE
|
||||
assert(this._blockBitmapSize === SECTOR_SIZE)
|
||||
|
||||
this._footer = footer
|
||||
this._header = header
|
||||
this.size = uint32ToUint64(this._footer.currentSize)
|
||||
|
||||
if (footer.diskType === HARD_DISK_TYPE_DIFFERENCING) {
|
||||
const parent = new Vhd(
|
||||
this._handler,
|
||||
`${dirname(this._path)}/${header.parentUnicodeName}`
|
||||
)
|
||||
await parent.readHeaderAndFooter()
|
||||
await parent.readBlockAllocationTable()
|
||||
|
||||
this._parent = parent
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
async readBlockAllocationTable () {
|
||||
const { maxTableEntries, tableOffset } = this._header
|
||||
const fuTable = fu.uint32(maxTableEntries)
|
||||
|
||||
this._blockAllocationTable = unpack(
|
||||
fuTable,
|
||||
await this._read(uint32ToUint64(tableOffset), fuTable.size)
|
||||
)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// read a single sector in a block
|
||||
async _readBlockSector (block, sector, begin, length, buf, offset) {
|
||||
assert(begin >= 0)
|
||||
assert(length > 0)
|
||||
assert(begin + length <= SECTOR_SIZE)
|
||||
|
||||
const blockAddr = this._getBlockAddress(block)
|
||||
const blockBitmapSize = this._blockBitmapSize
|
||||
const parent = this._parent
|
||||
|
||||
if (
|
||||
blockAddr &&
|
||||
(!parent || testBit(await this._read(blockAddr, blockBitmapSize), sector))
|
||||
) {
|
||||
return this._read(
|
||||
blockAddr + blockBitmapSize + sector * SECTOR_SIZE + begin,
|
||||
length,
|
||||
buf,
|
||||
offset
|
||||
)
|
||||
}
|
||||
|
||||
return parent
|
||||
? parent._readBlockSector(block, sector, begin, length, buf, offset)
|
||||
: this._zeroes(length, buf, offset)
|
||||
}
|
||||
|
||||
_readBlock (block, begin, length, buf, offset) {
|
||||
assert(begin >= 0)
|
||||
assert(length > 0)
|
||||
|
||||
const { blockSize } = this._header
|
||||
assert(begin + length <= blockSize)
|
||||
|
||||
const blockAddr = this._getBlockAddress(block)
|
||||
const parent = this._parent
|
||||
|
||||
if (!blockAddr) {
|
||||
return parent
|
||||
? parent._readBlock(block, begin, length, buf, offset)
|
||||
: this._zeroes(length, buf, offset)
|
||||
}
|
||||
|
||||
if (!parent) {
|
||||
return this._read(
|
||||
blockAddr + this._blockBitmapSize + begin,
|
||||
length,
|
||||
buf,
|
||||
offset
|
||||
)
|
||||
}
|
||||
|
||||
// FIXME: we should read as many sectors in a single pass as
|
||||
// possible for maximum perf.
|
||||
const [sector, beginInSector] = div(begin, SECTOR_SIZE)
|
||||
return this._readBlockSector(
|
||||
block,
|
||||
sector,
|
||||
beginInSector,
|
||||
Math.min(length, SECTOR_SIZE - beginInSector),
|
||||
buf,
|
||||
offset
|
||||
)
|
||||
}
|
||||
|
||||
read (buf, begin, length = buf.length, offset) {
|
||||
assert(Buffer.isBuffer(buf))
|
||||
assert(begin >= 0)
|
||||
|
||||
const { size } = this
|
||||
if (begin >= size) {
|
||||
return Promise.resolve(0)
|
||||
}
|
||||
|
||||
const { blockSize } = this._header
|
||||
const [block, beginInBlock] = div(begin, blockSize)
|
||||
|
||||
return this._readBlock(
|
||||
block,
|
||||
beginInBlock,
|
||||
Math.min(length, blockSize - beginInBlock, size - begin),
|
||||
buf,
|
||||
offset
|
||||
)
|
||||
}
|
||||
}
|
||||
24
packages/xen-api/.npmignore
Normal file
24
packages/xen-api/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
130
packages/xen-api/README.md
Normal file
130
packages/xen-api/README.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# xen-api [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> Connector to the Xen API
|
||||
|
||||
Tested with:
|
||||
|
||||
- XenServer 7.3
|
||||
- XenServer 7.2
|
||||
- XenServer 7.1
|
||||
- XenServer 7
|
||||
- XenServer 6.5
|
||||
- XenServer 6.2
|
||||
- XenServer 5.6
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/xen-api):
|
||||
|
||||
```
|
||||
> npm install --save xen-api
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Library
|
||||
|
||||
```javascript
|
||||
const { createClient } = require('xen-api')
|
||||
|
||||
const xapi = createClient({
|
||||
url: 'https://xen1.company.net',
|
||||
allowUnauthorized: false,
|
||||
auth: {
|
||||
user: 'root',
|
||||
password: 'important secret password'
|
||||
},
|
||||
readOnly: false
|
||||
})
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `url`: address of a host in the pool we are trying to connect to
|
||||
- `allowUnauthorized`: whether to accept self-signed certificates
|
||||
- `auth`: credentials used to sign in (can also be specified in the URL)
|
||||
- `readOnly = false`: if true, no methods with side-effects can be called
|
||||
|
||||
```js
|
||||
// Force connection.
|
||||
xapi.connect().catch(error => {
|
||||
console.error(error)
|
||||
})
|
||||
|
||||
// Watch objects.
|
||||
xapi.objects.on('add', objects => {
|
||||
console.log('new objects:', objects)
|
||||
})
|
||||
```
|
||||
|
||||
> Note: all objects are frozen and cannot be altered!
|
||||
|
||||
Custom fields on objects (hidden − ie. non enumerable):
|
||||
- `$type`: the type of the object (`VM`, `task`, …);
|
||||
- `$ref`: the (opaque) reference of the object;
|
||||
- `$id`: the identifier of this object (its UUID if any, otherwise its reference);
|
||||
- `$pool`: the pool object this object belongs to.
|
||||
|
||||
Furthermore, any field containing a reference (or references if an
|
||||
array) can be resolved by prepending the field name with a `$`:
|
||||
|
||||
```javascript
|
||||
console.log(xapi.pool.$master.$resident_VMs[0].name_label)
|
||||
// vm1
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
A CLI is provided to help exploration and discovery of the XAPI.
|
||||
|
||||
```
|
||||
> xen-api https://xen1.company.net root
|
||||
Password: ******
|
||||
root@xen1.company.net> xapi.status
|
||||
'connected'
|
||||
root@xen1.company.net> xapi.pool.master
|
||||
'OpaqueRef:ec7c5147-8aee-990f-c70b-0de916a8e993'
|
||||
root@xen1.company.net> xapi.pool.$master.name_label
|
||||
'xen1'
|
||||
```
|
||||
|
||||
To ease searches, `find()` and `findAll()` functions are available:
|
||||
|
||||
```
|
||||
root@xen1.company.net> findAll({ $type: 'vm' }).length
|
||||
183
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> npm install
|
||||
|
||||
# Run the tests
|
||||
> npm test
|
||||
|
||||
# Continuously compile
|
||||
> npm run dev
|
||||
|
||||
# Continuously run the tests
|
||||
> npm run dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> npm run build
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are *very* welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/xen-api/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
ISC © [Julien Fontanet](https://github.com/julien-f)
|
||||
50
packages/xen-api/examples/export-vdi
Executable file
50
packages/xen-api/examples/export-vdi
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.env.DEBUG = '*'
|
||||
|
||||
const defer = require('golike-defer').default
|
||||
const pump = require('pump')
|
||||
const { fromCallback } = require('promise-toolbox')
|
||||
|
||||
const { createClient } = require('../')
|
||||
|
||||
const { createOutputStream, resolveRef } = require('./utils')
|
||||
|
||||
defer(async ($defer, args) => {
|
||||
let raw = false
|
||||
if (args[0] === '--raw') {
|
||||
raw = true
|
||||
args.shift()
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
return console.log('Usage: export-vdi [--raw] <XS URL> <VDI identifier> [<VHD file>]')
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: args[0],
|
||||
watchEvents: false
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
// https://xapi-project.github.io/xen-api/snapshots.html#downloading-a-disk-or-snapshot
|
||||
const exportStream = await xapi.getResource('/export_raw_vdi/', {
|
||||
query: {
|
||||
format: raw ? 'raw' : 'vhd',
|
||||
vdi: await resolveRef(xapi, 'VDI', args[1])
|
||||
}
|
||||
})
|
||||
|
||||
console.warn('Export task:', exportStream.headers['task-id'])
|
||||
|
||||
await fromCallback(cb => pump(
|
||||
exportStream,
|
||||
createOutputStream(args[2]),
|
||||
cb
|
||||
))
|
||||
})(process.argv.slice(2)).catch(
|
||||
console.error.bind(console, 'error')
|
||||
)
|
||||
44
packages/xen-api/examples/export-vm
Executable file
44
packages/xen-api/examples/export-vm
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.env.DEBUG = '*'
|
||||
|
||||
const defer = require('golike-defer').default
|
||||
const pump = require('pump')
|
||||
const { fromCallback } = require('promise-toolbox')
|
||||
|
||||
const { createClient } = require('../')
|
||||
|
||||
const { createOutputStream, resolveRef } = require('./utils')
|
||||
|
||||
defer(async ($defer, args) => {
|
||||
if (args.length < 2) {
|
||||
return console.log('Usage: export-vm <XS URL> <VM identifier> [<XVA file>]')
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: args[0],
|
||||
watchEvents: false
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
// https://xapi-project.github.io/xen-api/importexport.html
|
||||
const exportStream = await xapi.getResource('/export/', {
|
||||
query: {
|
||||
ref: await resolveRef(xapi, 'VM', args[1]),
|
||||
use_compression: 'true'
|
||||
}
|
||||
})
|
||||
|
||||
console.warn('Export task:', exportStream.headers['task-id'])
|
||||
|
||||
await fromCallback(cb => pump(
|
||||
exportStream,
|
||||
createOutputStream(args[2]),
|
||||
cb
|
||||
))
|
||||
})(process.argv.slice(2)).catch(
|
||||
console.error.bind(console, 'error')
|
||||
)
|
||||
40
packages/xen-api/examples/import-vdi
Executable file
40
packages/xen-api/examples/import-vdi
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.env.DEBUG = '*'
|
||||
|
||||
const defer = require('golike-defer').default
|
||||
|
||||
const { createClient } = require('../')
|
||||
|
||||
const { createInputStream, resolveRef } = require('./utils')
|
||||
|
||||
defer(async ($defer, args) => {
|
||||
let raw = false
|
||||
if (args[0] === '--raw') {
|
||||
raw = true
|
||||
args.shift()
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
return console.log('Usage: import-vdi [--raw] <XS URL> <VDI identifier> [<VHD file>]')
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: args[0],
|
||||
watchEvents: false
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
// https://xapi-project.github.io/xen-api/snapshots.html#uploading-a-disk-or-snapshot
|
||||
await xapi.putResource(createInputStream(args[2]), '/import_raw_vdi/', {
|
||||
query: {
|
||||
format: raw ? 'raw' : 'vhd',
|
||||
vdi: await resolveRef(xapi, 'VDI', args[1])
|
||||
}
|
||||
})
|
||||
})(process.argv.slice(2)).catch(
|
||||
console.error.bind(console, 'error')
|
||||
)
|
||||
31
packages/xen-api/examples/import-vm
Executable file
31
packages/xen-api/examples/import-vm
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.env.DEBUG = '*'
|
||||
|
||||
const defer = require('golike-defer').default
|
||||
|
||||
const { createClient } = require('../')
|
||||
|
||||
const { createInputStream, resolveRef } = require('./utils')
|
||||
|
||||
defer(async ($defer, args) => {
|
||||
if (args.length < 1) {
|
||||
return console.log('Usage: import-vm <XS URL> [<XVA file>] [<SR identifier>]')
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: args[0],
|
||||
watchEvents: false
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
// https://xapi-project.github.io/xen-api/importexport.html
|
||||
await xapi.putResource(createInputStream(args[1]), '/import/', {
|
||||
query: args[2] && { sr_id: await resolveRef(xapi, 'SR', args[2]) }
|
||||
})
|
||||
})(process.argv.slice(2)).catch(
|
||||
console.error.bind(console, 'error')
|
||||
)
|
||||
57
packages/xen-api/examples/log-events
Executable file
57
packages/xen-api/examples/log-events
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('source-map-support').install()
|
||||
|
||||
const { forEach, size } = require('lodash')
|
||||
|
||||
const { createClient } = require('../')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
return console.log('Usage: log-events <XS URL>')
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Creation
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: process.argv[2]
|
||||
})
|
||||
|
||||
// ===================================================================
|
||||
// Method call
|
||||
|
||||
xapi.connect().then(() => {
|
||||
xapi.call('VM.get_all_records')
|
||||
.then(function (vms) {
|
||||
console.log('%s VMs fetched', size(vms))
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error(error)
|
||||
})
|
||||
})
|
||||
|
||||
// ===================================================================
|
||||
// Objects
|
||||
|
||||
const objects = xapi.objects
|
||||
|
||||
objects.on('add', objects => {
|
||||
forEach(objects, object => {
|
||||
console.log('+ %s: %s', object.$type, object.$id)
|
||||
})
|
||||
})
|
||||
|
||||
objects.on('update', objects => {
|
||||
forEach(objects, object => {
|
||||
console.log('± %s: %s', object.$type, object.$id)
|
||||
})
|
||||
})
|
||||
|
||||
objects.on('remove', objects => {
|
||||
forEach(objects, (value, id) => {
|
||||
console.log('- %s', id)
|
||||
})
|
||||
})
|
||||
6
packages/xen-api/examples/package.json
Normal file
6
packages/xen-api/examples/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"golike-defer": "^0.1.0",
|
||||
"pump": "^1.0.2"
|
||||
}
|
||||
}
|
||||
43
packages/xen-api/examples/utils.js
Normal file
43
packages/xen-api/examples/utils.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { createReadStream, createWriteStream, statSync } = require('fs')
|
||||
const { PassThrough } = require('stream')
|
||||
|
||||
const { isOpaqueRef } = require('../')
|
||||
|
||||
exports.createInputStream = path => {
|
||||
if (path === undefined || path === '-') {
|
||||
return process.stdin
|
||||
}
|
||||
|
||||
const { size } = statSync(path)
|
||||
|
||||
const stream = createReadStream(path)
|
||||
stream.length = size
|
||||
return stream
|
||||
}
|
||||
|
||||
exports.createOutputStream = path => {
|
||||
if (path !== undefined && path !== '-') {
|
||||
return createWriteStream(path)
|
||||
}
|
||||
|
||||
// introduce a through stream because stdout is not a normal stream!
|
||||
const stream = new PassThrough()
|
||||
stream.pipe(process.stdout)
|
||||
return stream
|
||||
}
|
||||
|
||||
exports.resolveRef = (xapi, type, refOrUuidOrNameLabel) =>
|
||||
isOpaqueRef(refOrUuidOrNameLabel)
|
||||
? refOrUuidOrNameLabel
|
||||
: xapi.call(`${type}.get_by_uuid`, refOrUuidOrNameLabel).catch(() =>
|
||||
xapi
|
||||
.call(`${type}.get_by_name_label`, refOrUuidOrNameLabel)
|
||||
.then(refs => {
|
||||
if (refs.length === 1) {
|
||||
return refs[0]
|
||||
}
|
||||
throw new Error(
|
||||
`no single match for ${type} with name label ${refOrUuidOrNameLabel}`
|
||||
)
|
||||
})
|
||||
)
|
||||
4
packages/xen-api/memory-test.gnu
Normal file
4
packages/xen-api/memory-test.gnu
Normal file
@@ -0,0 +1,4 @@
|
||||
set yrange [ 0 : ]
|
||||
set grid
|
||||
|
||||
plot for [i=2:4] "plot.dat" using 1:i with lines
|
||||
89
packages/xen-api/package.json
Normal file
89
packages/xen-api/package.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "xen-api",
|
||||
"version": "0.16.6",
|
||||
"license": "ISC",
|
||||
"description": "Connector to the Xen API",
|
||||
"keywords": [
|
||||
"xen",
|
||||
"api",
|
||||
"xen-api",
|
||||
"xenapi",
|
||||
"xapi"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xen-api",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@vates.fr"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {
|
||||
"xen-api": "dist/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"blocked": "^1.2.1",
|
||||
"debug": "^3.1.0",
|
||||
"event-to-promise": "^0.8.0",
|
||||
"exec-promise": "^0.7.0",
|
||||
"http-request-plus": "^0.5.0",
|
||||
"iterable-backoff": "^0.0.0",
|
||||
"json-rpc-protocol": "^0.11.2",
|
||||
"kindof": "^2.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
"make-error": "^1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"ms": "^2.1.1",
|
||||
"promise-toolbox": "^0.9.5",
|
||||
"pw": "0.0.4",
|
||||
"xmlrpc": "^1.3.2",
|
||||
"xo-collection": "^0.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-plugin-transform-function-bind": "^6.22.0",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.1.3",
|
||||
"rimraf": "^2.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"plot": "gnuplot -p memory-test.gnu",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"babel": {
|
||||
"plugins": [
|
||||
"lodash",
|
||||
"transform-decorators-legacy",
|
||||
"transform-function-bind"
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
||||
}
|
||||
106
packages/xen-api/src/cli.js
Executable file
106
packages/xen-api/src/cli.js
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import 'babel-polyfill'
|
||||
|
||||
import blocked from 'blocked'
|
||||
import createDebug from 'debug'
|
||||
import eventToPromise from 'event-to-promise'
|
||||
import execPromise from 'exec-promise'
|
||||
import minimist from 'minimist'
|
||||
import pw from 'pw'
|
||||
import { asCallback, fromCallback } from 'promise-toolbox'
|
||||
import { filter, find, isArray } from 'lodash'
|
||||
import { start as createRepl } from 'repl'
|
||||
|
||||
import { createClient } from './'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
function askPassword (prompt = 'Password: ') {
|
||||
if (prompt) {
|
||||
process.stdout.write(prompt)
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
pw(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const usage = 'Usage: xen-api <url> [<user> [<password>]]'
|
||||
|
||||
const main = async args => {
|
||||
const opts = minimist(args, {
|
||||
boolean: ['allow-unauthorized', 'help', 'read-only', 'verbose'],
|
||||
|
||||
alias: {
|
||||
'allow-unauthorized': 'au',
|
||||
debounce: 'd',
|
||||
help: 'h',
|
||||
'read-only': 'ro',
|
||||
verbose: 'v',
|
||||
},
|
||||
})
|
||||
|
||||
if (opts.help) {
|
||||
return usage
|
||||
}
|
||||
|
||||
if (opts.verbose) {
|
||||
// Does not work perfectly.
|
||||
//
|
||||
// https://github.com/visionmedia/debug/pull/156
|
||||
createDebug.enable('xen-api,xen-api:*')
|
||||
}
|
||||
|
||||
let auth
|
||||
if (opts._.length > 1) {
|
||||
const [, user, password = await askPassword()] = opts._
|
||||
auth = { user, password }
|
||||
}
|
||||
|
||||
{
|
||||
const debug = createDebug('xen-api:perf')
|
||||
blocked(ms => {
|
||||
debug('blocked for %sms', ms | 0)
|
||||
})
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
url: opts._[0],
|
||||
allowUnauthorized: opts.au,
|
||||
auth,
|
||||
debounce: opts.debounce != null ? +opts.debounce : null,
|
||||
readOnly: opts.ro,
|
||||
})
|
||||
await xapi.connect()
|
||||
|
||||
const repl = createRepl({
|
||||
prompt: `${xapi._humanId}> `,
|
||||
})
|
||||
repl.context.xapi = xapi
|
||||
|
||||
repl.context.find = predicate => find(xapi.objects.all, predicate)
|
||||
repl.context.findAll = predicate => filter(xapi.objects.all, predicate)
|
||||
|
||||
// Make the REPL waits for promise completion.
|
||||
repl.eval = (evaluate => (cmd, context, filename, cb) => {
|
||||
;fromCallback(cb => {
|
||||
evaluate.call(repl, cmd, context, filename, cb)
|
||||
})
|
||||
.then(value => (isArray(value) ? Promise.all(value) : value))
|
||||
::asCallback(cb)
|
||||
})(repl.eval)
|
||||
|
||||
await eventToPromise(repl, 'exit')
|
||||
|
||||
try {
|
||||
await xapi.disconnect()
|
||||
} catch (error) {}
|
||||
}
|
||||
export default main
|
||||
|
||||
if (!module.parent) {
|
||||
execPromise(main)
|
||||
}
|
||||
1060
packages/xen-api/src/index.js
Normal file
1060
packages/xen-api/src/index.js
Normal file
File diff suppressed because it is too large
Load Diff
32
packages/xen-api/src/inject-events.js
Executable file
32
packages/xen-api/src/inject-events.js
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { delay as pDelay } from 'promise-toolbox'
|
||||
|
||||
import { createClient } from './'
|
||||
|
||||
const xapi = (() => {
|
||||
const [, , url, user, password] = process.argv
|
||||
|
||||
return createClient({
|
||||
auth: { user, password },
|
||||
url,
|
||||
watchEvents: false,
|
||||
})
|
||||
})()
|
||||
|
||||
xapi
|
||||
.connect()
|
||||
|
||||
// Get the pool record's ref.
|
||||
.then(() => xapi.call('pool.get_all'))
|
||||
|
||||
// Injects lots of events.
|
||||
.then(([poolRef]) => {
|
||||
const loop = () =>
|
||||
xapi
|
||||
.call('event.inject', 'pool', poolRef)
|
||||
::pDelay(10) // A small delay is required to avoid overloading the Xen API.
|
||||
.then(loop)
|
||||
|
||||
return loop()
|
||||
})
|
||||
22
packages/xen-api/src/memory-test.js
Executable file
22
packages/xen-api/src/memory-test.js
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createClient } from './'
|
||||
|
||||
let i = 0
|
||||
setInterval(() => {
|
||||
const usage = process.memoryUsage()
|
||||
console.log(
|
||||
'%s %s %s %s',
|
||||
i++,
|
||||
Math.round(usage.rss / 1e6),
|
||||
Math.round(usage.heapTotal / 1e6),
|
||||
Math.round(usage.heapUsed / 1e6)
|
||||
)
|
||||
}, 1e2)
|
||||
|
||||
const [, , url, user, password] = process.argv
|
||||
createClient({
|
||||
auth: { user, password },
|
||||
readOnly: true,
|
||||
url,
|
||||
}).connect()
|
||||
3
packages/xen-api/src/transports/_utils.js
Normal file
3
packages/xen-api/src/transports/_utils.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import makeError from 'make-error'
|
||||
|
||||
export const UnsupportedTransport = makeError('UnsupportedTransport')
|
||||
36
packages/xen-api/src/transports/auto.js
Normal file
36
packages/xen-api/src/transports/auto.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import jsonRpc from './json-rpc'
|
||||
import xmlRpc from './xml-rpc'
|
||||
import xmlRpcJson from './xml-rpc-json'
|
||||
import { UnsupportedTransport } from './_utils'
|
||||
|
||||
const factories = [jsonRpc, xmlRpcJson, xmlRpc]
|
||||
const { length } = factories
|
||||
|
||||
export default opts => {
|
||||
let i = 0
|
||||
|
||||
let call
|
||||
function create () {
|
||||
const current = factories[i++](opts)
|
||||
if (i < length) {
|
||||
const currentI = i
|
||||
call = (method, args) =>
|
||||
current(method, args).catch(error => {
|
||||
if (error instanceof UnsupportedTransport) {
|
||||
if (currentI === i) {
|
||||
// not changed yet
|
||||
create()
|
||||
}
|
||||
return call(method, args)
|
||||
}
|
||||
|
||||
throw error
|
||||
})
|
||||
} else {
|
||||
call = current
|
||||
}
|
||||
}
|
||||
create()
|
||||
|
||||
return (method, args) => call(method, args)
|
||||
}
|
||||
43
packages/xen-api/src/transports/json-rpc.js
Normal file
43
packages/xen-api/src/transports/json-rpc.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import httpRequestPlus from 'http-request-plus'
|
||||
import { format, parse } from 'json-rpc-protocol'
|
||||
|
||||
import { UnsupportedTransport } from './_utils'
|
||||
|
||||
export default ({ allowUnauthorized, url }) => {
|
||||
return (method, args) =>
|
||||
httpRequestPlus
|
||||
.post(url, {
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
body: format.request(0, method, args),
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
path: '/jsonrpc',
|
||||
})
|
||||
.readAll('utf8')
|
||||
.then(
|
||||
text => {
|
||||
let response
|
||||
try {
|
||||
response = parse(text)
|
||||
} catch (error) {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
if (response.type === 'response') {
|
||||
return response.result
|
||||
}
|
||||
|
||||
throw response.error
|
||||
},
|
||||
error => {
|
||||
if (error.response !== undefined) {
|
||||
// HTTP error
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
)
|
||||
}
|
||||
87
packages/xen-api/src/transports/xml-rpc-json.js
Normal file
87
packages/xen-api/src/transports/xml-rpc-json.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { createClient, createSecureClient } from 'xmlrpc'
|
||||
import { promisify } from 'promise-toolbox'
|
||||
|
||||
import { UnsupportedTransport } from './_utils'
|
||||
|
||||
const logError = error => {
|
||||
if (error.res) {
|
||||
console.error(
|
||||
'XML-RPC Error: %s (response status %s)',
|
||||
error.message,
|
||||
error.res.statusCode
|
||||
)
|
||||
console.error('%s', error.body)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
const SPECIAL_CHARS = {
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
}
|
||||
const SPECIAL_CHARS_RE = new RegExp(Object.keys(SPECIAL_CHARS).join('|'), 'g')
|
||||
|
||||
const parseResult = result => {
|
||||
const status = result.Status
|
||||
|
||||
// Return the plain result if it does not have a valid XAPI
|
||||
// format.
|
||||
if (status === undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
if (status !== 'Success') {
|
||||
throw result.ErrorDescription
|
||||
}
|
||||
|
||||
const value = result.Value
|
||||
|
||||
// XAPI returns an empty string (invalid JSON) for an empty
|
||||
// result.
|
||||
if (value === '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (error) {
|
||||
// XAPI JSON sometimes contains invalid characters.
|
||||
if (!(error instanceof SyntaxError)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
let replaced = false
|
||||
const fixedValue = value.replace(SPECIAL_CHARS_RE, match => {
|
||||
replaced = true
|
||||
return SPECIAL_CHARS[match]
|
||||
})
|
||||
|
||||
if (replaced) {
|
||||
try {
|
||||
return JSON.parse(fixedValue)
|
||||
} catch (error) {
|
||||
if (!(error instanceof SyntaxError)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
export default ({
|
||||
allowUnauthorized,
|
||||
url: { hostname, path, port, protocol },
|
||||
}) => {
|
||||
const client = (protocol === 'https:' ? createSecureClient : createClient)({
|
||||
host: hostname,
|
||||
path: '/json',
|
||||
port,
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
})
|
||||
const call = promisify(client.methodCall, client)
|
||||
|
||||
return (method, args) => call(method, args).then(parseResult, logError)
|
||||
}
|
||||
45
packages/xen-api/src/transports/xml-rpc.js
Normal file
45
packages/xen-api/src/transports/xml-rpc.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { createClient, createSecureClient } from 'xmlrpc'
|
||||
import { promisify } from 'promise-toolbox'
|
||||
|
||||
const logError = error => {
|
||||
if (error.res) {
|
||||
console.error(
|
||||
'XML-RPC Error: %s (response status %s)',
|
||||
error.message,
|
||||
error.res.statusCode
|
||||
)
|
||||
console.error('%s', error.body)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
const parseResult = result => {
|
||||
const status = result.Status
|
||||
|
||||
// Return the plain result if it does not have a valid XAPI
|
||||
// format.
|
||||
if (status === undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
if (status !== 'Success') {
|
||||
throw result.ErrorDescription
|
||||
}
|
||||
|
||||
return result.Value
|
||||
}
|
||||
|
||||
export default ({
|
||||
allowUnauthorized,
|
||||
url: { hostname, path, port, protocol },
|
||||
}) => {
|
||||
const client = (protocol === 'https:' ? createSecureClient : createClient)({
|
||||
host: hostname,
|
||||
port,
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
})
|
||||
const call = promisify(client.methodCall, client)
|
||||
|
||||
return (method, args) => call(method, args).then(parseResult, logError)
|
||||
}
|
||||
13
packages/xo-acl-resolver/.babelrc
Normal file
13
packages/xo-acl-resolver/.babelrc
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"comments": false,
|
||||
"compact": true,
|
||||
"presets": [
|
||||
[
|
||||
"env", {
|
||||
"targets": {
|
||||
"node": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
74
packages/xo-acl-resolver/README.md
Normal file
74
packages/xo-acl-resolver/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# xo-acl-resolver [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> [Xen-Orchestra](http://xen-orchestra.com/) internal: do ACLs resolution.
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/xo-acl-resolver):
|
||||
|
||||
```
|
||||
> npm install --save xo-acl-resolver
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import check from 'xo-acl-resolver'
|
||||
|
||||
// This object contains a list of permissions returned from
|
||||
// xo-server's acl.getCurrentPermissions.
|
||||
const permissions = { /* ... */ }
|
||||
|
||||
// This function should returns synchronously an object from an id.
|
||||
const getObject = id => { /* ... */ }
|
||||
|
||||
// For a single object:
|
||||
if (check(permissions, getObject, objectId, permission)) {
|
||||
console.log(`${permission} set for object ${objectId}`)
|
||||
}
|
||||
|
||||
// For multiple objects/permissions:
|
||||
if (check(permissions, getObject, [
|
||||
[ object1Id, permission1 ],
|
||||
[ object12d, permission2 ],
|
||||
])) {
|
||||
console.log('all permissions checked')
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
```
|
||||
> npm install
|
||||
```
|
||||
|
||||
### Compilation
|
||||
|
||||
The sources files are watched and automatically recompiled on changes.
|
||||
|
||||
```
|
||||
> npm run dev
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
```
|
||||
> npm run test-dev
|
||||
```
|
||||
|
||||
## 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 © [Vates SAS](https://vates.fr)
|
||||
35
packages/xo-acl-resolver/package.json
Normal file
35
packages/xo-acl-resolver/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "xo-acl-resolver",
|
||||
"version": "0.2.3",
|
||||
"license": "ISC",
|
||||
"description": "Xen-Orchestra internal: do ACLs resolution",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-acl-resolver",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@vates.fr"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-preset-env": "^1.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prepublishOnly": "yarn run build"
|
||||
}
|
||||
}
|
||||
125
packages/xo-acl-resolver/src/index.js
Normal file
125
packages/xo-acl-resolver/src/index.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// These global variables are not a problem because the algorithm is
|
||||
// synchronous.
|
||||
let permissionsByObject
|
||||
let getObject
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const authorized = () => true // eslint-disable-line no-unused-vars
|
||||
const forbiddden = () => false // eslint-disable-line no-unused-vars
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const and = (...checkers) => (object, permission) => {
|
||||
for (const checker of checkers) {
|
||||
if (!checker(object, permission)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const or = (...checkers) => (object, permission) => {
|
||||
for (const checker of checkers) {
|
||||
if (checker(object, permission)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const checkMember = memberName => (object, permission) => {
|
||||
const member = object[memberName]
|
||||
return member !== object.id && checkAuthorization(member, permission)
|
||||
}
|
||||
|
||||
const checkSelf = ({ id }, permission) => {
|
||||
const permissionsForObject = permissionsByObject[id]
|
||||
|
||||
return permissionsForObject && permissionsForObject[permission]
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const checkAuthorizationByTypes = {
|
||||
host: or(checkSelf, checkMember('$pool')),
|
||||
|
||||
message: checkMember('$object'),
|
||||
|
||||
network: or(checkSelf, checkMember('$pool')),
|
||||
|
||||
SR: or(checkSelf, checkMember('$pool')),
|
||||
|
||||
task: checkMember('$host'),
|
||||
|
||||
VBD: checkMember('VDI'),
|
||||
|
||||
// Access to a VDI is granted if the user has access to the
|
||||
// containing SR or to a linked VM.
|
||||
VDI (vdi, permission) {
|
||||
// Check authorization for the containing SR.
|
||||
if (checkAuthorization(vdi.$SR, permission)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check authorization for each of the connected VMs.
|
||||
for (const vbdId of vdi.$VBDs) {
|
||||
if (checkAuthorization(getObject(vbdId).VM, permission)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
'VDI-snapshot': checkMember('$snapshot_of'),
|
||||
|
||||
VIF: or(checkMember('$network'), checkMember('$VM')),
|
||||
|
||||
VM: or(checkSelf, checkMember('$container')),
|
||||
|
||||
'VM-controller': checkMember('$container'),
|
||||
|
||||
'VM-snapshot': checkMember('$snapshot_of'),
|
||||
|
||||
'VM-template': or(checkSelf, checkMember('$pool')),
|
||||
}
|
||||
|
||||
// Hoisting is important for this function.
|
||||
function checkAuthorization (objectId, permission) {
|
||||
const object = getObject(objectId)
|
||||
if (!object) {
|
||||
return false
|
||||
}
|
||||
|
||||
const checker = checkAuthorizationByTypes[object.type] || checkSelf
|
||||
|
||||
return checker(object, permission)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export default (permissionsByObject_, getObject_, permissions, permission) => {
|
||||
// Assign global variables.
|
||||
permissionsByObject = permissionsByObject_
|
||||
getObject = getObject_
|
||||
|
||||
try {
|
||||
if (permission) {
|
||||
return checkAuthorization(permissions, permission)
|
||||
} else {
|
||||
for (const [objectId, permission] of permissions) {
|
||||
if (!checkAuthorization(objectId, permission)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
} finally {
|
||||
// Free the global variables.
|
||||
permissionsByObject = getObject = null
|
||||
}
|
||||
}
|
||||
47
packages/xo-cli/.babelrc.js
Normal file
47
packages/xo-cli/.babelrc.js
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict'
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
const __PROD__ = NODE_ENV === 'production'
|
||||
const __TEST__ = NODE_ENV === 'test'
|
||||
|
||||
const pkg = require('./package')
|
||||
|
||||
const plugins = {
|
||||
lodash: {},
|
||||
}
|
||||
|
||||
const presets = {
|
||||
'@babel/preset-env': {
|
||||
debug: !__TEST__,
|
||||
loose: true,
|
||||
shippedProposals: true,
|
||||
targets: __PROD__
|
||||
? (() => {
|
||||
let node = (pkg.engines || {}).node
|
||||
if (node !== undefined) {
|
||||
const trimChars = '^=>~'
|
||||
while (trimChars.includes(node[0])) {
|
||||
node = node.slice(1)
|
||||
}
|
||||
return { node: node }
|
||||
}
|
||||
})()
|
||||
: { browsers: '', node: 'current' },
|
||||
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
|
||||
},
|
||||
}
|
||||
|
||||
Object.keys(pkg.devDependencies || {}).forEach(name => {
|
||||
if (!(name in presets) && /@babel\/plugin-.+/.test(name)) {
|
||||
plugins[name] = {}
|
||||
} else if (!(name in presets) && /@babel\/preset-.+/.test(name)) {
|
||||
presets[name] = {}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
comments: !__PROD__,
|
||||
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
|
||||
plugins: Object.keys(plugins).map(plugin => [plugin, plugins[plugin]]),
|
||||
presets: Object.keys(presets).map(preset => [preset, presets[preset]]),
|
||||
}
|
||||
24
packages/xo-cli/.npmignore
Normal file
24
packages/xo-cli/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
159
packages/xo-cli/README.md
Normal file
159
packages/xo-cli/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# XO-CLI
|
||||
[](http://travis-ci.org/vatesfr/xen-orchestra)
|
||||
[](https://david-dm.org/vatesfr/xo-cli)
|
||||
[](https://david-dm.org/vatesfr/xo-cli#info=devDependencies)
|
||||
|
||||
> Basic CLI for Xen-Orchestra
|
||||
|
||||
## Install
|
||||
|
||||
#### [npm](https://npmjs.org/package/xo-cli)
|
||||
|
||||
```
|
||||
npm install -g xo-cli
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
> xo-cli --help
|
||||
Usage:
|
||||
|
||||
xo-cli --register [--expiresIn duration] <XO-Server URL> <username> [<password>]
|
||||
Registers the XO instance to use.
|
||||
|
||||
--expiresIn duration
|
||||
Can be used to change the validity duration of the
|
||||
authorization token (default: one month).
|
||||
|
||||
xo-cli --unregister
|
||||
Remove stored credentials.
|
||||
|
||||
xo-cli --list-commands [--json] [<pattern>]...
|
||||
Returns the list of available commands on the current XO instance.
|
||||
|
||||
The patterns can be used to filter on command names.
|
||||
|
||||
xo-cli --list-objects [--<property>]… [<property>=<value>]...
|
||||
Returns a list of XO objects.
|
||||
|
||||
--<property>
|
||||
Restricts displayed properties to those listed.
|
||||
|
||||
<property>=<value>
|
||||
Restricted displayed objects to those matching the patterns.
|
||||
|
||||
xo-cli <command> [<name>=<value>]...
|
||||
Executes a command on the current XO instance.
|
||||
```
|
||||
|
||||
#### Register your XO instance
|
||||
|
||||
```
|
||||
> xo-cli --register http://xo.my-company.net admin@admin.net admin
|
||||
Successfully logged with admin@admin.net
|
||||
```
|
||||
|
||||
Note: only a token will be saved in the configuration file.
|
||||
|
||||
#### List available objects
|
||||
|
||||
Prints all objects:
|
||||
|
||||
```
|
||||
> xo-cli --list-objects
|
||||
```
|
||||
|
||||
It is possible to filter on object properties, for instance to prints
|
||||
all VM templates:
|
||||
|
||||
```
|
||||
> xo-cli --list-objects type=VM-template
|
||||
```
|
||||
|
||||
#### List available commands
|
||||
|
||||
```
|
||||
> xo-cli --list-commands
|
||||
```
|
||||
|
||||
Commands can be filtered using patterns:
|
||||
|
||||
```
|
||||
> xo-cli --list-commands '{user,group}.*'
|
||||
```
|
||||
|
||||
#### Execute a command
|
||||
|
||||
The same syntax is used for all commands: `xo-cli <command> <param
|
||||
name>=<value>...`
|
||||
|
||||
E.g., adding a new server:
|
||||
|
||||
```
|
||||
> xo-cli server.add host=my.server.net username=root password=secret-password
|
||||
42
|
||||
```
|
||||
|
||||
The return value is the identifier of this new server in XO.
|
||||
|
||||
Parameters (except `true` and `false` which are correctly parsed as
|
||||
booleans) are assumed to be strings, for other types, you may use JSON
|
||||
encoding by prefixing with `json:`:
|
||||
|
||||
```
|
||||
> xo-cli foo.bar baz='json:[1, 2, 3]'
|
||||
```
|
||||
|
||||
##### Configuration export
|
||||
|
||||
```
|
||||
> xo-cli xo.exportConfig @=config.json
|
||||
```
|
||||
|
||||
##### VM export
|
||||
|
||||
```
|
||||
> xo-cli vm.export vm=a01667e0-8e29-49fc-a550-17be4226783c @=vm.xva
|
||||
```
|
||||
|
||||
##### VM import
|
||||
|
||||
```
|
||||
> xo-cli vm.import host=60a6939e-8b0a-4352-9954-5bde44bcdf7d @=vm.xva
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> yarn
|
||||
|
||||
# Run the tests
|
||||
> yarn test
|
||||
|
||||
# Continuously compile
|
||||
> yarn dev
|
||||
|
||||
# Continuously run the tests
|
||||
> yarn dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> yarn build
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
XO-CLI is released under the [AGPL
|
||||
v3](http://www.gnu.org/licenses/agpl-3.0-standalone.html).
|
||||
68
packages/xo-cli/package.json
Normal file
68
packages/xo-cli/package.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "xo-cli",
|
||||
"version": "0.10.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Basic CLI for Xen-Orchestra",
|
||||
"keywords": [
|
||||
"xo",
|
||||
"xen-orchestra",
|
||||
"xen",
|
||||
"orchestra"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-cli",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": "Julien Fontanet <julien.fontanet@vates.fr>",
|
||||
"preferGlobal": true,
|
||||
"main": "dist/",
|
||||
"bin": {
|
||||
"xo-cli": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "7.0.0-beta.40",
|
||||
"bluebird": "^3.5.1",
|
||||
"chalk": "^2.2.0",
|
||||
"event-to-promise": "^0.8.0",
|
||||
"exec-promise": "^0.7.0",
|
||||
"fs-promise": "^2.0.3",
|
||||
"got": "^8.0.1",
|
||||
"human-format": "^0.10.0",
|
||||
"l33teral": "^3.0.3",
|
||||
"lodash": "^4.17.4",
|
||||
"micromatch": "^3.1.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nice-pipe": "0.0.0",
|
||||
"pretty-ms": "^3.0.1",
|
||||
"progress-stream": "^2.0.0",
|
||||
"pw": "^0.0.4",
|
||||
"strip-indent": "^2.0.0",
|
||||
"xdg-basedir": "^3.0.0",
|
||||
"xo-lib": "^0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.0.0-beta.40",
|
||||
"@babel/core": "7.0.0-beta.40",
|
||||
"@babel/preset-env": "7.0.0-beta.40",
|
||||
"@babel/preset-flow": "7.0.0-beta.40",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^5.1.3",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build",
|
||||
"pretest": "flow status"
|
||||
}
|
||||
}
|
||||
56
packages/xo-cli/src/config.js
Normal file
56
packages/xo-cli/src/config.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const promisify = require('bluebird').promisify
|
||||
|
||||
const readFile = promisify(require('fs').readFile)
|
||||
const writeFile = promisify(require('fs').writeFile)
|
||||
|
||||
const assign = require('lodash/assign')
|
||||
const l33t = require('l33teral')
|
||||
const mkdirp = promisify(require('mkdirp'))
|
||||
const xdgBasedir = require('xdg-basedir')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const configPath = xdgBasedir.config + '/xo-cli'
|
||||
const configFile = configPath + '/config.json'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const load = (exports.load = function () {
|
||||
return readFile(configFile)
|
||||
.then(JSON.parse)
|
||||
.catch(function () {
|
||||
return {}
|
||||
})
|
||||
})
|
||||
|
||||
exports.get = function (path) {
|
||||
return load().then(function (config) {
|
||||
return l33t(config).tap(path)
|
||||
})
|
||||
}
|
||||
|
||||
const save = (exports.save = function (config) {
|
||||
return mkdirp(configPath).then(function () {
|
||||
return writeFile(configFile, JSON.stringify(config))
|
||||
})
|
||||
})
|
||||
|
||||
exports.set = function (data) {
|
||||
return load().then(function (config) {
|
||||
return save(assign(config, data))
|
||||
})
|
||||
}
|
||||
|
||||
exports.unset = function (paths) {
|
||||
return load().then(function (config) {
|
||||
const l33tConfig = l33t(config)
|
||||
;[].concat(paths).forEach(function (path) {
|
||||
l33tConfig.purge(path, true)
|
||||
})
|
||||
return save(config)
|
||||
})
|
||||
}
|
||||
420
packages/xo-cli/src/index.js
Executable file
420
packages/xo-cli/src/index.js
Executable file
@@ -0,0 +1,420 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict'
|
||||
|
||||
const Bluebird = require('bluebird')
|
||||
Bluebird.longStackTraces()
|
||||
|
||||
const createReadStream = require('fs').createReadStream
|
||||
const createWriteStream = require('fs').createWriteStream
|
||||
const resolveUrl = require('url').resolve
|
||||
const stat = require('fs-promise').stat
|
||||
|
||||
const chalk = require('chalk')
|
||||
const eventToPromise = require('event-to-promise')
|
||||
const forEach = require('lodash/forEach')
|
||||
const getKeys = require('lodash/keys')
|
||||
const got = require('got')
|
||||
const humanFormat = require('human-format')
|
||||
const identity = require('lodash/identity')
|
||||
const isArray = require('lodash/isArray')
|
||||
const isObject = require('lodash/isObject')
|
||||
const micromatch = require('micromatch')
|
||||
const nicePipe = require('nice-pipe')
|
||||
const pairs = require('lodash/toPairs')
|
||||
const pick = require('lodash/pick')
|
||||
const startsWith = require('lodash/startsWith')
|
||||
const prettyMs = require('pretty-ms')
|
||||
const progressStream = require('progress-stream')
|
||||
const pw = require('pw')
|
||||
const Xo = require('xo-lib').default
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const config = require('./config')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
async function connect () {
|
||||
const { server, token } = await config.load()
|
||||
if (server === undefined) {
|
||||
throw new Error('no server to connect to!')
|
||||
}
|
||||
|
||||
if (token === undefined) {
|
||||
throw new Error('no token available')
|
||||
}
|
||||
|
||||
const xo = new Xo({ url: server })
|
||||
await xo.open()
|
||||
await xo.signIn({ token })
|
||||
return xo
|
||||
}
|
||||
|
||||
const FLAG_RE = /^--([^=]+)(?:=([^]*))?$/
|
||||
function extractFlags (args) {
|
||||
const flags = {}
|
||||
|
||||
let i = 0
|
||||
const n = args.length
|
||||
let matches
|
||||
while (i < n && (matches = args[i].match(FLAG_RE))) {
|
||||
const value = matches[2]
|
||||
|
||||
flags[matches[1]] = value === undefined ? true : value
|
||||
++i
|
||||
}
|
||||
args.splice(0, i)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
const PARAM_RE = /^([^=]+)=([^]*)$/
|
||||
function parseParameters (args) {
|
||||
const params = {}
|
||||
forEach(args, function (arg) {
|
||||
let matches
|
||||
if (!(matches = arg.match(PARAM_RE))) {
|
||||
throw new Error('invalid arg: ' + arg)
|
||||
}
|
||||
const name = matches[1]
|
||||
let value = matches[2]
|
||||
|
||||
if (startsWith(value, 'json:')) {
|
||||
value = JSON.parse(value.slice(5))
|
||||
}
|
||||
|
||||
if (name === '@') {
|
||||
params['@'] = value
|
||||
return
|
||||
}
|
||||
|
||||
if (value === 'true') {
|
||||
value = true
|
||||
} else if (value === 'false') {
|
||||
value = false
|
||||
}
|
||||
|
||||
params[name] = value
|
||||
})
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const humanFormatOpts = {
|
||||
unit: 'B',
|
||||
scale: 'binary',
|
||||
}
|
||||
|
||||
function printProgress (progress) {
|
||||
if (progress.length) {
|
||||
console.warn(
|
||||
'%s% of %s @ %s/s - ETA %s',
|
||||
Math.round(progress.percentage),
|
||||
humanFormat(progress.length, humanFormatOpts),
|
||||
humanFormat(progress.speed, humanFormatOpts),
|
||||
prettyMs(progress.eta * 1e3)
|
||||
)
|
||||
} else {
|
||||
console.warn(
|
||||
'%s @ %s/s',
|
||||
humanFormat(progress.transferred, humanFormatOpts),
|
||||
humanFormat(progress.speed, humanFormatOpts)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function wrap (val) {
|
||||
return function wrappedValue () {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const help = wrap(
|
||||
(function (pkg) {
|
||||
return require('strip-indent')(
|
||||
`
|
||||
Usage:
|
||||
|
||||
$name --register [--expiresIn duration] <XO-Server URL> <username> [<password>]
|
||||
Registers the XO instance to use.
|
||||
|
||||
--expiresIn duration
|
||||
Can be used to change the validity duration of the
|
||||
authorization token (default: one month).
|
||||
|
||||
$name --unregister
|
||||
Remove stored credentials.
|
||||
|
||||
$name --list-commands [--json] [<pattern>]...
|
||||
Returns the list of available commands on the current XO instance.
|
||||
|
||||
The patterns can be used to filter on command names.
|
||||
|
||||
$name --list-objects [--<property>]… [<property>=<value>]...
|
||||
Returns a list of XO objects.
|
||||
|
||||
--<property>
|
||||
Restricts displayed properties to those listed.
|
||||
|
||||
<property>=<value>
|
||||
Restricted displayed objects to those matching the patterns.
|
||||
|
||||
$name <command> [<name>=<value>]...
|
||||
Executes a command on the current XO instance.
|
||||
|
||||
$name v$version
|
||||
`
|
||||
).replace(/<([^>]+)>|\$(\w+)/g, function (_, arg, key) {
|
||||
if (arg) {
|
||||
return '<' + chalk.yellow(arg) + '>'
|
||||
}
|
||||
|
||||
if (key === 'name') {
|
||||
return chalk.bold(pkg[key])
|
||||
}
|
||||
|
||||
return pkg[key]
|
||||
})
|
||||
})(require('../package'))
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
function main (args) {
|
||||
if (!args || !args.length || args[0] === '-h') {
|
||||
return help()
|
||||
}
|
||||
|
||||
const fnName = args[0].replace(/^--|-\w/g, function (match) {
|
||||
if (match === '--') {
|
||||
return ''
|
||||
}
|
||||
|
||||
return match[1].toUpperCase()
|
||||
})
|
||||
if (fnName in exports) {
|
||||
return exports[fnName](args.slice(1))
|
||||
}
|
||||
|
||||
return exports.call(args)
|
||||
}
|
||||
exports = module.exports = main
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
exports.help = help
|
||||
|
||||
async function register (args) {
|
||||
let expiresIn
|
||||
if (args[0] === '--expiresIn') {
|
||||
expiresIn = args[1]
|
||||
args = args.slice(2)
|
||||
}
|
||||
|
||||
const [
|
||||
url,
|
||||
email,
|
||||
password = await new Promise(function (resolve) {
|
||||
process.stdout.write('Password: ')
|
||||
pw(resolve)
|
||||
}),
|
||||
] = args
|
||||
|
||||
const xo = new Xo({ url })
|
||||
await xo.open()
|
||||
await xo.signIn({ email, password })
|
||||
console.log('Successfully logged with', xo.user.email)
|
||||
|
||||
await config.set({
|
||||
server: url,
|
||||
token: await xo.call('token.create', { expiresIn }),
|
||||
})
|
||||
}
|
||||
exports.register = register
|
||||
|
||||
function unregister () {
|
||||
return config.unset(['server', 'token'])
|
||||
}
|
||||
exports.unregister = unregister
|
||||
|
||||
async function listCommands (args) {
|
||||
const xo = await connect()
|
||||
let methods = await xo.call('system.getMethodsInfo')
|
||||
|
||||
let json = false
|
||||
const patterns = []
|
||||
forEach(args, function (arg) {
|
||||
if (arg === '--json') {
|
||||
json = true
|
||||
} else {
|
||||
patterns.push(arg)
|
||||
}
|
||||
})
|
||||
|
||||
if (patterns.length) {
|
||||
methods = pick(methods, micromatch(Object.keys(methods), patterns))
|
||||
}
|
||||
|
||||
if (json) {
|
||||
return methods
|
||||
}
|
||||
|
||||
methods = pairs(methods)
|
||||
methods.sort(function (a, b) {
|
||||
a = a[0]
|
||||
b = b[0]
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
return +(a > b)
|
||||
})
|
||||
|
||||
const str = []
|
||||
forEach(methods, function (method) {
|
||||
const name = method[0]
|
||||
const info = method[1]
|
||||
str.push(chalk.bold.blue(name))
|
||||
forEach(info.params || [], function (info, name) {
|
||||
str.push(' ')
|
||||
if (info.optional) {
|
||||
str.push('[')
|
||||
}
|
||||
|
||||
const type = info.type
|
||||
str.push(
|
||||
name,
|
||||
'=<',
|
||||
type == null ? 'unknown type' : isArray(type) ? type.join('|') : type,
|
||||
'>'
|
||||
)
|
||||
|
||||
if (info.optional) {
|
||||
str.push(']')
|
||||
}
|
||||
})
|
||||
str.push('\n')
|
||||
if (info.description) {
|
||||
str.push(' ', info.description, '\n')
|
||||
}
|
||||
})
|
||||
return str.join('')
|
||||
}
|
||||
exports.listCommands = listCommands
|
||||
|
||||
async function listObjects (args) {
|
||||
const properties = getKeys(extractFlags(args))
|
||||
const filterProperties = properties.length
|
||||
? function (object) {
|
||||
return pick(object, properties)
|
||||
}
|
||||
: identity
|
||||
|
||||
const filter = args.length ? parseParameters(args) : undefined
|
||||
|
||||
const xo = await connect()
|
||||
const objects = await xo.call('xo.getAllObjects', { filter })
|
||||
|
||||
const stdout = process.stdout
|
||||
stdout.write('[\n')
|
||||
const keys = Object.keys(objects)
|
||||
for (let i = 0, n = keys.length; i < n;) {
|
||||
stdout.write(JSON.stringify(filterProperties(objects[keys[i]]), null, 2))
|
||||
stdout.write(++i < n ? ',\n' : '\n')
|
||||
}
|
||||
stdout.write(']\n')
|
||||
}
|
||||
exports.listObjects = listObjects
|
||||
|
||||
function ensurePathParam (method, value) {
|
||||
if (typeof value !== 'string') {
|
||||
const error =
|
||||
method +
|
||||
' requires the @ parameter to be a path (e.g. @=/tmp/config.json)'
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function call (args) {
|
||||
if (!args.length) {
|
||||
throw new Error('missing command name')
|
||||
}
|
||||
|
||||
const method = args.shift()
|
||||
const params = parseParameters(args)
|
||||
|
||||
const file = params['@']
|
||||
delete params['@']
|
||||
|
||||
const xo = await connect()
|
||||
|
||||
// FIXME: do not use private properties.
|
||||
const baseUrl = xo._url.replace(/^ws/, 'http')
|
||||
|
||||
const result = await xo.call(method, params)
|
||||
let keys, key, url
|
||||
if (isObject(result) && (keys = getKeys(result)).length === 1) {
|
||||
key = keys[0]
|
||||
|
||||
if (key === '$getFrom') {
|
||||
ensurePathParam(method, file)
|
||||
url = resolveUrl(baseUrl, result[key])
|
||||
const output = createWriteStream(file)
|
||||
|
||||
const progress = progressStream({ time: 1e3 }, printProgress)
|
||||
|
||||
return eventToPromise(
|
||||
nicePipe([
|
||||
got.stream(url).on('response', function (response) {
|
||||
const length = response.headers['content-length']
|
||||
if (length !== undefined) {
|
||||
progress.length(length)
|
||||
}
|
||||
}),
|
||||
progress,
|
||||
output,
|
||||
]),
|
||||
'finish'
|
||||
)
|
||||
}
|
||||
|
||||
if (key === '$sendTo') {
|
||||
ensurePathParam(method, file)
|
||||
url = resolveUrl(baseUrl, result[key])
|
||||
|
||||
const stats = await stat(file)
|
||||
const length = stats.size
|
||||
|
||||
const input = nicePipe([
|
||||
createReadStream(file),
|
||||
progressStream(
|
||||
{
|
||||
length: length,
|
||||
time: 1e3,
|
||||
},
|
||||
printProgress
|
||||
),
|
||||
])
|
||||
|
||||
const response = await got.post(url, {
|
||||
body: input,
|
||||
headers: {
|
||||
'content-length': length,
|
||||
},
|
||||
method: 'POST',
|
||||
})
|
||||
return response.body
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
exports.call = call
|
||||
|
||||
// ===================================================================
|
||||
|
||||
if (!module.parent) {
|
||||
require('exec-promise')(exports)
|
||||
}
|
||||
24
packages/xo-collection/.npmignore
Normal file
24
packages/xo-collection/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
662
packages/xo-collection/LICENSE
Normal file
662
packages/xo-collection/LICENSE
Normal file
@@ -0,0 +1,662 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
265
packages/xo-collection/README.md
Normal file
265
packages/xo-collection/README.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# xo-collection [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> Generic in-memory collection with events
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/xo-collection):
|
||||
|
||||
```
|
||||
> npm install --save xo-collection
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
var Collection = require('xo-collection')
|
||||
```
|
||||
|
||||
### Creation
|
||||
|
||||
```javascript
|
||||
// Creates a new collection.
|
||||
var col = new Collection()
|
||||
```
|
||||
|
||||
### Manipulation
|
||||
|
||||
**Inserting a new item**
|
||||
|
||||
```javascript
|
||||
col.add('foo', true)
|
||||
```
|
||||
|
||||
- **Throws** `DuplicateItem` if the item is already in the collection.
|
||||
|
||||
**Updating an existing item**
|
||||
|
||||
```javascript
|
||||
col.update('foo', false)
|
||||
```
|
||||
|
||||
- **Throws** `NoSuchItem` if the item is not in the collection.
|
||||
|
||||
**Inserting or updating an item**
|
||||
|
||||
```javascript
|
||||
col.set('bar', true)
|
||||
```
|
||||
|
||||
**Notifying an external update**
|
||||
|
||||
> If an item is an object, it can be updated directly without using
|
||||
> the `set`/`update` methods.
|
||||
>
|
||||
> To make sure the collection stays in sync and the correct events are
|
||||
> sent, the `touch` method can be used to notify the change.
|
||||
|
||||
```javascript
|
||||
var baz = {}
|
||||
|
||||
col.add('baz', baz)
|
||||
|
||||
baz.prop = true
|
||||
col.touch('baz')
|
||||
```
|
||||
|
||||
> Because this is a much used pattern, `touch` returns the item to
|
||||
> allow its direct modification.
|
||||
|
||||
```javascript
|
||||
col.touch('baz').prop = false
|
||||
```
|
||||
|
||||
- **Throws** `NoSuchItem` if the item is not in the collection.
|
||||
- **Throws** `IllegalTouch` if the item is not an object.
|
||||
|
||||
**Removing an existing item**
|
||||
|
||||
```javascript
|
||||
col.remove('bar')
|
||||
```
|
||||
|
||||
- **Throws** `NoSuchItem` if the item is not in the collection.
|
||||
|
||||
**Removing an item without error**
|
||||
|
||||
This is the symmetric method of `set()`: it removes the item if it
|
||||
exists otherwise does nothing.
|
||||
|
||||
```javascript
|
||||
col.unset('bar')
|
||||
```
|
||||
|
||||
**Removing all items**
|
||||
|
||||
```javascript
|
||||
col.clear()
|
||||
```
|
||||
|
||||
### Query
|
||||
|
||||
**Checking the existence of an item**
|
||||
|
||||
```javascript
|
||||
var hasBar = col.has('bar')
|
||||
```
|
||||
|
||||
**Getting an existing item**
|
||||
|
||||
```javascript
|
||||
var foo = col.get('foo')
|
||||
|
||||
// The second parameter can be used to specify a fallback in case the
|
||||
// item does not exist.
|
||||
var bar = col.get('bar', 6.28)
|
||||
```
|
||||
|
||||
- **Throws** `NoSuchItem` if the item is not in the collection and no
|
||||
fallback has been passed.
|
||||
|
||||
**Getting a read-only view of the collection**
|
||||
|
||||
> This property is useful for example to iterate over the collection
|
||||
> or to make advanced queries with the help of utility libraries such
|
||||
> as lodash.
|
||||
|
||||
```javascript
|
||||
var _ = require('lodash')
|
||||
|
||||
// Prints all the items.
|
||||
_.forEach(col.all, function (value, key) {
|
||||
console.log('- %s: %j', key, value)
|
||||
})
|
||||
|
||||
// Finds all the items which are objects and have a property
|
||||
// `active` which equals `true`.
|
||||
var results = _.where(col.all, { active: true })
|
||||
```
|
||||
|
||||
**Getting the number of items**
|
||||
|
||||
```javascript
|
||||
var size = col.size
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
> The events are emitted asynchronously (at the next turn/tick of the
|
||||
> event loop) and are deduplicated which means, for instance, that an
|
||||
> addition followed by an update will result only in a single
|
||||
> addition.
|
||||
|
||||
**New items**
|
||||
|
||||
```javascript
|
||||
col.on('add', (added) => {
|
||||
forEach(added, (value, key) => {
|
||||
console.log('+ %s: %j', key, value)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Updated items**
|
||||
|
||||
```javascript
|
||||
col.on('update', (updated) => {
|
||||
forEach(updated, (value, key) => {
|
||||
console.log('± %s: %j', key, value)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Removed items**
|
||||
|
||||
```javascript
|
||||
col.on('remove', (removed) => {
|
||||
// For consistency, `removed` is also a map but contrary to `added`
|
||||
// and `updated`, the values associated to the keys are not
|
||||
// significant since the items have already be removed.
|
||||
|
||||
forEach(removed, (value, key) => {
|
||||
console.log('- %s', key)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**End of update**
|
||||
|
||||
> Emitted when all the update process is finished and all the update
|
||||
> events has been emitted.
|
||||
|
||||
```javascript
|
||||
col.on('finish', () => {
|
||||
console.log('the collection has been updated')
|
||||
})
|
||||
```
|
||||
|
||||
### Iteration
|
||||
|
||||
```javascript
|
||||
for (const [key, value] of col) {
|
||||
console.log('- %s: %j', key, value)
|
||||
}
|
||||
|
||||
for (const key of col.keys()) {
|
||||
console.log('- %s', key)
|
||||
}
|
||||
|
||||
for (const value of col.values()) {
|
||||
console.log('- %j', value)
|
||||
}
|
||||
```
|
||||
|
||||
### Views
|
||||
|
||||
```javascript
|
||||
const View = require('xo-collection/view')
|
||||
```
|
||||
|
||||
> A view is a read-only collection which contains only the items of a
|
||||
> parent collection which satisfy a predicate.
|
||||
>
|
||||
> It is updated at most once per turn of the event loop and therefore
|
||||
> can be briefly invalid.
|
||||
|
||||
```javascript
|
||||
const myView = new View(parentCollection, function predicate (value, key) {
|
||||
// This function should return a boolean indicating whether the
|
||||
// current item should be in this view.
|
||||
})
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> npm install
|
||||
|
||||
# Run the tests
|
||||
> npm test
|
||||
|
||||
# Continuously compile
|
||||
> npm run dev
|
||||
|
||||
# Continuously run the tests
|
||||
> npm run dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> npm run build
|
||||
```
|
||||
|
||||
## 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 © [Vates SAS](http://vates.fr)
|
||||
1
packages/xo-collection/index.js
Normal file
1
packages/xo-collection/index.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/index')
|
||||
67
packages/xo-collection/package.json
Normal file
67
packages/xo-collection/package.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "xo-collection",
|
||||
"version": "0.4.1",
|
||||
"license": "ISC",
|
||||
"description": "Generic in-memory collection with events",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-collection",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Fabrice Marsaud",
|
||||
"email": "fabrice.marsaud@vates.fr"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/collection",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/",
|
||||
"*.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.18.0",
|
||||
"kindof": "^2.0.0",
|
||||
"lodash": "^4.17.2",
|
||||
"make-error": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.5.2",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.1.3",
|
||||
"event-to-promise": "^0.8.0",
|
||||
"rimraf": "^2.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"babel": {
|
||||
"plugins": [
|
||||
"lodash",
|
||||
"transform-runtime"
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
||||
}
|
||||
5
packages/xo-collection/src/clear-object.js
Normal file
5
packages/xo-collection/src/clear-object.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function clearObject (object) {
|
||||
for (const key in object) {
|
||||
delete object[key]
|
||||
}
|
||||
}
|
||||
361
packages/xo-collection/src/collection.js
Normal file
361
packages/xo-collection/src/collection.js
Normal file
@@ -0,0 +1,361 @@
|
||||
import kindOf from 'kindof'
|
||||
import { BaseError } from 'make-error'
|
||||
import { EventEmitter } from 'events'
|
||||
import { forEach } from 'lodash'
|
||||
|
||||
import isEmpty from './is-empty'
|
||||
import isObject from './is-object'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const { create: createObject, prototype: { hasOwnProperty } } = Object
|
||||
|
||||
export const ACTION_ADD = 'add'
|
||||
export const ACTION_UPDATE = 'update'
|
||||
export const ACTION_REMOVE = 'remove'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export class BufferAlreadyFlushed extends BaseError {
|
||||
constructor () {
|
||||
super('buffer flush already requested')
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateIndex extends BaseError {
|
||||
constructor (name) {
|
||||
super('there is already an index with the name ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateItem extends BaseError {
|
||||
constructor (key) {
|
||||
super('there is already a item with the key ' + key)
|
||||
}
|
||||
}
|
||||
|
||||
export class IllegalTouch extends BaseError {
|
||||
constructor (value) {
|
||||
super('only an object value can be touched (found a ' + kindOf(value) + ')')
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidKey extends BaseError {
|
||||
constructor (key) {
|
||||
super('invalid key of type ' + kindOf(key))
|
||||
}
|
||||
}
|
||||
|
||||
export class NoSuchIndex extends BaseError {
|
||||
constructor (name) {
|
||||
super('there is no index with the name ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
export class NoSuchItem extends BaseError {
|
||||
constructor (key) {
|
||||
super('there is no item with the key ' + key)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export default class Collection extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this._buffer = createObject(null)
|
||||
this._buffering = 0
|
||||
this._indexes = createObject(null)
|
||||
this._indexedItems = createObject(null)
|
||||
this._items = {} // createObject(null)
|
||||
this._size = 0
|
||||
}
|
||||
|
||||
// Overridable method used to compute the key of an item when
|
||||
// unspecified.
|
||||
//
|
||||
// Default implementation returns the `id` property.
|
||||
getKey (value) {
|
||||
return value && value.id
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Properties
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
get all () {
|
||||
return this._items
|
||||
}
|
||||
|
||||
get indexes () {
|
||||
return this._indexedItems
|
||||
}
|
||||
|
||||
get size () {
|
||||
return this._size
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Manipulation
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
add (keyOrObjectWithId, valueIfKey = undefined) {
|
||||
const [key, value] = this._resolveItem(keyOrObjectWithId, valueIfKey)
|
||||
this._assertHasNot(key)
|
||||
|
||||
this._items[key] = value
|
||||
this._size++
|
||||
this._touch(ACTION_ADD, key)
|
||||
}
|
||||
|
||||
clear () {
|
||||
forEach(this._items, (_, key) => this._remove(key))
|
||||
}
|
||||
|
||||
remove (keyOrObjectWithId) {
|
||||
const [key] = this._resolveItem(keyOrObjectWithId)
|
||||
this._assertHas(key)
|
||||
|
||||
this._remove(key)
|
||||
}
|
||||
|
||||
set (keyOrObjectWithId, valueIfKey = undefined) {
|
||||
const [key, value] = this._resolveItem(keyOrObjectWithId, valueIfKey)
|
||||
|
||||
const action = this.has(key) ? ACTION_UPDATE : ACTION_ADD
|
||||
this._items[key] = value
|
||||
if (action === ACTION_ADD) {
|
||||
this._size++
|
||||
}
|
||||
this._touch(action, key)
|
||||
}
|
||||
|
||||
touch (keyOrObjectWithId) {
|
||||
const [key] = this._resolveItem(keyOrObjectWithId)
|
||||
this._assertHas(key)
|
||||
const value = this.get(key)
|
||||
if (!isObject(value)) {
|
||||
throw new IllegalTouch(value)
|
||||
}
|
||||
|
||||
this._touch(ACTION_UPDATE, key)
|
||||
|
||||
return this.get(key)
|
||||
}
|
||||
|
||||
unset (keyOrObjectWithId) {
|
||||
const [key] = this._resolveItem(keyOrObjectWithId)
|
||||
|
||||
if (this.has(key)) {
|
||||
this._remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
update (keyOrObjectWithId, valueIfKey = undefined) {
|
||||
const [key, value] = this._resolveItem(keyOrObjectWithId, valueIfKey)
|
||||
this._assertHas(key)
|
||||
|
||||
this._items[key] = value
|
||||
this._touch(ACTION_UPDATE, key)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Query
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
get (key, defaultValue) {
|
||||
if (this.has(key)) {
|
||||
return this._items[key]
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Throws a NoSuchItem.
|
||||
this._assertHas(key)
|
||||
}
|
||||
|
||||
has (key) {
|
||||
return hasOwnProperty.call(this._items, key)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Indexes
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
createIndex (name, index) {
|
||||
const { _indexes: indexes } = this
|
||||
if (hasOwnProperty.call(indexes, name)) {
|
||||
throw new DuplicateIndex(name)
|
||||
}
|
||||
|
||||
indexes[name] = index
|
||||
this._indexedItems[name] = index.items
|
||||
|
||||
index._attachCollection(this)
|
||||
}
|
||||
|
||||
deleteIndex (name) {
|
||||
const { _indexes: indexes } = this
|
||||
if (!hasOwnProperty.call(indexes, name)) {
|
||||
throw new NoSuchIndex(name)
|
||||
}
|
||||
|
||||
const index = indexes[name]
|
||||
delete indexes[name]
|
||||
delete this._indexedItems[name]
|
||||
|
||||
index._detachCollection(this)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Iteration
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
* [Symbol.iterator] () {
|
||||
const { _items: items } = this
|
||||
|
||||
for (const key in items) {
|
||||
yield [key, items[key]]
|
||||
}
|
||||
}
|
||||
|
||||
* keys () {
|
||||
const { _items: items } = this
|
||||
|
||||
for (const key in items) {
|
||||
yield key
|
||||
}
|
||||
}
|
||||
|
||||
* values () {
|
||||
const { _items: items } = this
|
||||
|
||||
for (const key in items) {
|
||||
yield items[key]
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Events buffering
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
bufferEvents () {
|
||||
++this._buffering
|
||||
|
||||
let called = false
|
||||
return () => {
|
||||
if (called) {
|
||||
throw new BufferAlreadyFlushed()
|
||||
}
|
||||
called = true
|
||||
|
||||
if (--this._buffering) {
|
||||
return
|
||||
}
|
||||
|
||||
const { _buffer: buffer } = this
|
||||
|
||||
// Due to deduplication there could be nothing in the buffer.
|
||||
if (isEmpty(buffer)) {
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
add: createObject(null),
|
||||
remove: createObject(null),
|
||||
update: createObject(null),
|
||||
}
|
||||
|
||||
for (const key in this._buffer) {
|
||||
data[buffer[key]][key] = this._items[key]
|
||||
}
|
||||
|
||||
forEach(data, (items, action) => {
|
||||
if (!isEmpty(items)) {
|
||||
this.emit(action, items)
|
||||
}
|
||||
})
|
||||
|
||||
// Indicates the end of the update.
|
||||
//
|
||||
// This name has been chosen because it is used in Node writable
|
||||
// streams when the data has been successfully committed.
|
||||
this.emit('finish')
|
||||
|
||||
this._buffer = createObject(null)
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
_assertHas (key) {
|
||||
if (!this.has(key)) {
|
||||
throw new NoSuchItem(key)
|
||||
}
|
||||
}
|
||||
|
||||
_assertHasNot (key) {
|
||||
if (this.has(key)) {
|
||||
throw new DuplicateItem(key)
|
||||
}
|
||||
}
|
||||
|
||||
_assertValidKey (key) {
|
||||
if (!this._isValidKey(key)) {
|
||||
throw new InvalidKey(key)
|
||||
}
|
||||
}
|
||||
|
||||
_isValidKey (key) {
|
||||
return typeof key === 'number' || typeof key === 'string'
|
||||
}
|
||||
|
||||
_remove (key) {
|
||||
delete this._items[key]
|
||||
this._size--
|
||||
this._touch(ACTION_REMOVE, key)
|
||||
}
|
||||
|
||||
_resolveItem (keyOrObjectWithId, valueIfKey = undefined) {
|
||||
if (valueIfKey !== undefined) {
|
||||
this._assertValidKey(keyOrObjectWithId)
|
||||
|
||||
return [keyOrObjectWithId, valueIfKey]
|
||||
}
|
||||
|
||||
if (this._isValidKey(keyOrObjectWithId)) {
|
||||
return [keyOrObjectWithId]
|
||||
}
|
||||
|
||||
const key = this.getKey(keyOrObjectWithId)
|
||||
this._assertValidKey(key)
|
||||
|
||||
return [key, keyOrObjectWithId]
|
||||
}
|
||||
|
||||
_touch (action, key) {
|
||||
if (this._buffering === 0) {
|
||||
const flush = this.bufferEvents()
|
||||
|
||||
process.nextTick(flush)
|
||||
}
|
||||
|
||||
if (action === ACTION_ADD) {
|
||||
this._buffer[key] = this._buffer[key] ? ACTION_UPDATE : ACTION_ADD
|
||||
} else if (action === ACTION_REMOVE) {
|
||||
if (this._buffer[key] === ACTION_ADD) {
|
||||
delete this._buffer[key]
|
||||
} else {
|
||||
this._buffer[key] = ACTION_REMOVE
|
||||
}
|
||||
} else {
|
||||
// update
|
||||
if (!this._buffer[key]) {
|
||||
this._buffer[key] = ACTION_UPDATE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
325
packages/xo-collection/src/collection.spec.js
Normal file
325
packages/xo-collection/src/collection.spec.js
Normal file
@@ -0,0 +1,325 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import eventToPromise from 'event-to-promise'
|
||||
import { forEach } from 'lodash'
|
||||
|
||||
import Collection, { DuplicateItem, NoSuchItem } from './collection'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
function waitTicks (n = 2) {
|
||||
const { nextTick } = process
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
;(function waitNextTick () {
|
||||
// The first tick is handled by Promise#then()
|
||||
if (--n) {
|
||||
nextTick(waitNextTick)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
describe('Collection', function () {
|
||||
let col
|
||||
beforeEach(function () {
|
||||
col = new Collection()
|
||||
col.add('bar', 0)
|
||||
|
||||
return waitTicks()
|
||||
})
|
||||
|
||||
it('is iterable', function () {
|
||||
const iterator = col[Symbol.iterator]()
|
||||
|
||||
expect(iterator.next()).toEqual({ done: false, value: ['bar', 0] })
|
||||
expect(iterator.next()).toEqual({ done: true, value: undefined })
|
||||
})
|
||||
|
||||
describe('#keys()', function () {
|
||||
it('returns an iterator over the keys', function () {
|
||||
const iterator = col.keys()
|
||||
|
||||
expect(iterator.next()).toEqual({ done: false, value: 'bar' })
|
||||
expect(iterator.next()).toEqual({ done: true, value: undefined })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#values()', function () {
|
||||
it('returns an iterator over the values', function () {
|
||||
const iterator = col.values()
|
||||
|
||||
expect(iterator.next()).toEqual({ done: false, value: 0 })
|
||||
expect(iterator.next()).toEqual({ done: true, value: undefined })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#add()', function () {
|
||||
it('adds item to the collection', function () {
|
||||
const spy = jest.fn()
|
||||
col.on('add', spy)
|
||||
|
||||
col.add('foo', true)
|
||||
|
||||
expect(col.get('foo')).toBe(true)
|
||||
|
||||
// No sync events.
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
|
||||
// Async event.
|
||||
return eventToPromise(col, 'add').then(function (added) {
|
||||
expect(Object.keys(added)).toEqual(['foo'])
|
||||
expect(added.foo).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an exception if the item already exists', function () {
|
||||
expect(() => col.add('bar', true)).toThrowError(DuplicateItem)
|
||||
})
|
||||
|
||||
it('accepts an object with an id property', function () {
|
||||
const foo = { id: 'foo' }
|
||||
|
||||
col.add(foo)
|
||||
|
||||
expect(col.get(foo.id)).toBe(foo)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#update()', function () {
|
||||
it('updates an item of the collection', function () {
|
||||
const spy = jest.fn()
|
||||
col.on('update', spy)
|
||||
|
||||
col.update('bar', 1)
|
||||
expect(col.get('bar')).toBe(1) // Will be forgotten by de-duplication
|
||||
col.update('bar', 2)
|
||||
expect(col.get('bar')).toBe(2)
|
||||
|
||||
// No sync events.
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
|
||||
// Async event.
|
||||
return eventToPromise(col, 'update').then(function (updated) {
|
||||
expect(Object.keys(updated)).toEqual(['bar'])
|
||||
expect(updated.bar).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an exception if the item does not exist', function () {
|
||||
expect(() => col.update('baz', true)).toThrowError(NoSuchItem)
|
||||
})
|
||||
|
||||
it('accepts an object with an id property', function () {
|
||||
const bar = { id: 'bar' }
|
||||
|
||||
col.update(bar)
|
||||
|
||||
expect(col.get(bar.id)).toBe(bar)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#remove()', function () {
|
||||
it('removes an item of the collection', function () {
|
||||
const spy = jest.fn()
|
||||
col.on('remove', spy)
|
||||
|
||||
col.update('bar', 1)
|
||||
expect(col.get('bar')).toBe(1) // Will be forgotten by de-duplication
|
||||
col.remove('bar')
|
||||
|
||||
// No sync events.
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
|
||||
// Async event.
|
||||
return eventToPromise(col, 'remove').then(function (removed) {
|
||||
expect(Object.keys(removed)).toEqual(['bar'])
|
||||
expect(removed.bar).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an exception if the item does not exist', function () {
|
||||
expect(() => col.remove('baz', true)).toThrowError(NoSuchItem)
|
||||
})
|
||||
|
||||
it('accepts an object with an id property', function () {
|
||||
const bar = { id: 'bar' }
|
||||
|
||||
col.remove(bar)
|
||||
|
||||
expect(col.has(bar.id)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#set()', function () {
|
||||
it('adds item if collection has not key', function () {
|
||||
const spy = jest.fn()
|
||||
col.on('add', spy)
|
||||
|
||||
col.set('foo', true)
|
||||
|
||||
expect(col.get('foo')).toBe(true)
|
||||
|
||||
// No sync events.
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
|
||||
// Async events.
|
||||
return eventToPromise(col, 'add').then(function (added) {
|
||||
expect(Object.keys(added)).toEqual(['foo'])
|
||||
expect(added.foo).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('updates item if collection has key', function () {
|
||||
const spy = jest.fn()
|
||||
col.on('udpate', spy)
|
||||
|
||||
col.set('bar', 1)
|
||||
|
||||
expect(col.get('bar')).toBe(1)
|
||||
|
||||
// No sync events.
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
|
||||
// Async events.
|
||||
return eventToPromise(col, 'update').then(function (updated) {
|
||||
expect(Object.keys(updated)).toEqual(['bar'])
|
||||
expect(updated.bar).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('accepts an object with an id property', function () {
|
||||
const foo = { id: 'foo' }
|
||||
|
||||
col.set(foo)
|
||||
|
||||
expect(col.get(foo.id)).toBe(foo)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#unset()', function () {
|
||||
it('removes an existing item', function () {
|
||||
col.unset('bar')
|
||||
|
||||
expect(col.has('bar')).toBe(false)
|
||||
|
||||
return eventToPromise(col, 'remove').then(function (removed) {
|
||||
expect(Object.keys(removed)).toEqual(['bar'])
|
||||
expect(removed.bar).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('does not throw if the item does not exists', function () {
|
||||
col.unset('foo')
|
||||
})
|
||||
|
||||
it('accepts an object with an id property', function () {
|
||||
col.unset({ id: 'bar' })
|
||||
|
||||
expect(col.has('bar')).toBe(false)
|
||||
|
||||
return eventToPromise(col, 'remove').then(function (removed) {
|
||||
expect(Object.keys(removed)).toEqual(['bar'])
|
||||
expect(removed.bar).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('touch()', function () {
|
||||
it('can be used to signal an indirect update', function () {
|
||||
const foo = { id: 'foo' }
|
||||
col.add(foo)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
col.touch(foo)
|
||||
|
||||
return eventToPromise(col, 'update', items => {
|
||||
expect(Object.keys(items)).toEqual(['foo'])
|
||||
expect(items.foo).toBe(foo)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('clear()', function () {
|
||||
it('removes all items from the collection', function () {
|
||||
col.clear()
|
||||
|
||||
expect(col.size).toBe(0)
|
||||
|
||||
return eventToPromise(col, 'remove').then(items => {
|
||||
expect(Object.keys(items)).toEqual(['bar'])
|
||||
expect(items.bar).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deduplicates events', function () {
|
||||
forEach(
|
||||
{
|
||||
'add & update → add': [
|
||||
[['add', 'foo', 0], ['update', 'foo', 1]],
|
||||
{
|
||||
add: {
|
||||
foo: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
'add & remove → ∅': [[['add', 'foo', 0], ['remove', 'foo']], {}],
|
||||
|
||||
'update & update → update': [
|
||||
[['update', 'bar', 1], ['update', 'bar', 2]],
|
||||
{
|
||||
update: {
|
||||
bar: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
'update & remove → remove': [
|
||||
[['update', 'bar', 1], ['remove', 'bar']],
|
||||
{
|
||||
remove: {
|
||||
bar: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
'remove & add → update': [
|
||||
[['remove', 'bar'], ['add', 'bar', 0]],
|
||||
{
|
||||
update: {
|
||||
bar: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
([operations, results], label) => {
|
||||
it(label, function () {
|
||||
forEach(operations, ([method, ...args]) => {
|
||||
col[method](...args)
|
||||
})
|
||||
|
||||
const spies = Object.create(null)
|
||||
forEach(['add', 'update', 'remove'], event => {
|
||||
col.on(event, (spies[event] = jest.fn()))
|
||||
})
|
||||
|
||||
return waitTicks().then(() => {
|
||||
forEach(spies, (spy, event) => {
|
||||
const items = results[event]
|
||||
if (items) {
|
||||
expect(spy.mock.calls).toEqual([[items]])
|
||||
} else {
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
136
packages/xo-collection/src/index.js
Normal file
136
packages/xo-collection/src/index.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import { bind, iteratee } from 'lodash'
|
||||
|
||||
import clearObject from './clear-object'
|
||||
import isEmpty from './is-empty'
|
||||
import NotImplemented from './not-implemented'
|
||||
import { ACTION_ADD, ACTION_UPDATE, ACTION_REMOVE } from './collection'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default class Index {
|
||||
constructor (computeHash) {
|
||||
if (computeHash) {
|
||||
this.computeHash = iteratee(computeHash)
|
||||
}
|
||||
|
||||
this._itemsByHash = Object.create(null)
|
||||
this._keysToHash = Object.create(null)
|
||||
|
||||
// Bound versions of listeners.
|
||||
this._onAdd = bind(this._onAdd, this)
|
||||
this._onUpdate = bind(this._onUpdate, this)
|
||||
this._onRemove = bind(this._onRemove, this)
|
||||
}
|
||||
|
||||
// This method is used to compute the hash under which an item must
|
||||
// be saved.
|
||||
computeHash (value, key) {
|
||||
throw new NotImplemented('this method must be overridden')
|
||||
}
|
||||
|
||||
// Remove empty items lists.
|
||||
sweep () {
|
||||
const { _itemsByHash: itemsByHash } = this
|
||||
for (const hash in itemsByHash) {
|
||||
if (isEmpty(itemsByHash[hash])) {
|
||||
delete itemsByHash[hash]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
get items () {
|
||||
return this._itemsByHash
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
_attachCollection (collection) {
|
||||
// Add existing entries.
|
||||
//
|
||||
// FIXME: I think there may be a race condition if the `add` event
|
||||
// has not been emitted yet.
|
||||
this._onAdd(collection.all)
|
||||
|
||||
collection.on(ACTION_ADD, this._onAdd)
|
||||
collection.on(ACTION_UPDATE, this._onUpdate)
|
||||
collection.on(ACTION_REMOVE, this._onRemove)
|
||||
}
|
||||
|
||||
_detachCollection (collection) {
|
||||
collection.removeListener(ACTION_ADD, this._onAdd)
|
||||
collection.removeListener(ACTION_UPDATE, this._onUpdate)
|
||||
collection.removeListener(ACTION_REMOVE, this._onRemove)
|
||||
|
||||
clearObject(this._itemsByHash)
|
||||
clearObject(this._keysToHash)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
_onAdd (items) {
|
||||
const {
|
||||
computeHash,
|
||||
_itemsByHash: itemsByHash,
|
||||
_keysToHash: keysToHash,
|
||||
} = this
|
||||
|
||||
for (const key in items) {
|
||||
const value = items[key]
|
||||
|
||||
const hash = computeHash(value, key)
|
||||
|
||||
if (hash != null) {
|
||||
;(itemsByHash[hash] ||
|
||||
// FIXME: We do not use objects without prototype for now
|
||||
// because it breaks Angular in xo-web, change it back when
|
||||
// this is fixed.
|
||||
(itemsByHash[hash] = {}))[key] = value
|
||||
|
||||
keysToHash[key] = hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdate (items) {
|
||||
const {
|
||||
computeHash,
|
||||
_itemsByHash: itemsByHash,
|
||||
_keysToHash: keysToHash,
|
||||
} = this
|
||||
|
||||
for (const key in items) {
|
||||
const value = items[key]
|
||||
|
||||
const prev = keysToHash[key]
|
||||
const hash = computeHash(value, key)
|
||||
|
||||
// Removes item from the previous hash's list if any.
|
||||
if (prev != null) delete itemsByHash[prev][key]
|
||||
|
||||
// Inserts item into the new hash's list if any.
|
||||
if (hash != null) {
|
||||
;(itemsByHash[hash] ||
|
||||
// FIXME: idem: change back to Object.create(null)
|
||||
(itemsByHash[hash] = {}))[key] = value
|
||||
|
||||
keysToHash[key] = hash
|
||||
} else {
|
||||
delete keysToHash[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onRemove (items) {
|
||||
const { _itemsByHash: itemsByHash, _keysToHash: keysToHash } = this
|
||||
|
||||
for (const key in items) {
|
||||
const prev = keysToHash[key]
|
||||
if (prev != null) {
|
||||
delete itemsByHash[prev][key]
|
||||
delete keysToHash[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
180
packages/xo-collection/src/index.spec.js
Normal file
180
packages/xo-collection/src/index.spec.js
Normal file
@@ -0,0 +1,180 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import eventToPromise from 'event-to-promise'
|
||||
import { forEach } from 'lodash'
|
||||
|
||||
import Collection from './collection'
|
||||
import Index from './index'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const waitTicks = (n = 2) => {
|
||||
const { nextTick } = process
|
||||
|
||||
return new Promise(resolve => {
|
||||
;(function waitNextTick () {
|
||||
// The first tick is handled by Promise#then()
|
||||
if (--n) {
|
||||
nextTick(waitNextTick)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
describe('Index', function () {
|
||||
let col, byGroup
|
||||
const item1 = {
|
||||
id: '2ccb8a72-dc65-48e4-88fe-45ef541f2cba',
|
||||
group: 'foo',
|
||||
}
|
||||
const item2 = {
|
||||
id: '7d21dc51-4da8-4538-a2e9-dd6f4784eb76',
|
||||
group: 'bar',
|
||||
}
|
||||
const item3 = {
|
||||
id: '668c1274-4442-44a6-b99a-512188e0bb09',
|
||||
group: 'foo',
|
||||
}
|
||||
const item4 = {
|
||||
id: 'd90b7335-e540-4a44-ad22-c4baae9cd0a9',
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
col = new Collection()
|
||||
forEach([item1, item2, item3, item4], item => {
|
||||
col.add(item)
|
||||
})
|
||||
|
||||
byGroup = new Index('group')
|
||||
|
||||
col.createIndex('byGroup', byGroup)
|
||||
|
||||
return waitTicks()
|
||||
})
|
||||
|
||||
it('works with existing items', function () {
|
||||
expect(col.indexes).toEqual({
|
||||
byGroup: {
|
||||
foo: {
|
||||
[item1.id]: item1,
|
||||
[item3.id]: item3,
|
||||
},
|
||||
bar: {
|
||||
[item2.id]: item2,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('works with added items', function () {
|
||||
const item5 = {
|
||||
id: '823b56c4-4b96-4f3a-9533-5d08177167ac',
|
||||
group: 'baz',
|
||||
}
|
||||
|
||||
col.add(item5)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byGroup: {
|
||||
foo: {
|
||||
[item1.id]: item1,
|
||||
[item3.id]: item3,
|
||||
},
|
||||
bar: {
|
||||
[item2.id]: item2,
|
||||
},
|
||||
baz: {
|
||||
[item5.id]: item5,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('works with updated items', function () {
|
||||
const item1bis = {
|
||||
id: item1.id,
|
||||
group: 'bar',
|
||||
}
|
||||
|
||||
col.update(item1bis)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byGroup: {
|
||||
foo: {
|
||||
[item3.id]: item3,
|
||||
},
|
||||
bar: {
|
||||
[item1.id]: item1bis,
|
||||
[item2.id]: item2,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('works with removed items', function () {
|
||||
col.remove(item2)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byGroup: {
|
||||
foo: {
|
||||
[item1.id]: item1,
|
||||
[item3.id]: item3,
|
||||
},
|
||||
bar: {},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('correctly updates the value even the same object has the same hash', function () {
|
||||
const item1bis = {
|
||||
id: item1.id,
|
||||
group: item1.group,
|
||||
newProp: true,
|
||||
}
|
||||
|
||||
col.update(item1bis)
|
||||
|
||||
return eventToPromise(col, 'finish').then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byGroup: {
|
||||
foo: {
|
||||
[item1.id]: item1bis,
|
||||
[item3.id]: item3,
|
||||
},
|
||||
bar: {
|
||||
[item2.id]: item2,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sweep()', function () {
|
||||
it('removes empty items lists', function () {
|
||||
col.remove(item2)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
byGroup.sweep()
|
||||
|
||||
expect(col.indexes).toEqual({
|
||||
byGroup: {
|
||||
foo: {
|
||||
[item1.id]: item1,
|
||||
[item3.id]: item3,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
7
packages/xo-collection/src/is-empty.js
Normal file
7
packages/xo-collection/src/is-empty.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function isEmpty (object) {
|
||||
/* eslint no-unused-vars: 0 */
|
||||
for (const key in object) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
3
packages/xo-collection/src/is-object.js
Normal file
3
packages/xo-collection/src/is-object.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function isObject (value) {
|
||||
return value !== null && typeof value === 'object'
|
||||
}
|
||||
7
packages/xo-collection/src/not-implemented.js
Normal file
7
packages/xo-collection/src/not-implemented.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseError } from 'make-error'
|
||||
|
||||
export default class NotImplemented extends BaseError {
|
||||
constructor (message) {
|
||||
super(message || 'this method is not implemented')
|
||||
}
|
||||
}
|
||||
117
packages/xo-collection/src/unique-index.js
Normal file
117
packages/xo-collection/src/unique-index.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { bind, iteratee } from 'lodash'
|
||||
|
||||
import clearObject from './clear-object'
|
||||
import NotImplemented from './not-implemented'
|
||||
import { ACTION_ADD, ACTION_UPDATE, ACTION_REMOVE } from './collection'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default class UniqueIndex {
|
||||
constructor (computeHash) {
|
||||
if (computeHash) {
|
||||
this.computeHash = iteratee(computeHash)
|
||||
}
|
||||
|
||||
this._itemByHash = Object.create(null)
|
||||
this._keysToHash = Object.create(null)
|
||||
|
||||
// Bound versions of listeners.
|
||||
this._onAdd = bind(this._onAdd, this)
|
||||
this._onUpdate = bind(this._onUpdate, this)
|
||||
this._onRemove = bind(this._onRemove, this)
|
||||
}
|
||||
|
||||
// This method is used to compute the hash under which an item must
|
||||
// be saved.
|
||||
computeHash (value, key) {
|
||||
throw new NotImplemented('this method must be overridden')
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
get items () {
|
||||
return this._itemByHash
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
_attachCollection (collection) {
|
||||
// Add existing entries.
|
||||
//
|
||||
// FIXME: I think there may be a race condition if the `add` event
|
||||
// has not been emitted yet.
|
||||
this._onAdd(collection.all)
|
||||
|
||||
collection.on(ACTION_ADD, this._onAdd)
|
||||
collection.on(ACTION_UPDATE, this._onUpdate)
|
||||
collection.on(ACTION_REMOVE, this._onRemove)
|
||||
}
|
||||
|
||||
_detachCollection (collection) {
|
||||
collection.removeListener(ACTION_ADD, this._onAdd)
|
||||
collection.removeListener(ACTION_UPDATE, this._onUpdate)
|
||||
collection.removeListener(ACTION_REMOVE, this._onRemove)
|
||||
|
||||
clearObject(this._itemByHash)
|
||||
clearObject(this._keysToHash)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
_onAdd (items) {
|
||||
const {
|
||||
computeHash,
|
||||
_itemByHash: itemByHash,
|
||||
_keysToHash: keysToHash,
|
||||
} = this
|
||||
|
||||
for (const key in items) {
|
||||
const value = items[key]
|
||||
|
||||
const hash = computeHash(value, key)
|
||||
|
||||
if (hash != null) {
|
||||
itemByHash[hash] = value
|
||||
keysToHash[key] = hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdate (items) {
|
||||
const {
|
||||
computeHash,
|
||||
_itemByHash: itemByHash,
|
||||
_keysToHash: keysToHash,
|
||||
} = this
|
||||
|
||||
for (const key in items) {
|
||||
const value = items[key]
|
||||
|
||||
const prev = keysToHash[key]
|
||||
const hash = computeHash(value, key)
|
||||
|
||||
// Removes item from the previous hash's list if any.
|
||||
if (prev != null) delete itemByHash[prev]
|
||||
|
||||
// Inserts item into the new hash's list if any.
|
||||
if (hash != null) {
|
||||
itemByHash[hash] = value
|
||||
keysToHash[key] = hash
|
||||
} else {
|
||||
delete keysToHash[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onRemove (items) {
|
||||
const { _itemByHash: itemByHash, _keysToHash: keysToHash } = this
|
||||
|
||||
for (const key in items) {
|
||||
const prev = keysToHash[key]
|
||||
if (prev != null) {
|
||||
delete itemByHash[prev]
|
||||
delete keysToHash[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
packages/xo-collection/src/unique-index.spec.js
Normal file
131
packages/xo-collection/src/unique-index.spec.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import eventToPromise from 'event-to-promise'
|
||||
import { forEach } from 'lodash'
|
||||
|
||||
import Collection from './collection'
|
||||
import Index from './unique-index'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const waitTicks = (n = 2) => {
|
||||
const { nextTick } = process
|
||||
|
||||
return new Promise(resolve => {
|
||||
;(function waitNextTick () {
|
||||
// The first tick is handled by Promise#then()
|
||||
if (--n) {
|
||||
nextTick(waitNextTick)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
describe('UniqueIndex', function () {
|
||||
let col, byKey
|
||||
const item1 = {
|
||||
id: '2ccb8a72-dc65-48e4-88fe-45ef541f2cba',
|
||||
key: '036dee1b-9a3b-4fb5-be8a-4f535b355581',
|
||||
}
|
||||
const item2 = {
|
||||
id: '7d21dc51-4da8-4538-a2e9-dd6f4784eb76',
|
||||
key: '103cd893-d2cc-4d37-96fd-c259ad04c0d4',
|
||||
}
|
||||
const item3 = {
|
||||
id: '668c1274-4442-44a6-b99a-512188e0bb09',
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
col = new Collection()
|
||||
forEach([item1, item2, item3], item => {
|
||||
col.add(item)
|
||||
})
|
||||
|
||||
byKey = new Index('key')
|
||||
|
||||
col.createIndex('byKey', byKey)
|
||||
|
||||
return waitTicks()
|
||||
})
|
||||
|
||||
it('works with existing items', function () {
|
||||
expect(col.indexes).toEqual({
|
||||
byKey: {
|
||||
[item1.key]: item1,
|
||||
[item2.key]: item2,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('works with added items', function () {
|
||||
const item4 = {
|
||||
id: '823b56c4-4b96-4f3a-9533-5d08177167ac',
|
||||
key: '1437af14-429a-40db-8a51-8a2f5ed03201',
|
||||
}
|
||||
|
||||
col.add(item4)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byKey: {
|
||||
[item1.key]: item1,
|
||||
[item2.key]: item2,
|
||||
[item4.key]: item4,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('works with updated items', function () {
|
||||
const item1bis = {
|
||||
id: item1.id,
|
||||
key: 'e03d4a3a-0331-4aca-97a2-016bbd43a29b',
|
||||
}
|
||||
|
||||
col.update(item1bis)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byKey: {
|
||||
[item1bis.key]: item1bis,
|
||||
[item2.key]: item2,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('works with removed items', function () {
|
||||
col.remove(item2)
|
||||
|
||||
return waitTicks().then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byKey: {
|
||||
[item1.key]: item1,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('correctly updates the value even the same object has the same hash', function () {
|
||||
const item1bis = {
|
||||
id: item1.id,
|
||||
key: item1.key,
|
||||
newProp: true,
|
||||
}
|
||||
|
||||
col.update(item1bis)
|
||||
|
||||
return eventToPromise(col, 'finish').then(() => {
|
||||
expect(col.indexes).toEqual({
|
||||
byKey: {
|
||||
[item1.key]: item1bis,
|
||||
[item2.key]: item2,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
56
packages/xo-collection/src/view.example.js
Normal file
56
packages/xo-collection/src/view.example.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { forEach } from 'lodash'
|
||||
|
||||
import Collection from './collection'
|
||||
import View from './view'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
// Create the collection.
|
||||
const users = new Collection()
|
||||
users.getKey = user => user.name
|
||||
|
||||
// Inserts some data.
|
||||
users.add({
|
||||
name: 'bob',
|
||||
})
|
||||
users.add({
|
||||
name: 'clara',
|
||||
active: true,
|
||||
})
|
||||
users.add({
|
||||
name: 'ophelia',
|
||||
})
|
||||
users.add({
|
||||
name: 'Steve',
|
||||
active: true,
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Create the view.
|
||||
const activeUsers = new View(users, 'active')
|
||||
|
||||
// Register some event listeners to see the changes.
|
||||
activeUsers.on('add', users => {
|
||||
forEach(users, (_, id) => {
|
||||
console.log('+ active user:', id)
|
||||
})
|
||||
})
|
||||
activeUsers.on('remove', users => {
|
||||
forEach(users, (_, id) => {
|
||||
console.log('- active user:', id)
|
||||
})
|
||||
})
|
||||
|
||||
// Make some changes in the future.
|
||||
setTimeout(function () {
|
||||
console.log('-----')
|
||||
|
||||
users.set({
|
||||
name: 'ophelia',
|
||||
active: true,
|
||||
})
|
||||
users.set({
|
||||
name: 'Steve',
|
||||
})
|
||||
}, 10)
|
||||
88
packages/xo-collection/src/view.js
Normal file
88
packages/xo-collection/src/view.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { bind, forEach, iteratee as createCallback } from 'lodash'
|
||||
|
||||
import Collection, {
|
||||
ACTION_ADD,
|
||||
ACTION_UPDATE,
|
||||
ACTION_REMOVE,
|
||||
} from './collection'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default class View extends Collection {
|
||||
constructor (collection, predicate) {
|
||||
super()
|
||||
|
||||
this._collection = collection
|
||||
this._predicate = createCallback(predicate)
|
||||
|
||||
// Handles initial items.
|
||||
this._onAdd(this._collection.all)
|
||||
|
||||
// Bound versions of listeners.
|
||||
this._onAdd = bind(this._onAdd, this)
|
||||
this._onUpdate = bind(this._onUpdate, this)
|
||||
this._onRemove = bind(this._onRemove, this)
|
||||
|
||||
// Register listeners.
|
||||
this._collection.on(ACTION_ADD, this._onAdd)
|
||||
this._collection.on(ACTION_UPDATE, this._onUpdate)
|
||||
this._collection.on(ACTION_REMOVE, this._onRemove)
|
||||
}
|
||||
|
||||
// This method is necessary to free the memory of the view if its
|
||||
// life span is shorter than the collection.
|
||||
destroy () {
|
||||
this._collection.removeListener(ACTION_ADD, this._onAdd)
|
||||
this._collection.removeListener(ACTION_UPDATE, this._onUpdate)
|
||||
this._collection.removeListener(ACTION_REMOVE, this._onRemove)
|
||||
}
|
||||
|
||||
add () {
|
||||
throw new Error('a view is read only')
|
||||
}
|
||||
|
||||
clear () {
|
||||
throw new Error('a view is read only')
|
||||
}
|
||||
|
||||
set () {
|
||||
throw new Error('a view is read only')
|
||||
}
|
||||
|
||||
update () {
|
||||
throw new Error('a view is read only')
|
||||
}
|
||||
|
||||
_onAdd (items) {
|
||||
const { _predicate: predicate } = this
|
||||
|
||||
forEach(items, (value, key) => {
|
||||
if (predicate(value, key, this)) {
|
||||
// super.add() cannot be used because the item may already be
|
||||
// in the view if it was already present at the creation of
|
||||
// the view and its event not already emitted.
|
||||
super.set(key, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_onUpdate (items) {
|
||||
const { _predicate: predicate } = this
|
||||
|
||||
forEach(items, (value, key) => {
|
||||
if (predicate(value, key, this)) {
|
||||
super.set(key, value)
|
||||
} else if (super.has(key)) {
|
||||
super.remove(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_onRemove (items) {
|
||||
forEach(items, (value, key) => {
|
||||
if (super.has(key)) {
|
||||
super.remove(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1
packages/xo-collection/unique-index.js
Normal file
1
packages/xo-collection/unique-index.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/unique-index')
|
||||
1
packages/xo-collection/view.js
Normal file
1
packages/xo-collection/view.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/view')
|
||||
10
packages/xo-common/.npmignore
Normal file
10
packages/xo-common/.npmignore
Normal file
@@ -0,0 +1,10 @@
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
49
packages/xo-common/README.md
Normal file
49
packages/xo-common/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# xo-common [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> Code shared between [XO](https://xen-orchestra.com) server and clients
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/xo-common):
|
||||
|
||||
```
|
||||
> npm install --save xo-common
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
**TODO**
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> npm install
|
||||
|
||||
# Run the tests
|
||||
> npm test
|
||||
|
||||
# Continuously compile
|
||||
> npm run dev
|
||||
|
||||
# Continuously run the tests
|
||||
> npm run dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> npm run build
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
AGPL3 © [Vates SAS](https://vates.fr)
|
||||
1
packages/xo-common/api-errors.js
Normal file
1
packages/xo-common/api-errors.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/api-errors')
|
||||
65
packages/xo-common/package.json
Normal file
65
packages/xo-common/package.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "xo-common",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Code shared between [XO](https://xen-orchestra.com) server and clients",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-common",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@isonoe.net"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/",
|
||||
"*.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.18.0",
|
||||
"lodash": "^4.16.6",
|
||||
"make-error": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.5.2",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.1.3",
|
||||
"rimraf": "^2.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"clean": "rimraf dist/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "yarn run clean",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"babel": {
|
||||
"plugins": [
|
||||
"lodash"
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": "> 1%",
|
||||
"node": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
||||
}
|
||||
166
packages/xo-common/src/api-errors.js
Normal file
166
packages/xo-common/src/api-errors.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import { BaseError } from 'make-error'
|
||||
import { isArray, iteratee } from 'lodash'
|
||||
|
||||
class XoError extends BaseError {
|
||||
constructor ({ code, message, data }) {
|
||||
super(message)
|
||||
this.code = code
|
||||
this.data = data
|
||||
}
|
||||
|
||||
toJsonRpcError () {
|
||||
return {
|
||||
message: this.message,
|
||||
code: this.code,
|
||||
data: this.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const create = (code, getProps) => {
|
||||
const factory = (...args) => new XoError({ ...getProps(...args), code })
|
||||
factory.is = (error, predicate) =>
|
||||
error.code === code && iteratee(predicate)(error)
|
||||
|
||||
return factory
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
export const notImplemented = create(0, () => ({
|
||||
message: 'not implemented',
|
||||
}))
|
||||
|
||||
export const noSuchObject = create(1, (id, type) => ({
|
||||
data: { id, type },
|
||||
message: 'no such object',
|
||||
}))
|
||||
|
||||
export const unauthorized = create(2, () => ({
|
||||
message: 'not authenticated or not enough permissions',
|
||||
}))
|
||||
|
||||
export const invalidCredentials = create(3, () => ({
|
||||
message: 'invalid credentials',
|
||||
}))
|
||||
|
||||
// Deprecated alreadyAuthenticated (4)
|
||||
|
||||
export const forbiddenOperation = create(5, (operation, reason) => ({
|
||||
data: { operation, reason },
|
||||
message: `forbidden operation: ${operation}`,
|
||||
}))
|
||||
|
||||
// Deprecated GenericError (6)
|
||||
|
||||
export const noHostsAvailable = create(7, () => ({
|
||||
message: 'no hosts available',
|
||||
}))
|
||||
|
||||
export const authenticationFailed = create(8, () => ({
|
||||
message: 'authentication failed',
|
||||
}))
|
||||
|
||||
export const serverUnreachable = create(9, objectId => ({
|
||||
data: {
|
||||
objectId,
|
||||
},
|
||||
message: 'server unreachable',
|
||||
}))
|
||||
|
||||
export const invalidParameters = create(10, (message, errors) => {
|
||||
if (isArray(message)) {
|
||||
errors = message
|
||||
message = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
data: { errors },
|
||||
message: message || 'invalid parameters',
|
||||
}
|
||||
})
|
||||
|
||||
export const vmMissingPvDrivers = create(11, ({ vm }) => ({
|
||||
data: {
|
||||
objectId: vm,
|
||||
},
|
||||
message: 'missing PV drivers',
|
||||
}))
|
||||
|
||||
export const vmIsTemplate = create(12, ({ vm }) => ({
|
||||
data: {
|
||||
objectId: vm,
|
||||
},
|
||||
message: 'VM is a template',
|
||||
}))
|
||||
|
||||
// TODO: We should probably create a more generic error which gathers all incorrect state errors.
|
||||
// e.g.:
|
||||
// incorrectState {
|
||||
// data: {
|
||||
// objectId: 'af43e227-3deb-4822-a79b-968825de72eb',
|
||||
// property: 'power_state',
|
||||
// actual: 'Running',
|
||||
// expected: 'Halted'
|
||||
// },
|
||||
// message: 'incorrect state'
|
||||
// }
|
||||
export const vmBadPowerState = create(13, ({ vm, expected, actual }) => ({
|
||||
data: {
|
||||
objectId: vm,
|
||||
expected,
|
||||
actual,
|
||||
},
|
||||
message: `VM state is ${actual} but should be ${expected}`,
|
||||
}))
|
||||
|
||||
export const vmLacksFeature = create(14, ({ vm, feature }) => ({
|
||||
data: {
|
||||
objectId: vm,
|
||||
feature,
|
||||
},
|
||||
message: `VM lacks feature ${feature || ''}`,
|
||||
}))
|
||||
|
||||
export const notSupportedDuringUpgrade = create(15, () => ({
|
||||
message: 'not supported during upgrade',
|
||||
}))
|
||||
|
||||
export const objectAlreadyExists = create(16, ({ objectId, objectType }) => ({
|
||||
data: {
|
||||
objectId,
|
||||
objectType,
|
||||
},
|
||||
message: `${objectType || 'object'} already exists`,
|
||||
}))
|
||||
|
||||
export const vdiInUse = create(17, ({ vdi, operation }) => ({
|
||||
data: {
|
||||
objectId: vdi,
|
||||
operation,
|
||||
},
|
||||
message: 'VDI in use',
|
||||
}))
|
||||
|
||||
export const hostOffline = create(18, ({ host }) => ({
|
||||
data: {
|
||||
objectId: host,
|
||||
},
|
||||
message: 'host offline',
|
||||
}))
|
||||
|
||||
export const operationBlocked = create(19, ({ objectId, code }) => ({
|
||||
data: {
|
||||
objectId,
|
||||
code,
|
||||
},
|
||||
message: 'operation blocked',
|
||||
}))
|
||||
|
||||
export const patchPrecheckFailed = create(20, ({ errorType, patch }) => ({
|
||||
data: {
|
||||
objectId: patch,
|
||||
errorType,
|
||||
},
|
||||
message: `patch precheck failed: ${errorType}`,
|
||||
}))
|
||||
24
packages/xo-lib/.npmignore
Normal file
24
packages/xo-lib/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
168
packages/xo-lib/README.md
Normal file
168
packages/xo-lib/README.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# xo-lib [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> Library to connect to XO-Server.
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/xo-lib):
|
||||
|
||||
```
|
||||
npm install --save xo-lib
|
||||
```
|
||||
|
||||
Then require the package:
|
||||
|
||||
```javascript
|
||||
import Xo from 'xo-lib'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
> If the URL is not provided and the current environment is a web
|
||||
> browser, the location of the current page will be used.
|
||||
|
||||
```javascript
|
||||
// Connect to XO.
|
||||
const xo = new Xo({ url: 'https://xo.company.tld' })
|
||||
|
||||
// Let's start by opening the connection.
|
||||
await xo.open()
|
||||
|
||||
// Must sign in before being able to call any methods (all calls will
|
||||
// be buffered until signed in).
|
||||
await xo.signIn({
|
||||
email: 'admin@admin.net',
|
||||
password: 'admin'
|
||||
})
|
||||
|
||||
console('signed as', xo.user)
|
||||
```
|
||||
|
||||
The credentials can also be passed directly to the constructor:
|
||||
|
||||
```javascript
|
||||
const xo = Xo({
|
||||
url: 'https://xo.company.tld',
|
||||
credentials: {
|
||||
email: 'admin@admin.net',
|
||||
password: 'admin',
|
||||
}
|
||||
})
|
||||
|
||||
xo.open()
|
||||
|
||||
xo.on('authenticated', () => {
|
||||
console.log(xo.user)
|
||||
})
|
||||
```
|
||||
|
||||
> If the URL is not provided and the current environment is a web
|
||||
> browser, the location of the current page will be used.
|
||||
|
||||
### Connection
|
||||
|
||||
```javascript
|
||||
await xo.open()
|
||||
|
||||
console.log('connected')
|
||||
```
|
||||
|
||||
### Disconnection
|
||||
|
||||
```javascript
|
||||
xo.close()
|
||||
|
||||
console.log('disconnected')
|
||||
```
|
||||
|
||||
### Method call
|
||||
|
||||
```javascript
|
||||
const token = await xo.call('token.create')
|
||||
|
||||
console.log('Token created', token)
|
||||
```
|
||||
|
||||
### Status
|
||||
|
||||
The connection status is available through the status property which
|
||||
is *open*, *connecting* or *closed*.
|
||||
|
||||
```javascript
|
||||
console.log('%s to xo-server', xo.status)
|
||||
```
|
||||
|
||||
### Current user
|
||||
|
||||
Information about the user account used to sign in is available
|
||||
through the `user` property.
|
||||
|
||||
```javascript
|
||||
console.log('Current user is', xo.user)
|
||||
```
|
||||
|
||||
> This property is null when the status is not connected.
|
||||
|
||||
### Events
|
||||
|
||||
```javascript
|
||||
xo.on('open', () => {
|
||||
console.log('connected')
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
xo.on('closed', () => {
|
||||
console.log('disconnected')
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
xo.on('notification', function (notif) {
|
||||
console.log('notification:', notif.method, notif.params)
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
xo.on('authenticated', () => {
|
||||
console.log('authenticated as', xo.user)
|
||||
})
|
||||
|
||||
xo.on('authenticationFailure', () => {
|
||||
console.log('failed to authenticate')
|
||||
})
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> npm install
|
||||
|
||||
# Run the tests
|
||||
> npm test
|
||||
|
||||
# Continuously compile
|
||||
> npm run dev
|
||||
|
||||
# Continuously run the tests
|
||||
> npm run dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> npm run build
|
||||
```
|
||||
|
||||
## 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 © [Vates SAS](http://vates.fr)
|
||||
62
packages/xo-lib/example.js
Normal file
62
packages/xo-lib/example.js
Normal file
@@ -0,0 +1,62 @@
|
||||
'use strict'
|
||||
|
||||
process.on('unhandledRejection', function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
const Xo = require('./').default
|
||||
|
||||
const xo = new Xo({
|
||||
url: 'localhost:9000',
|
||||
})
|
||||
|
||||
xo
|
||||
.open()
|
||||
.then(function () {
|
||||
return xo
|
||||
.call('acl.get', {})
|
||||
.then(function (result) {
|
||||
console.log('success:', result)
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('failure:', error)
|
||||
})
|
||||
})
|
||||
.then(function () {
|
||||
return xo
|
||||
.signIn({
|
||||
email: 'admin@admin.net',
|
||||
password: 'admin',
|
||||
})
|
||||
.then(function () {
|
||||
console.log('connected as ', xo.user)
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('failure:', error)
|
||||
})
|
||||
})
|
||||
.then(function () {
|
||||
return xo
|
||||
.signIn({
|
||||
email: 'tom',
|
||||
password: 'tom',
|
||||
})
|
||||
.then(function () {
|
||||
console.log('connected as', xo.user)
|
||||
|
||||
return xo
|
||||
.call('acl.get', {})
|
||||
.then(function (result) {
|
||||
console.log('success:', result)
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('failure:', error)
|
||||
})
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('failure', error)
|
||||
})
|
||||
})
|
||||
.then(function () {
|
||||
return xo.close()
|
||||
})
|
||||
73
packages/xo-lib/package.json
Normal file
73
packages/xo-lib/package.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "xo-lib",
|
||||
"version": "0.9.0",
|
||||
"license": "ISC",
|
||||
"description": "Library to connect to XO-Server",
|
||||
"keywords": [
|
||||
"xen",
|
||||
"orchestra",
|
||||
"xen-orchestra"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-lib",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@vates.fr"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonrpc-websocket-client": "^0.3.0",
|
||||
"lodash": "^4.17.2",
|
||||
"make-error": "^1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"babel-preset-env": "^1.5.2",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.1.3",
|
||||
"rimraf": "^2.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"babel": {
|
||||
"env": {
|
||||
"test": {
|
||||
"ignore": null
|
||||
}
|
||||
},
|
||||
"ignore": "*.spec.js",
|
||||
"plugins": [
|
||||
"lodash"
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": "> 2%",
|
||||
"node": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
||||
}
|
||||
81
packages/xo-lib/src/index.js
Normal file
81
packages/xo-lib/src/index.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import JsonRpcWebSocketClient, { OPEN, CLOSED } from 'jsonrpc-websocket-client'
|
||||
import { BaseError } from 'make-error'
|
||||
import { startsWith } from 'lodash'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export class XoError extends BaseError {}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export default class Xo extends JsonRpcWebSocketClient {
|
||||
constructor (opts) {
|
||||
const url = opts != null ? opts.url : '.'
|
||||
super(`${url === '/' ? '' : url}/api/`)
|
||||
|
||||
this._credentials = opts != null ? opts.credentials : null
|
||||
this._user = null
|
||||
|
||||
this.on(OPEN, () => {
|
||||
if (this._credentials) {
|
||||
this._signIn(this._credentials).catch(noop)
|
||||
}
|
||||
})
|
||||
this.on(CLOSED, () => {
|
||||
this._user = null
|
||||
})
|
||||
}
|
||||
|
||||
get user () {
|
||||
return this._user
|
||||
}
|
||||
|
||||
call (method, args, i) {
|
||||
if (startsWith(method, 'session.')) {
|
||||
return Promise.reject(
|
||||
new XoError('session.*() methods are disabled from this interface')
|
||||
)
|
||||
}
|
||||
|
||||
const promise = super.call(method, args)
|
||||
promise.retry = predicate =>
|
||||
promise.catch(error => {
|
||||
i = (i || 0) + 1
|
||||
if (predicate(error, i)) {
|
||||
return this.call(method, args, i)
|
||||
}
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
refreshUser () {
|
||||
return super.call('session.getUser').then(user => {
|
||||
return (this._user = user)
|
||||
})
|
||||
}
|
||||
|
||||
signIn (credentials) {
|
||||
// Register this credentials for future use.
|
||||
this._credentials = credentials
|
||||
|
||||
return this._signIn(credentials)
|
||||
}
|
||||
|
||||
_signIn (credentials) {
|
||||
return super.call('session.signIn', credentials).then(
|
||||
user => {
|
||||
this._user = user
|
||||
this.emit('authenticated')
|
||||
},
|
||||
error => {
|
||||
this.emit('authenticationFailure', error)
|
||||
throw error
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
24
packages/xo-remote-parser/.npmignore
Normal file
24
packages/xo-remote-parser/.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
/benchmark/
|
||||
/benchmarks/
|
||||
*.bench.js
|
||||
*.bench.js.map
|
||||
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/fixture/
|
||||
/fixtures/
|
||||
*.fixture.js
|
||||
*.fixture.js.map
|
||||
*.fixtures.js
|
||||
*.fixtures.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
|
||||
__snapshots__/
|
||||
49
packages/xo-remote-parser/README.md
Normal file
49
packages/xo-remote-parser/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# ${pkg.name} [](https://travis-ci.org/${pkg.shortGitHubPath})
|
||||
|
||||
> ${pkg.description}
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/${pkg.name}):
|
||||
|
||||
```
|
||||
> npm install --save ${pkg.name}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
**TODO**
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> npm install
|
||||
|
||||
# Run the tests
|
||||
> npm test
|
||||
|
||||
# Continuously compile
|
||||
> npm run dev
|
||||
|
||||
# Continuously run the tests
|
||||
> npm run dev-test
|
||||
|
||||
# Build for production (automatically called by npm install)
|
||||
> npm run build
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are *very* welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](${pkg.bugs})
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
${pkg.license} © [${pkg.author.name}](${pkg.author.url})
|
||||
62
packages/xo-remote-parser/package.json
Normal file
62
packages/xo-remote-parser/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "xo-remote-parser",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-remote-parser",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Fabrice Marsaud",
|
||||
"email": "fabrice.marsaud@vates.fr"
|
||||
},
|
||||
"preferGlobal": false,
|
||||
"main": "dist/",
|
||||
"bin": {},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"babel-preset-env": "^1.5.2",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.1.3",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"rimraf": "^2.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"babel": {
|
||||
"plugins": [
|
||||
"lodash"
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": "> 5%",
|
||||
"node": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user