Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ac7dfb677 | ||
|
|
683e939b0e | ||
|
|
772e02a738 | ||
|
|
f3242f9150 | ||
|
|
fd8a23dd76 | ||
|
|
5319726cba | ||
|
|
e153cf1be6 | ||
|
|
a9a82079ac | ||
|
|
a0c3ad7256 | ||
|
|
ce4aa12a34 | ||
|
|
ad2a4df70b | ||
|
|
13633f6582 | ||
|
|
70fc371644 | ||
|
|
5640eeb86b | ||
|
|
634a0a2af5 | ||
|
|
b16a4effd3 | ||
|
|
da1cea8263 | ||
|
|
8f60066cc8 | ||
|
|
fa9c3d317e | ||
|
|
9f21e94712 | ||
|
|
b6a3f0f72a | ||
|
|
d50b3a5625 | ||
|
|
8a920faaf0 | ||
|
|
42cce22e99 | ||
|
|
d9f6426d45 | ||
|
|
06a6496403 | ||
|
|
1a6c4da395 | ||
|
|
ffed3104aa | ||
|
|
f62f2e288e | ||
|
|
ef9b486e12 | ||
|
|
5ebca6ff6f | ||
|
|
c626267977 | ||
|
|
2666f6a0b6 | ||
|
|
d12c38e885 | ||
|
|
d6fbff246b | ||
|
|
ba4ed746e0 | ||
|
|
29c9796439 | ||
|
|
c7c74c81cb | ||
|
|
eb59d36c32 | ||
|
|
9521bc4ec8 | ||
|
|
2d6141af0f | ||
|
|
c8550550ab | ||
|
|
8121b12386 | ||
|
|
b4c109a28d | ||
|
|
5ac06afc3f | ||
|
|
9c85b1c119 | ||
|
|
a99887a052 | ||
|
|
bc4d668abe | ||
|
|
38efef0685 | ||
|
|
cab70b5955 | ||
|
|
6f1c21b14c | ||
|
|
94f77177bd | ||
|
|
3ed91b26a0 | ||
|
|
b1b0a3dfe6 | ||
|
|
6500019835 | ||
|
|
426cb61277 | ||
|
|
d53f532412 | ||
|
|
d19f79858b | ||
|
|
140b008447 | ||
|
|
b0dd8e4635 | ||
|
|
c716e8e5b4 | ||
|
|
6c314cb449 | ||
|
|
0c8ead139f | ||
|
|
b708f28577 | ||
|
|
beded286a2 | ||
|
|
4c5ce5e627 | ||
|
|
f72447f1e2 | ||
|
|
540139644c | ||
|
|
3d782582e5 | ||
|
|
e3400ec5ab | ||
|
|
937a8a6b0d | ||
|
|
23bdd10ce5 | ||
|
|
920971ee98 | ||
|
|
c5d21ee09d | ||
|
|
b54cbc96ad | ||
|
|
694dadb203 | ||
|
|
c75800514f | ||
|
|
7e28dee0ab | ||
|
|
05be4c1349 | ||
|
|
dad2d2d05e | ||
|
|
7c85310318 | ||
|
|
e9580ba85e | ||
|
|
efbf868a0a | ||
|
|
d10bb377b0 | ||
|
|
34c1adeba2 | ||
|
|
85744200f7 | ||
|
|
ec64d4f045 | ||
|
|
d30091db47 | ||
|
|
cdaf44f8eb | ||
|
|
9a91cc2ea8 | ||
|
|
a8fb814855 | ||
|
|
01d1271562 | ||
|
|
3929f0ef58 | ||
|
|
447dabf853 | ||
|
|
3deaf18902 | ||
|
|
c468468427 | ||
|
|
2b401ebb5e | ||
|
|
9da46928e6 | ||
|
|
9971d6f823 | ||
|
|
6bd7023776 | ||
|
|
3749c4c148 | ||
|
|
92dd707811 | ||
|
|
74178b8437 | ||
|
|
ca26e39668 | ||
|
|
864d6b139b | ||
|
|
298546829a | ||
|
|
22fa670d9b | ||
|
|
c48961cc89 | ||
|
|
23a353d68a | ||
|
|
ca587da91f | ||
|
|
ebe73db6ae | ||
|
|
4fdd4d777d | ||
|
|
ab9581ea12 | ||
|
|
48bb575b8f | ||
|
|
ea9e286de4 | ||
|
|
aa62348503 | ||
|
|
a5f0674e9d | ||
|
|
d0255fe4e1 | ||
|
|
d66b40ad9b | ||
|
|
9eac6cdc8b | ||
|
|
f6a29597af | ||
|
|
8496c05488 | ||
|
|
6ca69fee1b | ||
|
|
7af9aac902 | ||
|
|
eca47f14f9 | ||
|
|
ecd573ea37 | ||
|
|
ae589f7af1 | ||
|
|
13c349b996 | ||
|
|
cb22248fc8 | ||
|
|
731ce40850 | ||
|
|
c5601a5c63 | ||
|
|
f4aa98f362 | ||
|
|
1dcfc67e0a | ||
|
|
8f554706bc | ||
|
|
bf5006c4fe | ||
|
|
af0903b126 | ||
|
|
595443bccf | ||
|
|
8e03ccf337 | ||
|
|
22e3a3e6db | ||
|
|
d31aaa28e7 | ||
|
|
63eb2c3589 | ||
|
|
c6d9f5a824 | ||
|
|
e15198d944 | ||
|
|
5486b80c67 | ||
|
|
4994b8256b | ||
|
|
8d0db2b74a | ||
|
|
8e9b783b06 | ||
|
|
399c31f34c | ||
|
|
e0b8f7d796 | ||
|
|
7818575115 | ||
|
|
e1dab6ac31 | ||
|
|
ad2e8bdd6c | ||
|
|
2feeb28999 | ||
|
|
a44c859414 | ||
|
|
5a815ca619 | ||
|
|
f768ff20ea | ||
|
|
1dae46b2e7 | ||
|
|
114ea66899 | ||
|
|
0762ff8240 | ||
|
|
8689672e47 | ||
|
|
6de0c07549 | ||
|
|
8c81b3d60d | ||
|
|
88b4a1a243 | ||
|
|
77c6bead71 | ||
|
|
55199277cb | ||
|
|
bdddd84c66 | ||
|
|
8b338f0288 | ||
|
|
8c2ff4d2e9 | ||
|
|
1f4b577dcb | ||
|
|
9861db3c26 | ||
|
|
5f1df8a0a0 | ||
|
|
92eb0a4be2 | ||
|
|
8815dca31a | ||
|
|
790d047692 | ||
|
|
46c54fb22d | ||
|
|
2bb22a3dd7 | ||
|
|
220370cc18 | ||
|
|
d0fec6e30e | ||
|
|
ea85879cfc | ||
|
|
6d358a565f | ||
|
|
6d6e8757e9 | ||
|
|
7a4b568ac7 | ||
|
|
fb266d548c | ||
|
|
c132f6e73c | ||
|
|
3c9105c071 | ||
|
|
38843329a2 | ||
|
|
f540f052ba | ||
|
|
24d1764310 | ||
|
|
e5e231f93a | ||
|
|
5d2e70ed8d | ||
|
|
58731f1448 | ||
|
|
79f242c4aa | ||
|
|
35fd198ceb | ||
|
|
9a7a0d5c4e | ||
|
|
c9ef425152 | ||
|
|
960cf319f6 | ||
|
|
9fd8e7ef54 | ||
|
|
a1494be628 | ||
|
|
52dafcb672 | ||
|
|
fb80069d4e | ||
|
|
4c7f92f035 | ||
|
|
0dd0fc8592 | ||
|
|
ffd4352106 | ||
|
|
939a6fa83d | ||
|
|
aca197b1bd | ||
|
|
9bd581477f | ||
|
|
2a2364b52f | ||
|
|
b054d5a6b2 | ||
|
|
7503a7d055 | ||
|
|
bbd25cd42f | ||
|
|
ed248f583f | ||
|
|
2dda76a470 | ||
|
|
e0989fffe3 | ||
|
|
50aa6f5c17 | ||
|
|
318103b745 | ||
|
|
4438a6719f | ||
|
|
f4c3bc7eba | ||
|
|
a3ba4ddb83 | ||
|
|
616c7c73fb | ||
|
|
38ad758110 | ||
|
|
b15c8b056f | ||
|
|
0b44a649b7 | ||
|
|
21aef6a722 | ||
|
|
ea5ab02d70 | ||
|
|
83cf084ee6 | ||
|
|
021097ad44 | ||
|
|
4e513cbb9f | ||
|
|
18dcd341c7 | ||
|
|
5b245a3ce1 | ||
|
|
9ccae001d3 | ||
|
|
e0f81f1e61 | ||
|
|
d5394f3206 | ||
|
|
f2b91e3011 | ||
|
|
a119c97a3b | ||
|
|
0e4f8718a2 | ||
|
|
93fd52954f | ||
|
|
ac215e200f | ||
|
|
d53cfbcc33 | ||
|
|
361bdf02a4 | ||
|
|
13903fe8b9 | ||
|
|
edec68e677 | ||
|
|
fe3310ddd1 | ||
|
|
2f37581aed | ||
|
|
9a13cd2279 | ||
|
|
1cdc4a0092 | ||
|
|
a128882d46 | ||
|
|
56bc05eba1 | ||
|
|
3693829e96 | ||
|
|
4c997982be | ||
|
|
31c4dc32d3 | ||
|
|
1394e7c15f | ||
|
|
4d6456dc4d | ||
|
|
3ff51b30f0 | ||
|
|
cdaf6a84e0 | ||
|
|
92356c0c6e | ||
|
|
da3d94816b | ||
|
|
0b4b9b405f | ||
|
|
7cde322926 | ||
|
|
44f81118ec | ||
|
|
be747bc8c7 | ||
|
|
5f44c569d0 | ||
|
|
21569bcc60 | ||
|
|
2d479239c1 | ||
|
|
8546275249 | ||
|
|
449181390e | ||
|
|
591f05d72f | ||
|
|
2459f97e4a | ||
|
|
0776ec1af9 | ||
|
|
b6f8e001f0 | ||
|
|
537b43317b | ||
|
|
445e98d455 | ||
|
|
7e731a8008 | ||
|
|
2c5231d2ab | ||
|
|
7d98438ee9 | ||
|
|
0416badcfd | ||
|
|
e62a921cf4 | ||
|
|
ec61bf8081 | ||
|
|
200c3d54b1 | ||
|
|
e1282bb870 | ||
|
|
b33a4d0b1d | ||
|
|
e68d174410 | ||
|
|
37849ecf36 | ||
|
|
5633502d8e | ||
|
|
9ae037debf | ||
|
|
e2ca9f5a7a | ||
|
|
0c5f6c1743 | ||
|
|
6193fb8d8d | ||
|
|
8c95e804e6 | ||
|
|
b5ea82caaa | ||
|
|
b00055a38f | ||
|
|
1be4dba3db | ||
|
|
64ad424a3a | ||
|
|
a93bf5d4aa | ||
|
|
3437d4e665 | ||
|
|
cdf83c7de6 | ||
|
|
5e837fd2d5 | ||
|
|
98e9d8ef24 | ||
|
|
8ad078622f | ||
|
|
eb7dc62e9b | ||
|
|
d205f0f345 | ||
|
|
791a6e9d55 | ||
|
|
460c77994f | ||
|
|
4c32cee466 | ||
|
|
43ebe5bf25 | ||
|
|
f4c62e59a9 | ||
|
|
42e754ab26 | ||
|
|
fb05d3ebd8 | ||
|
|
b69dda5b82 | ||
|
|
9623806206 | ||
|
|
b647ff5d49 | ||
|
|
0ffb39ed9f | ||
|
|
f7b3ea3547 | ||
|
|
965c28a008 | ||
|
|
1188c7567c | ||
|
|
74d1b352b1 | ||
|
|
a6e34cce5c | ||
|
|
0d8752fbea | ||
|
|
46246441aa | ||
|
|
75f75878a1 | ||
|
|
12277c5557 | ||
|
|
f8c4eb470f | ||
|
|
f0065ddc13 | ||
|
|
203a4b3a55 | ||
|
|
ab84787961 | ||
|
|
52d6d1c4bf | ||
|
|
5584577673 | ||
|
|
6e36c85299 | ||
|
|
ee06a9c825 | ||
|
|
6f8d424bed | ||
|
|
dad3918d44 | ||
|
|
932e2e7b50 | ||
|
|
df071e0815 | ||
|
|
168e6e8700 | ||
|
|
44b5d7427a | ||
|
|
ad6baf2b3b | ||
|
|
d108baccfc | ||
|
|
6634d868c9 | ||
|
|
a585fc7a9a | ||
|
|
6872bc264a | ||
|
|
f2ea864c0d | ||
|
|
353a7f94ad | ||
|
|
92d7380f83 | ||
|
|
1b70f9acfb | ||
|
|
2bcfb55db9 | ||
|
|
9987888edd | ||
|
|
4559c1b650 | ||
|
|
d0f8da1f81 | ||
|
|
00aa86cfb7 | ||
|
|
aee519aa75 | ||
|
|
14838fc87f | ||
|
|
ab95378f6e | ||
|
|
2f89d928b1 | ||
|
|
bf35789bd3 | ||
|
|
feb9c7aee1 | ||
|
|
a1ac704649 | ||
|
|
b5b527e01a | ||
|
|
e52093c6ed | ||
|
|
4f14891c84 | ||
|
|
d62a8afd3b | ||
|
|
4c1e090619 | ||
|
|
7dd96334c3 | ||
|
|
cbda678a65 | ||
|
|
c1eaed32bd | ||
|
|
5e12566a65 | ||
|
|
5e918cdddc | ||
|
|
f64f6bfd05 | ||
|
|
8f11f2536b | ||
|
|
de58328125 | ||
|
|
b4693b821e | ||
|
|
323945d760 | ||
|
|
2aa1142034 | ||
|
|
b3bde173ba | ||
|
|
3fbb9e8847 | ||
|
|
aa51b62134 | ||
|
|
aed6efe565 | ||
|
|
dd1d211d41 | ||
|
|
a7bf8008a7 | ||
|
|
bd5a7c0aa9 | ||
|
|
096b7ea142 | ||
|
|
f7a2dd14e3 | ||
|
|
137f4ee3c8 | ||
|
|
55acdc76b6 | ||
|
|
29ddca21b8 | ||
|
|
fdef23de89 | ||
|
|
1c9e77938e | ||
|
|
8894617709 | ||
|
|
645af9502d | ||
|
|
cc1dcc5b5b | ||
|
|
20b76f5afe | ||
|
|
70949a4cd9 | ||
|
|
99ba4f59c0 | ||
|
|
d5911ec942 | ||
|
|
73566b0583 | ||
|
|
1e80414b32 | ||
|
|
043b760cd6 | ||
|
|
ce633ec0c0 | ||
|
|
19f48b2ca3 | ||
|
|
36eab2e0f1 | ||
|
|
0577cdec76 | ||
|
|
67c4440a90 | ||
|
|
b2b2f63bf3 | ||
|
|
2de2a79e7c | ||
|
|
46b6c51f5d | ||
|
|
cc56fb9636 | ||
|
|
c1d71e5166 | ||
|
|
d595b6c899 | ||
|
|
e0f569229b | ||
|
|
f13a3ee18c | ||
|
|
feec5d96f3 | ||
|
|
99cb449ff7 | ||
|
|
896b45910e | ||
|
|
a8202081e9 | ||
|
|
2bb63c3775 | ||
|
|
9e74c137b7 | ||
|
|
40ee7c56d4 | ||
|
|
e0653c559b | ||
|
|
ed5ac46329 | ||
|
|
2bf38b1257 | ||
|
|
b96e998fc9 | ||
|
|
78bff7f0d8 | ||
|
|
ff9a7b3969 | ||
|
|
a43065ec1c | ||
|
|
c5ced9f36f | ||
|
|
cd71c53ff2 | ||
|
|
8c47612977 | ||
|
|
e150b15f00 | ||
|
|
10806da5c2 | ||
|
|
cbf5cc44f6 | ||
|
|
da1056d222 | ||
|
|
6ad42a5eaa | ||
|
|
bb9d99eefb | ||
|
|
929b020cf7 | ||
|
|
d269d27e2c | ||
|
|
056bd8666c | ||
|
|
5e25ee1368 | ||
|
|
b3675ec0f0 | ||
|
|
f74e2a6e2c | ||
|
|
a011278c41 | ||
|
|
f9e3656ce9 | ||
|
|
bfd0b32cb3 | ||
|
|
f454ef988f | ||
|
|
27582e7c19 | ||
|
|
e7cf4c99a5 | ||
|
|
bb0eacb108 | ||
|
|
92b4dccf78 | ||
|
|
87bb8e3e39 | ||
|
|
f28f113509 | ||
|
|
f0b2152130 | ||
|
|
cbc732e9dd | ||
|
|
36375ae0be | ||
|
|
6f0d5fd338 | ||
|
|
676c3e0ad0 | ||
|
|
6e77ea655f | ||
|
|
005690f838 | ||
|
|
f1b053871d | ||
|
|
8de98a55f8 | ||
|
|
3c794dfa4b | ||
|
|
995c79804a | ||
|
|
26887b3cfe | ||
|
|
07b2c646b1 | ||
|
|
9255d6dd98 | ||
|
|
6a8bbcad9d | ||
|
|
a380bccbf1 | ||
|
|
de4955eb60 | ||
|
|
7b3c39d275 | ||
|
|
9add540104 | ||
|
|
eafb21ddb9 | ||
|
|
45dc7c0b52 | ||
|
|
02c3fadfdd | ||
|
|
d1b8318dbe | ||
|
|
04c8d7d10d | ||
|
|
a7ef430ccd | ||
|
|
b178e6cec8 | ||
|
|
8e8292ecfb | ||
|
|
285e1b0fe4 | ||
|
|
798f9a7430 | ||
|
|
274fd96541 | ||
|
|
e665d61071 | ||
|
|
75fcdc1f93 | ||
|
|
14dc77b546 | ||
|
|
10503450e2 | ||
|
|
b5ce0843ec | ||
|
|
979acfdcda | ||
|
|
0255e651d3 | ||
|
|
a04b9d90dd | ||
|
|
3b3e2c21e2 | ||
|
|
54e31c10a3 | ||
|
|
54205e010e | ||
|
|
7a3fe9d74f | ||
|
|
d68a465d8f | ||
|
|
a0a8aae139 | ||
|
|
ef018aac59 | ||
|
|
2febf3ac3c | ||
|
|
41b3a99617 | ||
|
|
c8ae0de083 | ||
|
|
7b2acf162d | ||
|
|
5544f87cc2 | ||
|
|
9806415030 | ||
|
|
ff514b076b | ||
|
|
b5e5b9164f | ||
|
|
01ac40228d | ||
|
|
bc9526f008 | ||
|
|
ffb00c708f | ||
|
|
bd7834ca81 | ||
|
|
e374213553 | ||
|
|
12bb8203c2 | ||
|
|
af291d279d | ||
|
|
614be72a6e | ||
|
|
712607cb0c | ||
|
|
44ea5e329b | ||
|
|
cd02f6aec3 | ||
|
|
6eccb0ea6c | ||
|
|
cc07df5806 | ||
|
|
72dcd0d3ae | ||
|
|
bab8c8ea44 | ||
|
|
d8aaddfcca | ||
|
|
dd8cb5a77e | ||
|
|
2cc0e735fb | ||
|
|
b85a4dd719 | ||
|
|
afdec9a6ed | ||
|
|
823066e6b4 | ||
|
|
cd60c0bbc3 | ||
|
|
1889dd0090 | ||
|
|
10c364264c | ||
|
|
9558f2e480 | ||
|
|
f6e5f5d9e7 | ||
|
|
c0ba8bfae3 | ||
|
|
6b0a1d6e2f | ||
|
|
f2934472c8 | ||
|
|
cd96313857 | ||
|
|
fdc69b8a25 | ||
|
|
2595ea7f2e | ||
|
|
2973d7ee11 | ||
|
|
b6386ddbee | ||
|
|
21820dda9b | ||
|
|
521250b6cb | ||
|
|
0177c8bc0a | ||
|
|
2738dcfef3 | ||
|
|
2db364feb7 | ||
|
|
efb5c9111b | ||
|
|
f0becdcdc4 | ||
|
|
e6928fe66f | ||
|
|
537d8f107f | ||
|
|
305d5e6825 | ||
|
|
121129ffd2 | ||
|
|
1fd6c8a27e | ||
|
|
6ad1691750 | ||
|
|
ecf460d89b | ||
|
|
b1ec40d72a | ||
|
|
506ca35ba1 | ||
|
|
2ee68136f7 | ||
|
|
4a178fd300 | ||
|
|
baf742c980 | ||
|
|
0717df8f27 | ||
|
|
279848439a | ||
|
|
4b30eefd94 | ||
|
|
bda7c7c88e | ||
|
|
d70ff06891 | ||
|
|
d332a21266 | ||
|
|
299c1cdd06 | ||
|
|
4837cb353e | ||
|
|
66df38d81d | ||
|
|
70f8080837 | ||
|
|
ac14e8786d | ||
|
|
7b5070115e | ||
|
|
0fcd55ef41 | ||
|
|
5c4d7e29fd | ||
|
|
172c9dde69 | ||
|
|
e3c2452199 | ||
|
|
a3fd2ff03c | ||
|
|
06684a7da2 | ||
|
|
2c1cdf6c9c | ||
|
|
c571dc23e0 | ||
|
|
bf09c9b0b8 | ||
|
|
ec8a55be31 | ||
|
|
8a641cd6b7 | ||
|
|
cd30e05e2e | ||
|
|
66ef638086 | ||
|
|
57d6be8b84 | ||
|
|
d2e914cba9 | ||
|
|
949560826e | ||
|
|
df673ad29b | ||
|
|
8007762bd1 | ||
|
|
bdc9dacdce | ||
|
|
cc20e5a076 | ||
|
|
0d1dfb23b2 | ||
|
|
76bc6833a1 | ||
|
|
d6b01e8308 | ||
|
|
4c0cc5375b | ||
|
|
c807af02f4 | ||
|
|
1a48bb180f | ||
|
|
403880107d | ||
|
|
2d263734d8 | ||
|
|
9821f1ffc7 | ||
|
|
6d83163951 | ||
|
|
af9bb1c618 | ||
|
|
03b8e2875f | ||
|
|
b3c49c43ea | ||
|
|
c239830765 | ||
|
|
eeb74d8437 | ||
|
|
95bad02ce6 | ||
|
|
f84752aa9c | ||
|
|
e2578a4577 | ||
|
|
eb36be3688 | ||
|
|
95c2f4a74d | ||
|
|
ff9c659ada | ||
|
|
d9564f5f89 | ||
|
|
942cb82921 | ||
|
|
9918d20838 | ||
|
|
b1ea3af02a | ||
|
|
660218220f | ||
|
|
cf2326140e | ||
|
|
9df6b56ae9 | ||
|
|
9e09be0e5f | ||
|
|
e0bc36a376 | ||
|
|
fd770b7a25 | ||
|
|
0bdc5e6f2f | ||
|
|
a4d39d465d | ||
|
|
5541c7bf1f | ||
|
|
f888e6eb03 | ||
|
|
7a95600456 | ||
|
|
2a41ed1153 | ||
|
|
6226464ffc | ||
|
|
e8c491cf95 | ||
|
|
b6721152b2 | ||
|
|
22700688b6 | ||
|
|
97d6384115 | ||
|
|
a0a75e1e42 | ||
|
|
6a140b5d21 | ||
|
|
f5f0916135 | ||
|
|
7cd59dcf37 | ||
|
|
174b6246c7 | ||
|
|
2005a9f1bb | ||
|
|
c7059ae210 | ||
|
|
cf1e5aa429 | ||
|
|
44e3ba4d76 | ||
|
|
9ef4a14801 | ||
|
|
bb20027b42 | ||
|
|
c0d64cd2a1 | ||
|
|
70be930f8e | ||
|
|
b4f5f86544 | ||
|
|
bc6064bb2e | ||
|
|
7e9e8b8960 | ||
|
|
7f2b753295 | ||
|
|
d9d1305b7f | ||
|
|
e08c717a12 | ||
|
|
d65db18266 | ||
|
|
4410f32625 | ||
|
|
47b0c87d89 | ||
|
|
52570b48fb | ||
|
|
9a8c47024e | ||
|
|
4c20571268 | ||
|
|
af92c65f60 | ||
|
|
8bfa454df5 | ||
|
|
7e354df44c | ||
|
|
046e9fdf4c | ||
|
|
ebf2a0f115 | ||
|
|
b480cb8263 | ||
|
|
28de23b517 | ||
|
|
a3bb788716 | ||
|
|
71361799f7 | ||
|
|
fe509d2e86 | ||
|
|
618d418093 | ||
|
|
4d8e628402 | ||
|
|
4f27332479 | ||
|
|
83c2e84647 | ||
|
|
429665a1e0 | ||
|
|
a174c332f3 | ||
|
|
d26b668b38 | ||
|
|
a8bd89fafd | ||
|
|
33256f4c64 | ||
|
|
fc3b574425 | ||
|
|
ba10451293 | ||
|
|
4dee759674 | ||
|
|
02858bbacb | ||
|
|
69ac051786 | ||
|
|
2c998bbb2b | ||
|
|
a79823d3b1 | ||
|
|
30d029a223 | ||
|
|
9ce25b8e83 | ||
|
|
ac5710979a | ||
|
|
af749b57bc | ||
|
|
646125f039 | ||
|
|
53aade5322 | ||
|
|
be20491d2e | ||
|
|
7d3a5227d6 | ||
|
|
8daf6129d1 | ||
|
|
6cec91b09b | ||
|
|
bc0af27afa | ||
|
|
f4cdbfc99c | ||
|
|
aaa205b4e0 | ||
|
|
c8b49beba6 | ||
|
|
6c2694999c | ||
|
|
dfc3989a38 | ||
|
|
d1fdfb2496 | ||
|
|
7b594ca906 | ||
|
|
88bb34828b | ||
|
|
3086cbf4e7 | ||
|
|
d7a4dd40ea | ||
|
|
7238a698a1 | ||
|
|
a7c5332a99 | ||
|
|
e7b1d29d9d | ||
|
|
4f7e5bb732 | ||
|
|
77aaacc8b3 | ||
|
|
ddc7fe939d | ||
|
|
767be30476 | ||
|
|
400b0b5f0b | ||
|
|
b3d0bf5685 | ||
|
|
5cb8826f4e | ||
|
|
fd1b5cfd74 | ||
|
|
6ef055bb15 | ||
|
|
5293a21e03 | ||
|
|
01a90a4d75 | ||
|
|
5dc99ef5ad | ||
|
|
488e724745 | ||
|
|
126b8944c4 | ||
|
|
b6b6eeff53 | ||
|
|
44078a78ad | ||
|
|
39ece5a2c0 | ||
|
|
02fc874ef5 | ||
|
|
5f83fa53cf | ||
|
|
097abcd3eb | ||
|
|
1f4e60ce97 | ||
|
|
e22c53ea27 | ||
|
|
425234148e |
No files matched your search
@@ -2,55 +2,76 @@ name: Validate Monorepo
|
||||
on: pull_request
|
||||
jobs:
|
||||
|
||||
compliance:
|
||||
name: License Compliance
|
||||
dependencies:
|
||||
name: Install dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
id: cache
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- uses: actions/setup-node@v1
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
node-version: '16.x'
|
||||
- run: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
|
||||
compliance:
|
||||
name: License Compliance
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16.x'
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
- run: yarn run compliance:licenses
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
id: cache
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- uses: actions/setup-node@v1
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- run: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
node-version: '16.x'
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
- run: yarn lint
|
||||
|
||||
test:
|
||||
name: Test
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
id: cache
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- uses: actions/setup-node@v1
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- run: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
node-version: '16.x'
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
- run: yarn test
|
||||
@@ -23,8 +23,3 @@ yarn-error.log
|
||||
storybook-static
|
||||
**/.storybook/*.html
|
||||
.watchman-cookie*
|
||||
|
||||
# ember-try
|
||||
.node_modules.ember-try
|
||||
bower.json.ember-try
|
||||
package.json.ember-try
|
||||
@@ -15,7 +15,3 @@
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -19,15 +19,9 @@
|
||||
/.travis.yml
|
||||
/.watchmanconfig
|
||||
/bower.json
|
||||
/config/ember-try.js
|
||||
/CONTRIBUTING.md
|
||||
/ember-cli-build.js
|
||||
/testem.js
|
||||
/tests/
|
||||
/yarn.lock
|
||||
.gitkeep
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -14,8 +14,3 @@
|
||||
/coverage/
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Ability } from 'ember-can';
|
||||
|
||||
/**
|
||||
* Generic ability provides abilities common to all models in the API.
|
||||
*/
|
||||
export default class CollectionAbility extends Ability {
|
||||
// =permissions
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canList() {
|
||||
return this.hasAuthorizedCollectionAction('list');
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canCreate() {
|
||||
return this.hasAuthorizedCollectionAction('create');
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Returns true if the given action is contained in the ability model's
|
||||
* `authorized_collection_actions` array.
|
||||
* @param {string} action
|
||||
* @return {boolean}
|
||||
*/
|
||||
hasAuthorizedCollectionAction(action) {
|
||||
const authorized_collection_actions =
|
||||
(this.model || {}).authorized_collection_actions || {};
|
||||
const collection = this.collection || {};
|
||||
return authorized_collection_actions[collection]?.includes(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import ModelAbility from './model';
|
||||
|
||||
/**
|
||||
* Provides abilities for host catalogs.
|
||||
*/
|
||||
export default class HostCatalogAbility extends ModelAbility {
|
||||
// =permissions
|
||||
|
||||
/**
|
||||
* Only "known" host catalog types may be read.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canRead() {
|
||||
return !this.model.isUnknown && this.hasAuthorizedAction('read');
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,20 @@ export default class ModelAbility extends Ability {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canList() {
|
||||
return this.hasAuthorizedCollectionAction('list');
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canCreate() {
|
||||
return this.hasAuthorizedCollectionAction('create');
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
@@ -56,4 +70,17 @@ export default class ModelAbility extends Ability {
|
||||
hasAuthorizedAction(action) {
|
||||
return this.model.authorized_actions?.includes(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given action is contained in the ability model's
|
||||
* `authorized_collection_actions` array.
|
||||
* @param {string} action
|
||||
* @return {boolean}
|
||||
*/
|
||||
hasAuthorizedCollectionAction(action) {
|
||||
const authorized_collection_actions =
|
||||
(this.model || {}).authorized_collection_actions || {};
|
||||
const collection = this.collection || {};
|
||||
return authorized_collection_actions[collection]?.includes(action);
|
||||
}
|
||||
}
|
||||
@@ -263,14 +263,36 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
source: { pointer: '/data' },
|
||||
};
|
||||
// Normalize field-specific errors, if any.
|
||||
const errors = fieldErrors.map((error) => ({
|
||||
detail: error.description,
|
||||
// Ember Data cannot handle errors on nested attributes, so these are
|
||||
// flattened (e.g. `attributes.foobar` becomes `attributes_foobar`).
|
||||
// To capture these errors, add read-only fields on the model
|
||||
// corresponding to the underscored field name.
|
||||
source: { pointer: `/data/attributes/${error.name.replace('.', '_')}` },
|
||||
}));
|
||||
const errors = fieldErrors.flatMap((error) => {
|
||||
const newValue = [
|
||||
{
|
||||
detail: error.description,
|
||||
// Ember Data cannot handle errors on nested attributes, so these are
|
||||
// flattened (e.g. `attributes.foobar` becomes `attributes_foobar`).
|
||||
// To capture these errors, add read-only fields on the model
|
||||
// corresponding to the underscored field name.
|
||||
source: {
|
||||
pointer: `/data/attributes/${error.name.replace('.', '_')}`,
|
||||
},
|
||||
},
|
||||
];
|
||||
if (error.name.includes('attributes.')) {
|
||||
newValue.push({
|
||||
detail: error.description,
|
||||
// Going forward, nested attributes will be hoisted. Thus "attributes."
|
||||
// must be stripped from the error name key. Until all models are
|
||||
// transitioned away from model fragments, however, both this and the
|
||||
// prior error key mappings are necessary.
|
||||
source: {
|
||||
pointer: `/data/attributes/${error.name.replace(
|
||||
'attributes.',
|
||||
''
|
||||
)}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
// Return a list of JSON API errors rooted under the `errors` key.
|
||||
return {
|
||||
errors: [baseError, ...errors],
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import ApplicationAdapter from './application';
|
||||
|
||||
export default class HostCatalogAdapter extends ApplicationAdapter {
|
||||
/**
|
||||
* Overrides the default `buildURL` such that on _create_, URLs for records of
|
||||
* type `plugin` get an extra query parameter `?plugin_name`. All other
|
||||
* request types behave as normal.
|
||||
*
|
||||
* @override
|
||||
* @param {string} modelName
|
||||
* @param {string} id
|
||||
* @return {string} url
|
||||
* @return {string} requestType
|
||||
*/
|
||||
buildURL(modelName, id, snapshot, requestType) {
|
||||
let url = super.buildURL(...arguments);
|
||||
if (requestType === 'createRecord') {
|
||||
const pluginName = snapshot.attr('plugin')?.name;
|
||||
if (pluginName) url = `${url}?plugin_name=${pluginName}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@@ -39,13 +39,74 @@ export default class GeneratedHostCatalogModel extends BaseModel {
|
||||
})
|
||||
disabled;
|
||||
|
||||
@attr({
|
||||
description: 'Attributes specific to the catalog type',
|
||||
})
|
||||
attributes;
|
||||
|
||||
@attr('number', {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
|
||||
@attr({
|
||||
description: 'Plugin information for this resource.',
|
||||
readOnly: true,
|
||||
})
|
||||
plugin;
|
||||
|
||||
// AWS & Azure
|
||||
|
||||
@attr('boolean', {
|
||||
isNestedAttribute: true,
|
||||
description: '',
|
||||
})
|
||||
disable_credential_rotation;
|
||||
|
||||
// AWS specific
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description: '',
|
||||
})
|
||||
region;
|
||||
|
||||
@attr('string', {
|
||||
isNestedSecret: true,
|
||||
description: '',
|
||||
})
|
||||
access_key_id;
|
||||
|
||||
@attr('string', {
|
||||
isNestedSecret: true,
|
||||
description: '',
|
||||
})
|
||||
secret_access_key;
|
||||
|
||||
// Azure specific
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description: '',
|
||||
})
|
||||
tenant_id;
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description: '',
|
||||
})
|
||||
client_id;
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description: '',
|
||||
})
|
||||
subscription_id;
|
||||
|
||||
@attr('string', {
|
||||
isNestedSecret: true,
|
||||
description: '',
|
||||
})
|
||||
secret_id;
|
||||
|
||||
@attr('string', {
|
||||
isNestedSecret: true,
|
||||
description: '',
|
||||
})
|
||||
secret_value;
|
||||
}
|
||||
@@ -50,4 +50,23 @@ export default class GeneratedHostSetModel extends BaseModel {
|
||||
readOnly: true,
|
||||
})
|
||||
size;
|
||||
|
||||
@attr({
|
||||
description: 'Plugin information for this resource',
|
||||
readOnly: true,
|
||||
})
|
||||
plugin;
|
||||
|
||||
@attr('number', {
|
||||
description:
|
||||
"The number of seconds between the time boundary syncs the hosts in this set using this host set's plugin. If not provided a system determined default is used",
|
||||
})
|
||||
sync_interval_seconds;
|
||||
|
||||
// Azure specific
|
||||
@attr('string', {
|
||||
description: '',
|
||||
isNestedAttribute: true,
|
||||
})
|
||||
filter;
|
||||
}
|
||||
@@ -43,4 +43,10 @@ export default class GeneratedHostModel extends BaseModel {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
|
||||
@attr({
|
||||
description: 'Plugin information for this resource.',
|
||||
readOnly: true,
|
||||
})
|
||||
plugin;
|
||||
}
|
||||
@@ -61,6 +61,8 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel {
|
||||
*/
|
||||
@equal('is_primary', true) isPrimary;
|
||||
|
||||
options = options;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
|
||||
@@ -47,6 +47,10 @@ export default class BaseModel extends Model {
|
||||
})
|
||||
authorized_collection_actions;
|
||||
|
||||
get scopeModel() {
|
||||
const id = this.scopeID;
|
||||
return this.store.peekRecord('scope', id);
|
||||
}
|
||||
set scopeModel(model) {
|
||||
if (model) {
|
||||
const json = model.serialize();
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
export default class FragmentHostAttributesModel extends Fragment {
|
||||
@attr('string', {
|
||||
description: 'The address (DNS or IP name) used to reach the host',
|
||||
})
|
||||
address;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
export default class FragmentTargetAttributesModel extends Fragment {
|
||||
@attr('number', {
|
||||
description: 'The default port a target should use if present.',
|
||||
})
|
||||
default_port;
|
||||
}
|
||||
@@ -1,3 +1,71 @@
|
||||
import GeneratedHostCatalogModel from '../generated/models/host-catalog';
|
||||
|
||||
export default class HostCatalogModel extends GeneratedHostCatalogModel {}
|
||||
export const types = ['aws', 'azure'];
|
||||
|
||||
export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* True if the host catalog is static.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isStatic() {
|
||||
return this.type === 'static';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the host catalog is a plugin.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPlugin() {
|
||||
return this.type === 'plugin';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the host catalog is an unknown type.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isUnknown() {
|
||||
return this.isPlugin && !types.includes(this.plugin?.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if host catalog plugin type is AWS.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isAWS() {
|
||||
return this.compositeType === 'aws';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if host catalog plugin type is Azure.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isAzure() {
|
||||
return this.compositeType === 'azure';
|
||||
}
|
||||
|
||||
/**
|
||||
* If host catalog is a plugin return `plugin.name`,
|
||||
* otherwise return the host catalog type.
|
||||
* @type {string}
|
||||
*/
|
||||
get compositeType() {
|
||||
if (this.isUnknown) return 'unknown';
|
||||
if (this.isPlugin) return this.plugin.name;
|
||||
return 'static';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `type`. If type is different than `static`, sets `type` to `plugin`
|
||||
* and `plugin.name` to the specified type.
|
||||
*/
|
||||
set compositeType(type) {
|
||||
if (type === 'static') {
|
||||
this.type = 'static';
|
||||
} else {
|
||||
this.type = 'plugin';
|
||||
this.plugin = { name: type };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,72 @@
|
||||
import GeneratedHostSetModel from '../generated/models/host-set';
|
||||
import { fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
import { types } from './host-catalog';
|
||||
|
||||
export default class HostSetModel extends GeneratedHostSetModel {
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Returns if the host-set is static or not.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isStatic() {
|
||||
return this.type === 'static';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the host-set is plugin or not.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPlugin() {
|
||||
return this.type === 'plugin';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the host set is an unknown type.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isUnknown() {
|
||||
return this.isPlugin && !types.includes(this.plugin?.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a host-set plugin is AWS or not
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isAWS() {
|
||||
return this.compositeType === 'aws';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if a host-set plugin is Azure or not.
|
||||
*/
|
||||
get isAzure() {
|
||||
return this.compositeType === 'azure';
|
||||
}
|
||||
|
||||
/**
|
||||
* If a host-set is a plugin return its name,
|
||||
* otherwise returns the host-set type
|
||||
* @type {string}
|
||||
*/
|
||||
get compositeType() {
|
||||
if (this.isUnknown) return 'unknown';
|
||||
if (this.isPlugin) return this.plugin.name;
|
||||
return 'static';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type, if type is different than static, set plugin name to type
|
||||
*/
|
||||
set compositeType(type) {
|
||||
if (type === 'static') {
|
||||
this.type = 'static';
|
||||
} else {
|
||||
this.type = 'plugin';
|
||||
this.plugin = { name: type };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Host IDs are read-only under normal circumstances. But these can
|
||||
* be persisted via a dedicated call to `addHosts()`.
|
||||
@@ -14,6 +77,18 @@ export default class HostSetModel extends GeneratedHostSetModel {
|
||||
})
|
||||
host_ids;
|
||||
|
||||
@fragmentArray('fragment-string', {
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
preferred_endpoints;
|
||||
|
||||
// AWS specific
|
||||
@fragmentArray('fragment-string', {
|
||||
emptyArrayIfMissing: true,
|
||||
isNestedAttribute: true,
|
||||
})
|
||||
filters;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,87 @@
|
||||
import GeneratedHostModel from '../generated/models/host';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
export default class HostModel extends GeneratedHostModel {
|
||||
// =attributes
|
||||
|
||||
// Static specific fields
|
||||
@attr('string', {
|
||||
description: 'The address (DNS or IP name) used to reach the host',
|
||||
isNestedAttribute: true,
|
||||
})
|
||||
address;
|
||||
|
||||
// Plugin specific fields
|
||||
@attr({
|
||||
readOnly: true,
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
ip_addresses;
|
||||
|
||||
@attr('string', {
|
||||
description: '',
|
||||
readOnly: true,
|
||||
})
|
||||
external_id;
|
||||
|
||||
// Aws specific
|
||||
@attr({
|
||||
readOnly: true,
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
dns_names;
|
||||
|
||||
/**
|
||||
* Attributes of this resource, if any, represented as a JSON fragment.
|
||||
* @type {FragmentHostAttributesModel}
|
||||
* True if the host is static.
|
||||
* @type {boolean}
|
||||
*/
|
||||
@fragment('fragment-host-attributes', { defaultValue: {} }) attributes;
|
||||
get isStatic() {
|
||||
return this.type === 'static';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the host is plugin.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPlugin() {
|
||||
return this.type === 'plugin';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the host plugin type is AWS.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isAWS() {
|
||||
return this.compositeType === 'aws';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the host plugin type is Azure.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isAzure() {
|
||||
return this.compositeType === 'azure';
|
||||
}
|
||||
|
||||
/**
|
||||
* If host is a plugin return `plugin.name`,
|
||||
* otherwise return the host type.
|
||||
* @type {string}
|
||||
*/
|
||||
get compositeType() {
|
||||
return this.isPlugin ? this.plugin.name : this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `type`. If type is different than `static`, sets `type` to `plugin`
|
||||
* and `plugin.name` to the specified type.
|
||||
*/
|
||||
set compositeType(type) {
|
||||
if (type === 'static') {
|
||||
this.type = 'static';
|
||||
} else {
|
||||
this.type = 'plugin';
|
||||
this.plugin = { name: type };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import GeneratedTargetModel from '../generated/models/target';
|
||||
import { fragment, fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default class TargetModel extends GeneratedTargetModel {
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Attributes of this resource, if any, represented as a JSON fragment.
|
||||
* @type {FragmentTargetAttributesModel}
|
||||
*/
|
||||
@fragment('fragment-target-attributes', { defaultValue: {} }) attributes;
|
||||
@attr('number', {
|
||||
isNestedAttribute: true,
|
||||
description: 'The default port a target should use if present.',
|
||||
})
|
||||
default_port;
|
||||
|
||||
/**
|
||||
* @type {[FragmentHostSourceModel]}
|
||||
@@ -206,4 +207,20 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
removeCredentialSource(credentialLibraryID, options) {
|
||||
return this.removeCredentialSources([credentialLibraryID], options);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the target type is tcp.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isTCP() {
|
||||
return this.type === 'tcp';
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the target type is ssh.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isSSH() {
|
||||
return this.type === 'ssh';
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,20 @@ export default class ApplicationSerializer extends RESTSerializer {
|
||||
// Do not serialize `disabled` fields.
|
||||
// TODO: disabled is temporarily disabled
|
||||
if (key === 'disabled') delete json[key];
|
||||
// Push nested attributes down into the attributes key
|
||||
if (options.isNestedAttribute && json[key] !== undefined) {
|
||||
if (!json.attributes) json.attributes = {};
|
||||
json.attributes[key] = json[key];
|
||||
delete json[key];
|
||||
}
|
||||
// Push nested secrets down into the secrets key
|
||||
if (options.isNestedSecret) {
|
||||
if (json[key]) {
|
||||
if (!json.secrets) json.secrets = {};
|
||||
json.secrets[key] = json[key];
|
||||
}
|
||||
delete json[key];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -195,9 +209,63 @@ export default class ApplicationSerializer extends RESTSerializer {
|
||||
* @return {Object}
|
||||
*/
|
||||
normalize(typeClass, hash) {
|
||||
const normalizedHash = copy(hash, true);
|
||||
let normalizedHash = copy(hash, true);
|
||||
const scopeID = get(normalizedHash, 'scope.id');
|
||||
if (scopeID) normalizedHash.scope.scope_id = scopeID;
|
||||
normalizedHash = this.normalizeNestedAttributes(typeClass, normalizedHash);
|
||||
normalizedHash = this.normalizeNestedSecrets(typeClass, normalizedHash);
|
||||
return super.normalize(typeClass, normalizedHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain typed resources in the API have type-specific fields nested under
|
||||
* an `attributes` key in the payload
|
||||
* (e.g. `attributes: { fieldName: '123' }`). Since Ember Data lacks
|
||||
* first-class support for nested attributes, these fields must be hoisted up
|
||||
* into the main body of the payload.
|
||||
*
|
||||
* In order to annotate a field as being a nested attribute, add the key name
|
||||
* to the `nestedAttributes` field on the serializer:
|
||||
* `@attr('string', { isNestedAttribute: true }) fieldName;`
|
||||
*
|
||||
* @param {Model} typeClass
|
||||
* @param {Object} hash
|
||||
* @return {Object}
|
||||
*/
|
||||
normalizeNestedAttributes(typeClass, hash) {
|
||||
typeClass.attributes.forEach((attribute) => {
|
||||
const {
|
||||
name,
|
||||
options: { isNestedAttribute },
|
||||
} = attribute;
|
||||
const value = hash.attributes?.[name];
|
||||
const isUndefined = value === undefined;
|
||||
if (isNestedAttribute && !isUndefined) hash[name] = value;
|
||||
});
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* If an attribute is declared with the `isNestedSecret` option, it will
|
||||
* be explicitly unset in the incoming payload to `null`. This helps ensure
|
||||
* secret values do not linger in the UI.
|
||||
*
|
||||
* For example:
|
||||
* `@attr('string', { isNestedSecret: true }) superSecretShhh;`
|
||||
*
|
||||
* @param {Model} typeClass
|
||||
* @param {Object} hash
|
||||
* @return {Object}
|
||||
*/
|
||||
normalizeNestedSecrets(typeClass, hash) {
|
||||
typeClass.attributes.forEach((attribute) => {
|
||||
const {
|
||||
name,
|
||||
options: { isNestedSecret },
|
||||
} = attribute;
|
||||
const value = hash.secrets?.[name];
|
||||
if (isNestedSecret) hash[name] = value || null;
|
||||
});
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
const fieldsByType = {
|
||||
aws: [
|
||||
'disable_credential_rotation',
|
||||
'region',
|
||||
'access_key_id',
|
||||
'secret_access_key',
|
||||
],
|
||||
azure: [
|
||||
'disable_credential_rotation',
|
||||
'tenant_id',
|
||||
'client_id',
|
||||
'subscription_id',
|
||||
'secret_id',
|
||||
'secret_value',
|
||||
],
|
||||
};
|
||||
|
||||
export default class HostCatalogSerializer extends ApplicationSerializer {
|
||||
// =methods
|
||||
serialize(snapshot) {
|
||||
const serialized = super.serialize(...arguments);
|
||||
switch (snapshot.record.compositeType) {
|
||||
case 'static':
|
||||
return this.serializeStatic(...arguments);
|
||||
default:
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
serializeAttribute(snapshot, json, key, attribute) {
|
||||
const value = super.serializeAttribute(...arguments);
|
||||
const { isPlugin, compositeType } = snapshot.record;
|
||||
const { options } = attribute;
|
||||
// Nested conditionals for readability
|
||||
// This deletes any fields that don't belong to the record type
|
||||
if (isPlugin && options.isNestedAttribute && json.attributes) {
|
||||
// The key must be included in the fieldsByType list above
|
||||
if (!fieldsByType[compositeType].includes(key))
|
||||
delete json.attributes[key];
|
||||
}
|
||||
// This deletes any fields that don't belong to the record type
|
||||
if (isPlugin && options.isNestedSecret && json.secrets) {
|
||||
if (!fieldsByType[compositeType].includes(key)) delete json.secrets[key];
|
||||
}
|
||||
|
||||
// new one for isNestedSecret
|
||||
return value;
|
||||
}
|
||||
|
||||
serializeStatic() {
|
||||
const serialized = super.serialize(...arguments);
|
||||
// Delete unnecessary fields for static host-catalog
|
||||
delete serialized.attributes;
|
||||
delete serialized.secrets;
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
import ApplicationSerializer from './application';
|
||||
import { copy } from 'ember-copy';
|
||||
|
||||
const fieldByType = {
|
||||
aws: ['preferred_endpoints', 'filters', 'sync_interval_seconds'],
|
||||
azure: ['preferred_endpoints', 'filter', 'sync_interval_seconds'],
|
||||
};
|
||||
export default class HostSetSerializer extends ApplicationSerializer {
|
||||
// =properties
|
||||
|
||||
@@ -19,10 +24,17 @@ export default class HostSetSerializer extends ApplicationSerializer {
|
||||
* @return {object}
|
||||
*/
|
||||
serialize(snapshot) {
|
||||
let serialized = super.serialize(...arguments);
|
||||
const hostIDs = snapshot?.adapterOptions?.hostIDs;
|
||||
if (hostIDs) serialized = this.serializeWithHostIDs(snapshot, hostIDs);
|
||||
return serialized;
|
||||
if (hostIDs) {
|
||||
return this.serializeWithHostIDs(snapshot, hostIDs);
|
||||
} else {
|
||||
switch (snapshot.record.compositeType) {
|
||||
case 'static':
|
||||
return this.serializeStatic(...arguments);
|
||||
default:
|
||||
return super.serialize(...arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,4 +49,41 @@ export default class HostSetSerializer extends ApplicationSerializer {
|
||||
host_ids: hostIDs,
|
||||
};
|
||||
}
|
||||
|
||||
serializeStatic() {
|
||||
let serialized = super.serialize(...arguments);
|
||||
// Delete unnecessary fields for static type
|
||||
delete serialized.attributes;
|
||||
delete serialized.preferred_endpoints;
|
||||
delete serialized.sync_interval_seconds;
|
||||
return serialized;
|
||||
}
|
||||
|
||||
serializeAttribute(snapshot, json, key, attribute) {
|
||||
const value = super.serializeAttribute(...arguments);
|
||||
const { isPlugin, compositeType } = snapshot.record;
|
||||
const { options } = attribute;
|
||||
|
||||
if (isPlugin && options.isNestedAttribute) {
|
||||
if (!fieldByType[compositeType].includes(key)) {
|
||||
delete json.attributes[key];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily converts `filters` to an array if it is a string. This is a
|
||||
* quirk of the API.
|
||||
*
|
||||
* TODO: remove once API consistently returns arrays.
|
||||
*/
|
||||
normalize(typeClass, hash, ...rest) {
|
||||
const normalizedHash = copy(hash, true);
|
||||
if (typeof normalizedHash?.filters === 'string') {
|
||||
normalizedHash.filters = [normalizedHash.filters];
|
||||
}
|
||||
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,11 @@ import { inject as service } from '@ember/service';
|
||||
* @example
|
||||
* const filter = new ResourceFilter({
|
||||
* type: 'oidc',
|
||||
* status: ['active', 'pending']
|
||||
* status: ['active', 'pending'],
|
||||
* authorized_actions: [{ contains: 'read' }]
|
||||
* });
|
||||
* log(filter.queryExpression);
|
||||
* // ("/item/type" == "oidc") and ("/item/status" == "active" or "/item/status" == "pending")
|
||||
* // ("/item/type" == "oidc") and ("/item/status" == "active" or "/item/status" == "pending") and ("read" in "/item/authorized_actions")
|
||||
*/
|
||||
export class ResourceFilter {
|
||||
// =attributes
|
||||
@@ -30,7 +31,14 @@ export class ResourceFilter {
|
||||
const value = this.#filterObject[key];
|
||||
const valuesArray = Array.isArray(value) ? value : [value];
|
||||
return this.parenthetical(
|
||||
this.or(valuesArray.map((value) => this.equals(key, value)))
|
||||
this.or(
|
||||
valuesArray.map((value) => {
|
||||
if (value && value.contains) {
|
||||
return this.in(value.contains, key);
|
||||
}
|
||||
return this.equals(key, value);
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.flat()
|
||||
@@ -56,6 +64,15 @@ export class ResourceFilter {
|
||||
return value !== null ? `"/item/${key}" == "${value}"` : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param value
|
||||
* @return {string}
|
||||
*/
|
||||
in(value, key) {
|
||||
return value !== null ? `"${value}" in "/item/${key}"` : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} clauses
|
||||
* @return {string}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from 'api/abilities/collection';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/abilities/host-catalog';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/adapters/host-catalog';
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from 'api/models/fragment-host-attributes';
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from 'api/models/fragment-target-attributes';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/host-catalog';
|
||||
@@ -1,86 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const getChannelURL = require('ember-source-channel-url');
|
||||
|
||||
module.exports = async function () {
|
||||
return {
|
||||
scenarios: [
|
||||
{
|
||||
name: 'ember-lts-3.12',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': '~3.12.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-lts-3.16',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': '~3.16.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-lts-3.20',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': '~3.20.5',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-release',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('release'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-beta',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('beta'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-canary',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('canary'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-default-with-jquery',
|
||||
env: {
|
||||
EMBER_OPTIONAL_FEATURES: JSON.stringify({
|
||||
'jquery-integration': true,
|
||||
}),
|
||||
},
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'@ember/jquery': '^1.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-classic',
|
||||
env: {
|
||||
EMBER_OPTIONAL_FEATURES: JSON.stringify({
|
||||
'application-template-wrapper': true,
|
||||
'default-async-observers': false,
|
||||
'template-only-glimmer-components': false,
|
||||
}),
|
||||
},
|
||||
npm: {
|
||||
ember: {
|
||||
edition: 'classic',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
+28
-10
@@ -26,16 +26,18 @@ export default function () {
|
||||
|
||||
this.get(
|
||||
'/scopes',
|
||||
({ scopes }, { queryParams: { scope_id, recursive } }) => {
|
||||
({ scopes }, { queryParams: { scope_id, recursive, filter } }) => {
|
||||
let resultSet;
|
||||
// Default parent scope is global
|
||||
if (!scope_id) scope_id = 'global';
|
||||
if (recursive && scope_id === 'global') {
|
||||
return scopes.all();
|
||||
resultSet = scopes.all();
|
||||
} else {
|
||||
return scopes.where((scope) => {
|
||||
resultSet = scopes.where((scope) => {
|
||||
return scope.scope ? scope.scope.id === scope_id : false;
|
||||
});
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
);
|
||||
this.post('/scopes', function ({ scopes }) {
|
||||
@@ -268,7 +270,19 @@ export default function () {
|
||||
return hostCatalogs.where({ scopeId });
|
||||
}
|
||||
);
|
||||
this.post('/host-catalogs');
|
||||
this.post(
|
||||
'/host-catalogs',
|
||||
function ({ hostCatalogs }, { queryParams: { plugin_name } }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
if (plugin_name) {
|
||||
attrs.type = 'plugin';
|
||||
attrs.plugin = {
|
||||
name: plugin_name,
|
||||
};
|
||||
}
|
||||
return hostCatalogs.create(attrs);
|
||||
}
|
||||
);
|
||||
this.get('/host-catalogs/:id');
|
||||
this.patch('/host-catalogs/:id');
|
||||
this.del('/host-catalogs/:id');
|
||||
@@ -347,19 +361,22 @@ export default function () {
|
||||
|
||||
this.get(
|
||||
'/targets',
|
||||
function ({ targets }, { queryParams: { scope_id, recursive } }) {
|
||||
function ({ targets }, { queryParams: { scope_id, recursive, filter } }) {
|
||||
let resultSet;
|
||||
if (recursive && scope_id === 'global') {
|
||||
return targets.all();
|
||||
resultSet = targets.all();
|
||||
} else if (recursive) {
|
||||
return targets.where((target) => {
|
||||
resultSet = targets.where((target) => {
|
||||
const targetModel = targets.find(target.id);
|
||||
return (
|
||||
target.scopeId === scope_id ||
|
||||
targetModel?.scope?.scope?.id === scope_id
|
||||
);
|
||||
});
|
||||
} else {
|
||||
resultSet = targets.where((target) => target.scopeId === scope_id);
|
||||
}
|
||||
return targets.where((target) => target.scopeId === scope_id);
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
);
|
||||
this.post('/targets', function ({ targets }) {
|
||||
@@ -531,9 +548,10 @@ export default function () {
|
||||
return managedGroups.where({ authMethodId });
|
||||
}
|
||||
);
|
||||
this.get('/managed-groups/:id');
|
||||
this.del('/managed-groups/:id');
|
||||
this.post('/managed-groups');
|
||||
this.get('/managed-groups/:id');
|
||||
this.patch('/managed-groups/:id');
|
||||
this.del('/managed-groups/:id');
|
||||
|
||||
/* Uncomment the following line and the Response import above
|
||||
* Then change the response code to simulate error responses.
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import factory from '../generated/factories/host-catalog';
|
||||
import { trait } from 'ember-cli-mirage';
|
||||
import permissions from '../helpers/permissions';
|
||||
import { datatype, address, random } from 'faker';
|
||||
|
||||
const types = ['static', 'plugin'];
|
||||
// Represents known plugin types, except "foobar" which models the possibility
|
||||
// of receiving an _unknown_ type, which the UI must handle gracefully.
|
||||
const pluginTypes = ['aws', 'azure', 'foobar'];
|
||||
|
||||
let pluginTypeCounter = 1;
|
||||
|
||||
export default factory.extend({
|
||||
id: (i) => `host-catalog-id-${i}`,
|
||||
|
||||
// Cycle through available types
|
||||
type: (i) => types[i % types.length],
|
||||
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('host-catalog') || [
|
||||
'no-op',
|
||||
@@ -10,17 +23,61 @@ export default factory.extend({
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
authorized_collection_actions: () => {
|
||||
authorized_collection_actions: function () {
|
||||
const isStatic = this.type === 'static';
|
||||
return {
|
||||
hosts: ['create', 'list'],
|
||||
// only static catalogs allow host create at this time
|
||||
hosts: isStatic ? ['create', 'list'] : ['list'],
|
||||
'host-sets': ['create', 'list'],
|
||||
};
|
||||
},
|
||||
|
||||
plugin: function (i) {
|
||||
if (this.type === 'plugin') {
|
||||
return {
|
||||
id: `plugin-id-${i}`,
|
||||
name: pluginTypes[pluginTypeCounter++ % pluginTypes.length],
|
||||
description: random.words(),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
attributes() {
|
||||
switch (this.plugin?.name) {
|
||||
case 'aws':
|
||||
return {
|
||||
disable_credential_rotation: datatype.boolean(),
|
||||
region: address.state(),
|
||||
};
|
||||
case 'azure':
|
||||
return {
|
||||
disable_credential_rotation: datatype.boolean(),
|
||||
tenant_id: datatype.hexaDecimal(6),
|
||||
client_id: datatype.hexaDecimal(6),
|
||||
subscription_id: datatype.hexaDecimal(8),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
secrets() {
|
||||
switch (this.plugin?.name) {
|
||||
case 'aws':
|
||||
return {
|
||||
access_key_id: datatype.hexaDecimal(8),
|
||||
secret_access_key: datatype.string(12),
|
||||
};
|
||||
case 'azure':
|
||||
return {
|
||||
secret_id: datatype.hexaDecimal(6),
|
||||
secret_value: datatype.string(12),
|
||||
};
|
||||
}
|
||||
},
|
||||
withChildren: trait({
|
||||
afterCreate(hostCatalog, server) {
|
||||
const { scope } = hostCatalog;
|
||||
const hosts = server.createList('host', 10, { scope, hostCatalog });
|
||||
server.createList('host-set', 3, { scope, hostCatalog, hosts });
|
||||
const { scope, type } = hostCatalog;
|
||||
const hosts = server.createList('host', 10, { scope, hostCatalog, type });
|
||||
server.createList('host-set', 3, { scope, hostCatalog, hosts, type });
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -1,15 +1,54 @@
|
||||
import factory from '../generated/factories/host-set';
|
||||
import permissions from '../helpers/permissions';
|
||||
import { datatype, internet, database } from 'faker';
|
||||
|
||||
export default factory.extend({
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('host-set') || [
|
||||
'no-op',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
'add-hosts',
|
||||
'remove-hosts',
|
||||
],
|
||||
id: (i) => `host-set-id-${i}`,
|
||||
type() {
|
||||
return this.hostCatalog?.type || factory.attrs.type;
|
||||
},
|
||||
authorized_actions: function () {
|
||||
const isStatic = this.type === 'static';
|
||||
const defaults = ['no-op', 'read', 'update', 'delete'];
|
||||
// Only static allows host management at this time.
|
||||
if (isStatic) defaults.push('add-hosts', 'remove-hosts');
|
||||
return permissions.authorizedActionsFor('host-set') || defaults;
|
||||
},
|
||||
plugin() {
|
||||
if (this.type === 'plugin') {
|
||||
return this.hostCatalog.plugin;
|
||||
}
|
||||
},
|
||||
preferred_endpoints() {
|
||||
if (this.type === 'plugin') {
|
||||
const enpointsAmount = datatype.number({ min: 1, max: 5 });
|
||||
let result = [];
|
||||
for (let i = 0; i < enpointsAmount; ++i) {
|
||||
result.push(internet.ip());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
sync_interval_seconds() {
|
||||
if (this.type === 'plugin') {
|
||||
return datatype.number();
|
||||
}
|
||||
},
|
||||
// AWS specific
|
||||
filters() {
|
||||
if (this.plugin?.name === 'aws') {
|
||||
const filtersAmount = datatype.number({ min: 1, max: 5 });
|
||||
let result = [];
|
||||
for (let i = 0; i < filtersAmount; ++i) {
|
||||
result.push(`${datatype.string(3)}=${datatype.string(8)}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
// Azure specific
|
||||
filter() {
|
||||
if (this.plugin?.name === 'azure') {
|
||||
return `${database.column()}=${database.collation()}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,13 +1,44 @@
|
||||
import factory from '../generated/factories/host';
|
||||
import permissions from '../helpers/permissions';
|
||||
import { internet, datatype } from 'faker';
|
||||
|
||||
export default factory.extend({
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('host') || [
|
||||
'no-op',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
id: (i) => `host-id-${i}`,
|
||||
type() {
|
||||
return this.hostCatalog?.type || factory.attrs.type;
|
||||
},
|
||||
authorized_actions: function () {
|
||||
const isStatic = this.type === 'static';
|
||||
const defaults = ['no-op', 'read'];
|
||||
// Only static allows update/delete at this time.
|
||||
if (isStatic) defaults.push('update', 'delete');
|
||||
return permissions.authorizedActionsFor('host') || defaults;
|
||||
},
|
||||
plugin() {
|
||||
if (this.type === 'plugin') {
|
||||
return this.hostCatalog.plugin;
|
||||
}
|
||||
},
|
||||
ip_addresses() {
|
||||
const addressesAmount = Math.floor(Math.random() * 6) + 1;
|
||||
let result = [];
|
||||
for (let i = 0; i < addressesAmount; ++i) {
|
||||
result.push(internet.ip());
|
||||
}
|
||||
return result;
|
||||
},
|
||||
external_id() {
|
||||
return datatype.uuid();
|
||||
},
|
||||
// Only aws plugins have dns_names
|
||||
dns_names() {
|
||||
if (this.plugin?.name === 'aws') {
|
||||
const dnsNamesAmount = Math.floor(Math.random() * 6) + 1;
|
||||
let result = [];
|
||||
for (let i = 0; i < dnsNamesAmount; i++) {
|
||||
result.push(internet.domainName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import permissions from '../helpers/permissions';
|
||||
|
||||
const randomBoolean = (chance = 0.5) => Math.random() < chance;
|
||||
const hostSetChance = 0.3;
|
||||
const types = ['tcp', 'ssh'];
|
||||
|
||||
export default factory.extend({
|
||||
authorized_actions: () =>
|
||||
@@ -24,12 +25,12 @@ export default factory.extend({
|
||||
* -1 means "unlimited" and we want to generate these on occasion.
|
||||
*/
|
||||
session_connection_limit: () => random.arrayElement([-1, datatype.number()]),
|
||||
|
||||
type: (i) => types[i % types.length],
|
||||
/**
|
||||
* Generates attributes fields by type.
|
||||
*/
|
||||
afterCreate(target) {
|
||||
if (target.type === 'tcp') {
|
||||
if (target.type === 'tcp' || target.type === 'ssh') {
|
||||
target.update({
|
||||
attributes: {
|
||||
default_port: datatype.number(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date, internet, datatype } from 'faker';
|
||||
import { random, date, datatype, internet } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedHostModelFactory
|
||||
|
||||
@@ -44,7 +44,7 @@ const commandHandlers = {
|
||||
id: 'token123',
|
||||
token: 'thetokenstring',
|
||||
account_id: '1',
|
||||
user_id: 'user123',
|
||||
user_id: 'authenticateduser',
|
||||
auth_method_id: 'authmethod123',
|
||||
created_time: '',
|
||||
updated_time: '',
|
||||
@@ -93,7 +93,7 @@ const commandHandlers = {
|
||||
id: 'token123',
|
||||
token: 'thetokenstring',
|
||||
account_id: '1',
|
||||
user_id: 'user123',
|
||||
user_id: 'authenticateduser',
|
||||
auth_method_id: 'authmethod123',
|
||||
created_time: '',
|
||||
updated_time: '',
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function (server) {
|
||||
|
||||
// Other resources
|
||||
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
||||
server.createList('host-catalog', 2, { scope }, 'withChildren');
|
||||
server.createList('host-catalog', 8, { scope }, 'withChildren');
|
||||
server.createList('credential-store', 3, { scope }, 'withAssociations');
|
||||
server.createList('target', 2, { scope }, 'withAssociations');
|
||||
// Sessions have target data. Create it after targets.
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ember-can": "^3.1.0",
|
||||
"ember-cli-babel": "^7.26.6",
|
||||
"ember-cli-babel": "^7.26.8",
|
||||
"ember-cli-htmlbars": "^5.7.1",
|
||||
"ember-cli-mirage": "^2.2.0",
|
||||
"ember-data": "^3.20.0",
|
||||
@@ -34,16 +34,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
||||
"@babel/core": "^7.15.8",
|
||||
"@babel/core": "^7.16.5",
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/test-helpers": "^2.4.2",
|
||||
"@ember/test-helpers": "^2.6.0",
|
||||
"@glimmer/component": "^1.0.4",
|
||||
"@glimmer/tracking": "^1.0.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"doctoc": "^2.0.1",
|
||||
"doctoc": "^2.1.0",
|
||||
"ember-auto-import": "^1.12.0",
|
||||
"ember-cli": "~3.28.3",
|
||||
"ember-cli": "~4.1.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
@@ -53,13 +53,11 @@
|
||||
"ember-export-application-global": "^2.0.1",
|
||||
"ember-load-initializers": "^2.1.2",
|
||||
"ember-maybe-import-regenerator": "^1.0.0",
|
||||
"ember-page-title": "^6.2.2",
|
||||
"ember-page-title": "^7.0.0",
|
||||
"ember-qunit": "^5.1.4",
|
||||
"ember-resolver": "^8.0.3",
|
||||
"ember-source": "~3.28.4",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^3.7.0",
|
||||
"ember-try": "^1.4.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-ember": "^10.5.4",
|
||||
@@ -73,7 +71,7 @@
|
||||
"qunit-dom": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12 <=17"
|
||||
"node": "12.* || 14.* || 16.*"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Abilities | Collection', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it reflects when a resource may be listed based on authorized_collection_actions', function (assert) {
|
||||
assert.expect(2);
|
||||
const service = this.owner.lookup('service:can');
|
||||
const model = {
|
||||
authorized_collection_actions: {
|
||||
foobars: ['list'],
|
||||
},
|
||||
};
|
||||
assert.ok(service.can('list collection', model, { collection: 'foobars' }));
|
||||
model.authorized_collection_actions.foobars = [];
|
||||
assert.notOk(
|
||||
service.can('list collection', model, { collection: 'foobars' })
|
||||
);
|
||||
});
|
||||
|
||||
test('it reflects when a resource may be created based on authorized_collection_actions', function (assert) {
|
||||
assert.expect(2);
|
||||
const service = this.owner.lookup('service:can');
|
||||
const model = {
|
||||
authorized_collection_actions: {
|
||||
foobars: ['create'],
|
||||
},
|
||||
};
|
||||
assert.ok(
|
||||
service.can('create collection', model, { collection: 'foobars' })
|
||||
);
|
||||
model.authorized_collection_actions.foobars = [];
|
||||
assert.notOk(
|
||||
service.can('create collection', model, { collection: 'foobars' })
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -65,4 +65,32 @@ module('Unit | Abilities | Model', function (hooks) {
|
||||
model.authorized_actions = [];
|
||||
assert.notOk(service.can('delete model', model));
|
||||
});
|
||||
|
||||
// =collections
|
||||
|
||||
test('it reflects when a resource may be listed based on authorized_collection_actions', function (assert) {
|
||||
assert.expect(2);
|
||||
const service = this.owner.lookup('service:can');
|
||||
const model = {
|
||||
authorized_collection_actions: {
|
||||
foobars: ['list'],
|
||||
},
|
||||
};
|
||||
assert.ok(service.can('list model', model, { collection: 'foobars' }));
|
||||
model.authorized_collection_actions.foobars = [];
|
||||
assert.notOk(service.can('list model', model, { collection: 'foobars' }));
|
||||
});
|
||||
|
||||
test('it reflects when a resource may be created based on authorized_collection_actions', function (assert) {
|
||||
assert.expect(2);
|
||||
const service = this.owner.lookup('service:can');
|
||||
const model = {
|
||||
authorized_collection_actions: {
|
||||
foobars: ['create'],
|
||||
},
|
||||
};
|
||||
assert.ok(service.can('create model', model, { collection: 'foobars' }));
|
||||
model.authorized_collection_actions.foobars = [];
|
||||
assert.notOk(service.can('create model', model, { collection: 'foobars' }));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Adapter | host catalog', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it generates correct createRecord URLs for static host catalogs', function (assert) {
|
||||
assert.expect(1);
|
||||
const scopeID = 'o_1';
|
||||
const mockSnapshot = {
|
||||
adapterOptions: {
|
||||
scopeID,
|
||||
},
|
||||
attr() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const adapter = this.owner.lookup('adapter:host-catalog');
|
||||
const createRecordURL = adapter.buildURL(
|
||||
'host-catalog',
|
||||
null,
|
||||
mockSnapshot,
|
||||
'createRecord'
|
||||
);
|
||||
assert.equal(createRecordURL, '/v1/host-catalogs');
|
||||
});
|
||||
|
||||
test('it generates correct createRecord URLs for dynamic host catalogs with extra query parameter ?plugin_name', function (assert) {
|
||||
assert.expect(1);
|
||||
const scopeID = 'o_1';
|
||||
const mockSnapshot = {
|
||||
adapterOptions: {
|
||||
scopeID,
|
||||
},
|
||||
attr() {
|
||||
return { name: 'aws' };
|
||||
},
|
||||
};
|
||||
const adapter = this.owner.lookup('adapter:host-catalog');
|
||||
const createRecordURL = adapter.buildURL(
|
||||
'host-catalog',
|
||||
null,
|
||||
mockSnapshot,
|
||||
'createRecord'
|
||||
);
|
||||
assert.equal(createRecordURL, '/v1/host-catalogs?plugin_name=aws');
|
||||
});
|
||||
});
|
||||
@@ -13,8 +13,8 @@ module('Unit | Model | base', function (hooks) {
|
||||
assert.ok(model);
|
||||
});
|
||||
|
||||
test('it may have a scope fragment', function (assert) {
|
||||
assert.expect(1);
|
||||
test('it may have a scope fragment (and thus a scopeModel)', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
@@ -37,6 +37,7 @@ module('Unit | Model | base', function (hooks) {
|
||||
const scope = store.peekRecord('scope', 'o_1');
|
||||
const model = store.peekRecord('user', '123abc');
|
||||
assert.equal(model.scope.scope_id, scope.id);
|
||||
assert.equal(model.scopeModel.id, scope.id);
|
||||
});
|
||||
|
||||
test('it may accept a `scopeModel` for convenience, instead of a fragment', function (assert) {
|
||||
|
||||
@@ -9,4 +9,108 @@ module('Unit | Model | host catalog', function (hooks) {
|
||||
let model = store.createRecord('host-catalog', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
|
||||
test('it has isStatic property and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelA = store.createRecord('host-catalog', { compositeType: 'aws' });
|
||||
const modelB = store.createRecord('host-catalog', {
|
||||
compositeType: 'foobar',
|
||||
});
|
||||
const modelC = store.createRecord('host-catalog', {
|
||||
compositeType: 'static',
|
||||
});
|
||||
assert.false(modelA.isStatic);
|
||||
assert.false(modelB.isStatic);
|
||||
assert.true(modelC.isStatic);
|
||||
});
|
||||
|
||||
test('it has isPlugin property and returns the expected values', async function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin = store.createRecord('host-catalog', {
|
||||
compositeType: 'aws',
|
||||
});
|
||||
const modelStatic = store.createRecord('host-catalog', {
|
||||
compositeType: 'static',
|
||||
});
|
||||
assert.true(modelPlugin.isPlugin);
|
||||
assert.false(modelStatic.isPlugin);
|
||||
});
|
||||
|
||||
test('it has isUnknown property and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelA = store.createRecord('host-catalog', {
|
||||
compositeType: 'static',
|
||||
});
|
||||
const modelB = store.createRecord('host-catalog', { compositeType: 'aws' });
|
||||
const modelC = store.createRecord('host-catalog', {
|
||||
compositeType: 'no-such-type',
|
||||
});
|
||||
assert.false(modelA.isUnknown);
|
||||
assert.false(modelB.isUnknown);
|
||||
assert.true(modelC.isUnknown);
|
||||
});
|
||||
|
||||
test('it has isAWS property and returns the expected values', async function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelAws = store.createRecord('host-catalog', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'aws' },
|
||||
});
|
||||
const modelRandom = store.createRecord('host-catalog', {
|
||||
plugin: { name: 'random' },
|
||||
});
|
||||
assert.true(modelAws.isAWS);
|
||||
assert.false(modelRandom.isAWS);
|
||||
});
|
||||
|
||||
test('it has isAzure property and return the expected values', async function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelAzure = store.createRecord('host-catalog', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'azure' },
|
||||
});
|
||||
const modelRandom = store.createRecord('host-catalog', {
|
||||
plugin: { name: 'random' },
|
||||
});
|
||||
assert.true(modelAzure.isAzure);
|
||||
assert.false(modelRandom.isAzure);
|
||||
});
|
||||
|
||||
test('get compositeType returns expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelA = store.createRecord('host-catalog', {
|
||||
type: 'static',
|
||||
});
|
||||
const modelB = store.createRecord('host-catalog', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'aws' },
|
||||
});
|
||||
const modelC = store.createRecord('host-catalog', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'no-such-type' },
|
||||
});
|
||||
assert.equal(modelA.compositeType, 'static');
|
||||
assert.equal(modelB.compositeType, 'aws');
|
||||
assert.equal(modelC.compositeType, 'unknown');
|
||||
});
|
||||
|
||||
test('set compositeType sets expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin = store.createRecord('host-catalog', {
|
||||
compositeType: 'aws',
|
||||
});
|
||||
const modelStatic = store.createRecord('host-catalog', {
|
||||
compositeType: 'static',
|
||||
});
|
||||
assert.equal(modelPlugin.type, 'plugin');
|
||||
assert.equal(modelPlugin.plugin.name, 'aws');
|
||||
assert.equal(modelStatic.type, 'static');
|
||||
});
|
||||
});
|
||||
@@ -129,4 +129,88 @@ module('Unit | Model | host set', function (hooks) {
|
||||
const model = store.peekRecord('host-set', '123abc');
|
||||
await model.removeHost('3');
|
||||
});
|
||||
|
||||
test('it has isStatic and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelA = store.createRecord('host-set', { type: 'static' });
|
||||
const modelB = store.createRecord('host-set', { type: 'plugin' });
|
||||
assert.equal(typeof modelA.isStatic, 'boolean');
|
||||
assert.true(modelA.isStatic);
|
||||
assert.false(modelB.isStatic);
|
||||
});
|
||||
|
||||
test('it has isPlugin and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin = store.createRecord('host-set', { type: 'plugin' });
|
||||
const modelStatic = store.createRecord('host-set', { type: 'static' });
|
||||
assert.equal(typeof modelPlugin.isPlugin, 'boolean');
|
||||
assert.true(modelPlugin.isPlugin);
|
||||
assert.false(modelStatic.isPlugin);
|
||||
});
|
||||
|
||||
test('it has isAws and return the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelAws = store.createRecord('host-set', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'aws' },
|
||||
});
|
||||
const modelRandom = store.createRecord('host-set', {
|
||||
plugin: { name: 'random' },
|
||||
});
|
||||
assert.equal(typeof modelAws.isAWS, 'boolean');
|
||||
assert.true(modelAws.isAWS);
|
||||
assert.false(modelRandom.isAWS);
|
||||
});
|
||||
|
||||
test('it has isAzure and return the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelAzure = store.createRecord('host-set', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'azure' },
|
||||
});
|
||||
const modelRandom = store.createRecord('host-set', {
|
||||
plugin: { name: 'random' },
|
||||
});
|
||||
assert.equal(typeof modelAzure.isAzure, 'boolean');
|
||||
assert.true(modelAzure.isAzure);
|
||||
assert.false(modelRandom.isAzure);
|
||||
});
|
||||
|
||||
test('get compositeType return expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin1 = store.createRecord('host-set', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'aws' },
|
||||
});
|
||||
const modelPlugin2 = store.createRecord('host-set', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'Test name' },
|
||||
});
|
||||
const modelStatic = store.createRecord('host-set', {
|
||||
type: 'static',
|
||||
});
|
||||
assert.equal(modelPlugin1.compositeType, 'aws');
|
||||
assert.equal(modelPlugin2.compositeType, 'unknown');
|
||||
assert.equal(modelStatic.compositeType, 'static');
|
||||
});
|
||||
|
||||
test('set compositeType sets expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin = store.createRecord('host-set', {
|
||||
compositeType: 'aws',
|
||||
});
|
||||
const modelStatic = store.createRecord('host-set', {
|
||||
compositeType: 'static',
|
||||
});
|
||||
|
||||
assert.equal(modelPlugin.type, 'plugin');
|
||||
assert.equal(modelPlugin.plugin.name, 'aws');
|
||||
assert.equal(modelStatic.type, 'static');
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,14 @@ module('Unit | Model | host', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
assert.expect(1);
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('host', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
|
||||
test('it contains attributes', function (assert) {
|
||||
assert.expect(1);
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('host', {
|
||||
type: 'static',
|
||||
@@ -20,4 +22,84 @@ module('Unit | Model | host', function (hooks) {
|
||||
});
|
||||
assert.equal(model.attributes.address, '127.0.0.1');
|
||||
});
|
||||
|
||||
test('it has isStatic and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelA = store.createRecord('host', { type: 'static' });
|
||||
const modelB = store.createRecord('host', { type: 'plugin' });
|
||||
assert.equal(typeof modelA.isStatic, 'boolean');
|
||||
assert.true(modelA.isStatic);
|
||||
assert.false(modelB.isStatic);
|
||||
});
|
||||
|
||||
test('it has isPlugin and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin = store.createRecord('host', { type: 'plugin' });
|
||||
const modelStatic = store.createRecord('host', { type: 'static' });
|
||||
assert.equal(typeof modelPlugin.isPlugin, 'boolean');
|
||||
assert.true(modelPlugin.isPlugin);
|
||||
assert.false(modelStatic.isPlugin);
|
||||
});
|
||||
|
||||
test('it has isAWS and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelAws = store.createRecord('host', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'aws' },
|
||||
});
|
||||
const modelRandom = store.createRecord('host', {
|
||||
plugin: { name: 'random' },
|
||||
});
|
||||
assert.equal(typeof modelAws.isAWS, 'boolean');
|
||||
assert.true(modelAws.isAWS);
|
||||
assert.false(modelRandom.isAWS);
|
||||
});
|
||||
|
||||
test('it has isAzure and returns the expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelAzure = store.createRecord('host', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'azure' },
|
||||
});
|
||||
const modelRandom = store.createRecord('host', {
|
||||
plugin: { name: 'random' },
|
||||
});
|
||||
assert.equal(typeof modelAzure.isAzure, 'boolean');
|
||||
assert.true(modelAzure.isAzure);
|
||||
assert.false(modelRandom.isAzure);
|
||||
});
|
||||
|
||||
test('get compositeType returns expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin = store.createRecord('host', {
|
||||
type: 'plugin',
|
||||
plugin: { name: 'Test name' },
|
||||
});
|
||||
const modelStatic = store.createRecord('host', {
|
||||
type: 'static',
|
||||
});
|
||||
assert.equal(typeof modelPlugin.compositeType, 'string');
|
||||
assert.equal(modelPlugin.compositeType, 'Test name');
|
||||
assert.equal(modelStatic.compositeType, 'static');
|
||||
});
|
||||
|
||||
test('set compositeType sets expected values', async function (assert) {
|
||||
assert.expect(3);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelPlugin = store.createRecord('host', {
|
||||
compositeType: 'aws',
|
||||
});
|
||||
const modelStatic = store.createRecord('host', {
|
||||
compositeType: 'static',
|
||||
});
|
||||
|
||||
assert.equal(modelPlugin.type, 'plugin');
|
||||
assert.equal(modelPlugin.plugin.name, 'aws');
|
||||
assert.equal(modelStatic.type, 'static');
|
||||
});
|
||||
});
|
||||
@@ -384,4 +384,24 @@ module('Unit | Model | target', function (hooks) {
|
||||
const model = store.peekRecord('target', '123abc');
|
||||
await model.removeCredentialSource('2');
|
||||
});
|
||||
|
||||
test('it has isSSH property and returns the expected values', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelSSH = store.createRecord('target', {
|
||||
type: 'ssh',
|
||||
});
|
||||
assert.true(modelSSH.isSSH);
|
||||
assert.false(modelSSH.isTCP);
|
||||
});
|
||||
|
||||
test('it has isTCP property and returns the expected values', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelTCP = store.createRecord('target', {
|
||||
type: 'tcp',
|
||||
});
|
||||
assert.true(modelTCP.isTCP);
|
||||
assert.false(modelTCP.isSSH);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,321 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Serializer | host catalog', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('host-catalog', {});
|
||||
|
||||
assert.ok(record.serialize());
|
||||
});
|
||||
|
||||
test('it serializes a static host-catalog as expected', async function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('host-catalog', {
|
||||
name: 'static',
|
||||
description: 'this is a static host-catalog',
|
||||
type: 'static',
|
||||
});
|
||||
const expectedResult = {
|
||||
name: 'static',
|
||||
description: 'this is a static host-catalog',
|
||||
type: 'static',
|
||||
};
|
||||
assert.deepEqual(record.serialize(), expectedResult);
|
||||
});
|
||||
|
||||
test('it serializes a new aws plugin as expected, ignoring azure fields', async function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('host-catalog', {
|
||||
compositeType: 'aws',
|
||||
name: 'AWS',
|
||||
description: 'this is a Aws plugin host-catalog',
|
||||
disable_credential_rotation: true,
|
||||
// these are AWS fields and should be included
|
||||
region: 'spain',
|
||||
access_key_id: 'foobars',
|
||||
secret_access_key: 'testing',
|
||||
// these are Azure fields and should be excluded
|
||||
tenant_id: 'a1b2c3',
|
||||
client_id: 'a1b2c3',
|
||||
subscription_id: 'a1b2c3',
|
||||
secret_id: 'a1b2c3',
|
||||
secret_value: 'a1b2c3',
|
||||
});
|
||||
const expectedResult = {
|
||||
name: 'AWS',
|
||||
description: 'this is a Aws plugin host-catalog',
|
||||
type: 'plugin',
|
||||
attributes: {
|
||||
disable_credential_rotation: true,
|
||||
region: 'spain',
|
||||
},
|
||||
secrets: {
|
||||
access_key_id: 'foobars',
|
||||
secret_access_key: 'testing',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(record.serialize(), expectedResult);
|
||||
});
|
||||
|
||||
test('it serializes a new azure record as expected, ignoring aws fields', async function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('host-catalog', {
|
||||
compositeType: 'azure',
|
||||
name: 'Azure',
|
||||
description: 'this is a Azure plugin host-catalog',
|
||||
disable_credential_rotation: true,
|
||||
region: 'spain',
|
||||
access_key_id: 'foobars',
|
||||
secret_access_key: 'testing',
|
||||
// these are Azure fields and should be excluded
|
||||
tenant_id: 'a1b2c3',
|
||||
client_id: 'a1b2c3',
|
||||
subscription_id: 'a1b2c3',
|
||||
secret_id: 'a1b2c3',
|
||||
secret_value: 'a1b2c3',
|
||||
});
|
||||
const expectedResult = {
|
||||
name: 'Azure',
|
||||
description: 'this is a Azure plugin host-catalog',
|
||||
type: 'plugin',
|
||||
attributes: {
|
||||
disable_credential_rotation: true,
|
||||
tenant_id: 'a1b2c3',
|
||||
client_id: 'a1b2c3',
|
||||
subscription_id: 'a1b2c3',
|
||||
},
|
||||
secrets: {
|
||||
secret_id: 'a1b2c3',
|
||||
secret_value: 'a1b2c3',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(record.serialize(), expectedResult);
|
||||
});
|
||||
|
||||
test('it serializes an existing aws record correctly', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host-catalog',
|
||||
attributes: {
|
||||
type: 'plugin',
|
||||
name: 'aws',
|
||||
description: 'test description',
|
||||
plugin: {
|
||||
name: 'aws',
|
||||
},
|
||||
region: 'andorra',
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('host-catalog', '1');
|
||||
const expectedResult = {
|
||||
type: 'plugin',
|
||||
name: 'aws',
|
||||
description: 'test description',
|
||||
attributes: {
|
||||
disable_credential_rotation: false,
|
||||
region: 'andorra',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(record.serialize(), expectedResult);
|
||||
});
|
||||
|
||||
test('it serializes an existing azure record correctly', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '2',
|
||||
type: 'host-catalog',
|
||||
attributes: {
|
||||
type: 'plugin',
|
||||
name: 'azure',
|
||||
description: 'test description',
|
||||
plugin: {
|
||||
name: 'azure',
|
||||
},
|
||||
tenant_id: 'a1b2c3',
|
||||
client_id: 'foobars12',
|
||||
subscription_id: 'barsfoo21',
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('host-catalog', '2');
|
||||
const expectedResult = {
|
||||
type: 'plugin',
|
||||
name: 'azure',
|
||||
description: 'test description',
|
||||
attributes: {
|
||||
disable_credential_rotation: false,
|
||||
tenant_id: 'a1b2c3',
|
||||
client_id: 'foobars12',
|
||||
subscription_id: 'barsfoo21',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(record.serialize(), expectedResult);
|
||||
});
|
||||
|
||||
test('it normalizes an static host catalog as expected', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('host-catalog');
|
||||
const hostCatalog = store.createRecord('host-catalog').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'Host catalog test',
|
||||
description: 'Test description',
|
||||
authorized_actions: ['no-op'],
|
||||
type: 'static',
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
hostCatalog,
|
||||
payload
|
||||
);
|
||||
assert.deepEqual(normalized, {
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host-catalog',
|
||||
attributes: {
|
||||
name: 'Host catalog test',
|
||||
description: 'Test description',
|
||||
authorized_actions: ['no-op'],
|
||||
type: 'static',
|
||||
access_key_id: null,
|
||||
secret_access_key: null,
|
||||
secret_id: null,
|
||||
secret_value: null,
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
included: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes an aws plugin type host catalog as expected', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('host-catalog');
|
||||
const hostCatalog = store.createRecord('host-catalog').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'Host catalog test',
|
||||
description: 'Test description',
|
||||
authorized_actions: ['no-op'],
|
||||
type: 'plugin',
|
||||
plugin: {
|
||||
id: 'plugin-id-5',
|
||||
name: 'aws',
|
||||
description: 'aws host catalog',
|
||||
},
|
||||
attributes: {
|
||||
disable_credential_rotation: false,
|
||||
region: 'Illinois',
|
||||
},
|
||||
secrets: {
|
||||
access_key_id: '0xF3B0a6f8',
|
||||
secret_access_key: 'zq{2:IVc8@W^',
|
||||
},
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
hostCatalog,
|
||||
payload
|
||||
);
|
||||
assert.deepEqual(normalized, {
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host-catalog',
|
||||
attributes: {
|
||||
name: 'Host catalog test',
|
||||
type: 'plugin',
|
||||
description: 'Test description',
|
||||
authorized_actions: ['no-op'],
|
||||
plugin: {
|
||||
id: 'plugin-id-5',
|
||||
name: 'aws',
|
||||
description: 'aws host catalog',
|
||||
},
|
||||
disable_credential_rotation: false,
|
||||
region: 'Illinois',
|
||||
access_key_id: '0xF3B0a6f8',
|
||||
secret_access_key: 'zq{2:IVc8@W^',
|
||||
secret_id: null,
|
||||
secret_value: null,
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
included: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes an azure plugin type host catalog as expected', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('host-catalog');
|
||||
const hostCatalog = store.createRecord('host-catalog').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'Host catalog test',
|
||||
description: 'Test description',
|
||||
authorized_actions: ['no-op', 'read', 'update', 'delete'],
|
||||
type: 'plugin',
|
||||
plugin: {
|
||||
id: 'plugin-id-6',
|
||||
name: 'azure',
|
||||
description: 'azure host catalog',
|
||||
},
|
||||
attributes: {
|
||||
disable_credential_rotation: true,
|
||||
tenant_id: 'tenant',
|
||||
client_id: 'client',
|
||||
subscription_id: 'subscription',
|
||||
},
|
||||
secrets: {
|
||||
secret_id: '0xF3B0a6f8',
|
||||
secret_value: 'zq{2:IVc8@W^',
|
||||
},
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
hostCatalog,
|
||||
payload
|
||||
);
|
||||
assert.deepEqual(normalized, {
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host-catalog',
|
||||
attributes: {
|
||||
name: 'Host catalog test',
|
||||
type: 'plugin',
|
||||
description: 'Test description',
|
||||
authorized_actions: ['no-op', 'read', 'update', 'delete'],
|
||||
plugin: {
|
||||
id: 'plugin-id-6',
|
||||
name: 'azure',
|
||||
description: 'azure host catalog',
|
||||
},
|
||||
disable_credential_rotation: true,
|
||||
secret_id: '0xF3B0a6f8',
|
||||
secret_value: 'zq{2:IVc8@W^',
|
||||
client_id: 'client',
|
||||
tenant_id: 'tenant',
|
||||
subscription_id: 'subscription',
|
||||
access_key_id: null,
|
||||
secret_access_key: null,
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
included: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,17 +7,14 @@ module('Unit | Serializer | host set', function (hooks) {
|
||||
test('it serializes host sets normally, without host_ids', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('host-set');
|
||||
const record = store.createRecord('host-set', {
|
||||
name: 'Host Set 1',
|
||||
compositeType: 'static',
|
||||
description: 'Description',
|
||||
host_catalog_id: '123',
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
snapshot.adapterOptions = {};
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
assert.deepEqual(record.serialize(), {
|
||||
name: 'Host Set 1',
|
||||
description: 'Description',
|
||||
host_catalog_id: '123',
|
||||
@@ -46,6 +43,58 @@ module('Unit | Serializer | host set', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes host sets as expected for Aws plugin', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('host-set', {
|
||||
name: 'Host Set 1',
|
||||
compositeType: 'aws',
|
||||
description: 'Description',
|
||||
host_catalog_id: '123',
|
||||
version: 1,
|
||||
preferred_endpoints: [{ value: 'option 1' }, { value: 'option 2' }],
|
||||
sync_interval_seconds: 1,
|
||||
filters: [{ value: 'filter 1' }, { value: 'filter 2' }],
|
||||
});
|
||||
assert.deepEqual(record.serialize(), {
|
||||
name: 'Host Set 1',
|
||||
description: 'Description',
|
||||
host_catalog_id: '123',
|
||||
version: 1,
|
||||
preferred_endpoints: ['option 1', 'option 2'],
|
||||
sync_interval_seconds: 1,
|
||||
attributes: {
|
||||
filters: ['filter 1', 'filter 2'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes host sets as expected for Azure plugin', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('host-set', {
|
||||
name: 'Host Set 1',
|
||||
compositeType: 'azure',
|
||||
description: 'Description',
|
||||
host_catalog_id: '123',
|
||||
version: 1,
|
||||
preferred_endpoints: [{ value: 'option 1' }, { value: 'option 2' }],
|
||||
sync_interval_seconds: 1,
|
||||
filter: 'filter 1 && filter 2',
|
||||
});
|
||||
assert.deepEqual(record.serialize(), {
|
||||
name: 'Host Set 1',
|
||||
description: 'Description',
|
||||
host_catalog_id: '123',
|
||||
version: 1,
|
||||
preferred_endpoints: ['option 1', 'option 2'],
|
||||
sync_interval_seconds: 1,
|
||||
attributes: {
|
||||
filter: 'filter 1 && filter 2',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes records with array fields', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
@@ -54,22 +103,32 @@ module('Unit | Serializer | host set', function (hooks) {
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'Host Set 1',
|
||||
type: 'static',
|
||||
host_ids: ['1', '2', '3'],
|
||||
preferred_endpoints: ['endpoint 1', 'endpoint 2'],
|
||||
filters: ['filter 1', 'filter 2'],
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
hostSetModelClass,
|
||||
payload
|
||||
);
|
||||
|
||||
assert.deepEqual(normalized, {
|
||||
included: [],
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host-set',
|
||||
attributes: {
|
||||
type: 'static',
|
||||
authorized_actions: [],
|
||||
name: 'Host Set 1',
|
||||
host_ids: [{ value: '1' }, { value: '2' }, { value: '3' }],
|
||||
preferred_endpoints: [
|
||||
{ value: 'endpoint 1' },
|
||||
{ value: 'endpoint 2' },
|
||||
],
|
||||
filters: [{ value: 'filter 1' }, { value: 'filter 2' }],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
@@ -101,6 +160,41 @@ module('Unit | Serializer | host set', function (hooks) {
|
||||
name: 'Host Set 1',
|
||||
scope: { scope_id: 'o_123' },
|
||||
host_ids: [],
|
||||
preferred_endpoints: [],
|
||||
filters: [],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes missing array fields to empty array', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('host-set');
|
||||
const hostSet = store.createRecord('host-set').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'Host Set 1',
|
||||
scope: { id: 'o_123' },
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
hostSet,
|
||||
payload
|
||||
);
|
||||
assert.deepEqual(normalized, {
|
||||
included: [],
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host-set',
|
||||
attributes: {
|
||||
authorized_actions: [],
|
||||
name: 'Host Set 1',
|
||||
scope: { scope_id: 'o_123' },
|
||||
host_ids: [],
|
||||
preferred_endpoints: [],
|
||||
filters: [],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
|
||||
@@ -4,19 +4,92 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Serializer | host', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('host');
|
||||
|
||||
assert.ok(serializer);
|
||||
test('it serializes host with type static as expected', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('host', {
|
||||
name: 'Host 1',
|
||||
compositeType: 'static',
|
||||
description: 'Description',
|
||||
host_catalog_id: '123',
|
||||
version: 1,
|
||||
address: '188e:68a9:b342:c05c:2595:2f46:499c:759f',
|
||||
});
|
||||
assert.deepEqual(record.serialize(), {
|
||||
name: 'Host 1',
|
||||
description: 'Description',
|
||||
type: 'static',
|
||||
host_catalog_id: '123',
|
||||
version: 1,
|
||||
attributes: {
|
||||
address: '188e:68a9:b342:c05c:2595:2f46:499c:759f',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('host', {});
|
||||
test('it normalizes type static record as expected', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('host');
|
||||
const host = store.createRecord('host').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'host test',
|
||||
type: 'static',
|
||||
authorized_actions: ['no-op', 'read'],
|
||||
address: '10.0.0.1',
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(store, host, payload);
|
||||
assert.deepEqual(normalized, {
|
||||
included: [],
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host',
|
||||
attributes: {
|
||||
name: 'host test',
|
||||
type: 'static',
|
||||
authorized_actions: ['no-op', 'read'],
|
||||
address: '10.0.0.1',
|
||||
dns_names: [],
|
||||
ip_addresses: [],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
test('it normalizes type plugin (aws) record as expected', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('host');
|
||||
const host = store.createRecord('host').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'host test',
|
||||
type: 'plugin',
|
||||
plugin: {
|
||||
name: 'aws',
|
||||
},
|
||||
authorized_actions: ['no-op', 'read'],
|
||||
ip_addresses: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
|
||||
dns_names: ['test.example.internal', 'test.example.external'],
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(store, host, payload);
|
||||
assert.deepEqual(normalized, {
|
||||
included: [],
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'host',
|
||||
attributes: {
|
||||
name: 'host test',
|
||||
type: 'plugin',
|
||||
authorized_actions: ['no-op', 'read'],
|
||||
ip_addresses: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
|
||||
dns_names: ['test.example.internal', 'test.example.external'],
|
||||
plugin: { name: 'aws' },
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,7 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
scope_id: 'org_1',
|
||||
type: 'org',
|
||||
},
|
||||
default_port: 1234,
|
||||
version: 1,
|
||||
type: 'tcp',
|
||||
});
|
||||
@@ -35,7 +36,7 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
session_max_seconds: 28800,
|
||||
session_connection_limit: 1,
|
||||
attributes: {
|
||||
default_port: null,
|
||||
default_port: 1234,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -123,6 +124,40 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes records with nested attributes', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('target');
|
||||
const targetModelClass = store.createRecord('target').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'Target 1',
|
||||
attributes: {
|
||||
default_port: 1234,
|
||||
},
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
targetModelClass,
|
||||
payload
|
||||
);
|
||||
assert.deepEqual(normalized, {
|
||||
included: [],
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'target',
|
||||
attributes: {
|
||||
application_credential_source_ids: [],
|
||||
authorized_actions: [],
|
||||
host_sources: [],
|
||||
name: 'Target 1',
|
||||
default_port: 1234,
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes missing host_sources and application_credential_source_ids to empty array', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
|
||||
@@ -10,9 +10,10 @@ module('Unit | Service | resource-filter-store', function (hooks) {
|
||||
const filter = new ResourceFilter({
|
||||
type: 'oidc',
|
||||
status: ['active', 'pending'],
|
||||
authorized_actions: [{ contains: 'read' }],
|
||||
});
|
||||
const expected =
|
||||
'("/item/type" == "oidc") and ("/item/status" == "active" or "/item/status" == "pending")';
|
||||
'("/item/type" == "oidc") and ("/item/status" == "active" or "/item/status" == "pending") and ("read" in "/item/authorized_actions")';
|
||||
assert.equal(filter.queryExpression, expected);
|
||||
});
|
||||
});
|
||||
@@ -14,8 +14,3 @@
|
||||
/coverage/
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -19,15 +19,9 @@
|
||||
/.travis.yml
|
||||
/.watchmanconfig
|
||||
/bower.json
|
||||
/config/ember-try.js
|
||||
/CONTRIBUTING.md
|
||||
/ember-cli-build.js
|
||||
/testem.js
|
||||
/tests/
|
||||
/yarn.lock
|
||||
.gitkeep
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -14,8 +14,3 @@
|
||||
/coverage/
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -1,79 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const getChannelURL = require('ember-source-channel-url');
|
||||
|
||||
module.exports = async function () {
|
||||
return {
|
||||
useYarn: true,
|
||||
scenarios: [
|
||||
{
|
||||
name: 'ember-lts-3.16',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': '~3.16.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-lts-3.20',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': '~3.20.5',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-release',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('release'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-beta',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('beta'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-canary',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('canary'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-default-with-jquery',
|
||||
env: {
|
||||
EMBER_OPTIONAL_FEATURES: JSON.stringify({
|
||||
'jquery-integration': true,
|
||||
}),
|
||||
},
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'@ember/jquery': '^1.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-classic',
|
||||
env: {
|
||||
EMBER_OPTIONAL_FEATURES: JSON.stringify({
|
||||
'application-template-wrapper': true,
|
||||
'default-async-observers': false,
|
||||
'template-only-glimmer-components': false,
|
||||
}),
|
||||
},
|
||||
npm: {
|
||||
ember: {
|
||||
edition: 'classic',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -21,22 +21,22 @@
|
||||
"doc:toc": "doctoc README.md"
|
||||
},
|
||||
"dependencies": {
|
||||
"ember-cli-babel": "^7.26.6",
|
||||
"ember-cli-babel": "^7.26.8",
|
||||
"ember-cli-htmlbars": "^5.7.1",
|
||||
"ember-fetch": "^8.1.1",
|
||||
"ember-simple-auth": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/test-helpers": "^2.4.2",
|
||||
"@ember/test-helpers": "^2.6.0",
|
||||
"@glimmer/component": "^1.0.4",
|
||||
"@glimmer/tracking": "^1.0.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"core": "*",
|
||||
"doctoc": "^2.0.1",
|
||||
"doctoc": "^2.1.0",
|
||||
"ember-auto-import": "^1.12.0",
|
||||
"ember-cli": "~3.28.3",
|
||||
"ember-cli": "~4.1.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
@@ -46,13 +46,11 @@
|
||||
"ember-export-application-global": "^2.0.1",
|
||||
"ember-load-initializers": "^2.1.2",
|
||||
"ember-maybe-import-regenerator": "^1.0.0",
|
||||
"ember-page-title": "^6.2.2",
|
||||
"ember-page-title": "^7.0.0",
|
||||
"ember-qunit": "^5.1.4",
|
||||
"ember-resolver": "^8.0.3",
|
||||
"ember-source": "~3.28.4",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^3.7.0",
|
||||
"ember-try": "^1.4.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-ember": "^10.5.4",
|
||||
@@ -66,7 +64,7 @@
|
||||
"qunit-dom": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12 <=17"
|
||||
"node": "12.* || 14.* || 16.*"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -14,8 +14,3 @@
|
||||
/coverage/
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -19,15 +19,9 @@
|
||||
/.travis.yml
|
||||
/.watchmanconfig
|
||||
/bower.json
|
||||
/config/ember-try.js
|
||||
/CONTRIBUTING.md
|
||||
/ember-cli-build.js
|
||||
/testem.js
|
||||
/tests/
|
||||
/yarn.lock
|
||||
.gitkeep
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -14,8 +14,3 @@
|
||||
/coverage/
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -1,6 +1,6 @@
|
||||
<a
|
||||
class="doc-link"
|
||||
href={{doc-url @doc}}
|
||||
href={{(doc-url @doc)}}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
title={{t "actions.get-topic-help"}}>
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* A resource filter param is a concise way to express a route query parameter.
|
||||
* Resource filter params abstract away a route's query parameter
|
||||
* interface, treating a query param as a simple attribute on a route instance.
|
||||
* Getting the attribute value is equivalent to looking up the associated query
|
||||
* parameter. Setting the attribute is equivalent to transitioning to the route
|
||||
* and passing the associated query parameter value.
|
||||
*
|
||||
* Additionally, resource filter params may declare `allowedValues`, an array
|
||||
* containing the set of values the parameter may take. While unenforced,
|
||||
* `allowedValues` are useful metadata about a resource filter and may be fetch
|
||||
* via `ResourceFilterParamHelper`. An optional default value may be specified
|
||||
* via `defaultValue` passed to the decorator.
|
||||
*
|
||||
* @see ResourceFilterParamHelper
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* export default class MyRoute extends Route {
|
||||
*
|
||||
* @resourceFilterParam(['active', 'pending'], ['active'])
|
||||
* status;
|
||||
*
|
||||
* @action
|
||||
* changeStatus(status) {
|
||||
* // Sets query param `filter-status` on the current route, which occurs
|
||||
* // via a route transition.
|
||||
* this.status = status;
|
||||
* // Log the value of query param `filter-status`.
|
||||
* log(this.status);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export function resourceFilterParam(allowedValues, defaultValue) {
|
||||
/**
|
||||
* @param {object} target
|
||||
* @param {string} name
|
||||
* @param {object} descriptor
|
||||
* @return {object{get, set}}
|
||||
*/
|
||||
return function (target, name /*, descriptor*/) {
|
||||
const filterKey = `filter-${name}`;
|
||||
const filterAllowedValuesKey = `filter-allowed-values-${name}`;
|
||||
|
||||
// If the route has no query params specified yet, declare them.
|
||||
if (!Object.keys(target.queryParams).length) target.queryParams = {};
|
||||
|
||||
// If the route has no resource filters list specified yet, create one.
|
||||
if (!target.resourceFilterParams) target.resourceFilterParams = [];
|
||||
|
||||
// Add the resource filter query parameter to the route. Resource filter
|
||||
// param changes refresh the model but do not contribute to browser history.
|
||||
// See https://guides.emberjs.com/release/routing/query-params/
|
||||
target.queryParams[filterKey] = {
|
||||
refreshModel: true,
|
||||
replace: true,
|
||||
};
|
||||
|
||||
// Note the name of this filter param.
|
||||
target.resourceFilterParams.push(name);
|
||||
|
||||
// Store the allowed values for future lookup
|
||||
// (via `ResourceFilterParamHelper`).
|
||||
target[filterAllowedValuesKey] = allowedValues;
|
||||
|
||||
// Override the decorated attribute with a getter and setter.
|
||||
return {
|
||||
/**
|
||||
* Returns the JSON-parsed query parameter value OR defaultValue.
|
||||
*/
|
||||
get() {
|
||||
const value =
|
||||
this.paramsFor(this.routeName)[filterKey] ||
|
||||
JSON.stringify(defaultValue);
|
||||
return value ? JSON.parse(value) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the resource filter param to the stringified representation
|
||||
* of `value`. NOTE: this is equivalent to transitioning into the
|
||||
* current route, changing the associated query param.
|
||||
* @param value
|
||||
*/
|
||||
set(value) {
|
||||
const queryParams = {};
|
||||
queryParams[filterKey] = JSON.stringify(value);
|
||||
this.transitionTo({ queryParams });
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import EmberObject from '@ember/object';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { typeOf } from '@ember/utils';
|
||||
|
||||
/**
|
||||
* @param value
|
||||
* @return {boolean} true if `value` is a function
|
||||
*/
|
||||
const isFunction = (value) => typeOf(value) === 'function';
|
||||
|
||||
/**
|
||||
* Represents a filter on a specific route and maintains filter state.
|
||||
* @class
|
||||
*/
|
||||
class RouteResourceFilter extends EmberObject {
|
||||
// =attributes
|
||||
|
||||
route;
|
||||
name;
|
||||
defaultValue;
|
||||
allowed;
|
||||
serialize;
|
||||
findBySerialized;
|
||||
|
||||
// =computed
|
||||
|
||||
/**
|
||||
* Query parameter name where the selected value(s) for this filter are
|
||||
* stored in the URL.
|
||||
* @type {string}
|
||||
*/
|
||||
get filterKey() {
|
||||
return `filter-${this.name}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The allowed values for this filter. See `allowed`.
|
||||
* @type {?array}
|
||||
*/
|
||||
get allowedValues() {
|
||||
if (isFunction(this.allowed)) {
|
||||
return this.allowed(this.route);
|
||||
}
|
||||
return this.allowed;
|
||||
}
|
||||
|
||||
get router() {
|
||||
const owner = getOwner(this.route);
|
||||
return owner.lookup('service:router');
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently selected value(s) for this filter, deserialized from the value of
|
||||
* the associated route query parameter.
|
||||
* @type {?array}
|
||||
*/
|
||||
get value() {
|
||||
const decodedValue =
|
||||
this.route.paramsFor(this.route.routeName)[this.filterKey] ||
|
||||
JSON.stringify(this.defaultValue);
|
||||
const value = decodedValue ? JSON.parse(decodedValue) : null;
|
||||
const deserializedValue = value
|
||||
? value.map((serializedValue) =>
|
||||
this.allowedValues.find((item) =>
|
||||
this.deserializeValue(item, serializedValue)
|
||||
)
|
||||
)
|
||||
: null;
|
||||
return deserializedValue;
|
||||
}
|
||||
set value(value) {
|
||||
const queryParams = {};
|
||||
const serialized = value.map((value) => this.serializeValue(value));
|
||||
queryParams[this.filterKey] = JSON.stringify(serialized);
|
||||
this.router.transitionTo(this.route.routeName, { queryParams });
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Calls `serialize` (if set) on the passed value. Otherwise returns the
|
||||
* value unaltered.
|
||||
*/
|
||||
serializeValue(value) {
|
||||
if (this.serialize) {
|
||||
return this.serialize(...arguments);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if `item` is represented by `serializedValue` when deserializing
|
||||
* items from the query string. If `findBySerialized` is unspecified,
|
||||
* a simple equality test is used.
|
||||
* @param item
|
||||
* @param {string} serializedValue
|
||||
* @return {boolean} item is represented by `serializedValue`
|
||||
*/
|
||||
deserializeValue(item, serializedValue) {
|
||||
if (this.findBySerialized) {
|
||||
return this.findBySerialized(...arguments);
|
||||
}
|
||||
return item === serializedValue;
|
||||
}
|
||||
|
||||
// =static methods
|
||||
|
||||
/**
|
||||
* Configures the specified route with a query parameter of `name` that, when
|
||||
* changed, refreshes the route model and replaces history (rather than
|
||||
* advancing history).
|
||||
* @static
|
||||
* @param {Route} routeClass
|
||||
* @param {string} name
|
||||
*/
|
||||
static setupRouteQueryParams(routeClass, name) {
|
||||
const filterKey = `filter-${name}`;
|
||||
|
||||
// If the route has no query params specified yet, declare them.
|
||||
if (!Object.keys(routeClass.queryParams).length)
|
||||
routeClass.queryParams = {};
|
||||
|
||||
// If the route has no resource filters list specified yet, create one.
|
||||
if (!routeClass.resourceFilterParams) routeClass.resourceFilterParams = [];
|
||||
|
||||
// Add the resource filter query parameter to the route. Resource filter
|
||||
// param changes refresh the model but do not contribute to browser history.
|
||||
// See https://guides.emberjs.com/release/routing/query-params/
|
||||
routeClass.queryParams[filterKey] = {
|
||||
refreshModel: true,
|
||||
replace: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to instantiate an instance of this class.
|
||||
* @static
|
||||
* @param {Route} routeInstance
|
||||
* @param {string} name
|
||||
* @param {array|function} allowed
|
||||
* @param {?array} defaultValue
|
||||
* @param {?function} serialize
|
||||
* @param {?function} findBySerialized
|
||||
* @return {RouteResourceFilter}
|
||||
*/
|
||||
static getOrCreateRouteResourceFilter(
|
||||
routeInstance,
|
||||
name,
|
||||
allowed,
|
||||
defaultValue,
|
||||
serialize,
|
||||
findBySerialized
|
||||
) {
|
||||
const owner = getOwner(routeInstance);
|
||||
const containerKey = `resource-filter:${name}@${routeInstance.routeName}`;
|
||||
const factory = owner.factoryFor(containerKey);
|
||||
if (!factory) {
|
||||
// create factory
|
||||
class AppliedRouteResourceFilter extends this {
|
||||
route = routeInstance;
|
||||
name = name;
|
||||
allowed = allowed;
|
||||
defaultValue = defaultValue;
|
||||
serialize = serialize;
|
||||
findBySerialized = findBySerialized;
|
||||
}
|
||||
owner.register(containerKey, AppliedRouteResourceFilter);
|
||||
}
|
||||
const instance = owner.lookup(containerKey);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This decorator provides a concise way to express a route resource filter.
|
||||
* Resource filter params abstract away a route's query parameter
|
||||
* interface, treating a query param as just an attribute on a route instance.
|
||||
* Getting the attribute value is equivalent to looking up the associated query
|
||||
* parameter. Setting the attribute is equivalent to transitioning to the route
|
||||
* and passing the associated query parameter value.
|
||||
*
|
||||
* Additionally, resource filter params may declare `allowed`, an array
|
||||
* (or function returning an array) containing the set of values the parameter
|
||||
* may take. While unenforced, `allowed` are useful metadata about a resource
|
||||
* filter and may be fetch via `ResourceFilterParamHelper`. An optional default
|
||||
* value may be specified via `defaultValue` passed to the decorator.
|
||||
*
|
||||
* @see ResourceFilterParamHelper
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* export default class MyRoute extends Route {
|
||||
*
|
||||
* @resourceFilter({
|
||||
* allowed: ['active', 'pending'],
|
||||
* defaultvalue: ['active'], // optional
|
||||
* })
|
||||
* status;
|
||||
*
|
||||
* @action
|
||||
* changeStatus(status) {
|
||||
* // Sets query param `filter-status` on the current route, which occurs
|
||||
* // via a route transition.
|
||||
* this.status = status;
|
||||
* // Log the value of query param `filter-status`.
|
||||
* log(this.status);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export function resourceFilter({
|
||||
allowed,
|
||||
defaultValue,
|
||||
serialize,
|
||||
findBySerialized,
|
||||
}) {
|
||||
/**
|
||||
* @param {object} target
|
||||
* @param {string} name
|
||||
* @param {object} descriptor
|
||||
* @return {object{get, set}}
|
||||
*/
|
||||
return function (target, name /*, descriptor*/) {
|
||||
RouteResourceFilter.setupRouteQueryParams(target, name);
|
||||
|
||||
let instance;
|
||||
|
||||
// Override the decorated attribute with a getter and setter.
|
||||
return {
|
||||
/**
|
||||
* Returns the current value of the resource filter.
|
||||
*/
|
||||
get() {
|
||||
instance = RouteResourceFilter.getOrCreateRouteResourceFilter(
|
||||
this,
|
||||
name,
|
||||
allowed,
|
||||
defaultValue,
|
||||
serialize,
|
||||
findBySerialized
|
||||
);
|
||||
return instance.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the resource filter param to the stringified representation
|
||||
* of `value`. NOTE: this is equivalent to transitioning into the
|
||||
* current route, changing the associated query param.
|
||||
* @param value
|
||||
*/
|
||||
set(value) {
|
||||
instance.value = value;
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
+5
-5
@@ -11,11 +11,11 @@ import { action } from '@ember/object';
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* {{#if (has-resource-filter-param-selections 'route.name' 'field')}}
|
||||
* {{#if (has-resource-filter-selections 'route.name' 'field')}}
|
||||
* Field has filter selections.
|
||||
* {{/if}}
|
||||
*/
|
||||
export default class HasResourceFilterParamSelectionsHelper extends Helper {
|
||||
export default class HasRouteResourceFilterSelectionsHelper extends Helper {
|
||||
// =services
|
||||
|
||||
@service router;
|
||||
@@ -44,14 +44,14 @@ export default class HasResourceFilterParamSelectionsHelper extends Helper {
|
||||
|
||||
/**
|
||||
* Returns true if the specified route has a value set for
|
||||
* any resource filter.
|
||||
* any resource filter passed into the helper.
|
||||
* @param {string} routeName
|
||||
* @param {string[]} names - names of resource filter fields
|
||||
* @return {boolean}
|
||||
*/
|
||||
compute([routeName]) {
|
||||
compute([routeName, ...names]) {
|
||||
const owner = getOwner(this);
|
||||
const route = owner.lookup(`route:${routeName}`);
|
||||
const names = route.resourceFilterParams;
|
||||
const selectedValues = names.map((name) => route[name] || null).flat();
|
||||
const anyTruthy = selectedValues.reduce(
|
||||
(previousValue, currentValue) => previousValue || currentValue,
|
||||
+6
-10
@@ -7,15 +7,15 @@ import { action } from '@ember/object';
|
||||
* Use this helper to fetch the current value of a resource filter param,
|
||||
* as well as its allowedValues.
|
||||
*
|
||||
* @see resourceFilterParam (decorator)
|
||||
* @see resourceFilter (decorator)
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* {{#with resource-filter-param 'status' as |param|}}
|
||||
* {{#with resource-filter 'status' as |param|}}
|
||||
* {{param.name}} / {{param.value}} / {{param.selectedValue}}
|
||||
* {{/with}}
|
||||
*/
|
||||
export default class ResourceFilterParamHelper extends Helper {
|
||||
export default class ResourceFilterHelper extends Helper {
|
||||
// =services
|
||||
|
||||
@service router;
|
||||
@@ -50,13 +50,9 @@ export default class ResourceFilterParamHelper extends Helper {
|
||||
*/
|
||||
compute([routeName, name]) {
|
||||
const owner = getOwner(this);
|
||||
// Filter options
|
||||
const route = owner.lookup(`route:${routeName}`);
|
||||
const filterAllowedValuesKey = `filter-allowed-values-${name}`;
|
||||
const allowedValues = route[filterAllowedValuesKey];
|
||||
// Selected filters
|
||||
const rawValue = route[name];
|
||||
const selectedValue = rawValue || null;
|
||||
const containerKey = `resource-filter:${name}@${routeName}`;
|
||||
const resourceFilter = owner.lookup(containerKey);
|
||||
const { allowedValues, value: selectedValue } = resourceFilter;
|
||||
return { name, allowedValues, selectedValue };
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from 'core/decorators/resource-filter-param';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'core/decorators/resource-filter';
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from 'core/helpers/has-resource-filter-param-selections';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'core/helpers/has-resource-filter-selections';
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from 'core/helpers/resource-filter-param';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'core/helpers/resource-filter';
|
||||
@@ -1,79 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const getChannelURL = require('ember-source-channel-url');
|
||||
|
||||
module.exports = async function () {
|
||||
return {
|
||||
useYarn: true,
|
||||
scenarios: [
|
||||
{
|
||||
name: 'ember-lts-3.16',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': '~3.16.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-lts-3.20',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': '~3.20.5',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-release',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('release'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-beta',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('beta'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-canary',
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'ember-source': await getChannelURL('canary'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-default-with-jquery',
|
||||
env: {
|
||||
EMBER_OPTIONAL_FEATURES: JSON.stringify({
|
||||
'jquery-integration': true,
|
||||
}),
|
||||
},
|
||||
npm: {
|
||||
devDependencies: {
|
||||
'@ember/jquery': '^1.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ember-classic',
|
||||
env: {
|
||||
EMBER_OPTIONAL_FEATURES: JSON.stringify({
|
||||
'application-template-wrapper': true,
|
||||
'default-async-observers': false,
|
||||
'template-only-glimmer-components': false,
|
||||
}),
|
||||
},
|
||||
npm: {
|
||||
ember: {
|
||||
edition: 'classic',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -31,25 +31,25 @@
|
||||
"api": "*",
|
||||
"auth": "*",
|
||||
"ember-browser-services": "^2.0.0",
|
||||
"ember-cli-babel": "^7.26.6",
|
||||
"ember-cli-babel": "^7.26.8",
|
||||
"ember-cli-htmlbars": "^5.7.1",
|
||||
"ember-feature-flags": "^6.0.0",
|
||||
"ember-intl": "^5.7.0",
|
||||
"ember-loading": "^0.4.0",
|
||||
"ember-loading": "^2.0.0",
|
||||
"ember-route-action-helper": "^2.0.8",
|
||||
"ember-truth-helpers": "^3.0.0",
|
||||
"rose": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/test-helpers": "^2.4.2",
|
||||
"@ember/test-helpers": "^2.6.0",
|
||||
"@glimmer/component": "^1.0.4",
|
||||
"@glimmer/tracking": "^1.0.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"doctoc": "^2.0.1",
|
||||
"doctoc": "^2.1.0",
|
||||
"ember-auto-import": "^1.12.0",
|
||||
"ember-cli": "~3.28.3",
|
||||
"ember-cli": "~4.1.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
@@ -59,13 +59,11 @@
|
||||
"ember-inline-svg": "^1.0.0",
|
||||
"ember-load-initializers": "^2.1.2",
|
||||
"ember-maybe-import-regenerator": "^1.0.0",
|
||||
"ember-page-title": "^6.2.2",
|
||||
"ember-page-title": "^7.0.0",
|
||||
"ember-qunit": "^5.1.4",
|
||||
"ember-resolver": "^8.0.3",
|
||||
"ember-source": "~3.28.4",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^3.7.0",
|
||||
"ember-try": "^1.4.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-ember": "^10.5.4",
|
||||
@@ -81,7 +79,7 @@
|
||||
"qunit-dom": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12 <=17"
|
||||
"node": "12.* || 14.* || 16.*"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -36,3 +36,4 @@ toggle-secret: Toggle secret visibility
|
||||
minimize: Minimize
|
||||
fullscreen: Fullscreen
|
||||
clear-all-filters: Clear all filters
|
||||
learn-more: Learn more
|
||||
@@ -30,12 +30,17 @@ titles:
|
||||
label: Label
|
||||
value: Value
|
||||
no-results: No Results
|
||||
choose-a-provider: Choose a provider
|
||||
provider: Provider
|
||||
descriptions:
|
||||
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
||||
origin-initialization: To get started, please enter your origin URL
|
||||
create-but-not-list: You do not have permission to list {resource}, but you can create new instances.
|
||||
neither-list-nor-create: You do not have permission to list or create {resource}.
|
||||
no-results: We found no matching records.
|
||||
choose-a-provider: There are several built-in plugins for the most common providers. You can choose from on of the included plugins or use a custom plugin.
|
||||
choose-a-provider-azure: Boundary does not yet support rotating credentials.
|
||||
provider: This is the provider for this host catalog. The format of the host set filters are specific to each provider.
|
||||
questions:
|
||||
delete-confirm: Are you sure you want to delete this resource?
|
||||
remove-confirm: Are you sure you want to remove this association?
|
||||
|
||||
@@ -118,3 +118,28 @@ callback_url:
|
||||
filter:
|
||||
label: Filter
|
||||
help: A boolean expression defining a filter run against the provided information.
|
||||
tenant_id:
|
||||
label: Tenant ID
|
||||
help: The tenant (directory) ID for your Azure Active Directory application.
|
||||
subscription_id:
|
||||
label: Subscription ID
|
||||
help: The subscription ID for the subscription with read access.
|
||||
access_key_id:
|
||||
label: Access Key ID
|
||||
help: The access key ID found on the Security Credentials page in AWS.
|
||||
secret_access_key:
|
||||
label: Secret Access Key
|
||||
help: The secret access key found on the Security Credentials page in AWS.
|
||||
region:
|
||||
label: Region
|
||||
secret:
|
||||
label: Client secret value
|
||||
help: The client secret value for the client you want Boundary to use
|
||||
disable_credential_rotation:
|
||||
label: Disable credential rotation
|
||||
dns_names:
|
||||
label: Latest DNS Names
|
||||
host:
|
||||
label: Host
|
||||
ip_addresses:
|
||||
label: Latest IP Addresses
|
||||
@@ -78,13 +78,25 @@ host-catalog:
|
||||
title: Host Catalog
|
||||
title_plural: Host Catalogs
|
||||
description: A host catalog is a collection of hosts and host sets.
|
||||
type: Type
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Host Catalogs
|
||||
actions:
|
||||
delete: Delete Host Catalog
|
||||
help:
|
||||
static: Provide a list of hosts and host sets with static addresses
|
||||
dynamic: Automate discovery of target hosts and services on Azure or AWS
|
||||
types:
|
||||
static: Static
|
||||
unknown: Plugin
|
||||
aws: AWS
|
||||
azure: Azure
|
||||
dynamic: Dynamic
|
||||
form:
|
||||
azure_client_id:
|
||||
label: Client ID
|
||||
help: The client ID for the client you want Boundary to use.
|
||||
host-set:
|
||||
title: Host Set
|
||||
title_plural: Host Sets
|
||||
@@ -95,6 +107,13 @@ host-set:
|
||||
delete: Delete Host Set
|
||||
create-and-add-host: Create and Add Host
|
||||
add-hosts: Add Existing Host
|
||||
form:
|
||||
filter:
|
||||
label: Filter
|
||||
help: Create a filter to select resources using values such as tag name or tag value. Filter format is specific to each provider
|
||||
preferred_endpoints:
|
||||
label: Preferred Endpoints
|
||||
help: Selects addresses of hosts, in cidr:<valid IPv4/6 CIDR> or dns:<globbed name> format
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Host Sets
|
||||
@@ -109,8 +128,6 @@ host-set:
|
||||
add-none:
|
||||
title: No Hosts Available
|
||||
description: No hosts available for selection.
|
||||
types:
|
||||
static: Static
|
||||
host:
|
||||
title: Host
|
||||
title_plural: Hosts
|
||||
@@ -128,8 +145,6 @@ host:
|
||||
add: Add Host
|
||||
create: New Host
|
||||
delete: Delete Host
|
||||
types:
|
||||
static: Static
|
||||
session:
|
||||
title: Session
|
||||
title_plural: Sessions
|
||||
@@ -262,6 +277,7 @@ target:
|
||||
add-credential-sources: Add Credential Sources
|
||||
types:
|
||||
tcp: TCP
|
||||
ssh: SSH
|
||||
host-source:
|
||||
title: Host Source
|
||||
title_plural: Host Sources
|
||||
|
||||
@@ -17,9 +17,4 @@ main.js
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
|
||||
index.js
|
||||
@@ -19,15 +19,9 @@
|
||||
/.travis.yml
|
||||
/.watchmanconfig
|
||||
/bower.json
|
||||
/config/ember-try.js
|
||||
/CONTRIBUTING.md
|
||||
/ember-cli-build.js
|
||||
/testem.js
|
||||
/tests/
|
||||
/yarn.lock
|
||||
.gitkeep
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -14,8 +14,3 @@
|
||||
/coverage/
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -4,4 +4,9 @@
|
||||
...attributes
|
||||
>
|
||||
{{yield}}
|
||||
{{#if @link}}
|
||||
{{#if @linkText}}
|
||||
<a href={{@link}} target='_blank' rel='noreferrer noopener'>{{@linkText}}</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</span>
|
||||
@@ -11,7 +11,7 @@ Displays helpful messages related to a form field. This component is used
|
||||
by form fields and should not be used directly.
|
||||
|
||||
```html
|
||||
<Rose::Form::HelperText @id="helper-text-{{field.id}}">
|
||||
<Rose::Form::HelperText @id="helper-text-{{field.id}}" @link='link' @linkText='link text goes here'>
|
||||
Helper text
|
||||
</Rose::Form::HelperText>
|
||||
```
|
||||
@@ -19,12 +19,14 @@ by form fields and should not be used directly.
|
||||
<Preview>
|
||||
<Story name="HelperText">{{
|
||||
template: hbs`
|
||||
<Rose::Form::HelperText @error={{this.error}}>
|
||||
<Rose::Form::HelperText @error={{this.error}} @link={{this.link}} @linkText={{this.linkText}}>
|
||||
Helper text
|
||||
</Rose::Form::HelperText>
|
||||
`,
|
||||
context: {
|
||||
error: boolean('error', false)
|
||||
error: boolean('error', false),
|
||||
link: text('link', 'http://www.example.net/'),
|
||||
linkText: text('linkText', 'sample link text')
|
||||
},
|
||||
}}</Story>
|
||||
</Preview>
|
||||
@@ -29,7 +29,7 @@
|
||||
</Rose::Form::Label>
|
||||
|
||||
{{#if @helperText}}
|
||||
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}}>
|
||||
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}} @link={{@link}} @linkText={{@linkText}}>
|
||||
{{@helperText}}
|
||||
</Rose::Form::HelperText>
|
||||
{{/if}}
|
||||
|
||||
@@ -16,6 +16,8 @@ Renders a simple form input.
|
||||
@value="info@example.com"
|
||||
@label="Email"
|
||||
@helperText="Enter your email"
|
||||
@link='www.example.net'
|
||||
@linkText='sample link text'
|
||||
@error={{false}}
|
||||
@disabled={{false}}
|
||||
@icon='flight-icons/svg/search-16'
|
||||
@@ -33,6 +35,8 @@ Or a fully contextual component without a wrapper, for maximum flexibility.
|
||||
@value="info@example.com"
|
||||
@label="Email"
|
||||
@helperText="Enter your email"
|
||||
@link='www.example.net'
|
||||
@linkText='sample link text'
|
||||
@error={{false}}
|
||||
@disabled={{false}}
|
||||
readonly={{false}}
|
||||
@@ -50,6 +54,8 @@ Or a fully contextual component without a wrapper, for maximum flexibility.
|
||||
@value={{value}}
|
||||
@label={{label}}
|
||||
@helperText={{helperText}}
|
||||
@link={{link}}
|
||||
@linkText={{linkText}}
|
||||
@error={{false}}
|
||||
@disabled={{disabled}}
|
||||
readonly={{read-only}}
|
||||
@@ -70,6 +76,8 @@ Or a fully contextual component without a wrapper, for maximum flexibility.
|
||||
value: text('value', 'Value'),
|
||||
label: text('label', 'Label'),
|
||||
helperText: text('helperText', 'Helper text'),
|
||||
link: text('link', 'link goes here'),
|
||||
linkText: text('linkText', 'link text'),
|
||||
disabled: boolean('disabled', false),
|
||||
'read-only': boolean('readonly', false),
|
||||
icon: select(
|
||||
@@ -93,6 +101,8 @@ Or a fully contextual component without a wrapper, for maximum flexibility.
|
||||
@value={{value}}
|
||||
@label={{label}}
|
||||
@helperText={{helperText}}
|
||||
@link={{link}}
|
||||
@linkText={{linkText}}
|
||||
@error={{true}}
|
||||
@disabled={{disabled}}
|
||||
readonly={{read-only}}
|
||||
@@ -117,6 +127,8 @@ Or a fully contextual component without a wrapper, for maximum flexibility.
|
||||
value: text('value', 'Value'),
|
||||
label: text('label', 'Label'),
|
||||
helperText: text('helperText', 'Helper text'),
|
||||
link: text('link', 'link goes here'),
|
||||
linkText: text('linkText', 'link text'),
|
||||
disabled: boolean('disabled', false),
|
||||
'read-only': boolean('readonly', false),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import RoseFormRadioRadioComponent from '../radio';
|
||||
|
||||
export default class RoseFormRadioCardComponent extends RoseFormRadioRadioComponent {
|
||||
// =attributes
|
||||
layout = this.args.layout;
|
||||
className = `rose-form-radio-card ${this.layout}`;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { text, boolean, select } from '@storybook/addon-knobs';
|
||||
|
||||
<Meta title="Rose|Form/Radio/Radio/Card" component="Form/Radio/Radio/Card" />
|
||||
|
||||
# Form Radiofield in a wide format
|
||||
|
||||
Renders a form radiofield in a Wide format.
|
||||
|
||||
```htmlbars
|
||||
<Rose::Form::Radio::Card
|
||||
@label="Yes"
|
||||
@error={{false}}
|
||||
@value="yes-agree"
|
||||
@selectedValue="yes-agree"
|
||||
@disabled={{false}}
|
||||
@changed={{fn}}
|
||||
@layout='wide'
|
||||
/>
|
||||
```
|
||||
|
||||
<Preview>
|
||||
<Story name="Wide" height="10rem">{{
|
||||
template: hbs`
|
||||
<Rose::Form::Radio::Card
|
||||
@label={{label}}
|
||||
@error={{error}}
|
||||
@value={{value}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@helperText={{helperText}}
|
||||
@layout='wide'
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
label: text('text', 'Azure'),
|
||||
error: boolean('error', false),
|
||||
value: text('value', 'value and selectedValue must match to select radiofield'),
|
||||
selectedValue: text(
|
||||
'selectedValue',
|
||||
'value and selectedValue must match to select radiofield'
|
||||
),
|
||||
helperText: text('helperText', 'Record financial transactions using'),
|
||||
disabled: boolean('disabled', false),
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
|
||||
Renders a form radiofield in a tile format.
|
||||
|
||||
```htmlbars
|
||||
<Rose::Form::Radio::Card
|
||||
@label="Yes"
|
||||
@error={{false}}
|
||||
@value="yes-agree"
|
||||
@selectedValue="yes-agree"
|
||||
@disabled={{false}}
|
||||
@changed={{fn}}
|
||||
@layout='tile'
|
||||
/>
|
||||
```
|
||||
|
||||
<Preview>
|
||||
<Story name="Tile" height="10rem">{{
|
||||
template: hbs`
|
||||
<Rose::Form::Radio::Card
|
||||
@label={{label}}
|
||||
@error={{error}}
|
||||
@value={{value}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@helperText={{helperText}}
|
||||
@layout='tile'
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
label: text('text', 'Azure'),
|
||||
error: boolean('error', false),
|
||||
value: text('value', 'value and selectedValue must match to select radiofield'),
|
||||
selectedValue: text(
|
||||
'selectedValue',
|
||||
'value and selectedValue must match to select radiofield'
|
||||
),
|
||||
helperText: text('helperText', 'Record financial transactions using'),
|
||||
disabled: boolean('disabled', false),
|
||||
icon: select(
|
||||
'icon',
|
||||
{
|
||||
'none': null,
|
||||
'flight-icons/svg/azure-color-16': 'flight-icons/svg/azure-color-16',
|
||||
},
|
||||
null
|
||||
),
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
@@ -1,3 +1,4 @@
|
||||
{{yield (hash
|
||||
radio=(component 'rose/form/radio/radio' name=@name selectedValue=@selectedValue disabled=@disabled changed=@changed inline=@inline)
|
||||
)}}
|
||||
card=(component 'rose/form/radio/card' name=@name selectedValue=@selectedValue disabled=@disabled changed=@changed layout=@layout)
|
||||
)}}
|
||||
@@ -81,3 +81,173 @@ Renders a form radiofield group.
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
```htmlbars
|
||||
<Rose::Form::Radio::Group
|
||||
@disabled={{false}}
|
||||
@name={{name}}
|
||||
@selectedValue="disagree"
|
||||
@changed={{fn}}
|
||||
@inline={{false}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<radioGroup.Card
|
||||
@label="Agree"
|
||||
@value="agree"
|
||||
@layout='tile'
|
||||
@helperText="dwsd"
|
||||
/>
|
||||
<radioGroup.Card
|
||||
@label="disagree"
|
||||
@value="disagree"
|
||||
@layout='tile'
|
||||
@helperText="dwsd"
|
||||
/>
|
||||
<radioGroup.Card
|
||||
@label="maybe"
|
||||
@value="maybe"
|
||||
@layout='tile'
|
||||
@helperText="dwsd"
|
||||
/>
|
||||
</Rose::Form::Radio::Group>
|
||||
```
|
||||
<Preview>
|
||||
<Story name="Card: Tile" height="10rem">{{
|
||||
template: hbs`
|
||||
<Rose::Form::Radio::Group
|
||||
@disabled={{disabled}}
|
||||
@name={{name}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@changed={{changed}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<radioGroup.card
|
||||
@label={{firstFieldLabel}}
|
||||
@value={{firstFieldValue}}
|
||||
@icon={{firstFieldIcon}}
|
||||
@layout='tile'
|
||||
/>
|
||||
<radioGroup.card
|
||||
@label={{secondFieldLabel}}
|
||||
@value={{secondFieldValue}}
|
||||
@icon={{secondFieldIcon}}
|
||||
@layout="tile"
|
||||
/>
|
||||
<radioGroup.card
|
||||
@label={{thirdFieldLabel}}
|
||||
@value={{thirdFieldValue}}
|
||||
@icon={{thirdFieldIcon}}
|
||||
@layout="tile"
|
||||
/>
|
||||
</Rose::Form::Radio::Group>
|
||||
`,
|
||||
context: {
|
||||
name: text('name', 'mustagree'),
|
||||
disabled: boolean('disabled', false),
|
||||
selectedValue: text('selectedValue', 'disagree'),
|
||||
firstFieldLabel: text('first label', 'Agree'),
|
||||
firstFieldValue: text('first value', 'agree'),
|
||||
firstFieldIcon: select(
|
||||
'firstFieldIcon',
|
||||
{
|
||||
'none': null,
|
||||
'flight-icons/svg/azure-color-16': 'flight-icons/svg/azure-color-16',
|
||||
},
|
||||
null
|
||||
),
|
||||
secondFieldIcon: select(
|
||||
'secondFieldIcon',
|
||||
{
|
||||
'none': null,
|
||||
'flight-icons/svg/azure-color-16': 'flight-icons/svg/azure-color-16',
|
||||
},
|
||||
null
|
||||
),
|
||||
thirdFieldIcon: select(
|
||||
'thirdFieldIcon',
|
||||
{
|
||||
'none': null,
|
||||
'flight-icons/svg/azure-color-16': 'flight-icons/svg/azure-color-16',
|
||||
},
|
||||
null
|
||||
),
|
||||
secondFieldLabel: text('second label', 'Maybe'),
|
||||
secondFieldValue: text('second value', 'maybe'),
|
||||
thirdFieldLabel: text('third label', 'Disagree'),
|
||||
thirdFieldValue: text('third value', 'disagree'),
|
||||
changed: action('changed')
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
```htmlbars
|
||||
<Rose::Form::Radio::Group
|
||||
@disabled={{false}}
|
||||
@name={{name}}
|
||||
@selectedValue="disagree"
|
||||
@changed={{fn}}
|
||||
@inline={{false}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<radioGroup.Card
|
||||
@label="Agree"
|
||||
@value="agree"
|
||||
@layout='wide'
|
||||
/>
|
||||
<radioGroup.Card
|
||||
@label="disagree"
|
||||
@value="disagree"
|
||||
@layout='wide'
|
||||
/>
|
||||
</Rose::Form::Radio::Group>
|
||||
```
|
||||
<Preview>
|
||||
<Story name="Card: Wide" height="10rem">{{
|
||||
template: hbs`
|
||||
<Rose::Form::Radio::Group
|
||||
@disabled={{disabled}}
|
||||
@name={{name}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@changed={{changed}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<radioGroup.card
|
||||
@label={{firstFieldLabel}}
|
||||
@value={{firstFieldValue}}
|
||||
@icon={{firstFieldIcon}}
|
||||
@layout="wide"
|
||||
/>
|
||||
<radioGroup.card
|
||||
@label={{secondFieldLabel}}
|
||||
@value={{secondFieldValue}}
|
||||
@layout="wide"
|
||||
/>
|
||||
<radioGroup.card
|
||||
@label={{thirdFieldLabel}}
|
||||
@value={{thirdFieldValue}}
|
||||
@layout="wide"
|
||||
/>
|
||||
</Rose::Form::Radio::Group>
|
||||
`,
|
||||
context: {
|
||||
name: text('name', 'mustagree'),
|
||||
disabled: boolean('disabled', false),
|
||||
selectedValue: text('selectedValue', 'disagree'),
|
||||
firstFieldLabel: text('first label', 'Agree'),
|
||||
firstFieldValue: text('first value', 'agree'),
|
||||
firstFieldIcon: select(
|
||||
'firstFieldIcon',
|
||||
{
|
||||
'none': null,
|
||||
'flight-icons/svg/azure-color-16': 'flight-icons/svg/azure-color-16',
|
||||
},
|
||||
null
|
||||
),
|
||||
secondFieldLabel: text('second label', 'Maybe'),
|
||||
secondFieldValue: text('second value', 'maybe'),
|
||||
thirdFieldLabel: text('third label', 'Disagree'),
|
||||
thirdFieldValue: text('third value', 'disagree'),
|
||||
changed: action('changed')
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="rose-form-radio{{if @error " error"}} {{if @inline "inline"}}">
|
||||
<div class="{{this.className}} {{if @error " error"}} {{if @inline "inline"}}">
|
||||
|
||||
<RadioButton
|
||||
...attributes
|
||||
@@ -22,7 +22,12 @@
|
||||
{{/if}}
|
||||
|
||||
{{@label}}
|
||||
|
||||
<span class="radio-card-overlay"></span>
|
||||
{{#if @helperText}}
|
||||
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}} @link={{@link}} @linkText={{@linkText}}>
|
||||
{{@helperText}}
|
||||
</Rose::Form::HelperText>
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
{{yield}}
|
||||
|
||||
@@ -3,6 +3,6 @@ import { generateComponentID } from '../../../../../utilities/component-auto-id'
|
||||
|
||||
export default class RoseFormRadioRadioComponent extends Component {
|
||||
// =attributes
|
||||
|
||||
className = 'rose-form-radio';
|
||||
id = generateComponentID();
|
||||
}
|
||||
@@ -29,12 +29,14 @@ Renders a form radiofield.
|
||||
@selectedValue={{selectedValue}}
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@helperText={{helperText}}
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
label: text('text', 'Radiofield'),
|
||||
error: boolean('error', false),
|
||||
value: text('value', 'value and selectedValue must match to select radiofield'),
|
||||
helperText: text('helperText', 'Helper text'),
|
||||
selectedValue: text(
|
||||
'selectedValue',
|
||||
'value and selectedValue must match to select radiofield'
|
||||
@@ -63,6 +65,7 @@ Renders a form radiofield.
|
||||
@disabled={{false}}
|
||||
@changed={{fn}}
|
||||
@inline={{true}}
|
||||
@helperText={{helperText}}
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -77,6 +80,7 @@ Renders a form radiofield.
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@inline={{inline}}
|
||||
@helperText={{helperText}}
|
||||
/>
|
||||
<Rose::Form::Radio::Radio
|
||||
@label='Radiofield2'
|
||||
@@ -86,6 +90,7 @@ Renders a form radiofield.
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@inline={{inline}}
|
||||
@helperText={{helperText}}
|
||||
/>
|
||||
<Rose::Form::Radio::Radio
|
||||
@label='Radiofield3'
|
||||
@@ -95,6 +100,7 @@ Renders a form radiofield.
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@inline={{inline}}
|
||||
@helperText={{helperText}}
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
@@ -104,6 +110,7 @@ Renders a form radiofield.
|
||||
'selectedValue',
|
||||
'value and selectedValue must match to select radiofield'
|
||||
),
|
||||
helperText: text('helperText', 'Helper text'),
|
||||
disabled: boolean('disabled', false),
|
||||
icon: select(
|
||||
'icon',
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</Rose::Form::Label>
|
||||
|
||||
{{#if @helperText}}
|
||||
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}}>
|
||||
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}} @link={{@link}} @linkText={{@linkText}}>
|
||||
{{@helperText}}
|
||||
</Rose::Form::HelperText>
|
||||
{{/if}}
|
||||
|
||||
@@ -18,6 +18,8 @@ Renders a simple form textarea.
|
||||
@error={{false}}
|
||||
@disabled={{false}}
|
||||
readonly={{false}}
|
||||
@link='www.example.net'
|
||||
@linkText='link text goes here'
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -32,12 +34,16 @@ Renders a simple form textarea.
|
||||
@error={{false}}
|
||||
@disabled={{disabled}}
|
||||
readonly={{read-only}}
|
||||
@link='www.example.net'
|
||||
@linkText='link text goes here'
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
value: text('value', 'Value'),
|
||||
label: text('label', 'Label'),
|
||||
helperText: text('helperText', 'Helper text'),
|
||||
link: text('link', 'link'),
|
||||
linkText: text('linkText', 'link text goes here'),
|
||||
disabled: boolean('disabled', false),
|
||||
'read-only': boolean('readonly', false),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<nav ...attributes class="rose-nav-breadcrumbs" aria-label="breadcrumbs">
|
||||
{{yield (hash
|
||||
link=(component "link-to" class="rose-nav-breadcrumbs-link")
|
||||
link=(component "rose/nav/link")
|
||||
)}}
|
||||
</nav>
|
||||
@@ -0,0 +1,9 @@
|
||||
{{#if @model}}
|
||||
<LinkTo @route={{@route}} @model={{@model}} class="rose-nav-link">
|
||||
{{yield}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<LinkTo @route={{@route}} class="rose-nav-link">
|
||||
{{yield}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { text, boolean, select } from '@storybook/addon-knobs';
|
||||
|
||||
<Meta title="Rose|Nav|Link" component="Nav/Link" />
|
||||
|
||||
# Generic Navigation Link
|
||||
|
||||
Proxies the built-in `LinkTo` component, applying a class `rose-nav-link`.
|
||||
|
||||
```html
|
||||
<Rose::Nav::Link @route='index'>Text</nav.link>
|
||||
```
|
||||
|
||||
<Preview>
|
||||
<Story name="Basic Nav Link">{{
|
||||
template: hbs`
|
||||
<Rose::Nav::Link @route='index'>Text</nav.link>
|
||||
`,
|
||||
context: {},
|
||||
}}</Story>
|
||||
</Preview>
|
||||
@@ -12,7 +12,7 @@
|
||||
{{/if}}
|
||||
|
||||
{{yield (hash
|
||||
link=(component "link-to" class="rose-nav-link")
|
||||
link=(component "rose/nav/link")
|
||||
)}}
|
||||
|
||||
</nav>
|
||||
@@ -3,7 +3,7 @@
|
||||
class="rose-nav-tabs">
|
||||
|
||||
{{yield (hash
|
||||
link=(component "link-to" class="rose-nav-link")
|
||||
link=(component "rose/nav/link")
|
||||
)}}
|
||||
|
||||
</nav>
|
||||
Loaded 100 of 360 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user