Compare commits
588 Commits
2022.11.11
...
3000.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3aa3b25b0 | ||
|
|
d944b09c51 | ||
|
|
b9851adfde | ||
|
|
45f9c18bc3 | ||
|
|
5d947f5a32 | ||
|
|
754d216827 | ||
|
|
3d902295ad | ||
|
|
5f8cd60736 | ||
|
|
ae360100ce | ||
|
|
4a851355a8 | ||
|
|
54d3c65df3 | ||
|
|
58ba8eeeb9 | ||
|
|
e1e9cd9c35 | ||
|
|
2a5fe71458 | ||
|
|
cbb163726e | ||
|
|
6836062b00 | ||
|
|
339dbe6dbd | ||
|
|
a24a7fbd01 | ||
|
|
c9c781b197 | ||
|
|
b0f24811b2 | ||
|
|
3884dc6d0a | ||
|
|
91dfe2437e | ||
|
|
60814b97e2 | ||
|
|
b330fbd1a5 | ||
|
|
7d5fa999e5 | ||
|
|
a464e6a445 | ||
|
|
a26a8bb032 | ||
|
|
7345744e41 | ||
|
|
570c0ba087 | ||
|
|
60c0c5db27 | ||
|
|
4a847f0587 | ||
|
|
6b342cbedb | ||
|
|
f46a02fced | ||
|
|
3dd7aaff88 | ||
|
|
7d4edeb60a | ||
|
|
387f1d9c1a | ||
|
|
c526fa323e | ||
|
|
17c716c599 | ||
|
|
c5b49b33ab | ||
|
|
2a73b58255 | ||
|
|
a62d58f119 | ||
|
|
e02ce2be4e | ||
|
|
21ad5871ce | ||
|
|
d4d3193c1d | ||
|
|
10e5a92cbe | ||
|
|
a06299bd9e | ||
|
|
2d4a3fc048 | ||
|
|
81ef166d78 | ||
|
|
d4fe9eaa79 | ||
|
|
af75afa82f | ||
|
|
b91c0d7e45 | ||
|
|
e922baa2e0 | ||
|
|
fcd1879d09 | ||
|
|
a8ac719af7 | ||
|
|
b99b3f4345 | ||
|
|
b16ce074ea | ||
|
|
54447774d1 | ||
|
|
260477e5e8 | ||
|
|
e559c32c37 | ||
|
|
2c7b33b730 | ||
|
|
1970311ff5 | ||
|
|
433456986d | ||
|
|
e38361f3d7 | ||
|
|
1ffdd93ddf | ||
|
|
18c5599704 | ||
|
|
224d7f40ed | ||
|
|
1357eae9b8 | ||
|
|
b2e7509959 | ||
|
|
cebab32514 | ||
|
|
7110399d41 | ||
|
|
74f0930dcc | ||
|
|
c20eea29cd | ||
|
|
8c073bf45f | ||
|
|
fcfa007cec | ||
|
|
b83b7a4c42 | ||
|
|
0a7f3f3226 | ||
|
|
633e513697 | ||
|
|
eb2721c023 | ||
|
|
765c698a9b | ||
|
|
c19428f6b6 | ||
|
|
f02982b451 | ||
|
|
6ffb0cedbc | ||
|
|
775b3d3ca6 | ||
|
|
6ad001619d | ||
|
|
db28e8915b | ||
|
|
bb4969c9ac | ||
|
|
aef70c0e1b | ||
|
|
27c287c2de | ||
|
|
ec1f974f67 | ||
|
|
4ccfd0f993 | ||
|
|
c5eaa35fb0 | ||
|
|
24ba4fb574 | ||
|
|
b742bd89c4 | ||
|
|
2fcc0a63a0 | ||
|
|
ba29325c15 | ||
|
|
88f639d29f | ||
|
|
fa44e958d8 | ||
|
|
182548e0dc | ||
|
|
c3097e40db | ||
|
|
2b66414a93 | ||
|
|
7da8a5bc38 | ||
|
|
2bc45e9a09 | ||
|
|
fd3c6087f0 | ||
|
|
c0b8dd71db | ||
|
|
1164e5fae2 | ||
|
|
902d4ed819 | ||
|
|
2320a56dd9 | ||
|
|
2981fc4c1f | ||
|
|
8865331da2 | ||
|
|
709d6ac2ad | ||
|
|
8d4e335dda | ||
|
|
44a9de080c | ||
|
|
9bb17badad | ||
|
|
ff31abe66c | ||
|
|
b4c886d5d3 | ||
|
|
e0bc1d86f6 | ||
|
|
270f20ec75 | ||
|
|
486253b253 | ||
|
|
6b9c6c8b9c | ||
|
|
1275934ac1 | ||
|
|
bbc6095e36 | ||
|
|
7906f2d363 | ||
|
|
d45bd887c2 | ||
|
|
7788f58aac | ||
|
|
f3bc82311d | ||
|
|
cae5d296ca | ||
|
|
5cd5bd2a25 | ||
|
|
6550223ee4 | ||
|
|
39368d511a | ||
|
|
0a4bcc6423 | ||
|
|
56ab1cec8f | ||
|
|
12f87ca6e1 | ||
|
|
2fc7c23416 | ||
|
|
2cf538bab1 | ||
|
|
906b6c5783 | ||
|
|
6d43ebef44 | ||
|
|
c04203e671 | ||
|
|
bf93707787 | ||
|
|
f0d70f0c15 | ||
|
|
17d9e29f19 | ||
|
|
be7168d9c5 | ||
|
|
d36b23832c | ||
|
|
b6429e9bc7 | ||
|
|
7ddc112b2c | ||
|
|
10a473ca1c | ||
|
|
c7207d1ee6 | ||
|
|
c585762ba8 | ||
|
|
476838d59a | ||
|
|
f0b1cefb72 | ||
|
|
b4a555e3da | ||
|
|
79a4119351 | ||
|
|
851d0dac84 | ||
|
|
655a7f205d | ||
|
|
13e48dded2 | ||
|
|
344df3739d | ||
|
|
0d981773b3 | ||
|
|
8d4ddd36cd | ||
|
|
209234c09f | ||
|
|
9bcb2d80dc | ||
|
|
de504f5f80 | ||
|
|
8fe91101db | ||
|
|
8717997b0e | ||
|
|
1bfb7c4b28 | ||
|
|
964dc7b48a | ||
|
|
ceb0a78180 | ||
|
|
9117163316 | ||
|
|
d3ad811ac5 | ||
|
|
ee26fe6b50 | ||
|
|
2950b59514 | ||
|
|
be5ddda020 | ||
|
|
16b07283b0 | ||
|
|
d7814ff6df | ||
|
|
be9b2a7c78 | ||
|
|
80e5b5b0c1 | ||
|
|
0283b51eca | ||
|
|
1af2e92132 | ||
|
|
71ad5d2e3a | ||
|
|
32b5fee108 | ||
|
|
de81ec00fd | ||
|
|
7cc07158ab | ||
|
|
3b8976fd51 | ||
|
|
fb7e650267 | ||
|
|
6a7187fb5b | ||
|
|
b31cdf15a2 | ||
|
|
6e0e0e8133 | ||
|
|
706441799f | ||
|
|
0b66bc7bbd | ||
|
|
86e876a9c9 | ||
|
|
8b33061bbb | ||
|
|
411f1bff1c | ||
|
|
eee4c4e404 | ||
|
|
a187c8c8a0 | ||
|
|
35dca50863 | ||
|
|
00856f15a7 | ||
|
|
d1c67a9dd8 | ||
|
|
6c289506c2 | ||
|
|
5d82ae7680 | ||
|
|
9737886839 | ||
|
|
1d9502d800 | ||
|
|
c0a18f82a5 | ||
|
|
9723a38ca1 | ||
|
|
8258a758d2 | ||
|
|
044613ae9a | ||
|
|
76c9171356 | ||
|
|
f8c330aae3 | ||
|
|
d4bd7dd586 | ||
|
|
fa20f6a63a | ||
|
|
b9ce695616 | ||
|
|
3f1dbf1130 | ||
|
|
6413e51cf5 | ||
|
|
defcf6d26e | ||
|
|
2671983c43 | ||
|
|
dc9653307b | ||
|
|
eb91eb33d5 | ||
|
|
ddb803d9b5 | ||
|
|
c9095b4d67 | ||
|
|
40158eda71 | ||
|
|
bbbbfceeb3 | ||
|
|
9ca1940834 | ||
|
|
37160a91f3 | ||
|
|
274d036f74 | ||
|
|
1fe0e49adb | ||
|
|
7a7a07d9ad | ||
|
|
5b5fca0837 | ||
|
|
fab0264d9b | ||
|
|
8d839ee6ce | ||
|
|
90efbf3159 | ||
|
|
17dd1b4158 | ||
|
|
e5f6d8ff50 | ||
|
|
e183910b88 | ||
|
|
8cd5b9fb46 | ||
|
|
6958445f83 | ||
|
|
ef91294d5e | ||
|
|
a36f286f8a | ||
|
|
d9e921950f | ||
|
|
ffcd19e748 | ||
|
|
d0eb132b95 | ||
|
|
1cf971c092 | ||
|
|
49f745be68 | ||
|
|
b50824a705 | ||
|
|
69d1cc75a7 | ||
|
|
a54ea505c9 | ||
|
|
a1563b72ae | ||
|
|
31d7168aac | ||
|
|
09016e1d5f | ||
|
|
652882350c | ||
|
|
20c4423799 | ||
|
|
bb90a5da92 | ||
|
|
3ca3a2dd3c | ||
|
|
510c48e1af | ||
|
|
3ac809c234 | ||
|
|
9dd33bc3f9 | ||
|
|
897cc0e034 | ||
|
|
482ef0e5e8 | ||
|
|
e0678fc0a9 | ||
|
|
9cb5574b99 | ||
|
|
c18b91f684 | ||
|
|
6622a6ded4 | ||
|
|
fb6aaa52c7 | ||
|
|
a1adc2a85d | ||
|
|
3d9cb621dd | ||
|
|
943b4c75aa | ||
|
|
fb972fae55 | ||
|
|
eb4f28dfda | ||
|
|
e36fedf4b2 | ||
|
|
362a97c59e | ||
|
|
c82beb61a9 | ||
|
|
00a8a0bf69 | ||
|
|
ffc17dd73b | ||
|
|
238abc95ad | ||
|
|
ac0f3ab6f8 | ||
|
|
4bac315568 | ||
|
|
daa48b5a73 | ||
|
|
c70cc864ee | ||
|
|
dc7ff40da6 | ||
|
|
046ffaecf4 | ||
|
|
3904f8563d | ||
|
|
18423a9888 | ||
|
|
8fbbd9b32c | ||
|
|
7e34efd453 | ||
|
|
9d33e62d34 | ||
|
|
6b2bc0adc0 | ||
|
|
80083fedab | ||
|
|
1ab35fdb36 | ||
|
|
f43ece4263 | ||
|
|
ea1ce82ded | ||
|
|
c24adaafcb | ||
|
|
c42875b107 | ||
|
|
447ad36d4d | ||
|
|
71dc467b31 | ||
|
|
8625e1fc0a | ||
|
|
638e5b7fe1 | ||
|
|
f467c69e8f | ||
|
|
8fd98874e2 | ||
|
|
dc6f6c2896 | ||
|
|
a783fc8cd4 | ||
|
|
6a16a7c6c2 | ||
|
|
ceb215a6dc | ||
|
|
d6cb634824 | ||
|
|
500419307b | ||
|
|
12cf0013e2 | ||
|
|
318d4fe0d0 | ||
|
|
a3aee3603f | ||
|
|
7c6f36c600 | ||
|
|
dde8a9cbb6 | ||
|
|
dfca712cb1 | ||
|
|
5057ba2e1c | ||
|
|
8f1dfffa49 | ||
|
|
6991724258 | ||
|
|
09e1de06ce | ||
|
|
5a644b0856 | ||
|
|
86b2187236 | ||
|
|
fafacfeafb | ||
|
|
362019738d | ||
|
|
1a0ba6c6b1 | ||
|
|
4c2cc5da1c | ||
|
|
30e2aa141b | ||
|
|
3768a57eaa | ||
|
|
db5de9be97 | ||
|
|
f60ab6ce85 | ||
|
|
43c48aef17 | ||
|
|
8341b1d6d0 | ||
|
|
dd11961a11 | ||
|
|
3de819a0e9 | ||
|
|
0b546eaeb5 | ||
|
|
558e1819bf | ||
|
|
9d6afa2fe7 | ||
|
|
5f3a967515 | ||
|
|
179f1be19a | ||
|
|
f13a5c070a | ||
|
|
9476d052b6 | ||
|
|
1d446ace04 | ||
|
|
3a8d8706a6 | ||
|
|
910418925a | ||
|
|
1b539df2aa | ||
|
|
f5794e57ee | ||
|
|
7dd1d0a7fc | ||
|
|
ce670cf58c | ||
|
|
c4d1a2a105 | ||
|
|
709223bd46 | ||
|
|
19b4340e18 | ||
|
|
a5a1c64dcb | ||
|
|
2b7bdffe41 | ||
|
|
a2df96354e | ||
|
|
8b49eeff98 | ||
|
|
a8c4db197d | ||
|
|
1319f314ff | ||
|
|
781abaaf40 | ||
|
|
744debc00a | ||
|
|
77321c1bcd | ||
|
|
c97fd60c25 | ||
|
|
2fb40ff443 | ||
|
|
a992ef84f5 | ||
|
|
8fce4c79b6 | ||
|
|
7e4aa9ebbe | ||
|
|
522e5bb129 | ||
|
|
1a97836dc3 | ||
|
|
e1bc9276bc | ||
|
|
555b6929e4 | ||
|
|
ee4d61e476 | ||
|
|
f15235d120 | ||
|
|
471d5726f6 | ||
|
|
e344530fc0 | ||
|
|
29fe38eff4 | ||
|
|
25e643371e | ||
|
|
32cb8dd873 | ||
|
|
12cc61fba5 | ||
|
|
fe40731df3 | ||
|
|
7464ca5543 | ||
|
|
ce83521429 | ||
|
|
27ab530ba6 | ||
|
|
843121f5fd | ||
|
|
d26c469107 | ||
|
|
97453260eb | ||
|
|
06b85c5769 | ||
|
|
5bb21031ac | ||
|
|
256d6139b7 | ||
|
|
aa342047ed | ||
|
|
ea663f8286 | ||
|
|
e953dfb017 | ||
|
|
54170f3da6 | ||
|
|
77ec2fea63 | ||
|
|
83beab4c92 | ||
|
|
29b6d12454 | ||
|
|
866a38f286 | ||
|
|
0ec2e7069b | ||
|
|
f3934b1906 | ||
|
|
fb2bb63d15 | ||
|
|
58cc04298f | ||
|
|
42535feadf | ||
|
|
baa690ffc2 | ||
|
|
d13f0a7acf | ||
|
|
b747199528 | ||
|
|
6a1af44aa0 | ||
|
|
6da050e3b9 | ||
|
|
b8f23ce80c | ||
|
|
865bef532a | ||
|
|
6fb158190d | ||
|
|
0e3f9c8ceb | ||
|
|
4dba6fd0f9 | ||
|
|
fbbf168e88 | ||
|
|
2cd739c3b4 | ||
|
|
a8e770726a | ||
|
|
0c5a99cc03 | ||
|
|
2dee246593 | ||
|
|
381bf59fbd | ||
|
|
fddf83f27d | ||
|
|
3d3e19e26a | ||
|
|
c082a5438a | ||
|
|
5adab1091f | ||
|
|
290a2ec91b | ||
|
|
9ebeabac51 | ||
|
|
b5e2c8d6b8 | ||
|
|
a482b6a3c2 | ||
|
|
f348de6a03 | ||
|
|
8df8978516 | ||
|
|
086d8fd21c | ||
|
|
7dce77adcf | ||
|
|
917a0469d8 | ||
|
|
c12431d8a3 | ||
|
|
dc0cf33d37 | ||
|
|
15d332012f | ||
|
|
99460beafd | ||
|
|
5b0cfb5c33 | ||
|
|
759e747f54 | ||
|
|
6892a679ee | ||
|
|
1b13a2bc4b | ||
|
|
63e8e10a6d | ||
|
|
0615a7e353 | ||
|
|
16acf9db91 | ||
|
|
0c19ba59d2 | ||
|
|
143528d919 | ||
|
|
07cdf378f6 | ||
|
|
f559a2f9c9 | ||
|
|
6b3e9e4676 | ||
|
|
3f72b7cc0d | ||
|
|
4ce5b0e645 | ||
|
|
e92ab8d741 | ||
|
|
6b0e414932 | ||
|
|
00927f3ba3 | ||
|
|
b0faecfa75 | ||
|
|
c15449f99a | ||
|
|
68f04c0912 | ||
|
|
c3c010443f | ||
|
|
15c8abf3d6 | ||
|
|
d0f74c1ab7 | ||
|
|
ca921c1890 | ||
|
|
3b60bbce0a | ||
|
|
8f6b384fe6 | ||
|
|
4d04264caa | ||
|
|
912fd8089e | ||
|
|
5739091773 | ||
|
|
a09c278585 | ||
|
|
a7fb49a11b | ||
|
|
3db8ae53b5 | ||
|
|
311f9a1d00 | ||
|
|
50204f8180 | ||
|
|
d639a802dc | ||
|
|
f41eafe3bd | ||
|
|
8b080eb3cc | ||
|
|
268d8a3a90 | ||
|
|
44d6c40c27 | ||
|
|
9522e25b1d | ||
|
|
db475895ca | ||
|
|
dc33509127 | ||
|
|
2ada67be04 | ||
|
|
77b17604f3 | ||
|
|
d169b3f2b1 | ||
|
|
c8210568eb | ||
|
|
2ea6430a3b | ||
|
|
ac5f997f85 | ||
|
|
e1930c0899 | ||
|
|
020281c1e6 | ||
|
|
9dc81a6280 | ||
|
|
a9906addff | ||
|
|
37269cef02 | ||
|
|
186ec00de7 | ||
|
|
abd13b756c | ||
|
|
6851683d94 | ||
|
|
2b4a3ab137 | ||
|
|
6d21bbe03a | ||
|
|
6f4a0e68a7 | ||
|
|
3de0f38049 | ||
|
|
216ccbfcef | ||
|
|
66b012cd55 | ||
|
|
8339bd3cb1 | ||
|
|
e93c62b06d | ||
|
|
43ceb33b53 | ||
|
|
ae0b45c308 | ||
|
|
bbaeff00f3 | ||
|
|
75a9697586 | ||
|
|
77bd37ca90 | ||
|
|
3a44a6a3b4 | ||
|
|
604d956d0c | ||
|
|
822327edfc | ||
|
|
3ea14f1353 | ||
|
|
7dbc077f76 | ||
|
|
e8cb661938 | ||
|
|
e5a870ed94 | ||
|
|
6116e954ba | ||
|
|
56ef7ce95c | ||
|
|
6e2e585175 | ||
|
|
8c1c0c6029 | ||
|
|
11dad79794 | ||
|
|
4b3485145f | ||
|
|
4b5a579b0b | ||
|
|
07bd09473c | ||
|
|
adba3b0d4b | ||
|
|
f6162c58f2 | ||
|
|
ed949d1dd8 | ||
|
|
5d94de418b | ||
|
|
f7fd8af124 | ||
|
|
d74734972b | ||
|
|
8736f5e387 | ||
|
|
89f08ae6c7 | ||
|
|
0b75b3847d | ||
|
|
6d27c3db11 | ||
|
|
b78065e23c | ||
|
|
c611c5c749 | ||
|
|
5b637154ea | ||
|
|
0b6edb3c38 | ||
|
|
f1ba1c55a6 | ||
|
|
2940a63d09 | ||
|
|
cc311f88a5 | ||
|
|
0911df0442 | ||
|
|
86e5dfe2ee | ||
|
|
34b5570563 | ||
|
|
2d806d8557 | ||
|
|
a792a0aa55 | ||
|
|
e7ab8441d7 | ||
|
|
d0b7efa24c | ||
|
|
d89d198f96 | ||
|
|
b638a7c992 | ||
|
|
74936a2c13 | ||
|
|
63fe78b4fe | ||
|
|
8ed3db0170 | ||
|
|
908d97e593 | ||
|
|
0d137fe121 | ||
|
|
afe0f0986c | ||
|
|
edd7935199 | ||
|
|
b2cc62059e | ||
|
|
cf56150b1e | ||
|
|
db93539e89 | ||
|
|
230cf358c9 | ||
|
|
8889808794 | ||
|
|
4855d16139 | ||
|
|
b1baaeddc5 | ||
|
|
8e27543ca5 | ||
|
|
77a6d4ddb0 | ||
|
|
413f184eec | ||
|
|
338c0617a8 | ||
|
|
8e2cb4ee74 | ||
|
|
c01c497b03 | ||
|
|
1d8f7fe1ab | ||
|
|
199a2e3729 | ||
|
|
dd8ad3466b | ||
|
|
7b7ec8f1e0 | ||
|
|
141efcdbbf | ||
|
|
5192484494 | ||
|
|
e7750e1c5c | ||
|
|
359775adde | ||
|
|
33375059bf | ||
|
|
a7a6ad53cc | ||
|
|
9df61ab044 | ||
|
|
11bb3f129f | ||
|
|
0335e58b12 | ||
|
|
2d173266df | ||
|
|
f05de1c726 | ||
|
|
b6196a4b3f | ||
|
|
e18df74d5a | ||
|
|
edf10fdd9f | ||
|
|
81934355ed | ||
|
|
e318084300 | ||
|
|
476f74efa8 | ||
|
|
e309dd6e67 | ||
|
|
49cb2e5c0a | ||
|
|
66370508ff | ||
|
|
875186ee24 | ||
|
|
384ef9787b | ||
|
|
dbcd39e91b | ||
|
|
2244d2c3ef | ||
|
|
37c54b5f77 | ||
|
|
f48e7dac83 | ||
|
|
21b33086ae | ||
|
|
6143b2f8ed | ||
|
|
d53c88f314 | ||
|
|
f42ac5d744 | ||
|
|
58f3aaf47d |
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
|
||||
commitmsg = ""
|
||||
|
||||
with open('.git/COMMIT_EDITMSG', mode='r') as f:
|
||||
commitmsg = f.readline().strip()
|
||||
|
||||
print("Commit message is: " + commitmsg)
|
||||
|
||||
ALLOWED_COMMIT_TYPES = [
|
||||
"cicd",
|
||||
"test",
|
||||
"refactor",
|
||||
"depbump",
|
||||
"typo",
|
||||
"fmt",
|
||||
"doc",
|
||||
"bugfix",
|
||||
"security",
|
||||
"feature",
|
||||
]
|
||||
|
||||
for allowedType in ALLOWED_COMMIT_TYPES:
|
||||
if commitmsg.startswith(allowedType + ":"):
|
||||
print("Allowing commit type: ", allowedType)
|
||||
sys.exit(0)
|
||||
|
||||
print("Commit message should start with commit type. One of: ", ", ".join(ALLOWED_COMMIT_TYPES))
|
||||
sys.exit(1)
|
||||
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,9 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels: bug
|
||||
labels:
|
||||
- "type: bug"
|
||||
- "waiting-on-developer"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,9 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
labels:
|
||||
- "type: feature-request"
|
||||
- "waiting-on-developer"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/support_request.md
vendored
@@ -2,7 +2,9 @@
|
||||
name: Support request
|
||||
about: Need some help? Got an error message?
|
||||
title: ""
|
||||
labels: support
|
||||
labels:
|
||||
- "type: support"
|
||||
- "waiting-on-developer"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -12,6 +14,12 @@ assignees: ''
|
||||
If you are getting an error message, then please copy/paste, or better, provide
|
||||
a screenshot to show us exactly what is wrong.
|
||||
|
||||
**Can you provide a sosreport?**
|
||||
|
||||
A sosreport really helps us to help you, by providing critical information about your install. If you can generate a sosreport, please copy and paste the output here.
|
||||
|
||||
How to generate a sosreport: https://docs.olivetin.app/sosreport.html
|
||||
|
||||
**What package/file/container did you use to install OliveTin?**
|
||||
|
||||
eg: OliveTin-1234-x86_64.rpm
|
||||
@@ -33,7 +41,7 @@ If possible, please copy and paste your OliveTin logs from when the error happen
|
||||
**Screenshot of WebDeveloper console logs**
|
||||
|
||||
If you know how, and if you think it's relevant, a screenshot of the
|
||||
WebDeveloper console from when you clicked a button is often really helpful.
|
||||
WebDeveloper console from when you clicked a button is often really helpful.
|
||||
|
||||
**Anything else?**
|
||||
|
||||
|
||||
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -4,7 +4,7 @@ First of all, thank you for considering to raise a pull request!
|
||||
|
||||
Don’t be afraid to ask for advice before working on a contribution. If you’re thinking about a bigger change, especially that might affect the core working or architecture, it’s almost essential to talk and ask about what you’re planning might affect things. Some of the larger future plans may not be documented well so it’s difficult to understand how your change might affect the general direction and roadmap of this project without asking.
|
||||
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
|
||||
Helpful information to understand the project can be found here: [CONTRIBUTING](https://github.com/OliveTin/OliveTin/blob/main/CONTRIBUTING.adoc)
|
||||
|
||||
@@ -13,10 +13,14 @@ Helpful information to understand the project can be found here: [CONTRIBUTING](
|
||||
# Checklist
|
||||
Please put a X in the boxes as evidence of reading through the checklist.
|
||||
|
||||
- [ ] I have forked the project, and raised this PR on a feature branch.
|
||||
- [ ] `make githooks` has been run, and my git commit message was accepted by the git hook.
|
||||
- [ ] `make daemon-compile` runs without any issues.
|
||||
- [ ] `make daemon-codestyle` runs without any issues.
|
||||
- [ ] `make daemon-unittests` runs without any issues.
|
||||
- [ ] `make webui-codestyle` runs without any issues.
|
||||
- [ ] I understand and accept the [AGPL-3.0 license](LICENSE) and [code of conduct](CODE_OF_CONDUCT.md), and my contributions fall under these.
|
||||
- [ ] I have read the [CONTRIBUTORS](CONTRIBUTORS.adoc) guide
|
||||
- [ ] I considered the "3 line" suggestion.
|
||||
- [ ] I followed the "1 logical change" rule.
|
||||
- [ ] I have forked the project, and raised this PR on a feature branch.
|
||||
- [ ] I ran the `pre-commit` hooks, and my commit message was validated.
|
||||
- [ ] `make -wC service compile` runs without any issues.
|
||||
- [ ] `make -wC service codestyle` runs without any issues.
|
||||
- [ ] `make -wC service unittests` runs without any issues.
|
||||
- [ ] `make -wC webui codestyle` runs without any issues.
|
||||
- [ ] `make -w it` runs without any issues.
|
||||
- [ ] I understand and accept the [AGPL-3.0 license](LICENSE) and [code of conduct](CODE_OF_CONDUCT.md), and my contributions fall under these.
|
||||
|
||||
105
.github/workflows/build-and-release.yml
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
name: "Build & Release pipeline"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
platforms: arm64,arm
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'service/go.mod'
|
||||
cache: true
|
||||
cache-dependency-path: 'service/go.mod'
|
||||
|
||||
- name: Print go version
|
||||
run: go version
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_KEY }}
|
||||
|
||||
- name: Login to ghcr
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.CONTAINER_TOKEN }}
|
||||
|
||||
- name: make webui
|
||||
run: make -w webui-dist
|
||||
|
||||
- name: unit tests
|
||||
run: make -w service-unittests
|
||||
|
||||
- name: build service
|
||||
run: make -w service
|
||||
|
||||
- name: integration tests
|
||||
run: cd integration-tests && make -w
|
||||
|
||||
- name: Archive integration tests
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
if: always()
|
||||
with:
|
||||
name: "OliveTin-integration-tests-${{ env.DATE }}-${{ github.sha }}"
|
||||
path: |
|
||||
integration-tests
|
||||
!integration-tests/node_modules
|
||||
|
||||
- name: Install goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
install-only: true
|
||||
|
||||
- name: release
|
||||
if: github.ref_type != 'tag'
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
with:
|
||||
extra_plugins: |
|
||||
@semantic-release/commit-analyzer
|
||||
@semantic-release/exec
|
||||
@semantic-release/git
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
|
||||
|
||||
- name: get date
|
||||
run: |
|
||||
echo "DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Archive binaries
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: "OliveTin-snapshot-${{ env.DATE }}-${{ github.sha }}"
|
||||
path: dist/OliveTin*.*
|
||||
|
||||
34
.github/workflows/build-buf.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Buf CI
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'proto/**'
|
||||
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
delete:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
jobs:
|
||||
buf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'service/go.mod'
|
||||
cache: true
|
||||
cache-dependency-path: 'service/go.mod'
|
||||
|
||||
- uses: bufbuild/buf-action@v1.1.0
|
||||
with:
|
||||
token: ${{ secrets.BUF_TOKEN }}
|
||||
# Change setup_only to true if you only want to set up the Action and not execute other commands.
|
||||
# Otherwise, you can delete this line--the default is false.
|
||||
setup_only: false
|
||||
# Optional GitHub token for API requests. Ensures requests aren't rate limited.
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
46
.github/workflows/build-snapshot.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: "Build Snapshot"
|
||||
|
||||
on:
|
||||
- push
|
||||
- workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build-snapshot:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
platforms: arm64,arm
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '>=1.18.0'
|
||||
|
||||
- name: grpc
|
||||
run: make grpc
|
||||
|
||||
- name: goreleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --snapshot --rm-dist --parallelism 1
|
||||
|
||||
- name: Archive binaries
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: "OliveTin-snapshot-${{ github.ref_name }}-${{ github.sha }}-dist"
|
||||
path: dist/OliveTin*.*
|
||||
|
||||
- name: Archive integration tests
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: integration-tests
|
||||
path: integration-tests
|
||||
55
.github/workflows/build-tag.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: "Build Tag"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-tag:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
platforms: arm64,arm
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16.0'
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_KEY }}
|
||||
|
||||
- name: grpc
|
||||
run: make grpc
|
||||
|
||||
- name: goreleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist --parallelism 1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Archive binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
path: dist/OliveTin*.*
|
||||
|
||||
- name: Archive integration tests
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: integration-tests
|
||||
path: integration-tests
|
||||
60
.github/workflows/codeql-analysis.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
@@ -13,16 +14,15 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
paths:
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'webui/**'
|
||||
- 'webui.dev/**'
|
||||
- 'integration-tests/**'
|
||||
- 'OliveTin.proto'
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '25 10 * * 5'
|
||||
|
||||
@@ -38,40 +38,26 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go', 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
language: ['go', 'javascript']
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'service/go.mod'
|
||||
cache: true
|
||||
cache-dependency-path: 'service/go.mod'
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
#- name: Autobuild
|
||||
# uses: github/codeql-action/autobuild@v1
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
25
.github/workflows/codestyle.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: "Codestyle checks"
|
||||
|
||||
on:
|
||||
@@ -5,7 +6,7 @@ on:
|
||||
paths:
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'webui/**'
|
||||
- 'webui.dev/**'
|
||||
- 'integration-tests/**'
|
||||
- 'OliveTin.proto'
|
||||
|
||||
@@ -15,18 +16,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16.0'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'service/go.mod'
|
||||
cache: true
|
||||
cache-dependency-path: 'service/go.mod'
|
||||
|
||||
- name: deps
|
||||
run: make grpc
|
||||
- name: Print go version
|
||||
run: go version
|
||||
|
||||
- name: daemon
|
||||
run: make daemon-codestyle
|
||||
- name: service
|
||||
run: make -wC service codestyle
|
||||
|
||||
- name: webui
|
||||
run: make webui-codestyle
|
||||
- name: frontend
|
||||
run: make -wC frontend codestyle
|
||||
|
||||
34
.github/workflows/devskim.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: DevSkim
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '34 21 * * 2'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: DevSkim
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@v1
|
||||
|
||||
- name: Upload DevSkim scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: devskim-results.sarif
|
||||
73
.github/workflows/issue-responsibility.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
name: Issue Responsibility
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
update-responsibility-labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update responsibility labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const commentAuthor = context.payload.comment.user.login;
|
||||
const issueNumber = context.payload.issue.number;
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
const skipAction = context.payload.comment.body.includes("/skip-responsibility");
|
||||
|
||||
if (skipAction) {
|
||||
core.info("Skipping responsibility label update");
|
||||
return;
|
||||
}
|
||||
|
||||
const developers = ["jamesread"]
|
||||
const commenterIsDeveloper = developers.includes(commentAuthor);
|
||||
const commenterIsUser = !commenterIsDeveloper;
|
||||
|
||||
const issueLabels = context.payload.issue.labels.map(label => label.name);
|
||||
|
||||
if (issueLabels.includes("waiting-on-developer")) {
|
||||
if (commenterIsDeveloper) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
name: "waiting-on-developer",
|
||||
});
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
labels: ["waiting-on-requestor"],
|
||||
});
|
||||
|
||||
core.info(`Switched responsibility to user for issue #${issueNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (issueLabels.includes("waiting-on-requestor")) {
|
||||
if (commenterIsUser) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
name: "waiting-on-requestor",
|
||||
});
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
labels: ["waiting-on-developer"],
|
||||
});
|
||||
|
||||
core.info(`Switched responsibility to developer for issue #${issueNumber}`);
|
||||
}
|
||||
}
|
||||
19
.gitignore
vendored
@@ -1,11 +1,18 @@
|
||||
webui/node_modules
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
gen/
|
||||
/OliveTin
|
||||
/OliveTin.armhf
|
||||
/OliveTin.exe
|
||||
reports
|
||||
service/OliveTin
|
||||
service/OliveTin.armhf
|
||||
service/OliveTin.exe
|
||||
service/reports
|
||||
releases/
|
||||
dist/
|
||||
installation-id.txt
|
||||
tmp/
|
||||
frontend/dist/
|
||||
frontend/node_modules
|
||||
custom-frontend
|
||||
integration-tests/screenshots/
|
||||
.vscode/
|
||||
webui/
|
||||
server.log
|
||||
OliveTin
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
project_name: OliveTin
|
||||
version: 2
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
- rm -rf webui/node_modules
|
||||
- make service-prep
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
binary: OliveTin
|
||||
main: main.go
|
||||
dir: service
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
- freebsd
|
||||
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- arm
|
||||
- riscv64
|
||||
|
||||
goarm:
|
||||
- 5 # For old RPIs
|
||||
- 6
|
||||
- 6
|
||||
- 7
|
||||
|
||||
main: cmd/OliveTin/main.go
|
||||
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: arm # Mac does not work on [32bit] arm
|
||||
@@ -39,9 +43,21 @@ builds:
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Branch }}-{{ .ShortCommit }}"
|
||||
version_template: "{{ .Branch }}-{{ .ShortCommit }}"
|
||||
changelog:
|
||||
sort: asc
|
||||
groups:
|
||||
- title: 'Security'
|
||||
regexp: '^.*?security(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: 'Features'
|
||||
regexp: '^.*?feat.*?(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: 'Bug fixes'
|
||||
regexp: '^.*?bugfix(\([[:word:]]+\))??!?:.+$'
|
||||
order: 2
|
||||
- title: Others
|
||||
order: 999
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
@@ -50,58 +66,54 @@ changelog:
|
||||
- '^refactor:'
|
||||
|
||||
archives:
|
||||
-
|
||||
format: tar.gz
|
||||
|
||||
files:
|
||||
- formats: tar.gz
|
||||
files:
|
||||
- config.yaml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- Dockerfile
|
||||
- webui
|
||||
- OliveTin.service
|
||||
|
||||
replacements:
|
||||
darwin: macOS
|
||||
arm: arm32v
|
||||
|
||||
- ./var/
|
||||
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}{{ .Arm }}"
|
||||
|
||||
wrap_in_directory: true
|
||||
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: zip
|
||||
|
||||
dockers:
|
||||
- image_templates:
|
||||
- "docker.io/jamesread/olivetin:{{ .Tag }}-amd64"
|
||||
- "ghcr.io/olivetin/olivetin:{{ .Tag }}-amd64"
|
||||
dockerfile: Dockerfile
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
skip_push: false
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Tag}}"
|
||||
- "--platform=linux/amd64"
|
||||
extra_files:
|
||||
- webui
|
||||
- var/entities/
|
||||
- config.yaml
|
||||
- var/helper-actions/
|
||||
|
||||
- image_templates:
|
||||
- "docker.io/jamesread/olivetin:{{ .Tag }}-arm64"
|
||||
- "ghcr.io/olivetin/olivetin:{{ .Tag }}-arm64"
|
||||
dockerfile: Dockerfile.arm64
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
skip_push: false
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Tag}}"
|
||||
extra_files:
|
||||
- webui
|
||||
- var/entities/
|
||||
- config.yaml
|
||||
- var/helper-actions/
|
||||
|
||||
docker_manifests:
|
||||
- name_template: docker.io/jamesread/olivetin:{{ .Version }}
|
||||
@@ -114,6 +126,16 @@ docker_manifests:
|
||||
- docker.io/jamesread/olivetin:{{ .Version }}-amd64
|
||||
- docker.io/jamesread/olivetin:{{ .Version }}-arm64
|
||||
|
||||
- name_template: ghcr.io/olivetin/olivetin:{{ .Version }}
|
||||
image_templates:
|
||||
- ghcr.io/olivetin/olivetin:{{ .Version }}-amd64
|
||||
- ghcr.io/olivetin/olivetin:{{ .Version }}-arm64
|
||||
|
||||
- name_template: ghcr.io/olivetin/olivetin:latest
|
||||
image_templates:
|
||||
- ghcr.io/olivetin/olivetin:{{ .Version }}-amd64
|
||||
- ghcr.io/olivetin/olivetin:{{ .Version }}-arm64
|
||||
|
||||
nfpms:
|
||||
- id: default
|
||||
maintainer: James Read <contact@jread.com>
|
||||
@@ -129,16 +151,23 @@ nfpms:
|
||||
file_name_template: '{{ .PackageName }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
|
||||
contents:
|
||||
- src: OliveTin.service
|
||||
- src: var/systemd/OliveTin.service
|
||||
dst: /etc/systemd/system/OliveTin.service
|
||||
|
||||
- src: webui
|
||||
- src: webui/*
|
||||
dst: /var/www/olivetin/
|
||||
|
||||
- src: config.yaml
|
||||
dst: /etc/OliveTin/config.yaml
|
||||
type: "config|noreplace"
|
||||
|
||||
- src: var/entities/*
|
||||
dst: /etc/OliveTin/entities/
|
||||
type: "config|noreplace"
|
||||
|
||||
- src: var/manpage/OliveTin.1.gz
|
||||
dst: /usr/share/man/man1/OliveTin.1.gz
|
||||
|
||||
- id: openrc
|
||||
maintainer: James Read <contact@jread.com>
|
||||
description: OliveTin is a web interface for running Linux shell commands.
|
||||
@@ -155,24 +184,37 @@ nfpms:
|
||||
- src: var/openrc/OliveTin
|
||||
dst: /etc/init.d/OliveTin
|
||||
|
||||
- src: webui
|
||||
- src: webui/*
|
||||
dst: /var/www/olivetin/
|
||||
|
||||
- src: config.yaml
|
||||
dst: /etc/OliveTin/config.yaml
|
||||
type: "config|noreplace"
|
||||
|
||||
- src: var/entities/*
|
||||
dst: /etc/OliveTin/entities/
|
||||
type: "config|noreplace"
|
||||
|
||||
- src: var/manpage/OliveTin.1.gz
|
||||
dst: /usr/share/man/man1/OliveTin.1.gz
|
||||
|
||||
release:
|
||||
footer: |
|
||||
## Container images (from GitHub)
|
||||
|
||||
- `docker pull ghcr.io/olivetin/olivetin:{{ .Version }}`
|
||||
|
||||
## Container images ([on Docker Hub](https://hub.docker.com/r/jamesread/olivetin/tags?page=1&ordering=last_updated))
|
||||
|
||||
- `docker pull docker.io/jamesread/olivetin:{{ .Version }}`
|
||||
|
||||
## Upgrade warnings, or breaking changes
|
||||
|
||||
- No such issues between the last release and this version.
|
||||
|
||||
## Useful links
|
||||
|
||||
- [Which download do I need?](https://docs.olivetin.app/choose-package.html)
|
||||
- [Which download do I need?](https://docs.olivetin.app/install/choose_package.html)
|
||||
- [Ask for help and chat with others users in the Discord community](https://discord.gg/jhYWWpNJ3v)
|
||||
|
||||
|
||||
Thanks for your interest in OliveTin!
|
||||
|
||||
|
||||
19
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
|
||||
# Alternative semantic commit checker
|
||||
- repo: https://github.com/compilerla/conventional-pre-commit
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: conventional-pre-commit
|
||||
stages: [commit-msg]
|
||||
args: [] # optional: list of Conventional Commits types to allow e.g. [feat, fix, ci, chore, test]
|
||||
16
.releaserc.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
branches:
|
||||
- name: main
|
||||
# range: '3000.x.x'
|
||||
|
||||
# - name: release/2k
|
||||
# range: '>=2000.0.0 <3000.0.0'
|
||||
|
||||
plugins:
|
||||
- '@semantic-release/commit-analyzer'
|
||||
- '@semantic-release/git'
|
||||
- - "@semantic-release/exec"
|
||||
- publishCmd: |
|
||||
goreleaser release --clean --timeout 60m
|
||||
|
||||
tagFormat: '${version}'
|
||||
68
AGENTS.md
Normal file
@@ -0,0 +1,68 @@
|
||||
## OliveTin – Agent Guide
|
||||
|
||||
This document helps AI agents contribute effectively to OliveTin.
|
||||
|
||||
If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
|
||||
|
||||
### Project Overview
|
||||
- **Service (Go)**: `service/` with business logic under `service/internal/*`
|
||||
- API (Connect RPC): `service/internal/api`
|
||||
- Command execution: `service/internal/executor`
|
||||
- HTTP frontends/proxy: `service/internal/httpservers`
|
||||
- Config/types/entities: `service/internal/config`, `service/internal/entities`
|
||||
- **Frontend (Vue 3)**: `frontend/` (served by the service)
|
||||
- **Integration tests**: `integration-tests/`
|
||||
- **Protos/Generated**: `proto/`, `service/gen/...`
|
||||
|
||||
### How to Run
|
||||
- Run the server (dev):
|
||||
- From repo root: `go run ./service`
|
||||
- Unit tests (Go):
|
||||
- From repo root: `cd service && make unittests`
|
||||
- Integration tests (Mocha + Selenium):
|
||||
- Single test: `cd integration-tests && npx --yes mocha test/general.mjs`
|
||||
- All tests: `cd integration-tests && npx --yes mocha`
|
||||
|
||||
### Test Notes and Gotchas
|
||||
- The top-level Makefile does not expose `unittests`; use `cd service && make unittests`.
|
||||
- Connect RPC API must be mounted correctly; in tests, create the handler via `GetNewHandler(ex)` and serve under `/api/`.
|
||||
- Frontend “ready” state: the app sets `document.body` attribute `initial-marshal-complete="true"` when loaded. Integration helpers wait for this before selecting elements.
|
||||
- Modern UI uses Vue components:
|
||||
- Action buttons are rendered as `.action-button button`.
|
||||
- Logs and Diagnostics are Vue router links available via `/logs` and `/diagnostics`.
|
||||
- Some legacy DOM ids (e.g., `contentActions`) no longer exist; prefer class-based selectors.
|
||||
- Hidden UI features:
|
||||
- Footer visibility is controlled by `showFooter` from Init API; tests may assert the footer is absent when config disables it.
|
||||
|
||||
### Coding Standards (Go)
|
||||
- Prefer clear, descriptive names; avoid 1–2 letter identifiers.
|
||||
- Use early returns and handle edge cases first.
|
||||
- Do not swallow errors; propagate or log meaningfully.
|
||||
- Match existing formatting; avoid unrelated reformatting.
|
||||
- Be safe around nils in executor steps (e.g., guard `req.Binding` and `req.Binding.Action`).
|
||||
|
||||
### API and Execution Flow (High-level)
|
||||
1. Client calls Connect RPC (e.g., `Init`, `GetDashboard`, `StartAction`).
|
||||
2. API translates requests to `executor.ExecutionRequest` and calls `Executor.ExecRequest`.
|
||||
3. Executor runs a chain of steps: request binding → concurrency/rate/ACL checks → arg parsing → exec → post-exec → logging/triggering.
|
||||
4. Logs are stored and can be fetched via `ExecutionStatus`/`GetLogs`.
|
||||
|
||||
### Common Tasks
|
||||
- Add/modify actions: update `config.yaml` and ensure `executor.RebuildActionMap()` is called when needed.
|
||||
- Adjust dashboard rendering: see `service/internal/api/dashboards.go` and `apiActions.go`.
|
||||
- Frontend behavior:
|
||||
- Router: `frontend/resources/vue/router.js`
|
||||
- Main shell/layout: `frontend/resources/vue/App.vue`
|
||||
- Action button behavior: `frontend/resources/vue/ActionButton.vue`
|
||||
|
||||
### Contributing Checklist
|
||||
- Review the contributuing guidelines at `CONTRIBUTING.adoc`.
|
||||
- Review the AI guidance in `AI.md`.
|
||||
- Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`.
|
||||
|
||||
### Troubleshooting
|
||||
- API tests failing with content-type errors: ensure Connect handler is served under `/api/` and the client targets that base URL.
|
||||
- Executor panics: check for nil `Binding/Action` and add guards in step functions.
|
||||
- Integration timeouts: wait for `initial-marshal-complete` and use selectors matching the Vue UI.
|
||||
|
||||
|
||||
32
AI.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# OliveTin's AI Policy
|
||||
|
||||
## Runtime:
|
||||
|
||||
- [x] The project does not include any AI functionality at runtime.
|
||||
- [x] No data, usage, or similar is sent to, or analyized by AI.
|
||||
|
||||
## Development - Contributions
|
||||
|
||||
- [x] The project **does accept** contributions that were written with AI help. **However**:
|
||||
- The contribution must be attributed to a human username who takes responsibility for the code as if they wrote it themselves.
|
||||
- AI often generates very unmaintainable code as it gets longer - loads of duplication, very little function re-use amd very poor at following style guides / idiomatic design. All code contributions (AI or not) are scrutinized hard for **maintainability** and **clean merging**. Please follow the CONTRIBUTORS guide.
|
||||
- AI that helps with short tab completion is generally fine.
|
||||
- AI that writes lots of new code across lots of files, or makes lots of superfluous changes is generally less likely to be accepted.
|
||||
- Vibe coding is not a suitable way to contribute to this project.
|
||||
- [x] Contributors should declare when AI has been used to help write contributions in the pull request body message.
|
||||
- [x] The project uses AI as an **optional** part of the PR process (coderabbitai). Please raise any concerns about usage within the PR.
|
||||
- [x] Suggestions from coderabbitai can be accepted verbaitem, but ideally it should be the PR author that uses coderabbitai as a guide, who then re-writes the contribution.
|
||||
- [x] Maintainers are the only agents permitted to accept merges.
|
||||
|
||||
## Development - Build process
|
||||
|
||||
- [x] Linters, code review tools, and others which are enabled by AI are allowed, but cannot be added as part of the standard build process.
|
||||
|
||||
## Community
|
||||
|
||||
- [x] Only project admins are allowed to run bots in the community discord server (at the time of writing, the current bot, Japella, is not AI-enabled).
|
||||
- [x] Support is currently not provided by AI.
|
||||
|
||||
## Training
|
||||
|
||||
- [x] You may use the OliveTin code base, documentation and repositories to train AI, but obviously usernames and personally identifiable information may not be stored.
|
||||
@@ -7,9 +7,17 @@ of multiple projects, your time and interest in contributing is most welcome.
|
||||
If you're not sure where to get started, raise an issue in the project.
|
||||
|
||||
Ideas may be discussed, purely on their merits and issues. Our Code of Conduct
|
||||
(CoC) is straightforward - it's important that contributors feel comfortable in
|
||||
discussion throughout the whole process. This project respects the
|
||||
link:https://www.kernel.org/doc/html/latest/process/code-of-conduct.html[Linux Kernel code of conduct].
|
||||
(CoC) is straightforward - it's important that contributors feel comfortable in
|
||||
discussion throughout the whole process. This project respects the
|
||||
link:https://www.kernel.org/doc/html/latest/process/code-of-conduct.html[Linux Kernel code of conduct].
|
||||
|
||||
== Suggestion: More than 3 lines - talk to someone first
|
||||
|
||||
If you're planning on making a change that's more than a 3 lines, please talk to someone first (raising a GitHub issue is the best way to do that). This is so that you don't waste your time on something that might not be accepted. It's also a good way to get some feedback on your idea and make sure you're on the right track.
|
||||
|
||||
== Rule: A PR should be one logical change
|
||||
|
||||
Please try to keep your pull requests small and focused. It's almost impossible to review PRs that change lots of files for lots of different reasons. If you have a big change, it's probably best to break it down into smaller, more manageable chunks, otherwise it's likely to be rejected.
|
||||
|
||||
== If you're not sure, ask!
|
||||
|
||||
@@ -18,19 +26,27 @@ contribution. If you're thinking about a bigger change, especially that might
|
||||
affect the core working or architecture, it's almost essential to talk and ask
|
||||
about what you're planning might affect things. Some of the larger future plans may not be
|
||||
documented well so it's difficult to understand how your change might affect
|
||||
the general direction and roadmap of this project without asking.
|
||||
the general direction and roadmap of this project without asking.
|
||||
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
|
||||
=== Dev environment setup and clean build - Fedora
|
||||
== Dev environment setup and clean build
|
||||
|
||||
```
|
||||
dnf install git go protobuf-compiler make -y
|
||||
# Step1: setup compile env
|
||||
# - Fedora
|
||||
dnf install git go protobuf-compiler make -y
|
||||
# - Windows with chocolatey
|
||||
choco install git go protoc make python nodejs-lts -y
|
||||
|
||||
# Step2: clone and setup repo
|
||||
git clone https://github.com/OliveTin/OliveTin.git
|
||||
cd OliveTin
|
||||
make githooks
|
||||
|
||||
# `make grpc` will also run `make go-tools`, which installs "buf". This binary
|
||||
# will be put in your GOPATH/bin/, which should be on your path. buf is used to
|
||||
# Step3: compile binary for current dev env (OS, ARCH)
|
||||
# `make grpc` will also run `make go-tools`, which installs "buf". This binary
|
||||
# will be put in your GOPATH/bin/, which should be on your path. buf is used to
|
||||
# generate the protobuf / grpc stubs.
|
||||
make grpc
|
||||
make
|
||||
@@ -39,10 +55,10 @@ make
|
||||
|
||||
=== Getting started to contribute;
|
||||
|
||||
The project layout is reasonably straightforward;
|
||||
The project layout is reasonably straightforward;
|
||||
|
||||
* See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any).
|
||||
* The API is defined in protobuf+grpc - you will need to `make grpc`.
|
||||
* The API is defined in protobuf+grpc - you will need to `make grpc`.
|
||||
* The Go daemon is built from the `cmd` and `internal` directories mostly.
|
||||
* The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver.
|
||||
|
||||
|
||||
36
Dockerfile
@@ -1,21 +1,45 @@
|
||||
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:36-x86_64
|
||||
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:42-x86_64 AS olivetin-tmputils
|
||||
|
||||
RUN mkdir -p /config /var/www/olivetin \
|
||||
&& microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
RUN microdnf -y install dnf-plugins-core && \
|
||||
dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \
|
||||
microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all
|
||||
|
||||
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:42-x86_64
|
||||
|
||||
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
|
||||
LABEL org.opencontainers.image.title OliveTin
|
||||
|
||||
RUN mkdir -p /config /config/entities/ /var/www/olivetin \
|
||||
&& \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
iputils \
|
||||
openssh-clients \
|
||||
kubernetes-client \
|
||||
shadow-utils \
|
||||
docker \
|
||||
apprise \
|
||||
jq \
|
||||
git \
|
||||
&& microdnf clean all
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
COPY --from=olivetin-tmputils \
|
||||
/usr/bin/docker \
|
||||
/usr/bin/docker
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
COPY --from=olivetin-tmputils \
|
||||
/usr/libexec/docker/cli-plugins/docker-compose \
|
||||
/usr/libexec/docker/cli-plugins/docker-compose
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
|
||||
COPY config.yaml /config
|
||||
COPY var/entities/* /config/entities/
|
||||
VOLUME /config
|
||||
|
||||
COPY OliveTin /usr/bin/OliveTin
|
||||
COPY webui /var/www/olivetin/
|
||||
COPY var/helper-actions/* /usr/bin/
|
||||
|
||||
USER olivetin
|
||||
|
||||
|
||||
@@ -1,20 +1,45 @@
|
||||
FROM --platform=linux/arm64 registry.fedoraproject.org/fedora-minimal:36-aarch64
|
||||
FROM --platform=linux/arm64 registry.fedoraproject.org/fedora-minimal:42-aarch64 AS olivetin-tmputils
|
||||
|
||||
RUN mkdir -p /config /var/www/olivetin \
|
||||
RUN microdnf -y install dnf-plugins-core && \
|
||||
dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \
|
||||
microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all
|
||||
|
||||
FROM --platform=linux/arm64 registry.fedoraproject.org/fedora-minimal:42-aarch64
|
||||
|
||||
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
|
||||
LABEL org.opencontainers.image.title OliveTin
|
||||
|
||||
RUN mkdir -p /config /config/entities/ /var/www/olivetin \
|
||||
&& \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
iputils \
|
||||
openssh-clients \
|
||||
kubernetes-client \
|
||||
shadow-utils \
|
||||
openssh-clients
|
||||
apprise \
|
||||
jq \
|
||||
git \
|
||||
&& microdnf clean all
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
COPY --from=olivetin-tmputils \
|
||||
/usr/bin/docker \
|
||||
/usr/bin/docker
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
COPY --from=olivetin-tmputils \
|
||||
/usr/libexec/docker/cli-plugins/docker-compose \
|
||||
/usr/libexec/docker/cli-plugins/docker-compose
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
|
||||
COPY config.yaml /config
|
||||
COPY var/entities/* /config/entities/
|
||||
VOLUME /config
|
||||
|
||||
COPY OliveTin /usr/bin/OliveTin
|
||||
COPY webui /var/www/olivetin/
|
||||
COPY var/helper-actions/* /usr/bin/
|
||||
|
||||
USER olivetin
|
||||
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
FROM --platform=linux/armhfp registry.fedoraproject.org/fedora-minimal:36-armhfp
|
||||
|
||||
RUN mkdir -p /config /var/www/olivetin \
|
||||
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
|
||||
LABEL org.opencontainers.image.title=OliveTin
|
||||
|
||||
RUN mkdir -p /config /config/entities /var/www/olivetin \
|
||||
&& \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
iputils \
|
||||
shadow-utils \
|
||||
openssh-clients
|
||||
shadow-utils \
|
||||
openssh-clients
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
EXPOSE 1337/tcp
|
||||
|
||||
COPY config.yaml /config
|
||||
COPY var/entities/* /config/entities/
|
||||
VOLUME /config
|
||||
|
||||
COPY OliveTin /usr/bin/OliveTin
|
||||
COPY webui /var/www/olivetin/
|
||||
COPY var/helper-actions/* /usr/bin/
|
||||
|
||||
USER olivetin
|
||||
|
||||
|
||||
50
Jenkinsfile
vendored
@@ -1,50 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
options {
|
||||
skipDefaultCheckout(true)
|
||||
}
|
||||
|
||||
stages {
|
||||
stage ('Pre-Build') {
|
||||
steps {
|
||||
cleanWs()
|
||||
checkout scm
|
||||
|
||||
sh 'make go-tools'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Compile') {
|
||||
steps {
|
||||
withEnv(["PATH+GO=/root/go/bin/"]) {
|
||||
sh 'go env'
|
||||
sh 'echo $PATH'
|
||||
sh 'buf generate'
|
||||
sh 'make daemon-compile'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Post-Compile') {
|
||||
parallel {
|
||||
stage('Codestyle') {
|
||||
steps {
|
||||
withEnv(["PATH+GO=/root/go/bin/"]) {
|
||||
sh 'make daemon-codestyle'
|
||||
sh 'make webui-codestyle'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('UnitTests') {
|
||||
steps {
|
||||
withEnv(["PATH+GO=/root/go/bin/"]) {
|
||||
sh 'make daemon-unittests'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
70
Makefile
@@ -1,41 +1,31 @@
|
||||
compile: daemon-compile-x64-lin
|
||||
define delete-files
|
||||
python -c "import shutil;shutil.rmtree('$(1)', ignore_errors=True)"
|
||||
endef
|
||||
|
||||
daemon-compile-armhf:
|
||||
GOARCH=arm GOARM=6 go build -o OliveTin.armhf github.com/OliveTin/OliveTin/cmd/OliveTin
|
||||
service:
|
||||
$(MAKE) -wC service
|
||||
|
||||
daemon-compile-x64-lin:
|
||||
GOOS=linux go build -o OliveTin github.com/OliveTin/OliveTin/cmd/OliveTin
|
||||
service-prep:
|
||||
$(MAKE) -wC service prep
|
||||
|
||||
daemon-compile-x64-win:
|
||||
GOOS=windows GOARCH=amd64 go build -o OliveTin.exe github.com/OliveTin/OliveTin/cmd/OliveTin
|
||||
service-unittests:
|
||||
$(MAKE) -wC service unittests
|
||||
|
||||
daemon-compile: daemon-compile-armhf daemon-compile-x64-lin daemon-compile-x64-win
|
||||
it:
|
||||
$(MAKE) -wC integration-tests
|
||||
|
||||
daemon-codestyle:
|
||||
go fmt ./...
|
||||
go vet ./...
|
||||
gocyclo -over 4 cmd internal
|
||||
gocritic check ./...
|
||||
|
||||
daemon-unittests:
|
||||
mkdir -p reports
|
||||
go test ./... -coverprofile reports/unittests.out
|
||||
go tool cover -html=reports/unittests.out -o reports/unittests.html
|
||||
|
||||
githooks:
|
||||
cp -v .githooks/* .git/hooks/
|
||||
|
||||
go-tools:
|
||||
go install "github.com/bufbuild/buf/cmd/buf"
|
||||
go install "github.com/fzipp/gocyclo/cmd/gocyclo"
|
||||
go install "github.com/go-critic/go-critic/cmd/gocritic"
|
||||
go install "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
|
||||
go install "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
|
||||
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
|
||||
go install "google.golang.org/protobuf/cmd/protoc-gen-go"
|
||||
$(MAKE) -wC service go-tools
|
||||
|
||||
grpc: githooks go-tools
|
||||
buf generate
|
||||
proto: grpc
|
||||
|
||||
grpc: go-tools
|
||||
$(MAKE) -wC proto
|
||||
|
||||
dist: protoc
|
||||
|
||||
protoc:
|
||||
protoc --go_out=. --go-grpc_out=. --grpc-gateway_out=. -I .:/usr/include/ OliveTin.proto
|
||||
|
||||
podman-image:
|
||||
buildah bud -t olivetin
|
||||
@@ -49,7 +39,7 @@ podman-container:
|
||||
integration-tests-docker-image:
|
||||
docker rm -f olivetin && docker rmi -f olivetin
|
||||
docker build -t olivetin:latest .
|
||||
docker create --name olivetin -p 1337:1337 -v `pwd`/integration-tests/configs/:/config/ olivetin
|
||||
docker create --name olivetin -p 1337:1337 -v `pwd`/integration-tests/configs/:/config/ olivetin
|
||||
|
||||
devrun: compile
|
||||
killall OliveTin || true
|
||||
@@ -57,12 +47,16 @@ devrun: compile
|
||||
|
||||
devcontainer: compile podman-image podman-container
|
||||
|
||||
webui-codestyle:
|
||||
cd webui && npm install
|
||||
cd webui && ./node_modules/.bin/eslint main.js js/*
|
||||
cd webui && ./node_modules/.bin/stylelint style.css
|
||||
webui-dist:
|
||||
$(MAKE) -wC frontend dist
|
||||
mv frontend/dist webui
|
||||
|
||||
clean:
|
||||
rm -rf dist OliveTin OliveTin.armhf OliveTin.exe reports gen
|
||||
$(call delete-files,dist)
|
||||
$(call delete-files,OliveTin)
|
||||
$(call delete-files,OliveTin.armhf)
|
||||
$(call delete-files,OliveTin.exe)
|
||||
$(call delete-files,reports)
|
||||
$(call delete-files,gen)
|
||||
|
||||
.PHONY: grpc
|
||||
.PHONY: grpc proto service
|
||||
|
||||
127
OliveTin.proto
@@ -1,127 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "gen/grpc";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
|
||||
message Action {
|
||||
string id = 1;
|
||||
string title = 2;
|
||||
string icon = 3;
|
||||
bool canExec = 4;
|
||||
|
||||
repeated ActionArgument arguments = 5;
|
||||
}
|
||||
|
||||
message ActionArgument {
|
||||
string name = 1;
|
||||
string title = 2;
|
||||
string type = 3;
|
||||
string defaultValue = 4;
|
||||
|
||||
repeated ActionArgumentChoice choices = 5;
|
||||
|
||||
string description = 6;
|
||||
}
|
||||
|
||||
message ActionArgumentChoice {
|
||||
string value = 1;
|
||||
string title = 2;
|
||||
}
|
||||
|
||||
message Entity {
|
||||
string title = 1;
|
||||
string icon = 2;
|
||||
repeated Action actions = 3;
|
||||
}
|
||||
|
||||
message GetDashboardComponentsResponse {
|
||||
string title = 1;
|
||||
repeated Action actions = 2;
|
||||
repeated Entity entities = 3;
|
||||
}
|
||||
|
||||
message GetDashboardComponentsRequest {}
|
||||
|
||||
message StartActionRequest {
|
||||
string actionName = 1;
|
||||
|
||||
repeated StartActionArgument arguments = 2;
|
||||
}
|
||||
|
||||
message StartActionArgument {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message StartActionResponse {
|
||||
LogEntry logEntry = 1;
|
||||
}
|
||||
|
||||
message GetLogsRequest{};
|
||||
|
||||
message LogEntry {
|
||||
string datetime = 1;
|
||||
string actionTitle = 2;
|
||||
string stdout = 3;
|
||||
string stderr = 4;
|
||||
bool timedOut = 5;
|
||||
int32 exitCode = 6;
|
||||
string user = 7;
|
||||
string userClass = 8;
|
||||
string actionIcon = 9;
|
||||
}
|
||||
|
||||
message GetLogsResponse {
|
||||
repeated LogEntry logs = 1;
|
||||
}
|
||||
|
||||
message ValidateArgumentTypeRequest {
|
||||
string value = 1;
|
||||
string type = 2;
|
||||
}
|
||||
|
||||
message ValidateArgumentTypeResponse {
|
||||
bool valid = 1;
|
||||
string description = 2;
|
||||
}
|
||||
|
||||
message WhoAmIRequest {}
|
||||
|
||||
message WhoAmIResponse {
|
||||
string authenticatedUser = 1;
|
||||
}
|
||||
|
||||
service OliveTinApi {
|
||||
rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/GetDashboardComponents"
|
||||
};
|
||||
}
|
||||
|
||||
rpc StartAction(StartActionRequest) returns (StartActionResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/api/StartAction"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetLogs(GetLogsRequest) returns (GetLogsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/GetLogs"
|
||||
};
|
||||
}
|
||||
|
||||
rpc ValidateArgumentType(ValidateArgumentTypeRequest) returns (ValidateArgumentTypeResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/api/ValidateArgumentType"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc WhoAmI(WhoAmIRequest) returns (WhoAmIResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/WhoAmI"
|
||||
};
|
||||
}
|
||||
}
|
||||
115
README.md
@@ -1,17 +1,25 @@
|
||||
# OliveTin
|
||||
<div align = "center">
|
||||
<img alt = "project logo" src = "https://github.com/OliveTin/OliveTin/blob/main/frontend/OliveTinLogo.png" width = "128" />
|
||||
<h1>OliveTin</h1>
|
||||
|
||||
<img alt = "project logo" src = "https://github.com/OliveTin/OliveTin/blob/main/webui/OliveTinLogo.png" align = "right" width = "160px" />
|
||||
|
||||
OliveTin gives **safe** and **simple** access to predefined shell commands from a web interface.
|
||||
OliveTin gives **safe** and **simple** access to predefined shell commands from a web interface.
|
||||
|
||||
[](#none)
|
||||
[](https://discord.gg/jhYWWpNJ3v)
|
||||
[](https://github.com/awesome-selfhosted/awesome-selfhosted#automation)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/5050)
|
||||
|
||||
[](https://goreportcard.com/report/github.com/OliveTin/OliveTin)
|
||||
[](https://github.com/OliveTin/OliveTin/actions/workflows/build-snapshot.yml)
|
||||
|
||||
## Use cases
|
||||
[OliveTin 2k to 3k upgrade guide](https://docs.olivetin.app/upgrade/2k3k.html)
|
||||
</div>
|
||||
|
||||
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-laptop_framed.png" />
|
||||
<a href = "#screenshots">More screenshots below</a>
|
||||
|
||||
All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app). This includes installation and usage guide, etc.
|
||||
|
||||
## Use cases
|
||||
|
||||
**Safely** give access to commands, for less technical people;
|
||||
|
||||
@@ -22,23 +30,23 @@ OliveTin gives **safe** and **simple** access to predefined shell commands from
|
||||
**Simplify** complex commands, make them accessible and repeatable;
|
||||
|
||||
* eg: Expose complex commands on touchscreen tablets stuck on walls around your house. `wake-on-lan aa:bb:cc:11:22:33`
|
||||
* eg: Run long running on your servers from your cell phone. `dnf update -y`
|
||||
* eg: Run long-lived commands on your servers from your cell phone. `dnf update -y`
|
||||
* eg: Define complex commands with lots of preset arguments, and turn a few arguments into dropdown select boxes. `docker rm {{ container }} && docker create {{ container }} && docker start {{ container }}`
|
||||
|
||||
[Join the community on Discord](https://discord.gg/jhYWWpNJ3v) to talk with other users about use cases, or to ask for support in getting started.
|
||||
|
||||
## YouTube demo video (6 mins)
|
||||
## YouTube demo video
|
||||
|
||||
[](https://www.youtube.com/watch?v=Ej6NM9rmZtk)
|
||||
[](https://www.youtube.com/watch?v=UBgOfNrzId4)
|
||||
|
||||
## Features
|
||||
|
||||
* **Responsive, touch-friendly UI** - great for tablets and mobile
|
||||
* **Super simple config in YAML** - because if it's not YAML now-a-days, it's not "cloud native" :-)
|
||||
* **Super simple config in YAML** - because if it's not YAML now-a-days, it's not "cloud native" :-)
|
||||
* **Dark mode** - for those of you that roll that way.
|
||||
* **Accessible** - passes all the accessibility checks in Firefox, and issues with accessibility are taken seriously.
|
||||
* **Container** - available for quickly testing and getting it up and running, great for the selfhosted community.
|
||||
* **Integrate with anything** - OliveTin just runs Linux shell commands, so theoretially you could integrate with a bunch of stuff just by using curl, ping, etc. However, writing your own shell scripts is a great way to extend OliveTin.
|
||||
* **Accessible** - passes all the accessibility checks in Firefox, and issues with accessibility are taken seriously.
|
||||
* **Container** - available for quickly testing and getting it up and running, great for the selfhosted community.
|
||||
* **Integrate with anything** - OliveTin just runs Linux shell commands, so theoretically you could integrate with a bunch of stuff just by using curl, ping, etc. However, writing your own shell scripts is a great way to extend OliveTin.
|
||||
* **Lightweight on resources** - uses only a few MB of RAM and barely any CPU. Written in Go, with a web interface written as a modern, responsive, Single Page App that uses the REST/gRPC API.
|
||||
* **Good amount of unit tests and style checks** - helps potential contributors be consistent, and helps with maintainability.
|
||||
|
||||
@@ -46,69 +54,34 @@ OliveTin gives **safe** and **simple** access to predefined shell commands from
|
||||
|
||||
Desktop web browser;
|
||||
|
||||

|
||||
<p align = "center">
|
||||
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-laptop_framed.png" />
|
||||
</p>
|
||||
|
||||
Desktop web browser (dark mode);
|
||||
Desktop web browser (dark mode);
|
||||
|
||||

|
||||
<p align = "center">
|
||||
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-darkop_framed.png" />
|
||||
</p>
|
||||
|
||||
Mobile screen size (responsive layout);
|
||||
Mobile screen size (responsive layout);
|
||||
|
||||

|
||||
<p align = "center">
|
||||
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-phone_framed.png" style = "width: 700px;" />
|
||||
</p>
|
||||
|
||||
## No-Nonsense Software Principles
|
||||
|
||||
OliveTin follows these principles:
|
||||
|
||||
* **Open Source & Free Software**: following the [Open Source Definition](https://opensource.org/osd) and the [Free Software Definition](https://www.gnu.org/philosophy/free-sw.html). All code and assets are available under the [AGPL-3.0 License](LICENSE).
|
||||
* **Independent**: No company owns the code or is responsible for the projects' governance.
|
||||
* **Inclusive**: No "core", "pro", "premium" or "enterprise" version. The only version is the one you can download and run, and it has all the features.
|
||||
* **Invisible**: No usage tracking, no user tracking, no ads, and no telemetry.
|
||||
* **Internal**: No internet connection required for any functionality.
|
||||
|
||||
## Documentation
|
||||
|
||||
All documentation can be found at http://docs.olivetin.app . This includes installation and usage guide, etc.
|
||||
|
||||
### Quickstart reference for `config.yaml`
|
||||
|
||||
This is a quick example of `config.yaml` - but again, lots of documentation for how to write your `config.yaml` can be found at [the documentation site.](https://docs.olivetin.app)
|
||||
|
||||
* (Recommended) [Linux package install (.rpm/.deb)](https://docs.olivetin.app/install-linuxpackage.html) install instructions
|
||||
* [Container (podman/docker)](https://docs.olivetin.app/install-container.html) install instructions
|
||||
* [Docker compose](https://docs.olivetin.app/install-compose.html) install instructions
|
||||
* [Helm on Kubernetes](https://docs.olivetin.app/install-helm.html) install instructions
|
||||
* [Kubernetes (manual)](https://docs.olivetin.app/install-k8s.html) install instructions
|
||||
* [.tar.gz (manual)](https://docs.olivetin.app/install-targz.html) install instructions
|
||||
|
||||
Put this `config.yaml` in `/etc/OliveTin/` if you're running a standard service, or mount it at `/config` if running in a container.
|
||||
|
||||
```yaml
|
||||
# Listen on all addresses available, port 1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
# Choose from INFO (default), WARN and DEBUG
|
||||
logLevel: "INFO"
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
# Docs: https://docs.olivetin.app/action-container-control.html
|
||||
- title: Restart Plex
|
||||
icon: restart
|
||||
shell: docker restart plex
|
||||
|
||||
# This will send 1 ping
|
||||
# Docs: https://docs.olivetin.app/action-ping.html
|
||||
- title: Ping host
|
||||
shell: ping {{ host }} -c {{ count }}
|
||||
icon: ping
|
||||
arguments:
|
||||
- name: host
|
||||
title: host
|
||||
type: ascii_identifier
|
||||
default: example.com
|
||||
|
||||
- name: count
|
||||
title: Count
|
||||
type: int
|
||||
default: 1
|
||||
|
||||
# Restart http on host "webserver1"
|
||||
# Docs: https://docs.olivetin.app/action-ssh.html
|
||||
- title: restart httpd
|
||||
icon: restart
|
||||
shell: ssh root@webserver1 'service httpd restart'
|
||||
```
|
||||
|
||||
A full example config can be found at in this repository - [config.yaml](https://github.com/OliveTin/OliveTin/blob/main/config.yaml).
|
||||
All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app). This includes installation and usage guide, etc.
|
||||
|
||||
You can find instructions in the docs on how to install as a **Linux package**, **Linux Container**, on **FreeBSD**, **Windows**, **MacOS** and other platforms, too!
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Currently, only the `main` branch is "supported".
|
||||
Currently, only the `main` branch is "supported".
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
@@ -10,4 +10,4 @@ Currently, only the `main` branch is "supported".
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please email `contact@jread.com` for responsible disclosure. Accepted issues will be made public once patched, and you will be given credit.
|
||||
Please email `contact@jread.com` for responsible disclosure. Accepted issues will be made public once patched, and you will be given credit.
|
||||
|
||||
20
buf.gen.yaml
@@ -1,20 +0,0 @@
|
||||
version: v1
|
||||
plugins:
|
||||
- name: go
|
||||
out: gen/grpc/
|
||||
opt: paths=source_relative
|
||||
|
||||
- name: go-grpc
|
||||
out: gen/grpc/
|
||||
opt: paths=source_relative,require_unimplemented_servers=false
|
||||
|
||||
- name: grpc-gateway
|
||||
out: gen/grpc/
|
||||
opt: paths=source_relative
|
||||
|
||||
# - name: swagger
|
||||
# out: reports/swagger
|
||||
|
||||
# - name: openapiv2
|
||||
# out: reports/openapiv2
|
||||
|
||||
7
buf.lock
@@ -1,7 +0,0 @@
|
||||
# Generated by buf. DO NOT EDIT.
|
||||
version: v1
|
||||
deps:
|
||||
- remote: buf.build
|
||||
owner: googleapis
|
||||
repository: googleapis
|
||||
commit: e9fcfb66f77242e5b8fd4564d7a01033
|
||||
@@ -1,108 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
grpcapi "github.com/OliveTin/OliveTin/internal/grpcapi"
|
||||
updatecheck "github.com/OliveTin/OliveTin/internal/updatecheck"
|
||||
|
||||
"github.com/OliveTin/OliveTin/internal/httpservers"
|
||||
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *config.Config
|
||||
version = "dev"
|
||||
commit = "nocommit"
|
||||
date = "nodate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
ForceQuote: true,
|
||||
DisableTimestamp: true,
|
||||
})
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"version": version,
|
||||
"commit": commit,
|
||||
"date": date,
|
||||
}).Info("OliveTin initializing")
|
||||
|
||||
log.SetLevel(log.DebugLevel) // Default to debug, to catch cfg issues
|
||||
|
||||
var configDir string
|
||||
flag.StringVar(&configDir, "configdir", ".", "Config directory path")
|
||||
flag.Parse()
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"value": configDir,
|
||||
}).Debugf("Value of -configdir flag")
|
||||
|
||||
viper.AutomaticEnv()
|
||||
viper.SetConfigName("config.yaml")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(configDir)
|
||||
viper.AddConfigPath("/config") // For containers.
|
||||
viper.AddConfigPath("/etc/OliveTin/")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Errorf("Config file error at startup. %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg = config.DefaultConfig()
|
||||
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
if e.Op == fsnotify.Write {
|
||||
log.Info("Config file changed:", e.String())
|
||||
|
||||
reloadConfig()
|
||||
}
|
||||
})
|
||||
|
||||
reloadConfig()
|
||||
|
||||
warnIfPuidGuid()
|
||||
|
||||
log.Info("Init complete")
|
||||
}
|
||||
|
||||
func warnIfPuidGuid() {
|
||||
if os.Getenv("PUID") != "" || os.Getenv("PGID") != "" {
|
||||
log.Warnf("PUID or PGID seem to be set to something, but they are ignored by OliveTin. Please check https://docs.olivetin.app/no-puid-pgid.html")
|
||||
}
|
||||
}
|
||||
|
||||
func reloadConfig() {
|
||||
if err := viper.UnmarshalExact(&cfg); err != nil {
|
||||
log.Errorf("Config unmarshal error %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg.Sanitize()
|
||||
}
|
||||
|
||||
func main() {
|
||||
configDir := path.Dir(viper.ConfigFileUsed())
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"configDir": configDir,
|
||||
}).Infof("OliveTin started")
|
||||
|
||||
log.Debugf("Config: %+v", cfg)
|
||||
|
||||
go updatecheck.StartUpdateChecker(version, commit, cfg, configDir)
|
||||
|
||||
go grpcapi.Start(cfg)
|
||||
|
||||
httpservers.StartServers(cfg)
|
||||
}
|
||||
369
config.yaml
@@ -1,79 +1,324 @@
|
||||
# There is a built-in micro proxy that will host the webui and REST API all on
|
||||
# one port (this is called the "Single HTTP Frontend") and means you just need
|
||||
# one open port in the container/firewalls/etc.
|
||||
# There is a built-in micro proxy that will host the webui and REST API all on
|
||||
# one port (this is called the "Single HTTP Frontend") and means you just need
|
||||
# one open port in the container/firewalls/etc.
|
||||
#
|
||||
# Listen on all addresses available, port 1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
bannerMessage: "This is an early alpha version of OliveTin 3000. Many thanks are broken, many things will change."
|
||||
bannerCss: "background-color: #b2e4b2; color: black; font-size: small; text-align: center; padding: .6em; border-radius: 0.5em;"
|
||||
|
||||
insecureAllowDumpSos: true
|
||||
insecureAllowDumpVars: true
|
||||
|
||||
# Choose from INFO (default), WARN and DEBUG
|
||||
logLevel: "INFO"
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
# This will run a simple script that you create.
|
||||
- title: Run backup script
|
||||
shell: /opt/backupScript.sh
|
||||
icon: backup
|
||||
# Checking for updates https://docs.olivetin.app/reference/updateChecks.html
|
||||
checkForUpdates: false
|
||||
|
||||
# This will send 1 ping (-c 1)
|
||||
# Docs: https://docs.olivetin.app/action-ping.html
|
||||
- title: Ping host
|
||||
shell: ping {{ host }} -c {{ count }}
|
||||
icon: ping
|
||||
arguments:
|
||||
- name: host
|
||||
title: host
|
||||
type: ascii_identifier
|
||||
default: example.com
|
||||
description: The host that you want to ping
|
||||
authLocalUsers:
|
||||
enabled: true
|
||||
|
||||
- name: count
|
||||
title: Count
|
||||
type: int
|
||||
default: 1
|
||||
description: How many times to do you want to ping?
|
||||
|
||||
# Restart lightdm on host "server1"
|
||||
# Docs: https://docs.olivetin.app/action-ping.html
|
||||
- title: restart httpd
|
||||
icon: restart
|
||||
shell: ssh root@server1 'service httpd restart'
|
||||
|
||||
# OliveTin can run long-running jobs like Ansible playbooks.
|
||||
#
|
||||
# For such jobs, you will need to install ansible-playbook on the host where
|
||||
# you are running OliveTin, or in the container.
|
||||
# Actions are commands that are executed by OliveTin, and normally show up as
|
||||
# buttons on the WebUI.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/action_execution/create_your_first.html
|
||||
actions:
|
||||
# This is the most simple action, it just runs the command and flashes the
|
||||
# button to indicate status.
|
||||
#
|
||||
# You probably want a much longer timeout as well (so that ansible completes).
|
||||
- title: "Run Ansible Playbook"
|
||||
icon: "🇦"
|
||||
shell: ansible-playbook -i /etc/hosts /root/myRepo/myPlaybook.yaml
|
||||
timeout: 120
|
||||
# If you are running OliveTin in a container remember to pass through the
|
||||
# docker socket! https://docs.olivetin.app/solutions/container-control-panel/index.html
|
||||
- title: Ping the Internet
|
||||
shell: ping -c 3 1.1.1.1
|
||||
icon: ping
|
||||
popupOnStart: execution-dialog-stdout-only
|
||||
|
||||
# This uses `popupOnStart: execution-dialog-stdout-only` to simply show just
|
||||
# the command output.
|
||||
- title: Check disk space
|
||||
icon: disk
|
||||
shell: df -h /media
|
||||
popupOnStart: execution-dialog-stdout-only
|
||||
|
||||
# This uses `popupOnStart: execution-dialog` to show a dialog with more
|
||||
# information about the command that was run.
|
||||
- title: check dmesg logs
|
||||
shell: dmesg | tail
|
||||
icon: logs
|
||||
popupOnStart: execution-dialog
|
||||
|
||||
# This uses `popupOnStart: execution-button` to display a mini button that
|
||||
# links to the logs.
|
||||
#
|
||||
# You can also rate-limit actions too.
|
||||
- title: date
|
||||
shell: date
|
||||
timeout: 6
|
||||
icon: clock
|
||||
popupOnStart: execution-button
|
||||
maxRate:
|
||||
- limit: 3
|
||||
duration: 5m
|
||||
|
||||
# You are not limited to operating system commands, and of course you can run
|
||||
# your own scripts. Here `maxConcurrent` stops the script running multiple
|
||||
# times in parallel. There is also a timeout that will kill the command if it
|
||||
# runs for too long.
|
||||
- title: Run backup script
|
||||
shell: /opt/backupScript.sh
|
||||
shellAfterCompleted: "apprise -t 'Notification: Backup script completed' -b 'The backup script completed with code {{ exitCode}}. The log is: \n {{ output }} '"
|
||||
maxConcurrent: 1
|
||||
timeout: 10
|
||||
icon: backup
|
||||
popupOnStart: execution-dialog
|
||||
|
||||
# When you want to prompt users for input, that is when you should use
|
||||
# `arguments` - this presents a popup dialog and asks for argument values.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/action_examples/ping.html
|
||||
- title: Ping host
|
||||
id: ping_host
|
||||
shell: ping {{ host }} -c {{ count }}
|
||||
icon: ping
|
||||
timeout: 100
|
||||
popupOnStart: execution-dialog-stdout-only
|
||||
arguments:
|
||||
- name: host
|
||||
title: Host
|
||||
type: ascii_identifier
|
||||
default: example.com
|
||||
description: The host that you want to ping
|
||||
|
||||
- name: count
|
||||
title: Count
|
||||
type: int
|
||||
default: 3
|
||||
description: How many times to do you want to ping?
|
||||
|
||||
# OliveTin can control containers - docker is just a command line app.
|
||||
#
|
||||
#
|
||||
# However, if you are running in a container you will need to do some setup,
|
||||
# see the docs below.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/action-container-control.html
|
||||
- title: Restart Docker Container
|
||||
icon: restart
|
||||
shell: docker restart {{ container }}
|
||||
arguments:
|
||||
- name: container
|
||||
title: Container name
|
||||
choices:
|
||||
- value: plex
|
||||
- value: traefik
|
||||
- value: grafana
|
||||
# Docs: https://docs.olivetin.app/solutions/container-control-panel/index.html
|
||||
- title: Restart Docker Container
|
||||
icon: restart
|
||||
shell: docker restart {{ .CurrentEntity }}
|
||||
arguments:
|
||||
- name: container
|
||||
title: Container name
|
||||
choices:
|
||||
- value: plex
|
||||
- value: traefik
|
||||
- value: grafana
|
||||
|
||||
- title: Slow Script
|
||||
shell: sleep 3
|
||||
timeout: 5
|
||||
icon: "🥱"
|
||||
# There is a special `confirmation` argument to help against accidental clicks
|
||||
# on "dangerous" actions.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/args/input_confirmation.html
|
||||
- title: Delete old backups
|
||||
icon: ashtonished
|
||||
shell: rm -rf /opt/oldBackups/
|
||||
arguments:
|
||||
- type: html
|
||||
title: Description
|
||||
default:
|
||||
The documentation for this action can be found at <a href = "example.com">example.com</a>.
|
||||
- type: confirmation
|
||||
title: Are you sure?!
|
||||
|
||||
- title: Broken Script (timeout)
|
||||
shell: sleep 5
|
||||
timeout: 5
|
||||
icon: "😪"
|
||||
# This is an action that runs a script included with OliveTin, that will
|
||||
# download themes. You will still need to set theme "themeName" in your config.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/reference/reference_themes_for_users.html
|
||||
- title: Get OliveTin Theme
|
||||
shell: olivetin-get-theme {{ themeGitRepo }} {{ themeFolderName }}
|
||||
icon: theme
|
||||
arguments:
|
||||
- name: themeGitRepo
|
||||
title: Theme's Git Repository
|
||||
description: Find new themes at https://olivetin.app/themes
|
||||
type: url
|
||||
|
||||
- name: themeFolderName
|
||||
title: Theme's Folder Name
|
||||
type: ascii_identifier
|
||||
|
||||
# Sometimes you want to run actions on other servers - don't overcomplicate
|
||||
# it, just use SSH! OliveTin includes a helper to make this easier, which is
|
||||
# entirely optional. You can also setup SSH manually.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/action_examples/ssh-easy.html
|
||||
# Docs: https://docs.olivetin.app/action_examples/ssh-manual.html
|
||||
- title: "Setup easy SSH"
|
||||
icon: ssh
|
||||
shell: olivetin-setup-easy-ssh
|
||||
popupOnStart: execution-dialog
|
||||
|
||||
# Here's how to use SSH with the "easy" config, to restart a service on
|
||||
# another server.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/action_examples/ssh-easy.html
|
||||
# Docs: https://docs.olivetin.app/action_examples/systemd_service.html
|
||||
- title: Restart httpd on server1
|
||||
id: restart_httpd
|
||||
icon: restart
|
||||
timeout: 1
|
||||
shell: ssh -F /config/ssh/easy.cfg root@server1 'service httpd restart'
|
||||
|
||||
# Lots of people use OliveTin to build web interfaces for their electronics
|
||||
# projects. It's best to install OliveTin as a native package (eg, .deb), and
|
||||
# then you can use either a python script or the `gpio` command.
|
||||
- title: Toggle GPIO light
|
||||
shell: gpioset gpiochip1 9=1
|
||||
icon: light
|
||||
|
||||
# There are several built-in shortcuts for the `icon` option, but you
|
||||
# can also just specify any HTML, this includes any unicode character,
|
||||
# or a <img = "..." /> link to a custom icon.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/action_customization/icons.html
|
||||
#
|
||||
# Lots of people use OliveTin to easily execute ansible-playbooks. You
|
||||
# probably want a much longer timeout as well (so that ansible completes).
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/action_examples/ansible.html
|
||||
- title: "Run Automation Playbook"
|
||||
icon: '🤖'
|
||||
shell: ansible-playbook -i /etc/hosts /root/myRepo/myPlaybook.yaml
|
||||
timeout: 120
|
||||
|
||||
# The following actions are "dummy" actions, used in a Dashboard. As long as
|
||||
# you have these referenced in a dashboard, they will not show up in the
|
||||
# `actions` view.
|
||||
- title: Ping hypervisor1
|
||||
shell: echo "hypervisor1 online"
|
||||
|
||||
- title: Ping hypervisor2
|
||||
shell: echo "hypervisor2 online"
|
||||
|
||||
- title: "{{ server.name }} Wake on Lan"
|
||||
shell: echo "Sending Wake on LAN to {{ server.hostname }}"
|
||||
entity: server
|
||||
|
||||
- title: "{{ server.name }} Power Off"
|
||||
shell: "echo 'Power Off Server: {{ server.hostname }}'"
|
||||
entity: server
|
||||
|
||||
- title: Ping All Servers
|
||||
shell: "echo 'Ping all servers'"
|
||||
icon: ping
|
||||
|
||||
- title: Start {{ .CurrentEntity.Names }}
|
||||
icon: box
|
||||
shell: docker start {{ .CurrentEntity.Names }}
|
||||
entity: container
|
||||
triggers: ["Update container entity file"]
|
||||
|
||||
- title: Stop {{ .CurrentEntity.Names }}
|
||||
icon: box
|
||||
shell: docker stop {{ .CurrentEntity.Names }}
|
||||
entity: container
|
||||
triggers: ["Update container entity file"]
|
||||
|
||||
# Lastly, you can hide actions from the web UI, this is useful for creating
|
||||
# background helpers that execute only on startup or a cron, for updating
|
||||
# entity files.
|
||||
|
||||
# - title: Update container entity file
|
||||
# shell: 'docker ps -a --format json > /etc/OliveTin/entities/containers.json'
|
||||
# hidden: true
|
||||
# execOnStartup: true
|
||||
# execOnCron: '*/1 * * * *'
|
||||
|
||||
# An entity is something that exists - a "thing", like a VM, or a Container
|
||||
# is an entity. OliveTin allows you to then dynamically generate actions based
|
||||
# around these entities.
|
||||
#
|
||||
# This is really useful if you want to generate wake on lan or poweroff actions
|
||||
# for `server` entities, for example.
|
||||
#
|
||||
# A very popular use case that entities were designed for was for `container`
|
||||
# entities - in a similar way you could generate `start`, `stop`, and `restart`
|
||||
# container actions.
|
||||
#
|
||||
# Entities are just loaded fome files on disk, OliveTin will also watch these
|
||||
# files for updates while OliveTin is running, and update entities.
|
||||
#
|
||||
# Entities can have properties defined in those files, and those can be used
|
||||
# in your configuration as variables. For example; `container.status`,
|
||||
# or `vm.hostname`.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/entities/intro.html
|
||||
entities:
|
||||
# YAML files are the default expected format, so you can use .yml or .yaml,
|
||||
# or even .txt, as long as the file contains valid a valid yaml LIST, then it
|
||||
# will load properly.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/entities/intro.html
|
||||
- file: entities/servers.yaml
|
||||
name: server
|
||||
|
||||
- file: entities/containers.json
|
||||
name: container
|
||||
|
||||
# Dashboards are a way of taking actions from the default "actions" view, and
|
||||
# organizing them into groups - either into folders, or fieldsets.
|
||||
#
|
||||
# The only way to properly use entities, are to use them with a `fieldset` on
|
||||
# a dashboard.
|
||||
#
|
||||
# Docs: https://docs.olivetin.app/dashboards/intro.html
|
||||
dashboards:
|
||||
# Top level items are dashboards.
|
||||
- title: My Servers
|
||||
contents:
|
||||
- title: All Servers
|
||||
type: fieldset
|
||||
contents:
|
||||
# The contents of a dashboard will try to look for an action with a
|
||||
# matching title IF the `contents: ` property is empty.
|
||||
- title: Ping All Servers
|
||||
|
||||
# If you create an item with some "contents:", OliveTin will show that as
|
||||
# directory.
|
||||
- title: Hypervisors
|
||||
contents:
|
||||
- title: Ping hypervisor1
|
||||
- title: Ping hypervisor2
|
||||
|
||||
# If you specify `type: fieldset` and some `contents`, it will show your
|
||||
# actions grouped together without a folder.
|
||||
- type: fieldset
|
||||
entity: server
|
||||
title: 'Server: {{ .CurrentEntity.hostname }}'
|
||||
contents:
|
||||
# By default OliveTin will look for an action with a matching title
|
||||
# and put it on the dashboard.
|
||||
#
|
||||
# Fieldsets also support `type: display`, which can display arbitary
|
||||
# text. This is useful for displaying things like a container's state.
|
||||
- type: display
|
||||
title: |
|
||||
Hostname: <strong>{{ server.name }}</strong>
|
||||
IP Address: <strong>{{ server.ip }}</strong>
|
||||
|
||||
# These are the actions (defined above) that we want on the dashboard.
|
||||
- title: '{{ server.name }} Wake on Lan'
|
||||
- title: '{{ server.name }} Power Off'
|
||||
|
||||
# This is the second dashboard.
|
||||
- title: My Containers
|
||||
contents:
|
||||
- title: 'Container {{ .CurrentEntity.Names }} ({{ .CurrentEntity.Image }})'
|
||||
entity: container
|
||||
type: fieldset
|
||||
contents:
|
||||
- type: display
|
||||
title: |
|
||||
{{ container.RunningFor }} <br /><br /><strong>{{ container.State }}</strong>
|
||||
|
||||
- title: 'Start {{ .CurrentEntity.Names }}'
|
||||
- title: 'Stop {{ .CurrentEntity.Names }}'
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
}
|
||||
1
frontend/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
fund=false
|
||||
21
frontend/Makefile
Normal file
@@ -0,0 +1,21 @@
|
||||
define delete-files
|
||||
python -c "import shutil;shutil.rmtree('$(1)', ignore_errors=True)"
|
||||
endef
|
||||
|
||||
codestyle:
|
||||
npm install
|
||||
npx eslint --fix main.js js/*
|
||||
npx stylelint style.css
|
||||
|
||||
clean:
|
||||
$(call delete-files,dist)
|
||||
|
||||
deps:
|
||||
npm install
|
||||
|
||||
build:
|
||||
npx vite build
|
||||
|
||||
dist: deps clean build
|
||||
|
||||
.PHONY: codestyle
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
69
frontend/index.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang = "en">
|
||||
<head>
|
||||
<meta charset = "UTF-8" />
|
||||
<meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
|
||||
<meta name = "description" content = "Give safe and simple access to predefined shell commands from a web interface." />
|
||||
|
||||
<title>OliveTin</title>
|
||||
|
||||
<link rel = "stylesheet" type = "text/css" href = "/theme.css" />
|
||||
<link rel = "stylesheet" href = "node_modules/@xterm/xterm/css/xterm.css" />
|
||||
|
||||
<link rel = "shortcut icon" type = "image/png" href = "OliveTinLogo.png" />
|
||||
|
||||
<link rel = "apple-touch-icon" sizes="57x57" href="OliveTinLogo-57px.png" />
|
||||
<link rel = "apple-touch-icon" sizes="120x120" href="OliveTinLogo-120px.png" />
|
||||
<link rel = "apple-touch-icon" sizes="180x180" href="OliveTinLogo-180px.png" />
|
||||
|
||||
<base href = "/" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<slot id = "app" />
|
||||
|
||||
<noscript>
|
||||
<div class = "error">Sorry, JavaScript is required to use OliveTin.</div>
|
||||
</noscript>
|
||||
|
||||
<dialog title = "Big Error Message" id = "big-error" class = "error padded-content">
|
||||
|
||||
</dialog>
|
||||
|
||||
<script type = "text/javascript">
|
||||
const bigErrorDialog = document.getElementById('big-error')
|
||||
|
||||
/**
|
||||
This is the bootstrap code, which relies on very simple, old javascript
|
||||
to at least display a helpful error message if we can't use OliveTin.
|
||||
*/
|
||||
window.showBigError = function (type, friendlyType, message, isFatal) {
|
||||
console.error('Error ' + type + ': ', message)
|
||||
return;
|
||||
|
||||
bigErrorDialog.innerHTML = '<h1>Error ' + friendlyType + '</h1><p>' + message + "</p><p><a href = 'http://docs.olivetin.app/troubleshooting/err-" + type + ".html' target = 'blank'/>" + type + " error in OliveTin Documentation</a></p>"
|
||||
|
||||
if (isFatal) {
|
||||
bigErrorDialog.innerHTML += '<p>You will need to refresh your browser to clear this message.</p>'
|
||||
} else {
|
||||
bigErrorDialog.innerHTML += '<p>This error message will go away automatically if the problem is solved.</p>'
|
||||
}
|
||||
|
||||
bigErrorDialog.showModal()
|
||||
|
||||
console.error('Error ' + type + ': ', message)
|
||||
}
|
||||
|
||||
window.clearBigErrors = function () {
|
||||
bigErrorDialog.close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type = "text/javascript" nomodule>
|
||||
showBigError("js-modules-not-supported", "Sorry, your browser does not support JavaScript modules.", null)
|
||||
</script>
|
||||
|
||||
<script type = "module" src = "main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
273
frontend/js/ArgumentForm.js
Normal file
@@ -0,0 +1,273 @@
|
||||
class ArgumentForm extends window.HTMLElement {
|
||||
getQueryParams () {
|
||||
return new URLSearchParams(window.location.search.substring(1))
|
||||
}
|
||||
|
||||
setup (json, callback) {
|
||||
this.setAttribute('class', 'action-arguments')
|
||||
|
||||
this.constructTemplate()
|
||||
this.domTitle.innerText = json.title
|
||||
this.domIcon.innerHTML = json.icon
|
||||
this.createDomFormArguments(json.arguments)
|
||||
|
||||
this.domBtnStart.onclick = () => {
|
||||
for (const arg of this.argInputs) {
|
||||
if (!arg.validity.valid) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const argvs = this.getArgumentValues()
|
||||
|
||||
callback(argvs)
|
||||
|
||||
this.remove()
|
||||
}
|
||||
|
||||
this.domBtnCancel.onclick = () => {
|
||||
this.clearBookmark()
|
||||
this.remove()
|
||||
}
|
||||
}
|
||||
|
||||
getArgumentValues () {
|
||||
const ret = []
|
||||
|
||||
for (const arg of this.argInputs) {
|
||||
if (arg.type === 'checkbox') {
|
||||
if (arg.checked) {
|
||||
arg.value = '1'
|
||||
} else {
|
||||
arg.value = '0'
|
||||
}
|
||||
}
|
||||
|
||||
if (arg.name === '') {
|
||||
continue
|
||||
}
|
||||
|
||||
ret.push({
|
||||
name: arg.name,
|
||||
value: arg.value
|
||||
})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
constructTemplate () {
|
||||
const tpl = document.getElementById('tplArgumentForm')
|
||||
const content = tpl.content.cloneNode(true)
|
||||
|
||||
this.appendChild(content)
|
||||
|
||||
this.domTitle = this.querySelector('h2')
|
||||
this.domIcon = this.querySelector('span.icon')
|
||||
this.domWrapper = this.querySelector('.wrapper')
|
||||
|
||||
this.domArgs = this.querySelector('.arguments')
|
||||
|
||||
this.domBtnStart = this.querySelector('[name=start]')
|
||||
this.domBtnCancel = this.querySelector('[name=cancel]')
|
||||
}
|
||||
|
||||
createDomFormArguments (args) {
|
||||
this.argInputs = []
|
||||
|
||||
for (const arg of args) {
|
||||
this.domArgs.appendChild(this.createDomLabel(arg))
|
||||
this.domArgs.appendChild(this.createDomSuggestions(arg))
|
||||
this.domArgs.appendChild(this.createDomInput(arg))
|
||||
this.domArgs.appendChild(this.createDomDescription(arg))
|
||||
}
|
||||
}
|
||||
|
||||
createDomLabel (arg) {
|
||||
const domLbl = document.createElement('label')
|
||||
|
||||
const lastChar = arg.title.charAt(arg.title.length - 1)
|
||||
|
||||
if (lastChar === '?' || lastChar === '.' || lastChar === ':') {
|
||||
domLbl.innerHTML = arg.title
|
||||
} else {
|
||||
domLbl.innerHTML = arg.title + ':'
|
||||
}
|
||||
|
||||
domLbl.setAttribute('for', arg.name)
|
||||
|
||||
return domLbl
|
||||
}
|
||||
|
||||
createDomSuggestions (arg) {
|
||||
if (typeof arg.suggestions !== 'object' || arg.suggestions.length === 0) {
|
||||
return document.createElement('span')
|
||||
}
|
||||
|
||||
const ret = document.createElement('datalist')
|
||||
ret.setAttribute('id', arg.name + '-choices')
|
||||
|
||||
for (const suggestion of Object.keys(arg.suggestions)) {
|
||||
const opt = document.createElement('option')
|
||||
|
||||
opt.setAttribute('value', suggestion)
|
||||
|
||||
if (typeof arg.suggestions[suggestion] !== 'undefined' && arg.suggestions[suggestion].length > 0) {
|
||||
opt.innerText = arg.suggestions[suggestion]
|
||||
}
|
||||
|
||||
ret.appendChild(opt)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
createDomInput (arg) {
|
||||
let domEl = null
|
||||
|
||||
if (arg.choices.length > 0 && (arg.type === 'select' || arg.type === '')) {
|
||||
domEl = document.createElement('select')
|
||||
|
||||
// select/choice elements don't get an onchange/validation because theoretically
|
||||
// the user should only select from a dropdown of valid options. The choices are
|
||||
// riggeriously checked on StartAction anyway. ValidateArgumentType is only
|
||||
// meant for showing simple warnings in the UI before running.
|
||||
|
||||
for (const choice of arg.choices) {
|
||||
domEl.appendChild(this.createSelectOption(choice))
|
||||
}
|
||||
} else {
|
||||
switch (arg.type) {
|
||||
case 'html':
|
||||
domEl = document.createElement('div')
|
||||
domEl.innerHTML = arg.defaultValue
|
||||
|
||||
return domEl
|
||||
case 'confirmation':
|
||||
this.domBtnStart.disabled = true
|
||||
|
||||
domEl = document.createElement('input')
|
||||
domEl.setAttribute('type', 'checkbox')
|
||||
domEl.onchange = () => {
|
||||
this.domBtnStart.disabled = false
|
||||
domEl.disabled = true
|
||||
}
|
||||
break
|
||||
case 'raw_string_multiline':
|
||||
domEl = document.createElement('textarea')
|
||||
domEl.setAttribute('rows', '5')
|
||||
domEl.style.resize = 'vertical'
|
||||
break
|
||||
case 'datetime':
|
||||
domEl = document.createElement('input')
|
||||
domEl.setAttribute('type', 'datetime-local')
|
||||
domEl.setAttribute('step', '1')
|
||||
break
|
||||
case 'checkbox':
|
||||
domEl = document.createElement('input')
|
||||
domEl.setAttribute('type', 'checkbox')
|
||||
domEl.setAttribute('name', arg.name)
|
||||
domEl.setAttribute('value', '1')
|
||||
|
||||
break
|
||||
case 'password':
|
||||
case 'email':
|
||||
domEl = document.createElement('input')
|
||||
domEl.setAttribute('type', arg.type)
|
||||
break
|
||||
default:
|
||||
domEl = document.createElement('input')
|
||||
|
||||
if (arg.type.startsWith('regex:')) {
|
||||
domEl.setAttribute('pattern', arg.type.replace('regex:', ''))
|
||||
}
|
||||
|
||||
domEl.onchange = () => {
|
||||
this.formatValidation(domEl, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
domEl.name = arg.name
|
||||
|
||||
// Use query parameter value if available
|
||||
const params = this.getQueryParams()
|
||||
const paramValue = params.get(arg.name)
|
||||
|
||||
if (paramValue !== null) {
|
||||
domEl.value = paramValue
|
||||
} else {
|
||||
domEl.value = arg.defaultValue
|
||||
}
|
||||
|
||||
// update the URL when a parameter is changed
|
||||
domEl.addEventListener('change', this.updateUrlWithArg)
|
||||
|
||||
if (typeof arg.suggestions === 'object' && Object.keys(arg.suggestions).length > 0) {
|
||||
domEl.setAttribute('list', arg.name + '-choices')
|
||||
}
|
||||
|
||||
this.argInputs.push(domEl)
|
||||
|
||||
return domEl
|
||||
}
|
||||
|
||||
async formatValidation (domEl, arg) {
|
||||
const validateArgumentTypeArgs = {
|
||||
value: domEl.value,
|
||||
type: arg.type
|
||||
}
|
||||
|
||||
const validation = await window.validateArgumentType(validateArgumentTypeArgs)
|
||||
|
||||
if (validation.valid) {
|
||||
domEl.setCustomValidity('')
|
||||
} else {
|
||||
domEl.setCustomValidity(validation.description)
|
||||
}
|
||||
}
|
||||
|
||||
updateUrlWithArg (ev) {
|
||||
if (!ev.target.name) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = new URL(window.location.href)
|
||||
|
||||
if (ev.target.type === 'password') {
|
||||
return
|
||||
}
|
||||
|
||||
// copy the parameter value
|
||||
url.searchParams.set(ev.target.name, ev.target.value)
|
||||
|
||||
// Update the URL without reloading the page
|
||||
window.history.replaceState({}, '', url.toString())
|
||||
}
|
||||
|
||||
createDomDescription (arg) {
|
||||
const domArgumentDescription = document.createElement('span')
|
||||
domArgumentDescription.classList.add('argument-description')
|
||||
domArgumentDescription.innerHTML = arg.description
|
||||
|
||||
return domArgumentDescription
|
||||
}
|
||||
|
||||
createSelectOption (choice) {
|
||||
const domEl = document.createElement('option')
|
||||
|
||||
domEl.setAttribute('value', choice.value)
|
||||
domEl.innerText = choice.title
|
||||
|
||||
return domEl
|
||||
}
|
||||
|
||||
clearBookmark () {
|
||||
// remove the action from the URL
|
||||
window.history.replaceState({
|
||||
path: window.location.pathname
|
||||
}, '', window.location.pathname)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('argument-form', ArgumentForm)
|
||||
29
frontend/js/ExecutionFeedbackButton.js
Normal file
@@ -0,0 +1,29 @@
|
||||
export class ExecutionFeedbackButton extends window.HTMLElement {
|
||||
onExecutionFinished (LogEntry) {
|
||||
if (LogEntry.timedOut) {
|
||||
this.renderExecutionResult('action-timeout', 'Timed out')
|
||||
} else if (LogEntry.blocked) {
|
||||
this.renderExecutionResult('action-blocked', 'Blocked!')
|
||||
} else if (LogEntry.exitCode !== 0) {
|
||||
this.renderExecutionResult('action-nonzero-exit', 'Exit code ' + LogEntry.exitCode)
|
||||
} else {
|
||||
this.ellapsed = Math.ceil(new Date(LogEntry.datetimeFinished) - new Date(LogEntry.datetimeStarted)) / 1000
|
||||
this.renderExecutionResult('action-success', 'Success!')
|
||||
}
|
||||
}
|
||||
|
||||
renderExecutionResult (resultCssClass, temporaryStatusMessage) {
|
||||
this.updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
|
||||
this.onExecStatusChanged()
|
||||
}
|
||||
|
||||
updateDom (resultCssClass, title) {
|
||||
if (resultCssClass == null) {
|
||||
this.btn.className = ''
|
||||
} else {
|
||||
this.btn.classList.add(resultCssClass)
|
||||
}
|
||||
|
||||
this.domTitle.innerText = title
|
||||
}
|
||||
}
|
||||
24
frontend/js/Mutex.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export class Mutex {
|
||||
constructor () {
|
||||
this._locked = false
|
||||
this._waiting = []
|
||||
}
|
||||
|
||||
lock () {
|
||||
const unlock = () => {
|
||||
const next = this._waiting.shift()
|
||||
if (next) {
|
||||
next(unlock)
|
||||
} else {
|
||||
this._locked = false
|
||||
}
|
||||
}
|
||||
|
||||
if (this._locked) {
|
||||
return new Promise(resolve => this._waiting.push(resolve)).then(() => unlock)
|
||||
} else {
|
||||
this._locked = true
|
||||
return Promise.resolve(unlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
76
frontend/js/OutputTerminal.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Terminal } from '@xterm/xterm'
|
||||
import { FitAddon } from '@xterm/addon-fit'
|
||||
import { Mutex } from './Mutex.js'
|
||||
|
||||
/**
|
||||
* xterm.js based terminal output for the execution dialog.
|
||||
*
|
||||
* the xterm.js methods for write(), reset() and clear() appear to be async,
|
||||
* but they do not return a Promise and instead use a callback. When calling
|
||||
* these methods in quick succession, the output can get garbled due to race
|
||||
* conditions.
|
||||
*
|
||||
* To avoid this, this class uses Mutex around those methods to ensure that
|
||||
* only one write OR reset is executed at a time, is completed, and the calls
|
||||
* occour in sequential order.
|
||||
*/
|
||||
export class OutputTerminal {
|
||||
constructor () {
|
||||
this.writeMutex = new Mutex()
|
||||
this.terminal = new Terminal({
|
||||
convertEol: true
|
||||
})
|
||||
|
||||
const fitAddon = new FitAddon()
|
||||
this.terminal.loadAddon(fitAddon)
|
||||
this.terminal.fit = fitAddon
|
||||
}
|
||||
|
||||
async write (out, then) {
|
||||
const unlock = await this.writeMutex.lock()
|
||||
|
||||
try {
|
||||
await new Promise(resolve => {
|
||||
this.terminal.write(out, () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
unlock()
|
||||
|
||||
if (then != null && then !== undefined) {
|
||||
then()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async reset () {
|
||||
const unlock = await this.writeMutex.lock()
|
||||
|
||||
try {
|
||||
await new Promise(resolve => {
|
||||
this.terminal.clear()
|
||||
this.terminal.reset()
|
||||
resolve()
|
||||
})
|
||||
} finally {
|
||||
unlock()
|
||||
}
|
||||
}
|
||||
|
||||
fit () {
|
||||
this.terminal.fit.fit()
|
||||
}
|
||||
|
||||
open (el) {
|
||||
this.terminal.open(el)
|
||||
}
|
||||
|
||||
close () {
|
||||
this.terminal.dispose()
|
||||
}
|
||||
|
||||
resize (cols, rows) {
|
||||
this.terminal.resize(cols, rows)
|
||||
}
|
||||
}
|
||||
13
frontend/js/marshaller.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export function initMarshaller () {
|
||||
window.addEventListener('EventOutputChunk', onOutputChunk)
|
||||
}
|
||||
|
||||
function onOutputChunk (evt) {
|
||||
const chunk = evt.payload
|
||||
|
||||
if (window.terminal) {
|
||||
if (chunk.executionTrackingId === window.terminal.executionTrackingId) {
|
||||
window.terminal.write(chunk.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
49
frontend/js/websocket.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { buttonResults } from '../resources/vue/stores/buttonResults.js'
|
||||
|
||||
export function checkWebsocketConnection () {
|
||||
reconnectWebsocket()
|
||||
}
|
||||
|
||||
window.websocketAvailable = false
|
||||
|
||||
async function reconnectWebsocket () {
|
||||
if (window.websocketAvailable) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
window.websocketAvailable = true
|
||||
for await (const e of window.client.eventStream()) {
|
||||
handleEvent(e)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Websocket connection failed: ', err)
|
||||
}
|
||||
|
||||
window.websocketAvailable = false
|
||||
console.log('Reconnecting websocket...')
|
||||
}
|
||||
|
||||
function handleEvent (msg) {
|
||||
const typeName = msg.event.value.$typeName.replace('olivetin.api.v1.', '')
|
||||
|
||||
const j = new Event(typeName)
|
||||
j.payload = msg.event.value
|
||||
|
||||
switch (typeName) {
|
||||
case 'EventOutputChunk':
|
||||
case 'EventConfigChanged':
|
||||
case 'EventEntityChanged':
|
||||
window.dispatchEvent(j)
|
||||
break
|
||||
case 'EventExecutionFinished':
|
||||
case 'EventExecutionStarted':
|
||||
buttonResults[msg.event.value.logEntry.executionTrackingId] = msg.event.value.logEntry
|
||||
window.dispatchEvent(j)
|
||||
break
|
||||
default:
|
||||
console.warn('Unhandled websocket message type from server: ', typeName)
|
||||
|
||||
window.showBigError('ws-unhandled-message', 'handling websocket message', 'Unhandled websocket message type from server: ' + typeName, true)
|
||||
}
|
||||
}
|
||||
53
frontend/main.js
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
import 'femtocrank/style.css'
|
||||
import './style.css'
|
||||
|
||||
import 'iconify-icon'
|
||||
|
||||
import { createClient } from '@connectrpc/connect'
|
||||
import { createConnectTransport } from '@connectrpc/connect-web'
|
||||
|
||||
import { OliveTinApiService } from './resources/scripts/gen/olivetin/api/v1/olivetin_pb'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import router from './resources/vue/router.js'
|
||||
import App from './resources/vue/App.vue'
|
||||
|
||||
import {
|
||||
initMarshaller
|
||||
} from './js/marshaller.js'
|
||||
|
||||
import { checkWebsocketConnection } from './js/websocket.js'
|
||||
|
||||
function initClient () {
|
||||
const transport = createConnectTransport({
|
||||
baseUrl: window.location.protocol + '//' + window.location.host + '/api/'
|
||||
|
||||
})
|
||||
|
||||
window.client = createClient(OliveTinApiService, transport)
|
||||
}
|
||||
|
||||
function setupVue () {
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
}
|
||||
|
||||
function main () {
|
||||
initClient()
|
||||
|
||||
// Expose websocket connection function globally so App.vue can call it after successful init
|
||||
window.checkWebsocketConnection = checkWebsocketConnection
|
||||
|
||||
setupVue()
|
||||
|
||||
initMarshaller()
|
||||
|
||||
// window.addEventListener('EventConfigChanged', fetchGetDashboardComponents)
|
||||
// window.addEventListener('EventEntityChanged', fetchGetDashboardComponents)
|
||||
}
|
||||
|
||||
main() // call self
|
||||
3345
frontend/package-lock.json
generated
Normal file
38
frontend/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "olivetin-webui",
|
||||
"version": "1.0.0",
|
||||
"description": "The WebUI for OliveTin",
|
||||
"repository": "https://github.com/OliveTin/OliveTin",
|
||||
"source": "index.html",
|
||||
"devDependencies": {
|
||||
"process": "^0.11.10",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-standard": "^39.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"parcelIgnore": [
|
||||
"theme.css",
|
||||
"OliveTinLogo.png",
|
||||
"OliveTinLogo-57px.png",
|
||||
"OliveTinLogo-120px.png",
|
||||
"OliveTinLogo-180px.png"
|
||||
],
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@connectrpc/connect": "^2.1.0",
|
||||
"@connectrpc/connect-web": "^2.1.0",
|
||||
"@hugeicons/core-free-icons": "^1.2.1",
|
||||
"@hugeicons/vue": "^1.0.3",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"iconify-icon": "^3.0.1",
|
||||
"picocrank": "^1.6.2",
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"vite": "^7.1.12",
|
||||
"vue-router": "^4.6.3"
|
||||
}
|
||||
}
|
||||
1694
frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts
vendored
Normal file
480
frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js
Normal file
296
frontend/resources/vue/ActionButton.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<div :id="`actionButton-${actionId}`" role="none" class="action-button">
|
||||
<button :id="`actionButtonInner-${actionId}`" :title="title" :disabled="!canExec || isDisabled"
|
||||
:class="buttonClasses" @click="handleClick">
|
||||
|
||||
<div class="navigate-on-start-container">
|
||||
<div v-if="navigateOnStart == 'pop'" class="navigate-on-start" title="Opens a popup dialog on start">
|
||||
<HugeiconsIcon :icon="ComputerTerminal01Icon" />
|
||||
</div>
|
||||
<div v-if="navigateOnStart == 'arg'" class="navigate-on-start" title="Opens an argument form on start">
|
||||
<HugeiconsIcon :icon="TypeCursorIcon" />
|
||||
</div>
|
||||
<div v-if="navigateOnStart == ''" class="navigate-on-start" title="Run in the background">
|
||||
<HugeiconsIcon :icon="WorkoutRunIcon" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="icon" v-html="unicodeIcon"></span>
|
||||
<span class="title" aria-live="polite">{{ displayTitle }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ArgumentForm from './views/ArgumentForm.vue'
|
||||
import { buttonResults } from './stores/buttonResults'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { WorkoutRunIcon, TypeCursorIcon, ComputerTerminal01Icon } from '@hugeicons/core-free-icons'
|
||||
|
||||
import { ref, watch, onMounted, inject } from 'vue'
|
||||
|
||||
const router = useRouter()
|
||||
const navigateOnStart = ref('')
|
||||
|
||||
const props = defineProps({
|
||||
actionData: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const actionId = ref('')
|
||||
const title = ref('')
|
||||
const canExec = ref(true)
|
||||
const popupOnStart = ref('')
|
||||
|
||||
// Display properties
|
||||
const unicodeIcon = ref('💩')
|
||||
const displayTitle = ref('')
|
||||
|
||||
// State
|
||||
const isDisabled = ref(false)
|
||||
const showArgumentForm = ref(false)
|
||||
|
||||
// Animation classes
|
||||
const buttonClasses = ref([])
|
||||
|
||||
// Timestamps
|
||||
const updateIterationTimestamp = ref(0)
|
||||
|
||||
function getUnicodeIcon(icon) {
|
||||
if (icon === '') {
|
||||
console.log('icon not found ', icon)
|
||||
|
||||
return '💩'
|
||||
} else {
|
||||
return unescape(icon)
|
||||
}
|
||||
}
|
||||
|
||||
function constructFromJson(json) {
|
||||
updateIterationTimestamp.value = 0
|
||||
|
||||
updateFromJson(json)
|
||||
|
||||
actionId.value = json.bindingId
|
||||
title.value = json.title
|
||||
canExec.value = json.canExec
|
||||
popupOnStart.value = json.popupOnStart
|
||||
|
||||
if (popupOnStart.value.includes('execution-dialog')) {
|
||||
navigateOnStart.value = 'pop'
|
||||
} else if (props.actionData.arguments.length > 0) {
|
||||
navigateOnStart.value = 'arg'
|
||||
}
|
||||
|
||||
isDisabled.value = !json.canExec
|
||||
displayTitle.value = title.value
|
||||
unicodeIcon.value = getUnicodeIcon(json.icon)
|
||||
}
|
||||
|
||||
function updateFromJson(json) {
|
||||
// Fields that should not be updated
|
||||
// title - as the callback URL relies on it
|
||||
|
||||
unicodeIcon.value = getUnicodeIcon(json.icon)
|
||||
}
|
||||
|
||||
async function handleClick() {
|
||||
if (props.actionData.arguments && props.actionData.arguments.length > 0) {
|
||||
router.push(`/actionBinding/${props.actionData.bindingId}/argumentForm`)
|
||||
} else {
|
||||
await startAction()
|
||||
}
|
||||
}
|
||||
|
||||
function getUniqueId() {
|
||||
if (window.isSecureContext) {
|
||||
return window.crypto.randomUUID()
|
||||
} else {
|
||||
return Date.now().toString()
|
||||
}
|
||||
}
|
||||
|
||||
async function startAction(actionArgs) {
|
||||
buttonClasses.value = [] // Removes old animation classes
|
||||
|
||||
if (actionArgs === undefined) {
|
||||
actionArgs = []
|
||||
}
|
||||
|
||||
// UUIDs are create client side, so that we can setup a "execution-button"
|
||||
// to track the execution before we send the request to the server.
|
||||
const startActionArgs = {
|
||||
bindingId: props.actionData.bindingId,
|
||||
arguments: actionArgs,
|
||||
uniqueTrackingId: getUniqueId()
|
||||
}
|
||||
|
||||
console.log('Watching buttonResults for', startActionArgs.uniqueTrackingId)
|
||||
|
||||
watch(
|
||||
() => buttonResults[startActionArgs.uniqueTrackingId],
|
||||
(newResult, oldResult) => {
|
||||
onLogEntryChanged(newResult)
|
||||
}
|
||||
)
|
||||
|
||||
try {
|
||||
await window.client.startAction(startActionArgs)
|
||||
} catch (err) {
|
||||
console.error('Failed to start action:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function onLogEntryChanged(logEntry) {
|
||||
if (logEntry.executionFinished) {
|
||||
onExecutionFinished(logEntry)
|
||||
} else {
|
||||
onExecutionStarted(logEntry)
|
||||
}
|
||||
}
|
||||
|
||||
function onExecutionStarted(logEntry) {
|
||||
if (popupOnStart.value && popupOnStart.value.includes('execution-dialog')) {
|
||||
router.push(`/logs/${logEntry.executionTrackingId}`)
|
||||
}
|
||||
|
||||
isDisabled.value = true
|
||||
}
|
||||
|
||||
function onExecutionFinished(logEntry) {
|
||||
if (logEntry.timedOut) {
|
||||
renderExecutionResult('action-timeout', 'Timed out')
|
||||
} else if (logEntry.blocked) {
|
||||
renderExecutionResult('action-blocked', 'Blocked!')
|
||||
} else if (logEntry.exitCode !== 0) {
|
||||
renderExecutionResult('action-nonzero-exit', 'Exit code ' + logEntry.exitCode)
|
||||
} else {
|
||||
const ellapsed = Math.ceil(new Date(logEntry.datetimeFinished) - new Date(logEntry.datetimeStarted)) / 1000
|
||||
renderExecutionResult('action-success', 'Success!')
|
||||
}
|
||||
}
|
||||
|
||||
function renderExecutionResult(resultCssClass, temporaryStatusMessage) {
|
||||
updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
|
||||
onExecStatusChanged()
|
||||
}
|
||||
|
||||
function updateDom(resultCssClass, newTitle) {
|
||||
if (resultCssClass == null) {
|
||||
buttonClasses.value = []
|
||||
} else {
|
||||
buttonClasses.value = [resultCssClass]
|
||||
}
|
||||
|
||||
displayTitle.value = newTitle
|
||||
}
|
||||
|
||||
function onExecStatusChanged() {
|
||||
isDisabled.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
updateDom(null, title.value)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
constructFromJson(props.actionData)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.actionData,
|
||||
(newData) => {
|
||||
updateFromJson(newData)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.action-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.action-button button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
padding: 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 0 .6em #aaa;
|
||||
font-size: .85em;
|
||||
border-radius: .7em;
|
||||
}
|
||||
|
||||
.action-button button:hover:not(:disabled) {
|
||||
background: #f5f5f5;
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.action-button button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-button button .icon {
|
||||
font-size: 3em;
|
||||
flex-grow: 1;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.action-button button .title {
|
||||
font-weight: 500;
|
||||
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
/* Animation classes */
|
||||
.action-button button.action-timeout {
|
||||
background: #fff3cd;
|
||||
border-color: #ffeaa7;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.action-button button.action-blocked {
|
||||
background: #f8d7da !important;
|
||||
border-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.action-button button.action-nonzero-exit {
|
||||
background: #f8d7da !important;
|
||||
border-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.action-button button.action-success {
|
||||
background: #d4edda !important;
|
||||
border-color: #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.action-button-footer {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.navigate-on-start-container {
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
height: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
168
frontend/resources/vue/App.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<Header title="OliveTin" :logoUrl="logoUrl" @toggleSidebar="toggleSidebar">
|
||||
<template #toolbar>
|
||||
<div id="banner" v-if="bannerMessage" :style="bannerCss">
|
||||
<p>{{ bannerMessage }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #user-info>
|
||||
<div class="flex-row" style="gap: .5em;">
|
||||
<span id="link-login" v-if="!isLoggedIn"><router-link to="/login">Login</router-link></span>
|
||||
<div v-else>
|
||||
<span id="username-text" :title="'Provider: ' + userProvider">{{ username }}</span>
|
||||
<span id="link-logout" v-if="isLoggedIn"><a href="/api/Logout">Logout</a></span>
|
||||
</div>
|
||||
<HugeiconsIcon :icon="UserCircle02Icon" width = "1.5em" height = "1.5em" />
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</Header>
|
||||
|
||||
<div id="layout">
|
||||
<Sidebar ref="sidebar" id = "mainnav" v-if="showNavigation && !initError" />
|
||||
|
||||
<div id="content" initial-martial-complete="{{ hasLoaded }}">
|
||||
<main title="Main content">
|
||||
<section v-if="initError" class="error-container error" style="text-align: center; padding: 2em;">
|
||||
<h2>Failed to Initialize OliveTin</h2>
|
||||
<p><strong>Error Message:</strong> {{ initErrorMessage }}</p>
|
||||
<p>Please check the your browser console first, and then the server logs for more details.</p>
|
||||
<button @click="retryInit" class="bad">Retry</button>
|
||||
</section>
|
||||
<router-view v-else :key="$route.fullPath" />
|
||||
</main>
|
||||
|
||||
<footer title="footer" v-if="showFooter && !initError">
|
||||
<p>
|
||||
<img title="application icon" src="../../OliveTinLogo.png" alt="OliveTin logo" height="1em"
|
||||
class="logo" />
|
||||
OliveTin 3000!
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
<a href="https://docs.olivetin.app" target="_new">Documentation</a>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/OliveTin/OliveTin/issues/new/choose" target="_new">Raise an issue on
|
||||
GitHub</a>
|
||||
</span>
|
||||
|
||||
<span>{{ currentVersion }}</span>
|
||||
|
||||
<span>{{ serverConnection }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<a id="available-version" href="http://olivetin.app" target="_blank" hidden>?</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Sidebar from 'picocrank/vue/components/Sidebar.vue';
|
||||
import Header from 'picocrank/vue/components/Header.vue';
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { Menu01Icon } from '@hugeicons/core-free-icons'
|
||||
import { UserCircle02Icon } from '@hugeicons/core-free-icons'
|
||||
import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||
import logoUrl from '../../OliveTinLogo.png';
|
||||
|
||||
const sidebar = ref(null);
|
||||
const username = ref('guest');
|
||||
const userProvider = ref('system');
|
||||
const isLoggedIn = ref(false);
|
||||
const serverConnection = ref('Connected');
|
||||
const currentVersion = ref('?');
|
||||
const bannerMessage = ref('');
|
||||
const bannerCss = ref('');
|
||||
const hasLoaded = ref(false);
|
||||
const showFooter = ref(true)
|
||||
const showNavigation = ref(true)
|
||||
const showLogs = ref(true)
|
||||
const showDiagnostics = ref(true)
|
||||
const initError = ref(false)
|
||||
const initErrorMessage = ref('')
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebar.value.toggle()
|
||||
}
|
||||
|
||||
async function requestInit() {
|
||||
try {
|
||||
const initResponse = await window.client.init({})
|
||||
|
||||
window.initResponse = initResponse
|
||||
window.initError = false
|
||||
window.initErrorMessage = ''
|
||||
window.initCompleted = true
|
||||
|
||||
username.value = initResponse.authenticatedUser
|
||||
currentVersion.value = initResponse.currentVersion
|
||||
bannerMessage.value = initResponse.bannerMessage || '';
|
||||
bannerCss.value = initResponse.bannerCss || '';
|
||||
showFooter.value = initResponse.showFooter
|
||||
showNavigation.value = initResponse.showNavigation
|
||||
showLogs.value = initResponse.showLogList
|
||||
showDiagnostics.value = initResponse.showDiagnostics
|
||||
|
||||
for (const rootDashboard of initResponse.rootDashboards) {
|
||||
sidebar.value.addNavigationLink({
|
||||
id: rootDashboard,
|
||||
name: rootDashboard,
|
||||
title: rootDashboard,
|
||||
path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`,
|
||||
icon: DashboardSquare01Icon,
|
||||
})
|
||||
}
|
||||
|
||||
sidebar.value.addSeparator()
|
||||
sidebar.value.addRouterLink('Entities')
|
||||
|
||||
if (showLogs.value) {
|
||||
sidebar.value.addRouterLink('Logs')
|
||||
}
|
||||
|
||||
if (showDiagnostics.value) {
|
||||
sidebar.value.addRouterLink('Diagnostics')
|
||||
}
|
||||
|
||||
hasLoaded.value = true;
|
||||
initError.value = false;
|
||||
|
||||
// Only start websocket connection after successful init
|
||||
if (window.checkWebsocketConnection) {
|
||||
window.checkWebsocketConnection()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error initializing client", error)
|
||||
initError.value = true
|
||||
initErrorMessage.value = error.message || 'Failed to connect to OliveTin server'
|
||||
window.initError = true
|
||||
window.initErrorMessage = error.message || 'Failed to connect to OliveTin server'
|
||||
window.initCompleted = false
|
||||
serverConnection.value = 'Disconnected'
|
||||
}
|
||||
}
|
||||
|
||||
function retryInit() {
|
||||
initError.value = false
|
||||
initErrorMessage.value = ''
|
||||
window.initError = false
|
||||
window.initErrorMessage = ''
|
||||
window.initCompleted = false
|
||||
requestInit()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
serverConnection.value = 'Connected';
|
||||
// Initialize global state
|
||||
window.initError = false
|
||||
window.initErrorMessage = ''
|
||||
window.initCompleted = false
|
||||
requestInit()
|
||||
})
|
||||
</script>
|
||||
174
frontend/resources/vue/Dashboard.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<section v-if="!dashboard && !initError" style = "text-align: center; padding: 2em;">
|
||||
<HugeiconsIcon :icon="Loading03Icon" width="3em" height="3em" style="animation: spin 1s linear infinite;" />
|
||||
<p>Loading dashboard...</p>
|
||||
<p style="color: var(--fg2);">{{ loadingTime }}s</p>
|
||||
</section>
|
||||
<section v-if="initError" style="text-align: center; padding: 2em;" class = "bad">
|
||||
<h2 style="color: var(--error);">Initialization Failed</h2>
|
||||
<p>{{ initError }}</p>
|
||||
<p style="color: var(--fg2);">Please check your configuration and try again.</p>
|
||||
</section>
|
||||
<div v-else-if="dashboard">
|
||||
<section v-if="dashboard.contents.length == 0">
|
||||
<legend>{{ dashboard.title }}</legend>
|
||||
<p style = "text-align: center" class = "padding">This dashboard is empty.</p>
|
||||
</section>
|
||||
|
||||
<section class="transparent" v-else>
|
||||
<div v-for="component in dashboard.contents" :key="component.title">
|
||||
<fieldset>
|
||||
<legend v-if = "dashboard.title != 'Default'">{{ component.title }}</legend>
|
||||
|
||||
<template v-for="subcomponent in component.contents">
|
||||
<DashboardComponent :component="subcomponent" />
|
||||
</template>
|
||||
</fieldset>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DashboardComponent from './components/DashboardComponent.vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { Loading03Icon } from '@hugeicons/core-free-icons'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
})
|
||||
|
||||
const dashboard = ref(null)
|
||||
const loadingTime = ref(0)
|
||||
const initError = ref(null)
|
||||
let loadingTimer = null
|
||||
let checkInitInterval = null
|
||||
|
||||
async function getDashboard() {
|
||||
let title = props.title
|
||||
|
||||
// If no specific title was provided or it's the placeholder 'default',
|
||||
// prefer the first configured root dashboard (e.g., "Test").
|
||||
if ((!title || title === 'default') && window.initResponse.rootDashboards && window.initResponse.rootDashboards.length > 0) {
|
||||
title = window.initResponse.rootDashboards[0]
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = await window.client.getDashboard({
|
||||
title: title,
|
||||
})
|
||||
|
||||
if (!ret || !ret.dashboard) {
|
||||
throw new Error('No dashboard found')
|
||||
}
|
||||
|
||||
dashboard.value = ret.dashboard
|
||||
document.title = ret.dashboard.title + ' - OliveTin'
|
||||
|
||||
// Clear any previous init error since we successfully loaded
|
||||
initError.value = null
|
||||
|
||||
// Stop the loading timer once dashboard is loaded
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
|
||||
// Set attribute to indicate dashboard is loaded successfully
|
||||
document.body.setAttribute('loaded-dashboard', title || 'default')
|
||||
} catch (e) {
|
||||
// On error, provide a safe fallback state
|
||||
console.error('Failed to load dashboard', e)
|
||||
dashboard.value = { title: title || 'Default', contents: [] }
|
||||
document.title = 'Error - OliveTin'
|
||||
|
||||
// Stop the loading timer on error
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
|
||||
// Set attribute even on error so tests can proceed
|
||||
document.body.setAttribute('loaded-dashboard', title || 'error')
|
||||
}
|
||||
}
|
||||
|
||||
function waitForInitAndLoadDashboard() {
|
||||
// Start the loading timer
|
||||
loadingTime.value = 0
|
||||
loadingTimer = setInterval(() => {
|
||||
loadingTime.value++
|
||||
}, 1000)
|
||||
|
||||
// Check if init has completed successfully
|
||||
if (window.initCompleted && window.initResponse) {
|
||||
getDashboard()
|
||||
} else if (window.initError) {
|
||||
// Init failed, show error immediately
|
||||
initError.value = window.initErrorMessage || 'Initialization failed. Please check your configuration and try again.'
|
||||
// Stop the loading timer since we're showing an error
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
} else {
|
||||
// Init hasn't completed yet, poll for completion
|
||||
checkInitInterval = setInterval(() => {
|
||||
if (window.initCompleted && window.initResponse) {
|
||||
clearInterval(checkInitInterval)
|
||||
checkInitInterval = null
|
||||
getDashboard()
|
||||
} else if (window.initError) {
|
||||
clearInterval(checkInitInterval)
|
||||
checkInitInterval = null
|
||||
initError.value = window.initErrorMessage || 'Initialization failed. Please check your configuration and try again.'
|
||||
// Stop the loading timer since we're showing an error
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
}
|
||||
}, 100) // Check every 100ms
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
waitForInitAndLoadDashboard()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// Clean up the timers when component is unmounted
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
if (checkInitInterval) {
|
||||
clearInterval(checkInitInterval)
|
||||
checkInitInterval = null
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
fieldset {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 180px);
|
||||
grid-auto-rows: 1fr;
|
||||
justify-content: center;
|
||||
place-items: stretch;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
141
frontend/resources/vue/ExecutionButton.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div
|
||||
:id="`execution-${executionTrackingId}`"
|
||||
class="execution-button"
|
||||
>
|
||||
<button
|
||||
:title="`${ellapsed}s`"
|
||||
@click="show"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//import { ExecutionFeedbackButton } from '../js/ExecutionFeedbackButton.js'
|
||||
|
||||
export default {
|
||||
name: 'ExecutionButton',
|
||||
// mixins: [ExecutionFeedbackButton],
|
||||
props: {
|
||||
executionTrackingId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ellapsed: 0,
|
||||
isWaiting: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
buttonText() {
|
||||
if (this.isWaiting) {
|
||||
return 'Executing...'
|
||||
} else {
|
||||
return `${this.ellapsed}s`
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.constructFromJson(this.executionTrackingId)
|
||||
},
|
||||
methods: {
|
||||
constructFromJson(json) {
|
||||
this.executionTrackingId = json
|
||||
this.ellapsed = 0
|
||||
this.isWaiting = true
|
||||
},
|
||||
|
||||
show() {
|
||||
this.$emit('show')
|
||||
|
||||
if (window.executionDialog) {
|
||||
window.executionDialog.reset()
|
||||
window.executionDialog.show()
|
||||
window.executionDialog.fetchExecutionResult(this.executionTrackingId)
|
||||
}
|
||||
},
|
||||
|
||||
onExecStatusChanged() {
|
||||
this.isWaiting = false
|
||||
this.domTitle = this.ellapsed + 's'
|
||||
},
|
||||
|
||||
// Override from ExecutionFeedbackButton
|
||||
onExecutionFinished(logEntry) {
|
||||
if (logEntry.timedOut) {
|
||||
this.renderExecutionResult('action-timeout', 'Timed out')
|
||||
} else if (logEntry.blocked) {
|
||||
this.renderExecutionResult('action-blocked', 'Blocked!')
|
||||
} else if (logEntry.exitCode !== 0) {
|
||||
this.renderExecutionResult('action-nonzero-exit', 'Exit code ' + logEntry.exitCode)
|
||||
} else {
|
||||
this.ellapsed = Math.ceil(new Date(logEntry.datetimeFinished) - new Date(logEntry.datetimeStarted)) / 1000
|
||||
this.renderExecutionResult('action-success', 'Success!')
|
||||
}
|
||||
},
|
||||
|
||||
renderExecutionResult(resultCssClass, temporaryStatusMessage) {
|
||||
this.updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
|
||||
this.onExecStatusChanged()
|
||||
},
|
||||
|
||||
updateDom(resultCssClass, title) {
|
||||
// For execution button, we don't need to update classes as much
|
||||
// since it's a simpler component
|
||||
if (resultCssClass) {
|
||||
this.$el.classList.add(resultCssClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.execution-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.execution-button button {
|
||||
padding: 0.25em 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.execution-button button:hover {
|
||||
background: #f5f5f5;
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
/* Animation classes */
|
||||
.execution-button button.action-timeout {
|
||||
background: #fff3cd;
|
||||
border-color: #ffeaa7;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.execution-button button.action-blocked {
|
||||
background: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.execution-button button.action-nonzero-exit {
|
||||
background: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.execution-button button.action-success {
|
||||
background: #d4edda;
|
||||
border-color: #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
</style>
|
||||
63
frontend/resources/vue/components/ActionStatusDisplay.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<span>
|
||||
<span :class="['action-status', statusClass]">{{ statusText }}</span><span>{{ exitCodeText }}</span>
|
||||
</span>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
logEntry: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const statusText = computed(() => {
|
||||
const logEntry = props.logEntry
|
||||
if (!logEntry) return 'unknown'
|
||||
|
||||
if (logEntry.executionFinished) {
|
||||
if (logEntry.blocked) {
|
||||
return 'Blocked'
|
||||
} else if (logEntry.timedOut) {
|
||||
return 'Timed out'
|
||||
} else {
|
||||
return 'Completed'
|
||||
}
|
||||
} else {
|
||||
return 'Still running...'
|
||||
}
|
||||
})
|
||||
|
||||
const exitCodeText = computed(() => {
|
||||
const logEntry = props.logEntry
|
||||
if (!logEntry) return ''
|
||||
if (logEntry.executionFinished) {
|
||||
if (logEntry.blocked || logEntry.timedOut) {
|
||||
return ''
|
||||
}
|
||||
return ' Exit code: ' + logEntry.exitCode
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const statusClass = computed(() => {
|
||||
const logEntry = props.logEntry
|
||||
if (!logEntry) return ''
|
||||
if (logEntry.executionFinished) {
|
||||
if (logEntry.blocked) {
|
||||
return 'action-blocked'
|
||||
} else if (logEntry.timedOut) {
|
||||
return 'action-timeout'
|
||||
} else if (logEntry.exitCode === 0) {
|
||||
return 'action-success'
|
||||
} else {
|
||||
return 'action-nonzero-exit'
|
||||
}
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
58
frontend/resources/vue/components/Breadcrumbs.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div id = "breadcrumbs">
|
||||
<template v-for="(link, index) in links" :key="link.name">
|
||||
<router-link :to="link.href">{{ link.name }}</router-link>
|
||||
<span v-if="index < links.length - 1" class="separator">
|
||||
»
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
padding: 0.4em;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const links = ref([]);
|
||||
|
||||
watch(() => route.matched, (matched) => {
|
||||
|
||||
links.value = [];
|
||||
matched.forEach((record) => {
|
||||
if (record.meta && record.meta.breadcrumb) {
|
||||
record.meta.breadcrumb.forEach((item) => {
|
||||
links.value.push({
|
||||
name: item.name,
|
||||
href: item.href || record.path || '/'
|
||||
});
|
||||
});
|
||||
} else if (record.name) {
|
||||
links.value.push({
|
||||
name: record.name,
|
||||
href: record.path || '/'
|
||||
});
|
||||
}
|
||||
});
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
41
frontend/resources/vue/components/DashboardComponent.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<ActionButton v-if="component.type == 'link'" :actionData="component.action" :key="component.title" />
|
||||
|
||||
<div v-else-if="component.type == 'directory'">
|
||||
<router-link :to="{ name: 'Dashboard', params: { title: component.title } }" class="dashboard-link">
|
||||
<button>
|
||||
{{ component.title }}
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div v-else-if="component.type == 'display'" class="display">
|
||||
<div v-html="component.title" />
|
||||
</div>
|
||||
|
||||
<template v-else-if="component.type == 'fieldset'">
|
||||
<fieldset>
|
||||
<legend>{{ component.title }}</legend>
|
||||
<template v-for="subcomponent in component.contents" :key="subcomponent.title">
|
||||
<DashboardComponent :component="subcomponent" />
|
||||
</template>
|
||||
</fieldset>
|
||||
</template>
|
||||
|
||||
<div v-else>
|
||||
OTHER: {{ component.type }}
|
||||
{{ component }}
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ActionButton from '../ActionButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
284
frontend/resources/vue/components/Pagination.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="pagination">
|
||||
<div class="pagination-info">
|
||||
<span class="pagination-text">
|
||||
Showing {{ startItem + 1 }}-{{ endItem }} of {{ total }} {{ itemTitle }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pagination-controls">
|
||||
<button
|
||||
class="pagination-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
title="Previous page"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M15.41 7.41L14 6l-6 6l6 6l1.41-1.41L10.83 12z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
|
||||
<div class="pagination-pages">
|
||||
<!-- First page -->
|
||||
<button
|
||||
v-if="showFirstPage"
|
||||
class="pagination-btn"
|
||||
:class="{ active: currentPage === 1 }"
|
||||
@click="goToPage(1)"
|
||||
>
|
||||
1
|
||||
</button>
|
||||
|
||||
<!-- Ellipsis after first page -->
|
||||
<span v-if="showFirstEllipsis" class="pagination-ellipsis">...</span>
|
||||
|
||||
<!-- Page numbers around current page -->
|
||||
<button
|
||||
v-for="page in visiblePages"
|
||||
:key="page"
|
||||
class="pagination-btn"
|
||||
:class="{ active: currentPage === page }"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
|
||||
<!-- Ellipsis before last page -->
|
||||
<span v-if="showLastEllipsis" class="pagination-ellipsis">...</span>
|
||||
|
||||
<!-- Last page -->
|
||||
<button
|
||||
v-if="showLastPage"
|
||||
class="pagination-btn"
|
||||
:class="{ active: currentPage === totalPages }"
|
||||
@click="goToPage(totalPages)"
|
||||
>
|
||||
{{ totalPages }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="pagination-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
title="Next page"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M8.59 16.59L10 18l6-6l-6-6L8.59 7.41L13.17 12z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pagination-size" v-if="canChangePageSize">
|
||||
<label for="page-size">Items per page:</label>
|
||||
<select
|
||||
id="page-size"
|
||||
v-model="localPageSize"
|
||||
@change="handlePageSizeChange"
|
||||
class="page-size-select"
|
||||
>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 25
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
canChangePageSize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
itemTitle: {
|
||||
type: String,
|
||||
default: 'items'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['page-change', 'page-size-change'])
|
||||
|
||||
const localPageSize = ref(props.pageSize)
|
||||
const localCurrentPage = ref(props.currentPage)
|
||||
|
||||
// Computed properties
|
||||
const totalPages = computed(() => Math.ceil(props.total / localPageSize.value))
|
||||
|
||||
const startItem = computed(() => (localCurrentPage.value - 1) * localPageSize.value)
|
||||
const endItem = computed(() => Math.min(localCurrentPage.value * localPageSize.value, props.total))
|
||||
|
||||
// Pagination logic
|
||||
const maxVisiblePages = 5
|
||||
const visiblePages = computed(() => {
|
||||
const pages = []
|
||||
const halfVisible = Math.floor(maxVisiblePages / 2)
|
||||
|
||||
let start = Math.max(1, localCurrentPage.value - halfVisible)
|
||||
let end = Math.min(totalPages.value, start + maxVisiblePages - 1)
|
||||
|
||||
// Adjust start if we're near the end
|
||||
if (end - start < maxVisiblePages - 1) {
|
||||
start = Math.max(1, end - maxVisiblePages + 1)
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
|
||||
return pages
|
||||
})
|
||||
|
||||
const showFirstPage = computed(() => visiblePages.value[0] > 1)
|
||||
const showLastPage = computed(() => visiblePages.value[visiblePages.value.length - 1] < totalPages.value)
|
||||
const showFirstEllipsis = computed(() => visiblePages.value[0] > 2)
|
||||
const showLastEllipsis = computed(() => visiblePages.value[visiblePages.value.length - 1] < totalPages.value - 1)
|
||||
|
||||
// Methods
|
||||
function goToPage(page) {
|
||||
if (page >= 1 && page <= totalPages.value && page !== localCurrentPage.value) {
|
||||
localCurrentPage.value = page
|
||||
emit('page-change', page)
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageSizeChange() {
|
||||
// Reset to first page when changing page size
|
||||
localCurrentPage.value = 1
|
||||
emit('page-size-change', localPageSize.value)
|
||||
emit('page-change', 1)
|
||||
}
|
||||
|
||||
// Watch for prop changes
|
||||
watch(() => props.currentPage, (newPage) => {
|
||||
localCurrentPage.value = newPage
|
||||
})
|
||||
|
||||
watch(() => props.pageSize, (newSize) => {
|
||||
localPageSize.value = newSize
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pagination-text {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.pagination-pages {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #dee2e6;
|
||||
background: #fff;
|
||||
color: #495057;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.pagination-btn:hover:not(:disabled) {
|
||||
background: #e9ecef;
|
||||
border-color: #adb5bd;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.pagination-btn.active {
|
||||
background: #c6d0d7;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pagination-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-ellipsis {
|
||||
padding: 0.5rem;
|
||||
color: #6c757d;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.pagination-size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.page-size-select {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.page-size-select:focus {
|
||||
outline: none;
|
||||
border-color: #5681af;
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.pagination {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pagination-size {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
129
frontend/resources/vue/router.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
import { Wrench01Icon } from '@hugeicons/core-free-icons'
|
||||
import { LeftToRightListDashIcon } from '@hugeicons/core-free-icons'
|
||||
import { CellsIcon } from '@hugeicons/core-free-icons'
|
||||
import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Actions',
|
||||
component: () => import('./Dashboard.vue'),
|
||||
meta: { title: 'Actions', icon: DashboardSquare01Icon }
|
||||
},
|
||||
{
|
||||
path: '/dashboards/:title',
|
||||
name: 'Dashboard',
|
||||
component: () => import('./Dashboard.vue'),
|
||||
props: true,
|
||||
meta: { title: 'Dashboard' }
|
||||
},
|
||||
{
|
||||
path: '/actionBinding/:bindingId/argumentForm',
|
||||
name: 'ActionBinding',
|
||||
component: () => import('./views/ArgumentForm.vue'),
|
||||
props: true,
|
||||
meta: { title: 'Action Binding' }
|
||||
},
|
||||
{
|
||||
path: '/logs',
|
||||
name: 'Logs',
|
||||
component: () => import('./views/LogsListView.vue'),
|
||||
meta: {
|
||||
title: 'Logs',
|
||||
icon: LeftToRightListDashIcon
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/entities',
|
||||
name: 'Entities',
|
||||
component: () => import('./views/EntitiesView.vue'),
|
||||
meta: {
|
||||
title: 'Entities',
|
||||
icon: CellsIcon
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/entity-details/:entityType/:entityKey',
|
||||
name: 'EntityDetails',
|
||||
component: () => import('./views/EntityDetailsView.vue'),
|
||||
props: true,
|
||||
meta: {
|
||||
title: 'OliveTin - Entity Details',
|
||||
breadcrumb: [
|
||||
{ name: "Entities", href: "/entities" },
|
||||
{ name: "Entity Details" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/logs/:executionTrackingId',
|
||||
name: 'Execution',
|
||||
component: () => import('./views/ExecutionView.vue'),
|
||||
props: true,
|
||||
meta: {
|
||||
title: 'Execution',
|
||||
breadcrumb: [
|
||||
{ name: "Logs", href: "/logs" },
|
||||
{ name: "Execution" },
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/diagnostics',
|
||||
name: 'Diagnostics',
|
||||
component: () => import('./views/DiagnosticsView.vue'),
|
||||
meta: {
|
||||
title: 'Diagnostics',
|
||||
icon: Wrench01Icon
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('./views/LoginView.vue'),
|
||||
meta: { title: 'Login' }
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('./views/NotFoundView.vue'),
|
||||
meta: { title: 'Page Not Found' }
|
||||
}
|
||||
]
|
||||
|
||||
// Create router instance
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
} else {
|
||||
return { top: 0 }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Navigation guard to update page title
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta && to.meta.title) {
|
||||
document.title = to.meta.title + " - OliveTin"
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
// Navigation guard for authentication (if needed)
|
||||
router.beforeEach((to, from, next) => {
|
||||
// Check if user is authenticated for protected routes
|
||||
const isAuthenticated = window.isAuthenticated || true // Default to true for now
|
||||
|
||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||
next('/login')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
3
frontend/resources/vue/stores/buttonResults.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const buttonResults = reactive({})
|
||||
345
frontend/resources/vue/views/ArgumentForm.vue
Normal file
@@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<section id = "argument-popup">
|
||||
<div class="section-header">
|
||||
<h2>Start action: {{ title }}</h2>
|
||||
</div>
|
||||
<div class="section-content padding">
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<template v-if="actionArguments.length > 0">
|
||||
|
||||
<template v-for="arg in actionArguments" :key="arg.name" class="argument-group">
|
||||
<label :for="arg.name">
|
||||
{{ formatLabel(arg.title) }}
|
||||
</label>
|
||||
|
||||
<datalist v-if="arg.suggestions && Object.keys(arg.suggestions).length > 0" :id="`${arg.name}-choices`">
|
||||
<option v-for="(suggestion, key) in arg.suggestions" :key="key" :value="key">
|
||||
{{ suggestion }}
|
||||
</option>
|
||||
</datalist>
|
||||
|
||||
<select v-if="getInputComponent(arg) === 'select'" :id="arg.name" :name="arg.name" :value="getArgumentValue(arg)"
|
||||
:required="arg.required" @input="handleInput(arg, $event)" @change="handleChange(arg, $event)">
|
||||
<option v-for="choice in arg.choices" :key="choice.value" :value="choice.value">
|
||||
{{ choice.title || choice.value }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<component v-else :is="getInputComponent(arg)" :id="arg.name" :name="arg.name" :value="getArgumentValue(arg)"
|
||||
:list="arg.suggestions ? `${arg.name}-choices` : undefined"
|
||||
:type="getInputComponent(arg) !== 'select' ? getInputType(arg) : undefined"
|
||||
:rows="arg.type === 'raw_string_multiline' ? 5 : undefined"
|
||||
:step="arg.type === 'datetime' ? 1 : undefined" :pattern="getPattern(arg)" :required="arg.required"
|
||||
@input="handleInput(arg, $event)" @change="handleChange(arg, $event)" />
|
||||
|
||||
<span class="argument-description" v-html="arg.description"></span>
|
||||
</template>
|
||||
</template>
|
||||
<div v-else>
|
||||
<p>No arguments required</p>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button name="start" type="submit" :disabled="!isFormValid || (hasConfirmation && !confirmationChecked)">
|
||||
Start
|
||||
</button>
|
||||
<button name="cancel" type="button" @click="handleCancel">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const emit = defineEmits(['submit', 'cancel', 'close'])
|
||||
|
||||
// Reactive data
|
||||
const dialog = ref(null)
|
||||
const title = ref('')
|
||||
const icon = ref('')
|
||||
//const arguments = ref([])
|
||||
const argValues = ref({})
|
||||
const confirmationChecked = ref(false)
|
||||
const hasConfirmation = ref(false)
|
||||
const formErrors = ref({})
|
||||
const actionArguments = ref([])
|
||||
|
||||
// Computed properties
|
||||
const isFormValid = computed(() => Object.keys(formErrors.value).length === 0)
|
||||
|
||||
const props = defineProps({
|
||||
bindingId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// Methods
|
||||
async function setup() {
|
||||
const ret = await window.client.getActionBinding({
|
||||
bindingId: props.bindingId
|
||||
})
|
||||
|
||||
const action = ret.action
|
||||
console.log('action', action)
|
||||
|
||||
title.value = action.title
|
||||
icon.value = action.icon
|
||||
actionArguments.value = action.arguments || []
|
||||
argValues.value = {}
|
||||
formErrors.value = {}
|
||||
confirmationChecked.value = false
|
||||
hasConfirmation.value = false
|
||||
|
||||
// Initialize values from query params or defaults
|
||||
actionArguments.value.forEach(arg => {
|
||||
const paramValue = getQueryParamValue(arg.name)
|
||||
argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
|
||||
|
||||
if (arg.type === 'confirmation') {
|
||||
hasConfirmation.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getQueryParamValue(paramName) {
|
||||
const params = new URLSearchParams(window.location.search.substring(1))
|
||||
return params.get(paramName)
|
||||
}
|
||||
|
||||
function formatLabel(title) {
|
||||
const lastChar = title.charAt(title.length - 1)
|
||||
if (lastChar === '?' || lastChar === '.' || lastChar === ':') {
|
||||
return title
|
||||
}
|
||||
return title + ':'
|
||||
}
|
||||
|
||||
function getInputComponent(arg) {
|
||||
if (arg.type === 'html') {
|
||||
return 'div'
|
||||
} else if (arg.type === 'raw_string_multiline') {
|
||||
return 'textarea'
|
||||
} else if (arg.choices && arg.choices.length > 0 && (arg.type === 'select' || arg.type === '')) {
|
||||
return 'select'
|
||||
} else {
|
||||
return 'input'
|
||||
}
|
||||
}
|
||||
|
||||
function getInputType(arg) {
|
||||
if (arg.type === 'html' || arg.type === 'raw_string_multiline' || arg.type === 'select') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (arg.type === 'ascii_identifier') {
|
||||
return 'text'
|
||||
}
|
||||
|
||||
return arg.type
|
||||
}
|
||||
|
||||
function getPattern(arg) {
|
||||
if (arg.type && arg.type.startsWith('regex:')) {
|
||||
return arg.type.replace('regex:', '')
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getArgumentValue(arg) {
|
||||
if (arg.type === 'checkbox') {
|
||||
return argValues.value[arg.name] === '1' || argValues.value[arg.name] === true
|
||||
}
|
||||
return argValues.value[arg.name] || ''
|
||||
}
|
||||
|
||||
function handleInput(arg, event) {
|
||||
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value
|
||||
argValues.value[arg.name] = value
|
||||
updateUrlWithArg(arg.name, value)
|
||||
}
|
||||
|
||||
function handleChange(arg, event) {
|
||||
if (arg.type === 'confirmation') {
|
||||
confirmationChecked.value = event.target.checked
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the input
|
||||
validateArgument(arg, event.target.value)
|
||||
}
|
||||
|
||||
async function validateArgument(arg, value) {
|
||||
if (!arg.type || arg.type.startsWith('regex:')) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const validateArgumentTypeArgs = {
|
||||
value: value,
|
||||
type: arg.type
|
||||
}
|
||||
|
||||
const validation = await window.validateArgumentType(validateArgumentTypeArgs)
|
||||
|
||||
if (validation.valid) {
|
||||
delete formErrors.value[arg.name]
|
||||
} else {
|
||||
formErrors.value[arg.name] = validation.description
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Validation failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function updateUrlWithArg(name, value) {
|
||||
if (name && value !== undefined) {
|
||||
const url = new URL(window.location.href)
|
||||
|
||||
// Don't add passwords to URL
|
||||
const arg = actionArguments.value.find(a => a.name === name)
|
||||
if (arg && arg.type === 'password') {
|
||||
return
|
||||
}
|
||||
|
||||
url.searchParams.set(name, value)
|
||||
window.history.replaceState({}, '', url.toString())
|
||||
}
|
||||
}
|
||||
|
||||
function getArgumentValues() {
|
||||
const ret = []
|
||||
|
||||
for (const arg of actionArguments.value) {
|
||||
let value = argValues.value[arg.name] || ''
|
||||
|
||||
if (arg.type === 'checkbox') {
|
||||
value = value ? '1' : '0'
|
||||
}
|
||||
|
||||
ret.push({
|
||||
name: arg.name,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
// Validate all inputs
|
||||
let isValid = true
|
||||
|
||||
for (const arg of actionArguments.value) {
|
||||
const value = argValues.value[arg.name]
|
||||
if (arg.required && (!value || value === '')) {
|
||||
formErrors.value[arg.name] = 'This field is required'
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
return
|
||||
}
|
||||
|
||||
const argvs = getArgumentValues()
|
||||
emit('submit', argvs)
|
||||
close()
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
router.back()
|
||||
clearBookmark()
|
||||
emit('cancel')
|
||||
close()
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function clearBookmark() {
|
||||
// Remove the action from the URL
|
||||
window.history.replaceState({
|
||||
path: window.location.pathname
|
||||
}, '', window.location.pathname)
|
||||
}
|
||||
|
||||
function show() {
|
||||
if (dialog.value) {
|
||||
dialog.value.showModal()
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (dialog.value) {
|
||||
dialog.value.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Expose methods for parent components
|
||||
defineExpose({
|
||||
show,
|
||||
close
|
||||
})
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
setup()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
form {
|
||||
grid-template-columns: max-content auto auto;
|
||||
}
|
||||
|
||||
.argument-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.argument-group label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.argument-group input:invalid,
|
||||
.argument-group select:invalid,
|
||||
.argument-group textarea:invalid {
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.argument-description {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* Checkbox specific styling */
|
||||
.argument-group input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.argument-group input[type="checkbox"]+label {
|
||||
display: inline;
|
||||
font-weight: normal;
|
||||
}
|
||||
</style>
|
||||
158
frontend/resources/vue/views/DiagnosticsView.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<Section title = "Get support">
|
||||
<p>If you are having problems with OliveTin and want to raise a support request, it would be very helpful to include a sosreport from this page.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://docs.olivetin.app/sosreport.html" target="_blank">sosreport Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href = "https://docs.olivetin.app/troubleshooting/wheretofindhelp.html" target="_blank">Where to find help</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Section>
|
||||
|
||||
<Section title = "SSH">
|
||||
<dl>
|
||||
<dt>Found Key</dt>
|
||||
<dd>{{ diagnostics.sshFoundKey || '?' }}</dd>
|
||||
<dt>Found Config</dt>
|
||||
<dd>{{ diagnostics.sshFoundConfig || '?' }}</dd>
|
||||
</dl>
|
||||
</Section>
|
||||
|
||||
<Section title = "SOS Report">
|
||||
<p>This section allows you to generate a detailed report of your configuration and environment. It is a good idea to include this when raising a support request.</p>
|
||||
|
||||
<div role="toolbar">
|
||||
<button @click="generateSosReport" :disabled="loading" class = "good">Generate SOS Report</button>
|
||||
</div>
|
||||
|
||||
<textarea v-model="sosReport" readonly style="flex: 1; min-height: 200px; resize: vertical;"></textarea>
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
|
||||
const diagnostics = ref({})
|
||||
const loading = ref(false)
|
||||
const sosReport = ref('Waiting to start...')
|
||||
|
||||
async function fetchDiagnostics() {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const response = await window.client.getDiagnostics();
|
||||
diagnostics.value = {
|
||||
sshFoundKey: response.SshFoundKey,
|
||||
sshFoundConfig: response.SshFoundConfig
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch diagnostics:', err);
|
||||
diagnostics.value = {
|
||||
sshFoundKey: 'Unknown',
|
||||
sshFoundConfig: 'Unknown'
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function formatKey(key) {
|
||||
return key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, str => str.toUpperCase())
|
||||
.trim()
|
||||
}
|
||||
|
||||
async function generateSosReport() {
|
||||
const response = await window.client.sosReport()
|
||||
console.log("response", response)
|
||||
sosReport.value = response.alert
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDiagnostics()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.diagnostics-view {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.diagnostics-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.note {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #007bff;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 0.875rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.note a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.note a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.diagnostics-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.diagnostics-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
}
|
||||
|
||||
.diagnostics-table td:first-child {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.diagnostics-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.error-list {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.error-item {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #dc3545;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.error-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
</style>
|
||||
50
frontend/resources/vue/views/EntitiesView.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<Section class = "with-header-and-content" v-if="entityDefinitions.length === 0" title="Loading entity definitions...">
|
||||
<div class = "section-header">
|
||||
<h2 class="loading-message">
|
||||
Loading entity definitions...
|
||||
</h2>
|
||||
</div>
|
||||
</Section>
|
||||
<template v-else>
|
||||
<Section v-for="def in entityDefinitions" :key="def.name" :title="'Entity: ' + def.title ">
|
||||
<div class = "section-content">
|
||||
<p>{{ def.instances.length }} instances.</p>
|
||||
|
||||
<ul>
|
||||
<li v-for="inst in def.instances" :key="inst.id">
|
||||
<router-link :to="{ name: 'EntityDetails', params: { entityType: inst.type, entityKey: inst.uniqueKey } }">
|
||||
{{ inst.title }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Used on Dashboards:</h3>
|
||||
<ul>
|
||||
<li v-for="dash in def.usedOnDashboards">
|
||||
<router-link :to="{ name: 'Dashboard', params: { title: dash } }">
|
||||
{{ dash }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
|
||||
const entityDefinitions = ref([])
|
||||
|
||||
async function fetchEntities() {
|
||||
const ret = await window.client.getEntities()
|
||||
|
||||
entityDefinitions.value = ret.entityDefinitions
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchEntities()
|
||||
})
|
||||
</script>
|
||||
40
frontend/resources/vue/views/EntityDetailsView.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<Section title="Entity Details">
|
||||
<div>
|
||||
<p v-if="!entityDetails">Loading entity details...</p>
|
||||
<p v-else-if="!entityDetails.title">No details available for this entity.</p>
|
||||
<p v-else>{{ entityDetails.title }}</p>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
|
||||
const entityDetails = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
entityType: String,
|
||||
entityKey: String
|
||||
})
|
||||
|
||||
async function fetchEntityDetails() {
|
||||
try {
|
||||
const response = await window.client.getEntity({
|
||||
type: props.entityType,
|
||||
uniqueKey: props.entityKey
|
||||
})
|
||||
|
||||
entityDetails.value = response
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch entity details:', err)
|
||||
window.showBigError('fetch-entity-details', 'getting entity details', err, false)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchEntityDetails()
|
||||
})
|
||||
|
||||
</script>
|
||||
310
frontend/resources/vue/views/ExecutionView.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<Section :title="'Execution Results: ' + title" id = "execution-results-popup">
|
||||
<template #toolbar>
|
||||
<button @click="toggleSize" title="Toggle dialog size">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M3 3h6v2H6.462l4.843 4.843l-1.415 1.414L5 6.367V9H3zm0 18h6v-2H6.376l4.929-4.928l-1.415-1.414L5 17.548V15H3zm12 0h6v-6h-2v2.524l-4.867-4.866l-1.414 1.414L17.647 19H15zm6-18h-6v2h2.562l-4.843 4.843l1.414 1.414L19 6.39V9h2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<div v-if="logEntry" class = "flex-row">
|
||||
<dl class = "fg1">
|
||||
<dt>Duration</dt>
|
||||
<dd><span v-html="duration"></span></dd>
|
||||
|
||||
<dt>Status</dt>
|
||||
<dd>
|
||||
<ActionStatusDisplay :log-entry="logEntry" id = "execution-dialog-status" />
|
||||
</dd>
|
||||
</dl>
|
||||
<span class="icon" role="img" v-html="icon" style = "align-self: start"></span>
|
||||
</div>
|
||||
|
||||
<div ref="xtermOutput"></div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="flex-row g1 buttons padded-content">
|
||||
<button @click="goBack" title="Go back">
|
||||
<HugeiconsIcon :icon="ArrowLeftIcon" />
|
||||
Back
|
||||
</button>
|
||||
|
||||
<div class = "fg1" />
|
||||
|
||||
<button :disabled="!canRerun" @click="rerunAction" title="Rerun">
|
||||
<HugeiconsIcon :icon="WorkoutRunIcon" />
|
||||
Rerun
|
||||
</button>
|
||||
<button :disabled="!canKill" @click="killAction" title="Kill" id = "execution-dialog-kill-action">
|
||||
<HugeiconsIcon :icon="Cancel02Icon" />
|
||||
Kill
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import { OutputTerminal } from '../../../js/OutputTerminal.js'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { WorkoutRunIcon, Cancel02Icon, ArrowLeftIcon } from '@hugeicons/core-free-icons'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { buttonResults } from '../stores/buttonResults'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// Refs for DOM elements
|
||||
const xtermOutput = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
executionTrackingId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const executionTrackingId = ref(props.executionTrackingId)
|
||||
const hideBasics = ref(false)
|
||||
const hideDetails = ref(false)
|
||||
const hideDetailsOnResult = ref(false)
|
||||
const executionSeconds = ref(0)
|
||||
const icon = ref('')
|
||||
const title = ref('Waiting for result...')
|
||||
const titleTooltip = ref('')
|
||||
const duration = ref('')
|
||||
const logEntry = ref(null)
|
||||
const canRerun = ref(false)
|
||||
const canKill = ref(false)
|
||||
|
||||
let executionTicker = null
|
||||
let terminal = null
|
||||
|
||||
function initializeTerminal() {
|
||||
terminal = new OutputTerminal(executionTrackingId.value, this)
|
||||
terminal.open(xtermOutput.value)
|
||||
terminal.resize(80, 24)
|
||||
|
||||
window.terminal = terminal
|
||||
}
|
||||
|
||||
function toggleSize() {
|
||||
terminal.fit()
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
executionSeconds.value = 0
|
||||
executionTrackingId.value = 'notset'
|
||||
hideBasics.value = false
|
||||
hideDetails.value = false
|
||||
hideDetailsOnResult.value = false
|
||||
|
||||
icon.value = ''
|
||||
title.value = 'Waiting for result...'
|
||||
titleTooltip.value = ''
|
||||
duration.value = ''
|
||||
|
||||
canRerun.value = false
|
||||
canKill.value = false
|
||||
logEntry.value = null
|
||||
|
||||
if (terminal) {
|
||||
await terminal.reset()
|
||||
terminal.fit()
|
||||
}
|
||||
}
|
||||
|
||||
function show(actionButton) {
|
||||
if (actionButton) {
|
||||
icon.value = actionButton.domIcon.innerText
|
||||
}
|
||||
|
||||
canKill.value = true
|
||||
|
||||
// Clear existing ticker
|
||||
if (executionTicker) {
|
||||
clearInterval(executionTicker)
|
||||
}
|
||||
|
||||
executionSeconds.value = 0
|
||||
executionTick()
|
||||
executionTicker = setInterval(() => {
|
||||
executionTick()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function rerunAction() {
|
||||
let startActionArgs = {}
|
||||
const res = await window.client.startAction(startActionArgs)
|
||||
|
||||
router.push(`/logs/${res.executionTrackingId}`)
|
||||
}
|
||||
|
||||
async function killAction() {
|
||||
if (!executionTrackingId.value || executionTrackingId.value === 'notset') {
|
||||
return
|
||||
}
|
||||
|
||||
const killActionArgs = {
|
||||
executionTrackingId: executionTrackingId.value
|
||||
}
|
||||
|
||||
try {
|
||||
await window.client.killAction(killActionArgs)
|
||||
} catch (err) {
|
||||
console.error('Failed to kill action:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function executionTick() {
|
||||
executionSeconds.value++
|
||||
updateDuration(null)
|
||||
}
|
||||
|
||||
function hideEverythingApartFromOutput() {
|
||||
hideDetailsOnResult.value = true
|
||||
hideBasics.value = true
|
||||
hideDetailsOnResult.value = true
|
||||
hideBasics.value = true
|
||||
}
|
||||
|
||||
async function fetchExecutionResult(executionTrackingIdParam) {
|
||||
console.log("fetchExecutionResult", executionTrackingIdParam)
|
||||
|
||||
executionTrackingId.value = executionTrackingIdParam
|
||||
|
||||
const executionStatusArgs = {
|
||||
executionTrackingId: executionTrackingId.value
|
||||
}
|
||||
|
||||
try {
|
||||
const logEntryResult = await window.client.executionStatus(executionStatusArgs)
|
||||
|
||||
await renderExecutionResult(logEntryResult)
|
||||
} catch (err) {
|
||||
renderError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function updateDuration(logEntryParam) {
|
||||
logEntry.value = logEntryParam
|
||||
if (logEntry.value == null) {
|
||||
duration.value = executionSeconds.value + ' seconds'
|
||||
duration.value = duration.value
|
||||
} else if (!logEntry.value.executionStarted) {
|
||||
duration.value = logEntry.value.datetimeStarted + ' (request time). Not executed.'
|
||||
} else if (logEntry.value.executionStarted && !logEntry.value.executionFinished) {
|
||||
duration.value = logEntry.value.datetimeStarted
|
||||
} else {
|
||||
let delta = ''
|
||||
try {
|
||||
delta = (new Date(logEntry.value.datetimeStarted) - new Date(logEntry.value.datetimeStarted)) / 1000
|
||||
delta = new Intl.RelativeTimeFormat().format(delta, 'seconds').replace('in ', '').replace('ago', '')
|
||||
} catch (e) {
|
||||
console.warn('Failed to calculate delta', e)
|
||||
}
|
||||
duration.value = logEntry.value.datetimeStarted + ' → ' + logEntry.value.datetimeFinished
|
||||
if (delta !== '') {
|
||||
duration.value += ' (' + delta + ')'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function renderExecutionResult(res) {
|
||||
logEntry.value = res.logEntry
|
||||
|
||||
// Clear ticker
|
||||
if (executionTicker) {
|
||||
clearInterval(executionTicker)
|
||||
}
|
||||
executionTicker = null
|
||||
|
||||
if (hideDetailsOnResult.value) {
|
||||
hideDetails.value = true
|
||||
}
|
||||
|
||||
executionTrackingId.value = res.logEntry.executionTrackingId
|
||||
canRerun.value = res.logEntry.executionFinished
|
||||
canKill.value = res.logEntry.canKill
|
||||
|
||||
icon.value = res.logEntry.actionIcon
|
||||
title.value = res.logEntry.actionTitle
|
||||
titleTooltip.value = 'Action ID: ' + res.logEntry.actionId + '\nExecution ID: ' + res.logEntry.executionTrackingId
|
||||
|
||||
updateDuration(res.logEntry)
|
||||
|
||||
if (terminal) {
|
||||
await terminal.reset()
|
||||
await terminal.write(res.logEntry.output, () => {
|
||||
terminal.fit()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function renderError(err) {
|
||||
window.showBigError('execution-dlg-err', 'in the execution dialog', 'Failed to fetch execution result. ' + err, false)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
if (executionTicker) {
|
||||
clearInterval(executionTicker)
|
||||
}
|
||||
|
||||
executionTicker = null
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (executionTicker) {
|
||||
clearInterval(executionTicker)
|
||||
}
|
||||
executionTicker = null
|
||||
if (terminal != null) {
|
||||
terminal.close()
|
||||
}
|
||||
terminal = null
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initializeTerminal()
|
||||
fetchExecutionResult(props.executionTrackingId)
|
||||
|
||||
watch(
|
||||
() => buttonResults[props.executionTrackingId],
|
||||
(newResult, oldResult) => {
|
||||
if (newResult) {
|
||||
renderExecutionResult({
|
||||
logEntry: newResult
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
// Expose methods for parent/imperative use
|
||||
defineExpose({
|
||||
reset,
|
||||
show,
|
||||
rerunAction,
|
||||
killAction,
|
||||
fetchExecutionResult,
|
||||
renderExecutionResult,
|
||||
hideEverythingApartFromOutput,
|
||||
handleClose
|
||||
})
|
||||
|
||||
</script>
|
||||
131
frontend/resources/vue/views/LoginView.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<Section title="Login to OliveTin" class="small">
|
||||
<div class="login-form" style="display: grid; grid-template-columns: max-content 1fr; gap: 1em;">
|
||||
<div v-if="!hasOAuth && !hasLocalLogin" class="login-disabled">
|
||||
<span>This server is not configured with either OAuth, or local users, so you cannot login.</span>
|
||||
</div>
|
||||
|
||||
<div v-if="hasOAuth" class="login-oauth2">
|
||||
<h3>OAuth Login</h3>
|
||||
<div class="oauth-providers">
|
||||
<button v-for="provider in oauthProviders" :key="provider.name" class="oauth-button"
|
||||
@click="loginWithOAuth(provider)">
|
||||
<span v-if="provider.icon" class="provider-icon" v-html="provider.icon"></span>
|
||||
<span class="provider-name">Login with {{ provider.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasLocalLogin" class="login-local">
|
||||
<h3>Local Login</h3>
|
||||
<form @submit.prevent="handleLocalLogin" class="local-login-form">
|
||||
<div v-if="loginError" class="error-message">
|
||||
{{ loginError }}
|
||||
</div>
|
||||
|
||||
<label for="username">Username:</label>
|
||||
<input id="username" v-model="username" type="text" name="username" autocomplete="username" required />
|
||||
|
||||
<label for="password">Password:</label>
|
||||
<input id="password" v-model="password" type="password" name="password" autocomplete="current-password"
|
||||
required />
|
||||
|
||||
<button type="submit" :disabled="loading" class="login-button">
|
||||
{{ loading ? 'Logging in...' : 'Login' }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const loading = ref(false)
|
||||
const loginError = ref('')
|
||||
const hasOAuth = ref(false)
|
||||
const hasLocalLogin = ref(false)
|
||||
const oauthProviders = ref([])
|
||||
|
||||
async function fetchLoginOptions() {
|
||||
try {
|
||||
const response = await fetch('webUiSettings.json')
|
||||
const settings = await response.json()
|
||||
|
||||
hasOAuth.value = settings.AuthOAuth2Providers && settings.AuthOAuth2Providers.length > 0
|
||||
hasLocalLogin.value = settings.AuthLocalLogin
|
||||
|
||||
if (hasOAuth.value) {
|
||||
oauthProviders.value = settings.AuthOAuth2Providers
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch login options:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLocalLogin() {
|
||||
loading.value = true
|
||||
loginError.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username.value,
|
||||
password: password.value
|
||||
})
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
// Redirect to home page on successful login
|
||||
router.push('/')
|
||||
} else {
|
||||
const error = await response.text()
|
||||
loginError.value = error || 'Login failed. Please check your credentials.'
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Login error:', err)
|
||||
loginError.value = 'Network error. Please try again.'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function loginWithOAuth(provider) {
|
||||
// Redirect to OAuth provider
|
||||
window.location.href = provider.authUrl
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchLoginOptions()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
section {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.login-view {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
form {
|
||||
grid-template-columns: max-content 1fr;
|
||||
gap: 1em;
|
||||
}
|
||||
</style>
|
||||
250
frontend/resources/vue/views/LogsListView.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<Section title="Logs" :padding="false">
|
||||
<template #toolbar>
|
||||
<label class="input-with-icons">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14" />
|
||||
</svg>
|
||||
<input placeholder="Filter current page" v-model="searchText" />
|
||||
<button title="Clear search filter" :disabled="!searchText" @click="clearSearch">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" />
|
||||
</svg>
|
||||
</button>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<p class = "padding">This is a list of logs from actions that have been executed. You can filter the list by action title.</p>
|
||||
<div v-show="filteredLogs.length > 0">
|
||||
<table class="logs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>Action</th>
|
||||
<th>Metadata</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="log in filteredLogs" :key="log.executionTrackingId" class="log-row" :title="log.actionTitle">
|
||||
<td class="timestamp">{{ formatTimestamp(log.datetimeStarted) }}</td>
|
||||
<td>
|
||||
<span class="icon" v-html="log.actionIcon"></span>
|
||||
<router-link :to="`/logs/${log.executionTrackingId}`">
|
||||
{{ log.actionTitle }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="tags">
|
||||
<span class="annotation">
|
||||
<span class="annotation-key">User:</span>
|
||||
<span class="annotation-val">{{ log.user }}</span>
|
||||
</span>
|
||||
<span v-if="log.tags && log.tags.length > 0" class="tag-list">
|
||||
<span v-for="tag in log.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="exit-code">
|
||||
<span :class="getStatusClass(log) + ' annotation'">
|
||||
{{ getStatusText(log) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" @page-change="handlePageChange" class = "padding"
|
||||
@page-size-change="handlePageSizeChange" itemTitle="execution logs" />
|
||||
</div>
|
||||
|
||||
<div v-show="logs.length === 0" class="empty-state">
|
||||
<p>There are no logs to display.</p>
|
||||
<router-link to="/">Return to index</router-link>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
|
||||
const logs = ref([])
|
||||
const searchText = ref('')
|
||||
const pageSize = ref(10)
|
||||
const currentPage = ref(1)
|
||||
const loading = ref(false)
|
||||
const totalCount = ref(0)
|
||||
|
||||
const filteredLogs = computed(() => {
|
||||
if (!searchText.value) {
|
||||
return logs.value
|
||||
}
|
||||
const searchLower = searchText.value.toLowerCase()
|
||||
return logs.value.filter(log =>
|
||||
log.actionTitle.toLowerCase().includes(searchLower)
|
||||
)
|
||||
})
|
||||
|
||||
async function fetchLogs() {
|
||||
loading.value = true
|
||||
try {
|
||||
const startOffset = (currentPage.value - 1) * pageSize.value
|
||||
|
||||
const args = {
|
||||
"startOffset": BigInt(startOffset),
|
||||
}
|
||||
|
||||
const response = await window.client.getLogs(args)
|
||||
|
||||
logs.value = response.logs
|
||||
pageSize.value = Number(response.pageSize) || 0
|
||||
totalCount.value = Number(response.totalCount) || 0
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch logs:', err)
|
||||
window.showBigError('fetch-logs', 'getting logs', err, false)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
if (!timestamp) return 'Unknown'
|
||||
try {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString()
|
||||
} catch (err) {
|
||||
return timestamp
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusClass(log) {
|
||||
if (log.timedOut) return 'status-timeout'
|
||||
if (log.blocked) return 'status-blocked'
|
||||
if (log.exitCode !== 0) return 'status-error'
|
||||
return 'status-success'
|
||||
}
|
||||
|
||||
function getStatusText(log) {
|
||||
if (log.timedOut) return 'Timed out'
|
||||
if (log.blocked) return 'Blocked'
|
||||
if (log.exitCode !== 0) return `Exit code ${log.exitCode}`
|
||||
return 'Completed'
|
||||
}
|
||||
|
||||
function handlePageChange(page) {
|
||||
currentPage.value = page
|
||||
fetchLogs()
|
||||
}
|
||||
|
||||
function handlePageSizeChange(newPageSize) {
|
||||
pageSize.value = newPageSize
|
||||
currentPage.value = 1 // Reset to first page
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchLogs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logs-view {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.input-with-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.input-with-icons input {
|
||||
border: none;
|
||||
outline: none;
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.input-with-icons button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.input-with-icons button:hover:not(:disabled) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.input-with-icons button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 0.5rem;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
color: #28a745;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
color: #dc3545;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-timeout {
|
||||
color: #ffc107;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-blocked {
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-state a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.empty-state a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
</style>
|
||||
60
frontend/resources/vue/views/NotFoundView.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="not-found-view">
|
||||
<div class="not-found-container">
|
||||
<div class="not-found-content">
|
||||
<h1>404</h1>
|
||||
<h2>Page Not Found</h2>
|
||||
|
||||
<div class="actions">
|
||||
<button class = "button good" @click="goToHome">
|
||||
Go to Home
|
||||
</button>
|
||||
<button class="button neutral" @click="goBack">
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NotFoundView',
|
||||
methods: {
|
||||
goBack() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
goToHome() {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.not-found-content {
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.not-found-content h1 {
|
||||
font-size: 6rem;
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.not-found-content h2 {
|
||||
font-size: 2rem;
|
||||
margin: 0 0 1rem 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.not-found-content p {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
</style>
|
||||
105
frontend/style.css
Normal file
@@ -0,0 +1,105 @@
|
||||
header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
aside {
|
||||
padding-top: 4em;
|
||||
z-index: 3; /* Make sure the sidebar is on top of the terminal */
|
||||
}
|
||||
|
||||
fieldset {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 180px);
|
||||
grid-auto-rows: 1fr;
|
||||
justify-content: center;
|
||||
place-items: stretch;
|
||||
}
|
||||
|
||||
main {
|
||||
padding-top: 4em;
|
||||
}
|
||||
|
||||
action-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
action-button > button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
font-weight: normal;
|
||||
font-size: 0.85em;
|
||||
box-shadow: 0 0 .6em #aaa;
|
||||
}
|
||||
|
||||
action-button > button .icon {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
dialog {
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
footer span {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
padding-top: 1.5em;
|
||||
}
|
||||
|
||||
button.neutral {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.display {
|
||||
border: 1px solid #666;
|
||||
padding: 1em;
|
||||
border-radius: .7em;
|
||||
box-shadow: 0 0 .6em #aaa;
|
||||
text-align: center;
|
||||
font-size: small;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
aside .flex-row {
|
||||
padding-left: 1em;
|
||||
padding-right: .5em;
|
||||
}
|
||||
|
||||
#sidebar-toggler-button {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
div.buttons button svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
section.small {
|
||||
border-radius: .4em;
|
||||
}
|
||||
29
frontend/vite.config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Components({
|
||||
dirs: ['resources/vue/'],
|
||||
extensions: ['vue'],
|
||||
deep: true,
|
||||
dts: false,
|
||||
}),
|
||||
vue(),
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
'/webUiSettings.json': {
|
||||
target: 'http://localhost:1337',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/api': {
|
||||
target: 'http://localhost:1337',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
3765
gen/grpc/OliveTin.pb.go
Normal file
1383
gen/grpc/OliveTin.pb.gw.go
Normal file
700
gen/grpc/OliveTin_grpc.pb.go
Normal file
@@ -0,0 +1,700 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc (unknown)
|
||||
// source: OliveTin.proto
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
context "context"
|
||||
httpbody "google.golang.org/genproto/googleapis/api/httpbody"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
OliveTinApiService_GetDashboardComponents_FullMethodName = "/OliveTinApiService/GetDashboardComponents"
|
||||
OliveTinApiService_StartAction_FullMethodName = "/OliveTinApiService/StartAction"
|
||||
OliveTinApiService_StartActionAndWait_FullMethodName = "/OliveTinApiService/StartActionAndWait"
|
||||
OliveTinApiService_StartActionByGet_FullMethodName = "/OliveTinApiService/StartActionByGet"
|
||||
OliveTinApiService_StartActionByGetAndWait_FullMethodName = "/OliveTinApiService/StartActionByGetAndWait"
|
||||
OliveTinApiService_KillAction_FullMethodName = "/OliveTinApiService/KillAction"
|
||||
OliveTinApiService_ExecutionStatus_FullMethodName = "/OliveTinApiService/ExecutionStatus"
|
||||
OliveTinApiService_GetLogs_FullMethodName = "/OliveTinApiService/GetLogs"
|
||||
OliveTinApiService_ValidateArgumentType_FullMethodName = "/OliveTinApiService/ValidateArgumentType"
|
||||
OliveTinApiService_WhoAmI_FullMethodName = "/OliveTinApiService/WhoAmI"
|
||||
OliveTinApiService_SosReport_FullMethodName = "/OliveTinApiService/SosReport"
|
||||
OliveTinApiService_DumpVars_FullMethodName = "/OliveTinApiService/DumpVars"
|
||||
OliveTinApiService_DumpPublicIdActionMap_FullMethodName = "/OliveTinApiService/DumpPublicIdActionMap"
|
||||
OliveTinApiService_GetReadyz_FullMethodName = "/OliveTinApiService/GetReadyz"
|
||||
OliveTinApiService_LocalUserLogin_FullMethodName = "/OliveTinApiService/LocalUserLogin"
|
||||
OliveTinApiService_PasswordHash_FullMethodName = "/OliveTinApiService/PasswordHash"
|
||||
OliveTinApiService_Logout_FullMethodName = "/OliveTinApiService/Logout"
|
||||
)
|
||||
|
||||
// OliveTinApiServiceClient is the client API for OliveTinApiService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type OliveTinApiServiceClient interface {
|
||||
GetDashboardComponents(ctx context.Context, in *GetDashboardComponentsRequest, opts ...grpc.CallOption) (*GetDashboardComponentsResponse, error)
|
||||
StartAction(ctx context.Context, in *StartActionRequest, opts ...grpc.CallOption) (*StartActionResponse, error)
|
||||
StartActionAndWait(ctx context.Context, in *StartActionAndWaitRequest, opts ...grpc.CallOption) (*StartActionAndWaitResponse, error)
|
||||
StartActionByGet(ctx context.Context, in *StartActionByGetRequest, opts ...grpc.CallOption) (*StartActionByGetResponse, error)
|
||||
StartActionByGetAndWait(ctx context.Context, in *StartActionByGetAndWaitRequest, opts ...grpc.CallOption) (*StartActionByGetAndWaitResponse, error)
|
||||
KillAction(ctx context.Context, in *KillActionRequest, opts ...grpc.CallOption) (*KillActionResponse, error)
|
||||
ExecutionStatus(ctx context.Context, in *ExecutionStatusRequest, opts ...grpc.CallOption) (*ExecutionStatusResponse, error)
|
||||
GetLogs(ctx context.Context, in *GetLogsRequest, opts ...grpc.CallOption) (*GetLogsResponse, error)
|
||||
ValidateArgumentType(ctx context.Context, in *ValidateArgumentTypeRequest, opts ...grpc.CallOption) (*ValidateArgumentTypeResponse, error)
|
||||
WhoAmI(ctx context.Context, in *WhoAmIRequest, opts ...grpc.CallOption) (*WhoAmIResponse, error)
|
||||
SosReport(ctx context.Context, in *SosReportRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
|
||||
DumpVars(ctx context.Context, in *DumpVarsRequest, opts ...grpc.CallOption) (*DumpVarsResponse, error)
|
||||
DumpPublicIdActionMap(ctx context.Context, in *DumpPublicIdActionMapRequest, opts ...grpc.CallOption) (*DumpPublicIdActionMapResponse, error)
|
||||
GetReadyz(ctx context.Context, in *GetReadyzRequest, opts ...grpc.CallOption) (*GetReadyzResponse, error)
|
||||
LocalUserLogin(ctx context.Context, in *LocalUserLoginRequest, opts ...grpc.CallOption) (*LocalUserLoginResponse, error)
|
||||
PasswordHash(ctx context.Context, in *PasswordHashRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
|
||||
Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
|
||||
}
|
||||
|
||||
type oliveTinApiServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewOliveTinApiServiceClient(cc grpc.ClientConnInterface) OliveTinApiServiceClient {
|
||||
return &oliveTinApiServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) GetDashboardComponents(ctx context.Context, in *GetDashboardComponentsRequest, opts ...grpc.CallOption) (*GetDashboardComponentsResponse, error) {
|
||||
out := new(GetDashboardComponentsResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_GetDashboardComponents_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) StartAction(ctx context.Context, in *StartActionRequest, opts ...grpc.CallOption) (*StartActionResponse, error) {
|
||||
out := new(StartActionResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_StartAction_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) StartActionAndWait(ctx context.Context, in *StartActionAndWaitRequest, opts ...grpc.CallOption) (*StartActionAndWaitResponse, error) {
|
||||
out := new(StartActionAndWaitResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_StartActionAndWait_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) StartActionByGet(ctx context.Context, in *StartActionByGetRequest, opts ...grpc.CallOption) (*StartActionByGetResponse, error) {
|
||||
out := new(StartActionByGetResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_StartActionByGet_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) StartActionByGetAndWait(ctx context.Context, in *StartActionByGetAndWaitRequest, opts ...grpc.CallOption) (*StartActionByGetAndWaitResponse, error) {
|
||||
out := new(StartActionByGetAndWaitResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_StartActionByGetAndWait_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) KillAction(ctx context.Context, in *KillActionRequest, opts ...grpc.CallOption) (*KillActionResponse, error) {
|
||||
out := new(KillActionResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_KillAction_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) ExecutionStatus(ctx context.Context, in *ExecutionStatusRequest, opts ...grpc.CallOption) (*ExecutionStatusResponse, error) {
|
||||
out := new(ExecutionStatusResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_ExecutionStatus_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) GetLogs(ctx context.Context, in *GetLogsRequest, opts ...grpc.CallOption) (*GetLogsResponse, error) {
|
||||
out := new(GetLogsResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_GetLogs_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) ValidateArgumentType(ctx context.Context, in *ValidateArgumentTypeRequest, opts ...grpc.CallOption) (*ValidateArgumentTypeResponse, error) {
|
||||
out := new(ValidateArgumentTypeResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_ValidateArgumentType_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) WhoAmI(ctx context.Context, in *WhoAmIRequest, opts ...grpc.CallOption) (*WhoAmIResponse, error) {
|
||||
out := new(WhoAmIResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_WhoAmI_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) SosReport(ctx context.Context, in *SosReportRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
|
||||
out := new(httpbody.HttpBody)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_SosReport_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) DumpVars(ctx context.Context, in *DumpVarsRequest, opts ...grpc.CallOption) (*DumpVarsResponse, error) {
|
||||
out := new(DumpVarsResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_DumpVars_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) DumpPublicIdActionMap(ctx context.Context, in *DumpPublicIdActionMapRequest, opts ...grpc.CallOption) (*DumpPublicIdActionMapResponse, error) {
|
||||
out := new(DumpPublicIdActionMapResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_DumpPublicIdActionMap_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) GetReadyz(ctx context.Context, in *GetReadyzRequest, opts ...grpc.CallOption) (*GetReadyzResponse, error) {
|
||||
out := new(GetReadyzResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_GetReadyz_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) LocalUserLogin(ctx context.Context, in *LocalUserLoginRequest, opts ...grpc.CallOption) (*LocalUserLoginResponse, error) {
|
||||
out := new(LocalUserLoginResponse)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_LocalUserLogin_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) PasswordHash(ctx context.Context, in *PasswordHashRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
|
||||
out := new(httpbody.HttpBody)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_PasswordHash_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *oliveTinApiServiceClient) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
|
||||
out := new(httpbody.HttpBody)
|
||||
err := c.cc.Invoke(ctx, OliveTinApiService_Logout_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// OliveTinApiServiceServer is the server API for OliveTinApiService service.
|
||||
// All implementations should embed UnimplementedOliveTinApiServiceServer
|
||||
// for forward compatibility
|
||||
type OliveTinApiServiceServer interface {
|
||||
GetDashboardComponents(context.Context, *GetDashboardComponentsRequest) (*GetDashboardComponentsResponse, error)
|
||||
StartAction(context.Context, *StartActionRequest) (*StartActionResponse, error)
|
||||
StartActionAndWait(context.Context, *StartActionAndWaitRequest) (*StartActionAndWaitResponse, error)
|
||||
StartActionByGet(context.Context, *StartActionByGetRequest) (*StartActionByGetResponse, error)
|
||||
StartActionByGetAndWait(context.Context, *StartActionByGetAndWaitRequest) (*StartActionByGetAndWaitResponse, error)
|
||||
KillAction(context.Context, *KillActionRequest) (*KillActionResponse, error)
|
||||
ExecutionStatus(context.Context, *ExecutionStatusRequest) (*ExecutionStatusResponse, error)
|
||||
GetLogs(context.Context, *GetLogsRequest) (*GetLogsResponse, error)
|
||||
ValidateArgumentType(context.Context, *ValidateArgumentTypeRequest) (*ValidateArgumentTypeResponse, error)
|
||||
WhoAmI(context.Context, *WhoAmIRequest) (*WhoAmIResponse, error)
|
||||
SosReport(context.Context, *SosReportRequest) (*httpbody.HttpBody, error)
|
||||
DumpVars(context.Context, *DumpVarsRequest) (*DumpVarsResponse, error)
|
||||
DumpPublicIdActionMap(context.Context, *DumpPublicIdActionMapRequest) (*DumpPublicIdActionMapResponse, error)
|
||||
GetReadyz(context.Context, *GetReadyzRequest) (*GetReadyzResponse, error)
|
||||
LocalUserLogin(context.Context, *LocalUserLoginRequest) (*LocalUserLoginResponse, error)
|
||||
PasswordHash(context.Context, *PasswordHashRequest) (*httpbody.HttpBody, error)
|
||||
Logout(context.Context, *LogoutRequest) (*httpbody.HttpBody, error)
|
||||
}
|
||||
|
||||
// UnimplementedOliveTinApiServiceServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedOliveTinApiServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedOliveTinApiServiceServer) GetDashboardComponents(context.Context, *GetDashboardComponentsRequest) (*GetDashboardComponentsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetDashboardComponents not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) StartAction(context.Context, *StartActionRequest) (*StartActionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method StartAction not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) StartActionAndWait(context.Context, *StartActionAndWaitRequest) (*StartActionAndWaitResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method StartActionAndWait not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) StartActionByGet(context.Context, *StartActionByGetRequest) (*StartActionByGetResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method StartActionByGet not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) StartActionByGetAndWait(context.Context, *StartActionByGetAndWaitRequest) (*StartActionByGetAndWaitResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method StartActionByGetAndWait not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) KillAction(context.Context, *KillActionRequest) (*KillActionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method KillAction not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) ExecutionStatus(context.Context, *ExecutionStatusRequest) (*ExecutionStatusResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecutionStatus not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) GetLogs(context.Context, *GetLogsRequest) (*GetLogsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetLogs not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) ValidateArgumentType(context.Context, *ValidateArgumentTypeRequest) (*ValidateArgumentTypeResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ValidateArgumentType not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) WhoAmI(context.Context, *WhoAmIRequest) (*WhoAmIResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method WhoAmI not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) SosReport(context.Context, *SosReportRequest) (*httpbody.HttpBody, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SosReport not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) DumpVars(context.Context, *DumpVarsRequest) (*DumpVarsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DumpVars not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) DumpPublicIdActionMap(context.Context, *DumpPublicIdActionMapRequest) (*DumpPublicIdActionMapResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DumpPublicIdActionMap not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) GetReadyz(context.Context, *GetReadyzRequest) (*GetReadyzResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetReadyz not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) LocalUserLogin(context.Context, *LocalUserLoginRequest) (*LocalUserLoginResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method LocalUserLogin not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) PasswordHash(context.Context, *PasswordHashRequest) (*httpbody.HttpBody, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method PasswordHash not implemented")
|
||||
}
|
||||
func (UnimplementedOliveTinApiServiceServer) Logout(context.Context, *LogoutRequest) (*httpbody.HttpBody, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented")
|
||||
}
|
||||
|
||||
// UnsafeOliveTinApiServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to OliveTinApiServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeOliveTinApiServiceServer interface {
|
||||
mustEmbedUnimplementedOliveTinApiServiceServer()
|
||||
}
|
||||
|
||||
func RegisterOliveTinApiServiceServer(s grpc.ServiceRegistrar, srv OliveTinApiServiceServer) {
|
||||
s.RegisterService(&OliveTinApiService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_GetDashboardComponents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetDashboardComponentsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).GetDashboardComponents(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_GetDashboardComponents_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).GetDashboardComponents(ctx, req.(*GetDashboardComponentsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_StartAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StartActionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).StartAction(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_StartAction_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).StartAction(ctx, req.(*StartActionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_StartActionAndWait_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StartActionAndWaitRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).StartActionAndWait(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_StartActionAndWait_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).StartActionAndWait(ctx, req.(*StartActionAndWaitRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_StartActionByGet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StartActionByGetRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).StartActionByGet(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_StartActionByGet_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).StartActionByGet(ctx, req.(*StartActionByGetRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_StartActionByGetAndWait_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StartActionByGetAndWaitRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).StartActionByGetAndWait(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_StartActionByGetAndWait_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).StartActionByGetAndWait(ctx, req.(*StartActionByGetAndWaitRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_KillAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(KillActionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).KillAction(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_KillAction_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).KillAction(ctx, req.(*KillActionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_ExecutionStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ExecutionStatusRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).ExecutionStatus(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_ExecutionStatus_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).ExecutionStatus(ctx, req.(*ExecutionStatusRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_GetLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetLogsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).GetLogs(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_GetLogs_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).GetLogs(ctx, req.(*GetLogsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_ValidateArgumentType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ValidateArgumentTypeRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).ValidateArgumentType(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_ValidateArgumentType_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).ValidateArgumentType(ctx, req.(*ValidateArgumentTypeRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_WhoAmI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(WhoAmIRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).WhoAmI(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_WhoAmI_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).WhoAmI(ctx, req.(*WhoAmIRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_SosReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SosReportRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).SosReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_SosReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).SosReport(ctx, req.(*SosReportRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_DumpVars_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DumpVarsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).DumpVars(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_DumpVars_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).DumpVars(ctx, req.(*DumpVarsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_DumpPublicIdActionMap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DumpPublicIdActionMapRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).DumpPublicIdActionMap(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_DumpPublicIdActionMap_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).DumpPublicIdActionMap(ctx, req.(*DumpPublicIdActionMapRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_GetReadyz_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetReadyzRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).GetReadyz(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_GetReadyz_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).GetReadyz(ctx, req.(*GetReadyzRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_LocalUserLogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LocalUserLoginRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).LocalUserLogin(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_LocalUserLogin_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).LocalUserLogin(ctx, req.(*LocalUserLoginRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_PasswordHash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PasswordHashRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).PasswordHash(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_PasswordHash_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).PasswordHash(ctx, req.(*PasswordHashRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _OliveTinApiService_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LogoutRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(OliveTinApiServiceServer).Logout(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: OliveTinApiService_Logout_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(OliveTinApiServiceServer).Logout(ctx, req.(*LogoutRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// OliveTinApiService_ServiceDesc is the grpc.ServiceDesc for OliveTinApiService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var OliveTinApiService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "OliveTinApiService",
|
||||
HandlerType: (*OliveTinApiServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetDashboardComponents",
|
||||
Handler: _OliveTinApiService_GetDashboardComponents_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "StartAction",
|
||||
Handler: _OliveTinApiService_StartAction_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "StartActionAndWait",
|
||||
Handler: _OliveTinApiService_StartActionAndWait_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "StartActionByGet",
|
||||
Handler: _OliveTinApiService_StartActionByGet_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "StartActionByGetAndWait",
|
||||
Handler: _OliveTinApiService_StartActionByGetAndWait_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "KillAction",
|
||||
Handler: _OliveTinApiService_KillAction_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExecutionStatus",
|
||||
Handler: _OliveTinApiService_ExecutionStatus_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetLogs",
|
||||
Handler: _OliveTinApiService_GetLogs_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ValidateArgumentType",
|
||||
Handler: _OliveTinApiService_ValidateArgumentType_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "WhoAmI",
|
||||
Handler: _OliveTinApiService_WhoAmI_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SosReport",
|
||||
Handler: _OliveTinApiService_SosReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DumpVars",
|
||||
Handler: _OliveTinApiService_DumpVars_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DumpPublicIdActionMap",
|
||||
Handler: _OliveTinApiService_DumpPublicIdActionMap_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetReadyz",
|
||||
Handler: _OliveTinApiService_GetReadyz_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "LocalUserLogin",
|
||||
Handler: _OliveTinApiService_LocalUserLogin_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "PasswordHash",
|
||||
Handler: _OliveTinApiService_PasswordHash_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Logout",
|
||||
Handler: _OliveTinApiService_Logout_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "OliveTin.proto",
|
||||
}
|
||||
80
go.mod
@@ -1,80 +0,0 @@
|
||||
module github.com/OliveTin/OliveTin
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/bufbuild/buf v1.3.1
|
||||
github.com/fsnotify/fsnotify v1.5.4
|
||||
github.com/fzipp/gocyclo v0.5.0
|
||||
github.com/go-co-op/gocron v1.13.0
|
||||
github.com/go-critic/go-critic v0.6.2
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.7.2
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127
|
||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac
|
||||
google.golang.org/grpc v1.47.0
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-toolsmith/astcast v1.0.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.0.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.0.1 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.0.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.0.0 // indirect
|
||||
github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5 // indirect
|
||||
github.com/go-toolsmith/strparse v1.0.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.0.2 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a // indirect
|
||||
github.com/jhump/protocompile v0.0.0-20220216033700-d705409f108f // indirect
|
||||
github.com/jhump/protoreflect v1.11.1-0.20220213155251-0c2aedc66cf4 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/profile v1.6.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.3.15 // indirect
|
||||
github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.4.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
647
go.sum
@@ -1,647 +0,0 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/bufbuild/buf v1.3.1 h1:AelWcENnbNEjwxmQXIZaU51GHgnWQ8Mc94kZdDUKgRs=
|
||||
github.com/bufbuild/buf v1.3.1/go.mod h1:CTRUb23N+zlm1U8ZIBKz0Sqluk++qQloB2i/MZNZHIs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fzipp/gocyclo v0.5.0 h1:jMgQATRpq/msyXdnO36+sOEDhQ4EWJDta3RIGA9GRqA=
|
||||
github.com/fzipp/gocyclo v0.5.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-co-op/gocron v1.13.0 h1:BjkuNImPy5NuIPEifhWItFG7pYyr27cyjS6BN9w/D4c=
|
||||
github.com/go-co-op/gocron v1.13.0/go.mod h1:GD5EIEly1YNW+LovFVx5dzbYVcIc8544K99D8UVRpGo=
|
||||
github.com/go-critic/go-critic v0.6.2 h1:L5SDut1N4ZfsWZY0sH4DCrsHLHnhuuWak2wa165t9gs=
|
||||
github.com/go-critic/go-critic v0.6.2/go.mod h1:td1s27kfmLpe5G/DPjlnFI7o1UCzePptwU7Az0V5iCM=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
|
||||
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
|
||||
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astequal v1.0.1 h1:JbSszi42Jiqu36Gnf363HWS9MTEAz67vTQLponh3Moc=
|
||||
github.com/go-toolsmith/astequal v1.0.1/go.mod h1:4oGA3EZXTVItV/ipGiOx7NWkY5veFfcsOJVS2YxltLw=
|
||||
github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k=
|
||||
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
|
||||
github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg=
|
||||
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
|
||||
github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5 h1:eD9POs68PHkwrx7hAB78z1cb6PfGq/jyWn3wJywsH1o=
|
||||
github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5/go.mod h1:3NAwwmD4uY/yggRxoEjk/S00MIV3A+H7rrE3i87eYxM=
|
||||
github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4=
|
||||
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||
github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYwvlk=
|
||||
github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3 h1:BGNSrTRW4rwfhJiFwvwF4XQ0Y72Jj9YEgxVrtovbD5o=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3/go.mod h1:VHn7KgNsRriXa4mcgtkpR00OXyQY6g67JWMvn+R27A4=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a h1:d4+I1YEKVmWZrgkt6jpXBnLgV2ZjO0YxEtLDdfIZfH4=
|
||||
github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
|
||||
github.com/jhump/protocompile v0.0.0-20220216033700-d705409f108f h1:BNuUg9k2EiJmlMwjoef3e8vZLHplbVw6DrjGFjLL+Yo=
|
||||
github.com/jhump/protocompile v0.0.0-20220216033700-d705409f108f/go.mod h1:qr2b5kx4HbFS7/g4uYO5qv9ei8303JMsC7ESbYiqr2Q=
|
||||
github.com/jhump/protoreflect v1.11.1-0.20220213155251-0c2aedc66cf4 h1:E2CdxLXYSn6Zrj2+u8DWrwMJW3YZLSWtM/7kIL8OL18=
|
||||
github.com/jhump/protoreflect v1.11.1-0.20220213155251-0c2aedc66cf4/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
|
||||
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30=
|
||||
github.com/quasilyte/go-ruleguard v0.3.15 h1:iWYzp1z72IlXTioET0+XI6SjQdPfMGfuAiZiKznOt7g=
|
||||
github.com/quasilyte/go-ruleguard v0.3.15/go.mod h1:NhuWhnlVEM1gT1A4VJHYfy9MuYSxxwHgxWoPsn9llB4=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.12-0.20220101150716-969a394a9451/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.12/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||
github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc=
|
||||
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
|
||||
github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3 h1:P4QPNn+TK49zJjXKERt/vyPbv/mCHB/zQ4flDYOMN+M=
|
||||
github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3/go.mod h1:wSEyW6O61xRV6zb6My3HxrQ5/8ke7NE2OayqCHa3xRM=
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY=
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
||||
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
|
||||
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs=
|
||||
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127 h1:S4NrSKDfihhl3+4jSTgwoIevKxX9p7Iv9x++OEIptDo=
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac h1:ByeiW1F67iV9o8ipGskA+HWzSkMbRJuKLlwCdPxzn7A=
|
||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
|
||||
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
8
integration-tests/.eslintrc.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
env:
|
||||
browser: true
|
||||
es2021: true
|
||||
extends: 'eslint:recommended'
|
||||
parserOptions:
|
||||
ecmaVersion: 12
|
||||
sourceType: module
|
||||
rules: {}
|
||||
3
integration-tests/.mocharc.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
require:
|
||||
- mochaSetup.mjs
|
||||
@@ -1,6 +1,24 @@
|
||||
cypress:
|
||||
npm install
|
||||
./cypressRun.sh "general"
|
||||
./cypressRun.sh "hiddenNav"
|
||||
default: test-install test-run
|
||||
|
||||
.PHONY: cypress container
|
||||
test-install:
|
||||
npm install --no-fund
|
||||
|
||||
test-run:
|
||||
# GitHub Actions fails badly on the default timeout of 2000ms
|
||||
npx mocha -t 10000
|
||||
|
||||
find-flakey-tests:
|
||||
echo "Running test-run infinately"
|
||||
sh -c "while make test-run; do :; done"
|
||||
|
||||
nginx:
|
||||
podman-compose up -d nginx
|
||||
|
||||
clean:
|
||||
podman-compose down
|
||||
|
||||
getsnapshot:
|
||||
rm -rf /opt/OliveTin-snapshot/*
|
||||
gh run download -D /opt/OliveTin-snapshot/
|
||||
|
||||
.PHONY: default
|
||||
|
||||
@@ -1 +1,14 @@
|
||||
# OliveTin-integration-tests
|
||||
# OliveTin-integration-tests
|
||||
|
||||
## GitHub Actions (Ubuntu, Local Process)
|
||||
|
||||
- `mocha` is run with the default runner that starts and stops OliveTin as a local process (ie, localhost:1337).
|
||||
|
||||
## Running different configurations (Local Process, VM, Container)
|
||||
|
||||
- Get the snapshot you want to test `make getsnapshot`
|
||||
- To test against VMs:
|
||||
-- `export OLIVETIN_TEST_RUNNER=vm`
|
||||
-- `vagrant up fedora38` (or whatever distro you like defined in `Vagrantfile`)
|
||||
-- `. envVagrant.sh fedora38` to set the $IP and $PORT
|
||||
- `mocha`
|
||||
|
||||
39
integration-tests/Vagrantfile
vendored
@@ -2,34 +2,35 @@
|
||||
# (eg, snapshot builds on GitHub)
|
||||
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "generic/centos8"
|
||||
config.vm.provision "shell", inline: "mkdir /etc/OliveTin && chmod o+w /etc/OliveTin/", privileged: true
|
||||
config.vm.provision "file", source: "configs/config.general.yaml/.", destination: "/etc/OliveTin/config.yaml"
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: "mkdir /etc/OliveTin && chmod o+w /etc/OliveTin/ && mkdir -p /opt/OliveTin-configs/ && chmod 0777 /opt/OliveTin-configs", privileged: true
|
||||
config.vm.provision "file", source: "configs/.", destination: "/opt/OliveTin-configs/"
|
||||
|
||||
config.vm.provider :libvirt do |libvirt|
|
||||
libvirt.management_network_device = 'virbr0'
|
||||
end
|
||||
|
||||
config.vm.define :f34 do |f34|
|
||||
f34.vm.box = "generic/fedora34"
|
||||
f34.vm.provision "file", source: "/opt/OliveTin-vagrant/linux_amd64_rpm/.", destination: "."
|
||||
f34.vm.provision "shell", inline: "rpm -U OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
|
||||
config.vm.define :stream9 do |i|
|
||||
i.vm.box = "centos/stream9"
|
||||
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.rpm", destination: "$HOME/"
|
||||
i.vm.provision "shell", inline: "rpm -U OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
|
||||
end
|
||||
|
||||
config.vm.define :debian do |debian|
|
||||
debian.vm.box = "generic/debian10"
|
||||
debian.vm.provision "file", source: "/opt/OliveTin-vagrant/linux_amd64_deb/.", destination: "."
|
||||
debian.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin"
|
||||
config.vm.define :fedora38 do |i|
|
||||
i.vm.box = "generic/fedora38"
|
||||
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.rpm", destination: "$HOME/"
|
||||
i.vm.provision "shell", inline: "rpm -U OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
|
||||
end
|
||||
|
||||
config.vm.define :ubuntu do |ubuntu|
|
||||
ubuntu.vm.box = "generic/ubuntu2110"
|
||||
ubuntu.vm.provision "file", source: "/opt/OliveTin-vagrant/linux_amd64_deb/.", destination: "."
|
||||
ubuntu.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
|
||||
config.vm.define :debian12 do |i|
|
||||
i.vm.box = "debian/bookworm64"
|
||||
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
|
||||
i.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin"
|
||||
end
|
||||
|
||||
# TODO
|
||||
#
|
||||
|
||||
config.vm.define :ubuntu2310 do |i|
|
||||
i.vm.box = "ubuntu/mantic64"
|
||||
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
|
||||
i.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
|
||||
end
|
||||
end
|
||||
|
||||
17
integration-tests/compose.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
version: "3.8"
|
||||
services:
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: docker.io/nginx
|
||||
volumes:
|
||||
- ./proxies/nginx/:/etc/nginx/
|
||||
ports:
|
||||
- "8443:8443"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- otproxy
|
||||
|
||||
networks:
|
||||
otproxy:
|
||||
name: otproxy
|
||||
@@ -0,0 +1,36 @@
|
||||
logLevel: debug
|
||||
|
||||
actions:
|
||||
- title: Ping
|
||||
shell: echo "Ping executed"
|
||||
icon: ping
|
||||
|
||||
- title: Action 1
|
||||
shell: echo "Action 1 executed"
|
||||
icon: check
|
||||
- title: Action 2
|
||||
shell: echo "Action 2 executed"
|
||||
icon: check
|
||||
|
||||
- title: Action 3
|
||||
shell: echo "Action 3 executed"
|
||||
icon: check
|
||||
- title: Action 4
|
||||
shell: echo "Action 4 executed"
|
||||
icon: check
|
||||
|
||||
dashboards:
|
||||
- title: Test
|
||||
contents:
|
||||
# Uncomment to see the dashboard with the "Ping" action only
|
||||
- title: Ping
|
||||
- title: Fieldset 1
|
||||
type: fieldset
|
||||
contents:
|
||||
- title: Action 1
|
||||
- title: Action 2
|
||||
- title: Fieldset 2
|
||||
type: fieldset
|
||||
contents:
|
||||
- title: Action 3
|
||||
- title: Action 4
|
||||
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# Integration Test Config: emptyDashboardsAreHidden
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: Ping
|
||||
shell: ping example.com
|
||||
icon: ping
|
||||
entity: server
|
||||
|
||||
|
||||
|
||||
dashboards:
|
||||
- title: Empty Dashboard
|
||||
contents: []
|
||||
|
||||
18
integration-tests/configs/entities/config.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Integration Test Config: Entities
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: Ping {{ server.hostname }}
|
||||
shell: ping {{ server.hostname }}
|
||||
icon: ping
|
||||
entity: server
|
||||
|
||||
entities:
|
||||
- file: entities/servers.yaml
|
||||
name: server
|
||||
3
integration-tests/configs/entities/entities/servers.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
- hostname: server1
|
||||
- hostname: server2
|
||||
- hostname: server3
|
||||
@@ -0,0 +1,9 @@
|
||||
actions:
|
||||
- title: 'Test me {{ test_me.name }}'
|
||||
popupOnStart: execution-dialog-stdout-only
|
||||
entity: testrows
|
||||
shell: echo "{{ test_me.val }}"
|
||||
|
||||
entities:
|
||||
- name: testrows
|
||||
file: entities/data.json
|
||||
@@ -0,0 +1,5 @@
|
||||
{"name":"INT with 10 numbers","val":1234567890}
|
||||
{"name":"INT with 6 numbers","val":123456}
|
||||
{"name":"INT with 7 numbers","val":1234567}
|
||||
{"name":"FLOAT with 6 numbers","val":1.234567}
|
||||
{"name":"FLOAT with 10 numbers","val":1.234567890}
|
||||
@@ -1,17 +1,17 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
actions:
|
||||
- title: Ping Google.com
|
||||
shell: ping google.com -c 1
|
||||
icon: ping
|
||||
|
||||
|
||||
- title: restart lightdm
|
||||
icon: poop
|
||||
shell: ssh root@overseer 'service lightdm restart'
|
||||
@@ -24,6 +24,13 @@ actions:
|
||||
shell: sleep 5
|
||||
icon: "😪"
|
||||
|
||||
- title: dir-popup
|
||||
shell: dir
|
||||
popupOnStart: execution-dialog-stdout-only
|
||||
|
||||
- title: cd-passive
|
||||
shell: cd
|
||||
|
||||
- title: "Run Ansible Playbook"
|
||||
icon: "🇦"
|
||||
shell: ansible-playbook -i /etc/hosts /root/myRepo/myPlaybook.yaml
|
||||
@@ -32,4 +39,3 @@ actions:
|
||||
- title: Restart Plex
|
||||
icon: smile
|
||||
shell: docker restart plex
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
#
|
||||
|
||||
showFooter: false
|
||||
checkForUpdates: false
|
||||
checkForUpdates: false
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
actions:
|
||||
- title: Ping example.com
|
||||
shell: ping example.com -c 1
|
||||
icon: ping
|
||||
@@ -1,14 +1,14 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
showNavigation: false
|
||||
checkForUpdates: false
|
||||
checkForUpdates: false
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
actions:
|
||||
- title: Ping example.com
|
||||
shell: ping example.com -c 1
|
||||
icon: ping
|
||||
25
integration-tests/configs/multipleDropdowns/config.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: Ping Google.com
|
||||
shell: ping google.com -c 1
|
||||
icon: ping
|
||||
|
||||
- title: Test multiple dropdowns
|
||||
shell: echo {{ salutation }} {{ person }}
|
||||
icon: ping
|
||||
arguments:
|
||||
- name: salutation
|
||||
choices:
|
||||
- value: Hello
|
||||
- value: Goodbye
|
||||
|
||||
- name: person
|
||||
choices:
|
||||
- value: Alice
|
||||
- value: Bob
|
||||
- value: Dave
|
||||
28
integration-tests/configs/onlyDashboards/config.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: action1
|
||||
shell: date
|
||||
icon: clock
|
||||
|
||||
- title: action2
|
||||
shell: date
|
||||
icon: clock
|
||||
|
||||
- title: action3
|
||||
shell: date
|
||||
icon: clock
|
||||
|
||||
dashboards:
|
||||
- title: My Dashboard
|
||||
contents:
|
||||
- title: action1
|
||||
- title: action2
|
||||
- title: action3
|
||||
15
integration-tests/configs/pageTitle/config.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
pageTitle: "My Custom App"
|
||||
|
||||
actions:
|
||||
- title: sleep 2 seconds
|
||||
shell: sleep 2
|
||||
icon: "🥱"
|
||||
14
integration-tests/configs/policy-all-false/config.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Integration Test Config: Policy All False
|
||||
#
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
defaultPolicy:
|
||||
showDiagnostics: false
|
||||
showLogList: false
|
||||
|
||||
actions:
|
||||
- title: sleep 2 seconds
|
||||
shell: sleep 2
|
||||
icon: "🥱"
|
||||
16
integration-tests/configs/prometheus/config.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
prometheus:
|
||||
enabled: true
|
||||
defaultGoMetrics: false
|
||||
|
||||
actions:
|
||||
- title: Hello OliveTin
|
||||
shell: echo "Hello OliveTin"
|
||||
14
integration-tests/configs/sleep/config.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
# Integration Test Config: Sleep
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: Sleep
|
||||
shell: sleep 10
|
||||
popupOnStart: execution-dialog
|
||||
timeout: 9
|
||||