Compare commits
4340 Commits
xo-server-
...
improveFor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d70da2a960 | ||
|
|
637eb1d2d7 | ||
|
|
86b86c5c99 | ||
|
|
0b8525febe | ||
|
|
a3ea70c61c | ||
|
|
ae0f3b4fe0 | ||
|
|
2552ef37d2 | ||
|
|
9803e8c6cb | ||
|
|
3410cbc3b9 | ||
|
|
93fce0d4bf | ||
|
|
dbdc5f3e3b | ||
|
|
581b42fa9d | ||
|
|
e07e2d3ccd | ||
|
|
ad928ec23d | ||
|
|
1d7559ded2 | ||
|
|
9099b58557 | ||
|
|
9e70397240 | ||
|
|
5f69b0e9a0 | ||
|
|
2a9bff1607 | ||
|
|
9e621d7de8 | ||
|
|
3e5c73528d | ||
|
|
397b5cd56d | ||
|
|
55cb6042e8 | ||
|
|
339d920b78 | ||
|
|
f14f716f3d | ||
|
|
fb83d1fc98 | ||
|
|
62208e7847 | ||
|
|
df91772f5c | ||
|
|
cf8a9d40be | ||
|
|
93d1c6c3fc | ||
|
|
f1fa811e5c | ||
|
|
5a9812c492 | ||
|
|
b53d613a64 | ||
|
|
225a67ae3b | ||
|
|
c7eb7db463 | ||
|
|
edfa729672 | ||
|
|
77d9798319 | ||
|
|
680f1e2f07 | ||
|
|
7c009b0fc0 | ||
|
|
eb7de4f2dd | ||
|
|
2378399981 | ||
|
|
37b2113763 | ||
|
|
5048485a85 | ||
|
|
9e667533e9 | ||
|
|
1fac7922b4 | ||
|
|
1a0e5eb6fc | ||
|
|
321e322492 | ||
|
|
8834af65f7 | ||
|
|
1a1dd0531d | ||
|
|
8752487280 | ||
|
|
4b12a6d31d | ||
|
|
2924f82754 | ||
|
|
9b236a6191 | ||
|
|
a3b8553cec | ||
|
|
00a1778a6d | ||
|
|
3b6bc629bc | ||
|
|
04dfd9a02c | ||
|
|
fb52868074 | ||
|
|
77d53d2abf | ||
|
|
6afb87def1 | ||
|
|
8bfe293414 | ||
|
|
2e634a9d1c | ||
|
|
bea771ca90 | ||
|
|
99e3622f31 | ||
|
|
a16522241e | ||
|
|
b86cb12649 | ||
|
|
2af74008b2 | ||
|
|
2e689592f1 | ||
|
|
3f8436b58b | ||
|
|
e3dd59d684 | ||
|
|
549d9b70a9 | ||
|
|
3bf6aae103 | ||
|
|
afb110c473 | ||
|
|
8727c3cf96 | ||
|
|
b13302ddeb | ||
|
|
e89ed06314 | ||
|
|
e3f57998f7 | ||
|
|
8cdb5ee31b | ||
|
|
5b734db656 | ||
|
|
e853f9d04f | ||
|
|
2a5e09719e | ||
|
|
3c0477e0da | ||
|
|
060d1c5297 | ||
|
|
55dd7bfb9c | ||
|
|
b00cf13029 | ||
|
|
73755e4ccf | ||
|
|
a1bd96da6a | ||
|
|
0e934c1413 | ||
|
|
eb69234a8e | ||
|
|
7659d9c0be | ||
|
|
2ba81d55f8 | ||
|
|
2e1abad255 | ||
|
|
c7d5b4b063 | ||
|
|
cc5f4b0996 | ||
|
|
55f627ed83 | ||
|
|
988179a3f0 | ||
|
|
ce617e0732 | ||
|
|
f0f429a473 | ||
|
|
bb6e158301 | ||
|
|
7ff304a042 | ||
|
|
7df1994d7f | ||
|
|
a3a2fda157 | ||
|
|
d8530f9518 | ||
|
|
d3062ac35c | ||
|
|
b11f11f4db | ||
|
|
79d48f3b56 | ||
|
|
869f7ffab0 | ||
|
|
6665d6a8e6 | ||
|
|
8eb0bdbda7 | ||
|
|
710689db0b | ||
|
|
801eea7e75 | ||
|
|
7885e1e6e7 | ||
|
|
d384c746ca | ||
|
|
a30d962b1d | ||
|
|
b6e078716b | ||
|
|
34b69c7ee8 | ||
|
|
70bf8d9620 | ||
|
|
c8bfda9cf5 | ||
|
|
1eb4c20844 | ||
|
|
e5c5f19219 | ||
|
|
db92f0e365 | ||
|
|
570de7c0fe | ||
|
|
90e0f26845 | ||
|
|
c714bc3518 | ||
|
|
48e0acda32 | ||
|
|
013cdbcd96 | ||
|
|
fdd886f213 | ||
|
|
de70ef3064 | ||
|
|
9142a95f79 | ||
|
|
1c6aebf997 | ||
|
|
7b9ec4b7a7 | ||
|
|
decb87f0c9 | ||
|
|
e17470f56c | ||
|
|
99ddbcdc67 | ||
|
|
6953e2fe7b | ||
|
|
beb1063ba1 | ||
|
|
7773edd590 | ||
|
|
0104649b84 | ||
|
|
1c9d1049e0 | ||
|
|
d992a4cb87 | ||
|
|
52114ad4b0 | ||
|
|
bcc62cfcaf | ||
|
|
60434b136a | ||
|
|
13f3c8851d | ||
|
|
f386f94dc2 | ||
|
|
fda1fd1a04 | ||
|
|
0b17bdd9bc | ||
|
|
2c5706a89b | ||
|
|
5448452b71 | ||
|
|
22e7c126e6 | ||
|
|
750fefe957 | ||
|
|
025e671989 | ||
|
|
df0ed5e794 | ||
|
|
da45ace7c1 | ||
|
|
2a623b8ae7 | ||
|
|
f034ec45f3 | ||
|
|
970bc0ac5d | ||
|
|
3abbc8d57e | ||
|
|
06570d78a0 | ||
|
|
6a0df7aec2 | ||
|
|
30aeb95f3a | ||
|
|
36d6d53a26 | ||
|
|
895773b6c6 | ||
|
|
8ebc0dba4f | ||
|
|
006f12f17f | ||
|
|
b22239804a | ||
|
|
afd174ca21 | ||
|
|
27c6c1b896 | ||
|
|
311b420b74 | ||
|
|
e403298140 | ||
|
|
9c7fd94a9b | ||
|
|
8cdae83150 | ||
|
|
5b1cc7415e | ||
|
|
f5d3bc1f2d | ||
|
|
ba81d0e08a | ||
|
|
3b3f927e4b | ||
|
|
5e8539865f | ||
|
|
3a3fa2882c | ||
|
|
3baa37846e | ||
|
|
999fba2030 | ||
|
|
785a5857ef | ||
|
|
067f4ac882 | ||
|
|
8a26e08102 | ||
|
|
42aa202f7a | ||
|
|
403d2c8e7b | ||
|
|
ad46bde302 | ||
|
|
1b6ec2c545 | ||
|
|
56388557cb | ||
|
|
1ddbe87d0f | ||
|
|
3081810450 | ||
|
|
155be7fd95 | ||
|
|
ef960e94d3 | ||
|
|
bfd99a48fe | ||
|
|
a13fda5fe9 | ||
|
|
66bee59774 | ||
|
|
685400bbf8 | ||
|
|
5bef8fc411 | ||
|
|
aa7ff1449a | ||
|
|
3dca7f2a71 | ||
|
|
3dc2f649f6 | ||
|
|
9eb537c2f9 | ||
|
|
dfd5f6882f | ||
|
|
7214016338 | ||
|
|
606e3c4ce5 | ||
|
|
fb04d3d25d | ||
|
|
db8c042131 | ||
|
|
fd9005fba8 | ||
|
|
2d25413b8d | ||
|
|
035679800a | ||
|
|
abd0a3035a | ||
|
|
d307730c68 | ||
|
|
1b44de4958 | ||
|
|
ec78a1ce8b | ||
|
|
19c82ab30d | ||
|
|
9986f3fb18 | ||
|
|
d24e9c093d | ||
|
|
70c8b24fac | ||
|
|
9c9c11104b | ||
|
|
cba90b27f4 | ||
|
|
46cbced570 | ||
|
|
52cf2d1514 | ||
|
|
e51351be8d | ||
|
|
2a42e0ff94 | ||
|
|
3a824a2bfc | ||
|
|
fc1c809a18 | ||
|
|
221cd40199 | ||
|
|
aca19d9a81 | ||
|
|
0601bbe18d | ||
|
|
2d52aee952 | ||
|
|
99605bf185 | ||
|
|
91b19d9bc4 | ||
|
|
562401ebe4 | ||
|
|
6fd2f2610d | ||
|
|
6ae19b0640 | ||
|
|
6b936d8a8c | ||
|
|
8f2cfaae00 | ||
|
|
5c215e1a8a | ||
|
|
e3cb98124f | ||
|
|
90c3319880 | ||
|
|
348db876d2 | ||
|
|
408fd7ec03 | ||
|
|
1fd84836b1 | ||
|
|
522204795f | ||
|
|
e29c422ac9 | ||
|
|
152cf09b7e | ||
|
|
ff728099dc | ||
|
|
706d94221d | ||
|
|
340e9af7f4 | ||
|
|
40e536ba61 | ||
|
|
fd4c56c8c2 | ||
|
|
20d04ba956 | ||
|
|
3b1bcc67ae | ||
|
|
1add3fbf9d | ||
|
|
97f0759de0 | ||
|
|
005ab47d9b | ||
|
|
14a0caa4c6 | ||
|
|
1c23bd5ff7 | ||
|
|
49c161b17a | ||
|
|
18dce3fce6 | ||
|
|
d6fc86b6bc | ||
|
|
61d960d4b1 | ||
|
|
02d3465832 | ||
|
|
4bbadc9515 | ||
|
|
78586291ca | ||
|
|
945dec94bf | ||
|
|
003140d96b | ||
|
|
363d7cf0d0 | ||
|
|
f0c94496bf | ||
|
|
de217eabd9 | ||
|
|
7c80d0c1e1 | ||
|
|
9fb749b1db | ||
|
|
ad9c59669a | ||
|
|
76a038e403 | ||
|
|
0e12072922 | ||
|
|
158a8e14a2 | ||
|
|
0c97910349 | ||
|
|
8347ac6ed8 | ||
|
|
996abd6e7e | ||
|
|
de8abd5b63 | ||
|
|
3de928c488 | ||
|
|
a2a514e483 | ||
|
|
ff432e04b0 | ||
|
|
4502590bb0 | ||
|
|
6d440a5af5 | ||
|
|
0840b4c359 | ||
|
|
696ee7dbe5 | ||
|
|
5e23e356ce | ||
|
|
c705051a89 | ||
|
|
ce2b918a29 | ||
|
|
df740b1e8e | ||
|
|
c3e0308ad0 | ||
|
|
1005e295b2 | ||
|
|
b3cf58b8c0 | ||
|
|
2652c87917 | ||
|
|
9e0b5575a4 | ||
|
|
56c089dc01 | ||
|
|
3b94da1790 | ||
|
|
ec39a8e9fe | ||
|
|
6339f971ca | ||
|
|
2978ad1486 | ||
|
|
c0d6dc48de | ||
|
|
f327422254 | ||
|
|
938d15d31b | ||
|
|
5ab1ddb9cb | ||
|
|
01302d7a60 | ||
|
|
c68630e2d6 | ||
|
|
db082bfbe9 | ||
|
|
650d88db46 | ||
|
|
7d1ecca669 | ||
|
|
5f71e629ae | ||
|
|
68205d4676 | ||
|
|
cdb466225d | ||
|
|
0e7fbd598f | ||
|
|
99147c893d | ||
|
|
c63fb6173d | ||
|
|
5932ada717 | ||
|
|
0d579748d6 | ||
|
|
8c5ee4eafe | ||
|
|
b03935ad2f | ||
|
|
38439cbc43 | ||
|
|
161c20b534 | ||
|
|
603696dad1 | ||
|
|
6b2ad5a7cc | ||
|
|
88063d4d87 | ||
|
|
8956a99745 | ||
|
|
0f0c0ec0d0 | ||
|
|
e5932e2c33 | ||
|
|
84ec8f5f3c | ||
|
|
661c5a269f | ||
|
|
5c6d7cae66 | ||
|
|
fcc73859b7 | ||
|
|
36645b0319 | ||
|
|
a62575e3cf | ||
|
|
d7af3d3c03 | ||
|
|
130ebb7d5f | ||
|
|
2af845ebd3 | ||
|
|
8e4d1701e6 | ||
|
|
4d16b6708f | ||
|
|
34ee08be25 | ||
|
|
d66a76a09e | ||
|
|
0d801c9766 | ||
|
|
b82b676fdb | ||
|
|
3494c0f64f | ||
|
|
311098adc2 | ||
|
|
58182e2083 | ||
|
|
a62ae43274 | ||
|
|
f256610e08 | ||
|
|
983d048219 | ||
|
|
3c6033f904 | ||
|
|
ef2bd2b59d | ||
|
|
04d70e9aa8 | ||
|
|
a2587ffc0a | ||
|
|
6776e7bb3d | ||
|
|
4c05064294 | ||
|
|
c135f1394f | ||
|
|
d68f4215f1 | ||
|
|
af562f3c3a | ||
|
|
7b949716bc | ||
|
|
d3e256289b | ||
|
|
3688e762b1 | ||
|
|
249f1a7af4 | ||
|
|
2de26030ff | ||
|
|
fcc76fb8d0 | ||
|
|
88d5b7095e | ||
|
|
b0e55d88de | ||
|
|
370ad3e928 | ||
|
|
07bf77d2dd | ||
|
|
a5ec65f3c0 | ||
|
|
522b318fd9 | ||
|
|
9eb2a4033f | ||
|
|
e87b0c393a | ||
|
|
1fb7e665fa | ||
|
|
7ea476d787 | ||
|
|
8260d07d61 | ||
|
|
ac0b4e6514 | ||
|
|
27b2f8cf27 | ||
|
|
27b5737f65 | ||
|
|
55b2e0292f | ||
|
|
464d83e70f | ||
|
|
614255a73a | ||
|
|
90d15e1346 | ||
|
|
b0e2ea64e9 | ||
|
|
1da05e239d | ||
|
|
fe7f0db81f | ||
|
|
983153e620 | ||
|
|
6fe791dcf2 | ||
|
|
1ad406c7dd | ||
|
|
4e032e11b1 | ||
|
|
ea34516d73 | ||
|
|
e1145f35ee | ||
|
|
6864775b8a | ||
|
|
f28721b847 | ||
|
|
2dc174fd9d | ||
|
|
07142d0410 | ||
|
|
41bb16ca30 | ||
|
|
d8f1034858 | ||
|
|
52b3c49cdb | ||
|
|
c5cb1a5e96 | ||
|
|
92d9d3232c | ||
|
|
9c4e0464f0 | ||
|
|
72d25754fd | ||
|
|
1465a0ba59 | ||
|
|
ac8ce28286 | ||
|
|
c4b06e1915 | ||
|
|
f77675a8a3 | ||
|
|
b907c1fd03 | ||
|
|
fba86bf653 | ||
|
|
b18ebcc38d | ||
|
|
4f7f18458e | ||
|
|
d412196052 | ||
|
|
1d140d8fd2 | ||
|
|
6948a25b09 | ||
|
|
26131917e3 | ||
|
|
44a0ab6d0a | ||
|
|
2b8b033ad7 | ||
|
|
3ee0b3e7df | ||
|
|
927a55ab30 | ||
|
|
b70721cb60 | ||
|
|
f71c820f15 | ||
|
|
74e0405a5e | ||
|
|
79b55ba30a | ||
|
|
ee0adaebc5 | ||
|
|
83c5c976e3 | ||
|
|
18bd2c607e | ||
|
|
e2695ce327 | ||
|
|
3f316fcaea | ||
|
|
8b7b162c76 | ||
|
|
aa36629def | ||
|
|
ca345bd6d8 | ||
|
|
61324d10f9 | ||
|
|
92fd92ae63 | ||
|
|
e48bfa2c88 | ||
|
|
cd5762fa19 | ||
|
|
71f7a6cd6c | ||
|
|
b8cade8b7a | ||
|
|
696c6f13f0 | ||
|
|
b8d923d3ba | ||
|
|
1a96c1bf0f | ||
|
|
14a01d0141 | ||
|
|
74a2a4d2e5 | ||
|
|
b13b44cfd0 | ||
|
|
50a164423a | ||
|
|
a40d50a3bd | ||
|
|
529e33140a | ||
|
|
132b1a41db | ||
|
|
75948b2977 | ||
|
|
eb84d4a7ef | ||
|
|
1816d0240e | ||
|
|
2c6d36b63e | ||
|
|
d9776ae8ed | ||
|
|
b456394663 | ||
|
|
94f599bdbd | ||
|
|
d466ca143a | ||
|
|
78ed85a49f | ||
|
|
c24e7f9ecd | ||
|
|
98caa89625 | ||
|
|
8e176eadb1 | ||
|
|
444268406f | ||
|
|
7e062977d0 | ||
|
|
f4bf56f159 | ||
|
|
9f3b020361 | ||
|
|
ef35021a44 | ||
|
|
b74ebd050a | ||
|
|
8a16d6aa3b | ||
|
|
cf7393992c | ||
|
|
c576114dad | ||
|
|
deeb399046 | ||
|
|
9cf8f8f492 | ||
|
|
28b7e99ebc | ||
|
|
0ba729e5b9 | ||
|
|
ac8c146cf7 | ||
|
|
2ba437be31 | ||
|
|
bd8bb73309 | ||
|
|
485c2f4669 | ||
|
|
6fb562d92f | ||
|
|
85efdcf7b9 | ||
|
|
fc1357d5d6 | ||
|
|
88b015bda4 | ||
|
|
b46f76cccf | ||
|
|
c3bb2185c2 | ||
|
|
a240853fe0 | ||
|
|
d7ce609940 | ||
|
|
1b0ec9839e | ||
|
|
77b166bb3b | ||
|
|
76bd54d7de | ||
|
|
684282f0a4 | ||
|
|
2459f46c19 | ||
|
|
5f0466e4d8 | ||
|
|
3738edfa83 | ||
|
|
769e27e2cb | ||
|
|
8ec5461338 | ||
|
|
4a2843cb67 | ||
|
|
a0e69a79ab | ||
|
|
3da94f18df | ||
|
|
17cb59b898 | ||
|
|
315e5c9289 | ||
|
|
01ba10fedb | ||
|
|
13e7594560 | ||
|
|
f9ac2ac84d | ||
|
|
09cfac1111 | ||
|
|
008f7a30fd | ||
|
|
ff65dbcba7 | ||
|
|
264a0d1678 | ||
|
|
7dcaf454ed | ||
|
|
17b2756291 | ||
|
|
57e48b5d34 | ||
|
|
57ed984e5a | ||
|
|
100122f388 | ||
|
|
12d4b3396e | ||
|
|
ab35c710cb | ||
|
|
4bd5b38aeb | ||
|
|
836db1b807 | ||
|
|
73d88cc5f1 | ||
|
|
3def66d968 | ||
|
|
3f73138fc3 | ||
|
|
bfe621a21d | ||
|
|
32fa792eeb | ||
|
|
a833050fc2 | ||
|
|
e7e6294bc3 | ||
|
|
7c71884e27 | ||
|
|
3e822044f2 | ||
|
|
d457f5fca4 | ||
|
|
1837e01719 | ||
|
|
f17f5abf0f | ||
|
|
82c229c755 | ||
|
|
c7e3ba3184 | ||
|
|
470c9bb6c8 | ||
|
|
bb3ab20b2a | ||
|
|
90ce1c4d1e | ||
|
|
5c436f3870 | ||
|
|
159339625d | ||
|
|
87e6f7fded | ||
|
|
fd2c7c2fc3 | ||
|
|
7fc76c1df4 | ||
|
|
f2758d036d | ||
|
|
ac670da793 | ||
|
|
c0465eb4d9 | ||
|
|
cea55b03e5 | ||
|
|
d78d802066 | ||
|
|
a562c74492 | ||
|
|
d1f2e0a84b | ||
|
|
49e2d128ad | ||
|
|
f587798fb0 | ||
|
|
3430ee743b | ||
|
|
83299587b0 | ||
|
|
7c0ecf9b06 | ||
|
|
abfd84d32c | ||
|
|
0583a978be | ||
|
|
75989cf92d | ||
|
|
f1cc284b6f | ||
|
|
0444cf0b3b | ||
|
|
226f9ad964 | ||
|
|
a956cb2ac9 | ||
|
|
76a91cc5e9 | ||
|
|
f012d126b9 | ||
|
|
bae0b52893 | ||
|
|
a24512cea9 | ||
|
|
84b75e8a58 | ||
|
|
6e25b7a83a | ||
|
|
136718df7e | ||
|
|
d48ef1f810 | ||
|
|
9e60c53750 | ||
|
|
f3c5e817a3 | ||
|
|
60f6e54da1 | ||
|
|
f5a59caca2 | ||
|
|
6ea671a434 | ||
|
|
036f3f6bd0 | ||
|
|
12552a1391 | ||
|
|
e9b658b60d | ||
|
|
15f69a19f5 | ||
|
|
54d885fa9c | ||
|
|
11cc299940 | ||
|
|
091b0a3ef3 | ||
|
|
87874a4b81 | ||
|
|
86aaa50946 | ||
|
|
68b2c287eb | ||
|
|
61f1316c42 | ||
|
|
afadc8f95a | ||
|
|
955ef6806c | ||
|
|
4d55c5ae48 | ||
|
|
5c6ae1912b | ||
|
|
083483645e | ||
|
|
c077e9a699 | ||
|
|
280b60808f | ||
|
|
eb9608b893 | ||
|
|
a29f3d67ea | ||
|
|
6b150dc8a8 | ||
|
|
8f55884602 | ||
|
|
2fdba2eb0f | ||
|
|
7e4bd30f04 | ||
|
|
eb8f098aaf | ||
|
|
5237fdd387 | ||
|
|
8a07b7a3db | ||
|
|
a41037833c | ||
|
|
6a780d94a3 | ||
|
|
506ef0b44f | ||
|
|
a4d1d41b6a | ||
|
|
4e9477f34a | ||
|
|
43b6285437 | ||
|
|
c26a7a3e51 | ||
|
|
93eb42785d | ||
|
|
02bb622e92 | ||
|
|
b873c147a6 | ||
|
|
5e7fb7a881 | ||
|
|
97790313eb | ||
|
|
954b29cb61 | ||
|
|
dc6a13962f | ||
|
|
23da202790 | ||
|
|
f237101b4a | ||
|
|
8a99326a76 | ||
|
|
8c95974e65 | ||
|
|
3f7454efad | ||
|
|
e5c890e29b | ||
|
|
53e0f17c55 | ||
|
|
34f6be868e | ||
|
|
c84b899276 | ||
|
|
266a26fa31 | ||
|
|
bbf92be652 | ||
|
|
e19c7b949d | ||
|
|
5ce6f1fe4d | ||
|
|
9c36520c79 | ||
|
|
a85a8ea208 | ||
|
|
c2e0c97d94 | ||
|
|
a5447fda3c | ||
|
|
507e9a55c2 | ||
|
|
5bd0eb3362 | ||
|
|
458496a09e | ||
|
|
f13a98b6b8 | ||
|
|
dde32724b1 | ||
|
|
63b76fdb50 | ||
|
|
1b9cd56e9f | ||
|
|
784b0dded8 | ||
|
|
4a658787de | ||
|
|
4beb49041d | ||
|
|
1f6e29084f | ||
|
|
7c6cb2454b | ||
|
|
96720d186c | ||
|
|
a45fb88c48 | ||
|
|
b4b0a925af | ||
|
|
72822c9529 | ||
|
|
ca6cdbf9cf | ||
|
|
74cd35f527 | ||
|
|
010866a0ef | ||
|
|
5885df4ae9 | ||
|
|
89bc6da5f4 | ||
|
|
d87f698512 | ||
|
|
08dd871cb8 | ||
|
|
b5578eadf7 | ||
|
|
aadc1bb84c | ||
|
|
2823af9441 | ||
|
|
d7da83359f | ||
|
|
a143cd3427 | ||
|
|
3c4dcde1d4 | ||
|
|
7adfc195dc | ||
|
|
5a2c315b20 | ||
|
|
299803f03c | ||
|
|
1eac62a26e | ||
|
|
f1b5416d0b | ||
|
|
65168c8532 | ||
|
|
35f6476d0f | ||
|
|
36fabe194f | ||
|
|
921c700fab | ||
|
|
2dbe35a31c | ||
|
|
656d13d79b | ||
|
|
77b1adae37 | ||
|
|
c18373bb0e | ||
|
|
d4e7563272 | ||
|
|
86d6052c89 | ||
|
|
c5ae0dc4ca | ||
|
|
e979a2be9b | ||
|
|
586b84f434 | ||
|
|
56b9d22d49 | ||
|
|
69aa241dc9 | ||
|
|
1335e12b97 | ||
|
|
d1b1fa7ffd | ||
|
|
d6a3492e90 | ||
|
|
4af57810d6 | ||
|
|
6555cc4639 | ||
|
|
3f57287d79 | ||
|
|
1713e311f3 | ||
|
|
8a14e78d2d | ||
|
|
1942e55f76 | ||
|
|
d83f41d0ff | ||
|
|
0f09240fb2 | ||
|
|
3731b49ea8 | ||
|
|
209223f77e | ||
|
|
7a5f5ee31d | ||
|
|
a148cb6c9b | ||
|
|
e9ac049744 | ||
|
|
e06d4bd841 | ||
|
|
6cad4f5839 | ||
|
|
86f5f9eba3 | ||
|
|
473d091fa8 | ||
|
|
aec5ad4099 | ||
|
|
f14f98f7c1 | ||
|
|
e3d9a7ddf2 | ||
|
|
58e4f9b7b4 | ||
|
|
ef1f09cd4a | ||
|
|
617619eb31 | ||
|
|
00a135b00f | ||
|
|
c71104db4f | ||
|
|
eef7940fbc | ||
|
|
da4b3db17a | ||
|
|
c0d20f04b6 | ||
|
|
8fda8668b7 | ||
|
|
ea2c641604 | ||
|
|
84e38505c5 | ||
|
|
6584eb0827 | ||
|
|
467d897e05 | ||
|
|
2e6ea202cd | ||
|
|
27f17551ad | ||
|
|
48bfc4e3cd | ||
|
|
61b9a4cf28 | ||
|
|
c95448bf25 | ||
|
|
f1ca60c182 | ||
|
|
52e79f78e5 | ||
|
|
586d6876f1 | ||
|
|
25759ecf0a | ||
|
|
1fbe870884 | ||
|
|
9fcd497c42 | ||
|
|
63ee6b7f0e | ||
|
|
73c0cd6934 | ||
|
|
e6c95a0913 | ||
|
|
af11cae29c | ||
|
|
b984a9ff00 | ||
|
|
13837e0bf3 | ||
|
|
f5d19fd28a | ||
|
|
24ac3ea37d | ||
|
|
13cb33cc4a | ||
|
|
949a4697fe | ||
|
|
3bbb828284 | ||
|
|
942b0f3dc9 | ||
|
|
208d8845c4 | ||
|
|
24cac9dcd5 | ||
|
|
c8b29da677 | ||
|
|
4f63d14529 | ||
|
|
cef6248650 | ||
|
|
774e443a79 | ||
|
|
1166807434 | ||
|
|
99cd502b65 | ||
|
|
d959e72a9c | ||
|
|
ee83788b43 | ||
|
|
62dd5f8ed7 | ||
|
|
2de9984945 | ||
|
|
890b46b697 | ||
|
|
5419957e06 | ||
|
|
39d4667916 | ||
|
|
083db67df9 | ||
|
|
8dceb6032b | ||
|
|
c300dad316 | ||
|
|
45b07f46f1 | ||
|
|
4023127c87 | ||
|
|
ab96c549ae | ||
|
|
bc0afb589e | ||
|
|
b42127f083 | ||
|
|
61d5a964ee | ||
|
|
f8fd6b78f5 | ||
|
|
4546ef6619 | ||
|
|
1f4457d9ca | ||
|
|
65cbbf78bc | ||
|
|
a73a24c1df | ||
|
|
31f850c19c | ||
|
|
6d90d7bc82 | ||
|
|
d2a1c02b92 | ||
|
|
6d96452ef8 | ||
|
|
833589e6e7 | ||
|
|
8bb566e189 | ||
|
|
38d2117752 | ||
|
|
914decd4f9 | ||
|
|
873c38f9e1 | ||
|
|
a3e37eca62 | ||
|
|
817911a41e | ||
|
|
9f4fce9daa | ||
|
|
9ff305d5db | ||
|
|
055c3e098f | ||
|
|
bc61dd85c6 | ||
|
|
db6f1405e9 | ||
|
|
3dc3376aec | ||
|
|
55920a58a3 | ||
|
|
2a70ebf667 | ||
|
|
2f65a86aa0 | ||
|
|
4bf81ac33b | ||
|
|
263c23ae8f | ||
|
|
bf51b945c5 | ||
|
|
9d7a461550 | ||
|
|
bbf60818eb | ||
|
|
103b22ebb2 | ||
|
|
cf4a1d7d40 | ||
|
|
e94f036aca | ||
|
|
675405f7ac | ||
|
|
f8a3536a88 | ||
|
|
e527a13b50 | ||
|
|
3be03451f8 | ||
|
|
9fa15d9c84 | ||
|
|
9c3d39b4a7 | ||
|
|
28800f43ee | ||
|
|
5c0b29c51f | ||
|
|
62d9d0208b | ||
|
|
4bf871e52f | ||
|
|
103972808c | ||
|
|
dc65bb87b5 | ||
|
|
bfa0282ecc | ||
|
|
aa66ec0ccd | ||
|
|
18fe19c680 | ||
|
|
ab0e411ac0 | ||
|
|
79671e6d61 | ||
|
|
71ad9773da | ||
|
|
34ecc2bcbb | ||
|
|
53f4f265dc | ||
|
|
97624ef836 | ||
|
|
fb8d0ed924 | ||
|
|
fedbdba13d | ||
|
|
a281682f7a | ||
|
|
07e9f09692 | ||
|
|
29d6e590de | ||
|
|
3e351f8529 | ||
|
|
bfbfb9379a | ||
|
|
4f31b7007a | ||
|
|
fe0cc2ebb9 | ||
|
|
2fd6f521f8 | ||
|
|
ec00728112 | ||
|
|
7174c1edeb | ||
|
|
7bd27e7437 | ||
|
|
0a28e30003 | ||
|
|
246c793c28 | ||
|
|
5f0ea4d586 | ||
|
|
3c7d316b3c | ||
|
|
645c8f32e3 | ||
|
|
adc5e7d0c0 | ||
|
|
b9b74ab1ac | ||
|
|
64298c04f2 | ||
|
|
3dfb7db039 | ||
|
|
b64d8f5cbf | ||
|
|
c2e5225728 | ||
|
|
6c44a94bf4 | ||
|
|
a2d9310d0a | ||
|
|
05197b93ee | ||
|
|
448d115d49 | ||
|
|
ae993dff45 | ||
|
|
1bc4805f3d | ||
|
|
98fe8f3955 | ||
|
|
e902bcef67 | ||
|
|
cb2a6e43a8 | ||
|
|
b73a0992f8 | ||
|
|
d0b3d78639 | ||
|
|
e6b8939772 | ||
|
|
bc372a982c | ||
|
|
3ff8064f1b | ||
|
|
834459186d | ||
|
|
12220ad4cf | ||
|
|
f6fd1db1ef | ||
|
|
a1050882ae | ||
|
|
687df5ead4 | ||
|
|
b057881ad0 | ||
|
|
2b23550996 | ||
|
|
afeb20e589 | ||
|
|
d7794518a2 | ||
|
|
fee61a43e3 | ||
|
|
b201afd192 | ||
|
|
feef1f8b0a | ||
|
|
1a5e2fde4f | ||
|
|
609e957a55 | ||
|
|
5c18404174 | ||
|
|
866a1dd8ae | ||
|
|
3bfd6c6979 | ||
|
|
06564e9091 | ||
|
|
1702783cfb | ||
|
|
4ea0cbaa37 | ||
|
|
2246e065f7 | ||
|
|
29a38cdf1a | ||
|
|
960c569e86 | ||
|
|
fa183fc97e | ||
|
|
a1d63118c0 | ||
|
|
f95a20173c | ||
|
|
b82d0fadc3 | ||
|
|
0635b3316e | ||
|
|
113235aec3 | ||
|
|
3921401e96 | ||
|
|
2e514478a4 | ||
|
|
b3d53b230e | ||
|
|
45dcb914ba | ||
|
|
711087b686 | ||
|
|
b100a59d1d | ||
|
|
109b2b0055 | ||
|
|
9dda99eb20 | ||
|
|
fa0f75b474 | ||
|
|
2d93e0d4be | ||
|
|
fe6406336d | ||
|
|
1037d44089 | ||
|
|
a8c3669f43 | ||
|
|
d91753aa82 | ||
|
|
b548514d44 | ||
|
|
ba782d2698 | ||
|
|
0552dc23a5 | ||
|
|
574bbbf5ff | ||
|
|
df11a92cdb | ||
|
|
33ae59adf7 | ||
|
|
e0a115b41d | ||
|
|
f838d6c179 | ||
|
|
6c3229f517 | ||
|
|
6973928b1a | ||
|
|
a5daba2a4d | ||
|
|
40ef83416e | ||
|
|
8518146455 | ||
|
|
d58f563de5 | ||
|
|
ad2454adab | ||
|
|
1f32557743 | ||
|
|
e95aae2129 | ||
|
|
9176171f20 | ||
|
|
d4f2249a4d | ||
|
|
e0b4069c17 | ||
|
|
6b25a21151 | ||
|
|
716dc45d85 | ||
|
|
57850230c8 | ||
|
|
362d597031 | ||
|
|
e89b84b37b | ||
|
|
ae6f6bf536 | ||
|
|
6f765bdd6f | ||
|
|
1982c6e6e6 | ||
|
|
527dceb43f | ||
|
|
f5a3d68d07 | ||
|
|
6c904fbc96 | ||
|
|
295036a1e3 | ||
|
|
5601d61b49 | ||
|
|
1c35c1a61a | ||
|
|
4143014466 | ||
|
|
90fea69b7e | ||
|
|
625663d619 | ||
|
|
403afc7aaf | ||
|
|
d295524c3c | ||
|
|
5eb4294e70 | ||
|
|
90598522a6 | ||
|
|
519fa1bcf8 | ||
|
|
7b0e5afe37 | ||
|
|
0b6b3a47a2 | ||
|
|
75db810508 | ||
|
|
2f52c564f5 | ||
|
|
011d582b80 | ||
|
|
32d21b2308 | ||
|
|
45971ca622 | ||
|
|
f3a09f2dad | ||
|
|
552a9c7b9f | ||
|
|
ed34d9cbc0 | ||
|
|
187ee99931 | ||
|
|
ff78dd8f7c | ||
|
|
b0eadb8ea4 | ||
|
|
a95754715a | ||
|
|
18ece4b90c | ||
|
|
3862fb2664 | ||
|
|
72c69d791a | ||
|
|
d6192a4a7a | ||
|
|
0f824ffa70 | ||
|
|
f6c227e7f5 | ||
|
|
9d5bc8af6e | ||
|
|
9480079770 | ||
|
|
54fe9147ac | ||
|
|
b6a0477232 | ||
|
|
c60644c578 | ||
|
|
abdce94c5f | ||
|
|
d7dee04013 | ||
|
|
dfc62132b7 | ||
|
|
36f7f193aa | ||
|
|
ca4a82ec38 | ||
|
|
37aea1888d | ||
|
|
92f3b4ddd7 | ||
|
|
647995428c | ||
|
|
407e9c25f3 | ||
|
|
1612ab7335 | ||
|
|
b952c36210 | ||
|
|
96b5cb2c61 | ||
|
|
c5b3acfce2 | ||
|
|
20a01bf266 | ||
|
|
a33b88cf1c | ||
|
|
09a2f45ada | ||
|
|
83a7dd7ea1 | ||
|
|
afc1b6a5c0 | ||
|
|
7f4f860735 | ||
|
|
d789e3aa0d | ||
|
|
f5b91cd45d | ||
|
|
92ab4b3309 | ||
|
|
2c456e4c89 | ||
|
|
1460e63449 | ||
|
|
8291124c1f | ||
|
|
fc4d9accfd | ||
|
|
80969b785f | ||
|
|
3dfd7f1835 | ||
|
|
65daa39ebe | ||
|
|
5ad94504e3 | ||
|
|
4101bf3ba5 | ||
|
|
e9d52864ef | ||
|
|
aef2696426 | ||
|
|
94c755b102 | ||
|
|
279b457348 | ||
|
|
b5988bb8b7 | ||
|
|
f73b1d8b40 | ||
|
|
b2ccb07a95 | ||
|
|
9560cc4e33 | ||
|
|
e87c380556 | ||
|
|
b0846876f7 | ||
|
|
477ed67957 | ||
|
|
5acacd7e1e | ||
|
|
8d542fe9c0 | ||
|
|
b0cb249ae9 | ||
|
|
185509a0cf | ||
|
|
08298d3284 | ||
|
|
7a4cec5093 | ||
|
|
f44f5199c6 | ||
|
|
81abc091de | ||
|
|
7e4f4c445d | ||
|
|
5a673c1833 | ||
|
|
266231ae0f | ||
|
|
9e87a887cb | ||
|
|
12e98bfd31 | ||
|
|
249f124ba6 | ||
|
|
131643a91b | ||
|
|
df3df18690 | ||
|
|
5401d17610 | ||
|
|
90ea2284c6 | ||
|
|
a4c5792f9e | ||
|
|
5723598923 | ||
|
|
aa0b2ff93a | ||
|
|
be6233f12b | ||
|
|
17df749790 | ||
|
|
97f852f8e8 | ||
|
|
dc3446d61a | ||
|
|
c830a0b208 | ||
|
|
ff0307b68f | ||
|
|
1c3cad9235 | ||
|
|
ccafc15b66 | ||
|
|
a40d6b32e3 | ||
|
|
de1ee92fe7 | ||
|
|
c7227d2f50 | ||
|
|
b2cebbfaf4 | ||
|
|
30fbbc92ca | ||
|
|
d1b210cf16 | ||
|
|
9963568368 | ||
|
|
ffc3249b33 | ||
|
|
29826db81b | ||
|
|
5367a76db5 | ||
|
|
2512a00205 | ||
|
|
72a3a9f04f | ||
|
|
b566e0fd46 | ||
|
|
4621fb4e9b | ||
|
|
7f3d25964f | ||
|
|
4b3728e8d8 | ||
|
|
5218d6df1a | ||
|
|
94b2b8ec70 | ||
|
|
6d1086539e | ||
|
|
7f758bbb73 | ||
|
|
62b88200c3 | ||
|
|
ce42883268 | ||
|
|
6b60cfce4d | ||
|
|
aebb47ad38 | ||
|
|
41f5634b7a | ||
|
|
87ddb01122 | ||
|
|
6898eea45e | ||
|
|
ba2679d3d7 | ||
|
|
971cdaa44f | ||
|
|
005d3b5976 | ||
|
|
663403cb14 | ||
|
|
b341e38623 | ||
|
|
8246db30cb | ||
|
|
9c9c656620 | ||
|
|
f36be0d5e0 | ||
|
|
72090ea8ff | ||
|
|
8d64a0a232 | ||
|
|
35974a0a33 | ||
|
|
3023439028 | ||
|
|
77f4a09d74 | ||
|
|
0fc797f7d0 | ||
|
|
0b02c84e33 | ||
|
|
e03ff0a9be | ||
|
|
d91f1841c0 | ||
|
|
0effc9cfc1 | ||
|
|
f08cbb458d | ||
|
|
b8c9770d43 | ||
|
|
44ff5d0e4d | ||
|
|
ecb580a629 | ||
|
|
0623d837c1 | ||
|
|
f92d1ce4ac | ||
|
|
88f84069d6 | ||
|
|
b9b7081184 | ||
|
|
ce3e0817db | ||
|
|
55b65a8bf6 | ||
|
|
6767141661 | ||
|
|
2eb3b15930 | ||
|
|
b63c4a0d4f | ||
|
|
1269ddfeae | ||
|
|
afd47f5522 | ||
|
|
7ede6bdbce | ||
|
|
03b505e40e | ||
|
|
ed7ff1fad4 | ||
|
|
2dda1aecce | ||
|
|
720e363577 | ||
|
|
545a65521a | ||
|
|
0cf6f94677 | ||
|
|
14e205ab69 | ||
|
|
c3da87a40c | ||
|
|
5d93b05088 | ||
|
|
2cdd33cb7a | ||
|
|
dc909fdfb0 | ||
|
|
a43199b754 | ||
|
|
876211879f | ||
|
|
fe323b8fe5 | ||
|
|
b60f5d593b | ||
|
|
2d4317b681 | ||
|
|
caf0eb3762 | ||
|
|
c1aa7b9d8a | ||
|
|
6c6efd9cfb | ||
|
|
551670a8b9 | ||
|
|
ac75225e7d | ||
|
|
20dbbeb38e | ||
|
|
37dea9980e | ||
|
|
5cec2d4cb0 | ||
|
|
ed76fa5141 | ||
|
|
389a765825 | ||
|
|
3bad40095a | ||
|
|
1a51c66028 | ||
|
|
05161bd4df | ||
|
|
db1102750f | ||
|
|
42a974476f | ||
|
|
0dd91c1efe | ||
|
|
756d2fe4e7 | ||
|
|
61c64b49c7 | ||
|
|
c2eb68a31a | ||
|
|
f1a1b922c7 | ||
|
|
a2dcceb470 | ||
|
|
1d78fdd673 | ||
|
|
4a53749ca0 | ||
|
|
7f73ec52d6 | ||
|
|
4abb172976 | ||
|
|
c52e0a5531 | ||
|
|
0197758780 | ||
|
|
e2521b6688 | ||
|
|
13f19de1a0 | ||
|
|
5e589019d0 | ||
|
|
feaad13ac3 | ||
|
|
ab9428a9c4 | ||
|
|
c964a1471a | ||
|
|
424322f7b7 | ||
|
|
956a4f8b2a | ||
|
|
d87210e903 | ||
|
|
3d3b63a596 | ||
|
|
4f9636b4c3 | ||
|
|
74c8d56046 | ||
|
|
2d6b827fd2 | ||
|
|
f82eb8aeb4 | ||
|
|
f1ab62524c | ||
|
|
ce78d22bb8 | ||
|
|
99a1dbeae1 | ||
|
|
2a71e28253 | ||
|
|
46fe3be322 | ||
|
|
9da65b6c7c | ||
|
|
ad02700b51 | ||
|
|
8fd10bace7 | ||
|
|
9d09a3adf5 | ||
|
|
4350f0cd1a | ||
|
|
5dc993255c | ||
|
|
e9188a9864 | ||
|
|
42dd70c2f7 | ||
|
|
191c124130 | ||
|
|
2742f948c2 | ||
|
|
455a3ba677 | ||
|
|
1961da9aed | ||
|
|
e82d9d7a74 | ||
|
|
dfb3166bed | ||
|
|
5a54f7f302 | ||
|
|
6002a497fe | ||
|
|
3fa8b6332e | ||
|
|
1b521b87c5 | ||
|
|
8b7d2aab6b | ||
|
|
b0006f91f4 | ||
|
|
31aaa968ec | ||
|
|
facb4593f0 | ||
|
|
d1a30363b4 | ||
|
|
eac5347f32 | ||
|
|
2006665fe8 | ||
|
|
26a3862d61 | ||
|
|
2371109b6f | ||
|
|
243bffebbd | ||
|
|
e69ae7b0db | ||
|
|
5aff7b94d8 | ||
|
|
a65058ddd5 | ||
|
|
b1e81d84c6 | ||
|
|
96e60f7e4f | ||
|
|
5e59c617e8 | ||
|
|
69ad0ade6e | ||
|
|
37cdbc19ef | ||
|
|
6cbce81faa | ||
|
|
8c14906a60 | ||
|
|
62591e1f6f | ||
|
|
ea4a888c5e | ||
|
|
281a1cc549 | ||
|
|
d52dcd0708 | ||
|
|
d8e01b2867 | ||
|
|
dca3f39156 | ||
|
|
31e964fe0f | ||
|
|
39d973c43f | ||
|
|
55f921959d | ||
|
|
6598090662 | ||
|
|
d7f29e7363 | ||
|
|
82df6089c3 | ||
|
|
80cc66964e | ||
|
|
7883d38622 | ||
|
|
2cb5169b6d | ||
|
|
9ad2c07984 | ||
|
|
a9c1239149 | ||
|
|
cb1223f72e | ||
|
|
4dc7575d5b | ||
|
|
276d1ce60a | ||
|
|
58ab32a623 | ||
|
|
c1846e6ff3 | ||
|
|
826de17111 | ||
|
|
8a09ea8bc1 | ||
|
|
1297c925ad | ||
|
|
74d15e1a92 | ||
|
|
ae373c3e77 | ||
|
|
e9b90caa3a | ||
|
|
b89e77a6a4 | ||
|
|
61691ac46b | ||
|
|
512b96af24 | ||
|
|
d369593979 | ||
|
|
2f38e0564b | ||
|
|
5e8dd4e4bc | ||
|
|
8f9f1f566d | ||
|
|
d7870b8860 | ||
|
|
97fa23f890 | ||
|
|
f839887da8 | ||
|
|
15bfaa15ca | ||
|
|
4a3183ffa0 | ||
|
|
18d03a076b | ||
|
|
4bed4195ac | ||
|
|
a963878af5 | ||
|
|
d6c3dc87e0 | ||
|
|
5391a9a5ad | ||
|
|
b50e95802c | ||
|
|
75a9799e96 | ||
|
|
dbb9e4d60f | ||
|
|
d27b6bd49d | ||
|
|
c5d2726faa | ||
|
|
a2a98c490f | ||
|
|
e2dc1d98f1 | ||
|
|
658c26d3c9 | ||
|
|
612095789a | ||
|
|
7418d9f670 | ||
|
|
f344c58a62 | ||
|
|
36b94f745d | ||
|
|
08cdcf4112 | ||
|
|
76813737ef | ||
|
|
53d15d6a77 | ||
|
|
dd01b62b87 | ||
|
|
9fab15537b | ||
|
|
d87db05b2b | ||
|
|
f1f32c962c | ||
|
|
ad149740b1 | ||
|
|
9a4e938b91 | ||
|
|
a226760b07 | ||
|
|
a11450c3a7 | ||
|
|
e0cab4f937 | ||
|
|
468250f291 | ||
|
|
d04b93c17e | ||
|
|
911556a1aa | ||
|
|
c7d3230eef | ||
|
|
b63086bf09 | ||
|
|
a4118a5676 | ||
|
|
26e7e6467c | ||
|
|
1c9552fa58 | ||
|
|
9875cb5575 | ||
|
|
d1c6bb8829 | ||
|
|
ef7005a291 | ||
|
|
8068b83ffe | ||
|
|
f01a89710c | ||
|
|
38ced81ada | ||
|
|
9834632d59 | ||
|
|
bb4504dd50 | ||
|
|
8864c2f2db | ||
|
|
19208472e6 | ||
|
|
10c77ba3cc | ||
|
|
cd28fd4945 | ||
|
|
6778d6aa4a | ||
|
|
433851d771 | ||
|
|
d157fd3528 | ||
|
|
9150823c37 | ||
|
|
07c3a44441 | ||
|
|
051bbf9449 | ||
|
|
22ea1c0e2a | ||
|
|
6432a44860 | ||
|
|
493d861de3 | ||
|
|
82452e9616 | ||
|
|
2fbeaa618a | ||
|
|
6c08afaa0e | ||
|
|
af4cc1f574 | ||
|
|
2fb27b26cd | ||
|
|
11e09e1f87 | ||
|
|
9ccb5f8aa9 | ||
|
|
af87d6a0ea | ||
|
|
d847f45cb3 | ||
|
|
38c615609a | ||
|
|
144cc4b82f | ||
|
|
d24ab141e9 | ||
|
|
8505374fcf | ||
|
|
e53d961fc3 | ||
|
|
dc8ca7a8ee | ||
|
|
3d1b87d9dc | ||
|
|
01fa2af5cd | ||
|
|
20a89ca45a | ||
|
|
16ca2f8da9 | ||
|
|
30fe9764ad | ||
|
|
e246c8ee47 | ||
|
|
ba03a48498 | ||
|
|
b96dd0160a | ||
|
|
49890a09b7 | ||
|
|
dfce56cee8 | ||
|
|
a6fee2946a | ||
|
|
34c849ee89 | ||
|
|
c7192ed3bf | ||
|
|
4d3dc0c5f7 | ||
|
|
9ba4afa073 | ||
|
|
3ea4422d13 | ||
|
|
de2e314f7d | ||
|
|
2380fb42fe | ||
|
|
95b76076a3 | ||
|
|
b415d4c34c | ||
|
|
2d82b6dd6e | ||
|
|
16b1935f12 | ||
|
|
50ec614b2a | ||
|
|
9e11a0af6e | ||
|
|
0c3e42e0b9 | ||
|
|
36b31bb0b3 | ||
|
|
c03c41450b | ||
|
|
dfc2b5d88b | ||
|
|
87e3e3ffe3 | ||
|
|
dae37c6a50 | ||
|
|
c7df11cc6f | ||
|
|
87f1f208c3 | ||
|
|
ba8c5d740e | ||
|
|
c275d5d999 | ||
|
|
cfc53c9c94 | ||
|
|
87df917157 | ||
|
|
395d87d290 | ||
|
|
aff8ec08ad | ||
|
|
4d40b56d85 | ||
|
|
667d0724c3 | ||
|
|
a49395553a | ||
|
|
cce09bd9cc | ||
|
|
03a66e4690 | ||
|
|
fd752fee80 | ||
|
|
8a71f84733 | ||
|
|
9ef2c7da4c | ||
|
|
8975073416 | ||
|
|
d1c1378c9d | ||
|
|
7941284a1d | ||
|
|
af2d17b7a5 | ||
|
|
3ca2b01d9a | ||
|
|
67193a2ab7 | ||
|
|
9757aa36de | ||
|
|
29854a9f87 | ||
|
|
b12c179470 | ||
|
|
bbef15e4e4 | ||
|
|
c483929a0d | ||
|
|
1741f395dd | ||
|
|
0f29262797 | ||
|
|
31ed477b96 | ||
|
|
9e5de5413d | ||
|
|
0f297a81a4 | ||
|
|
89313def99 | ||
|
|
8e0be4edaf | ||
|
|
a8dfdfb922 | ||
|
|
f096024248 | ||
|
|
4f50f90213 | ||
|
|
4501902331 | ||
|
|
df19679dba | ||
|
|
9f5a2f67f9 | ||
|
|
2d5c406325 | ||
|
|
151b8a8940 | ||
|
|
cda027b94a | ||
|
|
ee2117abf6 | ||
|
|
6e7294d49f | ||
|
|
062e45f697 | ||
|
|
d18b39990d | ||
|
|
7387ac2411 | ||
|
|
4186592f9f | ||
|
|
6c9d5a72a6 | ||
|
|
83690a4dd4 | ||
|
|
c11e03ab26 | ||
|
|
c7d8709267 | ||
|
|
6579deffad | ||
|
|
e2739e7a4b | ||
|
|
c0d587f541 | ||
|
|
05a96ffc14 | ||
|
|
32a47444d7 | ||
|
|
9ff5de5f33 | ||
|
|
09badf33d0 | ||
|
|
1643d3637f | ||
|
|
b962e9ebe8 | ||
|
|
66f3528e10 | ||
|
|
a5e9f051a2 | ||
|
|
63bfb76516 | ||
|
|
f88f7d41aa | ||
|
|
877383ac85 | ||
|
|
dd5e11e835 | ||
|
|
3d43550ffe | ||
|
|
115bc8fa0a | ||
|
|
15c46e324c | ||
|
|
df38366066 | ||
|
|
28b13ccfff | ||
|
|
26a433ebbe | ||
|
|
1902595190 | ||
|
|
80146cfb58 | ||
|
|
03d2d6fc94 | ||
|
|
379e4d7596 | ||
|
|
9860bd770b | ||
|
|
2af5328a0f | ||
|
|
4084a44f83 | ||
|
|
ba7c7ddb23 | ||
|
|
2351e7b98c | ||
|
|
d353dc622c | ||
|
|
3ef6adfd02 | ||
|
|
5063a6982a | ||
|
|
0008f2845c | ||
|
|
a0994bc428 | ||
|
|
8fe0d97aec | ||
|
|
a8b3c02780 | ||
|
|
f3489fb57c | ||
|
|
434b5b375d | ||
|
|
445120f9f5 | ||
|
|
71b11f0d9c | ||
|
|
8297a9e0e7 | ||
|
|
4999672f2d | ||
|
|
70608ed7e9 | ||
|
|
a0836ebdd7 | ||
|
|
2b1edd1d4c | ||
|
|
42bb7cc973 | ||
|
|
8299c37bb7 | ||
|
|
7a2005c20c | ||
|
|
ae0eb9e66e | ||
|
|
052126613a | ||
|
|
7959657bd6 | ||
|
|
9f8bb376ea | ||
|
|
ee8e2fa906 | ||
|
|
33a380b173 | ||
|
|
6e5b6996fa | ||
|
|
6409dc276c | ||
|
|
98f7ce43e3 | ||
|
|
aa076e1d2d | ||
|
|
7a096d1b5c | ||
|
|
93b17ccddd | ||
|
|
68c118c3e5 | ||
|
|
c0b0ba433f | ||
|
|
d7d81431ef | ||
|
|
7451f45885 | ||
|
|
c9882001a9 | ||
|
|
837b06ef2b | ||
|
|
0e49150b8e | ||
|
|
0ec5f4bf68 | ||
|
|
601730d737 | ||
|
|
28eb4b21bd | ||
|
|
a5afe0bca1 | ||
|
|
ad5691dcb2 | ||
|
|
80974fa1dc | ||
|
|
78330a0e11 | ||
|
|
b6cff2d784 | ||
|
|
cae3555ca7 | ||
|
|
1f9cf458ec | ||
|
|
d9ead2d9f5 | ||
|
|
92660fd03e | ||
|
|
5393d847f0 | ||
|
|
231f09de12 | ||
|
|
b75ca2700b | ||
|
|
bae7ef9067 | ||
|
|
8ec8a3b4d9 | ||
|
|
5b7228ed69 | ||
|
|
b02bf90c8a | ||
|
|
7d3546734e | ||
|
|
030013eb5b | ||
|
|
da181345a6 | ||
|
|
30874b2206 | ||
|
|
2ed6b2dc87 | ||
|
|
41532f35d1 | ||
|
|
7a198a44cd | ||
|
|
77d615d15b | ||
|
|
c7bc397c85 | ||
|
|
38388cc297 | ||
|
|
a7b17b2b8c | ||
|
|
d93afc4648 | ||
|
|
24449e41bb | ||
|
|
df6f3ed165 | ||
|
|
ca5914dbfb | ||
|
|
3c3a1f8981 | ||
|
|
01810f35b2 | ||
|
|
5db4083414 | ||
|
|
8bf3a747f0 | ||
|
|
f0e817a8d9 | ||
|
|
b181c59698 | ||
|
|
cfa094f208 | ||
|
|
9ee5a8d089 | ||
|
|
819127da57 | ||
|
|
6e9659a797 | ||
|
|
07bd9cadd4 | ||
|
|
a1bcd35e26 | ||
|
|
1a741e18fd | ||
|
|
2e133dd0fb | ||
|
|
ecae554a78 | ||
|
|
4bed50b4ed | ||
|
|
c92b371d9e | ||
|
|
35e6bb30db | ||
|
|
1aaa123f47 | ||
|
|
a8c507a1df | ||
|
|
581e3c358f | ||
|
|
e4f1b8f2e0 | ||
|
|
29e8a7fd7e | ||
|
|
4af289c492 | ||
|
|
cd95793054 | ||
|
|
ab71578cf2 | ||
|
|
df07d4a393 | ||
|
|
2518395c03 | ||
|
|
50f3ab7798 | ||
|
|
2d01056ea9 | ||
|
|
f40fb3bab3 | ||
|
|
fe7c60654d | ||
|
|
728b640ff8 | ||
|
|
55c247e5d0 | ||
|
|
6be15b780a | ||
|
|
150c552ef9 | ||
|
|
7005c1f5e5 | ||
|
|
a66ae33d5d | ||
|
|
8ed8447665 | ||
|
|
e740719732 | ||
|
|
bfd9238f6d | ||
|
|
cca47a8149 | ||
|
|
3ecf099fe0 | ||
|
|
6f56dc0339 | ||
|
|
20108208d0 | ||
|
|
0706e6f4ff | ||
|
|
af85df611c | ||
|
|
3c1239cfb8 | ||
|
|
50d144bf93 | ||
|
|
9a5a03d032 | ||
|
|
854ae0f65e | ||
|
|
4fb34ffee9 | ||
|
|
bbf3dae37f | ||
|
|
e69f58eb86 | ||
|
|
c9475ddc65 | ||
|
|
31d085b6a1 | ||
|
|
173866236f | ||
|
|
b176780527 | ||
|
|
89c72fdbad | ||
|
|
7d6e832226 | ||
|
|
c024346475 | ||
|
|
95ec5929b4 | ||
|
|
1646c50a94 | ||
|
|
b1429e1df3 | ||
|
|
6da0aa376f | ||
|
|
1ab5503558 | ||
|
|
4b9db257fd | ||
|
|
96f83d92fc | ||
|
|
7c7ee7fb9b | ||
|
|
2bd3d57f8a | ||
|
|
3f1c41a4f7 | ||
|
|
ef819f4d53 | ||
|
|
23189ed8f9 | ||
|
|
6b8a345241 | ||
|
|
b3cc168571 | ||
|
|
00b740c549 | ||
|
|
472bececb1 | ||
|
|
4fe9a4eb59 | ||
|
|
d331cd934a | ||
|
|
f6e1b95711 | ||
|
|
edec412bc7 | ||
|
|
e142bacb67 | ||
|
|
915e4b66a3 | ||
|
|
ee47a361b1 | ||
|
|
3ab2dad19b | ||
|
|
635c6db83a | ||
|
|
d2a13f531a | ||
|
|
e44857c023 | ||
|
|
749cdd011b | ||
|
|
4fde005b7f | ||
|
|
0a975fc0cc | ||
|
|
f5b7c59203 | ||
|
|
c775559912 | ||
|
|
4b9116ed72 | ||
|
|
178501e252 | ||
|
|
c9391abfd9 | ||
|
|
f7a7d9e52d | ||
|
|
5a65087aeb | ||
|
|
3cfeb8f492 | ||
|
|
0498e2d679 | ||
|
|
19563d0b3c | ||
|
|
4cc1d98a42 | ||
|
|
cd408c1687 | ||
|
|
546859531b | ||
|
|
857e467672 | ||
|
|
5de5a80eee | ||
|
|
5f1333b2a0 | ||
|
|
5e9548edbc | ||
|
|
e42edf2164 | ||
|
|
7e8c524a43 | ||
|
|
8d6ba86118 | ||
|
|
dc27317d0b | ||
|
|
0b8032c7a0 | ||
|
|
d3e18a81aa | ||
|
|
4707573c57 | ||
|
|
218e2deef4 | ||
|
|
47afff3bab | ||
|
|
24ce400429 | ||
|
|
ceac341016 | ||
|
|
7830c89e66 | ||
|
|
fa6ca01de9 | ||
|
|
746bff55a8 | ||
|
|
ec81e90153 | ||
|
|
74c9d06d91 | ||
|
|
3cfbd8b70c | ||
|
|
046d9b80bb | ||
|
|
516fda2a3a | ||
|
|
bc5c103cec | ||
|
|
98f0c068ad | ||
|
|
28e0a5a3a3 | ||
|
|
5f9d6db761 | ||
|
|
9bec52074f | ||
|
|
dfa5009a9b | ||
|
|
fbb5c47358 | ||
|
|
f2ca67a7f4 | ||
|
|
3a9af92571 | ||
|
|
b26148bf62 | ||
|
|
7e27e0bda8 | ||
|
|
94df05566a | ||
|
|
efa46414fd | ||
|
|
311623d71a | ||
|
|
89894b0f08 | ||
|
|
ed590eeb41 | ||
|
|
8956902c52 | ||
|
|
720d9649eb | ||
|
|
ce95041821 | ||
|
|
9cd84ac834 | ||
|
|
eddd6880f2 | ||
|
|
e7f9111ab5 | ||
|
|
0eb2ee72a4 | ||
|
|
1aa4fac528 | ||
|
|
3f07f7ef41 | ||
|
|
886ff2cd70 | ||
|
|
ed09608952 | ||
|
|
6aa5ec6eb6 | ||
|
|
eee4981d4c | ||
|
|
ab2c0f905a | ||
|
|
4eca3d169e | ||
|
|
e69944eaaf | ||
|
|
bc987b2dda | ||
|
|
244b150385 | ||
|
|
3acbc08ec5 | ||
|
|
1ce7e5d8a4 | ||
|
|
7813fe232a | ||
|
|
1b273071b2 | ||
|
|
43236f25ad | ||
|
|
5f2b1acfea | ||
|
|
b820dcd73f | ||
|
|
0a412c0ef7 | ||
|
|
db75f8046c | ||
|
|
7bdd1a4a3a | ||
|
|
b7d39105e8 | ||
|
|
f8b5dbbba5 | ||
|
|
c99120bd24 | ||
|
|
b9ff3db9b0 | ||
|
|
8ce1b4bf71 | ||
|
|
0b41a2b132 | ||
|
|
575ed92593 | ||
|
|
d3fe37b879 | ||
|
|
8318cb991b | ||
|
|
9779fb1d29 | ||
|
|
43b4e8c924 | ||
|
|
756a206a9e | ||
|
|
324ff44886 | ||
|
|
04913cabba | ||
|
|
c67b550435 | ||
|
|
88bf60b8c2 | ||
|
|
08fd2beb52 | ||
|
|
a870813ac5 | ||
|
|
c31f0100cb | ||
|
|
bc7fc750f0 | ||
|
|
9cd1784667 | ||
|
|
fbc7baa9a4 | ||
|
|
a4e5cf3914 | ||
|
|
e14c177f39 | ||
|
|
b5b53c636c | ||
|
|
33b45d2eda | ||
|
|
4c9bb71626 | ||
|
|
e9aa88e637 | ||
|
|
e7c7c2ce3e | ||
|
|
686e7e9527 | ||
|
|
86e390f70f | ||
|
|
e04352a577 | ||
|
|
ee4e7620b5 | ||
|
|
975dc4f314 | ||
|
|
dfff96ace1 | ||
|
|
f01171bb8b | ||
|
|
8f248f8d14 | ||
|
|
ae815a80b1 | ||
|
|
925514b0a3 | ||
|
|
45639e5a5c | ||
|
|
17ab78f064 | ||
|
|
c43a3343d8 | ||
|
|
095e40d1dd | ||
|
|
a400ef823f | ||
|
|
7684fbd5ec | ||
|
|
cd64e8dc28 | ||
|
|
dbba918b79 | ||
|
|
88dbe35c4b | ||
|
|
4a61fedf97 | ||
|
|
110181679b | ||
|
|
b0a0092a73 | ||
|
|
a6a975ce40 | ||
|
|
a73eb82ac6 | ||
|
|
702cade0d7 | ||
|
|
367b86bc7a | ||
|
|
7c5e823805 | ||
|
|
0975863d98 | ||
|
|
6e6886a6ba | ||
|
|
8bf11d6f6a | ||
|
|
6c97ccafd5 | ||
|
|
72ff66ebde | ||
|
|
b3d0da7392 | ||
|
|
a1c0d82889 | ||
|
|
fbd708d1b5 | ||
|
|
710ce965a1 | ||
|
|
2ee1535095 | ||
|
|
471fbef6ef | ||
|
|
ab63709104 | ||
|
|
0924184358 | ||
|
|
6e38cf2bc1 | ||
|
|
2183422eb5 | ||
|
|
3842f5b16d | ||
|
|
1dad6b37ef | ||
|
|
7023db2264 | ||
|
|
bf194f5c85 | ||
|
|
bcdaa37f8a | ||
|
|
a5f8900d23 | ||
|
|
3c684c7f45 | ||
|
|
cd0f4baa15 | ||
|
|
58f3050344 | ||
|
|
ab110bb0df | ||
|
|
636025c1b9 | ||
|
|
c28fa78963 | ||
|
|
ecb66fb9f3 | ||
|
|
f8e4192d34 | ||
|
|
3d15cd57c4 | ||
|
|
2e633f0df4 | ||
|
|
833b814fd5 | ||
|
|
da52d905b6 | ||
|
|
a2054c24d8 | ||
|
|
599db80467 | ||
|
|
6075a3307e | ||
|
|
9af06996c0 | ||
|
|
72c386cdce | ||
|
|
f6d4771603 | ||
|
|
15d06c591e | ||
|
|
0a07765027 | ||
|
|
81e0d06604 | ||
|
|
809314e516 | ||
|
|
cbe37b2ccd | ||
|
|
14fcbb6b8b | ||
|
|
bac976144e | ||
|
|
7677620645 | ||
|
|
c8597bd363 | ||
|
|
899be12418 | ||
|
|
31975b6737 | ||
|
|
315549e588 | ||
|
|
457886d538 | ||
|
|
8d02b206e0 | ||
|
|
45e5b94b4f | ||
|
|
ec866b4a61 | ||
|
|
2e8e2522e5 | ||
|
|
96bd46c3a8 | ||
|
|
797ed9ac51 | ||
|
|
7089dcddff | ||
|
|
8ee93dfe9c | ||
|
|
88f160820f | ||
|
|
b61b3e75c1 | ||
|
|
a1bac51f7b | ||
|
|
fa986fb4cb | ||
|
|
aa27b3be64 | ||
|
|
97d94b7952 | ||
|
|
96eb793298 | ||
|
|
b4f15de7be | ||
|
|
ae5726b836 | ||
|
|
692e72a78a | ||
|
|
ff24364bb6 | ||
|
|
b60a1958b6 | ||
|
|
f6a2b505db | ||
|
|
38aacdbd7d | ||
|
|
089b877cc5 | ||
|
|
81e55dcf77 | ||
|
|
58dd44bf5d | ||
|
|
3aa6669fd9 | ||
|
|
c10601d905 | ||
|
|
e15be7ebd3 | ||
|
|
b465a91cd3 | ||
|
|
f304a46bea | ||
|
|
6756faa1cc | ||
|
|
73fd7c7d54 | ||
|
|
60eda9ec69 | ||
|
|
a979c29a15 | ||
|
|
8f25082917 | ||
|
|
9375b1c8bd | ||
|
|
422a22a767 | ||
|
|
249f638495 | ||
|
|
6cf5e10195 | ||
|
|
b78a946458 | ||
|
|
e8a5694d51 | ||
|
|
514fa72ee2 | ||
|
|
e9ca13aa12 | ||
|
|
57f1ec6716 | ||
|
|
02e32cc9b9 | ||
|
|
902abd5d94 | ||
|
|
53380802ec | ||
|
|
af5d8d02b6 | ||
|
|
7abba76f03 | ||
|
|
79b22057d9 | ||
|
|
366daef718 | ||
|
|
a5ff0ba799 | ||
|
|
c2c6febb88 | ||
|
|
f119c72a7f | ||
|
|
8aee897d23 | ||
|
|
729db5c662 | ||
|
|
61c46df7bf | ||
|
|
9b1a04338d | ||
|
|
d307134d22 | ||
|
|
5bc44363f9 | ||
|
|
68c4fac3ab | ||
|
|
6ad9245019 | ||
|
|
763cf771fb | ||
|
|
3160b08637 | ||
|
|
f8949958a3 | ||
|
|
8b7ac07d2d | ||
|
|
044df9adba | ||
|
|
040139f4cc | ||
|
|
7b73bb9df0 | ||
|
|
24c8370daa | ||
|
|
029c4921d7 | ||
|
|
3a74c71f1a | ||
|
|
6022a1bbaa | ||
|
|
4e88c993f7 | ||
|
|
c9a61f467c | ||
|
|
e6a5f42f63 | ||
|
|
a373823eea | ||
|
|
b5e010eac8 | ||
|
|
50ffe58655 | ||
|
|
07eb3b59b3 | ||
|
|
5177b5e142 | ||
|
|
3c984e21cd | ||
|
|
aa2b27e22b | ||
|
|
14a7f00c90 | ||
|
|
56f98601bd | ||
|
|
027a8c675e | ||
|
|
bdaba9a767 | ||
|
|
4e9090f60d | ||
|
|
73b445d371 | ||
|
|
75bfc283af | ||
|
|
727de19b89 | ||
|
|
5d605d1bd7 | ||
|
|
ffdd1dfd6f | ||
|
|
d45418eb29 | ||
|
|
6ccc9d1ade | ||
|
|
93069159dd | ||
|
|
8c4780131f | ||
|
|
02ae8bceda | ||
|
|
bb10bbc945 | ||
|
|
478d88e97f | ||
|
|
6fb397a729 | ||
|
|
18dae34778 | ||
|
|
243566e936 | ||
|
|
87f4fd675d | ||
|
|
dec6b59a9f | ||
|
|
e51baedf7f | ||
|
|
530da14e24 | ||
|
|
02da7c272f | ||
|
|
a07c5418e9 | ||
|
|
c080db814b | ||
|
|
3abe13c006 | ||
|
|
fb331c0a2c | ||
|
|
19ea78afc5 | ||
|
|
2096c782e3 | ||
|
|
79a6a8a10c | ||
|
|
5a933bad93 | ||
|
|
7e302fd1cb | ||
|
|
cf9f0da6e5 | ||
|
|
10ac23e265 | ||
|
|
dc2e1cba1f | ||
|
|
7bfd190c22 | ||
|
|
c3bafeb468 | ||
|
|
7bacd781cf | ||
|
|
ee005c3679 | ||
|
|
315f54497a | ||
|
|
e30233347b | ||
|
|
56d4a7f01e | ||
|
|
7c110eebd8 | ||
|
|
39394f8c09 | ||
|
|
3283130dfc | ||
|
|
3146a591d0 | ||
|
|
e478b1ec04 | ||
|
|
7bc4d14f46 | ||
|
|
f3eeeef389 | ||
|
|
8d69208197 | ||
|
|
2c689af1a9 | ||
|
|
cb2a34c765 | ||
|
|
465c8f9009 | ||
|
|
8ea4c1c1fd | ||
|
|
ba0f7df9e8 | ||
|
|
e47dd723b0 | ||
|
|
fca6e2f6bf | ||
|
|
faa7ba6f24 | ||
|
|
fc2dbbe3ee | ||
|
|
cc98b81825 | ||
|
|
eb4a7069d4 | ||
|
|
4f65d9214e | ||
|
|
4d3c8ee63c | ||
|
|
e41c1b826a | ||
|
|
644bb48135 | ||
|
|
c9809285f6 | ||
|
|
5704949f4d | ||
|
|
a19e00fbc0 | ||
|
|
470a9b3e27 | ||
|
|
ace31dc566 | ||
|
|
ed252276cb | ||
|
|
26d0ff3c9a | ||
|
|
ff806a3ff9 | ||
|
|
949b17dee6 | ||
|
|
b1fdc68623 | ||
|
|
f502facfd1 | ||
|
|
bf0a74d709 | ||
|
|
7296d98313 | ||
|
|
30568ced49 | ||
|
|
5e1284a9e0 | ||
|
|
27d2de872a | ||
|
|
03d6e3356b | ||
|
|
ca8baa62fb | ||
|
|
2f607357c6 | ||
|
|
2de80f7aff | ||
|
|
386058ed88 | ||
|
|
033fa9e067 | ||
|
|
e8104420b5 | ||
|
|
ae24b10da0 | ||
|
|
407b05b643 | ||
|
|
79bf8bc9f6 | ||
|
|
65d6dca52c | ||
|
|
66eeefbd7b | ||
|
|
c10bbcde00 | ||
|
|
fe69928bcc | ||
|
|
3ad8508ea5 | ||
|
|
1f1ae759e0 | ||
|
|
6e4bfe8f0f | ||
|
|
6276c48768 | ||
|
|
f6005baf1a | ||
|
|
b62fdbc6a6 | ||
|
|
bbd3d31b6a | ||
|
|
481ac92bf8 | ||
|
|
a2f2b50f57 | ||
|
|
bbab9d0f36 | ||
|
|
7f8190056d | ||
|
|
8f4737c5f1 | ||
|
|
c5adba3c97 | ||
|
|
d91eb9e396 | ||
|
|
1b47102d6c | ||
|
|
cd147f3fc5 | ||
|
|
c3acdc8cbd | ||
|
|
c3d755dc7b | ||
|
|
6f49c48bd4 | ||
|
|
446f390b3d | ||
|
|
966091593a | ||
|
|
d5f21bc27c | ||
|
|
8c3b452c0d | ||
|
|
9cacb92c2c | ||
|
|
7a1b56db87 | ||
|
|
56c3d70149 | ||
|
|
1ec8fcc73f | ||
|
|
060b16c5ca | ||
|
|
0acc52e3e9 | ||
|
|
a9c2c9b6ba | ||
|
|
5b2a6bc56b | ||
|
|
19c8693b62 | ||
|
|
c4720e1215 | ||
|
|
b6d4c8044c | ||
|
|
57dd6ebfba | ||
|
|
c75569f278 | ||
|
|
a8757f9074 | ||
|
|
f5c3bf72e5 | ||
|
|
d7ee13f98d | ||
|
|
1f47aa491d | ||
|
|
ffe430758e | ||
|
|
a4bb453401 | ||
|
|
5c8ebce9eb | ||
|
|
8b0cee5e6f | ||
|
|
e5f4f825b6 | ||
|
|
b179dc1d56 | ||
|
|
7281c9505d | ||
|
|
4db82f447d | ||
|
|
834da3d2b4 | ||
|
|
c6c3a33dcc | ||
|
|
fb720d9b05 | ||
|
|
547d318e55 | ||
|
|
cb5a2c18f2 | ||
|
|
e01ca3ad07 | ||
|
|
314d193f35 | ||
|
|
e0200bb730 | ||
|
|
2a3f4a6f97 | ||
|
|
88628bbdc0 | ||
|
|
cb7b695a72 | ||
|
|
ae549e2a88 | ||
|
|
7f9a970714 | ||
|
|
7661d3372d | ||
|
|
dbb4f34015 | ||
|
|
8f15a4c29d | ||
|
|
1b0a885ac3 | ||
|
|
f7195bad88 | ||
|
|
15630aee5e | ||
|
|
a950a1fe24 | ||
|
|
71b8e625fe | ||
|
|
e7391675fb | ||
|
|
84fdd3fe4b | ||
|
|
4dc4b635f2 | ||
|
|
ee0c6d7f8b | ||
|
|
a637af395d | ||
|
|
59fb612315 | ||
|
|
59b21c7a3e | ||
|
|
40f881c2ac | ||
|
|
1d069683ca | ||
|
|
de1d942b90 | ||
|
|
fc73971d63 | ||
|
|
eb238bf107 | ||
|
|
2412f8b1e2 | ||
|
|
0c87dee31c | ||
|
|
215146f663 | ||
|
|
9fe1069df0 | ||
|
|
d2c5b52bf1 | ||
|
|
12153a414d | ||
|
|
5ec1092a83 | ||
|
|
284169a2f2 | ||
|
|
838bfbb75f | ||
|
|
a448da77c9 | ||
|
|
268fb22d5f | ||
|
|
07cc4c853d | ||
|
|
c62d727cbe | ||
|
|
7ef89d5043 | ||
|
|
9ceba1d6e8 | ||
|
|
e2e453985f | ||
|
|
84dccd800f | ||
|
|
f9734d202b | ||
|
|
d3cb0f4672 | ||
|
|
c198bbb6fa | ||
|
|
c965a89509 | ||
|
|
47f9da2160 | ||
|
|
348a75adb4 | ||
|
|
332218a7f7 | ||
|
|
6d7a26d2b9 | ||
|
|
d19a748f0c | ||
|
|
9c83e70a28 | ||
|
|
abcabb736b | ||
|
|
0451aaeb5c | ||
|
|
880c45830c | ||
|
|
5fa16d2344 | ||
|
|
9e50b5dd83 | ||
|
|
29d8753574 | ||
|
|
f93e1e1695 | ||
|
|
0eaac8fd7a | ||
|
|
06c71154b9 | ||
|
|
0e8f314dd6 | ||
|
|
f53ec8968b | ||
|
|
919d118f21 | ||
|
|
216b759df1 | ||
|
|
01450db71e | ||
|
|
ed987e1610 | ||
|
|
2773591e1f | ||
|
|
a995276d1e | ||
|
|
ffb6a8fa3f | ||
|
|
0966efb7f2 | ||
|
|
4a0a708092 | ||
|
|
6bf3b6f3e0 | ||
|
|
8f197fe266 | ||
|
|
e1a3f680f2 | ||
|
|
e89cca7e90 | ||
|
|
5bb2767d62 | ||
|
|
95f029e0e7 | ||
|
|
fb21e4d585 | ||
|
|
633805cec9 | ||
|
|
b8801d7d2a | ||
|
|
a84fac1b6a | ||
|
|
a9de4ceb30 | ||
|
|
827b55d60c | ||
|
|
0e1fe76b46 | ||
|
|
097c9e8e12 | ||
|
|
266356cb20 | ||
|
|
6dba39a804 | ||
|
|
3ddafa7aca | ||
|
|
9d8e232684 | ||
|
|
bf83c269c4 | ||
|
|
54e47c98cc | ||
|
|
118f2594ea | ||
|
|
ab4fcd6ac4 | ||
|
|
ca6f345429 | ||
|
|
79b8e1b4e4 | ||
|
|
cafa1ffa14 | ||
|
|
ea10df8a92 | ||
|
|
85abc42100 | ||
|
|
4747eb4386 | ||
|
|
ad9cc900b8 | ||
|
|
6cd93a7bb0 | ||
|
|
3338a02afb | ||
|
|
31cfe82224 | ||
|
|
70a191336b | ||
|
|
030477454c | ||
|
|
2a078d1572 | ||
|
|
3c1f96bc69 | ||
|
|
7d30bdc148 | ||
|
|
5d42961761 | ||
|
|
f20d5cd8d3 | ||
|
|
f5111c0f41 | ||
|
|
f5473236d0 | ||
|
|
d3cb31f1a7 | ||
|
|
d5f5cdd27a | ||
|
|
656dc8fefc | ||
|
|
a505cd9567 | ||
|
|
f2a860b01a | ||
|
|
1a5b93de9c | ||
|
|
0f165b33a6 | ||
|
|
4f53555f09 | ||
|
|
175be44823 | ||
|
|
20a6428290 | ||
|
|
4b4bea5f3b | ||
|
|
c82f860334 | ||
|
|
b2a56c047c | ||
|
|
bc6afc3933 | ||
|
|
280e4b65c3 | ||
|
|
c6f22f4d75 | ||
|
|
4bed8eb86f | ||
|
|
c482f18572 | ||
|
|
d7668acd9b | ||
|
|
05b978c568 | ||
|
|
62e5ab6990 | ||
|
|
12216f1463 | ||
|
|
cbfa13a8b4 | ||
|
|
03ec0cab1e | ||
|
|
d7940292d0 | ||
|
|
9139c5e9d6 | ||
|
|
65e62018e6 | ||
|
|
138a3673ce | ||
|
|
096f443b56 | ||
|
|
b37f30393d | ||
|
|
f095a05c42 | ||
|
|
3d15a73f1b | ||
|
|
bbd571e311 | ||
|
|
a7c554f033 | ||
|
|
25b4532ce3 | ||
|
|
a304f50a6b | ||
|
|
e75f476965 | ||
|
|
1c31460d27 | ||
|
|
19db468bf0 | ||
|
|
5fe05578c4 | ||
|
|
956f5a56cf | ||
|
|
a3f589d740 | ||
|
|
beef09bb6d | ||
|
|
ff0a246c28 | ||
|
|
f1459a1a52 | ||
|
|
f3501acb64 | ||
|
|
2238c98e95 | ||
|
|
9658d43f1f | ||
|
|
1748a0c3e5 | ||
|
|
4463d81758 | ||
|
|
74221a4ab5 | ||
|
|
0d998ed342 | ||
|
|
7d5a01756e | ||
|
|
d66313406b | ||
|
|
d96a267191 | ||
|
|
5467583bb3 | ||
|
|
9a8138d07b | ||
|
|
36c290ffea | ||
|
|
3413bf9f64 | ||
|
|
3c352a3545 | ||
|
|
56e4847b6b | ||
|
|
033b671d0b | ||
|
|
51f013851d | ||
|
|
dafa4ced27 | ||
|
|
05fe154749 | ||
|
|
5ddceb4660 | ||
|
|
341a1b195c | ||
|
|
29c3d1f9a6 | ||
|
|
734d4fb92b | ||
|
|
057a1cbab6 | ||
|
|
d44509b2cd | ||
|
|
58cf69795a | ||
|
|
6d39512576 | ||
|
|
ec4dde86f5 | ||
|
|
1c91fb9dd5 | ||
|
|
cbd650c5ef | ||
|
|
c5a769cb29 | ||
|
|
00a7277377 | ||
|
|
b8c32d41f5 | ||
|
|
49c9fc79c7 | ||
|
|
1284a7708e | ||
|
|
0dd8d15a9a | ||
|
|
90f59e954a | ||
|
|
03d7ec55a7 | ||
|
|
1929b69145 | ||
|
|
fbf194e4be | ||
|
|
a20927343a | ||
|
|
3b465dc09e | ||
|
|
fb8ca00ad1 | ||
|
|
dd7dddaa2b | ||
|
|
f41903c2a1 | ||
|
|
9984b5882d | ||
|
|
9ff20bee5a | ||
|
|
53caa11bc4 | ||
|
|
f6ac08567c | ||
|
|
040c6375c0 | ||
|
|
a03266aaad | ||
|
|
3479064348 | ||
|
|
b02d823b30 | ||
|
|
a204b6fb3f | ||
|
|
c2450843a5 | ||
|
|
00beb6170e | ||
|
|
9f1a300d2a | ||
|
|
05aefa1d5c | ||
|
|
059843f030 | ||
|
|
e202dc9851 | ||
|
|
18ae664ba7 | ||
|
|
76b563fa88 | ||
|
|
2553f4c161 | ||
|
|
f35c865348 | ||
|
|
b873ba3a75 | ||
|
|
d49e388ea3 | ||
|
|
b931699175 | ||
|
|
55fd58efd8 | ||
|
|
773847e139 | ||
|
|
3a52944f21 | ||
|
|
cc9d741275 | ||
|
|
f0096cf0e2 | ||
|
|
1d673bf6ff | ||
|
|
d986f00b6a | ||
|
|
01c3ca4f37 | ||
|
|
497bd7dad5 | ||
|
|
1d6a0ae8f1 | ||
|
|
c5e6b5ec7a | ||
|
|
ca26b4b30d | ||
|
|
254558e9de | ||
|
|
da0cd0b99c | ||
|
|
2e49c685cc | ||
|
|
a64af4da7c | ||
|
|
68bb2fa7f0 | ||
|
|
8bc2710380 | ||
|
|
1691e7ad83 | ||
|
|
6c2cb31923 | ||
|
|
0c6d920682 | ||
|
|
a126b5b61b | ||
|
|
dadb16bb04 | ||
|
|
f29473ef4c | ||
|
|
84b3162bcd | ||
|
|
c7f1469e1f | ||
|
|
d1dfd93e15 | ||
|
|
4ef55b8d1f | ||
|
|
7da22094f3 | ||
|
|
cf45cb56ad | ||
|
|
df96898543 | ||
|
|
a58bf66dea | ||
|
|
0f1fc0cc79 | ||
|
|
dc41f60f52 | ||
|
|
3d21afb640 | ||
|
|
79c3667fd4 | ||
|
|
ab1549f60e | ||
|
|
5d32fa36ff | ||
|
|
8ac17ab6e3 | ||
|
|
2076141f47 | ||
|
|
6d0f479f81 | ||
|
|
f56a5a3de1 | ||
|
|
d0c34fd760 | ||
|
|
9e7afd67bc | ||
|
|
964810858b | ||
|
|
7a51361099 | ||
|
|
ec2e71a22f | ||
|
|
5b188f35b5 | ||
|
|
5683571577 | ||
|
|
db75568905 | ||
|
|
5517305973 | ||
|
|
57ef531be0 | ||
|
|
b590e29608 | ||
|
|
569d575a96 | ||
|
|
dd8bf3776e | ||
|
|
d4ea9c8892 | ||
|
|
793c6b4a5a | ||
|
|
917c9dabc7 | ||
|
|
1d1bf504de | ||
|
|
d0c07e1e97 | ||
|
|
dfff520259 | ||
|
|
bb928bbd73 | ||
|
|
f86ec98e05 | ||
|
|
48af5c7ed6 | ||
|
|
cfaf336597 | ||
|
|
b52345236d | ||
|
|
87ebaf62c1 | ||
|
|
c7721d6100 | ||
|
|
40a722a7ff | ||
|
|
d41fbb9216 | ||
|
|
8bee0925d0 | ||
|
|
b8edca53cb | ||
|
|
34a13dd293 | ||
|
|
f72e582a80 | ||
|
|
6da2865781 | ||
|
|
a0ea12cf6c | ||
|
|
317bfde574 | ||
|
|
5f53ebdf12 | ||
|
|
cb835b7b6a | ||
|
|
bf76787e49 | ||
|
|
15a4f7e273 | ||
|
|
dc3e5ffa4b | ||
|
|
b84c7cc2bb | ||
|
|
049717260d | ||
|
|
a50a96de82 | ||
|
|
8ff8c0d176 | ||
|
|
a29b63c7d1 | ||
|
|
a8400c77fb | ||
|
|
e1c40bd218 | ||
|
|
757224683f | ||
|
|
95d982f3f3 | ||
|
|
7bfdfe5e41 | ||
|
|
8888b1a89a | ||
|
|
c6ba48be10 | ||
|
|
f132c4b5d1 | ||
|
|
87f5a8f6f2 | ||
|
|
de500af30d | ||
|
|
8b5607ac89 | ||
|
|
22727f68c1 | ||
|
|
ba64f8e5b5 | ||
|
|
b3bde5857e | ||
|
|
6e36a21d18 | ||
|
|
968ebeb5a3 | ||
|
|
47e11652fb | ||
|
|
84019ed4e7 | ||
|
|
37befd89e7 | ||
|
|
aa4f1b834a | ||
|
|
e6f8fd9234 | ||
|
|
86904892f2 | ||
|
|
d176dd6533 | ||
|
|
283efe0eac | ||
|
|
0e361cb105 | ||
|
|
53aeb085ac | ||
|
|
cd8c618f08 | ||
|
|
18b74d9797 | ||
|
|
4008934bbb | ||
|
|
8ae432554e | ||
|
|
337b26176a | ||
|
|
2e643fce28 | ||
|
|
5edd271975 | ||
|
|
c219ea06bf | ||
|
|
ffacc0d8d0 | ||
|
|
70fff77a28 | ||
|
|
bcc52d586e | ||
|
|
521ded5079 | ||
|
|
73b6b59ec9 | ||
|
|
157c81b0e9 | ||
|
|
233096354c | ||
|
|
01ac23162f | ||
|
|
4e3628c6fb | ||
|
|
d6bea8aed8 | ||
|
|
a254097092 | ||
|
|
b2a3d224a5 | ||
|
|
b495c2b60b | ||
|
|
452f76cbef | ||
|
|
3a0690bfee | ||
|
|
29fd2ff5e9 | ||
|
|
a344b3b76d | ||
|
|
14cf955cb9 | ||
|
|
31193d5b40 | ||
|
|
d6dc63c491 | ||
|
|
263f693542 | ||
|
|
3f42199f8f | ||
|
|
251ccd2e38 | ||
|
|
82ccf5886e | ||
|
|
6acb1e3853 | ||
|
|
8c0238e98f | ||
|
|
e7779c3d55 | ||
|
|
bdb0ca836c | ||
|
|
53038a0372 | ||
|
|
1b0eb91d58 | ||
|
|
5814ba38ac | ||
|
|
b2ec0d288b | ||
|
|
5171378bea | ||
|
|
7f570c074b | ||
|
|
dac675143f | ||
|
|
72a5f0e220 | ||
|
|
375aaa8430 | ||
|
|
4c704a8a3a | ||
|
|
78c0f2c7e9 | ||
|
|
c262dd06e6 | ||
|
|
e0d6b501c7 | ||
|
|
efc3f45ef6 | ||
|
|
24d8ef25bb | ||
|
|
2aca775907 | ||
|
|
7aa10ef4be | ||
|
|
17ad622ce3 | ||
|
|
cc7431a092 | ||
|
|
4199d02d98 | ||
|
|
8c434760fb | ||
|
|
5f63b99dc8 | ||
|
|
edd0ae4c59 | ||
|
|
3944e6450d | ||
|
|
a8e5ad42ba | ||
|
|
d3bfb0b87b | ||
|
|
75e3e36aa8 | ||
|
|
9102b4aa1b | ||
|
|
e744d90dbb | ||
|
|
c38b957d7c | ||
|
|
282bb26da9 | ||
|
|
6b1c30157f | ||
|
|
e433251420 | ||
|
|
49ed9c7f7f | ||
|
|
5a5c0326b7 | ||
|
|
a25708be2b | ||
|
|
e8f2934534 | ||
|
|
37f8ac9da9 | ||
|
|
0ded95ce48 | ||
|
|
108e769833 | ||
|
|
5b2313ee56 | ||
|
|
368b84b7ff | ||
|
|
864946477b | ||
|
|
da67298b43 | ||
|
|
db5cb8b3a9 | ||
|
|
9643292be6 | ||
|
|
a651e34206 | ||
|
|
a4e7fd3209 | ||
|
|
d1113d40aa | ||
|
|
dcd834d3e4 | ||
|
|
c0be8a2c04 | ||
|
|
09182172cf | ||
|
|
56e903e359 | ||
|
|
9922d60e5b | ||
|
|
09ea42439e | ||
|
|
ce1acf1adc | ||
|
|
fe00badb0f | ||
|
|
2146d67dc2 | ||
|
|
6728768b3e | ||
|
|
48db3de08c | ||
|
|
b944364d1e | ||
|
|
39c2fbe8c3 | ||
|
|
c7ba640ecb | ||
|
|
f749f6be72 | ||
|
|
ccdd384c6e | ||
|
|
4061e2c149 | ||
|
|
e7b8461555 | ||
|
|
70d1537ecc | ||
|
|
cb37f85d8e | ||
|
|
9becf565a4 | ||
|
|
b1a4e5467d | ||
|
|
4bbe8488fc | ||
|
|
54a0d126b5 | ||
|
|
9b1fbf0fbf | ||
|
|
6f626974ac | ||
|
|
5c47beb1c4 | ||
|
|
b4fbe8df07 | ||
|
|
3cc9fd2782 | ||
|
|
eaecba7ec8 | ||
|
|
42a43be092 | ||
|
|
052aafd7cb | ||
|
|
4abae578f4 | ||
|
|
4132d96591 | ||
|
|
8e4c90129e | ||
|
|
31406927e6 | ||
|
|
303646efd3 | ||
|
|
9efc4f9113 | ||
|
|
31a5a42ec7 | ||
|
|
2d0ed3ec8a | ||
|
|
de288a008d | ||
|
|
3c5d73224a | ||
|
|
05f9c07836 | ||
|
|
a7ba6add39 | ||
|
|
479973bf06 | ||
|
|
854c9fe794 | ||
|
|
5a17c75fe4 | ||
|
|
4dc5eff252 | ||
|
|
7fe0d78154 | ||
|
|
2c709dc205 | ||
|
|
9353349a39 | ||
|
|
d3049b2bfa | ||
|
|
61cb2529bd | ||
|
|
e6c6e4395f | ||
|
|
959c955616 | ||
|
|
538253cdc1 | ||
|
|
b4c6594333 | ||
|
|
a7f5f8889c | ||
|
|
1c9b4cf552 | ||
|
|
ce09f487bd | ||
|
|
a5d1decf40 | ||
|
|
7024c7d598 | ||
|
|
8109253eeb | ||
|
|
b61f1e3803 | ||
|
|
db40f80be7 | ||
|
|
26eaf97032 | ||
|
|
da349374bf | ||
|
|
0ffa925fee | ||
|
|
082787c4cf | ||
|
|
be9b5332d9 | ||
|
|
97ae3ba7d3 | ||
|
|
d047f401c2 | ||
|
|
1e9e78223b | ||
|
|
6d5baebd08 | ||
|
|
4e758dbb85 | ||
|
|
40d943c620 | ||
|
|
e69b6c4dc8 | ||
|
|
23444f7083 | ||
|
|
8c077b96df | ||
|
|
4b1a055a88 | ||
|
|
b4ddcc1dec | ||
|
|
271d2e3abc | ||
|
|
37b6399398 | ||
|
|
ebf19b1506 | ||
|
|
e4dd773644 | ||
|
|
f9b3a1f293 | ||
|
|
7c9850ada8 | ||
|
|
9ef05b8afe | ||
|
|
efdd196441 | ||
|
|
6e780a3876 | ||
|
|
b475b265ae | ||
|
|
3bb7d2c294 | ||
|
|
594a148a39 | ||
|
|
779591db36 | ||
|
|
c002eeffb7 | ||
|
|
1dac973d70 | ||
|
|
f5024f0e75 | ||
|
|
cf320c08c5 | ||
|
|
8973c9550c | ||
|
|
bb671f0e93 | ||
|
|
a8774b5011 | ||
|
|
f092cd41bc | ||
|
|
b17ec9731a | ||
|
|
021810201b | ||
|
|
6038dc9c8a | ||
|
|
4df8c9610a | ||
|
|
6c12dd4f16 | ||
|
|
ad3b8fa59f | ||
|
|
cb52a8b51b | ||
|
|
22ba1302d2 | ||
|
|
7d04559921 | ||
|
|
e40e35d30c | ||
|
|
d1af9f236c | ||
|
|
45a0ff26c5 | ||
|
|
1fd330d7a4 | ||
|
|
09833f31cf | ||
|
|
20e7a036cf | ||
|
|
e6667c1782 | ||
|
|
657935eba5 | ||
|
|
67b905a757 | ||
|
|
55cede0434 | ||
|
|
c7677d6d1e | ||
|
|
d191ca54ad | ||
|
|
20f4c952fe | ||
|
|
0bd09896f3 | ||
|
|
60ecfbfb8e | ||
|
|
8921d78610 | ||
|
|
b243ff94e9 | ||
|
|
5f1c1278e3 | ||
|
|
fa56e594b1 | ||
|
|
c9b64927be | ||
|
|
3689cb2a99 | ||
|
|
3bb7541361 | ||
|
|
7b15aa5f83 | ||
|
|
690d3036db | ||
|
|
416e8d02a1 | ||
|
|
a968c2d2b7 | ||
|
|
b4787bf444 | ||
|
|
a4d90e8aff | ||
|
|
32d0606ee4 | ||
|
|
4541f7c758 | ||
|
|
65428d629c | ||
|
|
bdfd9cc617 | ||
|
|
6d324921a0 | ||
|
|
dcf0f5c5a3 | ||
|
|
d98f851a2c | ||
|
|
a95b102396 | ||
|
|
7e2fbbaae6 | ||
|
|
070e8b0b54 | ||
|
|
7b49a1296c | ||
|
|
1e278bde92 | ||
|
|
078f402819 | ||
|
|
52af565f77 | ||
|
|
853905e52f | ||
|
|
2e0e1d2aac | ||
|
|
7f33a62bb5 | ||
|
|
bdb59ea429 | ||
|
|
1c0ffe39f7 | ||
|
|
2fbfc97cca | ||
|
|
482299e765 | ||
|
|
54f4734847 | ||
|
|
0fb6cef577 | ||
|
|
7eec264961 | ||
|
|
aff874c68a | ||
|
|
27abee0850 | ||
|
|
bcfb19f7c5 | ||
|
|
306a8ce0df | ||
|
|
d9ea8d2c9c | ||
|
|
b479956bb2 | ||
|
|
b32dc0e450 | ||
|
|
5cca5d69af | ||
|
|
e0e89213d3 | ||
|
|
e246c19eb3 | ||
|
|
d282d8dd52 | ||
|
|
9601ad13ee | ||
|
|
b7603e109d | ||
|
|
066f54906b | ||
|
|
ea0aa9df70 | ||
|
|
0811da9014 | ||
|
|
d601290c46 | ||
|
|
64357aff55 | ||
|
|
a20a3311b5 | ||
|
|
ffce5d4bb5 | ||
|
|
cbfadc019a | ||
|
|
bf5427f3e8 | ||
|
|
4c27562650 | ||
|
|
e8d20532ba | ||
|
|
d928157569 | ||
|
|
872b05a7de | ||
|
|
6ea71ec6a2 | ||
|
|
139cb72209 | ||
|
|
855a15e696 | ||
|
|
eeebd3fc1b | ||
|
|
a4b209c654 | ||
|
|
43aad3d117 | ||
|
|
f2d4fdd4d2 | ||
|
|
a630106d80 | ||
|
|
c7acd455c5 | ||
|
|
555a9d4883 | ||
|
|
ec4ce0c70c | ||
|
|
edf275badc | ||
|
|
2e91285f02 | ||
|
|
ec69ba7e0e | ||
|
|
3804ca18cb | ||
|
|
9ea3222da8 | ||
|
|
df48524ca5 | ||
|
|
b3aff1162c | ||
|
|
891ca8a31b | ||
|
|
ba99ac8b17 | ||
|
|
1ff25943dc | ||
|
|
deb58e40d5 | ||
|
|
eab6eb8fab | ||
|
|
ff65367851 | ||
|
|
f16e29c63e | ||
|
|
cdfeb094b3 | ||
|
|
b63c5d2987 | ||
|
|
015309c882 | ||
|
|
20377e9c56 | ||
|
|
08857a6198 | ||
|
|
d9ce1b3a97 | ||
|
|
d166073b16 | ||
|
|
f858c196f4 | ||
|
|
57612eeced | ||
|
|
be2257153c | ||
|
|
d920a97f4f | ||
|
|
322f2a1728 | ||
|
|
cfe6b0d9ab | ||
|
|
e229deb238 | ||
|
|
8cdde947bc | ||
|
|
c1b3ddf87a | ||
|
|
27d97add1e | ||
|
|
3783724c40 | ||
|
|
67bc4ffe68 | ||
|
|
453bbfbbde | ||
|
|
ff463c4261 | ||
|
|
748b77ae7a | ||
|
|
58c1005657 | ||
|
|
9271eb61ac | ||
|
|
c82cee25a5 | ||
|
|
2e5dfa5845 | ||
|
|
693c07b927 | ||
|
|
71a6f70f46 | ||
|
|
2952b5a7ec | ||
|
|
baa5847949 | ||
|
|
b9ce0bd99d | ||
|
|
aac61d8120 | ||
|
|
1f6edfdbcc | ||
|
|
9d1ce7fadf | ||
|
|
fd560c351f | ||
|
|
b45556062d | ||
|
|
5be45599ed | ||
|
|
9b2533dbc9 | ||
|
|
ec1a4b1974 | ||
|
|
bb9fde17c9 | ||
|
|
8cb524080c | ||
|
|
171ec54781 | ||
|
|
5d9503b78c | ||
|
|
f56cb69c2e | ||
|
|
4eb9aa9ccb | ||
|
|
11801f306c | ||
|
|
95c2944f30 | ||
|
|
5bd4c54ab6 | ||
|
|
95d6d0a0fe | ||
|
|
7941be083a | ||
|
|
e36efaec08 | ||
|
|
637afdb540 | ||
|
|
dafdedef9a | ||
|
|
ce17ee2ae6 | ||
|
|
e74daa97d2 | ||
|
|
44d64d1b80 | ||
|
|
1a4731aa83 | ||
|
|
a75e1c52b7 | ||
|
|
1b97cb263c | ||
|
|
5c9a47b6b7 | ||
|
|
8a5fe86193 | ||
|
|
d9531e24a3 | ||
|
|
624f328269 | ||
|
|
a6f4e6771d | ||
|
|
a506c21b80 | ||
|
|
981193ed23 | ||
|
|
85a6204db2 | ||
|
|
b82aba1181 | ||
|
|
0a6dea2c79 | ||
|
|
69b6d75927 | ||
|
|
eff2d48cc5 | ||
|
|
ca5af2505c | ||
|
|
a958fe86d7 | ||
|
|
3ed488e10f | ||
|
|
dcc11f16b1 | ||
|
|
209706b70d | ||
|
|
1bc80eb485 | ||
|
|
9ab9e3fe46 | ||
|
|
d654c096ed | ||
|
|
f5d5884988 | ||
|
|
2c016204bf | ||
|
|
04fd625bde | ||
|
|
8455d4a49f | ||
|
|
a3960bb7c5 | ||
|
|
769262d60e | ||
|
|
942567586f | ||
|
|
ba6baaec0a | ||
|
|
a8ac6fc738 | ||
|
|
b027d3b1d6 | ||
|
|
71f9d268c9 | ||
|
|
2b91d4af99 | ||
|
|
0ec0e286ba | ||
|
|
258ae64568 | ||
|
|
90cafa126f | ||
|
|
43d31e285c | ||
|
|
57945e6751 | ||
|
|
fce56cbf4c | ||
|
|
7a13771198 | ||
|
|
819c798e99 | ||
|
|
8560ca0661 | ||
|
|
82cdfe7014 | ||
|
|
52642f5854 | ||
|
|
6c6f9f5a44 | ||
|
|
039ce15253 | ||
|
|
695a4c785c | ||
|
|
7d7f160159 | ||
|
|
b454b4dff1 | ||
|
|
e5d711dd28 | ||
|
|
10b127ca55 | ||
|
|
fb4dff4fca | ||
|
|
ef25b364ec | ||
|
|
9394db986d | ||
|
|
9226c6cac1 | ||
|
|
283193e992 | ||
|
|
72f8a6d220 | ||
|
|
f5e4fb49c3 | ||
|
|
3cd15c783c | ||
|
|
bf51ba860a | ||
|
|
6aa8515df4 | ||
|
|
3bf4ee35a1 | ||
|
|
e08c600740 | ||
|
|
f823690b44 | ||
|
|
350b0c1e3c | ||
|
|
b01a6124a9 | ||
|
|
b00652f9eb | ||
|
|
19159a203a | ||
|
|
be8c77af5a | ||
|
|
8bb7803d23 | ||
|
|
54a85a8dd0 | ||
|
|
6fd40c0a7c | ||
|
|
97dd423486 | ||
|
|
281d60df4f | ||
|
|
43933f4089 | ||
|
|
4f7e140737 | ||
|
|
2b6945a382 | ||
|
|
8a3ae59f77 | ||
|
|
db253875cc | ||
|
|
a8359dcb75 | ||
|
|
e5dac06d91 | ||
|
|
e9f82558ed | ||
|
|
26f5ef5e31 | ||
|
|
874e889b36 | ||
|
|
bece5f7083 | ||
|
|
2f535e6db1 | ||
|
|
61c3057060 | ||
|
|
063d7d5cc4 | ||
|
|
0e0211050b | ||
|
|
c8c7245da1 | ||
|
|
3e27e50bab | ||
|
|
6b9d3ed60e | ||
|
|
11a78111de | ||
|
|
2655421171 | ||
|
|
c6bc2ea485 | ||
|
|
289b7a3dbe | ||
|
|
70083c6dca | ||
|
|
3e25b92369 | ||
|
|
806eaaf14b | ||
|
|
fb3f2d46fa | ||
|
|
14d06fe754 | ||
|
|
752146028b | ||
|
|
6c6ae30ce5 | ||
|
|
b00750bfa3 | ||
|
|
55eac005a0 | ||
|
|
257524de18 | ||
|
|
d4f78056dd | ||
|
|
66c054f24b | ||
|
|
711b722118 | ||
|
|
26614b5f40 | ||
|
|
9240211f3e | ||
|
|
67d84d956e | ||
|
|
97b620f98f | ||
|
|
2f5c91a1e1 | ||
|
|
038dad834d | ||
|
|
b3cd265955 | ||
|
|
2c670bc838 | ||
|
|
30c2b8e192 | ||
|
|
a00d45522b | ||
|
|
525369e0ce | ||
|
|
ba413f3e8f | ||
|
|
4afebca77b | ||
|
|
d2eb92143d | ||
|
|
e01d3c64fe | ||
|
|
9f497c9c2c | ||
|
|
9aae154c4e | ||
|
|
339f012794 | ||
|
|
af500d7b7b | ||
|
|
16a71b3917 | ||
|
|
7dfa104f65 | ||
|
|
44a7b1761f | ||
|
|
22c8ea255c | ||
|
|
a1c10828d8 | ||
|
|
25d69d1bd7 | ||
|
|
a84961f8ba | ||
|
|
e17b6790b5 | ||
|
|
815aed52d3 | ||
|
|
a03581ccd3 | ||
|
|
c10f6e6c6a | ||
|
|
18abd0384f | ||
|
|
4292bdd7b4 | ||
|
|
1149648399 | ||
|
|
b6846eb21d | ||
|
|
d19546fcb4 | ||
|
|
6a1eb198d1 | ||
|
|
e4757d4345 | ||
|
|
3873a59a37 | ||
|
|
cf9f6c10d7 | ||
|
|
8bcd9debc2 | ||
|
|
510a159eee | ||
|
|
062fb3ba30 | ||
|
|
3bc477d21b | ||
|
|
79eb2feb2c | ||
|
|
1fa42a5753 | ||
|
|
2eaab408dd | ||
|
|
f7fd0d9121 | ||
|
|
3b7b776ac4 | ||
|
|
43abc8440b | ||
|
|
37515b5da9 | ||
|
|
2dec327013 | ||
|
|
8f4dae3134 | ||
|
|
a584daa92d | ||
|
|
43431aa9a0 | ||
|
|
f196d2abec | ||
|
|
4a6724f664 | ||
|
|
a960737207 | ||
|
|
da08bd7fff | ||
|
|
517430f23d | ||
|
|
48e82ac15b | ||
|
|
eead64ff71 | ||
|
|
9ac6db2f4c | ||
|
|
92cf6bb887 | ||
|
|
1d3978ce2f | ||
|
|
16c71da487 | ||
|
|
214dbafd62 | ||
|
|
89b162704c | ||
|
|
fbf906d97c | ||
|
|
7961ff0785 | ||
|
|
00e53f455b | ||
|
|
d1d4839a09 | ||
|
|
31b19725b7 | ||
|
|
a776eaf61a | ||
|
|
ae2a92d229 | ||
|
|
dedc4aa8b9 | ||
|
|
7a8ca2f068 | ||
|
|
fdf52a3d59 | ||
|
|
e0987059d3 | ||
|
|
ee7217c7c9 | ||
|
|
1027659f34 | ||
|
|
424a212cc3 | ||
|
|
949ddbdcd7 | ||
|
|
7fcfc306f9 | ||
|
|
a691e033eb | ||
|
|
b76f62d470 | ||
|
|
01a90a1694 | ||
|
|
97bcc7afb6 | ||
|
|
9fa0ec440d | ||
|
|
28559cde02 | ||
|
|
6970d48cc3 | ||
|
|
52801c5afc | ||
|
|
7797bce814 | ||
|
|
18762dc624 | ||
|
|
5a828a6465 | ||
|
|
eaa9f36478 | ||
|
|
2b63134bcf | ||
|
|
8dcff63aea | ||
|
|
c2777607be | ||
|
|
9ba2b18fdb | ||
|
|
4ebc10db6a | ||
|
|
610b6c7bb0 | ||
|
|
357333c4e4 | ||
|
|
723334a685 | ||
|
|
b2c218ff83 | ||
|
|
adabd6966d | ||
|
|
b3eb1270dd | ||
|
|
7659a195d3 | ||
|
|
8d2e23f4a8 | ||
|
|
539d7dab5d | ||
|
|
06d43cdb24 | ||
|
|
af7bcf19ab | ||
|
|
7ebeb37881 | ||
|
|
4911bbe3a2 | ||
|
|
e0b6ab3f8a | ||
|
|
8736c2cf9a | ||
|
|
d825c33b55 | ||
|
|
171ecaaf62 | ||
|
|
5e6d5d4eb0 | ||
|
|
3733a3c335 | ||
|
|
7fca6defd6 | ||
|
|
2a270b399e | ||
|
|
64109aee05 | ||
|
|
e1d9395128 | ||
|
|
32eec95c26 | ||
|
|
f41cca45aa | ||
|
|
48eeab974c | ||
|
|
eed44156ae | ||
|
|
1177d9bdd8 | ||
|
|
d151a94285 | ||
|
|
a7fe6453ee | ||
|
|
313eb136f4 | ||
|
|
98591ff83d | ||
|
|
0b9d78560b | ||
|
|
32a930e598 | ||
|
|
edd8512196 | ||
|
|
7a6aec34ae | ||
|
|
009a0c5703 | ||
|
|
a99086b6bd | ||
|
|
a186672447 | ||
|
|
0b8a7c0d09 | ||
|
|
1990bf3d7a | ||
|
|
ea74a7e401 | ||
|
|
bf12c3ff74 | ||
|
|
9d261aae76 | ||
|
|
3d8c8fd745 | ||
|
|
6ad7db522a | ||
|
|
385984b1d8 | ||
|
|
4f3d4b06b5 | ||
|
|
2291986e2c | ||
|
|
fc81cf4d70 | ||
|
|
fdeab86a87 | ||
|
|
3616b7a67b | ||
|
|
83ea57d825 | ||
|
|
24a69bcade | ||
|
|
58dc3244be | ||
|
|
61e580b992 | ||
|
|
1116530a6b | ||
|
|
8cfaabedeb | ||
|
|
66ba05dcd0 | ||
|
|
d1db616d1e | ||
|
|
aed09b152a | ||
|
|
f755365e23 | ||
|
|
ccd34c1610 | ||
|
|
f9104e6cc9 | ||
|
|
4bb702fe89 | ||
|
|
511a04dad5 | ||
|
|
f3527a44d7 | ||
|
|
fdbe84cb1e | ||
|
|
45fe70f0fa | ||
|
|
2aed2fd534 | ||
|
|
a523fa9733 | ||
|
|
0f42f032e4 | ||
|
|
4575b98fd5 | ||
|
|
3a0cc0d6f6 | ||
|
|
626e2fcb12 | ||
|
|
592feb54b7 | ||
|
|
9c6b63e7e4 | ||
|
|
4364a74b7a | ||
|
|
00f13102f8 | ||
|
|
3f17389871 | ||
|
|
726ba287b1 | ||
|
|
42ee29cb3c | ||
|
|
8a98b6b012 | ||
|
|
14ab694804 | ||
|
|
14b8cda543 | ||
|
|
4264e34ffd | ||
|
|
bd9bf55e43 | ||
|
|
7c802bbd33 | ||
|
|
9e37f3f586 | ||
|
|
1d4f5d068a | ||
|
|
5be5eb80e8 | ||
|
|
12c774a34a | ||
|
|
14c3fa4378 | ||
|
|
2f17420721 | ||
|
|
8d7f8d156f | ||
|
|
38248d8c35 | ||
|
|
edaae02892 | ||
|
|
846eff4984 | ||
|
|
481adf3a1e | ||
|
|
d622f7a65c | ||
|
|
a479501aef | ||
|
|
2456374e5a | ||
|
|
c77016ea44 | ||
|
|
6fd45a37e2 | ||
|
|
9be56d3ab8 | ||
|
|
24b264b6c9 | ||
|
|
7f9130470b | ||
|
|
b82aa1daa5 | ||
|
|
53cb325974 | ||
|
|
1256c320e3 | ||
|
|
15bc30a2d5 | ||
|
|
fc3bc8468f | ||
|
|
b4e068f630 | ||
|
|
08eef80673 | ||
|
|
152f73ebf0 | ||
|
|
38de5048bc | ||
|
|
c4d96fbc49 | ||
|
|
ff25d402c1 | ||
|
|
f957024605 | ||
|
|
006e54e2fd | ||
|
|
5f7bc58788 | ||
|
|
bdd93603aa | ||
|
|
8392a17cb2 | ||
|
|
5f7f0b777e | ||
|
|
3f574606d9 | ||
|
|
45f0f93895 | ||
|
|
af2710135b | ||
|
|
95ed6094fe | ||
|
|
6af8ce9eeb | ||
|
|
3ff37f00fe | ||
|
|
ed5b066cbe | ||
|
|
cec5593c70 | ||
|
|
04924884ad | ||
|
|
3ccf64fcd3 | ||
|
|
8eb7f9b91c | ||
|
|
f25c50c629 | ||
|
|
e524a1b865 | ||
|
|
ac15e3355e | ||
|
|
0930a37819 | ||
|
|
d62f91a9e6 | ||
|
|
2789ead999 | ||
|
|
f25fd267dd | ||
|
|
47999f1f72 | ||
|
|
095bbcd15c | ||
|
|
9177bb8451 | ||
|
|
119bf9b0ff | ||
|
|
015c6037c4 | ||
|
|
452a7e7445 | ||
|
|
407586e2d5 | ||
|
|
ffa431a3cd | ||
|
|
281a5ff991 | ||
|
|
92db9bd284 | ||
|
|
ea8f319f45 | ||
|
|
a11e9fe04e | ||
|
|
27367bd1fc | ||
|
|
c6f48ae054 | ||
|
|
7d6efe3694 | ||
|
|
f4aad05edc | ||
|
|
d8f7637ca0 | ||
|
|
f9a7bd199e | ||
|
|
68b7ed284a | ||
|
|
e782895cf5 | ||
|
|
a5935b40d5 | ||
|
|
035d2cb440 | ||
|
|
2a74a49995 | ||
|
|
902953a1fa | ||
|
|
1ffef91b7a | ||
|
|
3d13d9b0dc | ||
|
|
adcc5d5692 | ||
|
|
c49d70170e | ||
|
|
349a78a5bd | ||
|
|
48734c6896 | ||
|
|
0f60a3b24d | ||
|
|
d3a88011a6 | ||
|
|
9b6e4c605b | ||
|
|
7c91524111 | ||
|
|
e1573069e4 | ||
|
|
f2459c964b | ||
|
|
43aa0b815d | ||
|
|
0740630e05 | ||
|
|
c9244b2b13 | ||
|
|
0d398f867f | ||
|
|
b74ec2d7d3 | ||
|
|
26a295c8ed | ||
|
|
2a71d3d20c | ||
|
|
b79605b692 | ||
|
|
ea0fc68a53 | ||
|
|
1ca5c32de3 | ||
|
|
f51bcfa05a | ||
|
|
e1bf68ab38 | ||
|
|
99e03b7ce5 | ||
|
|
cd70d3ea46 | ||
|
|
d387227cef | ||
|
|
2f4530e426 | ||
|
|
4db181d8bf | ||
|
|
9a7a1cc752 | ||
|
|
59ca6c6708 | ||
|
|
fe7901ca7f | ||
|
|
9351b4a5bb | ||
|
|
dfdd0a0496 | ||
|
|
cda39ec256 | ||
|
|
3720a46ff3 | ||
|
|
7ea50ea41e | ||
|
|
60a696916b | ||
|
|
b6a255d96f | ||
|
|
44a0cce7f2 | ||
|
|
f580e0d26f | ||
|
|
6beefe86e2 | ||
|
|
cbada35788 | ||
|
|
44ff2f872d | ||
|
|
2198853662 | ||
|
|
4636109081 | ||
|
|
1c042778b6 | ||
|
|
34b5962eac | ||
|
|
fc7af59eb7 | ||
|
|
7e557ca059 | ||
|
|
1d0cea8ad0 | ||
|
|
5c901d7c1e | ||
|
|
1dffab0bb8 | ||
|
|
ae89e14ea2 | ||
|
|
908255060c | ||
|
|
88278d0041 | ||
|
|
86bfd91c9d | ||
|
|
0ee412ccb9 | ||
|
|
b8bd6ea820 | ||
|
|
98a1ab3033 | ||
|
|
e360f53a40 | ||
|
|
237ec38003 | ||
|
|
30ea1bbf87 | ||
|
|
0d0aef6014 | ||
|
|
1b7441715c | ||
|
|
e3223b6124 | ||
|
|
41fb06187b | ||
|
|
adf0e8ae3b | ||
|
|
42dd1efb41 | ||
|
|
b6a6694abf | ||
|
|
04f2f50d6d | ||
|
|
6d1048e5c5 | ||
|
|
fe722c8b31 | ||
|
|
0326ce1d85 | ||
|
|
183ddb68d3 | ||
|
|
d7fe1afc08 | ||
|
|
ae9aeaf5fd | ||
|
|
ec9476216f | ||
|
|
619f2ef119 | ||
|
|
52020abde8 | ||
|
|
1bd504d67e | ||
|
|
edc4414de4 | ||
|
|
c1d588264c | ||
|
|
94b84b75ad | ||
|
|
b72a4c5aa9 | ||
|
|
857a9f3efc | ||
|
|
ce53128657 | ||
|
|
d9211053ce | ||
|
|
e8316178a0 | ||
|
|
bf763d2cf4 | ||
|
|
eba5b34982 | ||
|
|
afb8b3dd6b | ||
|
|
c5fa94894b | ||
|
|
4137758caa | ||
|
|
3578d16e9e | ||
|
|
3ef263a5cc | ||
|
|
510460c966 | ||
|
|
f74ecc53ae | ||
|
|
c4121073ad | ||
|
|
9ded2641a7 | ||
|
|
295ca68d02 | ||
|
|
27f53f262b | ||
|
|
3fc16cb414 | ||
|
|
90db25d732 | ||
|
|
bbb359470e | ||
|
|
319652c7c7 | ||
|
|
c9c271fee8 | ||
|
|
ca0755e92b | ||
|
|
acd38597f6 | ||
|
|
f4a5a80f3c | ||
|
|
c45d00fee8 | ||
|
|
ffae59fa1c | ||
|
|
b697178f68 | ||
|
|
83ade5eecb | ||
|
|
6973b92c4a | ||
|
|
6261f8a778 | ||
|
|
6048493ac6 | ||
|
|
1cbd715235 | ||
|
|
703fcbccd6 | ||
|
|
2f9cbec07e | ||
|
|
9f0b22d3e9 | ||
|
|
ab5907c09c | ||
|
|
fae0b168f6 | ||
|
|
f18e98a63e | ||
|
|
3524886d5d | ||
|
|
fb44eea06c | ||
|
|
3ea4c757e6 | ||
|
|
cfb8d79049 | ||
|
|
1ea86da7af | ||
|
|
e289f2dba2 | ||
|
|
7f64cd1801 | ||
|
|
d4526e1ed2 | ||
|
|
34f42216c8 | ||
|
|
a26a24a8ad | ||
|
|
4530fd4164 | ||
|
|
9156b8f48c | ||
|
|
6212109fc1 | ||
|
|
c22a080e23 | ||
|
|
834a7109f9 | ||
|
|
7cbf32202d | ||
|
|
d0b9380dca | ||
|
|
13fd9be566 | ||
|
|
53a9aa6ad2 | ||
|
|
c2ce4aca1b | ||
|
|
567f6d7cc0 | ||
|
|
489c0b27f9 | ||
|
|
343f988584 | ||
|
|
7f676c56c8 | ||
|
|
3c0ca7026f | ||
|
|
2ceba11aa7 | ||
|
|
6db5e0b27c | ||
|
|
dfecb801db | ||
|
|
c10bfe3db2 | ||
|
|
20fb2c99bc | ||
|
|
b4a0b5c58b | ||
|
|
faa46c2a21 | ||
|
|
9691199ae8 | ||
|
|
f736381933 | ||
|
|
f44e5b3b7a | ||
|
|
6e24bf5f8c | ||
|
|
8fb43e31c5 | ||
|
|
0860c80e51 | ||
|
|
66fc25756f | ||
|
|
f008e240cd | ||
|
|
8d7e95d6e9 | ||
|
|
3e3ce543a8 | ||
|
|
6c447a82f1 | ||
|
|
64a0918ff1 | ||
|
|
9274223701 | ||
|
|
1368c18844 | ||
|
|
67a60a7557 | ||
|
|
3d5fd47748 | ||
|
|
b9a18807ae | ||
|
|
088c0b6321 | ||
|
|
ecee11a24c | ||
|
|
ec8df7ce57 | ||
|
|
4159fd2ffb | ||
|
|
1a1d21bbb3 | ||
|
|
be1045fed9 | ||
|
|
e43773c712 | ||
|
|
30d69dadbb | ||
|
|
b138438036 | ||
|
|
d649211330 | ||
|
|
6cf211a9ad | ||
|
|
3388e5e8a4 | ||
|
|
5d497a1908 | ||
|
|
c820646fb6 | ||
|
|
5870f6f734 | ||
|
|
6732150121 | ||
|
|
1dead8b080 | ||
|
|
d547aa8ebd | ||
|
|
1da889e420 | ||
|
|
5d0a308d1d | ||
|
|
f9886d52da | ||
|
|
4f8e48b7d4 | ||
|
|
258e07c2ca | ||
|
|
cc32c50665 | ||
|
|
ec1d91f73e | ||
|
|
eb2f429964 | ||
|
|
1ad067309d | ||
|
|
48ce7df43a | ||
|
|
6555e2c440 | ||
|
|
a05191e112 | ||
|
|
b8eeee1d5d | ||
|
|
4aa87f3fa5 | ||
|
|
40c37d923b | ||
|
|
5a5837b8ed | ||
|
|
1e0b521070 | ||
|
|
35ed58cc5e | ||
|
|
c4a1579197 | ||
|
|
e471706422 | ||
|
|
d78b7350b5 | ||
|
|
47b29d5a49 | ||
|
|
e5946a51d1 | ||
|
|
a88798cc22 | ||
|
|
6fbd32523a | ||
|
|
94b1cc2bdd | ||
|
|
0ed5c8f0ae | ||
|
|
5f883f552b | ||
|
|
9db99ab4a5 | ||
|
|
287214c2b2 | ||
|
|
317a020841 | ||
|
|
b50e3aec5f | ||
|
|
21a9e0e2a7 | ||
|
|
7775df8ef1 | ||
|
|
53f9b5d131 | ||
|
|
bf4d4a4742 | ||
|
|
0dbbe7104d | ||
|
|
561ef00680 | ||
|
|
85428fa72e | ||
|
|
29d0593b86 | ||
|
|
ac524dd799 | ||
|
|
aba8b764b6 | ||
|
|
a9050e0f41 | ||
|
|
15ef84e238 | ||
|
|
09096fef5b | ||
|
|
19b08e1019 | ||
|
|
06d67642dd | ||
|
|
ceb6c450c0 | ||
|
|
a745e42cf5 | ||
|
|
462d6a4450 | ||
|
|
a2e39c5e2e | ||
|
|
ec899be3b5 | ||
|
|
c79ebfdd0a | ||
|
|
cd95e6c552 | ||
|
|
f676145302 | ||
|
|
0847267069 | ||
|
|
6fe1da1587 | ||
|
|
7ab555d869 | ||
|
|
4a35e9e60d | ||
|
|
5e8dfdfd9b | ||
|
|
278a1b8ab3 | ||
|
|
6cb03dded1 | ||
|
|
4752ec1b67 | ||
|
|
e641371544 | ||
|
|
8d9a7e9af1 | ||
|
|
2b8e9bf887 | ||
|
|
a16c55c679 | ||
|
|
9a72c40149 | ||
|
|
8424fc4c19 | ||
|
|
9f29a047a7 | ||
|
|
af4904ce8d | ||
|
|
6f2a323063 | ||
|
|
1da8ecfaac | ||
|
|
1ba386bbd2 | ||
|
|
d00d791cda | ||
|
|
51aabd7b21 | ||
|
|
7840c3bdc8 | ||
|
|
07d13002b0 | ||
|
|
23abe2ba06 | ||
|
|
4d73821d21 | ||
|
|
f2687cf807 | ||
|
|
8abce0d4cf | ||
|
|
62ad3848c4 | ||
|
|
521e9969f1 | ||
|
|
5ee1ceced3 | ||
|
|
efffbafa42 | ||
|
|
899cec8814 | ||
|
|
55beb993e5 | ||
|
|
fba46a1e00 | ||
|
|
68c4d6f295 | ||
|
|
2ad07c018e | ||
|
|
c2418559f1 | ||
|
|
de0277eb3b | ||
|
|
b58802b495 | ||
|
|
898e20e8b2 | ||
|
|
75bf10d83d | ||
|
|
e8e58cc4a2 | ||
|
|
342d02c8a8 | ||
|
|
ae885eaddc | ||
|
|
b75bb4ed9f | ||
|
|
a171863591 | ||
|
|
bcc2286018 | ||
|
|
beb8b9723f | ||
|
|
ab3129b9c3 | ||
|
|
c061505bf8 | ||
|
|
171bbedcde | ||
|
|
ae655727c0 | ||
|
|
288b3783c3 | ||
|
|
320612c826 | ||
|
|
9b0a293d2b | ||
|
|
d5b6f27e97 | ||
|
|
cf8bd759e3 | ||
|
|
c2142c5cd9 | ||
|
|
eeef536803 | ||
|
|
ec51cf7606 | ||
|
|
ec83b76e46 | ||
|
|
6bc73f8f43 | ||
|
|
65127e04aa | ||
|
|
2726045fb0 | ||
|
|
d4046c3295 | ||
|
|
97d4f5583c | ||
|
|
96dc36cd16 | ||
|
|
ab2344ebfc | ||
|
|
4dd7d86ea8 | ||
|
|
6adfa618a3 | ||
|
|
855e896a5a | ||
|
|
2bacb0f073 | ||
|
|
399131a5a9 | ||
|
|
4c77be9e83 | ||
|
|
8ed5463f19 | ||
|
|
63739df903 | ||
|
|
3d72232bb9 | ||
|
|
d5ff811de7 | ||
|
|
f74f47965c | ||
|
|
79adb7225e | ||
|
|
11e79914ef | ||
|
|
9b34fc369b | ||
|
|
ae812806a1 | ||
|
|
956c7728bf | ||
|
|
6d5a5a46e4 | ||
|
|
b244126d60 | ||
|
|
2a7f8c5229 | ||
|
|
59fddf7c59 | ||
|
|
d21fd2a1ed | ||
|
|
6828a5d9a0 | ||
|
|
32960332b9 | ||
|
|
9ba77c9498 | ||
|
|
65f9a9dcc1 | ||
|
|
13315eec42 | ||
|
|
e43ccd155b | ||
|
|
e92960a413 | ||
|
|
e82eec0cd6 | ||
|
|
23b687c528 | ||
|
|
5044a814a1 | ||
|
|
3250b95f5e | ||
|
|
2a0934ec28 | ||
|
|
f1752abc5d | ||
|
|
e931ec74dd | ||
|
|
528d823c55 | ||
|
|
6475b58541 | ||
|
|
59e8b26015 | ||
|
|
0894c21296 | ||
|
|
4223bdd4ad | ||
|
|
c55ed42b2b | ||
|
|
7abc833ebe | ||
|
|
6abe399e36 | ||
|
|
555b1a16da | ||
|
|
52b956e677 | ||
|
|
0296cbe9e9 | ||
|
|
8315d7790a | ||
|
|
dd60d289ff | ||
|
|
8c61fd0bf7 | ||
|
|
814e08edd4 | ||
|
|
44956dff85 | ||
|
|
18685d061a | ||
|
|
692d5be166 | ||
|
|
60bdaef716 | ||
|
|
cb51f44a45 | ||
|
|
88f43a8124 | ||
|
|
7de7cdba60 | ||
|
|
ba140c60e3 | ||
|
|
95a5c7a001 | ||
|
|
b399da72d8 | ||
|
|
f9a10d8932 | ||
|
|
549ce6fbf9 | ||
|
|
e63d27a035 | ||
|
|
ab3621fe3c | ||
|
|
8b99f2ecbc | ||
|
|
433b309907 | ||
|
|
ac40cec138 | ||
|
|
93702ece48 | ||
|
|
1678474830 | ||
|
|
4260099c23 | ||
|
|
6139cb50bc | ||
|
|
49e334d726 | ||
|
|
6beded153b | ||
|
|
750308a16a | ||
|
|
57fa00b765 | ||
|
|
b57d4fdbec | ||
|
|
93aca81265 | ||
|
|
5c704e142e | ||
|
|
1a4a77066e | ||
|
|
a690b9d825 | ||
|
|
b70969fd03 | ||
|
|
daf5fff83f | ||
|
|
aef1f9b857 | ||
|
|
c1c8ea7df0 | ||
|
|
b75034b40c | ||
|
|
cb41a79b36 | ||
|
|
cedc20ce6a | ||
|
|
43bc1e9116 | ||
|
|
6fa1379c1e | ||
|
|
cb16438b1c | ||
|
|
e6c8d6cc7d | ||
|
|
f0e87e71ab | ||
|
|
cdd28b8e31 | ||
|
|
9bff564ace | ||
|
|
62c616c6af | ||
|
|
8a35f5d6ca | ||
|
|
e1b051324d | ||
|
|
a947c3152a | ||
|
|
cca43040d3 | ||
|
|
9b42657ca7 | ||
|
|
bb19c55c3a | ||
|
|
1ec6611410 | ||
|
|
218bd0ffc1 | ||
|
|
d649a22b80 | ||
|
|
0bffbbfe65 | ||
|
|
04a562372b | ||
|
|
11b08ce53a | ||
|
|
70f8f9679d | ||
|
|
4773f9ebf6 | ||
|
|
c1b6d1706a | ||
|
|
9f94b8f915 | ||
|
|
3abd97d0fb | ||
|
|
416a0687ee | ||
|
|
7056e20075 | ||
|
|
de4b158a44 | ||
|
|
c7f4648d5a | ||
|
|
a456be9d76 | ||
|
|
3befaac114 | ||
|
|
11616ee03b | ||
|
|
2a59feddb6 | ||
|
|
e2fa0aface | ||
|
|
6d4d954713 | ||
|
|
a4592ca425 | ||
|
|
700eae4cc6 | ||
|
|
2c9fe6f37d | ||
|
|
558ede11cf | ||
|
|
4c0a68ab0b | ||
|
|
d6b4931001 | ||
|
|
79ef01cb25 | ||
|
|
ea8735f390 | ||
|
|
7b70855e94 | ||
|
|
65f818b631 | ||
|
|
0a0baeaeab | ||
|
|
e011ed1f64 | ||
|
|
5142bf4338 | ||
|
|
e3532612ff | ||
|
|
d25e403233 | ||
|
|
8a5580eae5 | ||
|
|
cf1251ad7b | ||
|
|
4b1d0e8786 | ||
|
|
b6e99ce4a6 | ||
|
|
920def30d7 | ||
|
|
3839aa7419 | ||
|
|
8fde720f02 | ||
|
|
c6dfaa30b5 | ||
|
|
0d4975ba0f | ||
|
|
77325c98a6 | ||
|
|
01dc088a6f | ||
|
|
c20e9820fe | ||
|
|
d255c116dd | ||
|
|
2c1da3458a | ||
|
|
8017e42797 | ||
|
|
c162a7d3b1 | ||
|
|
d759d3dfee | ||
|
|
daecf4db14 | ||
|
|
1e8318598a | ||
|
|
53450d1160 | ||
|
|
c0049b3223 | ||
|
|
3ea9ce0270 | ||
|
|
bb18ffd9e7 | ||
|
|
1064c04ab8 | ||
|
|
2d84027a59 | ||
|
|
98680508d3 | ||
|
|
74cbec468f | ||
|
|
31e89b0868 | ||
|
|
c12a7a6319 | ||
|
|
82b0df6058 | ||
|
|
b6bd67b62f | ||
|
|
877226014d | ||
|
|
7572d306a0 | ||
|
|
f2f4573064 | ||
|
|
b1641edff6 | ||
|
|
b5147256e9 | ||
|
|
06f596adc6 | ||
|
|
1f3b54e0c4 | ||
|
|
2ddfbe8566 | ||
|
|
c61a118e4f | ||
|
|
d69e61a634 | ||
|
|
14f0cbaec6 | ||
|
|
b313eb14ee | ||
|
|
7b47e40244 | ||
|
|
b52204817d | ||
|
|
377552103e | ||
|
|
688b65ccde | ||
|
|
6cb4faf33d | ||
|
|
78b83bb901 | ||
|
|
9ff6f60b66 | ||
|
|
624e10ed15 | ||
|
|
19e10bbb53 | ||
|
|
cca945e05b | ||
|
|
21901f2a75 | ||
|
|
ef7f943eee | ||
|
|
ec1062f9f2 | ||
|
|
2f67ed3138 | ||
|
|
ce912db30e | ||
|
|
41d790f346 | ||
|
|
bf426e15ec | ||
|
|
e4403baeb9 | ||
|
|
61101b00a1 | ||
|
|
69f8ffcfeb | ||
|
|
6b8042291c | ||
|
|
ffc0c83b50 | ||
|
|
8ccd4c269a | ||
|
|
934ec86f93 | ||
|
|
23be38b5fa | ||
|
|
fe7f74e46b | ||
|
|
a3fd78f8e2 | ||
|
|
137bad6f7b | ||
|
|
17df6fc764 | ||
|
|
2e51c8a124 | ||
|
|
5588a46366 | ||
|
|
a8122f9add | ||
|
|
5568be91d2 | ||
|
|
a04bd6f93c | ||
|
|
56d63b10e4 | ||
|
|
2c97643b10 | ||
|
|
679f403648 | ||
|
|
d482c707f6 | ||
|
|
2ec9641783 | ||
|
|
dab1788a3b | ||
|
|
47bb79cce1 | ||
|
|
41dbc20be9 | ||
|
|
10a631ec96 | ||
|
|
830e5aed96 | ||
|
|
7db573885b | ||
|
|
a74d56ebc6 | ||
|
|
ff7d84297e | ||
|
|
3a76509fe9 | ||
|
|
ac4de9ab0f | ||
|
|
471f397418 | ||
|
|
73bbdf6d4e | ||
|
|
7f26aea585 | ||
|
|
1c767b709f | ||
|
|
0ced82c885 | ||
|
|
21dd195b0d | ||
|
|
6aa6cfba8e | ||
|
|
fd7d52d38b | ||
|
|
a47bb14364 | ||
|
|
d6e6fa5735 | ||
|
|
46da11a52e | ||
|
|
68e3dc21e4 | ||
|
|
7232cc45b4 | ||
|
|
be5a297248 | ||
|
|
257031b1bc | ||
|
|
c9db9fa17a | ||
|
|
13f961a422 | ||
|
|
3b38e0c4e1 | ||
|
|
07526efe61 | ||
|
|
8753c02adb | ||
|
|
6a0bbfa447 | ||
|
|
21faaeb33d | ||
|
|
0525fc5909 | ||
|
|
a1a53bb285 | ||
|
|
0c453c4415 | ||
|
|
d0406f9736 | ||
|
|
ba74b8603d | ||
|
|
c675a4d61d | ||
|
|
965c45bc70 | ||
|
|
139a22602a | ||
|
|
e0e4969198 | ||
|
|
08d69d95b3 | ||
|
|
4e6c507ba9 | ||
|
|
fd06374365 | ||
|
|
a07ebc636a | ||
|
|
4c151ac9aa | ||
|
|
05c425698f | ||
|
|
2a961979e6 | ||
|
|
211ede92cc | ||
|
|
256af03772 | ||
|
|
654fd5a4f9 | ||
|
|
541d90e49f | ||
|
|
974e7038e7 | ||
|
|
e2f5b30aa9 | ||
|
|
3483e7d9e0 | ||
|
|
56cb20a1af | ||
|
|
64929653dd | ||
|
|
c955da9bc6 | ||
|
|
291354fa8e | ||
|
|
905d736512 | ||
|
|
3406d6e2a9 | ||
|
|
fc10b5ffb9 | ||
|
|
f89c313166 | ||
|
|
7c734168d0 | ||
|
|
1e7bfec2ce | ||
|
|
1eb0603b4e | ||
|
|
4b32730ce8 | ||
|
|
ad083c1d9b | ||
|
|
b4f84c2de2 | ||
|
|
fc17443ce4 | ||
|
|
342ae06b21 | ||
|
|
093fb7f959 | ||
|
|
f6472424ad | ||
|
|
31ed3767c6 | ||
|
|
366acb65ea | ||
|
|
7c6946931b | ||
|
|
5d971433a5 | ||
|
|
05264b326b | ||
|
|
fdd5c6bfd8 | ||
|
|
42c3528c2f | ||
|
|
18640714f1 | ||
|
|
cda4d3399b | ||
|
|
4da8af6e69 | ||
|
|
b535565612 | ||
|
|
bef39b8a96 | ||
|
|
fb2502a031 | ||
|
|
0b90befda1 | ||
|
|
f9800f104a | ||
|
|
99134cc381 | ||
|
|
66ca08da6d | ||
|
|
5eb7ece6ba | ||
|
|
6b3d334e76 | ||
|
|
14f5fd8f73 | ||
|
|
5f73aee0df | ||
|
|
f8666ba367 | ||
|
|
9e80f76dd8 | ||
|
|
c76a5eaf67 | ||
|
|
cd378f0168 | ||
|
|
7d51ff0cf5 | ||
|
|
47819ea956 | ||
|
|
c7e3560c98 | ||
|
|
b24400b21d | ||
|
|
6c1d651687 | ||
|
|
e7757b53e7 | ||
|
|
a6d182e92d | ||
|
|
925eca1463 | ||
|
|
8b454f0d39 | ||
|
|
7c4d110353 | ||
|
|
6df55523b6 | ||
|
|
3ec6a24634 | ||
|
|
164b4218c4 | ||
|
|
56df8a6477 | ||
|
|
47a83b312d | ||
|
|
41a28ae088 | ||
|
|
436a8755ae | ||
|
|
960b179d95 | ||
|
|
0f0d0e1076 | ||
|
|
a8bd0d8075 | ||
|
|
986d3af685 | ||
|
|
1833f9ffdf | ||
|
|
30a6877f8a | ||
|
|
aaae2583c7 | ||
|
|
7f24afc2e7 | ||
|
|
0040923e12 | ||
|
|
844efb88d8 | ||
|
|
9efc3dd1fb | ||
|
|
67853bad8e | ||
|
|
faa8e1441a | ||
|
|
5c54611d1b | ||
|
|
dcf55e4385 | ||
|
|
2b0f1b6aab | ||
|
|
ae6cc8eea3 | ||
|
|
5279fa49a7 | ||
|
|
dcd8a62784 | ||
|
|
8c197b0e1a | ||
|
|
aed824b200 | ||
|
|
036b30212e | ||
|
|
3451ab3f50 | ||
|
|
0d0a92c2b1 | ||
|
|
aa19bc7bf5 | ||
|
|
347759b2e7 | ||
|
|
352230446c | ||
|
|
3eff8102e1 | ||
|
|
6693d845d9 | ||
|
|
4d79c462db | ||
|
|
c44ef6a1dc | ||
|
|
f0996fcfa7 | ||
|
|
54bc384d37 | ||
|
|
504fc1efe8 | ||
|
|
f4179b93fb | ||
|
|
564252c198 | ||
|
|
802a7a4463 | ||
|
|
3b3d6ba13c | ||
|
|
7350bf58e2 | ||
|
|
d37e29afc6 | ||
|
|
40de8c9e23 | ||
|
|
c81eac13c8 | ||
|
|
a6e1860f0d | ||
|
|
03eb2d81f0 | ||
|
|
171710b5e8 | ||
|
|
bed76429c2 | ||
|
|
d19f9b5062 | ||
|
|
38081d9822 | ||
|
|
54e278d3f7 | ||
|
|
181ed1b1a5 | ||
|
|
fb2d325ccb | ||
|
|
5f94a52537 | ||
|
|
c69b50c5d2 | ||
|
|
1c72f89178 | ||
|
|
14bd16da14 | ||
|
|
11a57f4618 | ||
|
|
57f35aff90 | ||
|
|
60e63a307f | ||
|
|
175e878ea6 | ||
|
|
5c960a3213 | ||
|
|
5dfb299e37 | ||
|
|
3890d4d9d1 | ||
|
|
77c62d6e7d | ||
|
|
ba54b53194 | ||
|
|
b4ef7352f2 | ||
|
|
1ce3368530 | ||
|
|
a4b32f3cb7 | ||
|
|
ee9cc05ae0 | ||
|
|
b8ccf2b0d6 | ||
|
|
886b499b94 | ||
|
|
07924d5621 | ||
|
|
43f3367ae4 | ||
|
|
454c73f42f | ||
|
|
041df698d5 | ||
|
|
97081f1219 | ||
|
|
f6792bf080 | ||
|
|
88635f31d6 | ||
|
|
abd0f115fc | ||
|
|
e9766c76c1 | ||
|
|
570506b324 | ||
|
|
11889880eb | ||
|
|
a86abde893 | ||
|
|
2cfe3360d8 | ||
|
|
60d75cb8ee | ||
|
|
68838e310a | ||
|
|
161de6cb7c | ||
|
|
af5a9b644b | ||
|
|
785426eab5 | ||
|
|
9267aef498 | ||
|
|
ae27a07578 | ||
|
|
131b2a35aa | ||
|
|
5a89601b24 | ||
|
|
2528bbc552 | ||
|
|
7c3a480003 | ||
|
|
80eac8443d | ||
|
|
a97234c48d | ||
|
|
53ea58c2f6 | ||
|
|
d867524c6b | ||
|
|
5edf9bde78 | ||
|
|
770ea55872 | ||
|
|
4eb0101c5b | ||
|
|
5d7af94abf | ||
|
|
b729b8f7c8 | ||
|
|
064e69d943 | ||
|
|
d880931951 | ||
|
|
f24741cd32 | ||
|
|
45c7017e83 | ||
|
|
7cfb891e6b | ||
|
|
fc8604e896 | ||
|
|
6b5e94103d | ||
|
|
aee4679ae5 | ||
|
|
2c2c930fce | ||
|
|
3f309e4db5 | ||
|
|
d26be402db | ||
|
|
a571e83005 | ||
|
|
10d5228eb2 | ||
|
|
7ed49b476f | ||
|
|
5396b90695 | ||
|
|
a6983d4e7b | ||
|
|
a3d1c76f67 | ||
|
|
15fab226b7 | ||
|
|
5a065d5a05 | ||
|
|
de81f3ffbb | ||
|
|
9103369cf6 | ||
|
|
7be36e6d0d | ||
|
|
a00e3e6f41 | ||
|
|
82ba02b4f3 | ||
|
|
d70ae6ebe3 | ||
|
|
f6c411a261 | ||
|
|
b606eaf9ee | ||
|
|
516edd1b09 | ||
|
|
e31c3b1f27 | ||
|
|
619818f968 | ||
|
|
79a80a1adf | ||
|
|
7cef48b995 | ||
|
|
7d3d1b1544 | ||
|
|
3f935f271d | ||
|
|
89935a1517 | ||
|
|
c67af4fb2f | ||
|
|
0b4adc36a0 | ||
|
|
44776b795f | ||
|
|
bec73a1c43 | ||
|
|
6ce35fdfa8 | ||
|
|
dabc2d0442 | ||
|
|
0527d3bc2b | ||
|
|
a7cfb71070 | ||
|
|
52003bedb4 | ||
|
|
a02fb8e739 | ||
|
|
60fad187a2 | ||
|
|
e8cd1e070f | ||
|
|
de6620be12 | ||
|
|
72dee73faa | ||
|
|
d8ce27907d | ||
|
|
3d8891d518 | ||
|
|
97742ccdc2 | ||
|
|
82fec86179 | ||
|
|
be83b53875 | ||
|
|
85fda0c18b | ||
|
|
a89f8fbd9c | ||
|
|
efdfa1f2f7 | ||
|
|
5bd61e3fb0 | ||
|
|
a45f83b646 | ||
|
|
16135b8e37 | ||
|
|
b011e8656f | ||
|
|
215432be6c | ||
|
|
d373760412 | ||
|
|
a1de04e285 | ||
|
|
23e16732fd | ||
|
|
5efac84b8b | ||
|
|
2cbc7b7d7d | ||
|
|
b1acbaecc2 | ||
|
|
6d61e8efff | ||
|
|
482e6b3cb3 | ||
|
|
445b13ec29 | ||
|
|
116af372dc | ||
|
|
970952783c | ||
|
|
e59cf13456 | ||
|
|
d0cfddce19 | ||
|
|
30b2a8dd8d | ||
|
|
b811ee7e7e | ||
|
|
ebe7f6784a | ||
|
|
e40792378f | ||
|
|
cc9c8fb891 | ||
|
|
ca06c4d403 | ||
|
|
c8aa058ede | ||
|
|
34169d685e | ||
|
|
d5a9d36815 | ||
|
|
c7aaeca530 | ||
|
|
863e4f0c19 | ||
|
|
0226e0553d | ||
|
|
02995d278f | ||
|
|
78a2104bcc | ||
|
|
4e9d143996 | ||
|
|
0811e5c765 | ||
|
|
b2cf2edd43 | ||
|
|
db493f6887 | ||
|
|
2cd0dec480 | ||
|
|
29024888fb | ||
|
|
dbcaab2bc1 | ||
|
|
28d445ae1c | ||
|
|
530360f859 | ||
|
|
738c55bad0 | ||
|
|
4b09bc85f5 | ||
|
|
5bc67d3570 | ||
|
|
f7ae6222b7 | ||
|
|
1e50dab093 | ||
|
|
d1935bf778 | ||
|
|
70a346d11e | ||
|
|
fd39a2063d | ||
|
|
682512fffe | ||
|
|
b13f91ec8d | ||
|
|
a140fc09ac | ||
|
|
f403a7e753 | ||
|
|
dfe5f412eb | ||
|
|
033d784c52 | ||
|
|
62c3fa13ca | ||
|
|
ce338cb6ca | ||
|
|
003eadc8fd | ||
|
|
8782151c5d | ||
|
|
b22c74c5a8 | ||
|
|
254fa36c01 | ||
|
|
a3e4253005 | ||
|
|
2388593b8a | ||
|
|
cdced63c1b | ||
|
|
45e1d1ecef | ||
|
|
f44447ce71 | ||
|
|
238e9cd8cc | ||
|
|
e171d8ed0e | ||
|
|
bd3399e04b | ||
|
|
2b4443f333 | ||
|
|
ab6548122f | ||
|
|
f81573d999 | ||
|
|
84ccebb858 | ||
|
|
530bc50e7c | ||
|
|
57e490fc23 | ||
|
|
61e902c094 | ||
|
|
8378ba77d6 | ||
|
|
c9e30b74e2 | ||
|
|
af944fd2e3 | ||
|
|
bcc0e76f1d | ||
|
|
95078d250a | ||
|
|
4b16a2c0c5 | ||
|
|
b8524732ce | ||
|
|
814fee4f47 | ||
|
|
d641d35d5c | ||
|
|
7464d95b57 | ||
|
|
8924a64622 | ||
|
|
3d6aa667fe | ||
|
|
147c3d2e7b | ||
|
|
ac298c3be3 | ||
|
|
e88848c44a | ||
|
|
cd518e3e4c | ||
|
|
114d521636 | ||
|
|
24d4fad394 | ||
|
|
6d8785e689 | ||
|
|
508cbf0a82 | ||
|
|
c83f56166d | ||
|
|
7199e1a214 | ||
|
|
85d55e97e7 | ||
|
|
cc2c71c076 | ||
|
|
9ca273b2c4 | ||
|
|
b85c2f35b6 | ||
|
|
fdd79885f9 | ||
|
|
b2eb970796 | ||
|
|
3ee9c1b550 | ||
|
|
2566c24753 | ||
|
|
49e1b0ba7e | ||
|
|
453c329f14 | ||
|
|
abad2944fb | ||
|
|
27193f38f3 | ||
|
|
d3dc94e210 | ||
|
|
6dad860635 | ||
|
|
0362ac8909 | ||
|
|
e7b79f83d1 | ||
|
|
62379c1e41 | ||
|
|
23b422e3df | ||
|
|
f8e6dee635 | ||
|
|
c8e9b287f4 | ||
|
|
c9412dbcd0 | ||
|
|
77222e9e6b | ||
|
|
2827544409 | ||
|
|
9d0f24eae1 | ||
|
|
db0a399da1 | ||
|
|
6e527947be | ||
|
|
e7051c1129 | ||
|
|
3196c7ca09 | ||
|
|
0e1e32d241 | ||
|
|
a34912fb0d | ||
|
|
c7c6e0e2ff | ||
|
|
1e529c995a | ||
|
|
7be1c7a47b | ||
|
|
b17380443b | ||
|
|
59e68682bd | ||
|
|
b7a92cfe92 | ||
|
|
5ebe27da49 | ||
|
|
42df6ba6fa | ||
|
|
8210fddfab | ||
|
|
f55ed273c5 | ||
|
|
d67e95af7b | ||
|
|
0b0f235252 | ||
|
|
36a5f52068 | ||
|
|
31266728f7 | ||
|
|
87d2096ed7 | ||
|
|
8c79ea4ce3 | ||
|
|
c73a4204cb | ||
|
|
0b3c2cc252 | ||
|
|
2bd3ca1d0b | ||
|
|
ce8649d991 | ||
|
|
9bd563b111 | ||
|
|
6ceb924a85 | ||
|
|
c2ef0ded43 | ||
|
|
6081a6f6db | ||
|
|
a0d92a0b1d | ||
|
|
3cf1f7ede2 | ||
|
|
5757afa1d8 | ||
|
|
86e9b9c1b8 | ||
|
|
1cdd1fa00e | ||
|
|
9d12759c68 | ||
|
|
d47f66548d | ||
|
|
594341fab6 | ||
|
|
4e88125cbe | ||
|
|
13237180a2 | ||
|
|
f64d7e0b6e | ||
|
|
040a6930a4 | ||
|
|
c54b9189a6 | ||
|
|
8882f1b019 | ||
|
|
ae6416c4d2 | ||
|
|
8faed87656 | ||
|
|
0983f05969 | ||
|
|
d43e2544a1 | ||
|
|
ca83d11ac8 | ||
|
|
1cdcdd9b5f | ||
|
|
cc7806e35b | ||
|
|
0ee48b6623 | ||
|
|
8c02e0efbd | ||
|
|
34d3ca82bc | ||
|
|
43822d3667 | ||
|
|
f4ac73b3b4 | ||
|
|
f084b6def9 | ||
|
|
a00d101ff7 | ||
|
|
9d5900d9b6 | ||
|
|
28fb4e8216 | ||
|
|
bec4dbe652 | ||
|
|
72cc14f508 | ||
|
|
d20941cc2c | ||
|
|
9cb8a05316 | ||
|
|
dccd799f6d | ||
|
|
b42b3d1b01 | ||
|
|
a40d6f772a | ||
|
|
6e9bfd18d9 | ||
|
|
3b92dd0139 | ||
|
|
564d53610a | ||
|
|
b4c7b8ac7f | ||
|
|
7acd90307b | ||
|
|
d3ec76c19f | ||
|
|
fb9425e503 | ||
|
|
688cb20674 | ||
|
|
c63be20bea | ||
|
|
df36633223 | ||
|
|
3597621d88 | ||
|
|
8387684839 | ||
|
|
f261f395f1 | ||
|
|
f27170ff0e | ||
|
|
d82c951db6 | ||
|
|
41ca853e03 | ||
|
|
d75580e11d | ||
|
|
a08d098265 | ||
|
|
a64960ddd0 | ||
|
|
875681b8ce | ||
|
|
a03dcbbf55 | ||
|
|
97cabbbc69 | ||
|
|
13725a9e21 | ||
|
|
f47df961f7 | ||
|
|
2f644d5eeb | ||
|
|
4b292bb78c | ||
|
|
804891cc81 | ||
|
|
d335e06371 | ||
|
|
477058ad23 | ||
|
|
eb3b68401d | ||
|
|
865d2df124 | ||
|
|
88160bae1d | ||
|
|
f581e93b88 | ||
|
|
876850a7a7 | ||
|
|
21a7cf7158 | ||
|
|
5edee4bae0 | ||
|
|
916ca5576a | ||
|
|
6c861bfd1f | ||
|
|
56961b55bd | ||
|
|
cdcd7154ba | ||
|
|
654a2ee870 | ||
|
|
903634073a | ||
|
|
0d4818feb6 | ||
|
|
d6aa40679b | ||
|
|
b7cc31c94d | ||
|
|
6860156b6f | ||
|
|
29486c9ce2 | ||
|
|
7cfa6a5da4 | ||
|
|
2563be472b | ||
|
|
7289e856d9 | ||
|
|
975de1954e | ||
|
|
95bcf0c080 | ||
|
|
f900a5ef4f | ||
|
|
7f1ab529ae | ||
|
|
49fc86e4b1 | ||
|
|
924aef84f1 | ||
|
|
96e6e2b72a | ||
|
|
71997d4e65 | ||
|
|
447f2f9506 | ||
|
|
79aef9024b | ||
|
|
fdf6f4fdf3 | ||
|
|
4d1eaaaade | ||
|
|
bdad6c0f6d | ||
|
|
ff1ca5d933 | ||
|
|
2cf4c494a4 | ||
|
|
95ac0a861a | ||
|
|
746c301f39 | ||
|
|
6455b12b58 | ||
|
|
485b8fe993 | ||
|
|
d7527f280c | ||
|
|
d57fa4375d | ||
|
|
d9e42c6625 | ||
|
|
28293d3fce | ||
|
|
d505401446 | ||
|
|
fafc24aeae | ||
|
|
f78ef0d208 | ||
|
|
8384cc3652 | ||
|
|
60aa18a229 | ||
|
|
3d64b42a89 | ||
|
|
b301997d4b | ||
|
|
ab34743250 | ||
|
|
bc14a1d167 | ||
|
|
2886ec116f | ||
|
|
c2beb2a5fa | ||
|
|
d6ac10f527 | ||
|
|
9dcd8a707a | ||
|
|
e1e97ef158 | ||
|
|
5d6b37f81a | ||
|
|
e1da08ba38 | ||
|
|
1dfb50fefd | ||
|
|
5c06ebc9c8 | ||
|
|
52a9270fb0 | ||
|
|
82247d7422 | ||
|
|
b34688043f | ||
|
|
ce4bcbd19d | ||
|
|
cde9a02c32 | ||
|
|
fe1da4ea12 | ||
|
|
a73306817b | ||
|
|
54e683d3d4 | ||
|
|
f49910ca82 | ||
|
|
4052f7f736 | ||
|
|
b47e097983 | ||
|
|
e44dbfb2a4 | ||
|
|
7d69dd9400 | ||
|
|
e6aae8fcfa | ||
|
|
da800b3391 | ||
|
|
3a574bcecc | ||
|
|
1bb0e234e7 | ||
|
|
b7e14ebf2a | ||
|
|
2af1207702 | ||
|
|
ecfed30e6e | ||
|
|
d06c3e3dd8 | ||
|
|
16b3fbeb16 | ||
|
|
0938804947 | ||
|
|
851bcf9816 | ||
|
|
9f6fc785bc | ||
|
|
56636bf5d4 | ||
|
|
3899a65167 | ||
|
|
628e53c1c3 | ||
|
|
0b689d99fa | ||
|
|
9fa424dd8d | ||
|
|
3e6f2eecfa | ||
|
|
cc655c8ba8 | ||
|
|
78aa0474ee | ||
|
|
9caefa2f49 | ||
|
|
478726fa3b | ||
|
|
f64917ec52 | ||
|
|
2bc25f91c4 | ||
|
|
623d7ffe2f | ||
|
|
07510b5099 | ||
|
|
9f21f9a7bc | ||
|
|
93da70709e | ||
|
|
00436e744a | ||
|
|
1e642fc512 | ||
|
|
6baef2450c | ||
|
|
600f34f85a | ||
|
|
6c0c6bc5c4 | ||
|
|
fcd62ed3cd | ||
|
|
cd0064d19c | ||
|
|
785f2e3a6d | ||
|
|
c2925f7c1e | ||
|
|
60814d8b58 | ||
|
|
2dec448f2c | ||
|
|
b71f4f6800 | ||
|
|
558083a916 | ||
|
|
d507ed9dff | ||
|
|
7ed0242662 | ||
|
|
b4baa6cd7b | ||
|
|
1ab2cdeed3 | ||
|
|
83c0281a33 | ||
|
|
437b0b0240 | ||
|
|
5c48697eda | ||
|
|
0feea5b7a6 | ||
|
|
9eb27fdd5e | ||
|
|
6e4a64232a | ||
|
|
4bbedeeea9 | ||
|
|
b5c004e870 | ||
|
|
a0ef1ab4f4 | ||
|
|
c9172a11a8 | ||
|
|
a0feee912e | ||
|
|
8e42b7b891 | ||
|
|
147d7e773f | ||
|
|
759ab1c5ee | ||
|
|
4c1581d845 | ||
|
|
e1c6e4347a | ||
|
|
256f117bbf | ||
|
|
3b0acf82c7 | ||
|
|
3a12f3d6c7 | ||
|
|
335ac5a595 | ||
|
|
d0e2e97007 | ||
|
|
85e1baa2dc | ||
|
|
0c66c39211 | ||
|
|
250afa38ca | ||
|
|
b7e58eeb3f | ||
|
|
6f024d78a6 | ||
|
|
1e48096f36 | ||
|
|
ccf6a1bedb | ||
|
|
3639edb4db | ||
|
|
d3bbe0b3b6 | ||
|
|
e8ab101993 | ||
|
|
ef98b10063 | ||
|
|
84943e7fe6 | ||
|
|
d0fa5ff385 | ||
|
|
3609559ced | ||
|
|
950c780122 | ||
|
|
32b510ef40 | ||
|
|
4cc33ed29b | ||
|
|
d72906a6ba | ||
|
|
d577b51a86 | ||
|
|
63d4865427 | ||
|
|
1355477e37 | ||
|
|
d50e1b4e02 | ||
|
|
606ae41698 | ||
|
|
b6ee5ae779 | ||
|
|
aeb1b2c30f | ||
|
|
35ace281cc | ||
|
|
6cd056eee5 | ||
|
|
6c664bfaa7 | ||
|
|
8890d445dc | ||
|
|
7a7db1ea08 | ||
|
|
e585a3e5c4 | ||
|
|
7336032009 | ||
|
|
d29bc63b24 | ||
|
|
2a9bd1d4cb | ||
|
|
6578c14292 | ||
|
|
ceee93883f | ||
|
|
dae8fd2370 | ||
|
|
48f8322390 | ||
|
|
7df833bd9f | ||
|
|
2d639e191a | ||
|
|
db758c6806 | ||
|
|
6822e4ac0c | ||
|
|
14b1b07ecd | ||
|
|
3c71a20bb2 | ||
|
|
8f73619ba1 | ||
|
|
0ee6e5a35f | ||
|
|
22692757e6 | ||
|
|
ed9584270d | ||
|
|
5a5c35a1c9 | ||
|
|
1f842e4fe4 | ||
|
|
9275c4a6d6 | ||
|
|
9c7e61cbf3 | ||
|
|
69a6066fd8 | ||
|
|
47d2d09e50 | ||
|
|
da648e0a78 | ||
|
|
9e1c526d51 | ||
|
|
d81998f91c | ||
|
|
a717d9b8f3 | ||
|
|
31d1243a14 | ||
|
|
2424222964 | ||
|
|
370b245d65 | ||
|
|
c4dfcc27e3 | ||
|
|
dfa870a777 | ||
|
|
572375fff4 | ||
|
|
ed1caee9f8 | ||
|
|
6f7757c81b | ||
|
|
4c92965313 | ||
|
|
bbce96eb67 | ||
|
|
e3cb7bd4c7 | ||
|
|
79599bf831 | ||
|
|
1ab67bc225 | ||
|
|
37df213771 | ||
|
|
d48ffdb14f | ||
|
|
766cdc9f59 | ||
|
|
21a40c9d14 | ||
|
|
9275e9d006 | ||
|
|
ef9fe025e0 | ||
|
|
05694a8cda | ||
|
|
e6304cb028 | ||
|
|
b2d00784a4 | ||
|
|
ae31ebdc33 | ||
|
|
a2d50b380f | ||
|
|
654e8fd13f | ||
|
|
bcd44e4b2d | ||
|
|
5200793744 | ||
|
|
abcb29391c | ||
|
|
6a682dc143 | ||
|
|
d93d30537f | ||
|
|
377e88ff36 | ||
|
|
1733290c02 | ||
|
|
e702ccc48a | ||
|
|
ba729c493b | ||
|
|
1c55950b7e | ||
|
|
18c8282bac | ||
|
|
1d20456853 | ||
|
|
7e32d0ae10 | ||
|
|
5d33e45eae | ||
|
|
1590930ef9 | ||
|
|
8186d34f4e |
1
.commitlintrc.json
Normal file
1
.commitlintrc.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "extends": ["@commitlint/config-conventional"] }
|
||||
72
.eslintrc.js
72
.eslintrc.js
@@ -1,11 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'standard',
|
||||
'standard-jsx',
|
||||
'prettier',
|
||||
'prettier/standard',
|
||||
'prettier/react',
|
||||
],
|
||||
extends: ['plugin:eslint-comments/recommended', 'plugin:n/recommended', 'standard', 'standard-jsx', 'prettier'],
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
$Dict: true,
|
||||
@@ -16,16 +12,62 @@ module.exports = {
|
||||
$PropertyType: true,
|
||||
$Shape: true,
|
||||
},
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true,
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ['cli.{,c,m}js', '*-cli.{,c,m}js', '**/*cli*/**/*.{,c,m}js'],
|
||||
rules: {
|
||||
'n/no-process-exit': 'off',
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.mjs'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.{integ,spec,test}.{,c,m}js'],
|
||||
rules: {
|
||||
'n/no-unpublished-require': 'off',
|
||||
'n/no-unpublished-import': 'off',
|
||||
'n/no-unsupported-features/node-builtins': [
|
||||
'error',
|
||||
{
|
||||
version: '>=16',
|
||||
},
|
||||
],
|
||||
'n/no-unsupported-features/es-syntax': [
|
||||
'error',
|
||||
{
|
||||
version: '>=16',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
parserOptions: {
|
||||
ecmaVersion: 13,
|
||||
sourceType: 'script',
|
||||
},
|
||||
|
||||
rules: {
|
||||
'no-var': 'error',
|
||||
'node/no-extraneous-import': 'error',
|
||||
'node/no-extraneous-require': 'error',
|
||||
'prefer-const': 'error',
|
||||
// disabled because XAPI objects are using camel case
|
||||
camelcase: ['off'],
|
||||
|
||||
'react/jsx-handler-names': 'off',
|
||||
|
||||
// disabled because not always relevant, we might reconsider in the future
|
||||
//
|
||||
// enabled by https://github.com/standard/eslint-config-standard/commit/319b177750899d4525eb1210686f6aca96190b2f
|
||||
//
|
||||
// example: https://github.com/vatesfr/xen-orchestra/blob/31ed3767c67044ca445658eb6b560718972402f2/packages/xen-api/src/index.js#L156-L157
|
||||
'lines-between-class-members': 'off',
|
||||
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
|
||||
strict: 'error',
|
||||
},
|
||||
}
|
||||
|
||||
16
.flowconfig
16
.flowconfig
@@ -1,16 +0,0 @@
|
||||
[ignore]
|
||||
<PROJECT_ROOT>/node_modules/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
esproposal.decorators=ignore
|
||||
esproposal.optional_chaining=enable
|
||||
include_warnings=true
|
||||
module.use_strict=true
|
||||
|
||||
[strict]
|
||||
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'status: triaging :triangular_flag_on_post:, type: bug :bug:'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
1. ⚠️ **If you don't follow this template, the issue will be closed**.
|
||||
2. ⚠️ **If your issue can't be easily reproduced, please report it [on the forum first](https://xcp-ng.org/forum/category/12/xen-orchestra)**.
|
||||
|
||||
Are you using XOA or XO from the sources?
|
||||
|
||||
If XOA:
|
||||
|
||||
- which release channel? (`stable` vs `latest`)
|
||||
- please consider creating a support ticket in [your dedicated support area](https://xen-orchestra.com/#!/member/support)
|
||||
|
||||
If XO from the sources:
|
||||
|
||||
- Provide **your commit number**. If it's older than a week, we won't investigate
|
||||
- Don't forget to [read this first](https://xen-orchestra.com/docs/community.html)
|
||||
- As well as follow [this guide](https://xen-orchestra.com/docs/community.html#report-a-bug)
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please provide the following information):**
|
||||
|
||||
- Node: [e.g. 16.12.1]
|
||||
- hypervisor: [e.g. XCP-ng 8.2.0]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
32
.github/workflows/ci.yml
vendored
Normal file
32
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Continous Integration
|
||||
on: push
|
||||
jobs:
|
||||
CI:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y curl qemu-utils python3-vmdkstream git libxml2-utils libfuse2 nbdkit
|
||||
- name: Cache Turbo
|
||||
# https://github.com/actions/cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules/.cache/turbo'
|
||||
key: ${{ runner.os }}-turbo-cache
|
||||
- name: Setup Node environment
|
||||
# https://github.com/actions/setup-node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'yarn'
|
||||
- name: Install project dependencies
|
||||
run: yarn
|
||||
- name: Build the project
|
||||
run: yarn build
|
||||
- name: Lint tests
|
||||
run: yarn test-lint
|
||||
- name: Integration tests
|
||||
run: sudo yarn test-integration
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -1,23 +1,22 @@
|
||||
/_book/
|
||||
/coverage/
|
||||
/node_modules/
|
||||
/lerna-debug.log
|
||||
/lerna-debug.log.*
|
||||
|
||||
/@vates/*/dist/
|
||||
/@vates/*/node_modules/
|
||||
/@xen-orchestra/*/dist/
|
||||
/@xen-orchestra/*/node_modules/
|
||||
/packages/*/dist/
|
||||
/packages/*/node_modules/
|
||||
|
||||
/packages/vhd-cli/src/commands/index.js
|
||||
|
||||
/packages/xen-api/examples/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/src/api/index.mjs
|
||||
/packages/xo-server/src/xapi/mixins/index.mjs
|
||||
/packages/xo-server/src/xo-mixins/index.mjs
|
||||
|
||||
/packages/xo-server-auth-ldap/ldap.cache.conf
|
||||
|
||||
@@ -31,3 +30,8 @@ pnpm-debug.log.*
|
||||
yarn-error.log
|
||||
yarn-error.log.*
|
||||
.env
|
||||
|
||||
# code coverage
|
||||
.nyc_output/
|
||||
coverage/
|
||||
.turbo/
|
||||
|
||||
11
.husky/commit-msg
Executable file
11
.husky/commit-msg
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Only check commit message if commit on master or first commit on another
|
||||
# branch to avoid bothering fix commits after reviews
|
||||
#
|
||||
# FIXME: does not properly run with git commit --amend
|
||||
if [ "$(git rev-parse --abbrev-ref HEAD)" = master ] || [ "$(git rev-list --count master..)" -eq 0 ]
|
||||
then
|
||||
npx --no -- commitlint --edit "$1"
|
||||
fi
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
@@ -1,6 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
arrowParens: 'avoid',
|
||||
jsxSingleQuote: true,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'es5',
|
||||
|
||||
// 2020-11-24: Requested by nraynaud and approved by the rest of the team
|
||||
//
|
||||
// https://team.vates.fr/vates/pl/a1i8af1b9id7pgzm3jcg4toacy
|
||||
printWidth: 120,
|
||||
}
|
||||
|
||||
24
.travis.yml
24
.travis.yml
@@ -1,24 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
#- stable # disable for now due to an issue of indirect dep upath with Node 9
|
||||
- 8
|
||||
|
||||
# Use containers.
|
||||
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- qemu-utils
|
||||
- blktap-utils
|
||||
- vmdk-stream-converter
|
||||
|
||||
before_install:
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
|
||||
script:
|
||||
- yarn run travis-tests
|
||||
35
@vates/async-each/.USAGE.md
Normal file
35
@vates/async-each/.USAGE.md
Normal file
@@ -0,0 +1,35 @@
|
||||
### `asyncEach(iterable, iteratee, [opts])`
|
||||
|
||||
Executes `iteratee` in order for each value yielded by `iterable`.
|
||||
|
||||
Returns a promise wich rejects as soon as a call to `iteratee` throws or a promise returned by it rejects, and which resolves when all promises returned by `iteratee` have resolved.
|
||||
|
||||
`iterable` must be an iterable or async iterable.
|
||||
|
||||
`iteratee` is called with the same `this` value as `asyncEach`, and with the following arguments:
|
||||
|
||||
- `value`: the value yielded by `iterable`
|
||||
- `index`: the 0-based index for this value
|
||||
- `iterable`: the iterable itself
|
||||
|
||||
`opts` is an object that can contains the following options:
|
||||
|
||||
- `concurrency`: a number which indicates the maximum number of parallel call to `iteratee`, defaults to `10`. The value `0` means no concurrency limit.
|
||||
- `signal`: an abort signal to stop the iteration
|
||||
- `stopOnError`: wether to stop iteration of first error, or wait for all calls to finish and throw an `AggregateError`, defaults to `true`
|
||||
|
||||
```js
|
||||
import { asyncEach } from '@vates/async-each'
|
||||
|
||||
const contents = []
|
||||
await asyncEach(
|
||||
['foo.txt', 'bar.txt', 'baz.txt'],
|
||||
async function (filename, i) {
|
||||
contents[i] = await readFile(filename)
|
||||
},
|
||||
{
|
||||
// reads two files at a time
|
||||
concurrency: 2,
|
||||
}
|
||||
)
|
||||
```
|
||||
1
@vates/async-each/.npmignore
Symbolic link
1
@vates/async-each/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
68
@vates/async-each/README.md
Normal file
68
@vates/async-each/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/async-each
|
||||
|
||||
[](https://npmjs.org/package/@vates/async-each)  [](https://bundlephobia.com/result?p=@vates/async-each) [](https://npmjs.org/package/@vates/async-each)
|
||||
|
||||
> Run async fn for each item in (async) iterable
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/async-each):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/async-each
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### `asyncEach(iterable, iteratee, [opts])`
|
||||
|
||||
Executes `iteratee` in order for each value yielded by `iterable`.
|
||||
|
||||
Returns a promise wich rejects as soon as a call to `iteratee` throws or a promise returned by it rejects, and which resolves when all promises returned by `iteratee` have resolved.
|
||||
|
||||
`iterable` must be an iterable or async iterable.
|
||||
|
||||
`iteratee` is called with the same `this` value as `asyncEach`, and with the following arguments:
|
||||
|
||||
- `value`: the value yielded by `iterable`
|
||||
- `index`: the 0-based index for this value
|
||||
- `iterable`: the iterable itself
|
||||
|
||||
`opts` is an object that can contains the following options:
|
||||
|
||||
- `concurrency`: a number which indicates the maximum number of parallel call to `iteratee`, defaults to `10`. The value `0` means no concurrency limit.
|
||||
- `signal`: an abort signal to stop the iteration
|
||||
- `stopOnError`: wether to stop iteration of first error, or wait for all calls to finish and throw an `AggregateError`, defaults to `true`
|
||||
|
||||
```js
|
||||
import { asyncEach } from '@vates/async-each'
|
||||
|
||||
const contents = []
|
||||
await asyncEach(
|
||||
['foo.txt', 'bar.txt', 'baz.txt'],
|
||||
async function (filename, i) {
|
||||
contents[i] = await readFile(filename)
|
||||
},
|
||||
{
|
||||
// reads two files at a time
|
||||
concurrency: 2,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
108
@vates/async-each/index.js
Normal file
108
@vates/async-each/index.js
Normal file
@@ -0,0 +1,108 @@
|
||||
'use strict'
|
||||
|
||||
const noop = Function.prototype
|
||||
|
||||
class AggregateError extends Error {
|
||||
constructor(errors, message) {
|
||||
super(message)
|
||||
this.errors = errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template Item
|
||||
* @param {Iterable<Item>} iterable
|
||||
* @param {(item: Item, index: number, iterable: Iterable<Item>) => Promise<void>} iteratee
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
exports.asyncEach = function asyncEach(iterable, iteratee, { concurrency = 10, signal, stopOnError = true } = {}) {
|
||||
if (concurrency === 0) {
|
||||
concurrency = Infinity
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const it = (iterable[Symbol.iterator] || iterable[Symbol.asyncIterator]).call(iterable)
|
||||
const errors = []
|
||||
let running = 0
|
||||
let index = 0
|
||||
|
||||
let onAbort
|
||||
if (signal !== undefined) {
|
||||
onAbort = () => {
|
||||
onRejectedWrapper(new Error('asyncEach aborted'))
|
||||
}
|
||||
signal.addEventListener('abort', onAbort)
|
||||
}
|
||||
|
||||
const clean = () => {
|
||||
onFulfilled = onRejected = noop
|
||||
if (onAbort !== undefined) {
|
||||
signal.removeEventListener('abort', onAbort)
|
||||
}
|
||||
}
|
||||
|
||||
resolve = (resolve =>
|
||||
function resolveAndClean(value) {
|
||||
resolve(value)
|
||||
clean()
|
||||
})(resolve)
|
||||
reject = (reject =>
|
||||
function rejectAndClean(reason) {
|
||||
reject(reason)
|
||||
clean()
|
||||
})(reject)
|
||||
|
||||
let onFulfilled = value => {
|
||||
--running
|
||||
next()
|
||||
}
|
||||
const onFulfilledWrapper = value => onFulfilled(value)
|
||||
|
||||
let onRejected = stopOnError
|
||||
? reject
|
||||
: error => {
|
||||
--running
|
||||
errors.push(error)
|
||||
next()
|
||||
}
|
||||
const onRejectedWrapper = reason => onRejected(reason)
|
||||
|
||||
let nextIsRunning = false
|
||||
let next = async () => {
|
||||
if (nextIsRunning) {
|
||||
return
|
||||
}
|
||||
nextIsRunning = true
|
||||
if (running < concurrency) {
|
||||
const cursor = await it.next()
|
||||
if (cursor.done) {
|
||||
next = () => {
|
||||
if (running === 0) {
|
||||
if (errors.length === 0) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new AggregateError(errors))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
++running
|
||||
try {
|
||||
const result = iteratee.call(this, cursor.value, index++, iterable)
|
||||
let then
|
||||
if (result != null && typeof result === 'object' && typeof (then = result.then) === 'function') {
|
||||
then.call(result, onFulfilledWrapper, onRejectedWrapper)
|
||||
} else {
|
||||
onFulfilled(result)
|
||||
}
|
||||
} catch (error) {
|
||||
onRejected(error)
|
||||
}
|
||||
}
|
||||
nextIsRunning = false
|
||||
return next()
|
||||
}
|
||||
nextIsRunning = false
|
||||
}
|
||||
next()
|
||||
})
|
||||
}
|
||||
119
@vates/async-each/index.test.js
Normal file
119
@vates/async-each/index.test.js
Normal file
@@ -0,0 +1,119 @@
|
||||
'use strict'
|
||||
|
||||
const { describe, it, beforeEach } = require('test')
|
||||
const assert = require('assert').strict
|
||||
const { spy } = require('sinon')
|
||||
|
||||
const { asyncEach } = require('./')
|
||||
|
||||
const randomDelay = (max = 10) =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(resolve, Math.floor(Math.random() * max + 1))
|
||||
})
|
||||
|
||||
const rejectionOf = p =>
|
||||
new Promise((resolve, reject) => {
|
||||
p.then(reject, resolve)
|
||||
})
|
||||
|
||||
describe('asyncEach', () => {
|
||||
const thisArg = 'qux'
|
||||
const values = ['foo', 'bar', 'baz']
|
||||
|
||||
Object.entries({
|
||||
'sync iterable': () => values,
|
||||
'async iterable': async function* () {
|
||||
for (const value of values) {
|
||||
await randomDelay()
|
||||
yield value
|
||||
}
|
||||
},
|
||||
}).forEach(([what, getIterable]) =>
|
||||
describe('with ' + what, () => {
|
||||
let iterable
|
||||
beforeEach(() => {
|
||||
iterable = getIterable()
|
||||
})
|
||||
|
||||
it('works', async () => {
|
||||
const iteratee = spy(async () => {})
|
||||
|
||||
await asyncEach.call(thisArg, iterable, iteratee, { concurrency: 1 })
|
||||
|
||||
assert.deepStrictEqual(
|
||||
iteratee.thisValues,
|
||||
Array.from(values, () => thisArg)
|
||||
)
|
||||
assert.deepStrictEqual(
|
||||
iteratee.args,
|
||||
Array.from(values, (value, index) => [value, index, iterable])
|
||||
)
|
||||
})
|
||||
;[1, 2, 4].forEach(concurrency => {
|
||||
it('respects a concurrency of ' + concurrency, async () => {
|
||||
let running = 0
|
||||
|
||||
await asyncEach(
|
||||
values,
|
||||
async () => {
|
||||
++running
|
||||
assert.deepStrictEqual(running <= concurrency, true)
|
||||
await randomDelay()
|
||||
--running
|
||||
},
|
||||
{ concurrency }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('stops on first error when stopOnError is true', async () => {
|
||||
const tracker = new assert.CallTracker()
|
||||
|
||||
const error = new Error()
|
||||
const iteratee = tracker.calls((_, i) => {
|
||||
if (i === 1) {
|
||||
throw error
|
||||
}
|
||||
}, 2)
|
||||
assert.deepStrictEqual(
|
||||
await rejectionOf(asyncEach(iterable, iteratee, { concurrency: 1, stopOnError: true })),
|
||||
error
|
||||
)
|
||||
|
||||
tracker.verify()
|
||||
})
|
||||
|
||||
it('rejects AggregateError when stopOnError is false', async () => {
|
||||
const errors = []
|
||||
const iteratee = spy(() => {
|
||||
const error = new Error()
|
||||
errors.push(error)
|
||||
throw error
|
||||
})
|
||||
|
||||
const error = await rejectionOf(asyncEach(iterable, iteratee, { stopOnError: false }))
|
||||
assert.deepStrictEqual(error.errors, errors)
|
||||
assert.deepStrictEqual(
|
||||
iteratee.args,
|
||||
Array.from(values, (value, index) => [value, index, iterable])
|
||||
)
|
||||
})
|
||||
|
||||
it('can be interrupted with an AbortSignal', async () => {
|
||||
const tracker = new assert.CallTracker()
|
||||
|
||||
const ac = new AbortController()
|
||||
const iteratee = tracker.calls((_, i) => {
|
||||
if (i === 1) {
|
||||
ac.abort()
|
||||
}
|
||||
}, 2)
|
||||
await assert.rejects(asyncEach(iterable, iteratee, { concurrency: 1, signal: ac.signal }), {
|
||||
message: 'asyncEach aborted',
|
||||
})
|
||||
|
||||
tracker.verify()
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
40
@vates/async-each/package.json
Normal file
40
@vates/async-each/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/async-each",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/async-each",
|
||||
"description": "Run async fn for each item in (async) iterable",
|
||||
"keywords": [
|
||||
"array",
|
||||
"async",
|
||||
"collection",
|
||||
"each",
|
||||
"for",
|
||||
"foreach",
|
||||
"iterable",
|
||||
"iterator"
|
||||
],
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/async-each",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sinon": "^16.0.0",
|
||||
"tap": "^16.3.0",
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
30
@vates/cached-dns.lookup/.USAGE.md
Normal file
30
@vates/cached-dns.lookup/.USAGE.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Node does not cache queries to `dns.lookup`, which can lead application doing a lot of connections to have perf issues and to saturate Node threads pool.
|
||||
|
||||
This library attempts to mitigate these problems by providing a version of this function with a version short cache, applied on both errors and results.
|
||||
|
||||
> Limitation: `verbatim: false` option is not supported.
|
||||
|
||||
It has exactly the same API as the native method and can be used directly:
|
||||
|
||||
```js
|
||||
import { createCachedLookup } from '@vates/cached-dns.lookup'
|
||||
|
||||
const lookup = createCachedLookup()
|
||||
|
||||
lookup('example.net', { all: true, family: 0 }, (error, result) => {
|
||||
if (error != null) {
|
||||
return console.warn(error)
|
||||
}
|
||||
console.log(result)
|
||||
})
|
||||
```
|
||||
|
||||
Or it can be used to replace the native implementation and speed up the whole app:
|
||||
|
||||
```js
|
||||
// assign our cached implementation to dns.lookup
|
||||
const restore = createCachedLookup().patchGlobal()
|
||||
|
||||
// to restore the previous implementation
|
||||
restore()
|
||||
```
|
||||
1
@vates/cached-dns.lookup/.npmignore
Symbolic link
1
@vates/cached-dns.lookup/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
63
@vates/cached-dns.lookup/README.md
Normal file
63
@vates/cached-dns.lookup/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/cached-dns.lookup
|
||||
|
||||
[](https://npmjs.org/package/@vates/cached-dns.lookup)  [](https://bundlephobia.com/result?p=@vates/cached-dns.lookup) [](https://npmjs.org/package/@vates/cached-dns.lookup)
|
||||
|
||||
> Cached implementation of dns.lookup
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/cached-dns.lookup):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/cached-dns.lookup
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Node does not cache queries to `dns.lookup`, which can lead application doing a lot of connections to have perf issues and to saturate Node threads pool.
|
||||
|
||||
This library attempts to mitigate these problems by providing a version of this function with a version short cache, applied on both errors and results.
|
||||
|
||||
> Limitation: `verbatim: false` option is not supported.
|
||||
|
||||
It has exactly the same API as the native method and can be used directly:
|
||||
|
||||
```js
|
||||
import { createCachedLookup } from '@vates/cached-dns.lookup'
|
||||
|
||||
const lookup = createCachedLookup()
|
||||
|
||||
lookup('example.net', { all: true, family: 0 }, (error, result) => {
|
||||
if (error != null) {
|
||||
return console.warn(error)
|
||||
}
|
||||
console.log(result)
|
||||
})
|
||||
```
|
||||
|
||||
Or it can be used to replace the native implementation and speed up the whole app:
|
||||
|
||||
```js
|
||||
// assign our cached implementation to dns.lookup
|
||||
const restore = createCachedLookup().patchGlobal()
|
||||
|
||||
// to restore the previous implementation
|
||||
restore()
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
72
@vates/cached-dns.lookup/index.js
Normal file
72
@vates/cached-dns.lookup/index.js
Normal file
@@ -0,0 +1,72 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
const dns = require('dns')
|
||||
const LRU = require('lru-cache')
|
||||
|
||||
function reportResults(all, results, callback) {
|
||||
if (all) {
|
||||
callback(null, results)
|
||||
} else {
|
||||
const first = results[0]
|
||||
callback(null, first.address, first.family)
|
||||
}
|
||||
}
|
||||
|
||||
exports.createCachedLookup = function createCachedLookup({ lookup = dns.lookup } = {}) {
|
||||
const cache = new LRU({
|
||||
max: 500,
|
||||
|
||||
// 1 minute: long enough to be effective, short enough so there is no need to bother with DNS TTLs
|
||||
ttl: 60e3,
|
||||
})
|
||||
|
||||
function cachedLookup(hostname, options, callback) {
|
||||
let all = false
|
||||
let family = 0
|
||||
if (typeof options === 'function') {
|
||||
callback = options
|
||||
} else if (typeof options === 'number') {
|
||||
family = options
|
||||
} else if (options != null) {
|
||||
assert.notStrictEqual(options.verbatim, false, 'not supported by this implementation')
|
||||
;({ all = all, family = family } = options)
|
||||
}
|
||||
|
||||
// cache by family option because there will be an error if there is no
|
||||
// entries for the requestion family so we cannot easily cache all families
|
||||
// and filter on reporting back
|
||||
const key = hostname + '/' + family
|
||||
|
||||
const result = cache.get(key)
|
||||
if (result !== undefined) {
|
||||
setImmediate(reportResults, all, result, callback)
|
||||
} else {
|
||||
lookup(hostname, { all: true, family, verbatim: true }, function onLookup(error, results) {
|
||||
// errors are not cached because this will delay recovery after DNS/network issues
|
||||
//
|
||||
// there are no reliable way to detect if the error is real or simply
|
||||
// that there are no results for the requested hostname
|
||||
//
|
||||
// there should be much fewer errors than success, therefore it should
|
||||
// not be a big deal to not cache them
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
cache.set(key, results)
|
||||
reportResults(all, results, callback)
|
||||
})
|
||||
}
|
||||
}
|
||||
cachedLookup.patchGlobal = function patchGlobal() {
|
||||
const previous = dns.lookup
|
||||
dns.lookup = cachedLookup
|
||||
return function restoreGlobal() {
|
||||
assert.strictEqual(dns.lookup, cachedLookup)
|
||||
dns.lookup = previous
|
||||
}
|
||||
}
|
||||
|
||||
return cachedLookup
|
||||
}
|
||||
32
@vates/cached-dns.lookup/package.json
Normal file
32
@vates/cached-dns.lookup/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.0.4"
|
||||
},
|
||||
"private": false,
|
||||
"name": "@vates/cached-dns.lookup",
|
||||
"description": "Cached implementation of dns.lookup",
|
||||
"keywords": [
|
||||
"cache",
|
||||
"dns",
|
||||
"lookup"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/cached-dns.lookup",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/cached-dns.lookup",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
}
|
||||
}
|
||||
13
@vates/coalesce-calls/.USAGE.md
Normal file
13
@vates/coalesce-calls/.USAGE.md
Normal file
@@ -0,0 +1,13 @@
|
||||
```js
|
||||
import { coalesceCalls } from '@vates/coalesce-calls'
|
||||
|
||||
const connect = coalesceCalls(async function () {
|
||||
// async operation
|
||||
})
|
||||
|
||||
connect()
|
||||
|
||||
// the previous promise result will be returned if the operation is not
|
||||
// complete yet
|
||||
connect()
|
||||
```
|
||||
1
@vates/coalesce-calls/.npmignore
Symbolic link
1
@vates/coalesce-calls/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
46
@vates/coalesce-calls/README.md
Normal file
46
@vates/coalesce-calls/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/coalesce-calls
|
||||
|
||||
[](https://npmjs.org/package/@vates/coalesce-calls)  [](https://bundlephobia.com/result?p=@vates/coalesce-calls) [](https://npmjs.org/package/@vates/coalesce-calls)
|
||||
|
||||
> Wraps an async function so that concurrent calls will be coalesced
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/coalesce-calls):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/coalesce-calls
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { coalesceCalls } from '@vates/coalesce-calls'
|
||||
|
||||
const connect = coalesceCalls(async function () {
|
||||
// async operation
|
||||
})
|
||||
|
||||
connect()
|
||||
|
||||
// the previous promise result will be returned if the operation is not
|
||||
// complete yet
|
||||
connect()
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
16
@vates/coalesce-calls/index.js
Normal file
16
@vates/coalesce-calls/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
exports.coalesceCalls = function (fn) {
|
||||
let promise
|
||||
const clean = () => {
|
||||
promise = undefined
|
||||
}
|
||||
return function () {
|
||||
if (promise !== undefined) {
|
||||
return promise
|
||||
}
|
||||
promise = fn.apply(this, arguments)
|
||||
promise.then(clean, clean)
|
||||
return promise
|
||||
}
|
||||
}
|
||||
36
@vates/coalesce-calls/index.test.js
Normal file
36
@vates/coalesce-calls/index.test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
|
||||
const { describe, it } = require('test')
|
||||
const assert = require('assert')
|
||||
|
||||
const { coalesceCalls } = require('./')
|
||||
|
||||
const pDefer = () => {
|
||||
const r = {}
|
||||
r.promise = new Promise((resolve, reject) => {
|
||||
r.reject = reject
|
||||
r.resolve = resolve
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
describe('coalesceCalls', () => {
|
||||
it('decorates an async function', async () => {
|
||||
const fn = coalesceCalls(promise => promise)
|
||||
|
||||
const defer1 = pDefer()
|
||||
const promise1 = fn(defer1.promise)
|
||||
const defer2 = pDefer()
|
||||
const promise2 = fn(defer2.promise)
|
||||
|
||||
defer1.resolve('foo')
|
||||
assert.strictEqual(await promise1, 'foo')
|
||||
assert.strictEqual(await promise2, 'foo')
|
||||
|
||||
const defer3 = pDefer()
|
||||
const promise3 = fn(defer3.promise)
|
||||
|
||||
defer3.resolve('bar')
|
||||
assert.strictEqual(await promise3, 'bar')
|
||||
})
|
||||
})
|
||||
39
@vates/coalesce-calls/package.json
Normal file
39
@vates/coalesce-calls/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/coalesce-calls",
|
||||
"description": "Wraps an async function so that concurrent calls will be coalesced",
|
||||
"keywords": [
|
||||
"async",
|
||||
"calls",
|
||||
"coalesce",
|
||||
"decorate",
|
||||
"decorator",
|
||||
"merge",
|
||||
"promise",
|
||||
"wrap",
|
||||
"wrapper"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/coalesce-calls",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/coalesce-calls",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "0.1.0",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
65
@vates/compose/.USAGE.md
Normal file
65
@vates/compose/.USAGE.md
Normal file
@@ -0,0 +1,65 @@
|
||||
```js
|
||||
import { compose } from '@vates/compose'
|
||||
|
||||
const add2 = x => x + 2
|
||||
const mul3 = x => x * 3
|
||||
|
||||
// const f = x => mul3(add2(x))
|
||||
const f = compose(add2, mul3)
|
||||
|
||||
console.log(f(5))
|
||||
// → 21
|
||||
```
|
||||
|
||||
> The call context (`this`) of the composed function is forwarded to all functions.
|
||||
|
||||
The first function is called with all arguments of the composed function:
|
||||
|
||||
```js
|
||||
const add = (x, y) => x + y
|
||||
const mul3 = x => x * 3
|
||||
|
||||
// const f = (x, y) => mul3(add(x, y))
|
||||
const f = compose(add, mul3)
|
||||
|
||||
console.log(f(4, 5))
|
||||
// → 27
|
||||
```
|
||||
|
||||
Functions may also be passed in an array:
|
||||
|
||||
```js
|
||||
const f = compose([add2, mul3])
|
||||
```
|
||||
|
||||
Options can be passed as first parameter:
|
||||
|
||||
```js
|
||||
const f = compose(
|
||||
{
|
||||
// compose async functions
|
||||
async: true,
|
||||
|
||||
// compose from right to left
|
||||
right: true,
|
||||
},
|
||||
[add2, mul3]
|
||||
)
|
||||
```
|
||||
|
||||
Functions can receive extra parameters:
|
||||
|
||||
```js
|
||||
const isIn = (value, min, max) => min <= value && value <= max
|
||||
|
||||
// Only compatible when `fns` is passed as an array!
|
||||
const f = compose([
|
||||
[add, 2],
|
||||
[isIn, 3, 10],
|
||||
])
|
||||
|
||||
console.log(f(1))
|
||||
// → true
|
||||
```
|
||||
|
||||
> Note: if the first function is defined with extra parameters, it will only receive the first value passed to the composed function, instead of all the parameters.
|
||||
1
@vates/compose/.npmignore
Symbolic link
1
@vates/compose/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
98
@vates/compose/README.md
Normal file
98
@vates/compose/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/compose
|
||||
|
||||
[](https://npmjs.org/package/@vates/compose)  [](https://bundlephobia.com/result?p=@vates/compose) [](https://npmjs.org/package/@vates/compose)
|
||||
|
||||
> Compose functions from left to right
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/compose):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/compose
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { compose } from '@vates/compose'
|
||||
|
||||
const add2 = x => x + 2
|
||||
const mul3 = x => x * 3
|
||||
|
||||
// const f = x => mul3(add2(x))
|
||||
const f = compose(add2, mul3)
|
||||
|
||||
console.log(f(5))
|
||||
// → 21
|
||||
```
|
||||
|
||||
> The call context (`this`) of the composed function is forwarded to all functions.
|
||||
|
||||
The first function is called with all arguments of the composed function:
|
||||
|
||||
```js
|
||||
const add = (x, y) => x + y
|
||||
const mul3 = x => x * 3
|
||||
|
||||
// const f = (x, y) => mul3(add(x, y))
|
||||
const f = compose(add, mul3)
|
||||
|
||||
console.log(f(4, 5))
|
||||
// → 27
|
||||
```
|
||||
|
||||
Functions may also be passed in an array:
|
||||
|
||||
```js
|
||||
const f = compose([add2, mul3])
|
||||
```
|
||||
|
||||
Options can be passed as first parameter:
|
||||
|
||||
```js
|
||||
const f = compose(
|
||||
{
|
||||
// compose async functions
|
||||
async: true,
|
||||
|
||||
// compose from right to left
|
||||
right: true,
|
||||
},
|
||||
[add2, mul3]
|
||||
)
|
||||
```
|
||||
|
||||
Functions can receive extra parameters:
|
||||
|
||||
```js
|
||||
const isIn = (value, min, max) => min <= value && value <= max
|
||||
|
||||
// Only compatible when `fns` is passed as an array!
|
||||
const f = compose([
|
||||
[add, 2],
|
||||
[isIn, 3, 10],
|
||||
])
|
||||
|
||||
console.log(f(1))
|
||||
// → true
|
||||
```
|
||||
|
||||
> Note: if the first function is defined with extra parameters, it will only receive the first value passed to the composed function, instead of all the parameters.
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
66
@vates/compose/index.js
Normal file
66
@vates/compose/index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
'use strict'
|
||||
|
||||
const defaultOpts = { async: false, right: false }
|
||||
|
||||
exports.compose = function compose(opts, fns) {
|
||||
if (Array.isArray(opts)) {
|
||||
fns = opts.slice() // don't mutate passed array
|
||||
opts = defaultOpts
|
||||
} else if (typeof opts === 'object') {
|
||||
opts = Object.assign({}, defaultOpts, opts)
|
||||
if (Array.isArray(fns)) {
|
||||
fns = fns.slice() // don't mutate passed array
|
||||
} else {
|
||||
fns = Array.prototype.slice.call(arguments, 1)
|
||||
}
|
||||
} else {
|
||||
fns = Array.from(arguments)
|
||||
opts = defaultOpts
|
||||
}
|
||||
|
||||
const n = fns.length
|
||||
if (n === 0) {
|
||||
throw new TypeError('at least one function must be passed')
|
||||
}
|
||||
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const entry = fns[i]
|
||||
if (Array.isArray(entry)) {
|
||||
const fn = entry[0]
|
||||
const args = entry.slice()
|
||||
args[0] = undefined
|
||||
fns[i] = function composeWithArgs(value) {
|
||||
args[0] = value
|
||||
try {
|
||||
return fn.apply(this, args)
|
||||
} finally {
|
||||
args[0] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (n === 1) {
|
||||
return fns[0]
|
||||
}
|
||||
|
||||
if (opts.right) {
|
||||
fns.reverse()
|
||||
}
|
||||
|
||||
return opts.async
|
||||
? async function () {
|
||||
let value = await fns[0].apply(this, arguments)
|
||||
for (let i = 1; i < n; ++i) {
|
||||
value = await fns[i].call(this, value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
: function () {
|
||||
let value = fns[0].apply(this, arguments)
|
||||
for (let i = 1; i < n; ++i) {
|
||||
value = fns[i].call(this, value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
66
@vates/compose/index.test.js
Normal file
66
@vates/compose/index.test.js
Normal file
@@ -0,0 +1,66 @@
|
||||
'use strict'
|
||||
|
||||
const { describe, it } = require('test')
|
||||
const assert = require('node:assert').strict
|
||||
|
||||
const { compose } = require('./')
|
||||
|
||||
const add2 = x => x + 2
|
||||
const mul3 = x => x * 3
|
||||
|
||||
describe('compose()', () => {
|
||||
it('throws when no functions is passed', () => {
|
||||
assert.throws(() => compose(), TypeError)
|
||||
assert.throws(() => compose([]), TypeError)
|
||||
})
|
||||
|
||||
it('applies from left to right', () => {
|
||||
assert.strictEqual(compose(add2, mul3)(5), 21)
|
||||
})
|
||||
|
||||
it('accepts functions in an array', () => {
|
||||
assert.strictEqual(compose([add2, mul3])(5), 21)
|
||||
})
|
||||
|
||||
it('can apply from right to left', () => {
|
||||
assert.strictEqual(compose({ right: true }, add2, mul3)(5), 17)
|
||||
})
|
||||
|
||||
it('accepts options with functions in an array', () => {
|
||||
assert.strictEqual(compose({ right: true }, [add2, mul3])(5), 17)
|
||||
})
|
||||
|
||||
it('can compose async functions', async () => {
|
||||
assert.strictEqual(
|
||||
await compose(
|
||||
{ async: true },
|
||||
async x => x + 2,
|
||||
async x => x * 3
|
||||
)(5),
|
||||
21
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards all args to first function', () => {
|
||||
const expectedArgs = [Math.random(), Math.random()]
|
||||
compose(
|
||||
(...args) => {
|
||||
assert.deepEqual(args, expectedArgs)
|
||||
},
|
||||
// add a second function to avoid the one function special case
|
||||
Function.prototype
|
||||
)(...expectedArgs)
|
||||
})
|
||||
|
||||
it('forwards context to all functions', () => {
|
||||
const expectedThis = {}
|
||||
compose(
|
||||
function () {
|
||||
assert.strictEqual(this, expectedThis)
|
||||
},
|
||||
function () {
|
||||
assert.strictEqual(this, expectedThis)
|
||||
}
|
||||
).call(expectedThis)
|
||||
})
|
||||
})
|
||||
28
@vates/compose/package.json
Normal file
28
@vates/compose/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/compose",
|
||||
"description": "Compose functions from left to right",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/compose",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/compose",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "2.1.0",
|
||||
"engines": {
|
||||
"node": ">=7.6"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
86
@vates/decorate-with/.USAGE.md
Normal file
86
@vates/decorate-with/.USAGE.md
Normal file
@@ -0,0 +1,86 @@
|
||||
### `decorateWith(fn, ...args)`
|
||||
|
||||
Creates a new ([legacy](https://babeljs.io/docs/en/babel-plugin-syntax-decorators#legacy)) method decorator from a function decorator, for instance, allows using Lodash's functions as decorators:
|
||||
|
||||
```js
|
||||
import { decorateWith } from '@vates/decorate-with'
|
||||
|
||||
class Foo {
|
||||
@decorateWith(lodash.debounce, 150)
|
||||
bar() {
|
||||
// body
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `decorateClass(class, map)`
|
||||
|
||||
Decorates a number of accessors and methods directly, without using the decorator syntax:
|
||||
|
||||
```js
|
||||
import { decorateClass } from '@vates/decorate-with'
|
||||
|
||||
class Foo {
|
||||
get bar() {
|
||||
// body
|
||||
}
|
||||
|
||||
set bar(value) {
|
||||
// body
|
||||
}
|
||||
|
||||
baz() {
|
||||
// body
|
||||
}
|
||||
}
|
||||
|
||||
decorateClass(Foo, {
|
||||
// getter and/or setter
|
||||
bar: {
|
||||
// without arguments
|
||||
get: lodash.memoize,
|
||||
|
||||
// with arguments
|
||||
set: [lodash.debounce, 150],
|
||||
},
|
||||
|
||||
// method (with or without arguments)
|
||||
baz: lodash.curry,
|
||||
})
|
||||
```
|
||||
|
||||
The decorated class is returned, so you can export it directly.
|
||||
|
||||
To apply multiple transforms to an accessor/method, you can either call `decorateClass` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
||||
|
||||
```js
|
||||
decorateClass(Foo, {
|
||||
baz: compose([
|
||||
[lodash.debounce, 150]
|
||||
lodash.curry,
|
||||
])
|
||||
})
|
||||
```
|
||||
|
||||
### `perInstance(fn, ...args)`
|
||||
|
||||
Helper to decorate the method by instance instead of for the whole class.
|
||||
|
||||
This is often necessary for caching or deduplicating calls.
|
||||
|
||||
```js
|
||||
import { perInstance } from '@vates/decorateWith'
|
||||
|
||||
class Foo {
|
||||
@decorateWith(perInstance, lodash.memoize)
|
||||
bar() {
|
||||
// body
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Because it's a normal function, it can also be used with `decorateClass`, with `compose` or even by itself.
|
||||
|
||||
### `decorateMethodsWith(class, map)`
|
||||
|
||||
> Deprecated alias for [`decorateClass(class, map)`](#decorateclassclass-map).
|
||||
1
@vates/decorate-with/.npmignore
Symbolic link
1
@vates/decorate-with/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
119
@vates/decorate-with/README.md
Normal file
119
@vates/decorate-with/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/decorate-with
|
||||
|
||||
[](https://npmjs.org/package/@vates/decorate-with)  [](https://bundlephobia.com/result?p=@vates/decorate-with) [](https://npmjs.org/package/@vates/decorate-with)
|
||||
|
||||
> Creates a decorator from a function wrapper
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/decorate-with):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/decorate-with
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### `decorateWith(fn, ...args)`
|
||||
|
||||
Creates a new ([legacy](https://babeljs.io/docs/en/babel-plugin-syntax-decorators#legacy)) method decorator from a function decorator, for instance, allows using Lodash's functions as decorators:
|
||||
|
||||
```js
|
||||
import { decorateWith } from '@vates/decorate-with'
|
||||
|
||||
class Foo {
|
||||
@decorateWith(lodash.debounce, 150)
|
||||
bar() {
|
||||
// body
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `decorateClass(class, map)`
|
||||
|
||||
Decorates a number of accessors and methods directly, without using the decorator syntax:
|
||||
|
||||
```js
|
||||
import { decorateClass } from '@vates/decorate-with'
|
||||
|
||||
class Foo {
|
||||
get bar() {
|
||||
// body
|
||||
}
|
||||
|
||||
set bar(value) {
|
||||
// body
|
||||
}
|
||||
|
||||
baz() {
|
||||
// body
|
||||
}
|
||||
}
|
||||
|
||||
decorateClass(Foo, {
|
||||
// getter and/or setter
|
||||
bar: {
|
||||
// without arguments
|
||||
get: lodash.memoize,
|
||||
|
||||
// with arguments
|
||||
set: [lodash.debounce, 150],
|
||||
},
|
||||
|
||||
// method (with or without arguments)
|
||||
baz: lodash.curry,
|
||||
})
|
||||
```
|
||||
|
||||
The decorated class is returned, so you can export it directly.
|
||||
|
||||
To apply multiple transforms to an accessor/method, you can either call `decorateClass` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
||||
|
||||
```js
|
||||
decorateClass(Foo, {
|
||||
baz: compose([
|
||||
[lodash.debounce, 150]
|
||||
lodash.curry,
|
||||
])
|
||||
})
|
||||
```
|
||||
|
||||
### `perInstance(fn, ...args)`
|
||||
|
||||
Helper to decorate the method by instance instead of for the whole class.
|
||||
|
||||
This is often necessary for caching or deduplicating calls.
|
||||
|
||||
```js
|
||||
import { perInstance } from '@vates/decorateWith'
|
||||
|
||||
class Foo {
|
||||
@decorateWith(perInstance, lodash.memoize)
|
||||
bar() {
|
||||
// body
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Because it's a normal function, it can also be used with `decorateClass`, with `compose` or even by itself.
|
||||
|
||||
### `decorateMethodsWith(class, map)`
|
||||
|
||||
> Deprecated alias for [`decorateClass(class, map)`](#decorateclassclass-map).
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
48
@vates/decorate-with/index.js
Normal file
48
@vates/decorate-with/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict'
|
||||
|
||||
exports.decorateWith = function decorateWith(fn, ...args) {
|
||||
return (target, name, descriptor) => ({
|
||||
...descriptor,
|
||||
value: fn(descriptor.value, ...args),
|
||||
})
|
||||
}
|
||||
|
||||
const { getOwnPropertyDescriptor, defineProperty } = Object
|
||||
|
||||
function applyDecorator(decorator, value) {
|
||||
return typeof decorator === 'function' ? decorator(value) : decorator[0](value, ...decorator.slice(1))
|
||||
}
|
||||
|
||||
exports.decorateClass = exports.decorateMethodsWith = function decorateClass(klass, map) {
|
||||
const { prototype } = klass
|
||||
for (const name of Object.keys(map)) {
|
||||
const decorator = map[name]
|
||||
const descriptor = getOwnPropertyDescriptor(prototype, name)
|
||||
if (typeof decorator === 'function' || Array.isArray(decorator)) {
|
||||
descriptor.value = applyDecorator(decorator, descriptor.value)
|
||||
} else {
|
||||
const { get, set } = decorator
|
||||
if (get !== undefined) {
|
||||
descriptor.get = applyDecorator(get, descriptor.get)
|
||||
}
|
||||
if (set !== undefined) {
|
||||
descriptor.set = applyDecorator(set, descriptor.set)
|
||||
}
|
||||
}
|
||||
|
||||
defineProperty(prototype, name, descriptor)
|
||||
}
|
||||
return klass
|
||||
}
|
||||
|
||||
exports.perInstance = function perInstance(fn, decorator, ...args) {
|
||||
const map = new WeakMap()
|
||||
return function () {
|
||||
let decorated = map.get(this)
|
||||
if (decorated === undefined) {
|
||||
decorated = decorator(fn, ...args)
|
||||
map.set(this, decorated)
|
||||
}
|
||||
return decorated.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
155
@vates/decorate-with/index.test.js
Normal file
155
@vates/decorate-with/index.test.js
Normal file
@@ -0,0 +1,155 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
const { describe, it } = require('test')
|
||||
|
||||
const { decorateClass, decorateWith, decorateMethodsWith, perInstance } = require('./')
|
||||
|
||||
const identity = _ => _
|
||||
|
||||
describe('decorateWith', () => {
|
||||
it('works', () => {
|
||||
const expectedArgs = [Math.random(), Math.random()]
|
||||
const expectedFn = Function.prototype
|
||||
const newFn = () => {}
|
||||
|
||||
const decorator = decorateWith(
|
||||
function wrapper(fn, ...args) {
|
||||
assert.deepStrictEqual(fn, expectedFn)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
|
||||
return newFn
|
||||
},
|
||||
...expectedArgs
|
||||
)
|
||||
|
||||
const descriptor = {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: expectedFn,
|
||||
writable: true,
|
||||
}
|
||||
assert.deepStrictEqual(decorator({}, 'foo', descriptor), {
|
||||
...descriptor,
|
||||
value: newFn,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('decorateClass', () => {
|
||||
it('works', () => {
|
||||
class C {
|
||||
foo() {}
|
||||
bar() {}
|
||||
get baz() {}
|
||||
// eslint-disable-next-line accessor-pairs
|
||||
set qux(_) {}
|
||||
}
|
||||
|
||||
const expectedArgs = [Math.random(), Math.random()]
|
||||
const P = C.prototype
|
||||
|
||||
const descriptors = Object.getOwnPropertyDescriptors(P)
|
||||
|
||||
const newFoo = () => {}
|
||||
const newBar = () => {}
|
||||
const newGetBaz = () => {}
|
||||
const newSetQux = _ => {}
|
||||
|
||||
decorateClass(C, {
|
||||
foo(fn) {
|
||||
assert.strictEqual(arguments.length, 1)
|
||||
assert.strictEqual(fn, P.foo)
|
||||
return newFoo
|
||||
},
|
||||
bar: [
|
||||
function (fn, ...args) {
|
||||
assert.strictEqual(fn, P.bar)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
return newBar
|
||||
},
|
||||
...expectedArgs,
|
||||
],
|
||||
baz: {
|
||||
get(fn) {
|
||||
assert.strictEqual(arguments.length, 1)
|
||||
assert.strictEqual(fn, descriptors.baz.get)
|
||||
return newGetBaz
|
||||
},
|
||||
},
|
||||
qux: {
|
||||
set: [
|
||||
function (fn, ...args) {
|
||||
assert.strictEqual(fn, descriptors.qux.set)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
return newSetQux
|
||||
},
|
||||
...expectedArgs,
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const newDescriptors = Object.getOwnPropertyDescriptors(P)
|
||||
assert.deepStrictEqual(newDescriptors.foo, { ...descriptors.foo, value: newFoo })
|
||||
assert.deepStrictEqual(newDescriptors.bar, { ...descriptors.bar, value: newBar })
|
||||
assert.deepStrictEqual(newDescriptors.baz, { ...descriptors.baz, get: newGetBaz })
|
||||
assert.deepStrictEqual(newDescriptors.qux, { ...descriptors.qux, set: newSetQux })
|
||||
})
|
||||
|
||||
it('throws if using an accessor decorator for a method', function () {
|
||||
assert.throws(() =>
|
||||
decorateClass(
|
||||
class {
|
||||
foo() {}
|
||||
},
|
||||
{ foo: { get: identity, set: identity } }
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
it('throws if using a method decorator for an accessor', function () {
|
||||
assert.throws(() =>
|
||||
decorateClass(
|
||||
class {
|
||||
get foo() {}
|
||||
},
|
||||
{ foo: identity }
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('decorateMethodsWith is an alias of decorateClass', function () {
|
||||
assert.strictEqual(decorateMethodsWith, decorateClass)
|
||||
})
|
||||
|
||||
describe('perInstance', () => {
|
||||
it('works', () => {
|
||||
let calls = 0
|
||||
|
||||
const expectedArgs = [Math.random(), Math.random()]
|
||||
const expectedFn = Function.prototype
|
||||
function wrapper(fn, ...args) {
|
||||
assert.strictEqual(fn, expectedFn)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
const i = ++calls
|
||||
return () => i
|
||||
}
|
||||
|
||||
const wrapped = perInstance(expectedFn, wrapper, ...expectedArgs)
|
||||
|
||||
// decorator is not called before decorated called
|
||||
assert.strictEqual(calls, 0)
|
||||
|
||||
const o1 = {}
|
||||
const o2 = {}
|
||||
|
||||
assert.strictEqual(wrapped.call(o1), 1)
|
||||
|
||||
// the same decorated function is returned for the same instance
|
||||
assert.strictEqual(wrapped.call(o1), 1)
|
||||
|
||||
// a new decorated function is returned for another instance
|
||||
assert.strictEqual(wrapped.call(o2), 2)
|
||||
})
|
||||
})
|
||||
34
@vates/decorate-with/package.json
Normal file
34
@vates/decorate-with/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/decorate-with",
|
||||
"description": "Creates a decorator from a function wrapper",
|
||||
"keywords": [
|
||||
"apply",
|
||||
"decorator",
|
||||
"factory",
|
||||
"wrapper"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/decorate-with",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/decorate-with",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "2.0.0",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
32
@vates/diff/.USAGE.md
Normal file
32
@vates/diff/.USAGE.md
Normal file
@@ -0,0 +1,32 @@
|
||||
```js
|
||||
import diff from '@vates/diff'
|
||||
|
||||
diff('foo bar baz', 'Foo qux')
|
||||
// → [ 0, 'F', 4, 'qux', 7, '' ]
|
||||
//
|
||||
// Differences of the second string from the first one:
|
||||
// - at position 0, it contains `F`
|
||||
// - at position 4, it contains `qux`
|
||||
// - at position 7, it ends
|
||||
|
||||
diff('Foo qux', 'foo bar baz')
|
||||
// → [ 0, 'f', 4, 'bar', 7, ' baz' ]
|
||||
//
|
||||
// Differences of the second string from the first one:
|
||||
// - at position 0, it contains f`
|
||||
// - at position 4, it contains `bar`
|
||||
// - at position 7, it contains `baz`
|
||||
|
||||
// works with all collections that supports
|
||||
// - `.length`
|
||||
// - `collection[index]`
|
||||
// - `.slice(start, end)`
|
||||
//
|
||||
// which includes:
|
||||
// - arrays
|
||||
// - strings
|
||||
// - `Buffer`
|
||||
// - `TypedArray`
|
||||
diff([0, 1, 2], [3, 4])
|
||||
// → [ 0, [ 3, 4 ], 2, [] ]
|
||||
```
|
||||
1
@vates/diff/.npmignore
Symbolic link
1
@vates/diff/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
65
@vates/diff/README.md
Normal file
65
@vates/diff/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/diff
|
||||
|
||||
[](https://npmjs.org/package/@vates/diff)  [](https://bundlephobia.com/result?p=@vates/diff) [](https://npmjs.org/package/@vates/diff)
|
||||
|
||||
> Computes differences between two arrays, buffers or strings
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/diff):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/diff
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import diff from '@vates/diff'
|
||||
|
||||
diff('foo bar baz', 'Foo qux')
|
||||
// → [ 0, 'F', 4, 'qux', 7, '' ]
|
||||
//
|
||||
// Differences of the second string from the first one:
|
||||
// - at position 0, it contains `F`
|
||||
// - at position 4, it contains `qux`
|
||||
// - at position 7, it ends
|
||||
|
||||
diff('Foo qux', 'foo bar baz')
|
||||
// → [ 0, 'f', 4, 'bar', 7, ' baz' ]
|
||||
//
|
||||
// Differences of the second string from the first one:
|
||||
// - at position 0, it contains f`
|
||||
// - at position 4, it contains `bar`
|
||||
// - at position 7, it contains `baz`
|
||||
|
||||
// works with all collections that supports
|
||||
// - `.length`
|
||||
// - `collection[index]`
|
||||
// - `.slice(start, end)`
|
||||
//
|
||||
// which includes:
|
||||
// - arrays
|
||||
// - strings
|
||||
// - `Buffer`
|
||||
// - `TypedArray`
|
||||
diff([0, 1, 2], [3, 4])
|
||||
// → [ 0, [ 3, 4 ], 2, [] ]
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
37
@vates/diff/index.js
Normal file
37
@vates/diff/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Compare two data arrays, buffers or strings and invoke the provided callback function for each difference.
|
||||
*
|
||||
* @template {Array|Buffer|string} T
|
||||
* @param {Array|Buffer|string} data1 - The first data array or buffer to compare.
|
||||
* @param {T} data2 - The second data array or buffer to compare.
|
||||
* @param {(index: number, diff: T) => void} [cb] - The callback function to invoke for each difference. If not provided, an array of differences will be returned.
|
||||
* @returns {Array<number|T>|undefined} - An array of differences if no callback is provided, otherwise undefined.
|
||||
*/
|
||||
module.exports = function diff(data1, data2, cb) {
|
||||
let result
|
||||
if (cb === undefined) {
|
||||
result = []
|
||||
cb = result.push.bind(result)
|
||||
}
|
||||
|
||||
const n1 = data1.length
|
||||
const n2 = data2.length
|
||||
const n = Math.min(n1, n2)
|
||||
for (let i = 0; i < n; ++i) {
|
||||
if (data1[i] !== data2[i]) {
|
||||
let j = i + 1
|
||||
while (j < n && data1[j] !== data2[j]) {
|
||||
++j
|
||||
}
|
||||
cb(i, data2.slice(i, j))
|
||||
i = j
|
||||
}
|
||||
}
|
||||
if (n1 !== n2) {
|
||||
cb(n, n1 < n2 ? data2.slice(n) : data2.slice(0, 0))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
51
@vates/diff/index.test.js
Normal file
51
@vates/diff/index.test.js
Normal file
@@ -0,0 +1,51 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('node:assert/strict')
|
||||
const test = require('test')
|
||||
|
||||
const diff = require('./index.js')
|
||||
|
||||
test('data of equal length', function () {
|
||||
const data1 = 'foo bar baz'
|
||||
const data2 = 'baz bar foo'
|
||||
assert.deepEqual(diff(data1, data2), [0, 'baz', 8, 'foo'])
|
||||
})
|
||||
|
||||
test('data1 is longer', function () {
|
||||
const data1 = 'foo bar'
|
||||
const data2 = 'foo'
|
||||
assert.deepEqual(diff(data1, data2), [3, ''])
|
||||
})
|
||||
|
||||
test('data2 is longer', function () {
|
||||
const data1 = 'foo'
|
||||
const data2 = 'foo bar'
|
||||
assert.deepEqual(diff(data1, data2), [3, ' bar'])
|
||||
})
|
||||
|
||||
test('with arrays', function () {
|
||||
const data1 = 'foo bar baz'.split('')
|
||||
const data2 = 'baz bar foo'.split('')
|
||||
assert.deepEqual(diff(data1, data2), [0, 'baz'.split(''), 8, 'foo'.split('')])
|
||||
})
|
||||
|
||||
test('with buffers', function () {
|
||||
const data1 = Buffer.from('foo bar baz')
|
||||
const data2 = Buffer.from('baz bar foo')
|
||||
assert.deepEqual(diff(data1, data2), [0, Buffer.from('baz'), 8, Buffer.from('foo')])
|
||||
})
|
||||
|
||||
test('cb param', function () {
|
||||
const data1 = 'foo bar baz'
|
||||
const data2 = 'baz bar foo'
|
||||
|
||||
const calls = []
|
||||
const cb = (...args) => calls.push(args)
|
||||
|
||||
diff(data1, data2, cb)
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
[0, 'baz'],
|
||||
[8, 'foo'],
|
||||
])
|
||||
})
|
||||
36
@vates/diff/package.json
Normal file
36
@vates/diff/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/diff",
|
||||
"description": "Computes differences between two arrays, buffers or strings",
|
||||
"keywords": [
|
||||
"array",
|
||||
"binary",
|
||||
"buffer",
|
||||
"diff",
|
||||
"differences",
|
||||
"string"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/diff",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/diff",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "0.1.0",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"test": "^3.3.0"
|
||||
}
|
||||
}
|
||||
56
@vates/disposable/.USAGE.md
Normal file
56
@vates/disposable/.USAGE.md
Normal file
@@ -0,0 +1,56 @@
|
||||
This library contains utilities for disposables as defined by the [`promise-toolbox` library](https://github.com/JsCommunity/promise-toolbox#resource-management).
|
||||
|
||||
### `deduped(fn, keyFn)`
|
||||
|
||||
Creates a new function that wraps `fn` and instead of creating a new disposables at each call, returns copies of the same one when `keyFn` returns the same keys.
|
||||
|
||||
Those copies contains the same value and can be disposed independently, the source disposable will only be disposed when all copies are disposed.
|
||||
|
||||
`keyFn` is called with the same context and arguments as the wrapping function and must returns an array of keys which will be used to identify which disposables should be grouped together.
|
||||
|
||||
```js
|
||||
import { deduped } from '@vates/disposable/deduped'
|
||||
|
||||
// the connection with the passed host will be established once at the first call, then, it will be shared with the next calls
|
||||
const getConnection = deduped(async function (host)) {
|
||||
const connection = new Connection(host)
|
||||
return new Disposabe(connection, () => connection.close())
|
||||
}, host => [host])
|
||||
```
|
||||
|
||||
### `debounceResource(disposable, delay)`
|
||||
|
||||
Creates a new disposable with the same value and with a delayed disposer.
|
||||
|
||||
On calling this disposer, the source disposable will be disposed when the `delay` is passed.
|
||||
|
||||
```js
|
||||
import { createDebounceResource } from '@vates/disposable/debounceResource'
|
||||
|
||||
const debounceResource = createDebounceResource()
|
||||
|
||||
// it will wait for 10 seconds before calling the disposer
|
||||
Disposable.use(debounceResource(getConnection(host), 10e3), connection => {})
|
||||
```
|
||||
|
||||
### `debounceResource.flushAll()`
|
||||
|
||||
Disposes all delayed disposers and cancels the delaying of the disposables that are in usage.
|
||||
|
||||
```js
|
||||
import { createDebounceResource } from '@vates/disposable/debounceResource'
|
||||
|
||||
const debounceResource = createDebounceResource()
|
||||
|
||||
const res1 = await debounceResource(res, 10e3)
|
||||
const res2 = await debounceResource(res, 10e3)
|
||||
const res3 = await debounceResource(res, 10e3)
|
||||
|
||||
rest1.dispose()
|
||||
rest2.dispose()
|
||||
// res3 is in usage
|
||||
|
||||
debounceResource.flushAll()
|
||||
// res1 and res2 are immediately disposed
|
||||
// res3 will be disposed immediately when its disposer will be called
|
||||
```
|
||||
1
@vates/disposable/.npmignore
Symbolic link
1
@vates/disposable/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
89
@vates/disposable/README.md
Normal file
89
@vates/disposable/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/disposable
|
||||
|
||||
[](https://npmjs.org/package/@vates/disposable)  [](https://bundlephobia.com/result?p=@vates/disposable) [](https://npmjs.org/package/@vates/disposable)
|
||||
|
||||
> Utilities for disposables
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/disposable):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/disposable
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
This library contains utilities for disposables as defined by the [`promise-toolbox` library](https://github.com/JsCommunity/promise-toolbox#resource-management).
|
||||
|
||||
### `deduped(fn, keyFn)`
|
||||
|
||||
Creates a new function that wraps `fn` and instead of creating a new disposables at each call, returns copies of the same one when `keyFn` returns the same keys.
|
||||
|
||||
Those copies contains the same value and can be disposed independently, the source disposable will only be disposed when all copies are disposed.
|
||||
|
||||
`keyFn` is called with the same context and arguments as the wrapping function and must returns an array of keys which will be used to identify which disposables should be grouped together.
|
||||
|
||||
```js
|
||||
import { deduped } from '@vates/disposable/deduped'
|
||||
|
||||
// the connection with the passed host will be established once at the first call, then, it will be shared with the next calls
|
||||
const getConnection = deduped(async function (host)) {
|
||||
const connection = new Connection(host)
|
||||
return new Disposabe(connection, () => connection.close())
|
||||
}, host => [host])
|
||||
```
|
||||
|
||||
### `debounceResource(disposable, delay)`
|
||||
|
||||
Creates a new disposable with the same value and with a delayed disposer.
|
||||
|
||||
On calling this disposer, the source disposable will be disposed when the `delay` is passed.
|
||||
|
||||
```js
|
||||
import { createDebounceResource } from '@vates/disposable/debounceResource'
|
||||
|
||||
const debounceResource = createDebounceResource()
|
||||
|
||||
// it will wait for 10 seconds before calling the disposer
|
||||
Disposable.use(debounceResource(getConnection(host), 10e3), connection => {})
|
||||
```
|
||||
|
||||
### `debounceResource.flushAll()`
|
||||
|
||||
Disposes all delayed disposers and cancels the delaying of the disposables that are in usage.
|
||||
|
||||
```js
|
||||
import { createDebounceResource } from '@vates/disposable/debounceResource'
|
||||
|
||||
const debounceResource = createDebounceResource()
|
||||
|
||||
const res1 = await debounceResource(res, 10e3)
|
||||
const res2 = await debounceResource(res, 10e3)
|
||||
const res3 = await debounceResource(res, 10e3)
|
||||
|
||||
rest1.dispose()
|
||||
rest2.dispose()
|
||||
// res3 is in usage
|
||||
|
||||
debounceResource.flushAll()
|
||||
// res1 and res2 are immediately disposed
|
||||
// res3 will be disposed immediately when its disposer will be called
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
58
@vates/disposable/debounceResource.js
Normal file
58
@vates/disposable/debounceResource.js
Normal file
@@ -0,0 +1,58 @@
|
||||
'use strict'
|
||||
|
||||
const { asyncMap } = require('@xen-orchestra/async-map')
|
||||
const { createLogger } = require('@xen-orchestra/log')
|
||||
|
||||
const { warn } = createLogger('vates:disposable:debounceResource')
|
||||
|
||||
exports.createDebounceResource = () => {
|
||||
const flushers = new Set()
|
||||
async function debounceResource(pDisposable, delay = debounceResource.defaultDelay) {
|
||||
if (delay === 0) {
|
||||
return pDisposable
|
||||
}
|
||||
|
||||
const disposable = await pDisposable
|
||||
|
||||
let timeoutId
|
||||
const disposeWrapper = async () => {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = undefined
|
||||
flushers.delete(flusher)
|
||||
|
||||
try {
|
||||
await disposable.dispose()
|
||||
} catch (error) {
|
||||
warn(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flusher = () => {
|
||||
const shouldDisposeNow = timeoutId !== undefined
|
||||
if (shouldDisposeNow) {
|
||||
return disposeWrapper()
|
||||
} else {
|
||||
// will dispose ASAP
|
||||
delay = 0
|
||||
}
|
||||
}
|
||||
flushers.add(flusher)
|
||||
|
||||
return {
|
||||
dispose() {
|
||||
timeoutId = setTimeout(disposeWrapper, delay)
|
||||
},
|
||||
value: disposable.value,
|
||||
}
|
||||
}
|
||||
debounceResource.flushAll = () => {
|
||||
// iterate on a sync way in order to not remove a flusher added on processing flushers
|
||||
const promise = asyncMap(flushers, flush => flush())
|
||||
flushers.clear()
|
||||
return promise
|
||||
}
|
||||
|
||||
return debounceResource
|
||||
}
|
||||
32
@vates/disposable/debounceResource.test.js
Normal file
32
@vates/disposable/debounceResource.test.js
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const { describe, it } = require('test')
|
||||
const { useFakeTimers, spy, assert } = require('sinon')
|
||||
|
||||
const { createDebounceResource } = require('./debounceResource')
|
||||
|
||||
const clock = useFakeTimers()
|
||||
|
||||
describe('debounceResource()', () => {
|
||||
it('calls the resource disposer after 10 seconds', async () => {
|
||||
const debounceResource = createDebounceResource()
|
||||
const delay = 10e3
|
||||
const dispose = spy()
|
||||
|
||||
const resource = await debounceResource(
|
||||
Promise.resolve({
|
||||
value: '',
|
||||
dispose,
|
||||
}),
|
||||
delay
|
||||
)
|
||||
|
||||
resource.dispose()
|
||||
|
||||
assert.notCalled(dispose)
|
||||
|
||||
clock.tick(delay)
|
||||
|
||||
assert.called(dispose)
|
||||
})
|
||||
})
|
||||
54
@vates/disposable/deduped.js
Normal file
54
@vates/disposable/deduped.js
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict'
|
||||
|
||||
const ensureArray = require('ensure-array')
|
||||
const { MultiKeyMap } = require('@vates/multi-key-map')
|
||||
|
||||
function State(factory) {
|
||||
this.factory = factory
|
||||
this.users = 0
|
||||
}
|
||||
|
||||
const call = fn => fn()
|
||||
|
||||
exports.deduped = (factory, keyFn = (...args) => args) =>
|
||||
(function () {
|
||||
const states = new MultiKeyMap()
|
||||
return function () {
|
||||
const keys = ensureArray(keyFn.apply(this, arguments))
|
||||
let state = states.get(keys)
|
||||
if (state === undefined) {
|
||||
const result = factory.apply(this, arguments)
|
||||
|
||||
const createFactory = disposable => {
|
||||
const wrapper = {
|
||||
dispose() {
|
||||
if (--state.users === 0) {
|
||||
states.delete(keys)
|
||||
return disposable.dispose()
|
||||
}
|
||||
},
|
||||
value: disposable.value,
|
||||
}
|
||||
|
||||
return () => {
|
||||
return wrapper
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof result.then !== 'function') {
|
||||
state = new State(createFactory(result))
|
||||
} else {
|
||||
result.catch(() => {
|
||||
states.delete(keys)
|
||||
})
|
||||
const pFactory = result.then(createFactory)
|
||||
state = new State(() => pFactory.then(call))
|
||||
}
|
||||
|
||||
states.set(keys, state)
|
||||
}
|
||||
|
||||
++state.users
|
||||
return state.factory()
|
||||
}
|
||||
})()
|
||||
79
@vates/disposable/deduped.test.js
Normal file
79
@vates/disposable/deduped.test.js
Normal file
@@ -0,0 +1,79 @@
|
||||
'use strict'
|
||||
|
||||
const { describe, it } = require('test')
|
||||
const { spy, assert } = require('sinon')
|
||||
|
||||
const { deduped } = require('./deduped')
|
||||
|
||||
describe('deduped()', () => {
|
||||
it('calls the resource function only once', async () => {
|
||||
const value = {}
|
||||
const getResource = spy(async () => ({
|
||||
value,
|
||||
dispose: Function.prototype,
|
||||
}))
|
||||
|
||||
const dedupedGetResource = deduped(getResource)
|
||||
|
||||
const { value: v1 } = await dedupedGetResource()
|
||||
const { value: v2 } = await dedupedGetResource()
|
||||
|
||||
assert.calledOnce(getResource)
|
||||
assert.match(v1, value)
|
||||
assert.match(v2, value)
|
||||
})
|
||||
|
||||
it('only disposes the source disposable when its all copies dispose', async () => {
|
||||
const dispose = spy()
|
||||
const getResource = async () => ({
|
||||
value: '',
|
||||
dispose,
|
||||
})
|
||||
|
||||
const dedupedGetResource = deduped(getResource)
|
||||
|
||||
const { dispose: d1 } = await dedupedGetResource()
|
||||
const { dispose: d2 } = await dedupedGetResource()
|
||||
|
||||
d1()
|
||||
|
||||
assert.notCalled(dispose)
|
||||
|
||||
d2()
|
||||
|
||||
assert.calledOnce(dispose)
|
||||
})
|
||||
|
||||
it('works with sync factory', () => {
|
||||
const value = {}
|
||||
const dispose = spy()
|
||||
const dedupedGetResource = deduped(() => ({ value, dispose }))
|
||||
|
||||
const d1 = dedupedGetResource()
|
||||
assert.match(d1.value, value)
|
||||
|
||||
const d2 = dedupedGetResource()
|
||||
assert.match(d2.value, value)
|
||||
|
||||
d1.dispose()
|
||||
|
||||
assert.notCalled(dispose)
|
||||
|
||||
d2.dispose()
|
||||
|
||||
assert.calledOnce(dispose)
|
||||
})
|
||||
|
||||
it('no race condition on dispose before async acquisition', async () => {
|
||||
const dispose = spy()
|
||||
const dedupedGetResource = deduped(async () => ({ value: 42, dispose }))
|
||||
|
||||
const d1 = await dedupedGetResource()
|
||||
|
||||
dedupedGetResource()
|
||||
|
||||
d1.dispose()
|
||||
|
||||
assert.notCalled(dispose)
|
||||
})
|
||||
})
|
||||
35
@vates/disposable/package.json
Normal file
35
@vates/disposable/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/disposable",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/disposable",
|
||||
"description": "Utilities for disposables",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/disposable",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "0.1.4",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "node--test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vates/multi-key-map": "^0.1.0",
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"ensure-array": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sinon": "^16.0.0",
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
50
@vates/event-listeners-manager/.USAGE.md
Normal file
50
@vates/event-listeners-manager/.USAGE.md
Normal file
@@ -0,0 +1,50 @@
|
||||
> This library is compatible with Node's `EventEmitter` and web browsers' `EventTarget` APIs.
|
||||
|
||||
### API
|
||||
|
||||
```js
|
||||
import { EventListenersManager } from '@vates/event-listeners-manager'
|
||||
|
||||
const events = new EventListenersManager(emitter)
|
||||
|
||||
// adding listeners
|
||||
events.add('foo', onFoo).add('bar', onBar).on('baz', onBaz)
|
||||
|
||||
// removing a specific listener
|
||||
events.remove('foo', onFoo)
|
||||
|
||||
// removing all listeners for a specific event
|
||||
events.removeAll('foo')
|
||||
|
||||
// removing all listeners
|
||||
events.removeAll()
|
||||
```
|
||||
|
||||
### Typical use case
|
||||
|
||||
> Removing all listeners when no longer necessary.
|
||||
|
||||
Manually:
|
||||
|
||||
```js
|
||||
const onFoo = () => {}
|
||||
const onBar = () => {}
|
||||
const onBaz = () => {}
|
||||
emitter.on('foo', onFoo).on('bar', onBar).on('baz', onBaz)
|
||||
|
||||
// CODE LOGIC
|
||||
|
||||
emitter.off('foo', onFoo).off('bar', onBar).off('baz', onBaz)
|
||||
```
|
||||
|
||||
With this library:
|
||||
|
||||
```js
|
||||
const events = new EventListenersManager(emitter)
|
||||
|
||||
events.add('foo', () => {})).add('bar', () => {})).add('baz', () => {}))
|
||||
|
||||
// CODE LOGIC
|
||||
|
||||
events.removeAll()
|
||||
```
|
||||
1
@vates/event-listeners-manager/.npmignore
Symbolic link
1
@vates/event-listeners-manager/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
81
@vates/event-listeners-manager/README.md
Normal file
81
@vates/event-listeners-manager/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/event-listeners-manager
|
||||
|
||||
[](https://npmjs.org/package/@vates/event-listeners-manager)  [](https://bundlephobia.com/result?p=@vates/event-listeners-manager) [](https://npmjs.org/package/@vates/event-listeners-manager)
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/event-listeners-manager):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/event-listeners-manager
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
> This library is compatible with Node's `EventEmitter` and web browsers' `EventTarget` APIs.
|
||||
|
||||
### API
|
||||
|
||||
```js
|
||||
import { EventListenersManager } from '@vates/event-listeners-manager'
|
||||
|
||||
const events = new EventListenersManager(emitter)
|
||||
|
||||
// adding listeners
|
||||
events.add('foo', onFoo).add('bar', onBar).on('baz', onBaz)
|
||||
|
||||
// removing a specific listener
|
||||
events.remove('foo', onFoo)
|
||||
|
||||
// removing all listeners for a specific event
|
||||
events.removeAll('foo')
|
||||
|
||||
// removing all listeners
|
||||
events.removeAll()
|
||||
```
|
||||
|
||||
### Typical use case
|
||||
|
||||
> Removing all listeners when no longer necessary.
|
||||
|
||||
Manually:
|
||||
|
||||
```js
|
||||
const onFoo = () => {}
|
||||
const onBar = () => {}
|
||||
const onBaz = () => {}
|
||||
emitter.on('foo', onFoo).on('bar', onBar).on('baz', onBaz)
|
||||
|
||||
// CODE LOGIC
|
||||
|
||||
emitter.off('foo', onFoo).off('bar', onBar).off('baz', onBaz)
|
||||
```
|
||||
|
||||
With this library:
|
||||
|
||||
```js
|
||||
const events = new EventListenersManager(emitter)
|
||||
|
||||
events.add('foo', () => {})).add('bar', () => {})).add('baz', () => {}))
|
||||
|
||||
// CODE LOGIC
|
||||
|
||||
events.removeAll()
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
56
@vates/event-listeners-manager/index.js
Normal file
56
@vates/event-listeners-manager/index.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict'
|
||||
|
||||
exports.EventListenersManager = class EventListenersManager {
|
||||
constructor(emitter) {
|
||||
this._listeners = new Map()
|
||||
|
||||
this._add = (emitter.addListener || emitter.addEventListener).bind(emitter)
|
||||
this._remove = (emitter.removeListener || emitter.removeEventListener).bind(emitter)
|
||||
}
|
||||
|
||||
add(type, listener) {
|
||||
let listeners = this._listeners.get(type)
|
||||
if (listeners === undefined) {
|
||||
listeners = new Set()
|
||||
this._listeners.set(type, listeners)
|
||||
}
|
||||
|
||||
// don't add the same listener multiple times (allowed on Node.js)
|
||||
if (!listeners.has(listener)) {
|
||||
listeners.add(listener)
|
||||
this._add(type, listener)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
remove(type, listener) {
|
||||
const allListeners = this._listeners
|
||||
const listeners = allListeners.get(type)
|
||||
if (listeners !== undefined && listeners.delete(listener)) {
|
||||
this._remove(type, listener)
|
||||
if (listeners.size === 0) {
|
||||
allListeners.delete(type)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
removeAll(type) {
|
||||
const allListeners = this._listeners
|
||||
const remove = this._remove
|
||||
const types = type !== undefined ? [type] : allListeners.keys()
|
||||
for (const type of types) {
|
||||
const listeners = allListeners.get(type)
|
||||
if (listeners !== undefined) {
|
||||
allListeners.delete(type)
|
||||
for (const listener of listeners) {
|
||||
remove(type, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
67
@vates/event-listeners-manager/index.spec.js
Normal file
67
@vates/event-listeners-manager/index.spec.js
Normal file
@@ -0,0 +1,67 @@
|
||||
'use strict'
|
||||
|
||||
const t = require('tap')
|
||||
const { EventEmitter } = require('events')
|
||||
|
||||
const { EventListenersManager } = require('./')
|
||||
|
||||
const noop = Function.prototype
|
||||
|
||||
// function spy (impl = Function.prototype) {
|
||||
// function spy() {
|
||||
// spy.calls.push([Array.from(arguments), this])
|
||||
// }
|
||||
// spy.calls = []
|
||||
// return spy
|
||||
// }
|
||||
|
||||
function assertListeners(t, event, listeners) {
|
||||
t.strictSame(t.context.ee.listeners(event), listeners)
|
||||
}
|
||||
|
||||
t.beforeEach(function (t) {
|
||||
t.context.ee = new EventEmitter()
|
||||
t.context.em = new EventListenersManager(t.context.ee)
|
||||
})
|
||||
|
||||
t.test('.add adds a listener', function (t) {
|
||||
t.context.em.add('foo', noop)
|
||||
|
||||
assertListeners(t, 'foo', [noop])
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('.add does not add a duplicate listener', function (t) {
|
||||
t.context.em.add('foo', noop).add('foo', noop)
|
||||
|
||||
assertListeners(t, 'foo', [noop])
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('.remove removes a listener', function (t) {
|
||||
t.context.em.add('foo', noop).remove('foo', noop)
|
||||
|
||||
assertListeners(t, 'foo', [])
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('.removeAll removes all listeners of a given type', function (t) {
|
||||
t.context.em.add('foo', noop).add('bar', noop).removeAll('foo')
|
||||
|
||||
assertListeners(t, 'foo', [])
|
||||
assertListeners(t, 'bar', [noop])
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('.removeAll removes all listeners', function (t) {
|
||||
t.context.em.add('foo', noop).add('bar', noop).removeAll()
|
||||
|
||||
assertListeners(t, 'foo', [])
|
||||
assertListeners(t, 'bar', [])
|
||||
|
||||
t.end()
|
||||
})
|
||||
46
@vates/event-listeners-manager/package.json
Normal file
46
@vates/event-listeners-manager/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"private": false,
|
||||
"name": "@vates/event-listeners-manager",
|
||||
"descriptions": "Easy way to clean up event listeners",
|
||||
"keywords": [
|
||||
"add",
|
||||
"addEventListener",
|
||||
"addListener",
|
||||
"browser",
|
||||
"clear",
|
||||
"DOM",
|
||||
"emitter",
|
||||
"event",
|
||||
"EventEmitter",
|
||||
"EventTarget",
|
||||
"management",
|
||||
"manager",
|
||||
"node",
|
||||
"remove",
|
||||
"removeEventListener",
|
||||
"removeListener"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/event-listeners-manager",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/event-listeners-manager",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "1.0.1",
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "tap --branches=72"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tap": "^16.2.0"
|
||||
}
|
||||
}
|
||||
1
@vates/fuse-vhd/.npmignore
Symbolic link
1
@vates/fuse-vhd/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
64
@vates/fuse-vhd/index.mjs
Normal file
64
@vates/fuse-vhd/index.mjs
Normal file
@@ -0,0 +1,64 @@
|
||||
import LRU from 'lru-cache'
|
||||
import Fuse from 'fuse-native'
|
||||
import { VhdSynthetic } from 'vhd-lib'
|
||||
import { Disposable, fromCallback } from 'promise-toolbox'
|
||||
|
||||
// build a s stat object from https://github.com/fuse-friends/fuse-native/blob/master/test/fixtures/stat.js
|
||||
const stat = st => ({
|
||||
mtime: st.mtime || new Date(),
|
||||
atime: st.atime || new Date(),
|
||||
ctime: st.ctime || new Date(),
|
||||
size: st.size !== undefined ? st.size : 0,
|
||||
mode: st.mode === 'dir' ? 16877 : st.mode === 'file' ? 33188 : st.mode === 'link' ? 41453 : st.mode,
|
||||
uid: st.uid !== undefined ? st.uid : process.getuid(),
|
||||
gid: st.gid !== undefined ? st.gid : process.getgid(),
|
||||
})
|
||||
|
||||
export const mount = Disposable.factory(async function* mount(handler, diskPath, mountDir) {
|
||||
const vhd = yield VhdSynthetic.fromVhdChain(handler, diskPath)
|
||||
|
||||
const cache = new LRU({
|
||||
max: 16, // each cached block is 2MB in size
|
||||
})
|
||||
await vhd.readBlockAllocationTable()
|
||||
const fuse = new Fuse(mountDir, {
|
||||
async readdir(path, cb) {
|
||||
if (path === '/') {
|
||||
return cb(null, ['vhd0'])
|
||||
}
|
||||
cb(Fuse.ENOENT)
|
||||
},
|
||||
async getattr(path, cb) {
|
||||
if (path === '/') {
|
||||
return cb(
|
||||
null,
|
||||
stat({
|
||||
mode: 'dir',
|
||||
size: 4096,
|
||||
})
|
||||
)
|
||||
}
|
||||
if (path === '/vhd0') {
|
||||
return cb(
|
||||
null,
|
||||
stat({
|
||||
mode: 'file',
|
||||
size: vhd.footer.currentSize,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
cb(Fuse.ENOENT)
|
||||
},
|
||||
read(path, fd, buf, len, pos, cb) {
|
||||
if (path === '/vhd0') {
|
||||
return vhd.readRawData(pos, len, cache, buf).then(cb)
|
||||
}
|
||||
throw new Error(`read file ${path} not exists`)
|
||||
},
|
||||
})
|
||||
return new Disposable(
|
||||
() => fromCallback(() => fuse.unmount()),
|
||||
fromCallback(() => fuse.mount())
|
||||
)
|
||||
})
|
||||
30
@vates/fuse-vhd/package.json
Normal file
30
@vates/fuse-vhd/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@vates/fuse-vhd",
|
||||
"version": "2.0.0",
|
||||
"license": "ISC",
|
||||
"private": false,
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/fuse-vhd",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/fuse-vhd",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"main": "./index.mjs",
|
||||
"dependencies": {
|
||||
"fuse-native": "^2.2.6",
|
||||
"lru-cache": "^7.14.0",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"vhd-lib": "^4.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
}
|
||||
}
|
||||
20
@vates/multi-key-map/.USAGE.md
Normal file
20
@vates/multi-key-map/.USAGE.md
Normal file
@@ -0,0 +1,20 @@
|
||||
```js
|
||||
import { MultiKeyMap } from '@vates/multi-key-map'
|
||||
|
||||
const map = new MultiKeyMap()
|
||||
|
||||
const OBJ = {}
|
||||
map.set([], 0)
|
||||
map.set(['foo'], 1)
|
||||
map.set(['foo', 'bar'], 2)
|
||||
map.set(['bar', 'foo'], 3)
|
||||
map.set([OBJ], 4)
|
||||
map.set([{}], 5)
|
||||
|
||||
map.get([]) // 0
|
||||
map.get(['foo']) // 1
|
||||
map.get(['foo', 'bar']) // 2
|
||||
map.get(['bar', 'foo']) // 3
|
||||
map.get([OBJ]) // 4
|
||||
map.get([{}]) // undefined
|
||||
```
|
||||
1
@vates/multi-key-map/.npmignore
Symbolic link
1
@vates/multi-key-map/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
53
@vates/multi-key-map/README.md
Normal file
53
@vates/multi-key-map/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/multi-key-map
|
||||
|
||||
[](https://npmjs.org/package/@vates/multi-key-map)  [](https://bundlephobia.com/result?p=@vates/multi-key-map) [](https://npmjs.org/package/@vates/multi-key-map)
|
||||
|
||||
> Create map with values affected to multiple keys
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/multi-key-map):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/multi-key-map
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { MultiKeyMap } from '@vates/multi-key-map'
|
||||
|
||||
const map = new MultiKeyMap()
|
||||
|
||||
const OBJ = {}
|
||||
map.set([], 0)
|
||||
map.set(['foo'], 1)
|
||||
map.set(['foo', 'bar'], 2)
|
||||
map.set(['bar', 'foo'], 3)
|
||||
map.set([OBJ], 4)
|
||||
map.set([{}], 5)
|
||||
|
||||
map.get([]) // 0
|
||||
map.get(['foo']) // 1
|
||||
map.get(['foo', 'bar']) // 2
|
||||
map.get(['bar', 'foo']) // 3
|
||||
map.get([OBJ]) // 4
|
||||
map.get([{}]) // undefined
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
89
@vates/multi-key-map/index.js
Normal file
89
@vates/multi-key-map/index.js
Normal file
@@ -0,0 +1,89 @@
|
||||
'use strict'
|
||||
|
||||
class Node {
|
||||
constructor(value) {
|
||||
this.children = new Map()
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
function del(node, i, keys) {
|
||||
if (i === keys.length) {
|
||||
if (node instanceof Node) {
|
||||
node.value = undefined
|
||||
return node
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!(node instanceof Node)) {
|
||||
return node
|
||||
}
|
||||
const key = keys[i]
|
||||
const { children } = node
|
||||
const child = children.get(key)
|
||||
if (child === undefined) {
|
||||
return node
|
||||
}
|
||||
const newChild = del(child, i + 1, keys)
|
||||
if (newChild === undefined) {
|
||||
if (children.size === 1) {
|
||||
return node.value
|
||||
}
|
||||
children.delete(key)
|
||||
} else if (newChild !== child) {
|
||||
children.set(key, newChild)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
function get(node, i, keys) {
|
||||
return i === keys.length
|
||||
? node instanceof Node
|
||||
? node.value
|
||||
: node
|
||||
: node instanceof Node
|
||||
? get(node.children.get(keys[i]), i + 1, keys)
|
||||
: undefined
|
||||
}
|
||||
|
||||
function set(node, i, keys, value) {
|
||||
if (i === keys.length) {
|
||||
if (node instanceof Node) {
|
||||
node.value = value
|
||||
return node
|
||||
}
|
||||
return value
|
||||
}
|
||||
const key = keys[i]
|
||||
if (!(node instanceof Node)) {
|
||||
node = new Node(node)
|
||||
node.children.set(key, set(undefined, i + 1, keys, value))
|
||||
} else {
|
||||
const { children } = node
|
||||
const child = children.get(key)
|
||||
const newChild = set(child, i + 1, keys, value)
|
||||
if (newChild !== child) {
|
||||
children.set(key, newChild)
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
exports.MultiKeyMap = class MultiKeyMap {
|
||||
constructor() {
|
||||
// each node is either a value or a Node if it contains children
|
||||
this._root = undefined
|
||||
}
|
||||
|
||||
delete(keys) {
|
||||
this._root = del(this._root, 0, keys)
|
||||
}
|
||||
|
||||
get(keys) {
|
||||
return get(this._root, 0, keys)
|
||||
}
|
||||
|
||||
set(keys, value) {
|
||||
this._root = set(this._root, 0, keys, value)
|
||||
}
|
||||
}
|
||||
37
@vates/multi-key-map/index.test.js
Normal file
37
@vates/multi-key-map/index.test.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const { describe, it } = require('test')
|
||||
const assert = require('node:assert')
|
||||
|
||||
const { MultiKeyMap } = require('./')
|
||||
|
||||
describe('MultiKeyMap', () => {
|
||||
it('works', () => {
|
||||
const map = new MultiKeyMap()
|
||||
|
||||
const keys = [
|
||||
// null key
|
||||
[],
|
||||
// simple key
|
||||
['foo'],
|
||||
// composite key
|
||||
['foo', 'bar'],
|
||||
// reverse composite key
|
||||
['bar', 'foo'],
|
||||
]
|
||||
const values = keys.map(() => ({}))
|
||||
|
||||
// set all values first to make sure they are all stored and not only the
|
||||
// last one
|
||||
keys.forEach((key, i) => {
|
||||
map.set(key, values[i])
|
||||
})
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
// copy the key to make sure the array itself is not the key
|
||||
assert.strictEqual(map.get(key.slice()), values[i])
|
||||
map.delete(key.slice())
|
||||
assert.strictEqual(map.get(key.slice()), undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
32
@vates/multi-key-map/package.json
Normal file
32
@vates/multi-key-map/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/multi-key-map",
|
||||
"description": "Create map with values affected to multiple keys",
|
||||
"keywords": [
|
||||
"cache",
|
||||
"map"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/multi-key-map",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/multi-key-map",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "0.1.0",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "node--test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"test": "^3.2.1"
|
||||
}
|
||||
}
|
||||
16
@vates/nbd-client/.USAGE.md
Normal file
16
@vates/nbd-client/.USAGE.md
Normal file
@@ -0,0 +1,16 @@
|
||||
### `new NdbClient({address, exportname, secure = true, port = 10809})`
|
||||
|
||||
create a new nbd client
|
||||
|
||||
```js
|
||||
import NbdClient from '@vates/nbd-client'
|
||||
const client = new NbdClient({
|
||||
address: 'MY_NBD_HOST',
|
||||
exportname: 'MY_SECRET_EXPORT',
|
||||
cert: 'Server certificate', // optional, will use encrypted link if provided
|
||||
})
|
||||
|
||||
await client.connect()
|
||||
const block = await client.readBlock(blockIndex, BlockSize)
|
||||
await client.disconnect()
|
||||
```
|
||||
1
@vates/nbd-client/.npmignore
Symbolic link
1
@vates/nbd-client/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
47
@vates/nbd-client/README.md
Normal file
47
@vates/nbd-client/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/nbd-client
|
||||
|
||||
[](https://npmjs.org/package/@vates/nbd-client)  [](https://bundlephobia.com/result?p=@vates/nbd-client) [](https://npmjs.org/package/@vates/nbd-client)
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/nbd-client):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/nbd-client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### `new NdbClient({address, exportname, secure = true, port = 10809})`
|
||||
|
||||
create a new nbd client
|
||||
|
||||
```js
|
||||
import NbdClient from '@vates/nbd-client'
|
||||
const client = new NbdClient({
|
||||
address: 'MY_NBD_HOST',
|
||||
exportname: 'MY_SECRET_EXPORT',
|
||||
cert: 'Server certificate', // optional, will use encrypted link if provided
|
||||
})
|
||||
|
||||
await client.connect()
|
||||
const block = await client.readBlock(blockIndex, BlockSize)
|
||||
await client.disconnect()
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
41
@vates/nbd-client/constants.mjs
Normal file
41
@vates/nbd-client/constants.mjs
Normal file
@@ -0,0 +1,41 @@
|
||||
export const INIT_PASSWD = Buffer.from('NBDMAGIC') // "NBDMAGIC" ensure we're connected to a nbd server
|
||||
export const OPTS_MAGIC = Buffer.from('IHAVEOPT') // "IHAVEOPT" start an option block
|
||||
export const NBD_OPT_REPLY_MAGIC = 1100100111001001n // magic received during negociation
|
||||
export const NBD_OPT_EXPORT_NAME = 1
|
||||
export const NBD_OPT_ABORT = 2
|
||||
export const NBD_OPT_LIST = 3
|
||||
export const NBD_OPT_STARTTLS = 5
|
||||
export const NBD_OPT_INFO = 6
|
||||
export const NBD_OPT_GO = 7
|
||||
|
||||
export const NBD_FLAG_HAS_FLAGS = 1 << 0
|
||||
export const NBD_FLAG_READ_ONLY = 1 << 1
|
||||
export const NBD_FLAG_SEND_FLUSH = 1 << 2
|
||||
export const NBD_FLAG_SEND_FUA = 1 << 3
|
||||
export const NBD_FLAG_ROTATIONAL = 1 << 4
|
||||
export const NBD_FLAG_SEND_TRIM = 1 << 5
|
||||
|
||||
export const NBD_FLAG_FIXED_NEWSTYLE = 1 << 0
|
||||
|
||||
export const NBD_CMD_FLAG_FUA = 1 << 0
|
||||
export const NBD_CMD_FLAG_NO_HOLE = 1 << 1
|
||||
export const NBD_CMD_FLAG_DF = 1 << 2
|
||||
export const NBD_CMD_FLAG_REQ_ONE = 1 << 3
|
||||
export const NBD_CMD_FLAG_FAST_ZERO = 1 << 4
|
||||
|
||||
export const NBD_CMD_READ = 0
|
||||
export const NBD_CMD_WRITE = 1
|
||||
export const NBD_CMD_DISC = 2
|
||||
export const NBD_CMD_FLUSH = 3
|
||||
export const NBD_CMD_TRIM = 4
|
||||
export const NBD_CMD_CACHE = 5
|
||||
export const NBD_CMD_WRITE_ZEROES = 6
|
||||
export const NBD_CMD_BLOCK_STATUS = 7
|
||||
export const NBD_CMD_RESIZE = 8
|
||||
|
||||
export const NBD_REQUEST_MAGIC = 0x25609513 // magic number to create a new NBD request to send to the server
|
||||
export const NBD_REPLY_MAGIC = 0x67446698 // magic number received from the server when reading response to a nbd request
|
||||
export const NBD_REPLY_ACK = 1
|
||||
|
||||
export const NBD_DEFAULT_PORT = 10809
|
||||
export const NBD_DEFAULT_BLOCK_SIZE = 64 * 1024
|
||||
351
@vates/nbd-client/index.mjs
Normal file
351
@vates/nbd-client/index.mjs
Normal file
@@ -0,0 +1,351 @@
|
||||
import assert from 'node:assert'
|
||||
import { Socket } from 'node:net'
|
||||
import { connect } from 'node:tls'
|
||||
import { fromCallback, pRetry, pDelay, pTimeout } from 'promise-toolbox'
|
||||
import { readChunkStrict } from '@vates/read-chunk'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
|
||||
import {
|
||||
INIT_PASSWD,
|
||||
NBD_CMD_READ,
|
||||
NBD_DEFAULT_BLOCK_SIZE,
|
||||
NBD_DEFAULT_PORT,
|
||||
NBD_FLAG_FIXED_NEWSTYLE,
|
||||
NBD_FLAG_HAS_FLAGS,
|
||||
NBD_OPT_EXPORT_NAME,
|
||||
NBD_OPT_REPLY_MAGIC,
|
||||
NBD_OPT_STARTTLS,
|
||||
NBD_REPLY_ACK,
|
||||
NBD_REPLY_MAGIC,
|
||||
NBD_REQUEST_MAGIC,
|
||||
OPTS_MAGIC,
|
||||
NBD_CMD_DISC,
|
||||
} from './constants.mjs'
|
||||
|
||||
const { warn } = createLogger('vates:nbd-client')
|
||||
|
||||
// documentation is here : https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md
|
||||
|
||||
export default class NbdClient {
|
||||
#serverAddress
|
||||
#serverCert
|
||||
#serverPort
|
||||
#serverSocket
|
||||
|
||||
#exportName
|
||||
#exportSize
|
||||
|
||||
#waitBeforeReconnect
|
||||
#readAhead
|
||||
#readBlockRetries
|
||||
#reconnectRetry
|
||||
#connectTimeout
|
||||
|
||||
// AFAIK, there is no guaranty the server answers in the same order as the queries
|
||||
// so we handle a backlog of command waiting for response and handle concurrency manually
|
||||
|
||||
#waitingForResponse // there is already a listenner waiting for a response
|
||||
#nextCommandQueryId = BigInt(0)
|
||||
#commandQueryBacklog // map of command waiting for an response queryId => { size/*in byte*/, resolve, reject}
|
||||
#connected = false
|
||||
|
||||
#reconnectingPromise
|
||||
constructor(
|
||||
{ address, port = NBD_DEFAULT_PORT, exportname, cert },
|
||||
{ connectTimeout = 6e4, waitBeforeReconnect = 1e3, readAhead = 10, readBlockRetries = 5, reconnectRetry = 5 } = {}
|
||||
) {
|
||||
this.#serverAddress = address
|
||||
this.#serverPort = port
|
||||
this.#exportName = exportname
|
||||
this.#serverCert = cert
|
||||
this.#waitBeforeReconnect = waitBeforeReconnect
|
||||
this.#readAhead = readAhead
|
||||
this.#readBlockRetries = readBlockRetries
|
||||
this.#reconnectRetry = reconnectRetry
|
||||
this.#connectTimeout = connectTimeout
|
||||
}
|
||||
|
||||
get exportSize() {
|
||||
return this.#exportSize
|
||||
}
|
||||
|
||||
async #tlsConnect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.#serverSocket = connect({
|
||||
socket: this.#serverSocket,
|
||||
rejectUnauthorized: false,
|
||||
cert: this.#serverCert,
|
||||
})
|
||||
this.#serverSocket.once('error', reject)
|
||||
this.#serverSocket.once('secureConnect', () => {
|
||||
this.#serverSocket.removeListener('error', reject)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// mandatory , at least to start the handshake
|
||||
async #unsecureConnect() {
|
||||
this.#serverSocket = new Socket()
|
||||
return new Promise((resolve, reject) => {
|
||||
this.#serverSocket.connect(this.#serverPort, this.#serverAddress)
|
||||
this.#serverSocket.once('error', reject)
|
||||
this.#serverSocket.once('connect', () => {
|
||||
this.#serverSocket.removeListener('error', reject)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async #connect() {
|
||||
// first we connect to the server without tls, and then we upgrade the connection
|
||||
// to tls during the handshake
|
||||
await this.#unsecureConnect()
|
||||
await this.#handshake()
|
||||
this.#connected = true
|
||||
// reset internal state if we reconnected a nbd client
|
||||
this.#commandQueryBacklog = new Map()
|
||||
this.#waitingForResponse = false
|
||||
}
|
||||
async connect() {
|
||||
return pTimeout.call(this.#connect(), this.#connectTimeout)
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (!this.#connected) {
|
||||
return
|
||||
}
|
||||
|
||||
const buffer = Buffer.alloc(28)
|
||||
buffer.writeInt32BE(NBD_REQUEST_MAGIC, 0) // it is a nbd request
|
||||
buffer.writeInt16BE(0, 4) // no command flags for a disconnect
|
||||
buffer.writeInt16BE(NBD_CMD_DISC, 6) // we want to disconnect from nbd server
|
||||
await this.#write(buffer)
|
||||
await this.#serverSocket.destroy()
|
||||
this.#serverSocket = undefined
|
||||
this.#connected = false
|
||||
}
|
||||
|
||||
#clearReconnectPromise = () => {
|
||||
this.#reconnectingPromise = undefined
|
||||
}
|
||||
|
||||
async #reconnect() {
|
||||
await this.disconnect().catch(() => {})
|
||||
await pDelay(this.#waitBeforeReconnect) // need to let the xapi clean things on its side
|
||||
await this.connect()
|
||||
}
|
||||
|
||||
async reconnect() {
|
||||
// we need to ensure reconnections do not occur in parallel
|
||||
if (this.#reconnectingPromise === undefined) {
|
||||
this.#reconnectingPromise = pRetry(() => this.#reconnect(), {
|
||||
tries: this.#reconnectRetry,
|
||||
})
|
||||
this.#reconnectingPromise.then(this.#clearReconnectPromise, this.#clearReconnectPromise)
|
||||
}
|
||||
|
||||
return this.#reconnectingPromise
|
||||
}
|
||||
|
||||
// we can use individual read/write from the socket here since there is no concurrency
|
||||
async #sendOption(option, buffer = Buffer.alloc(0)) {
|
||||
await this.#write(OPTS_MAGIC)
|
||||
await this.#writeInt32(option)
|
||||
await this.#writeInt32(buffer.length)
|
||||
await this.#write(buffer)
|
||||
assert.strictEqual(await this.#readInt64(), NBD_OPT_REPLY_MAGIC) // magic number everywhere
|
||||
assert.strictEqual(await this.#readInt32(), option) // the option passed
|
||||
assert.strictEqual(await this.#readInt32(), NBD_REPLY_ACK) // ACK
|
||||
const length = await this.#readInt32()
|
||||
assert.strictEqual(length, 0) // length
|
||||
}
|
||||
|
||||
// we can use individual read/write from the socket here since there is only one handshake at once, no concurrency
|
||||
async #handshake() {
|
||||
assert((await this.#read(8)).equals(INIT_PASSWD))
|
||||
assert((await this.#read(8)).equals(OPTS_MAGIC))
|
||||
const flagsBuffer = await this.#read(2)
|
||||
const flags = flagsBuffer.readInt16BE(0)
|
||||
assert.strictEqual(flags & NBD_FLAG_FIXED_NEWSTYLE, NBD_FLAG_FIXED_NEWSTYLE) // only FIXED_NEWSTYLE one is supported from the server options
|
||||
await this.#writeInt32(NBD_FLAG_FIXED_NEWSTYLE) // client also support NBD_FLAG_C_FIXED_NEWSTYLE
|
||||
|
||||
if (this.#serverCert !== undefined) {
|
||||
// upgrade socket to TLS if needed
|
||||
await this.#sendOption(NBD_OPT_STARTTLS)
|
||||
await this.#tlsConnect()
|
||||
}
|
||||
|
||||
// send export name we want to access.
|
||||
// it's implictly closing the negociation phase.
|
||||
await this.#write(OPTS_MAGIC)
|
||||
await this.#writeInt32(NBD_OPT_EXPORT_NAME)
|
||||
const exportNameBuffer = Buffer.from(this.#exportName)
|
||||
await this.#writeInt32(exportNameBuffer.length)
|
||||
await this.#write(exportNameBuffer)
|
||||
|
||||
// 8 (export size ) + 2 (flags) + 124 zero = 134
|
||||
// must read all to ensure nothing stays in the buffer
|
||||
const answer = await this.#read(134)
|
||||
this.#exportSize = answer.readBigUInt64BE(0)
|
||||
const transmissionFlags = answer.readInt16BE(8)
|
||||
assert.strictEqual(transmissionFlags & NBD_FLAG_HAS_FLAGS, NBD_FLAG_HAS_FLAGS, 'NBD_FLAG_HAS_FLAGS') // must always be 1 by the norm
|
||||
|
||||
// note : xapi server always send NBD_FLAG_READ_ONLY (3) as a flag
|
||||
}
|
||||
|
||||
#read(length) {
|
||||
return readChunkStrict(this.#serverSocket, length)
|
||||
}
|
||||
|
||||
#write(buffer) {
|
||||
return fromCallback.call(this.#serverSocket, 'write', buffer)
|
||||
}
|
||||
|
||||
async #readInt32() {
|
||||
const buffer = await this.#read(4)
|
||||
return buffer.readInt32BE(0)
|
||||
}
|
||||
|
||||
async #readInt64() {
|
||||
const buffer = await this.#read(8)
|
||||
return buffer.readBigUInt64BE(0)
|
||||
}
|
||||
|
||||
#writeInt32(int) {
|
||||
const buffer = Buffer.alloc(4)
|
||||
buffer.writeInt32BE(int)
|
||||
return this.#write(buffer)
|
||||
}
|
||||
|
||||
// when one read fail ,stop everything
|
||||
async #rejectAll(error) {
|
||||
this.#commandQueryBacklog.forEach(({ reject }) => {
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
|
||||
async #readBlockResponse() {
|
||||
// ensure at most one read occur in parallel
|
||||
if (this.#waitingForResponse) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
this.#waitingForResponse = true
|
||||
const magic = await this.#readInt32()
|
||||
|
||||
if (magic !== NBD_REPLY_MAGIC) {
|
||||
throw new Error(`magic number for block answer is wrong : ${magic} ${NBD_REPLY_MAGIC}`)
|
||||
}
|
||||
|
||||
const error = await this.#readInt32()
|
||||
if (error !== 0) {
|
||||
// @todo use error code from constants.mjs
|
||||
throw new Error(`GOT ERROR CODE : ${error}`)
|
||||
}
|
||||
|
||||
const blockQueryId = await this.#readInt64()
|
||||
const query = this.#commandQueryBacklog.get(blockQueryId)
|
||||
if (!query) {
|
||||
throw new Error(` no query associated with id ${blockQueryId}`)
|
||||
}
|
||||
this.#commandQueryBacklog.delete(blockQueryId)
|
||||
const data = await this.#read(query.size)
|
||||
query.resolve(data)
|
||||
this.#waitingForResponse = false
|
||||
if (this.#commandQueryBacklog.size > 0) {
|
||||
// it doesn't throw directly but will throw all relevant promise on failure
|
||||
this.#readBlockResponse()
|
||||
}
|
||||
} catch (error) {
|
||||
// reject all the promises
|
||||
// we don't need to call readBlockResponse on failure
|
||||
// since we will empty the backlog
|
||||
await this.#rejectAll(error)
|
||||
}
|
||||
}
|
||||
|
||||
async readBlock(index, size = NBD_DEFAULT_BLOCK_SIZE) {
|
||||
// we don't want to add anything in backlog while reconnecting
|
||||
if (this.#reconnectingPromise) {
|
||||
await this.#reconnectingPromise
|
||||
}
|
||||
|
||||
const queryId = this.#nextCommandQueryId
|
||||
this.#nextCommandQueryId++
|
||||
|
||||
// create and send command at once to ensure there is no concurrency issue
|
||||
const buffer = Buffer.alloc(28)
|
||||
buffer.writeInt32BE(NBD_REQUEST_MAGIC, 0) // it is a nbd request
|
||||
buffer.writeInt16BE(0, 4) // no command flags for a simple block read
|
||||
buffer.writeInt16BE(NBD_CMD_READ, 6) // we want to read a data block
|
||||
buffer.writeBigUInt64BE(queryId, 8)
|
||||
// byte offset in the raw disk
|
||||
buffer.writeBigUInt64BE(BigInt(index) * BigInt(size), 16)
|
||||
buffer.writeInt32BE(size, 24)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
function decoratedReject(error) {
|
||||
error.index = index
|
||||
error.size = size
|
||||
reject(error)
|
||||
}
|
||||
|
||||
// this will handle one block response, but it can be another block
|
||||
// since server does not guaranty to handle query in order
|
||||
this.#commandQueryBacklog.set(queryId, {
|
||||
size,
|
||||
resolve,
|
||||
reject: decoratedReject,
|
||||
})
|
||||
// really send the command to the server
|
||||
this.#write(buffer).catch(decoratedReject)
|
||||
|
||||
// #readBlockResponse never throws directly
|
||||
// but if it fails it will reject all the promises in the backlog
|
||||
this.#readBlockResponse()
|
||||
})
|
||||
}
|
||||
|
||||
async *readBlocks(indexGenerator) {
|
||||
// default : read all blocks
|
||||
if (indexGenerator === undefined) {
|
||||
const exportSize = this.#exportSize
|
||||
const chunkSize = 2 * 1024 * 1024
|
||||
indexGenerator = function* () {
|
||||
const nbBlocks = Math.ceil(Number(exportSize / BigInt(chunkSize)))
|
||||
for (let index = 0; BigInt(index) < nbBlocks; index++) {
|
||||
yield { index, size: chunkSize }
|
||||
}
|
||||
}
|
||||
}
|
||||
const readAhead = []
|
||||
const readAheadMaxLength = this.#readAhead
|
||||
const makeReadBlockPromise = (index, size) => {
|
||||
const promise = pRetry(() => this.readBlock(index, size), {
|
||||
tries: this.#readBlockRetries,
|
||||
onRetry: async err => {
|
||||
warn('will retry reading block ', index, err)
|
||||
await this.reconnect()
|
||||
},
|
||||
})
|
||||
// error is handled during unshift
|
||||
promise.catch(() => {})
|
||||
return promise
|
||||
}
|
||||
|
||||
// read all blocks, but try to keep readAheadMaxLength promise waiting ahead
|
||||
for (const { index, size } of indexGenerator()) {
|
||||
// stack readAheadMaxLength promises before starting to handle the results
|
||||
if (readAhead.length === readAheadMaxLength) {
|
||||
// any error will stop reading blocks
|
||||
yield readAhead.shift()
|
||||
}
|
||||
|
||||
readAhead.push(makeReadBlockPromise(index, size))
|
||||
}
|
||||
while (readAhead.length > 0) {
|
||||
yield readAhead.shift()
|
||||
}
|
||||
}
|
||||
}
|
||||
37
@vates/nbd-client/package.json
Normal file
37
@vates/nbd-client/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/nbd-client",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/nbd-client",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/nbd-client",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "2.0.0",
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
},
|
||||
"main": "./index.mjs",
|
||||
"dependencies": {
|
||||
"@vates/async-each": "^1.0.0",
|
||||
"@vates/read-chunk": "^1.2.0",
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"xen-api": "^1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tap": "^16.3.0",
|
||||
"tmp": "^0.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test-integration": "tap --lines 97 --functions 95 --branches 74 --statements 97 tests/*.integ.mjs"
|
||||
}
|
||||
}
|
||||
182
@vates/nbd-client/tests/ca-cert.pem
Normal file
182
@vates/nbd-client/tests/ca-cert.pem
Normal file
@@ -0,0 +1,182 @@
|
||||
Public Key Info:
|
||||
Public Key Algorithm: RSA
|
||||
Key Security Level: High (3072 bits)
|
||||
|
||||
modulus:
|
||||
00:be:92:be:df:de:0a:ab:38:fc:1a:c0:1a:58:4d:86
|
||||
b8:1f:25:10:7d:19:05:17:bf:02:3d:e9:ef:f8:c0:04
|
||||
5d:6f:98:de:5c:dd:c3:0f:e2:61:61:e4:b5:9c:42:ac
|
||||
3e:af:fd:30:10:e1:54:32:66:75:f6:80:90:85:05:a0
|
||||
6a:14:a2:6f:a7:2e:f0:f3:52:94:2a:f2:34:fc:0d:b4
|
||||
fb:28:5d:1c:11:5c:59:6e:63:34:ba:b3:fd:73:b1:48
|
||||
35:00:84:53:da:6a:9b:84:ab:64:b1:a1:2b:3a:d1:5a
|
||||
d7:13:7c:12:2a:4e:72:e9:96:d6:30:74:c5:71:05:14
|
||||
4b:2d:01:94:23:67:4e:37:3c:1e:c1:a0:bc:34:04:25
|
||||
21:11:fb:4b:6b:53:74:8f:90:93:57:af:7f:3b:78:d6
|
||||
a4:87:fe:7d:ed:20:11:8b:70:54:67:b8:c9:f5:c0:6b
|
||||
de:4e:e7:a5:79:ff:f7:ad:cf:10:57:f5:51:70:7b:54
|
||||
68:28:9e:b9:c2:10:7b:ab:aa:11:47:9f:ec:e6:2f:09
|
||||
44:4a:88:5b:dd:8c:10:b4:c4:03:25:06:d9:e0:9f:a0
|
||||
0d:cf:94:4b:3b:fa:a5:17:2c:e4:67:c4:17:6a:ab:d8
|
||||
c8:7a:16:41:b9:91:b7:9c:ae:8c:94:be:26:61:51:71
|
||||
c1:a6:39:39:97:75:28:a9:0e:21:ea:f0:bd:71:4a:8c
|
||||
e1:f8:1d:a9:22:2f:10:a8:1b:e5:a4:9a:fd:0f:fa:c6
|
||||
20:bc:96:99:79:c6:ba:a4:1f:3e:d4:91:c5:af:bb:71
|
||||
0a:5a:ef:69:9c:64:69:ce:5a:fe:3f:c2:24:f4:26:d4
|
||||
3d:ab:ab:9a:f0:f6:f1:b1:64:a9:f4:e2:34:6a:ab:2e
|
||||
95:47:b9:07:5a:39:c6:95:9c:a9:e8:ed:71:dd:c1:21
|
||||
16:c8:2d:4c:2c:af:06:9d:c6:fa:fe:c5:2a:6c:b4:c3
|
||||
d5:96:fc:5e:fd:ec:1c:30:b4:9d:cb:29:ef:a8:50:1c
|
||||
21:
|
||||
|
||||
public exponent:
|
||||
01:00:01:
|
||||
|
||||
private exponent:
|
||||
25:37:c5:7d:35:01:02:65:73:9e:c9:cb:9b:59:30:a9
|
||||
3e:b3:df:5f:7f:06:66:97:d0:19:45:59:af:4b:d8:ce
|
||||
62:a0:09:35:3b:bd:ff:99:27:89:95:bf:fe:0f:6b:52
|
||||
26:ce:9c:97:7f:5a:11:29:bf:79:ef:ab:c9:be:ca:90
|
||||
4d:0d:58:1e:df:65:01:30:2c:6d:a2:b5:c4:4f:ec:fb
|
||||
6b:eb:9b:32:ac:c5:6e:70:83:78:be:f4:0d:a7:1e:c1
|
||||
f3:22:e4:b9:70:3e:85:0f:6f:ef:dc:d8:f3:78:b5:73
|
||||
f1:83:36:8c:fa:9b:28:91:63:ad:3c:f0:de:5c:ae:94
|
||||
eb:ea:36:03:20:06:bf:74:c7:50:eb:52:36:1a:65:21
|
||||
eb:40:17:7f:93:61:dd:33:d0:02:bc:ec:6d:31:f1:41
|
||||
5a:a9:d1:f0:00:66:4c:c4:18:47:d5:67:e3:cd:bb:83
|
||||
44:07:ab:62:83:21:dc:d8:e6:89:37:08:bb:9d:ea:62
|
||||
c2:5d:ce:85:c2:dc:48:27:0c:a4:23:61:b7:30:e7:26
|
||||
44:dc:1e:5c:2e:16:35:2b:2e:a6:e6:a4:ce:1f:9b:e9
|
||||
fe:96:fa:49:1d:fb:2a:df:bc:bf:46:da:52:f8:37:8a
|
||||
84:ab:e4:73:e6:46:56:b5:b4:3d:e1:63:eb:02:8e:d7
|
||||
67:96:c4:dc:28:6d:6b:b6:0c:a3:0b:db:87:29:ad:f9
|
||||
ec:73:b6:55:a3:40:32:13:84:c7:2f:33:74:04:dc:42
|
||||
00:11:9c:fb:fc:62:35:b3:82:c3:3c:28:80:e8:09:a8
|
||||
97:c7:c1:2e:3d:27:fa:4f:9b:fc:c2:34:58:41:5c:a1
|
||||
e2:70:2e:2f:82:ad:bd:bd:8e:dd:23:12:25:de:89:70
|
||||
60:75:48:90:80:ac:55:74:51:6f:49:9e:7f:63:41:8b
|
||||
3c:b1:f5:c3:6b:4b:5a:50:a6:4d:38:e8:82:c2:04:c8
|
||||
30:fd:06:9b:c1:04:27:b6:63:3a:5e:f5:4d:00:c3:d1
|
||||
|
||||
|
||||
prime1:
|
||||
00:f6:00:2e:7d:89:61:24:16:5e:87:ca:18:6c:03:b8
|
||||
b4:33:df:4a:a7:7f:db:ed:39:15:41:12:61:4f:4e:b4
|
||||
de:ab:29:d9:0c:6c:01:7e:53:2e:ee:e7:5f:a2:e4:6d
|
||||
c6:4b:07:4e:d8:a3:ae:45:06:97:bd:18:a3:e9:dd:29
|
||||
54:64:6d:f0:af:08:95:ae:ae:3e:71:63:76:2a:a1:18
|
||||
c4:b1:fc:bc:3d:42:15:74:b3:c5:38:1f:5d:92:f1:b2
|
||||
c6:3f:10:fe:35:1a:c6:b1:ce:70:38:ff:08:5c:de:61
|
||||
79:c7:50:91:22:4d:e9:c8:18:49:e2:5c:91:84:86:e2
|
||||
4d:0f:6e:9b:0d:81:df:aa:f3:59:75:56:e9:33:18:dd
|
||||
ab:39:da:e2:25:01:05:a1:6e:23:59:15:2c:89:35:c7
|
||||
ae:9c:c7:ea:88:9a:1a:f3:48:07:11:82:59:79:8c:62
|
||||
53:06:37:30:14:b3:82:b1:50:fc:ae:b8:f7:1c:57:44
|
||||
7d:
|
||||
|
||||
prime2:
|
||||
00:c6:51:cc:dc:88:2e:cf:98:90:10:19:e0:d3:a4:d1
|
||||
3f:dc:b0:29:d3:bb:26:ee:eb:00:17:17:d1:d1:bb:9b
|
||||
34:b1:4e:af:b5:6c:1c:54:53:b4:bb:55:da:f7:78:cd
|
||||
38:b4:2e:3a:8c:63:80:3b:64:9c:b4:2b:cd:dd:50:0b
|
||||
05:d2:00:7a:df:8e:c3:e6:29:e0:9c:d8:40:b7:11:09
|
||||
f4:38:df:f6:ed:93:1e:18:d4:93:fa:8d:ee:82:9c:0f
|
||||
c1:88:26:84:9d:4f:ae:8a:17:d5:55:54:4c:c6:0a:ac
|
||||
4d:ec:33:51:68:0f:4b:92:2e:04:57:fe:15:f5:00:46
|
||||
5c:8e:ad:09:2c:e7:df:d5:36:7a:4e:bd:da:21:22:d7
|
||||
58:b4:72:93:94:af:34:cc:e2:b8:d0:4f:0b:5d:97:08
|
||||
12:19:17:34:c5:15:49:00:48:56:13:b8:45:4e:3b:f8
|
||||
bc:d5:ab:d9:6d:c2:4a:cc:01:1a:53:4d:46:50:49:3b
|
||||
75:
|
||||
|
||||
coefficient:
|
||||
63:67:50:29:10:6a:85:a3:dc:51:90:20:76:86:8c:83
|
||||
8e:d5:ff:aa:75:fd:b5:f8:31:b0:96:6c:18:1d:5b:ed
|
||||
a4:2e:47:8d:9c:c2:1e:2c:a8:6d:4b:10:a5:c2:53:46
|
||||
8a:9a:84:91:d7:fc:f5:cc:03:ce:b9:3d:5c:01:d2:27
|
||||
99:7b:79:89:4f:a1:12:e3:05:5d:ee:10:f6:8c:e6:ce
|
||||
5e:da:32:56:6d:6f:eb:32:b4:75:7b:94:49:d8:2d:9e
|
||||
4d:19:59:2e:e4:0b:bc:95:df:df:65:67:a1:dd:c6:2b
|
||||
99:f4:76:e8:9f:fa:57:1d:ca:f9:58:a9:ce:9b:30:5c
|
||||
42:8a:ba:05:e7:e2:15:45:25:bc:e9:68:c1:8b:1a:37
|
||||
cc:e1:aa:45:2e:94:f5:81:47:1e:64:7f:c0:c1:b7:a8
|
||||
21:58:18:a9:a0:ed:e0:27:75:bf:65:81:6b:e4:1d:5a
|
||||
b7:7e:df:d8:28:c6:36:21:19:c8:6e:da:ca:9e:da:84
|
||||
|
||||
|
||||
exp1:
|
||||
00:ba:d7:fe:77:a9:0d:98:2c:49:56:57:c0:5e:e2:20
|
||||
ba:f6:1f:26:03:bc:d0:5d:08:9b:45:16:61:c4:ab:e2
|
||||
22:b1:dc:92:17:a6:3d:28:26:a4:22:1e:a8:7b:ff:86
|
||||
05:33:5d:74:9c:85:0d:cb:2d:ab:b8:9b:6b:7c:28:57
|
||||
c8:da:92:ca:59:17:6b:21:07:05:34:78:37:fb:3e:ea
|
||||
a2:13:12:04:23:7e:fa:ee:ed:cf:e0:c5:a9:fb:ff:0a
|
||||
2b:1b:21:9c:02:d7:b8:8c:ba:60:70:59:fc:8f:14:f4
|
||||
f2:5a:d9:ad:b2:61:7d:2c:56:8e:5f:98:b1:89:f8:2d
|
||||
10:1c:a5:84:ad:28:b4:aa:92:34:a3:34:04:e1:a3:84
|
||||
52:16:1a:52:e3:8a:38:2d:99:8a:cd:91:90:87:12:ca
|
||||
fc:ab:e6:08:14:03:00:6f:41:88:e4:da:9d:7c:fd:8c
|
||||
7c:c4:de:cb:ed:1d:3f:29:d0:7a:6b:76:df:71:ae:32
|
||||
bd:
|
||||
|
||||
exp2:
|
||||
4a:e9:d3:6c:ea:b4:64:0e:c9:3c:8b:c9:f5:a8:a8:b2
|
||||
6a:f6:d0:95:fe:78:32:7f:ea:c4:ce:66:9f:c7:32:55
|
||||
b1:34:7c:03:18:17:8b:73:23:2e:30:bc:4a:07:03:de
|
||||
8b:91:7a:e4:55:21:b7:4d:c6:33:f8:e8:06:d5:99:94
|
||||
55:43:81:26:b9:93:1e:7a:6b:32:54:2d:fd:f9:1d:bd
|
||||
77:4e:82:c4:33:72:87:06:a5:ef:5b:75:e1:38:7a:6b
|
||||
2c:b7:00:19:3c:64:3e:1d:ca:a4:34:f7:db:47:64:d6
|
||||
fa:86:58:15:ea:d1:2d:22:dc:d9:30:4d:b3:02:ab:91
|
||||
83:03:b2:17:98:6f:60:e6:f7:44:8f:4a:ba:81:a2:bf
|
||||
0b:4a:cc:9c:b9:a2:44:52:d0:65:3f:b6:97:5f:d9:d8
|
||||
9c:49:bb:d1:46:bd:10:b2:42:71:a8:85:e5:8b:99:e6
|
||||
1b:00:93:5d:76:ab:32:6c:a8:39:17:53:9c:38:4d:91
|
||||
|
||||
|
||||
|
||||
Public Key PIN:
|
||||
pin-sha256:ISh/UeFjUG5Gwrpx6hMUGQPvg9wOKjOkHmRbs4YjZqs=
|
||||
Public Key ID:
|
||||
sha256:21287f51e163506e46c2ba71ea13141903ef83dc0e2a33a41e645bb3862366ab
|
||||
sha1:1a48455111ac45fb5807c5cdb7b20b896c52f0b6
|
||||
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIG4wIBAAKCAYEAvpK+394Kqzj8GsAaWE2GuB8lEH0ZBRe/Aj3p7/jABF1vmN5c
|
||||
3cMP4mFh5LWcQqw+r/0wEOFUMmZ19oCQhQWgahSib6cu8PNSlCryNPwNtPsoXRwR
|
||||
XFluYzS6s/1zsUg1AIRT2mqbhKtksaErOtFa1xN8EipOcumW1jB0xXEFFEstAZQj
|
||||
Z043PB7BoLw0BCUhEftLa1N0j5CTV69/O3jWpIf+fe0gEYtwVGe4yfXAa95O56V5
|
||||
//etzxBX9VFwe1RoKJ65whB7q6oRR5/s5i8JREqIW92MELTEAyUG2eCfoA3PlEs7
|
||||
+qUXLORnxBdqq9jIehZBuZG3nK6MlL4mYVFxwaY5OZd1KKkOIerwvXFKjOH4Haki
|
||||
LxCoG+Wkmv0P+sYgvJaZeca6pB8+1JHFr7txClrvaZxkac5a/j/CJPQm1D2rq5rw
|
||||
9vGxZKn04jRqqy6VR7kHWjnGlZyp6O1x3cEhFsgtTCyvBp3G+v7FKmy0w9WW/F79
|
||||
7BwwtJ3LKe+oUBwhAgMBAAECggGAJTfFfTUBAmVznsnLm1kwqT6z319/BmaX0BlF
|
||||
Wa9L2M5ioAk1O73/mSeJlb/+D2tSJs6cl39aESm/ee+ryb7KkE0NWB7fZQEwLG2i
|
||||
tcRP7Ptr65syrMVucIN4vvQNpx7B8yLkuXA+hQ9v79zY83i1c/GDNoz6myiRY608
|
||||
8N5crpTr6jYDIAa/dMdQ61I2GmUh60AXf5Nh3TPQArzsbTHxQVqp0fAAZkzEGEfV
|
||||
Z+PNu4NEB6tigyHc2OaJNwi7nepiwl3OhcLcSCcMpCNhtzDnJkTcHlwuFjUrLqbm
|
||||
pM4fm+n+lvpJHfsq37y/RtpS+DeKhKvkc+ZGVrW0PeFj6wKO12eWxNwobWu2DKML
|
||||
24cprfnsc7ZVo0AyE4THLzN0BNxCABGc+/xiNbOCwzwogOgJqJfHwS49J/pPm/zC
|
||||
NFhBXKHicC4vgq29vY7dIxIl3olwYHVIkICsVXRRb0mef2NBizyx9cNrS1pQpk04
|
||||
6ILCBMgw/QabwQQntmM6XvVNAMPRAoHBAPYALn2JYSQWXofKGGwDuLQz30qnf9vt
|
||||
ORVBEmFPTrTeqynZDGwBflMu7udfouRtxksHTtijrkUGl70Yo+ndKVRkbfCvCJWu
|
||||
rj5xY3YqoRjEsfy8PUIVdLPFOB9dkvGyxj8Q/jUaxrHOcDj/CFzeYXnHUJEiTenI
|
||||
GEniXJGEhuJND26bDYHfqvNZdVbpMxjdqzna4iUBBaFuI1kVLIk1x66cx+qImhrz
|
||||
SAcRgll5jGJTBjcwFLOCsVD8rrj3HFdEfQKBwQDGUczciC7PmJAQGeDTpNE/3LAp
|
||||
07sm7usAFxfR0bubNLFOr7VsHFRTtLtV2vd4zTi0LjqMY4A7ZJy0K83dUAsF0gB6
|
||||
347D5ingnNhAtxEJ9Djf9u2THhjUk/qN7oKcD8GIJoSdT66KF9VVVEzGCqxN7DNR
|
||||
aA9Lki4EV/4V9QBGXI6tCSzn39U2ek692iEi11i0cpOUrzTM4rjQTwtdlwgSGRc0
|
||||
xRVJAEhWE7hFTjv4vNWr2W3CSswBGlNNRlBJO3UCgcEAutf+d6kNmCxJVlfAXuIg
|
||||
uvYfJgO80F0Im0UWYcSr4iKx3JIXpj0oJqQiHqh7/4YFM110nIUNyy2ruJtrfChX
|
||||
yNqSylkXayEHBTR4N/s+6qITEgQjfvru7c/gxan7/worGyGcAte4jLpgcFn8jxT0
|
||||
8lrZrbJhfSxWjl+YsYn4LRAcpYStKLSqkjSjNATho4RSFhpS44o4LZmKzZGQhxLK
|
||||
/KvmCBQDAG9BiOTanXz9jHzE3svtHT8p0Hprdt9xrjK9AoHASunTbOq0ZA7JPIvJ
|
||||
9aiosmr20JX+eDJ/6sTOZp/HMlWxNHwDGBeLcyMuMLxKBwPei5F65FUht03GM/jo
|
||||
BtWZlFVDgSa5kx56azJULf35Hb13ToLEM3KHBqXvW3XhOHprLLcAGTxkPh3KpDT3
|
||||
20dk1vqGWBXq0S0i3NkwTbMCq5GDA7IXmG9g5vdEj0q6gaK/C0rMnLmiRFLQZT+2
|
||||
l1/Z2JxJu9FGvRCyQnGoheWLmeYbAJNddqsybKg5F1OcOE2RAoHAY2dQKRBqhaPc
|
||||
UZAgdoaMg47V/6p1/bX4MbCWbBgdW+2kLkeNnMIeLKhtSxClwlNGipqEkdf89cwD
|
||||
zrk9XAHSJ5l7eYlPoRLjBV3uEPaM5s5e2jJWbW/rMrR1e5RJ2C2eTRlZLuQLvJXf
|
||||
32Vnod3GK5n0duif+lcdyvlYqc6bMFxCiroF5+IVRSW86WjBixo3zOGqRS6U9YFH
|
||||
HmR/wMG3qCFYGKmg7eAndb9lgWvkHVq3ft/YKMY2IRnIbtrKntqE
|
||||
-----END RSA PRIVATE KEY-----
|
||||
168
@vates/nbd-client/tests/nbdclient.integ.mjs
Normal file
168
@vates/nbd-client/tests/nbdclient.integ.mjs
Normal file
@@ -0,0 +1,168 @@
|
||||
import NbdClient from '../index.mjs'
|
||||
import { spawn, exec } from 'node:child_process'
|
||||
import fs from 'node:fs/promises'
|
||||
import { test } from 'tap'
|
||||
import tmp from 'tmp'
|
||||
import { pFromCallback } from 'promise-toolbox'
|
||||
import { Socket } from 'node:net'
|
||||
import { NBD_DEFAULT_PORT } from '../constants.mjs'
|
||||
import assert from 'node:assert'
|
||||
|
||||
const FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
async function createTempFile(size) {
|
||||
const tmpPath = await pFromCallback(cb => tmp.file(cb))
|
||||
const data = Buffer.alloc(size, 0)
|
||||
for (let i = 0; i < size; i += 4) {
|
||||
data.writeUInt32BE(i, i)
|
||||
}
|
||||
await fs.writeFile(tmpPath, data)
|
||||
|
||||
return tmpPath
|
||||
}
|
||||
|
||||
async function spawnNbdKit(path) {
|
||||
let tries = 5
|
||||
// wait for server to be ready
|
||||
|
||||
const nbdServer = spawn(
|
||||
'nbdkit',
|
||||
[
|
||||
'file',
|
||||
path,
|
||||
'--newstyle', //
|
||||
'--exit-with-parent',
|
||||
'--read-only',
|
||||
'--export-name=MY_SECRET_EXPORT',
|
||||
'--tls=on',
|
||||
'--tls-certificates=./tests/',
|
||||
// '--tls-verify-peer',
|
||||
// '--verbose',
|
||||
'--exit-with-parent',
|
||||
],
|
||||
{
|
||||
stdio: ['inherit', 'inherit', 'inherit'],
|
||||
}
|
||||
)
|
||||
nbdServer.on('error', err => {
|
||||
console.error(err)
|
||||
})
|
||||
do {
|
||||
try {
|
||||
const socket = new Socket()
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.connect(NBD_DEFAULT_PORT, 'localhost')
|
||||
socket.once('error', reject)
|
||||
socket.once('connect', resolve)
|
||||
})
|
||||
socket.destroy()
|
||||
break
|
||||
} catch (err) {
|
||||
tries--
|
||||
if (tries <= 0) {
|
||||
throw err
|
||||
} else {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
}
|
||||
} while (true)
|
||||
return nbdServer
|
||||
}
|
||||
|
||||
async function killNbdKit() {
|
||||
return new Promise((resolve, reject) =>
|
||||
exec('pkill -9 -f -o nbdkit', err => {
|
||||
err ? reject(err) : resolve()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
test('it works with unsecured network', async tap => {
|
||||
const path = await createTempFile(FILE_SIZE)
|
||||
|
||||
let nbdServer = await spawnNbdKit(path)
|
||||
const client = new NbdClient(
|
||||
{
|
||||
address: '127.0.0.1',
|
||||
exportname: 'MY_SECRET_EXPORT',
|
||||
cert: `-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUeHpQ0IeD6BmP2zgsv3LV3J4BI/EwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzA1MTcxMzU1MzBaFw0yNDA1
|
||||
MTYxMzU1MzBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQC/8wLopj/iZY6ijmpvgCJsl+zY0hQZQcIoaCs0H75u
|
||||
8PPSzHedtOLURAkJeMmIS40UY/eIvHh7yZolevaSJLNT2Iolscvc2W9NCF4N1V6y
|
||||
zs4pDzP+YPF7Q8ldNaQIX0bAk4PfaMSM+pLh67u+uI40732AfQqD01BNCTD/uHRB
|
||||
lKnQuqQpe9UM9UzRRVejpu1r19D4dJruAm6y2SJVTeT4a1sSJixl6I1YPmt80FJh
|
||||
gq9O2KRGbXp1xIjemWgW99MHg63pTgxEiULwdJOGgmqGRDzgZKJS5UUpxe/ViEO4
|
||||
59I18vIkgibaRYhENgmnP3lIzTOLlUe07tbSML5RGBbBAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBR/8+zYoL0H0LdWfULHg1LynFdSbzAfBgNVHSMEGDAWgBR/8+zYoL0H0LdW
|
||||
fULHg1LynFdSbzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBD
|
||||
OF5bTmbDEGoZ6OuQaI0vyya/T4FeaoWmh22gLeL6dEEmUVGJ1NyMTOvG9GiGJ8OM
|
||||
QhD1uHJei45/bXOYIDGey2+LwLWye7T4vtRFhf8amYh0ReyP/NV4/JoR/U3pTSH6
|
||||
tns7GZ4YWdwUhvOOlm17EQKVO/hP3t9mp74gcjdL4bCe5MYSheKuNACAakC1OR0U
|
||||
ZakJMP9ijvQuq8spfCzrK+NbHKNHR9tEgQw+ax/t1Au4dGVtFbcoxqCrx2kTl0RP
|
||||
CYu1Xn/FVPx1HoRgWc7E8wFhDcA/P3SJtfIQWHB9FzSaBflKGR4t8WCE2eE8+cTB
|
||||
57ABhfYpMlZ4aHjuN1bL
|
||||
-----END CERTIFICATE-----
|
||||
`,
|
||||
},
|
||||
{
|
||||
readAhead: 2,
|
||||
}
|
||||
)
|
||||
|
||||
await client.connect()
|
||||
tap.equal(client.exportSize, BigInt(FILE_SIZE))
|
||||
const CHUNK_SIZE = 1024 * 1024 // non default size
|
||||
const indexes = []
|
||||
for (let i = 0; i < FILE_SIZE / CHUNK_SIZE; i++) {
|
||||
indexes.push(i)
|
||||
}
|
||||
const nbdIterator = client.readBlocks(function* () {
|
||||
for (const index of indexes) {
|
||||
yield { index, size: CHUNK_SIZE }
|
||||
}
|
||||
})
|
||||
let i = 0
|
||||
for await (const block of nbdIterator) {
|
||||
let blockOk = true
|
||||
let firstFail
|
||||
for (let j = 0; j < CHUNK_SIZE; j += 4) {
|
||||
const wanted = i * CHUNK_SIZE + j
|
||||
const found = block.readUInt32BE(j)
|
||||
blockOk = blockOk && found === wanted
|
||||
if (!blockOk && firstFail === undefined) {
|
||||
firstFail = j
|
||||
}
|
||||
}
|
||||
tap.ok(blockOk, `check block ${i} content`)
|
||||
i++
|
||||
|
||||
// flaky server is flaky
|
||||
if (i % 7 === 0) {
|
||||
// kill the older nbdkit process
|
||||
await killNbdKit()
|
||||
nbdServer = await spawnNbdKit(path)
|
||||
}
|
||||
}
|
||||
|
||||
// we can reuse the conneciton to read other blocks
|
||||
// default iterator
|
||||
const nbdIteratorWithDefaultBlockIterator = client.readBlocks()
|
||||
let nb = 0
|
||||
for await (const block of nbdIteratorWithDefaultBlockIterator) {
|
||||
nb++
|
||||
tap.equal(block.length, 2 * 1024 * 1024)
|
||||
}
|
||||
|
||||
tap.equal(nb, 5)
|
||||
assert.rejects(() => client.readBlock(100, CHUNK_SIZE))
|
||||
|
||||
await client.disconnect()
|
||||
// double disconnection shouldn't pose any problem
|
||||
await client.disconnect()
|
||||
nbdServer.kill()
|
||||
await fs.unlink(path)
|
||||
})
|
||||
21
@vates/nbd-client/tests/server-cert.pem
Normal file
21
@vates/nbd-client/tests/server-cert.pem
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUeHpQ0IeD6BmP2zgsv3LV3J4BI/EwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzA1MTcxMzU1MzBaFw0yNDA1
|
||||
MTYxMzU1MzBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQC/8wLopj/iZY6ijmpvgCJsl+zY0hQZQcIoaCs0H75u
|
||||
8PPSzHedtOLURAkJeMmIS40UY/eIvHh7yZolevaSJLNT2Iolscvc2W9NCF4N1V6y
|
||||
zs4pDzP+YPF7Q8ldNaQIX0bAk4PfaMSM+pLh67u+uI40732AfQqD01BNCTD/uHRB
|
||||
lKnQuqQpe9UM9UzRRVejpu1r19D4dJruAm6y2SJVTeT4a1sSJixl6I1YPmt80FJh
|
||||
gq9O2KRGbXp1xIjemWgW99MHg63pTgxEiULwdJOGgmqGRDzgZKJS5UUpxe/ViEO4
|
||||
59I18vIkgibaRYhENgmnP3lIzTOLlUe07tbSML5RGBbBAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBR/8+zYoL0H0LdWfULHg1LynFdSbzAfBgNVHSMEGDAWgBR/8+zYoL0H0LdW
|
||||
fULHg1LynFdSbzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBD
|
||||
OF5bTmbDEGoZ6OuQaI0vyya/T4FeaoWmh22gLeL6dEEmUVGJ1NyMTOvG9GiGJ8OM
|
||||
QhD1uHJei45/bXOYIDGey2+LwLWye7T4vtRFhf8amYh0ReyP/NV4/JoR/U3pTSH6
|
||||
tns7GZ4YWdwUhvOOlm17EQKVO/hP3t9mp74gcjdL4bCe5MYSheKuNACAakC1OR0U
|
||||
ZakJMP9ijvQuq8spfCzrK+NbHKNHR9tEgQw+ax/t1Au4dGVtFbcoxqCrx2kTl0RP
|
||||
CYu1Xn/FVPx1HoRgWc7E8wFhDcA/P3SJtfIQWHB9FzSaBflKGR4t8WCE2eE8+cTB
|
||||
57ABhfYpMlZ4aHjuN1bL
|
||||
-----END CERTIFICATE-----
|
||||
28
@vates/nbd-client/tests/server-key.pem
Normal file
28
@vates/nbd-client/tests/server-key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/8wLopj/iZY6i
|
||||
jmpvgCJsl+zY0hQZQcIoaCs0H75u8PPSzHedtOLURAkJeMmIS40UY/eIvHh7yZol
|
||||
evaSJLNT2Iolscvc2W9NCF4N1V6yzs4pDzP+YPF7Q8ldNaQIX0bAk4PfaMSM+pLh
|
||||
67u+uI40732AfQqD01BNCTD/uHRBlKnQuqQpe9UM9UzRRVejpu1r19D4dJruAm6y
|
||||
2SJVTeT4a1sSJixl6I1YPmt80FJhgq9O2KRGbXp1xIjemWgW99MHg63pTgxEiULw
|
||||
dJOGgmqGRDzgZKJS5UUpxe/ViEO459I18vIkgibaRYhENgmnP3lIzTOLlUe07tbS
|
||||
ML5RGBbBAgMBAAECggEATLYiafcTHfgnZmjTOad0WoDnC4n9tVBV948WARlUooLS
|
||||
duL3RQRHCLz9/ZaTuFA1XDpNcYyc/B/IZoU7aJGZR3+JSmJBjowpUphu+klVNNG4
|
||||
i6lDRrzYlUI0hfdLjHsDTDBIKi91KcB0lix/VkvsrVQvDHwsiR2ZAIiVWAWQFKrR
|
||||
5O3DhSTHbqyq47uR58rWr4Zf3zvZaUl841AS1yELzCiZqz7AenvyWphim0c0XA5d
|
||||
I63CEShntHnEAA9OMcP8+BNf/3AmqB4welY+m8elB3aJNH+j7DKq/AWqaM5nl2PC
|
||||
cS6qgpxwOyTxEOyj1xhwK5ZMRR3heW3NfutIxSOPlwKBgQDB9ZkrBeeGVtCISO7C
|
||||
eCANzSLpeVrahTvaCSQLdPHsLRLDUc+5mxdpi3CaRlzYs3S1OWdAtyWX9mBryltF
|
||||
qDPhCNjFDyHok4D3wLEWdS9oUVwEKUM8fOPW3tXLLiMM7p4862Qo7LqnqHzPqsnz
|
||||
22iZo5yjcc7aLJ+VmFrbAowwOwKBgQD9WNCvczTd7Ymn7zEvdiAyNoS0OZ0orwEJ
|
||||
zGaxtjqVguGklNfrb/UB+eKNGE80+YnMiSaFc9IQPetLntZdV0L7kWYdCI8kGDNA
|
||||
DbVRCOp+z8DwAojlrb/zsYu23anQozT3WeHxVU66lNuyEQvSW2tJa8gN1htrD7uY
|
||||
5KLibYrBMwKBgEM0iiHyJcrSgeb2/mO7o7+keJhVSDm3OInP6QFfQAQJihrLWiKB
|
||||
rpcPjbCm+LzNUX8JqNEvpIMHB1nR/9Ye9frfSdzd5W3kzicKSVHywL5wkmWOtpFa
|
||||
5Mcq5wFDtzlf5MxO86GKhRJauwRptRgdyhySKFApuva1x4XaCIEiXNjJAoGBAN82
|
||||
t3c+HCBEv3o05rMYcrmLC1T3Rh6oQlPtwbVmByvfywsFEVCgrc/16MPD3VWhXuXV
|
||||
GRmPuE8THxLbead30M5xhvShq+xzXgRbj5s8Lc9ZIHbW5OLoOS1vCtgtaQcoJOyi
|
||||
Rs4pCVqe+QpktnO6lEZ2Libys+maTQEiwNibBxu9AoGAUG1V5aKMoXa7pmGeuFR6
|
||||
ES+1NDiCt6yDq9BsLZ+e2uqvWTkvTGLLwvH6xf9a0pnnILd0AUTKAAaoUdZS6++E
|
||||
cGob7fxMwEE+UETp0QBgLtfjtExMOFwr2avw8PV4CYEUkPUAm2OFB2Twh+d/PNfr
|
||||
FAxF1rN47SBPNbFI8N4TFsg=
|
||||
-----END PRIVATE KEY-----
|
||||
1
@vates/node-vsphere-soap/.npmignore
Symbolic link
1
@vates/node-vsphere-soap/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
22
@vates/node-vsphere-soap/LICENSE
Normal file
22
@vates/node-vsphere-soap/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 reedog117
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
127
@vates/node-vsphere-soap/README.md
Normal file
127
@vates/node-vsphere-soap/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
forked from https://github.com/reedog117/node-vsphere-soap
|
||||
|
||||
# node-vsphere-soap
|
||||
|
||||
[](https://gitter.im/reedog117/node-vsphere-soap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
This is a Node.js module to connect to VMware vCenter servers and/or ESXi hosts and perform operations using the [vSphere Web Services API]. If you're feeling really adventurous, you can use this module to port vSphere operations from other languages (such as the Perl, Python, and Go libraries that exist) and have fully native Node.js code controlling your VMware virtual infrastructure!
|
||||
|
||||
This is very much in alpha.
|
||||
|
||||
## Authors
|
||||
|
||||
- Patrick C - [@reedog117]
|
||||
|
||||
## Version
|
||||
|
||||
0.0.2-5
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ npm install node-vsphere-soap --save
|
||||
```
|
||||
|
||||
## Sample Code
|
||||
|
||||
### To connect to a vCenter server:
|
||||
|
||||
var nvs = require('node-vsphere-soap');
|
||||
var vc = new nvs.Client(host, user, password, sslVerify);
|
||||
vc.once('ready', function() {
|
||||
// perform work here
|
||||
});
|
||||
vc.once('error', function(err) {
|
||||
// handle error here
|
||||
});
|
||||
|
||||
#### Arguments
|
||||
|
||||
- host = hostname or IP of vCenter/ESX/ESXi server
|
||||
- user = username
|
||||
- password = password
|
||||
- sslVerify = true|false - set to false if you have self-signed/unverified certificates
|
||||
|
||||
#### Events
|
||||
|
||||
- ready = emits when session authenticated with server
|
||||
- error = emits when there's an error
|
||||
- _err_ contains the error
|
||||
|
||||
#### Client instance variables
|
||||
|
||||
- serviceContent - ServiceContent object retrieved by RetrieveServiceContent API call
|
||||
- userName - username of authenticated user
|
||||
- fullName - full name of authenticated user
|
||||
|
||||
### To run a command:
|
||||
|
||||
var vcCmd = vc.runCommand( commandToRun, arguments );
|
||||
vcCmd.once('result', function( result, raw, soapHeader) {
|
||||
// handle results
|
||||
});
|
||||
vcCmd.once('error', function( err) {
|
||||
// handle errors
|
||||
});
|
||||
|
||||
#### Arguments
|
||||
|
||||
- commandToRun = Method from the vSphere API
|
||||
- arguments = JSON document containing arguments to send
|
||||
|
||||
#### Events
|
||||
|
||||
- result = emits when session authenticated with server
|
||||
- _result_ contains the JSON-formatted result from the server
|
||||
- _raw_ contains the raw SOAP XML response from the server
|
||||
- _soapHeader_ contains any soapHeaders from the server
|
||||
- error = emits when there's an error
|
||||
- _err_ contains the error
|
||||
|
||||
Make sure you check out tests/vsphere-soap.test.js for examples on how to create commands to run
|
||||
|
||||
## Development
|
||||
|
||||
node-vsphere-soap uses a number of open source projects to work properly:
|
||||
|
||||
- [node.js] - evented I/O for the backend
|
||||
- [node-soap] - SOAP client for Node.js
|
||||
- [soap-cookie] - cookie authentication for the node-soap module
|
||||
- [lodash] - for quickly manipulating JSON
|
||||
- [lab] - testing engine
|
||||
- [code] - assertion engine used with lab
|
||||
|
||||
Want to contribute? Great!
|
||||
|
||||
### Todo's
|
||||
|
||||
- Write More Tests
|
||||
- Create Travis CI test harness with a fake vCenter Instance
|
||||
- Add Code Comments
|
||||
|
||||
### Testing
|
||||
|
||||
I have been testing on a Mac with node v0.10.36 and both ESXi and vCenter 5.5.
|
||||
|
||||
To edit tests, edit the file **test/vsphere-soap.test.js**
|
||||
|
||||
To point the module at your own vCenter/ESXi host, edit **config-test.stub.js** and save it as **config-test.js**
|
||||
|
||||
To run test scripts:
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
[vSphere Web Services API]: http://pubs.vmware.com/vsphere-55/topic/com.vmware.wssdk.apiref.doc/right-pane.html
|
||||
[node-soap]: https://github.com/vpulim/node-soap
|
||||
[node.js]: http://nodejs.org/
|
||||
[soap-cookie]: https://github.com/shanestillwell/soap-cookie
|
||||
[code]: https://github.com/hapijs/code
|
||||
[lab]: https://github.com/hapijs/lab
|
||||
[lodash]: https://lodash.com/
|
||||
[@reedog117]: http://www.twitter.com/reedog117
|
||||
230
@vates/node-vsphere-soap/lib/client.mjs
Normal file
230
@vates/node-vsphere-soap/lib/client.mjs
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
|
||||
node-vsphere-soap
|
||||
|
||||
client.js
|
||||
|
||||
This file creates the Client class
|
||||
|
||||
- when the class is instantiated, a connection will be made to the ESXi/vCenter server to verify that the creds are good
|
||||
- upon a bad login, the connnection will be terminated
|
||||
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import axios from 'axios'
|
||||
import https from 'node:https'
|
||||
import util from 'util'
|
||||
import soap from 'soap'
|
||||
import Cookie from 'soap-cookie' // required for session persistence
|
||||
|
||||
// Client class
|
||||
// inherits from EventEmitter
|
||||
// possible events: connect, error, ready
|
||||
|
||||
export function Client(vCenterHostname, username, password, sslVerify) {
|
||||
this.status = 'disconnected'
|
||||
this.reconnectCount = 0
|
||||
|
||||
sslVerify = typeof sslVerify !== 'undefined' ? sslVerify : false
|
||||
|
||||
EventEmitter.call(this)
|
||||
|
||||
// sslVerify argument handling
|
||||
if (sslVerify) {
|
||||
this.clientopts = {}
|
||||
} else {
|
||||
this.clientopts = {
|
||||
request: axios.create({
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
this.connectionInfo = {
|
||||
host: vCenterHostname,
|
||||
user: username,
|
||||
password,
|
||||
sslVerify,
|
||||
}
|
||||
|
||||
this._loginArgs = {
|
||||
userName: this.connectionInfo.user,
|
||||
password: this.connectionInfo.password,
|
||||
}
|
||||
|
||||
this._vcUrl = 'https://' + this.connectionInfo.host + '/sdk/vimService.wsdl'
|
||||
|
||||
// connect to the vCenter / ESXi host
|
||||
this.on('connect', this._connect)
|
||||
this.emit('connect')
|
||||
|
||||
// close session
|
||||
this.on('close', this._close)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
util.inherits(Client, EventEmitter)
|
||||
|
||||
Client.prototype.runCommand = function (command, args) {
|
||||
const self = this
|
||||
let cmdargs
|
||||
if (!args || args === null) {
|
||||
cmdargs = {}
|
||||
} else {
|
||||
cmdargs = args
|
||||
}
|
||||
|
||||
const emitter = new EventEmitter()
|
||||
|
||||
// check if client has successfully connected
|
||||
if (self.status === 'ready' || self.status === 'connecting') {
|
||||
self.client.VimService.VimPort[command](cmdargs, function (err, result, raw, soapHeader) {
|
||||
if (err) {
|
||||
_soapErrorHandler(self, emitter, command, cmdargs, err)
|
||||
}
|
||||
if (command === 'Logout') {
|
||||
self.status = 'disconnected'
|
||||
process.removeAllListeners('beforeExit')
|
||||
}
|
||||
emitter.emit('result', result, raw, soapHeader)
|
||||
})
|
||||
} else {
|
||||
// if connection not ready or connecting, reconnect to instance
|
||||
if (self.status === 'disconnected') {
|
||||
self.emit('connect')
|
||||
}
|
||||
self.once('ready', function () {
|
||||
self.client.VimService.VimPort[command](cmdargs, function (err, result, raw, soapHeader) {
|
||||
if (err) {
|
||||
_soapErrorHandler(self, emitter, command, cmdargs, err)
|
||||
}
|
||||
if (command === 'Logout') {
|
||||
self.status = 'disconnected'
|
||||
process.removeAllListeners('beforeExit')
|
||||
}
|
||||
emitter.emit('result', result, raw, soapHeader)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return emitter
|
||||
}
|
||||
|
||||
Client.prototype.close = function () {
|
||||
const self = this
|
||||
|
||||
self.emit('close')
|
||||
}
|
||||
|
||||
Client.prototype._connect = function () {
|
||||
const self = this
|
||||
|
||||
if (self.status !== 'disconnected') {
|
||||
return
|
||||
}
|
||||
|
||||
self.status = 'connecting'
|
||||
|
||||
soap.createClient(
|
||||
self._vcUrl,
|
||||
self.clientopts,
|
||||
function (err, client) {
|
||||
if (err) {
|
||||
self.emit('error', err)
|
||||
throw err
|
||||
}
|
||||
|
||||
self.client = client // save client for later use
|
||||
|
||||
self
|
||||
.runCommand('RetrieveServiceContent', { _this: 'ServiceInstance' })
|
||||
.once('result', function (result, raw, soapHeader) {
|
||||
if (!result.returnval) {
|
||||
self.status = 'disconnected'
|
||||
self.emit('error', raw)
|
||||
return
|
||||
}
|
||||
|
||||
self.serviceContent = result.returnval
|
||||
self.sessionManager = result.returnval.sessionManager
|
||||
const loginArgs = { _this: self.sessionManager, ...self._loginArgs }
|
||||
|
||||
self
|
||||
.runCommand('Login', loginArgs)
|
||||
.once('result', function (result, raw, soapHeader) {
|
||||
self.authCookie = new Cookie(client.lastResponseHeaders)
|
||||
self.client.setSecurity(self.authCookie) // needed since vSphere SOAP WS uses cookies
|
||||
|
||||
self.userName = result.returnval.userName
|
||||
self.fullName = result.returnval.fullName
|
||||
self.reconnectCount = 0
|
||||
|
||||
self.status = 'ready'
|
||||
self.emit('ready')
|
||||
process.once('beforeExit', self._close)
|
||||
})
|
||||
.once('error', function (err) {
|
||||
self.status = 'disconnected'
|
||||
self.emit('error', err)
|
||||
})
|
||||
})
|
||||
.once('error', function (err) {
|
||||
self.status = 'disconnected'
|
||||
self.emit('error', err)
|
||||
})
|
||||
},
|
||||
self._vcUrl
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype._close = function () {
|
||||
const self = this
|
||||
|
||||
if (self.status === 'ready') {
|
||||
self
|
||||
.runCommand('Logout', { _this: self.sessionManager })
|
||||
.once('result', function () {
|
||||
self.status = 'disconnected'
|
||||
})
|
||||
.once('error', function () {
|
||||
/* don't care of error during disconnection */
|
||||
self.status = 'disconnected'
|
||||
})
|
||||
} else {
|
||||
self.status = 'disconnected'
|
||||
}
|
||||
}
|
||||
|
||||
function _soapErrorHandler(self, emitter, command, args, err) {
|
||||
err = err || { body: 'general error' }
|
||||
|
||||
if (err.body.match(/session is not authenticated/)) {
|
||||
self.status = 'disconnected'
|
||||
process.removeAllListeners('beforeExit')
|
||||
|
||||
if (self.reconnectCount < 10) {
|
||||
self.reconnectCount += 1
|
||||
self
|
||||
.runCommand(command, args)
|
||||
.once('result', function (result, raw, soapHeader) {
|
||||
emitter.emit('result', result, raw, soapHeader)
|
||||
})
|
||||
.once('error', function (err) {
|
||||
emitter.emit('error', err.body)
|
||||
throw err
|
||||
})
|
||||
} else {
|
||||
emitter.emit('error', err.body)
|
||||
throw err
|
||||
}
|
||||
} else {
|
||||
emitter.emit('error', err.body)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// end
|
||||
38
@vates/node-vsphere-soap/package.json
Normal file
38
@vates/node-vsphere-soap/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@vates/node-vsphere-soap",
|
||||
"version": "2.0.0",
|
||||
"description": "interface to vSphere SOAP/WSDL from node for interfacing with vCenter or ESXi, forked from node-vsphere-soap",
|
||||
"main": "lib/client.mjs",
|
||||
"author": "reedog117",
|
||||
"repository": {
|
||||
"directory": "@vates/node-vsphere-soap",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"dependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"soap": "^1.0.0",
|
||||
"soap-cookie": "^0.10.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"test": "^3.3.0"
|
||||
},
|
||||
"keywords": [
|
||||
"vsphere",
|
||||
"vcenter",
|
||||
"api",
|
||||
"soap",
|
||||
"wsdl"
|
||||
],
|
||||
"preferGlobal": false,
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/node-vsphere-soap",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
}
|
||||
}
|
||||
11
@vates/node-vsphere-soap/test/config-test.stub.mjs
Normal file
11
@vates/node-vsphere-soap/test/config-test.stub.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
// place your own credentials here for a vCenter or ESXi server
|
||||
// this information will be used for connecting to a vCenter instance
|
||||
// for module testing
|
||||
// name the file config-test.js
|
||||
|
||||
export const vCenterTestCreds = {
|
||||
vCenterIP: 'vcsa',
|
||||
vCenterUser: 'vcuser',
|
||||
vCenterPassword: 'vcpw',
|
||||
vCenter: true,
|
||||
}
|
||||
138
@vates/node-vsphere-soap/test/vsphere-soap.test.mjs
Normal file
138
@vates/node-vsphere-soap/test/vsphere-soap.test.mjs
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
vsphere-soap.test.js
|
||||
|
||||
tests for the vCenterConnectionInstance class
|
||||
*/
|
||||
|
||||
import assert from 'assert'
|
||||
import { describe, it } from 'test'
|
||||
|
||||
import * as vc from '../lib/client.mjs'
|
||||
|
||||
// eslint-disable-next-line n/no-missing-import
|
||||
import { vCenterTestCreds as TestCreds } from '../config-test.mjs'
|
||||
|
||||
const VItest = new vc.Client(TestCreds.vCenterIP, TestCreds.vCenterUser, TestCreds.vCenterPassword, false)
|
||||
|
||||
describe('Client object initialization:', function () {
|
||||
it('provides a successful login', { timeout: 5000 }, function (t, done) {
|
||||
VItest.once('ready', function () {
|
||||
assert.notEqual(VItest.userName, null)
|
||||
assert.notEqual(VItest.fullName, null)
|
||||
assert.notEqual(VItest.serviceContent, null)
|
||||
done()
|
||||
}).once('error', function (err) {
|
||||
console.error(err)
|
||||
// this should fail if there's a problem
|
||||
assert.notEqual(VItest.userName, null)
|
||||
assert.notEqual(VItest.fullName, null)
|
||||
assert.notEqual(VItest.serviceContent, null)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Client reconnection test:', function () {
|
||||
it('can successfully reconnect', { timeout: 5000 }, function (t, done) {
|
||||
VItest.runCommand('Logout', { _this: VItest.serviceContent.sessionManager })
|
||||
.once('result', function (result) {
|
||||
// now we're logged out, so let's try running a command to test automatic re-login
|
||||
VItest.runCommand('CurrentTime', { _this: 'ServiceInstance' })
|
||||
.once('result', function (result) {
|
||||
assert(result.returnval instanceof Date)
|
||||
done()
|
||||
})
|
||||
.once('error', function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
})
|
||||
.once('error', function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// these tests don't work yet
|
||||
describe('Client tests - query commands:', function () {
|
||||
it('retrieves current time', { timeout: 5000 }, function (t, done) {
|
||||
VItest.runCommand('CurrentTime', { _this: 'ServiceInstance' }).once('result', function (result) {
|
||||
assert(result.returnval instanceof Date)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('retrieves current time 2 (check for event clobbering)', { timeout: 5000 }, function (t, done) {
|
||||
VItest.runCommand('CurrentTime', { _this: 'ServiceInstance' }).once('result', function (result) {
|
||||
assert(result.returnval instanceof Date)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can obtain the names of all Virtual Machines in the inventory', { timeout: 20000 }, function (t, done) {
|
||||
// get property collector
|
||||
const propertyCollector = VItest.serviceContent.propertyCollector
|
||||
// get view manager
|
||||
const viewManager = VItest.serviceContent.viewManager
|
||||
// get root folder
|
||||
const rootFolder = VItest.serviceContent.rootFolder
|
||||
|
||||
let containerView, objectSpec, traversalSpec, propertySpec, propertyFilterSpec
|
||||
// this is the equivalent to
|
||||
VItest.runCommand('CreateContainerView', {
|
||||
_this: viewManager,
|
||||
container: rootFolder,
|
||||
type: ['VirtualMachine'],
|
||||
recursive: true,
|
||||
}).once('result', function (result) {
|
||||
// build all the data structures needed to query all the vm names
|
||||
containerView = result.returnval
|
||||
|
||||
objectSpec = {
|
||||
attributes: { 'xsi:type': 'ObjectSpec' }, // setting attributes xsi:type is important or else the server may mis-recognize types!
|
||||
obj: containerView,
|
||||
skip: true,
|
||||
}
|
||||
|
||||
traversalSpec = {
|
||||
attributes: { 'xsi:type': 'TraversalSpec' },
|
||||
name: 'traverseEntities',
|
||||
type: 'ContainerView',
|
||||
path: 'view',
|
||||
skip: false,
|
||||
}
|
||||
|
||||
objectSpec = { ...objectSpec, selectSet: [traversalSpec] }
|
||||
|
||||
propertySpec = {
|
||||
attributes: { 'xsi:type': 'PropertySpec' },
|
||||
type: 'VirtualMachine',
|
||||
pathSet: ['name'],
|
||||
}
|
||||
|
||||
propertyFilterSpec = {
|
||||
attributes: { 'xsi:type': 'PropertyFilterSpec' },
|
||||
propSet: [propertySpec],
|
||||
objectSet: [objectSpec],
|
||||
}
|
||||
// TODO: research why it fails if propSet is declared after objectSet
|
||||
|
||||
VItest.runCommand('RetrievePropertiesEx', {
|
||||
_this: propertyCollector,
|
||||
specSet: [propertyFilterSpec],
|
||||
options: { attributes: { type: 'RetrieveOptions' } },
|
||||
})
|
||||
.once('result', function (result, raw) {
|
||||
assert.notEqual(result.returnval.objects, null)
|
||||
if (Array.isArray(result.returnval.objects)) {
|
||||
assert.strictEqual(result.returnval.objects[0].obj.attributes.type, 'VirtualMachine')
|
||||
} else {
|
||||
assert.strictEqual(result.returnval.objects.obj.attributes.type, 'VirtualMachine')
|
||||
}
|
||||
done()
|
||||
})
|
||||
.once('error', function (err) {
|
||||
console.error('\n\nlast request : ' + VItest.client.lastRequest, err)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
130
@vates/otp/.USAGE.md
Normal file
130
@vates/otp/.USAGE.md
Normal file
@@ -0,0 +1,130 @@
|
||||
### Usual workflow
|
||||
|
||||
> This section presents how this library should be used to implement a classic two factor authentification.
|
||||
|
||||
#### Setup
|
||||
|
||||
```js
|
||||
import { generateSecret, generateTotp } from '@vates/otp'
|
||||
import QrCode from 'qrcode'
|
||||
|
||||
// Generates a secret that will be shared by both the service and the user:
|
||||
const secret = generateSecret()
|
||||
|
||||
// Stores the secret in the service:
|
||||
await currentUser.saveOtpSecret(secret)
|
||||
|
||||
// Generates an URI to present to the user
|
||||
const uri = generateTotpUri({ secret })
|
||||
|
||||
// Generates the QR code from the URI to make it easily importable in Authy or Google Authenticator
|
||||
const qr = await QrCode.toDataURL(uri)
|
||||
```
|
||||
|
||||
#### Authentication
|
||||
|
||||
```js
|
||||
import { verifyTotp } from '@vates/otp'
|
||||
|
||||
// Verifies a `token` entered by the user against a `secret` generated during setup.
|
||||
if (await verifyTotp(token, { secret })) {
|
||||
console.log('authenticated!')
|
||||
}
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
#### Secret
|
||||
|
||||
```js
|
||||
import { generateSecret } from '@vates/otp'
|
||||
|
||||
const secret = generateSecret()
|
||||
// 'OJOKA65RY5FQQ2RYWVKD5Y3YG5CSHGYH'
|
||||
```
|
||||
|
||||
#### HOTP
|
||||
|
||||
> This is likely not what you want to use, see TOTP below instead.
|
||||
|
||||
```js
|
||||
import { generateHotp, generateHotpUri, verifyHotp } from '@vates/otp'
|
||||
|
||||
// a sequence number, see HOTP specification
|
||||
const counter = 0
|
||||
|
||||
// generate a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
const token = await generateHotp({ counter, secret })
|
||||
// '239988'
|
||||
|
||||
// verify a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
const isValid = await verifyHotp(token, { counter, secret })
|
||||
// true
|
||||
|
||||
// generate a URI than can be displayed as a QR code to be used with Authy or Google Authenticator
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
const uri = generateHotpUri({ counter, label: 'account name', issuer: 'my app', secret })
|
||||
// 'otpauth://hotp/my%20app:account%20name?counter=0&issuer=my%20app&secret=OJOKA65RY5FQQ2RYWVKD5Y3YG5CSHGYH'
|
||||
```
|
||||
|
||||
Optional params and their default values:
|
||||
|
||||
- `digits = 6`: length of the token, avoid using it because not compatible with Google Authenticator
|
||||
|
||||
#### TOTP
|
||||
|
||||
```js
|
||||
import { generateTotp, generateTotpUri, verifyTotp } from '@vates/otp'
|
||||
|
||||
// generate a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
// - period
|
||||
// - timestamp
|
||||
const token = await generateTotp({ secret })
|
||||
// '632869'
|
||||
|
||||
// verify a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
// - period
|
||||
// - timestamp
|
||||
// - window
|
||||
const isValid = await verifyTotp(token, { secret })
|
||||
// true
|
||||
|
||||
// generate a URI than can be displayed as a QR code to be used with Authy or Google Authenticator
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
// - period
|
||||
const uri = generateTotpUri({ label: 'account name', issuer: 'my app', secret })
|
||||
// 'otpauth://totp/my%20app:account%20name?issuer=my%20app&secret=OJOKA65RY5FQQ2RYWVKD5Y3YG5CSHGYH'
|
||||
```
|
||||
|
||||
Optional params and their default values:
|
||||
|
||||
- `digits = 6`: length of the token, avoid using it because not compatible with Google Authenticator
|
||||
- `period = 30`: number of seconds a token is valid
|
||||
- `timestamp = Date.now() / 1e3`: Unix timestamp, in seconds, when this token will be valid, default to now
|
||||
- `window = 1`: number of periods before and after `timestamp` for which the token is considered valid
|
||||
|
||||
#### Verification from URI
|
||||
|
||||
```js
|
||||
import { verifyFromUri } from '@vates/otp'
|
||||
|
||||
// Verify the token using all the information contained in the URI
|
||||
const isValid = await verifyFromUri(token, uri)
|
||||
// true
|
||||
```
|
||||
1
@vates/otp/.npmignore
Symbolic link
1
@vates/otp/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
163
@vates/otp/README.md
Normal file
163
@vates/otp/README.md
Normal file
@@ -0,0 +1,163 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/otp
|
||||
|
||||
[](https://npmjs.org/package/@vates/otp)  [](https://bundlephobia.com/result?p=@vates/otp) [](https://npmjs.org/package/@vates/otp)
|
||||
|
||||
> Minimal HTOP/TOTP implementation
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/otp):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/otp
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Usual workflow
|
||||
|
||||
> This section presents how this library should be used to implement a classic two factor authentification.
|
||||
|
||||
#### Setup
|
||||
|
||||
```js
|
||||
import { generateSecret, generateTotp } from '@vates/otp'
|
||||
import QrCode from 'qrcode'
|
||||
|
||||
// Generates a secret that will be shared by both the service and the user:
|
||||
const secret = generateSecret()
|
||||
|
||||
// Stores the secret in the service:
|
||||
await currentUser.saveOtpSecret(secret)
|
||||
|
||||
// Generates an URI to present to the user
|
||||
const uri = generateTotpUri({ secret })
|
||||
|
||||
// Generates the QR code from the URI to make it easily importable in Authy or Google Authenticator
|
||||
const qr = await QrCode.toDataURL(uri)
|
||||
```
|
||||
|
||||
#### Authentication
|
||||
|
||||
```js
|
||||
import { verifyTotp } from '@vates/otp'
|
||||
|
||||
// Verifies a `token` entered by the user against a `secret` generated during setup.
|
||||
if (await verifyTotp(token, { secret })) {
|
||||
console.log('authenticated!')
|
||||
}
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
#### Secret
|
||||
|
||||
```js
|
||||
import { generateSecret } from '@vates/otp'
|
||||
|
||||
const secret = generateSecret()
|
||||
// 'OJOKA65RY5FQQ2RYWVKD5Y3YG5CSHGYH'
|
||||
```
|
||||
|
||||
#### HOTP
|
||||
|
||||
> This is likely not what you want to use, see TOTP below instead.
|
||||
|
||||
```js
|
||||
import { generateHotp, generateHotpUri, verifyHotp } from '@vates/otp'
|
||||
|
||||
// a sequence number, see HOTP specification
|
||||
const counter = 0
|
||||
|
||||
// generate a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
const token = await generateHotp({ counter, secret })
|
||||
// '239988'
|
||||
|
||||
// verify a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
const isValid = await verifyHotp(token, { counter, secret })
|
||||
// true
|
||||
|
||||
// generate a URI than can be displayed as a QR code to be used with Authy or Google Authenticator
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
const uri = generateHotpUri({ counter, label: 'account name', issuer: 'my app', secret })
|
||||
// 'otpauth://hotp/my%20app:account%20name?counter=0&issuer=my%20app&secret=OJOKA65RY5FQQ2RYWVKD5Y3YG5CSHGYH'
|
||||
```
|
||||
|
||||
Optional params and their default values:
|
||||
|
||||
- `digits = 6`: length of the token, avoid using it because not compatible with Google Authenticator
|
||||
|
||||
#### TOTP
|
||||
|
||||
```js
|
||||
import { generateTotp, generateTotpUri, verifyTotp } from '@vates/otp'
|
||||
|
||||
// generate a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
// - period
|
||||
// - timestamp
|
||||
const token = await generateTotp({ secret })
|
||||
// '632869'
|
||||
|
||||
// verify a token
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
// - period
|
||||
// - timestamp
|
||||
// - window
|
||||
const isValid = await verifyTotp(token, { secret })
|
||||
// true
|
||||
|
||||
// generate a URI than can be displayed as a QR code to be used with Authy or Google Authenticator
|
||||
//
|
||||
// optional params:
|
||||
// - digits
|
||||
// - period
|
||||
const uri = generateTotpUri({ label: 'account name', issuer: 'my app', secret })
|
||||
// 'otpauth://totp/my%20app:account%20name?issuer=my%20app&secret=OJOKA65RY5FQQ2RYWVKD5Y3YG5CSHGYH'
|
||||
```
|
||||
|
||||
Optional params and their default values:
|
||||
|
||||
- `digits = 6`: length of the token, avoid using it because not compatible with Google Authenticator
|
||||
- `period = 30`: number of seconds a token is valid
|
||||
- `timestamp = Date.now() / 1e3`: Unix timestamp, in seconds, when this token will be valid, default to now
|
||||
- `window = 1`: number of periods before and after `timestamp` for which the token is considered valid
|
||||
|
||||
#### Verification from URI
|
||||
|
||||
```js
|
||||
import { verifyFromUri } from '@vates/otp'
|
||||
|
||||
// Verify the token using all the information contained in the URI
|
||||
const isValid = await verifyFromUri(token, uri)
|
||||
// true
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
111
@vates/otp/index.mjs
Normal file
111
@vates/otp/index.mjs
Normal file
@@ -0,0 +1,111 @@
|
||||
import { base32 } from 'rfc4648'
|
||||
import { webcrypto } from 'node:crypto'
|
||||
|
||||
const { subtle } = webcrypto
|
||||
|
||||
function assert(name, value) {
|
||||
if (!value) {
|
||||
throw new TypeError('invalid value for param ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
function generateUri(protocol, label, params) {
|
||||
assert('label', typeof label === 'string')
|
||||
assert('secret', typeof params.secret === 'string')
|
||||
|
||||
let path = encodeURIComponent(label)
|
||||
|
||||
const { issuer } = params
|
||||
if (issuer !== undefined) {
|
||||
path = encodeURIComponent(issuer) + ':' + path
|
||||
}
|
||||
|
||||
const query = Object.entries(params)
|
||||
.filter(_ => _[1] !== undefined)
|
||||
.map(([key, value]) => key + '=' + encodeURIComponent(value))
|
||||
.join('&')
|
||||
|
||||
return `otpauth://${protocol}/${path}?${query}`
|
||||
}
|
||||
|
||||
export function generateSecret() {
|
||||
// https://www.rfc-editor.org/rfc/rfc4226 recommends 160 bits (i.e. 20 bytes)
|
||||
const data = new Uint8Array(20)
|
||||
webcrypto.getRandomValues(data)
|
||||
return base32.stringify(data, { pad: false })
|
||||
}
|
||||
|
||||
const DIGITS = 6
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc4226
|
||||
export async function generateHotp({ counter, digits = DIGITS, secret }) {
|
||||
const data = new Uint8Array(8)
|
||||
new DataView(data.buffer).setBigInt64(0, BigInt(counter), false)
|
||||
|
||||
const key = await subtle.importKey(
|
||||
'raw',
|
||||
base32.parse(secret, { loose: true }),
|
||||
{ name: 'HMAC', hash: 'SHA-1' },
|
||||
false,
|
||||
['sign', 'verify']
|
||||
)
|
||||
const digest = new DataView(await subtle.sign('HMAC', key, data))
|
||||
|
||||
const offset = digest.getUint8(digest.byteLength - 1) & 0xf
|
||||
const p = digest.getUint32(offset) & 0x7f_ff_ff_ff
|
||||
|
||||
return String(p % Math.pow(10, digits)).padStart(digits, '0')
|
||||
}
|
||||
|
||||
export function generateHotpUri({ counter, digits, issuer, label, secret }) {
|
||||
assert('counter', typeof counter === 'number')
|
||||
return generateUri('hotp', label, { counter, digits, issuer, secret })
|
||||
}
|
||||
|
||||
export async function verifyHotp(token, opts) {
|
||||
return token === (await generateHotp(opts))
|
||||
}
|
||||
|
||||
function totpCounter(period = 30, timestamp = Math.floor(Date.now() / 1e3)) {
|
||||
return Math.floor(timestamp / period)
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc6238.html
|
||||
export async function generateTotp({ period, timestamp, ...opts }) {
|
||||
opts.counter = totpCounter(period, timestamp)
|
||||
return await generateHotp(opts)
|
||||
}
|
||||
|
||||
export function generateTotpUri({ digits, issuer, label, period, secret }) {
|
||||
return generateUri('totp', label, { digits, issuer, period, secret })
|
||||
}
|
||||
|
||||
export async function verifyTotp(token, { period, timestamp, window = 1, ...opts }) {
|
||||
const counter = totpCounter(period, timestamp)
|
||||
const end = counter + window
|
||||
opts.counter = counter - window
|
||||
while (opts.counter <= end) {
|
||||
if (token === (await generateHotp(opts))) {
|
||||
return true
|
||||
}
|
||||
opts.counter += 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export async function verifyFromUri(token, uri) {
|
||||
const url = new URL(uri)
|
||||
assert('protocol', url.protocol === 'otpauth:')
|
||||
|
||||
const { host } = url
|
||||
const opts = Object.fromEntries(url.searchParams.entries())
|
||||
if (host === 'hotp') {
|
||||
return await verifyHotp(token, opts)
|
||||
}
|
||||
if (host === 'totp') {
|
||||
return await verifyTotp(token, opts)
|
||||
}
|
||||
|
||||
assert('host', false)
|
||||
}
|
||||
112
@vates/otp/index.spec.mjs
Normal file
112
@vates/otp/index.spec.mjs
Normal file
@@ -0,0 +1,112 @@
|
||||
import { strict as assert } from 'node:assert'
|
||||
import { describe, it } from 'tap/mocha'
|
||||
|
||||
import {
|
||||
generateHotp,
|
||||
generateHotpUri,
|
||||
generateSecret,
|
||||
generateTotp,
|
||||
generateTotpUri,
|
||||
verifyHotp,
|
||||
verifyTotp,
|
||||
} from './index.mjs'
|
||||
|
||||
describe('generateSecret', function () {
|
||||
it('generates a string of 32 chars', async function () {
|
||||
const secret = generateSecret()
|
||||
assert.equal(typeof secret, 'string')
|
||||
assert.equal(secret.length, 32)
|
||||
})
|
||||
|
||||
it('generates a different secret at each call', async function () {
|
||||
assert.notEqual(generateSecret(), generateSecret())
|
||||
})
|
||||
})
|
||||
|
||||
describe('HOTP', function () {
|
||||
it('generate and verify valid tokens', async function () {
|
||||
for (const [token, opts] of Object.entries({
|
||||
382752: {
|
||||
counter: -3088,
|
||||
secret: 'PJYFSZ3JNVXVQMZXOB2EQYJSKB2HE6TB',
|
||||
},
|
||||
163376: {
|
||||
counter: 30598,
|
||||
secret: 'GBUDQZ3UKZZGIMRLNVYXA33GMFMEGQKN',
|
||||
},
|
||||
})) {
|
||||
assert.equal(await generateHotp(opts), token)
|
||||
assert(await verifyHotp(token, opts))
|
||||
}
|
||||
})
|
||||
|
||||
describe('generateHotpUri', function () {
|
||||
const opts = {
|
||||
counter: 59732,
|
||||
label: 'the label',
|
||||
secret: 'OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX',
|
||||
}
|
||||
|
||||
Object.entries({
|
||||
'without optional params': [
|
||||
opts,
|
||||
'otpauth://hotp/the%20label?counter=59732&secret=OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX',
|
||||
],
|
||||
'with issuer': [
|
||||
{ ...opts, issuer: 'the issuer' },
|
||||
'otpauth://hotp/the%20issuer:the%20label?counter=59732&issuer=the%20issuer&secret=OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX',
|
||||
],
|
||||
'with digits': [
|
||||
{ ...opts, digits: 7 },
|
||||
'otpauth://hotp/the%20label?counter=59732&digits=7&secret=OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX',
|
||||
],
|
||||
}).forEach(([title, [opts, uri]]) => {
|
||||
it(title, async function () {
|
||||
assert.strictEqual(generateHotpUri(opts), uri)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('TOTP', function () {
|
||||
Object.entries({
|
||||
'033702': {
|
||||
secret: 'PJYFSZ3JNVXVQMZXOB2EQYJSKB2HE6TB',
|
||||
timestamp: 1665416296,
|
||||
period: 30,
|
||||
},
|
||||
107250: {
|
||||
secret: 'GBUDQZ3UKZZGIMRLNVYXA33GMFMEGQKN',
|
||||
timestamp: 1665416674,
|
||||
period: 60,
|
||||
},
|
||||
}).forEach(([token, opts]) => {
|
||||
it('works', async function () {
|
||||
assert.equal(await generateTotp(opts), token)
|
||||
assert(await verifyTotp(token, opts))
|
||||
})
|
||||
})
|
||||
|
||||
describe('generateHotpUri', function () {
|
||||
const opts = {
|
||||
label: 'the label',
|
||||
secret: 'OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX',
|
||||
}
|
||||
|
||||
Object.entries({
|
||||
'without optional params': [opts, 'otpauth://totp/the%20label?secret=OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX'],
|
||||
'with issuer': [
|
||||
{ ...opts, issuer: 'the issuer' },
|
||||
'otpauth://totp/the%20issuer:the%20label?issuer=the%20issuer&secret=OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX',
|
||||
],
|
||||
'with digits': [
|
||||
{ ...opts, digits: 7 },
|
||||
'otpauth://totp/the%20label?digits=7&secret=OGK45BBZAIGNGELHZPXYKN4GUVWWO6YX',
|
||||
],
|
||||
}).forEach(([title, [opts, uri]]) => {
|
||||
it(title, async function () {
|
||||
assert.strictEqual(generateTotpUri(opts), uri)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
39
@vates/otp/package.json
Normal file
39
@vates/otp/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/otp",
|
||||
"description": "Minimal HTOP/TOTP implementation",
|
||||
"keywords": [
|
||||
"2fa",
|
||||
"authenticator",
|
||||
"hotp",
|
||||
"otp",
|
||||
"totp"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/otp",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"main": "index.mjs",
|
||||
"repository": {
|
||||
"directory": "@vates/otp",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"node": ">=15"
|
||||
},
|
||||
"dependencies": {
|
||||
"rfc4648": "^1.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public",
|
||||
"test": "tap"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tap": "^16.3.0"
|
||||
}
|
||||
}
|
||||
14
@vates/parse-duration/.USAGE.md
Normal file
14
@vates/parse-duration/.USAGE.md
Normal file
@@ -0,0 +1,14 @@
|
||||
`ms` without magic: always parse a duration and throws if invalid.
|
||||
|
||||
```js
|
||||
import { parseDuration } from '@vates/parse-duration'
|
||||
|
||||
parseDuration('2 days')
|
||||
// 172800000
|
||||
|
||||
parseDuration(172800000)
|
||||
// 172800000
|
||||
|
||||
parseDuration(undefined)
|
||||
// throws TypeError('not a valid duration: undefined')
|
||||
```
|
||||
1
@vates/parse-duration/.npmignore
Symbolic link
1
@vates/parse-duration/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
47
@vates/parse-duration/README.md
Normal file
47
@vates/parse-duration/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/parse-duration
|
||||
|
||||
[](https://npmjs.org/package/@vates/parse-duration)  [](https://bundlephobia.com/result?p=@vates/parse-duration) [](https://npmjs.org/package/@vates/parse-duration)
|
||||
|
||||
> Small wrapper around ms to parse a duration
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/parse-duration):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/parse-duration
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`ms` without magic: always parse a duration and throws if invalid.
|
||||
|
||||
```js
|
||||
import { parseDuration } from '@vates/parse-duration'
|
||||
|
||||
parseDuration('2 days')
|
||||
// 172800000
|
||||
|
||||
parseDuration(172800000)
|
||||
// 172800000
|
||||
|
||||
parseDuration(undefined)
|
||||
// throws TypeError('not a valid duration: undefined')
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
14
@vates/parse-duration/index.js
Normal file
14
@vates/parse-duration/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
const ms = require('ms')
|
||||
|
||||
exports.parseDuration = value => {
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
const duration = ms(value)
|
||||
if (duration === undefined) {
|
||||
throw new TypeError(`not a valid duration: ${value}`)
|
||||
}
|
||||
return duration
|
||||
}
|
||||
32
@vates/parse-duration/package.json
Normal file
32
@vates/parse-duration/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@vates/parse-duration",
|
||||
"description": "Small wrapper around ms to parse a duration",
|
||||
"keywords": [
|
||||
"duration",
|
||||
"ms",
|
||||
"parse"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/parse-duration",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@vates/parse-duration",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Vates SAS",
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "ISC",
|
||||
"version": "0.1.1",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": "^2.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
}
|
||||
}
|
||||
72
@vates/predicates/.USAGE.md
Normal file
72
@vates/predicates/.USAGE.md
Normal file
@@ -0,0 +1,72 @@
|
||||
`undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition:
|
||||
|
||||
```js
|
||||
const compositePredicate = not(every(undefined, some(not(predicate2), undefined)))
|
||||
|
||||
// ends up as
|
||||
|
||||
const compositePredicate = predicate2
|
||||
```
|
||||
|
||||
Predicates can also be passed wrapped in an array:
|
||||
|
||||
```js
|
||||
const compositePredicate = every([predicate1, some([predicate2, predicate3])])
|
||||
```
|
||||
|
||||
`this` and all arguments are passed to the nested predicates.
|
||||
|
||||
### `every(predicates)`
|
||||
|
||||
> Returns a predicate that returns `true` iff every predicate returns `true`.
|
||||
|
||||
```js
|
||||
const isBetween3And7 = every(
|
||||
n => n >= 3,
|
||||
n => n <= 7
|
||||
)
|
||||
|
||||
isBetween3And10(0)
|
||||
// → false
|
||||
|
||||
isBetween3And10(5)
|
||||
// → true
|
||||
|
||||
isBetween3And10(10)
|
||||
// → false
|
||||
```
|
||||
|
||||
### `not(predicate)`
|
||||
|
||||
> Returns a predicate that returns the negation of the predicate.
|
||||
|
||||
```js
|
||||
const isEven = n => n % 2 === 0
|
||||
const isOdd = not(isEven)
|
||||
|
||||
isOdd(1)
|
||||
// true
|
||||
|
||||
isOdd(2)
|
||||
// false
|
||||
```
|
||||
|
||||
### `some(predicates)`
|
||||
|
||||
> Returns a predicate that returns `true` iff some predicate returns `true`.
|
||||
|
||||
```js
|
||||
const isAliceOrBob = some(
|
||||
name => name === 'Alice',
|
||||
name => name === 'Bob'
|
||||
)
|
||||
|
||||
isAliceOrBob('Alice')
|
||||
// → true
|
||||
|
||||
isAliceOrBob('Bob')
|
||||
// → true
|
||||
|
||||
isAliceOrBob('Oscar')
|
||||
// → false
|
||||
```
|
||||
1
@vates/predicates/.npmignore
Symbolic link
1
@vates/predicates/.npmignore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../scripts/npmignore
|
||||
105
@vates/predicates/README.md
Normal file
105
@vates/predicates/README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
|
||||
|
||||
# @vates/predicates
|
||||
|
||||
[](https://npmjs.org/package/@vates/predicates)  [](https://bundlephobia.com/result?p=@vates/predicates) [](https://npmjs.org/package/@vates/predicates)
|
||||
|
||||
> Utilities to compose predicates
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/@vates/predicates):
|
||||
|
||||
```sh
|
||||
npm install --save @vates/predicates
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition:
|
||||
|
||||
```js
|
||||
const compositePredicate = not(every(undefined, some(not(predicate2), undefined)))
|
||||
|
||||
// ends up as
|
||||
|
||||
const compositePredicate = predicate2
|
||||
```
|
||||
|
||||
Predicates can also be passed wrapped in an array:
|
||||
|
||||
```js
|
||||
const compositePredicate = every([predicate1, some([predicate2, predicate3])])
|
||||
```
|
||||
|
||||
`this` and all arguments are passed to the nested predicates.
|
||||
|
||||
### `every(predicates)`
|
||||
|
||||
> Returns a predicate that returns `true` iff every predicate returns `true`.
|
||||
|
||||
```js
|
||||
const isBetween3And7 = every(
|
||||
n => n >= 3,
|
||||
n => n <= 7
|
||||
)
|
||||
|
||||
isBetween3And10(0)
|
||||
// → false
|
||||
|
||||
isBetween3And10(5)
|
||||
// → true
|
||||
|
||||
isBetween3And10(10)
|
||||
// → false
|
||||
```
|
||||
|
||||
### `not(predicate)`
|
||||
|
||||
> Returns a predicate that returns the negation of the predicate.
|
||||
|
||||
```js
|
||||
const isEven = n => n % 2 === 0
|
||||
const isOdd = not(isEven)
|
||||
|
||||
isOdd(1)
|
||||
// true
|
||||
|
||||
isOdd(2)
|
||||
// false
|
||||
```
|
||||
|
||||
### `some(predicates)`
|
||||
|
||||
> Returns a predicate that returns `true` iff some predicate returns `true`.
|
||||
|
||||
```js
|
||||
const isAliceOrBob = some(
|
||||
name => name === 'Alice',
|
||||
name => name === 'Bob'
|
||||
)
|
||||
|
||||
isAliceOrBob('Alice')
|
||||
// → true
|
||||
|
||||
isAliceOrBob('Bob')
|
||||
// → true
|
||||
|
||||
isAliceOrBob('Oscar')
|
||||
// → false
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are _very_ welcomed, either on the documentation or on
|
||||
the code.
|
||||
|
||||
You may:
|
||||
|
||||
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
|
||||
you've encountered;
|
||||
- fork and create a pull request.
|
||||
|
||||
## License
|
||||
|
||||
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user