Compare commits
1282 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a634046757 | ||
|
|
6a4d86e084 | ||
|
|
9f9b41c63d | ||
|
|
d813f6515b | ||
|
|
730a445ef6 | ||
|
|
938947548b | ||
|
|
2d5085e652 | ||
|
|
e665e299f2 | ||
|
|
30b9c5eda5 | ||
|
|
fbf29e774a | ||
|
|
5429971553 | ||
|
|
df81bde6fd | ||
|
|
cb8b56ceb8 | ||
|
|
1886c9954b | ||
|
|
c942503351 | ||
|
|
806fc42379 | ||
|
|
06a49f0272 | ||
|
|
a99bcdc437 | ||
|
|
2d4378423b | ||
|
|
6aaf620f18 | ||
|
|
8e38cb2149 | ||
|
|
49ea05c890 | ||
|
|
54b479fb3a | ||
|
|
182fbd3e23 | ||
|
|
6819e33510 | ||
|
|
d547c657a0 | ||
|
|
62172db59b | ||
|
|
fc507b69ab | ||
|
|
cfde05f5af | ||
|
|
15618ad03b | ||
|
|
d54fec069c | ||
|
|
f3cbb9b9d1 | ||
|
|
01b510f7f8 | ||
|
|
2c1463e071 | ||
|
|
9f90ab1b6b | ||
|
|
ca5bf930d0 | ||
|
|
7eb0ab342a | ||
|
|
f400916351 | ||
|
|
858cf49966 | ||
|
|
a5e97fc425 | ||
|
|
981d1c626b | ||
|
|
ca2b1a6612 | ||
|
|
fc749b6ef8 | ||
|
|
7c4e5b1a57 | ||
|
|
4e0e5e955c | ||
|
|
a07568f412 | ||
|
|
092735680e | ||
|
|
5d43125284 | ||
|
|
05d51a5447 | ||
|
|
dd6b8b7157 | ||
|
|
3218760f84 | ||
|
|
3fa2cdfe44 | ||
|
|
60eef31490 | ||
|
|
3e83e89c2b | ||
|
|
949fd9092d | ||
|
|
4276f3436d | ||
|
|
4735aa12a9 | ||
|
|
5e38b1fbca | ||
|
|
65555e1fe3 | ||
|
|
a5e53df998 | ||
|
|
70452428ab | ||
|
|
a431c57e25 | ||
|
|
a575afdb05 | ||
|
|
40d6c7f87b | ||
|
|
8feae0a80f | ||
|
|
0a5b91f0cf | ||
|
|
08aae4df39 | ||
|
|
0ff8aecbea | ||
|
|
e6cae84764 | ||
|
|
2d2d2931f1 | ||
|
|
69ad5e7d1f | ||
|
|
97abc6aeb9 | ||
|
|
8cf37dd37c | ||
|
|
e11d091ac5 | ||
|
|
e952dfcedd | ||
|
|
2565d5cab2 | ||
|
|
e4e66c74bc | ||
|
|
6607c41823 | ||
|
|
43570f20b5 | ||
|
|
9ea7a93206 | ||
|
|
dec4922fe5 | ||
|
|
ef1f95b347 | ||
|
|
7317312f68 | ||
|
|
2965e8df65 | ||
|
|
4fdcc7fae9 | ||
|
|
6be8f7539d | ||
|
|
a6de65fe39 | ||
|
|
fc5dae5a8e | ||
|
|
db69454d32 | ||
|
|
c824335b0e | ||
|
|
1f1ce11078 | ||
|
|
0ec1571d37 | ||
|
|
407e8b52a5 | ||
|
|
1be13a151d | ||
|
|
4d9d417d7a | ||
|
|
aaff8f0c4a | ||
|
|
8c064daf3f | ||
|
|
b0b3dae147 | ||
|
|
2ea2be43d3 | ||
|
|
81f7364e93 | ||
|
|
c530869530 | ||
|
|
586ccd90ec | ||
|
|
ece237c49f | ||
|
|
11da5ca98c | ||
|
|
276a68bda6 | ||
|
|
fba89d197a | ||
|
|
b3259e2489 | ||
|
|
880704dda0 | ||
|
|
d5e92e9c09 | ||
|
|
df2f5692bd | ||
|
|
dacc99b8b6 | ||
|
|
0e8ade3b61 | ||
|
|
fe2d8f6261 | ||
|
|
927381039b | ||
|
|
6b95947acb | ||
|
|
b05e53ce58 | ||
|
|
a2546582f2 | ||
|
|
a023b3401e | ||
|
|
07142e9872 | ||
|
|
f5fea6c34d | ||
|
|
5eb0fb3422 | ||
|
|
fa915e7c35 | ||
|
|
2b7905d31c | ||
|
|
9cbbf37add | ||
|
|
642a0c3b0d | ||
|
|
ff47a19250 | ||
|
|
1fc1a3fbe3 | ||
|
|
996172bd69 | ||
|
|
df038314ef | ||
|
|
67881c2e34 | ||
|
|
9c691a17b2 | ||
|
|
54df86a4a2 | ||
|
|
70aff759d7 | ||
|
|
d903cc6e3b | ||
|
|
c520271ab5 | ||
|
|
f6936224d9 | ||
|
|
283984a6a6 | ||
|
|
c4fc09f72a | ||
|
|
efdbabfa04 | ||
|
|
a43af333fd | ||
|
|
1f5894ff82 | ||
|
|
79b4054e31 | ||
|
|
376d7d0c78 | ||
|
|
c8b85330ed | ||
|
|
c65dffee13 | ||
|
|
275320ad9b | ||
|
|
f71cdf9065 | ||
|
|
8e0a239de9 | ||
|
|
ed2b95bc3f | ||
|
|
2e23ee4c66 | ||
|
|
58def52e30 | ||
|
|
aea5ef3b68 | ||
|
|
f2e14cf0a6 | ||
|
|
bc9b6d1595 | ||
|
|
fd11b62390 | ||
|
|
8db00a5ab5 | ||
|
|
a6757e9374 | ||
|
|
e0fe385404 | ||
|
|
f9f4258ec1 | ||
|
|
f939d563ad | ||
|
|
fad03add54 | ||
|
|
9ab6dab081 | ||
|
|
5a55268830 | ||
|
|
fc3769239d | ||
|
|
c9fb3153eb | ||
|
|
a31f9ed9c5 | ||
|
|
2f75828ba4 | ||
|
|
89679ec358 | ||
|
|
98f77f52df | ||
|
|
ca647d44bb | ||
|
|
838b7dab5b | ||
|
|
4f7fe247b4 | ||
|
|
cee20bd7d2 | ||
|
|
f99ed0c4d9 | ||
|
|
4ae6523164 | ||
|
|
2ece764dcb | ||
|
|
f3bdd98af5 | ||
|
|
3c3c511c5b | ||
|
|
45167b87e5 | ||
|
|
f7d5755744 | ||
|
|
e2adafd16b | ||
|
|
9d178b238d | ||
|
|
431054a1be | ||
|
|
5bf6428253 | ||
|
|
6a4a03267b | ||
|
|
6598e16974 | ||
|
|
b1943a4cec | ||
|
|
5f70d5afd3 | ||
|
|
7dead36212 | ||
|
|
1f68a7830b | ||
|
|
42c5b7a181 | ||
|
|
6596b47e54 | ||
|
|
02e4a3e244 | ||
|
|
d9af90c76b | ||
|
|
a54405f24f | ||
|
|
2b39c5d517 | ||
|
|
a09d295948 | ||
|
|
2c0b0be8c4 | ||
|
|
b289877273 | ||
|
|
869d54b9a8 | ||
|
|
df48ae8316 | ||
|
|
e42cf2a420 | ||
|
|
bd899b649d | ||
|
|
2bc43ee2a5 | ||
|
|
48e2907426 | ||
|
|
4e33cce128 | ||
|
|
6658028f90 | ||
|
|
3fe67bb179 | ||
|
|
ce9a2d0cc3 | ||
|
|
45b7c7af15 | ||
|
|
3116aaeccb | ||
|
|
bfd5517c6c | ||
|
|
c83598a4f9 | ||
|
|
3dc983871f | ||
|
|
ac6fcefffd | ||
|
|
094080cc9f | ||
|
|
d2dff968fb | ||
|
|
496f82a01f | ||
|
|
9293c3e861 | ||
|
|
da7d42edd5 | ||
|
|
87b96d6526 | ||
|
|
a371821ff8 | ||
|
|
748cb6eb8f | ||
|
|
05bee642e9 | ||
|
|
081be04592 | ||
|
|
f77518d086 | ||
|
|
35e6202b3c | ||
|
|
1d84ea6ab9 | ||
|
|
df461d81d8 | ||
|
|
821d5aa595 | ||
|
|
c25bd71592 | ||
|
|
7e9224680e | ||
|
|
bc89116d3d | ||
|
|
aabe75ee13 | ||
|
|
8903adc523 | ||
|
|
61d8c8cd39 | ||
|
|
2e1d991e0c | ||
|
|
5a57167832 | ||
|
|
c3df181dcc | ||
|
|
699ea47ac5 | ||
|
|
fba69f3109 | ||
|
|
063583dda1 | ||
|
|
a31ca5cfb6 | ||
|
|
c0a13305ad | ||
|
|
9d8cbf87dc | ||
|
|
009f7da26b | ||
|
|
f1d0359dc4 | ||
|
|
473baa8c5b | ||
|
|
1a5dda6c72 | ||
|
|
8a78dcb06e | ||
|
|
185768a80f | ||
|
|
619a2817ce | ||
|
|
12a33ee9fc | ||
|
|
26302ca930 | ||
|
|
7e85555ba7 | ||
|
|
2546676f6a | ||
|
|
93b7edf5c4 | ||
|
|
571e432263 | ||
|
|
580b158567 | ||
|
|
395aa665d7 | ||
|
|
a80e54e0b3 | ||
|
|
8ccd41b551 | ||
|
|
9d6a3a8a0d | ||
|
|
a9d1b4b863 | ||
|
|
459d664a60 | ||
|
|
5f10b0156c | ||
|
|
348c07f847 | ||
|
|
934b1894c4 | ||
|
|
a660f4af93 | ||
|
|
12c0d39b13 | ||
|
|
bc6de68006 | ||
|
|
52bb753594 | ||
|
|
ed6b763d06 | ||
|
|
07e421afea | ||
|
|
71352841bf | ||
|
|
91e8fcbb24 | ||
|
|
5b67a85624 | ||
|
|
b49efa0d5a | ||
|
|
abeb585a0d | ||
|
|
cf621f1cc9 | ||
|
|
389a494e00 | ||
|
|
a9c55dc23b | ||
|
|
3d35b7e71b | ||
|
|
6f78146711 | ||
|
|
8afa47c351 | ||
|
|
20838cfc3e | ||
|
|
d00acd6d2f | ||
|
|
0b58a36779 | ||
|
|
54bc98e9c1 | ||
|
|
836daf2ad9 | ||
|
|
9a8cd9bd87 | ||
|
|
c555d91503 | ||
|
|
e0e7c1bcc4 | ||
|
|
4bf733beec | ||
|
|
e93f23c943 | ||
|
|
5384f4d9f2 | ||
|
|
3c0a97c3cc | ||
|
|
129db6cf4e | ||
|
|
c6bfa5652f | ||
|
|
780472d83e | ||
|
|
0d02e3f15a | ||
|
|
bf82f22d0f | ||
|
|
e18f182ce6 | ||
|
|
761c26b587 | ||
|
|
5e62769dcf | ||
|
|
86b8a718a0 | ||
|
|
a729cfc31d | ||
|
|
96cfda852a | ||
|
|
0423d9246c | ||
|
|
985798757f | ||
|
|
72876f6749 | ||
|
|
03d6e223d8 | ||
|
|
62d9f2ed97 | ||
|
|
a550a44a52 | ||
|
|
b19d586949 | ||
|
|
bbfa3ee57f | ||
|
|
af409dd3b8 | ||
|
|
3db79ca149 | ||
|
|
e880420494 | ||
|
|
28ec17b8fe | ||
|
|
8026a40807 | ||
|
|
068c6d406a | ||
|
|
57470e4ef0 | ||
|
|
ca8e1537f5 | ||
|
|
d67b818398 | ||
|
|
f5f46424bb | ||
|
|
1e259717ce | ||
|
|
91ece99a9c | ||
|
|
d30ebdb369 | ||
|
|
ade8522b69 | ||
|
|
159b308f31 | ||
|
|
837bf25f7b | ||
|
|
8301d3669b | ||
|
|
cc752e8d80 | ||
|
|
d20e6bd42f | ||
|
|
bfee9fe233 | ||
|
|
9c962b09f1 | ||
|
|
46edefead7 | ||
|
|
725cfc8a2b | ||
|
|
1ce48b9ef4 | ||
|
|
37cc28f225 | ||
|
|
5ebe3bb8e0 | ||
|
|
aa01512f89 | ||
|
|
37ff2b886a | ||
|
|
ce18212756 | ||
|
|
08f50a274a | ||
|
|
892ee0013a | ||
|
|
e9a47041fd | ||
|
|
d419fa06e8 | ||
|
|
8b961a890c | ||
|
|
db32915114 | ||
|
|
2ffdb10cce | ||
|
|
507fd7e5d4 | ||
|
|
7df599b5a9 | ||
|
|
9987334f55 | ||
|
|
d8c3365384 | ||
|
|
2e284b5af9 | ||
|
|
89ddd4e6a3 | ||
|
|
36524ea481 | ||
|
|
35cf693610 | ||
|
|
42148a66bd | ||
|
|
96878715bf | ||
|
|
1a5f3342e7 | ||
|
|
3613d13a2b | ||
|
|
7786140d85 | ||
|
|
3a23b57f1b | ||
|
|
5a6e6fba69 | ||
|
|
910b3a6162 | ||
|
|
0dc19bed45 | ||
|
|
a7bdb64301 | ||
|
|
4739072a85 | ||
|
|
07bdae2488 | ||
|
|
b84035d821 | ||
|
|
dc0a7b9dae | ||
|
|
2937bd8de0 | ||
|
|
37315ef4d9 | ||
|
|
1986fece07 | ||
|
|
16b1657f35 | ||
|
|
c115c62cd9 | ||
|
|
d2df7f8d5b | ||
|
|
6cb79864e6 | ||
|
|
982d28c7b4 | ||
|
|
0f57516fdc | ||
|
|
75a89defcb | ||
|
|
22a6c52060 | ||
|
|
37607e4a41 | ||
|
|
5936155998 | ||
|
|
8d76de935f | ||
|
|
9baa530064 | ||
|
|
103414b338 | ||
|
|
72fe68ebfe | ||
|
|
cfed31958b | ||
|
|
3d5fca2d36 | ||
|
|
a5710286ec | ||
|
|
d0ce0c6c58 | ||
|
|
779e2cf0f2 | ||
|
|
2108474777 | ||
|
|
e25e0f1747 | ||
|
|
e8e74b639c | ||
|
|
a14fcd1e50 | ||
|
|
485c0e0891 | ||
|
|
00a19e9d43 | ||
|
|
67d0dc0783 | ||
|
|
3718c35842 | ||
|
|
567ffc80b6 | ||
|
|
5d15a08ac8 | ||
|
|
2b84037346 | ||
|
|
536f11e617 | ||
|
|
5d367a384e | ||
|
|
04933e6bf0 | ||
|
|
12abe9aa69 | ||
|
|
cba642a4bc | ||
|
|
9fce0ac0aa | ||
|
|
681812b619 | ||
|
|
6d0be57698 | ||
|
|
1d521602e1 | ||
|
|
2f67e7937a | ||
|
|
e0078a42dc | ||
|
|
3ec875d49d | ||
|
|
35028d1310 | ||
|
|
7ba3d7c4bb | ||
|
|
c05e64098c | ||
|
|
e82ee56a2c | ||
|
|
5681b61aea | ||
|
|
d6ce7eab25 | ||
|
|
bf597fe41c | ||
|
|
6a920f964f | ||
|
|
977b66ba4e | ||
|
|
4659c29358 | ||
|
|
e3bc95b275 | ||
|
|
bf51255e13 | ||
|
|
915fe6837b | ||
|
|
d1237215c0 | ||
|
|
8ba5cfdfa6 | ||
|
|
0d1097425d | ||
|
|
0789a9ecc8 | ||
|
|
400f764b74 | ||
|
|
46e036e3a4 | ||
|
|
17e6264af8 | ||
|
|
468b6ae125 | ||
|
|
e5f5ee4f6a | ||
|
|
3695082ec2 | ||
|
|
f384d86014 | ||
|
|
34bf5028c3 | ||
|
|
8b727aa939 | ||
|
|
9432600de6 | ||
|
|
bb790ec687 | ||
|
|
8bad658d7c | ||
|
|
a66cc9c08a | ||
|
|
0dc24fb342 | ||
|
|
b965ac6232 | ||
|
|
08d61013c8 | ||
|
|
7796f65814 | ||
|
|
02a3afb9ad | ||
|
|
15a11c384b | ||
|
|
57b8496d89 | ||
|
|
7bbe44f5d2 | ||
|
|
ac11ab0431 | ||
|
|
12e352964c | ||
|
|
7daefc4384 | ||
|
|
29541becd0 | ||
|
|
ef6dc82f88 | ||
|
|
6d66d93180 | ||
|
|
3ded17b920 | ||
|
|
d35651f570 | ||
|
|
235300d034 | ||
|
|
2f7ebf5166 | ||
|
|
4819cc87c6 | ||
|
|
c5da383d1e | ||
|
|
2da17329fa | ||
|
|
70d84acf76 | ||
|
|
fef99a0023 | ||
|
|
76d0935e8f | ||
|
|
42dfe5ada9 | ||
|
|
ef136c25e1 | ||
|
|
4251635a8a | ||
|
|
13f00104dd | ||
|
|
b0b82b29c1 | ||
|
|
e0d43ad3d9 | ||
|
|
ae15115af7 | ||
|
|
a2890c6cb0 | ||
|
|
2b00ece07f | ||
|
|
2e3c34571f | ||
|
|
b8b10b5176 | ||
|
|
e6e62bbd24 | ||
|
|
05e9bbf1d6 | ||
|
|
bf94c2395f | ||
|
|
4a749ca345 | ||
|
|
80ffe6c9a3 | ||
|
|
5414b5a8f9 | ||
|
|
e3ba85a326 | ||
|
|
154fa69dbb | ||
|
|
76c44973e9 | ||
|
|
26fdc82cc1 | ||
|
|
dcc587182c | ||
|
|
7cee1ff5ff | ||
|
|
78b8e3bf0b | ||
|
|
bd4c014c12 | ||
|
|
daa263cd68 | ||
|
|
e2965d39af | ||
|
|
8279d15ebb | ||
|
|
9731fa35d8 | ||
|
|
53be4db22b | ||
|
|
0202fa2ca3 | ||
|
|
d5262b5474 | ||
|
|
cb2e1d54c2 | ||
|
|
6392297a27 | ||
|
|
6001d4eba3 | ||
|
|
79588e4bbc | ||
|
|
2b21c38382 | ||
|
|
481d25845e | ||
|
|
e0f8bfe283 | ||
|
|
d2ecb01358 | ||
|
|
316e793b1e | ||
|
|
6df6809ab3 | ||
|
|
c71f892601 | ||
|
|
d0b5a16ce7 | ||
|
|
84638d3228 | ||
|
|
bb21a2aa5f | ||
|
|
c37c948129 | ||
|
|
5155476dd7 | ||
|
|
7ef4f60309 | ||
|
|
b0865cbbdc | ||
|
|
990c1070e2 | ||
|
|
65bdd413eb | ||
|
|
60fa35eb73 | ||
|
|
32cfd5c233 | ||
|
|
7852495527 | ||
|
|
3679a78f47 | ||
|
|
6b21568408 | ||
|
|
04a5aa41d7 | ||
|
|
fea1a2199c | ||
|
|
9c8492efb9 | ||
|
|
20b89a8ddd | ||
|
|
5a8e281fb1 | ||
|
|
a9bb413199 | ||
|
|
428d8ab1b1 | ||
|
|
c2675bcdb6 | ||
|
|
1fe228e4c0 | ||
|
|
c5a6497f10 | ||
|
|
f25a82585f | ||
|
|
01209b66ac | ||
|
|
4e19817d6f | ||
|
|
a48adff8ac | ||
|
|
ab3b32fda1 | ||
|
|
cafab78ab4 | ||
|
|
2d497dee7d | ||
|
|
280ecd9aea | ||
|
|
3a980a3bc0 | ||
|
|
556a5bdd4e | ||
|
|
eca26a345f | ||
|
|
eacff7232d | ||
|
|
3c071db207 | ||
|
|
6609c56063 | ||
|
|
f7ae3de914 | ||
|
|
d82bc4e77e | ||
|
|
31e1df0afd | ||
|
|
700e9236f9 | ||
|
|
ee1e62aff0 | ||
|
|
aa0c38d66f | ||
|
|
4e4d9e33f0 | ||
|
|
f78daa98bc | ||
|
|
dad8802fb0 | ||
|
|
529b7041e7 | ||
|
|
5de1eecf92 | ||
|
|
08ee301f89 | ||
|
|
4154904b21 | ||
|
|
79282809e3 | ||
|
|
108c2533c2 | ||
|
|
fd7309cd17 | ||
|
|
c5f871edf6 | ||
|
|
3f2d2b238d | ||
|
|
e6a30b99d3 | ||
|
|
08ce2b7799 | ||
|
|
dbe7336f32 | ||
|
|
aac953f483 | ||
|
|
49bc469430 | ||
|
|
f3ac8a5cc5 | ||
|
|
847d08a9bb | ||
|
|
0563a363c2 | ||
|
|
f9dc0d6b54 | ||
|
|
40ef3604aa | ||
|
|
970604dc73 | ||
|
|
d6617f214d | ||
|
|
860fccbd4c | ||
|
|
b3adb839e0 | ||
|
|
5e9654c3bc | ||
|
|
43c6bff964 | ||
|
|
aef84189a4 | ||
|
|
04f8bfc975 | ||
|
|
d139e93160 | ||
|
|
1fcc0694ca | ||
|
|
7ba7858076 | ||
|
|
b1cbc433ed | ||
|
|
8beaf97306 | ||
|
|
e626364f45 | ||
|
|
ebaec51f67 | ||
|
|
36b90451af | ||
|
|
e0b1c2a6a4 | ||
|
|
42c6f4264e | ||
|
|
78641e7052 | ||
|
|
6bf0118d20 | ||
|
|
0415cd3719 | ||
|
|
a3412b2f95 | ||
|
|
6a4b3b026f | ||
|
|
f213976a5d | ||
|
|
3605f71f70 | ||
|
|
dc08847a7d | ||
|
|
a34204e25e | ||
|
|
25d73ac0a2 | ||
|
|
f67c0c0e75 | ||
|
|
08c8255d59 | ||
|
|
157efaa02e | ||
|
|
a56a29495e | ||
|
|
c454cd75b3 | ||
|
|
633684f576 | ||
|
|
de50b5e0a1 | ||
|
|
855f287e39 | ||
|
|
f018dee75e | ||
|
|
7d3b218e24 | ||
|
|
536382ce34 | ||
|
|
f6f003d524 | ||
|
|
8126de4048 | ||
|
|
4c41df85a0 | ||
|
|
2c9f78bb9e | ||
|
|
23899e196e | ||
|
|
ecbc87265e | ||
|
|
9f8731d668 | ||
|
|
12d545138a | ||
|
|
0c054581ac | ||
|
|
c8c2f83763 | ||
|
|
9056d591c3 | ||
|
|
191f58074e | ||
|
|
92d3af3d2b | ||
|
|
2ddc4d62a2 | ||
|
|
81a2967683 | ||
|
|
29e3c55df0 | ||
|
|
27e3cdffb1 | ||
|
|
a1ad56aebb | ||
|
|
2573b3b827 | ||
|
|
bcd9a8fcd2 | ||
|
|
8dbc83fd27 | ||
|
|
275280d24e | ||
|
|
c82fa4709d | ||
|
|
c8e826014b | ||
|
|
75b2e93678 | ||
|
|
b0573df9d8 | ||
|
|
e6c14590f1 | ||
|
|
248ac43f28 | ||
|
|
9361a27dca | ||
|
|
700836903f | ||
|
|
039ce26f58 | ||
|
|
95bfb7f0b6 | ||
|
|
9a181556ca | ||
|
|
9554b6f3ec | ||
|
|
45089437ef | ||
|
|
f47a3b0586 | ||
|
|
1a19c7520a | ||
|
|
6266af66f7 | ||
|
|
d3cdc24ebf | ||
|
|
f37d2fc4d7 | ||
|
|
6cd182b858 | ||
|
|
d46bf37726 | ||
|
|
363295e0ea | ||
|
|
611d5daf27 | ||
|
|
0c1e42078e | ||
|
|
c9acafd3ad | ||
|
|
75dd760148 | ||
|
|
28bfbbfa68 | ||
|
|
36326697d7 | ||
|
|
34b7cfec6f | ||
|
|
f65b725b27 | ||
|
|
d7bceb8f2d | ||
|
|
549f418b69 | ||
|
|
8a95f49f8f | ||
|
|
2c269a87a6 | ||
|
|
c17a27b7bf | ||
|
|
3eaecf2ca0 | ||
|
|
07182639f3 | ||
|
|
046628eda4 | ||
|
|
8f0054b2b0 | ||
|
|
33f86a7730 | ||
|
|
2487c69136 | ||
|
|
492d24f28a | ||
|
|
9a355da75c | ||
|
|
8bae065572 | ||
|
|
efebb5a869 | ||
|
|
32cc745a85 | ||
|
|
42bfe3ffac | ||
|
|
de3a5f87cf | ||
|
|
bd1add6dea | ||
|
|
d275cc6a8b | ||
|
|
556b5bd2e9 | ||
|
|
e26cf2a19d | ||
|
|
94f5f1faf6 | ||
|
|
fb3c58bd60 | ||
|
|
e50c918e4d | ||
|
|
eb3ff3369c | ||
|
|
a07a08991f | ||
|
|
6fcc8f3401 | ||
|
|
36b38c6da4 | ||
|
|
7f2f86b7ea | ||
|
|
68514bc7ed | ||
|
|
f0dfab536c | ||
|
|
3b75c9999e | ||
|
|
084ddf4f5d | ||
|
|
6770b1fa72 | ||
|
|
118d39ed83 | ||
|
|
c24df4f876 | ||
|
|
0ae9c25d6f | ||
|
|
f99b2a9def | ||
|
|
caf9eeae2e | ||
|
|
6afecb5708 | ||
|
|
739d32e2e0 | ||
|
|
4e601298b0 | ||
|
|
74cffced3f | ||
|
|
f8cc76be35 | ||
|
|
dced2dd418 | ||
|
|
418fe97c67 | ||
|
|
1440f9b721 | ||
|
|
9504c086ea | ||
|
|
3a15d6475c | ||
|
|
18f3a00c0f | ||
|
|
5138bbe947 | ||
|
|
a309feb516 | ||
|
|
e9c172c261 | ||
|
|
6ac6842a8d | ||
|
|
71dc53ac1f | ||
|
|
4450fa1379 | ||
|
|
af0f9489c7 | ||
|
|
75b2b2bdb2 | ||
|
|
2d8224fd12 | ||
|
|
c3aea3ba72 | ||
|
|
ad1ff29ed2 | ||
|
|
9887bd99c7 | ||
|
|
9af9d0192d | ||
|
|
62933cf637 | ||
|
|
ee0837571d | ||
|
|
c2b12419c7 | ||
|
|
991aed7e8c | ||
|
|
528d5fc5a9 | ||
|
|
93569227f3 | ||
|
|
46812f9e42 | ||
|
|
61f043319d | ||
|
|
77140d01a0 | ||
|
|
beff326001 | ||
|
|
f2841b7328 | ||
|
|
2ba63b2484 | ||
|
|
f054241a73 | ||
|
|
b5007b9195 | ||
|
|
9deea64097 | ||
|
|
105777da21 | ||
|
|
e13743dfa8 | ||
|
|
35bdd3a41a | ||
|
|
464d757800 | ||
|
|
8076fcd148 | ||
|
|
53b1f07601 | ||
|
|
2626d78835 | ||
|
|
3442ddfae2 | ||
|
|
5ed863c5ec | ||
|
|
00f6387182 | ||
|
|
ca70e8f0c8 | ||
|
|
081e95b293 | ||
|
|
1282e610ba | ||
|
|
287c52cef4 | ||
|
|
dd421fec80 | ||
|
|
9b10bdaf0d | ||
|
|
4b916829a4 | ||
|
|
b33e9d9187 | ||
|
|
9d0d323b50 | ||
|
|
2872569ce0 | ||
|
|
cd2212b754 | ||
|
|
eb6525b8d9 | ||
|
|
10319a1e3d | ||
|
|
6bcfea9372 | ||
|
|
4699d1c2ee | ||
|
|
68f0b2efc3 | ||
|
|
b2af8e196b | ||
|
|
2e626a915f | ||
|
|
0127050d89 | ||
|
|
a25938022f | ||
|
|
c47b2973b0 | ||
|
|
efd3934470 | ||
|
|
fb7da4e07e | ||
|
|
d7d209f694 | ||
|
|
83a88299c2 | ||
|
|
bfae3b86cc | ||
|
|
027ee4d336 | ||
|
|
b07a65c879 | ||
|
|
284b2762f5 | ||
|
|
5ba6e3b332 | ||
|
|
8b0e3d960a | ||
|
|
fba2953974 | ||
|
|
a36b843eff | ||
|
|
0735bc2d8a | ||
|
|
69d6fd645b | ||
|
|
039b11d434 | ||
|
|
485581f680 | ||
|
|
a0c2006e24 | ||
|
|
7a288c360d | ||
|
|
597d39f651 | ||
|
|
7d27f64b8a | ||
|
|
bd280bdc53 | ||
|
|
018429866e | ||
|
|
78ad6526b8 | ||
|
|
8c6e960db7 | ||
|
|
46d38ff8c0 | ||
|
|
db6fe469ad | ||
|
|
5a7630bcd1 | ||
|
|
099b57169d | ||
|
|
e22dfd3314 | ||
|
|
be71992930 | ||
|
|
37433178cb | ||
|
|
14499a4564 | ||
|
|
0355777f59 | ||
|
|
a55834c78d | ||
|
|
79bbb47459 | ||
|
|
d3bcf0605b | ||
|
|
19a02f3a40 | ||
|
|
db20e9308a | ||
|
|
b3f9f28554 | ||
|
|
ea5bd3f346 | ||
|
|
5c13797639 | ||
|
|
f147905532 | ||
|
|
a73314c12a | ||
|
|
619934a275 | ||
|
|
5fe1384e55 | ||
|
|
9af2c4a18a | ||
|
|
8f4c56b24a | ||
|
|
5eb0f65934 | ||
|
|
4ed4f165ac | ||
|
|
eb8419fda0 | ||
|
|
b4a7ee0e89 | ||
|
|
83ad1bc529 | ||
|
|
56948d3035 | ||
|
|
e71e5ad57e | ||
|
|
e06d1fa5b0 | ||
|
|
460439f6bd | ||
|
|
4b4072a47e | ||
|
|
65d19d92e9 | ||
|
|
79e52d1c6e | ||
|
|
59963ee7b9 | ||
|
|
88273b1c9b | ||
|
|
bc1eb994f9 | ||
|
|
136f0fd4bd | ||
|
|
021dc13ce9 | ||
|
|
925b71489d | ||
|
|
580ce9f8f5 | ||
|
|
2b3d8c062d | ||
|
|
41858e091a | ||
|
|
5b7eaf08ae | ||
|
|
7362722adb | ||
|
|
8198ea8819 | ||
|
|
cb615f4cbc | ||
|
|
6623fd8362 | ||
|
|
7f8be85116 | ||
|
|
c61381441c | ||
|
|
b3cecdfea2 | ||
|
|
66f582df58 | ||
|
|
7f6f7e11ce | ||
|
|
0bdda313da | ||
|
|
d5e851cff7 | ||
|
|
92f1ec13f9 | ||
|
|
5376006754 | ||
|
|
361979411e | ||
|
|
3981308083 | ||
|
|
facd5e14cc | ||
|
|
ecce92c528 | ||
|
|
7696940989 | ||
|
|
941ff46c2c | ||
|
|
94036073de | ||
|
|
13216f481c | ||
|
|
eb35df8720 | ||
|
|
bf500fe1a4 | ||
|
|
c173e86e62 | ||
|
|
d4cb148272 | ||
|
|
ffc3f8544e | ||
|
|
b326bfe63a | ||
|
|
12f3f47ef9 | ||
|
|
45734747c1 | ||
|
|
37933d814c | ||
|
|
5b26287264 | ||
|
|
bac20b1d1f | ||
|
|
904c641992 | ||
|
|
36004bfd94 | ||
|
|
0fcba7e90c | ||
|
|
2e9182933e | ||
|
|
e3674fa2c1 | ||
|
|
a14cbe1319 | ||
|
|
c03c35de88 | ||
|
|
dc6c649af8 | ||
|
|
b0d2bdb0ba | ||
|
|
9239dd9a8b | ||
|
|
07a368a0bd | ||
|
|
b8d6d1c249 | ||
|
|
0f1d6a1ad2 | ||
|
|
37c6928ffc | ||
|
|
7478b6dbe0 | ||
|
|
e3cd6e1441 | ||
|
|
8081d968b6 | ||
|
|
a226eee6ef | ||
|
|
8b5a21e593 | ||
|
|
2c71f6c6be | ||
|
|
1a50b01edf | ||
|
|
05f08eeb4c | ||
|
|
fdc3ebb5c5 | ||
|
|
8f06bccd48 | ||
|
|
16b4eb67d4 | ||
|
|
ffa545cb41 | ||
|
|
f67454d51c | ||
|
|
8ae54c56db | ||
|
|
9a17adcc6f | ||
|
|
4dd6903025 | ||
|
|
085770d0ad | ||
|
|
fbaba7c37d | ||
|
|
d5cea208d2 | ||
|
|
36397adca4 | ||
|
|
4971e25d44 | ||
|
|
150357441d | ||
|
|
c4ee663285 | ||
|
|
8c95b4a9b9 | ||
|
|
32ba919851 | ||
|
|
eb37345e85 | ||
|
|
681e454917 | ||
|
|
7c7b82dee4 | ||
|
|
2ae47af8f0 | ||
|
|
b72d91d0d4 | ||
|
|
56010e7203 | ||
|
|
f658698f7b | ||
|
|
b708b9c6df | ||
|
|
c2908e17fa | ||
|
|
302daab824 | ||
|
|
ec49f238d6 | ||
|
|
3e43a5d866 | ||
|
|
a99d5e68e9 | ||
|
|
28514276ec | ||
|
|
b611427c17 | ||
|
|
ce7a4ead04 | ||
|
|
5fe15a85fd | ||
|
|
ae412909f6 | ||
|
|
c6ed579797 | ||
|
|
b2a525268e | ||
|
|
eb78b4881c | ||
|
|
4ce6e5a9f6 | ||
|
|
15d7e75555 | ||
|
|
6453cc6ccc | ||
|
|
d882a2eb63 | ||
|
|
065ac4aa25 | ||
|
|
dea54fc55e | ||
|
|
8f425bd9c7 | ||
|
|
9d909d73b8 | ||
|
|
b417d50a28 | ||
|
|
aa93a2f702 | ||
|
|
ba7125e773 | ||
|
|
46a7eaa4bb | ||
|
|
887439a6fe | ||
|
|
0e2b07d06d | ||
|
|
b7d4bde11b | ||
|
|
9707360948 | ||
|
|
103a8eb092 | ||
|
|
dcba4f9376 | ||
|
|
12aaa6fcb1 | ||
|
|
d8393481a4 | ||
|
|
70d3202a2c | ||
|
|
c7f1bbc03d | ||
|
|
61a55cb3f4 | ||
|
|
6a484af775 | ||
|
|
3074d41cf2 | ||
|
|
54199058e3 | ||
|
|
87a533791b | ||
|
|
c3109bb2c6 | ||
|
|
031cb836d2 | ||
|
|
aa0a9134cb | ||
|
|
749eac74fe | ||
|
|
ac39908737 | ||
|
|
bb8f4bf488 | ||
|
|
db0d157a74 | ||
|
|
bd8e1bbe71 | ||
|
|
7aba898cf5 | ||
|
|
baf039755f | ||
|
|
70977f7d80 | ||
|
|
884342d6af | ||
|
|
411b0fb4c2 | ||
|
|
8053fb2399 | ||
|
|
ed42dcab9c | ||
|
|
866b5b0f2f | ||
|
|
5834a1a0fa | ||
|
|
8c6d845603 | ||
|
|
667f363f3c | ||
|
|
e209ff38d9 | ||
|
|
ea915993b6 | ||
|
|
72d0468c6a | ||
|
|
35ae13b1c3 | ||
|
|
1d9460f565 | ||
|
|
e9a3ed71b4 | ||
|
|
e344e5b7e6 | ||
|
|
e814064403 | ||
|
|
f5aeb5cbcd | ||
|
|
f41164b892 | ||
|
|
03073c8364 | ||
|
|
a359bff531 | ||
|
|
3789ac6433 | ||
|
|
9f53f59f18 | ||
|
|
521bd746e3 | ||
|
|
3d8e43a42b | ||
|
|
9452e1852d | ||
|
|
3920d24af6 | ||
|
|
3af11e6ba8 | ||
|
|
7bcde46d49 | ||
|
|
0b73041718 | ||
|
|
dcdc0cb8c1 | ||
|
|
ad6ac1aad6 | ||
|
|
249dc6accd | ||
|
|
939e5999ca | ||
|
|
1f91e57d56 | ||
|
|
81e749dc60 | ||
|
|
241b2afda8 | ||
|
|
94cc29f227 | ||
|
|
6d6659eee1 | ||
|
|
5220be0775 | ||
|
|
07ebbf0863 | ||
|
|
b60c767fc5 | ||
|
|
371c3b78c3 | ||
|
|
6a3aa219d5 | ||
|
|
df586ddfdd | ||
|
|
f3b2bbfec0 | ||
|
|
3ea2114798 | ||
|
|
809cf70e0e | ||
|
|
390619535f | ||
|
|
3392ac1fa8 | ||
|
|
b09d94abbe | ||
|
|
03ac01d21f | ||
|
|
12b1d785e5 | ||
|
|
71c58e9d2e | ||
|
|
13c2997a52 | ||
|
|
47b840cac9 | ||
|
|
3f7f243f50 | ||
|
|
1d1e83b18d | ||
|
|
6f3a602c76 | ||
|
|
afb7a4c6a7 | ||
|
|
d0c950d4ca | ||
|
|
63ee7d5211 | ||
|
|
6b0d875745 | ||
|
|
a47a9b9c0c | ||
|
|
27040a3e5c | ||
|
|
c7a8a40e22 | ||
|
|
408de6af63 | ||
|
|
8a324afaa1 | ||
|
|
54bc4cede0 | ||
|
|
4cfbea80b0 | ||
|
|
64cb8286bd | ||
|
|
3e92e0afc6 | ||
|
|
8a91308280 | ||
|
|
3da861d71e | ||
|
|
3566e18e99 | ||
|
|
6e608e627a | ||
|
|
65f7fb979b | ||
|
|
558b5e14f1 | ||
|
|
a5ee2dd03b | ||
|
|
9e9fe78b8d | ||
|
|
265e126faf | ||
|
|
af440e5c5b | ||
|
|
32b8c91113 | ||
|
|
dbc60ccca4 | ||
|
|
d5b5af5d3a | ||
|
|
8c1bd3bc4e | ||
|
|
8f638186a3 | ||
|
|
0b5a27e674 | ||
|
|
870f1aaaec | ||
|
|
23cb4dcbe5 | ||
|
|
4f125b4244 | ||
|
|
2b9de7938f | ||
|
|
75bb07184f | ||
|
|
1fb491d2e1 | ||
|
|
0bc8bdd841 | ||
|
|
71d4e2626e | ||
|
|
0b26cbdd01 | ||
|
|
ca72b3c18b | ||
|
|
8676f2711b | ||
|
|
788a86bdcf | ||
|
|
0df6b4b220 | ||
|
|
f14dcd5c28 | ||
|
|
f86548e046 | ||
|
|
9dea11ab11 | ||
|
|
00749f806c | ||
|
|
e4e3591413 | ||
|
|
3f847d7c7f | ||
|
|
67a10994a4 | ||
|
|
4016296e0b | ||
|
|
92777366ec | ||
|
|
beb34f8264 | ||
|
|
f6f8c5c858 | ||
|
|
a19c4dffcd | ||
|
|
a5754971f6 | ||
|
|
dbe6435809 | ||
|
|
7e5b980600 | ||
|
|
a6c3b70ee7 | ||
|
|
0a23e7951a | ||
|
|
e0e3fc8fef | ||
|
|
a8b947f5ca | ||
|
|
0f28df51f1 | ||
|
|
6e36528f35 | ||
|
|
a885fdbea7 | ||
|
|
713de74abb | ||
|
|
8db6a33e15 | ||
|
|
ed2d577be0 | ||
|
|
084eddecbf | ||
|
|
5e09ba302a | ||
|
|
fa37b40435 | ||
|
|
c95dde5187 | ||
|
|
4d5a4d501a | ||
|
|
ce97f20826 | ||
|
|
ab9cd13abc | ||
|
|
7af5e828bb | ||
|
|
98d53c49a0 | ||
|
|
bb32849e47 | ||
|
|
13221faee0 | ||
|
|
abda14f543 | ||
|
|
3f0e459181 | ||
|
|
3df61381a4 | ||
|
|
daf8ae2192 | ||
|
|
3bf374ea5a | ||
|
|
94b3559eee | ||
|
|
1c93b42681 | ||
|
|
1d9d44ecc3 | ||
|
|
19178f963e | ||
|
|
4dc54f95b6 | ||
|
|
0790403923 | ||
|
|
c55008d302 | ||
|
|
df9df94bb8 | ||
|
|
502ce4e414 | ||
|
|
f5f07c0e96 | ||
|
|
9549424835 | ||
|
|
062b9baeab | ||
|
|
08d1fcaf03 | ||
|
|
ad7523a500 | ||
|
|
28aa0f3b76 | ||
|
|
8247843936 | ||
|
|
09ff5a37a9 | ||
|
|
663ec7710c | ||
|
|
0dad0b10e6 | ||
|
|
8c352813c4 | ||
|
|
745f5d9759 | ||
|
|
6536ace4f7 | ||
|
|
67e6e06e72 | ||
|
|
d0bf85de17 | ||
|
|
14cb3bdc67 | ||
|
|
e9dadd5571 | ||
|
|
d5eceb05ce | ||
|
|
b510c88f84 | ||
|
|
16e409e5f5 | ||
|
|
ad257fcd0e | ||
|
|
e84087d6ec | ||
|
|
218c2385f0 | ||
|
|
4ef8c2bc77 | ||
|
|
d9bc1bdfca | ||
|
|
a5c287e8d6 | ||
|
|
1e9f5c9c56 | ||
|
|
fbcf439f89 | ||
|
|
766c3666d8 | ||
|
|
f08289351d | ||
|
|
e426bba8b5 | ||
|
|
4b200762a5 | ||
|
|
fd203ed436 | ||
|
|
04cea4dbd6 | ||
|
|
a8f1056c3c | ||
|
|
c2576f4c62 | ||
|
|
1da5d1094f | ||
|
|
34e6122407 | ||
|
|
ee61feb581 | ||
|
|
7f04f1ed62 | ||
|
|
82646c3576 | ||
|
|
b3f62240c4 | ||
|
|
86a5992e8a | ||
|
|
82a731ed06 | ||
|
|
e650529efb | ||
|
|
1de57e124f | ||
|
|
9e72dcc390 | ||
|
|
80019e2071 | ||
|
|
ab7cdcd1b1 | ||
|
|
7afe1e0a30 | ||
|
|
a39eb80214 | ||
|
|
10e2a08cd2 | ||
|
|
6da4949c98 | ||
|
|
4d092f329b | ||
|
|
69c1d35406 | ||
|
|
a256a7328e | ||
|
|
75c8658366 | ||
|
|
c3e68915a3 | ||
|
|
d97f4da7fa | ||
|
|
06e81dbe37 | ||
|
|
4a46aaa880 | ||
|
|
e766372a81 | ||
|
|
c5ee9f095a | ||
|
|
9734ed1cf8 | ||
|
|
548d122c64 | ||
|
|
c5205ef7a9 | ||
|
|
f71a27960b | ||
|
|
d747e656dc | ||
|
|
867215e2af | ||
|
|
3614b9f3b7 | ||
|
|
cbfa4c85c1 | ||
|
|
b9bef5042a | ||
|
|
46e523d005 | ||
|
|
fa2930dfc5 | ||
|
|
086ab15e8a | ||
|
|
c2e3e784a2 | ||
|
|
9dd01bd36e | ||
|
|
02d08b0762 | ||
|
|
cbd312e67d | ||
|
|
5084d9f87e | ||
|
|
3c3408624d | ||
|
|
6e8403390b | ||
|
|
ef9e5d4bbc | ||
|
|
c0b49948d7 | ||
|
|
48b066fb83 | ||
|
|
7d551d59db | ||
|
|
5a057857dc | ||
|
|
0a9cea722c | ||
|
|
f7eb7b1ec6 | ||
|
|
0eb57a734e | ||
|
|
82ff9a2b06 | ||
|
|
051a48bc73 | ||
|
|
6b9d5cf7a0 | ||
|
|
2e7d64d97b | ||
|
|
dd6050ee0b | ||
|
|
c954910728 | ||
|
|
0fe2bc4a54 | ||
|
|
9f39cd615f | ||
|
|
07bf21dbb4 | ||
|
|
43e8c65473 | ||
|
|
8a926eabbe | ||
|
|
fcbed5f63e | ||
|
|
8d203a1d7c | ||
|
|
d6efb2dd12 | ||
|
|
6ea3938f74 | ||
|
|
63e773ce96 | ||
|
|
60b8e1041c | ||
|
|
fb263c926b | ||
|
|
dbbae30f5a | ||
|
|
7b73c86fa4 | ||
|
|
c16c6ea6a6 | ||
|
|
4ecba0040a | ||
|
|
f65aabe897 | ||
|
|
de2337afe7 | ||
|
|
3360f2fc08 | ||
|
|
7d8f3a9d9b | ||
|
|
63884bc836 | ||
|
|
9c231a2b8c | ||
|
|
6a5c979d2a | ||
|
|
094734ae1f | ||
|
|
97b4d457fd | ||
|
|
9669c0ddda | ||
|
|
daf8257a1f | ||
|
|
40f32c7cf9 | ||
|
|
b8295e5cfc | ||
|
|
aba489217a | ||
|
|
7775689dfa | ||
|
|
90397b2b6f | ||
|
|
32683a2b59 | ||
|
|
9e36847397 | ||
|
|
4c82804622 | ||
|
|
cb911c474e | ||
|
|
ce2537d69d | ||
|
|
38746e83c5 | ||
|
|
be8988d309 | ||
|
|
ed59994db8 | ||
|
|
11a8d3ba70 | ||
|
|
adf91bf3d0 | ||
|
|
7729b88a00 | ||
|
|
e93aecabb4 | ||
|
|
3cf43b2cc1 | ||
|
|
3cba8cf98d | ||
|
|
e2ca94c93b | ||
|
|
1c3e25ba1a | ||
|
|
f297f4ec52 | ||
|
|
534f10b8a7 | ||
|
|
a7aed3d49e | ||
|
|
9a915a48b5 | ||
|
|
81a3b299f0 | ||
|
|
a4edae6692 | ||
|
|
047f6c409f |
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*_plus.go
|
||||
*-plus.sh
|
||||
*_plus.html
|
||||
*_plus.js
|
||||
*@plus.js
|
||||
*_plus.less
|
||||
*_plus.css
|
||||
*_plus.css.map
|
||||
48
README.md
48
README.md
@@ -1,17 +1,49 @@
|
||||
# GoEdge目标
|
||||
做一款人人用得起的CDN & WAF系统。
|
||||
|
||||

|
||||

|
||||
|
||||
## 特性
|
||||
* `免费` - 开源、免费、自由、开放
|
||||
* `简单` - 架构简单清晰,安装简单,使用简单,运维简单
|
||||
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
|
||||
|
||||
## 功能介绍
|
||||
* 多用户
|
||||
* 日志审计
|
||||
* 集群管理
|
||||
* HTTP/HTTPS/TCP/UDP等协议支持
|
||||
* WAF
|
||||
* 缓存
|
||||
* DNS自动解析
|
||||
* 多域名绑定
|
||||
* 免费证书申请
|
||||
* IP黑白名单
|
||||
* 访问日志
|
||||
* 统计
|
||||
* 内容压缩
|
||||
* Protocol Proxy协议
|
||||
* 本地静态文件
|
||||
* URL跳转
|
||||
* 路由规则
|
||||
* 重写规则
|
||||
* 访问控制
|
||||
* 字符编码
|
||||
* 自定义页面
|
||||
* 自定义HTTP Header
|
||||
* Websocket
|
||||
* WebP自动转换
|
||||
* Fastcgi
|
||||
* 请求限制
|
||||
* 流量限制
|
||||
|
||||
## 在线演示
|
||||
* [http://demo.goedge.cn](http://demo.goedge.cn)
|
||||
|
||||
## 文档
|
||||
* [新手指南](https://edge.teaos.cn/docs/QuickStart/Index.md)
|
||||
* [完整文档](https://edge.teaos.cn/docs)
|
||||
* [开发者指南](https://edge.teaos.cn/docs/Developer/Build.md)
|
||||
* [新手指南](https://goedge.cn/docs/QuickStart/Index.md)
|
||||
* [完整文档](https://goedge.cn/docs)
|
||||
* [开发者指南](https://goedge.cn/docs/Developer/Build.md)
|
||||
|
||||
## 架构
|
||||

|
||||
@@ -22,8 +54,10 @@
|
||||
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
|
||||
|
||||
## 联系我们
|
||||
有什么问题和建议都可以加入QQ群 `659832182`。
|
||||
有什么问题和建议都可以加入 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
|
||||
|
||||
## 企业版
|
||||
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统
|
||||
|
||||
## 感谢
|
||||
* 感谢[JetBrains公司](https://www.jetbrains.com/)提供免费的IDE开发Licence。
|
||||
* 感谢[Gitee](https://gitee.com/)提供国内源代码托管平台
|
||||
* 感谢 [Gitee](https://gitee.com/) 提供国内源代码托管平台
|
||||
3
build/.gitignore
vendored
3
build/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
edge-api/
|
||||
edge-api/
|
||||
build-all-test.sh
|
||||
@@ -1,5 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ROOT=$(dirname $0)
|
||||
|
||||
# build all nodes
|
||||
if [ -f $ROOT"/../../EdgeNode/build/build-all-plus.sh" ]; then
|
||||
echo "=============================="
|
||||
echo "build all edge-node"
|
||||
echo "=============================="
|
||||
cd $ROOT"/../../EdgeNode/build"
|
||||
./build-all-plus.sh
|
||||
cd -
|
||||
fi
|
||||
|
||||
./build.sh linux amd64
|
||||
./build.sh linux 386
|
||||
./build.sh linux arm64
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname $0)
|
||||
NAME="edge-admin"
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
|
||||
if [ -z $OS ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $ARCH ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
|
||||
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
|
||||
# check edge-api
|
||||
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
|
||||
echo "building edge-api v${APINodeVersion} ..."
|
||||
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
|
||||
if [ ! -f $EDGE_API_BUILD_SCRIPT ]; then
|
||||
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
|
||||
exit
|
||||
fi
|
||||
|
||||
cd $ROOT"/../../EdgeAPI/build"
|
||||
echo "=============================="
|
||||
./build.sh $OS $ARCH
|
||||
echo "=============================="
|
||||
cd -
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d $DIST ]; then
|
||||
mkdir $DIST
|
||||
mkdir $DIST/bin
|
||||
mkdir $DIST/configs
|
||||
mkdir $DIST/logs
|
||||
fi
|
||||
|
||||
cp -R $ROOT/../web $DIST/
|
||||
rm -f $DIST/web/tmp/*
|
||||
cp $ROOT/configs/server.template.yaml $DIST/configs/
|
||||
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
|
||||
cp $EDGE_API_ZIP_FILE $DIST/
|
||||
cd $DIST/
|
||||
unzip -q $(basename $EDGE_API_ZIP_FILE)
|
||||
rm -f $(basename $EDGE_API_ZIP_FILE)
|
||||
cd -
|
||||
|
||||
# build
|
||||
echo "building "${NAME}" ..."
|
||||
env GOOS=$OS GOARCH=$GOARCH go build -ldflags="-s -w" -tags demo -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
|
||||
|
||||
# delete hidden files
|
||||
find $DIST -name ".DS_Store" -delete
|
||||
find $DIST -name ".gitignore" -delete
|
||||
|
||||
# zip
|
||||
echo "zip files ..."
|
||||
cd "${DIST}/../" || exit
|
||||
if [ -f "${ZIP}" ]; then
|
||||
rm -f "${ZIP}"
|
||||
fi
|
||||
zip -r -X -q "${ZIP}" ${NAME}/
|
||||
rm -rf ${NAME}
|
||||
cd - || exit
|
||||
|
||||
echo "[done]"
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat $FILE)
|
||||
re="Version[ ]+=[ ]+\"([0-9.]+)\""
|
||||
if [[ $VERSION_DATA =~ $re ]]; then
|
||||
VERSION=${BASH_REMATCH[1]}
|
||||
echo $VERSION
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
build $1 $2
|
||||
110
build/build.sh
110
build/build.sh
@@ -1,66 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname $0)
|
||||
ROOT=$(dirname "$0")
|
||||
JS_ROOT=$ROOT/../web/public/js
|
||||
NAME="edge-admin"
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
TAG=${3}
|
||||
|
||||
if [ -z $OS ]; then
|
||||
if [ -z "$OS" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $ARCH ]; then
|
||||
if [ -z "$ARCH" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
# checking environment
|
||||
echo "checking required commands ..."
|
||||
commands=("zip" "unzip" "go" "find" "sed")
|
||||
for cmd in "${commands[@]}"; do
|
||||
if [ "$(which "${cmd}")" ]; then
|
||||
echo "checking ${cmd}: ok"
|
||||
else
|
||||
echo "checking ${cmd}: not found"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
# check edge-api
|
||||
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
|
||||
|
||||
# build edge-api
|
||||
APINodeVersion=$(lookup-version "$ROOT""/../../EdgeAPI/internal/const/const.go")
|
||||
echo "building edge-api v${APINodeVersion} ..."
|
||||
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
|
||||
if [ ! -f $EDGE_API_BUILD_SCRIPT ]; then
|
||||
if [ ! -f "$EDGE_API_BUILD_SCRIPT" ]; then
|
||||
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
|
||||
exit
|
||||
fi
|
||||
|
||||
cd $ROOT"/../../EdgeAPI/build"
|
||||
cd "$ROOT""/../../EdgeAPI/build" || exit
|
||||
echo "=============================="
|
||||
./build.sh $OS $ARCH
|
||||
./build.sh "$OS" "$ARCH" $TAG
|
||||
echo "=============================="
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
# generate files
|
||||
echo "generating files ..."
|
||||
go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js
|
||||
fi
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d $DIST ]; then
|
||||
mkdir $DIST
|
||||
mkdir $DIST/bin
|
||||
mkdir $DIST/configs
|
||||
mkdir $DIST/logs
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
fi
|
||||
|
||||
cp -R $ROOT/../web $DIST/
|
||||
rm -f $DIST/web/tmp/*
|
||||
cp $ROOT/configs/server.template.yaml $DIST/configs/
|
||||
cp -R "$ROOT"/../web "$DIST"/
|
||||
rm -f "$DIST"/web/tmp/*
|
||||
rm -rf "$DIST"/web/public/js/components
|
||||
rm -f "$DIST"/web/public/js/components.src.js
|
||||
cp "$ROOT"/configs/server.template.yaml "$DIST"/configs/
|
||||
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
|
||||
cp $EDGE_API_ZIP_FILE $DIST/
|
||||
cd $DIST/
|
||||
unzip -q $(basename $EDGE_API_ZIP_FILE)
|
||||
rm -f $(basename $EDGE_API_ZIP_FILE)
|
||||
cd -
|
||||
# change _plus.[ext] to .[ext]
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
echo "converting filenames ..."
|
||||
exts=("html" "js" "css")
|
||||
for ext in "${exts[@]}"; do
|
||||
pattern="*_plus."${ext}
|
||||
find "$DIST"/web/views -type f -name "$pattern" | \
|
||||
while read filename; do
|
||||
mv "${filename}" "${filename/_plus."${ext}"/."${ext}"}"
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-${TAG}-v${APINodeVersion}.zip"
|
||||
cp "$EDGE_API_ZIP_FILE" "$DIST"/
|
||||
cd "$DIST"/ || exit
|
||||
unzip -q "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
cd - || exit
|
||||
|
||||
# build
|
||||
echo "building "${NAME}" ..."
|
||||
env GOOS=$OS GOARCH=$GOARCH go build -ldflags="-s -w" -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
|
||||
echo "building ${NAME} ..."
|
||||
env GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG -ldflags="-s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
|
||||
# delete hidden files
|
||||
find $DIST -name ".DS_Store" -delete
|
||||
find $DIST -name ".gitignore" -delete
|
||||
find "$DIST" -name ".DS_Store" -delete
|
||||
find "$DIST" -name ".gitignore" -delete
|
||||
find "$DIST" -name "*.less" -delete
|
||||
#find "$DIST" -name "*.css.map" -delete
|
||||
#find "$DIST" -name "*.js.map" -delete
|
||||
|
||||
# zip
|
||||
echo "zip files ..."
|
||||
@@ -77,15 +123,15 @@ function build() {
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat $FILE)
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
re="Version[ ]+=[ ]+\"([0-9.]+)\""
|
||||
if [[ $VERSION_DATA =~ $re ]]; then
|
||||
VERSION=${BASH_REMATCH[1]}
|
||||
echo $VERSION
|
||||
echo "$VERSION"
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
build $1 $2
|
||||
build "$1" "$2" "$3"
|
||||
|
||||
2
build/configs/.gitignore
vendored
2
build/configs/.gitignore
vendored
@@ -2,4 +2,4 @@ api.yaml
|
||||
server.yaml
|
||||
api_db.yaml
|
||||
*.pem
|
||||
tip.json
|
||||
*.cache.json
|
||||
16
build/generate.sh
Executable file
16
build/generate.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
JS_ROOT=../web/public/js
|
||||
|
||||
echo "generate component.src.js ..."
|
||||
go run -tags=community ../cmd/edge-admin/main.go generate
|
||||
|
||||
if [ `which uglifyjs` ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
@@ -1 +1 @@
|
||||
这个目录下我们列举了所有需要公开声明的第三方License,如果有遗漏,烦请告知 iwind.liu@gmail.com。再次感谢这些开源软件项目和贡献人员!
|
||||
这个目录下我们列举了所有需要公开声明的第三方License,如果有遗漏,烦请告知 goedge.cdn@gmail.com。再次感谢这些开源软件项目和贡献人员!
|
||||
@@ -1,27 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/apps"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/gen"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
|
||||
_ "github.com/TeaOSLab/EdgeCommon/pkg/langs/messages"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := apps.NewAppCmd().
|
||||
var app = apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset]").
|
||||
Usage(teaconst.ProcessName+" [-h|-v|start|stop|restart|service|daemon|reset|recover|demo|upgrade]").
|
||||
Usage(teaconst.ProcessName+" [dev|prod]").
|
||||
Option("-h", "show this help").
|
||||
Option("-v", "show version").
|
||||
Option("start", "start the service").
|
||||
Option("stop", "stop the service").
|
||||
Option("restart", "restart the service").
|
||||
Option("service", "register service into systemd").
|
||||
Option("daemon", "start the service with daemon").
|
||||
Option("reset", "reset configs")
|
||||
Option("reset", "reset configs").
|
||||
Option("recover", "enter recovery mode").
|
||||
Option("demo", "switch to demo mode").
|
||||
Option("dev", "switch to 'dev' mode").
|
||||
Option("prod", "switch to 'prod' mode").
|
||||
Option("upgrade [--url=URL]", "upgrade from official site or an url")
|
||||
|
||||
app.On("daemon", func() {
|
||||
nodes.NewAdminNode().Daemon()
|
||||
@@ -40,10 +59,127 @@ func main() {
|
||||
fmt.Println("[ERROR]reset failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// reset local api
|
||||
var apiNodeExe = Tea.Root + "/edge-api/bin/edge-api"
|
||||
_, err = os.Stat(apiNodeExe)
|
||||
if err == nil {
|
||||
var cmd = exec.Command(apiNodeExe, "reset")
|
||||
var stderr = &bytes.Buffer{}
|
||||
cmd.Stderr = stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("reset api node failed: " + stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("recover", func() {
|
||||
sock := gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
_, err := sock.Send(&gosock.Command{Code: "recover"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]enter recovery mode failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("enter recovery mode successfully")
|
||||
})
|
||||
app.On("demo", func() {
|
||||
sock := gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
reply, err := sock.Send(&gosock.Command{Code: "demo"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]change demo mode failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
var isDemo = maps.NewMap(reply.Params).GetBool("isDemo")
|
||||
if isDemo {
|
||||
fmt.Println("change demo mode to: on")
|
||||
} else {
|
||||
fmt.Println("change demo mode to: off")
|
||||
}
|
||||
})
|
||||
app.On("generate", func() {
|
||||
err := gen.Generate()
|
||||
if err != nil {
|
||||
fmt.Println("generate failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
})
|
||||
app.On("dev", func() {
|
||||
var env = "dev"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{
|
||||
Code: env,
|
||||
Params: nil,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("failed to switch to '" + env + "': " + err.Error())
|
||||
} else {
|
||||
fmt.Println("switch to '" + env + "' ok")
|
||||
}
|
||||
})
|
||||
app.On("prod", func() {
|
||||
var env = "prod"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{
|
||||
Code: env,
|
||||
Params: nil,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("failed to switch to '" + env + "': " + err.Error())
|
||||
} else {
|
||||
fmt.Println("switch to '" + env + "' ok")
|
||||
}
|
||||
})
|
||||
app.On("upgrade", func() {
|
||||
var downloadURL = ""
|
||||
var flagSet = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flagSet.StringVar(&downloadURL, "url", "", "new version download url")
|
||||
_ = flagSet.Parse(os.Args[2:])
|
||||
|
||||
var manager = utils.NewUpgradeManager("admin", downloadURL)
|
||||
log.Println("checking latest version ...")
|
||||
var ticker = time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
var lastProgress float32 = 0
|
||||
var isStarted = false
|
||||
for range ticker.C {
|
||||
if manager.IsDownloading() {
|
||||
if !isStarted {
|
||||
log.Println("start downloading v" + manager.NewVersion() + " ...")
|
||||
isStarted = true
|
||||
}
|
||||
var progress = manager.Progress()
|
||||
if progress >= 0 {
|
||||
if progress == 0 || progress == 1 || progress-lastProgress >= 0.1 {
|
||||
lastProgress = progress
|
||||
log.Println(fmt.Sprintf("%.2f%%", manager.Progress()*100))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := manager.Start()
|
||||
if err != nil {
|
||||
log.Println("upgrade failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
log.Println("finished!")
|
||||
log.Println("restarting ...")
|
||||
app.RunRestart()
|
||||
})
|
||||
app.Run(func() {
|
||||
adminNode := nodes.NewAdminNode()
|
||||
var adminNode = nodes.NewAdminNode()
|
||||
adminNode.Run()
|
||||
})
|
||||
}
|
||||
|
||||
2
dist/.gitignore
vendored
2
dist/.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
*.zip
|
||||
shield-admin
|
||||
edge-admin
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 225 KiB |
1
docker/.gitignore
vendored
Normal file
1
docker/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.zip
|
||||
37
docker/Dockerfile
Normal file
37
docker/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="goedge.cdn@gmail.com"
|
||||
ENV TZ "Asia/Shanghai"
|
||||
ENV VERSION 1.2.4
|
||||
ENV ROOT_DIR /usr/local/goedge
|
||||
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
|
||||
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
#ENV TAR_URL "http://192.168.2.60:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip" # your local repository
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
RUN apk add wget
|
||||
RUN mkdir ${ROOT_DIR}; \
|
||||
cd ${ROOT_DIR}; \
|
||||
wget ${TAR_URL} -O ${TAR_FILE}; \
|
||||
apk add unzip; \
|
||||
unzip ${TAR_FILE}; \
|
||||
rm -f ${TAR_FILE}
|
||||
|
||||
RUN apk add mysql mysql-client; \
|
||||
sed -e "s/\[mysqld\]/\[mysqld\]\n\ndatadir=\/var\/lib\/mysql\nport=3306\ninnodb_flush_log_at_trx_commit=2\nmax_connections=256\nmax_prepared_stmt_count=65535\nbinlog_cache_size=1M\nbinlog_stmt_cache_size=1M\nthread_cache_size=32\nbinlog_expire_logs_seconds=1209600\n\n/" /etc/my.cnf > /tmp/my.cnf; \
|
||||
cp /tmp/my.cnf /etc/my.cnf; \
|
||||
sed -e "s/skip-networking/#skip-networking/" /etc/my.cnf.d/mariadb-server.cnf > /tmp/mariadb-server.cnf; \
|
||||
cp /tmp/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf; \
|
||||
mysql_install_db --user=mysql
|
||||
RUN mysqld_safe --user=mysql & \
|
||||
sleep 5; \
|
||||
mysql -uroot -hlocalhost --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"
|
||||
|
||||
RUN echo -e "#!/usr/bin/env sh\n\nmysqld_safe --user=mysql &\n/usr/local/goedge/edge-admin/bin/edge-admin\n" > ${ROOT_DIR}/run.sh; \
|
||||
chmod u+x ${ROOT_DIR}/run.sh
|
||||
|
||||
EXPOSE 7788
|
||||
EXPOSE 8001
|
||||
EXPOSE 3306
|
||||
|
||||
ENTRYPOINT [ "/usr/local/goedge/run.sh" ]
|
||||
5
docker/build.sh
Executable file
5
docker/build.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker build --no-cache -t goedge/edge-admin:${VERSION} .
|
||||
5
docker/run.sh
Executable file
5
docker/run.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker run -d -p 7788:7788 -p 8001:8001 -p 3306:3306 --name edge-admin goedge/edge-admin:${VERSION}
|
||||
55
go.mod
55
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/TeaOSLab/EdgeAdmin
|
||||
|
||||
go 1.15
|
||||
go 1.18
|
||||
|
||||
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
|
||||
@@ -8,19 +8,48 @@ require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible // indirect
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/quic-go/quic-go v0.36.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.5
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tealeg/xlsx/v3 v3.2.3
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/sys v0.9.0
|
||||
google.golang.org/grpc v1.45.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/frankban/quicktest v1.11.3 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
|
||||
github.com/tdewolff/minify/v2 v2.12.7 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
174
go.sum
174
go.sum
@@ -1,40 +1,58 @@
|
||||
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=
|
||||
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/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
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-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-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY=
|
||||
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/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=
|
||||
@@ -44,50 +62,62 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
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 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
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/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
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.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/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 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f h1:r2O8PONj/KiuZjJHVHn7KlCePUIjNtgAmvLfgRafQ8o=
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f h1:xo6XmXLtveKcwcZAXV6VMxkWNzy/2dStfHEnyowsGAE=
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
@@ -95,105 +125,140 @@ github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
|
||||
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/quic-go v0.36.0 h1:JIrO7p7Ug6hssFcARjWDiqS2RAKJHCiwPxBAA989rbI=
|
||||
github.com/quic-go/quic-go v0.36.0/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc=
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
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.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
|
||||
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ=
|
||||
github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
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.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
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-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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-20180906233101-161cd47e91fd/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
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-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
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-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/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-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -201,19 +266,19 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus=
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
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=
|
||||
@@ -222,26 +287,27 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
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.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/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-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -2,29 +2,35 @@ package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App命令帮助
|
||||
// AppCmd App命令帮助
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
usage string
|
||||
usages []string
|
||||
options []*CommandHelpOption
|
||||
appendStrings []string
|
||||
|
||||
directives []*Directive
|
||||
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{}
|
||||
return &AppCmd{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandHelpOption struct {
|
||||
@@ -32,25 +38,25 @@ type CommandHelpOption struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
// 产品
|
||||
// Product 产品
|
||||
func (this *AppCmd) Product(product string) *AppCmd {
|
||||
this.product = product
|
||||
return this
|
||||
}
|
||||
|
||||
// 版本
|
||||
// Version 版本
|
||||
func (this *AppCmd) Version(version string) *AppCmd {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
// Usage 使用方法
|
||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||
this.usage = usage
|
||||
this.usages = append(this.usages, usage)
|
||||
return this
|
||||
}
|
||||
|
||||
// 选项
|
||||
// Option 选项
|
||||
func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
this.options = append(this.options, &CommandHelpOption{
|
||||
Code: code,
|
||||
@@ -59,18 +65,20 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
return this
|
||||
}
|
||||
|
||||
// 附加内容
|
||||
// Append 附加内容
|
||||
func (this *AppCmd) Append(appendString string) *AppCmd {
|
||||
this.appendStrings = append(this.appendStrings, appendString)
|
||||
return this
|
||||
}
|
||||
|
||||
// 打印
|
||||
// Print 打印
|
||||
func (this *AppCmd) Print() {
|
||||
fmt.Println(this.product + " v" + this.version)
|
||||
|
||||
usage := this.usage
|
||||
fmt.Println("Usage:", "\n "+usage)
|
||||
fmt.Println("Usage:")
|
||||
for _, usage := range this.usages {
|
||||
fmt.Println(" " + usage)
|
||||
}
|
||||
|
||||
if len(this.options) > 0 {
|
||||
fmt.Println("")
|
||||
@@ -104,7 +112,7 @@ func (this *AppCmd) Print() {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加指令
|
||||
// On 添加指令
|
||||
func (this *AppCmd) On(arg string, callback func()) {
|
||||
this.directives = append(this.directives, &Directive{
|
||||
Arg: arg,
|
||||
@@ -112,10 +120,10 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
})
|
||||
}
|
||||
|
||||
// 运行
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "-v", "version", "-version", "--version":
|
||||
@@ -131,7 +139,7 @@ func (this *AppCmd) Run(main func()) {
|
||||
this.runStop()
|
||||
return
|
||||
case "restart":
|
||||
this.runRestart()
|
||||
this.RunRestart()
|
||||
return
|
||||
case "status":
|
||||
this.runStatus()
|
||||
@@ -151,11 +159,8 @@ func (this *AppCmd) Run(main func()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 记录PID
|
||||
_ = this.writePid()
|
||||
|
||||
// 日志
|
||||
writer := new(LogWriter)
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
@@ -165,7 +170,7 @@ func (this *AppCmd) Run(main func()) {
|
||||
|
||||
// 版本号
|
||||
func (this *AppCmd) runVersion() {
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH+")")
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
|
||||
}
|
||||
|
||||
// 帮助
|
||||
@@ -175,9 +180,9 @@ func (this *AppCmd) runHelp() {
|
||||
|
||||
// 启动
|
||||
func (this *AppCmd) runStart() {
|
||||
proc := this.checkPid()
|
||||
if proc != nil {
|
||||
fmt.Println(this.product+" already started, pid:", proc.Pid)
|
||||
var pid = this.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(this.product+" already started, pid:", pid)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -193,22 +198,19 @@ func (this *AppCmd) runStart() {
|
||||
|
||||
// 停止
|
||||
func (this *AppCmd) runStop() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
// 停止进程
|
||||
_ = proc.Signal(syscall.SIGQUIT)
|
||||
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
|
||||
|
||||
// 在Windows上经常不能及时释放资源
|
||||
_ = DeletePid(Tea.Root + "/bin/pid")
|
||||
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
|
||||
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
|
||||
}
|
||||
|
||||
// 重启
|
||||
func (this *AppCmd) runRestart() {
|
||||
// RunRestart 重启
|
||||
func (this *AppCmd) RunRestart() {
|
||||
this.runStop()
|
||||
time.Sleep(1 * time.Second)
|
||||
this.runStart()
|
||||
@@ -216,20 +218,24 @@ func (this *AppCmd) runRestart() {
|
||||
|
||||
// 状态
|
||||
func (this *AppCmd) runStatus() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
} else {
|
||||
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(this.product + " is running, pid: " + types.String(pid))
|
||||
}
|
||||
|
||||
// 检查PID
|
||||
func (this *AppCmd) checkPid() *os.Process {
|
||||
return CheckPid(Tea.Root + "/bin/pid")
|
||||
}
|
||||
// 获取当前的PID
|
||||
func (this *AppCmd) getPID() int {
|
||||
if !this.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 写入PID
|
||||
func (this *AppCmd) writePid() error {
|
||||
return WritePid(Tea.Root + "/bin/pid")
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
@@ -1,51 +1,108 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/utils/time"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogWriter struct {
|
||||
fileAppender *files.Appender
|
||||
fp *os.File
|
||||
c chan string
|
||||
}
|
||||
|
||||
func (this *LogWriter) Init() {
|
||||
// 创建目录
|
||||
dir := files.NewFile(Tea.LogDir())
|
||||
var dir = files.NewFile(Tea.LogDir())
|
||||
if !dir.Exists() {
|
||||
err := dir.Mkdir()
|
||||
if err != nil {
|
||||
log.Println("[error]" + err.Error())
|
||||
log.Println("[LOG]create log dir failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
logFile := files.NewFile(Tea.LogFile("run.log"))
|
||||
|
||||
// 打开要写入的日志文件
|
||||
appender, err := logFile.Appender()
|
||||
var logPath = Tea.LogFile("run.log")
|
||||
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
log.Println("[LOG]open log file failed: " + err.Error())
|
||||
} else {
|
||||
this.fileAppender = appender
|
||||
this.fp = fp
|
||||
}
|
||||
|
||||
this.c = make(chan string, 1024)
|
||||
|
||||
// 异步写入文件
|
||||
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
|
||||
if fp != nil {
|
||||
goman.New(func() {
|
||||
var totalSize int64 = 0
|
||||
stat, err := fp.Stat()
|
||||
if err == nil {
|
||||
totalSize = stat.Size()
|
||||
}
|
||||
|
||||
for message := range this.c {
|
||||
totalSize += int64(len(message))
|
||||
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
if err != nil {
|
||||
log.Println("[LOG]write log failed: " + err.Error())
|
||||
} else {
|
||||
// 如果太大则Truncate
|
||||
if totalSize > maxFileSize {
|
||||
_ = fp.Truncate(0)
|
||||
totalSize = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *LogWriter) Write(message string) {
|
||||
log.Println(message)
|
||||
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
|
||||
if backgroundEnv != "on" {
|
||||
// 文件和行号
|
||||
var file string
|
||||
var line int
|
||||
if Tea.IsTesting() {
|
||||
var callDepth = 3
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(callDepth)
|
||||
if ok {
|
||||
file = this.packagePath(file)
|
||||
}
|
||||
}
|
||||
|
||||
if this.fileAppender != nil {
|
||||
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
if err != nil {
|
||||
log.Println("[error]" + err.Error())
|
||||
if len(file) > 0 {
|
||||
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
|
||||
} else {
|
||||
log.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
this.c <- message
|
||||
}
|
||||
|
||||
func (this *LogWriter) Close() {
|
||||
if this.fileAppender != nil {
|
||||
_ = this.fileAppender.Close()
|
||||
if this.fp != nil {
|
||||
_ = this.fp.Close()
|
||||
}
|
||||
|
||||
close(this.c)
|
||||
}
|
||||
|
||||
func (this *LogWriter) packagePath(path string) string {
|
||||
var pieces = strings.Split(path, "/")
|
||||
if len(pieces) >= 2 {
|
||||
return strings.Join(pieces[len(pieces)-2:], "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var pidFileList = []*os.File{}
|
||||
|
||||
// 检查Pid
|
||||
func CheckPid(path string) *os.Process {
|
||||
// windows上打开的文件是不能删除的
|
||||
if runtime.GOOS == "windows" {
|
||||
if os.Remove(path) == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
// 是否能取得Lock
|
||||
err = LockFile(file)
|
||||
if err == nil {
|
||||
_ = UnlockFile(file)
|
||||
return nil
|
||||
}
|
||||
|
||||
pidBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
pid := types.Int(string(pidBytes))
|
||||
|
||||
if pid <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
proc, _ := os.FindProcess(pid)
|
||||
return proc
|
||||
}
|
||||
|
||||
// 写入Pid
|
||||
func WritePid(path string) error {
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 写入Ppid
|
||||
func WritePpid(path string) error {
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除Pid
|
||||
func DeletePid(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fp := range pidFileList {
|
||||
_ = UnlockFile(fp)
|
||||
_ = fp.Close()
|
||||
}
|
||||
return os.Remove(path)
|
||||
}
|
||||
@@ -3,8 +3,11 @@ package configloaders
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
@@ -19,8 +22,10 @@ const (
|
||||
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
|
||||
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
|
||||
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
|
||||
AdminModuleCodePlan AdminModuleCode = "plan" // 套餐
|
||||
AdminModuleCodeLog AdminModuleCode = "log" // 日志
|
||||
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
|
||||
AdminModuleCodeTicket AdminModuleCode = "ticket" // 工单
|
||||
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
|
||||
)
|
||||
|
||||
@@ -44,6 +49,8 @@ func loadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
|
||||
list := &AdminModuleList{
|
||||
IsSuper: m.IsSuper,
|
||||
Fullname: m.Fullname,
|
||||
Theme: m.Theme,
|
||||
Lang: m.Lang,
|
||||
}
|
||||
|
||||
for _, pbModule := range m.Modules {
|
||||
@@ -132,60 +139,131 @@ func FindAdminFullname(adminId int64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindAdminTheme 查找某个管理员选择的风格
|
||||
func FindAdminTheme(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Theme
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminTheme 设置某个管理员的风格
|
||||
func UpdateAdminTheme(adminId int64, theme string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
// FindAdminLang 查找某个管理员选择的语言
|
||||
func FindAdminLang(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Lang
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func FindAdminLangForAction(actionPtr actions.ActionWrapper) (langCode langs.LangCode) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var adminId = actionPtr.Object().Session().GetInt64(teaconst.SessionAdminId)
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
var result = ""
|
||||
if ok {
|
||||
result = list.Lang
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
result = langs.ParseLangFromAction(actionPtr)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AllModuleMaps 所有权限列表
|
||||
func AllModuleMaps() []maps.Map {
|
||||
m := []maps.Map{
|
||||
func AllModuleMaps(langCode string) []maps.Map {
|
||||
var m = []maps.Map{
|
||||
{
|
||||
"name": "看板",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Dashboard),
|
||||
"code": AdminModuleCodeDashboard,
|
||||
"url": "/dashboard",
|
||||
},
|
||||
{
|
||||
"name": "网站服务",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Servers),
|
||||
"code": AdminModuleCodeServer,
|
||||
"url": "/servers",
|
||||
},
|
||||
{
|
||||
"name": "边缘节点",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Nodes),
|
||||
"code": AdminModuleCodeNode,
|
||||
"url": "/clusters",
|
||||
},
|
||||
{
|
||||
"name": "域名解析",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_DNS),
|
||||
"code": AdminModuleCodeDNS,
|
||||
"url": "/dns",
|
||||
},
|
||||
}
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, maps.Map{
|
||||
"name": "域名服务",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_NS),
|
||||
"code": AdminModuleCodeNS,
|
||||
"url": "/ns",
|
||||
})
|
||||
}
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": "平台用户",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Users),
|
||||
"code": AdminModuleCodeUser,
|
||||
"url": "/users",
|
||||
},
|
||||
{
|
||||
"name": "系统用户",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Admins),
|
||||
"code": AdminModuleCodeAdmin,
|
||||
"url": "/admins",
|
||||
},
|
||||
{
|
||||
"name": "财务管理",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Finance),
|
||||
"code": AdminModuleCodeFinance,
|
||||
"url": "/finance",
|
||||
},
|
||||
}...)
|
||||
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Plans),
|
||||
"code": AdminModuleCodePlan,
|
||||
"url": "/plans",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Tickets),
|
||||
"code": AdminModuleCodeTicket,
|
||||
"url": "/tickets",
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": "日志审计",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Logs),
|
||||
"code": AdminModuleCodeLog,
|
||||
"url": "/log",
|
||||
},
|
||||
{
|
||||
"name": "系统设置",
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Settings),
|
||||
"code": AdminModuleCodeSetting,
|
||||
"url": "/settings",
|
||||
},
|
||||
|
||||
@@ -6,6 +6,8 @@ type AdminModuleList struct {
|
||||
IsSuper bool
|
||||
Modules []*systemconfigs.AdminModule
|
||||
Fullname string
|
||||
Theme string
|
||||
Lang string
|
||||
}
|
||||
|
||||
func (this *AdminModuleList) Allow(module string) bool {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestLoadAdminModuleMapping(t *testing.T) {
|
||||
m, err := LoadAdminModuleMapping()
|
||||
m, err := loadAdminModuleMapping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ package configloaders
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil
|
||||
|
||||
const (
|
||||
AdminUISettingName = "adminUIConfig"
|
||||
)
|
||||
|
||||
func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -28,6 +28,15 @@ func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func ReloadAdminUIConfig() error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
sharedAdminUIConfig = nil
|
||||
_, err := loadAdminUIConfig()
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -41,7 +50,7 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: AdminUISettingName,
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -49,10 +58,13 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
}
|
||||
sharedAdminUIConfig = uiConfig
|
||||
|
||||
// timezone
|
||||
updateTimeZone(uiConfig)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 是否显示财务信息
|
||||
// ShowFinance 是否显示财务信息
|
||||
func ShowFinance() bool {
|
||||
config, _ := LoadAdminUIConfig()
|
||||
if config != nil && !config.ShowFinance {
|
||||
@@ -70,7 +82,7 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: AdminUISettingName,
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -80,23 +92,39 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.AdminUIConfig{}
|
||||
var config = &systemconfigs.AdminUIConfig{}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedAdminUIConfig = defaultAdminUIConfig()
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
// timezone
|
||||
updateTimeZone(config)
|
||||
|
||||
sharedAdminUIConfig = config
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
|
||||
return &systemconfigs.AdminUIConfig{
|
||||
ProductName: "GoEdge",
|
||||
AdminSystemName: "GoEdge管理员系统",
|
||||
ProductName: langs.DefaultMessage(codes.AdminUI_DefaultProductName),
|
||||
AdminSystemName: langs.DefaultMessage(codes.AdminUI_DefaultSystemName),
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
DefaultPageSize: 10,
|
||||
TimeZone: nodeconfigs.DefaultTimeZoneLocation,
|
||||
}
|
||||
}
|
||||
|
||||
// 修改时区
|
||||
func updateTimeZone(config *systemconfigs.AdminUIConfig) {
|
||||
if len(config.TimeZone) > 0 {
|
||||
location, err := time.LoadLocation(config.TimeZone)
|
||||
if err == nil && time.Local != location {
|
||||
time.Local = location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
func TestLoadUIConfig(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
before := time.Now()
|
||||
config, err := LoadUIConfig()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func TestLoadUIConfig(t *testing.T) {
|
||||
|
||||
func TestLoadUIConfig2(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
config, err := LoadUIConfig()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
||||
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
@@ -83,7 +83,12 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.SecurityConfig{}
|
||||
var config = &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[SECURITY_MANAGER]" + err.Error())
|
||||
@@ -100,7 +105,9 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
|
||||
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
|
||||
return &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
|
||||
|
||||
const (
|
||||
UserUISettingName = "userUIConfig"
|
||||
)
|
||||
|
||||
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadUserUIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(uiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: UserUISettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedUserUIConfig = uiConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
if sharedUserUIConfig != nil {
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: UserUISettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUserUIConfig = defaultUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.UserUIConfig{}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedUserUIConfig = defaultUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
sharedUserUIConfig = config
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
func defaultUserUIConfig() *systemconfigs.UserUIConfig {
|
||||
return &systemconfigs.UserUIConfig{
|
||||
ProductName: "GoEdge",
|
||||
UserSystemName: "GoEdge用户系统",
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,8 @@ package configs
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"io/ioutil"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
@@ -12,7 +11,8 @@ import (
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
RPC struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
} `yaml:"rpc"`
|
||||
NodeId string `yaml:"nodeId"`
|
||||
Secret string `yaml:"secret"`
|
||||
@@ -21,9 +21,9 @@ type APIConfig struct {
|
||||
// LoadAPIConfig 加载API配置
|
||||
func LoadAPIConfig() (*APIConfig, error) {
|
||||
// 候选文件
|
||||
localFile := Tea.ConfigFile("api.yaml")
|
||||
isFromLocal := false
|
||||
paths := []string{localFile}
|
||||
var localFile = Tea.ConfigFile("api.yaml")
|
||||
var isFromLocal = false
|
||||
var paths = []string{localFile}
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/api.yaml")
|
||||
@@ -33,7 +33,7 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
var data []byte
|
||||
var err error
|
||||
for _, path := range paths {
|
||||
data, err = ioutil.ReadFile(path)
|
||||
data, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
if path == localFile {
|
||||
isFromLocal = true
|
||||
@@ -45,7 +45,7 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &APIConfig{}
|
||||
var config = &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -53,7 +53,7 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
|
||||
if !isFromLocal {
|
||||
// 恢复文件
|
||||
_ = ioutil.WriteFile(localFile, data, 0666)
|
||||
_ = os.WriteFile(localFile, data, 0666)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
@@ -61,10 +61,11 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
|
||||
// ResetAPIConfig 重置配置
|
||||
func ResetAPIConfig() error {
|
||||
filename := "api.yaml"
|
||||
var filename = "api.yaml"
|
||||
|
||||
// 重置 configs/api.yaml
|
||||
{
|
||||
configFile := Tea.ConfigFile(filename)
|
||||
var configFile = Tea.ConfigFile(filename)
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
@@ -77,7 +78,7 @@ func ResetAPIConfig() error {
|
||||
// 重置 ~/.edge-admin/api.yaml
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
configFile := homeDir + "/." + teaconst.ProcessName + "/" + filename
|
||||
var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
@@ -89,7 +90,7 @@ func ResetAPIConfig() error {
|
||||
|
||||
// 重置 /etc/edge-admin/api.yaml
|
||||
{
|
||||
configFile := "/etc/" + teaconst.ProcessName + "/" + filename
|
||||
var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
@@ -108,38 +109,61 @@ func (this *APIConfig) WriteFile(path string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(path, data, 0666)
|
||||
|
||||
err = os.WriteFile(path, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
|
||||
// 写入 ~/.edge-admin/
|
||||
filename := filepath.Base(path)
|
||||
// 这个用来判断用户是否为重装,所以比较重要
|
||||
var filename = filepath.Base(path)
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
dir := homeDir + "/." + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入 /etc/edge-admin
|
||||
{
|
||||
dir := "/etc/" + teaconst.ProcessName
|
||||
var dir = "/etc/" + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone 克隆当前配置
|
||||
func (this *APIConfig) Clone() *APIConfig {
|
||||
return &APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{},
|
||||
NodeId: this.NodeId,
|
||||
Secret: this.Secret,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,7 @@ func TestLoadAPIConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIConfig_WriteFile(t *testing.T) {
|
||||
config := &APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{},
|
||||
NodeId: "1",
|
||||
Secret: "2",
|
||||
}
|
||||
config := &APIConfig{}
|
||||
err := config.WriteFile("/tmp/api_config.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
8
internal/const/build.go
Normal file
8
internal/const/build.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = true
|
||||
const BuildPlus = false
|
||||
const Tag = "community"
|
||||
@@ -1,7 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.2.4"
|
||||
Version = "1.2.5"
|
||||
|
||||
APINodeVersion = "1.2.5"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
@@ -12,8 +14,9 @@ const (
|
||||
EncryptKey = "8f983f4d69b83aaa0d74b21a212f6967"
|
||||
EncryptMethod = "aes-256-cfb"
|
||||
|
||||
ErrServer = "服务器出了点小问题,请联系技术人员处理。"
|
||||
CookieSID = "edgesid"
|
||||
CookieSID = "edgesid"
|
||||
SessionAdminId = "adminId"
|
||||
|
||||
SystemdServiceName = "edge-admin"
|
||||
UpdatesURL = "https://goedge.cn/api/boot/versions?os=${os}&arch=${arch}&version=${version}"
|
||||
)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build demo
|
||||
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
IsDemo = true
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build !demo
|
||||
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
IsDemo = false
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
// IsPlus 是否为企业版
|
||||
var IsPlus = false
|
||||
|
||||
32
internal/const/vars.go
Normal file
32
internal/const/vars.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package teaconst
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
IsRecoverMode = false
|
||||
|
||||
IsDemoMode = false
|
||||
ErrorDemoOperation = "DEMO模式下无法进行创建、修改、删除等操作"
|
||||
|
||||
NewVersionCode = "" // 有新的版本
|
||||
NewVersionDownloadURL = "" // 新版本下载地址
|
||||
|
||||
IsMain = checkMain()
|
||||
)
|
||||
|
||||
// 检查是否为主程序
|
||||
func checkMain() bool {
|
||||
if len(os.Args) == 1 ||
|
||||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
|
||||
return true
|
||||
}
|
||||
exe, _ := os.Executable()
|
||||
return strings.HasSuffix(exe, ".test") ||
|
||||
strings.HasSuffix(exe, ".test.exe") ||
|
||||
strings.Contains(exe, "___")
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 生成Token
|
||||
// Generate 生成Token
|
||||
func Generate() string {
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
@@ -23,7 +23,7 @@ func Generate() string {
|
||||
return token
|
||||
}
|
||||
|
||||
// 校验Token
|
||||
// Validate 校验Token
|
||||
func Validate(token string) (b bool) {
|
||||
if len(token) == 0 {
|
||||
return
|
||||
|
||||
140
internal/gen/generate.go
Normal file
140
internal/gen/generate.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Generate() error {
|
||||
err := generateComponentsJSFile()
|
||||
if err != nil {
|
||||
return errors.New("generate 'components.src.js' failed: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 生成Javascript文件
|
||||
func generateComponentsJSFile() error {
|
||||
var buffer = bytes.NewBuffer([]byte{})
|
||||
|
||||
var webRoot string
|
||||
if Tea.IsTesting() {
|
||||
webRoot = Tea.Root + "/../web/public/js/components/"
|
||||
} else {
|
||||
webRoot = Tea.Root + "/web/public/js/components/"
|
||||
}
|
||||
f := files.NewFile(webRoot)
|
||||
|
||||
f.Range(func(file *files.File) {
|
||||
if !file.IsFile() {
|
||||
return
|
||||
}
|
||||
if file.Ext() != ".js" {
|
||||
return
|
||||
}
|
||||
data, err := file.ReadAll()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
buffer.Write(data)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
})
|
||||
|
||||
// 条件组件
|
||||
typesJSON, err := json.Marshal(condutils.ReadAllAvailableCondTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request cond types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
|
||||
buffer.Write(typesJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
}
|
||||
|
||||
// 条件操作符
|
||||
requestOperatorsJSON, err := json.Marshal(shared.AllRequestOperators())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
|
||||
buffer.Write(requestOperatorsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
}
|
||||
|
||||
// 请求变量
|
||||
requestVariablesJSON, err := json.Marshal(shared.DefaultRequestVariables())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request variables failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_VARIABLES = ")
|
||||
buffer.Write(requestVariablesJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
}
|
||||
|
||||
// 指标
|
||||
metricHTTPKeysJSON, err := json.Marshal(serverconfigs.FindAllMetricKeyDefinitions(serverconfigs.MetricItemCategoryHTTP))
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal metric http keys failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
|
||||
buffer.Write(metricHTTPKeysJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值项目
|
||||
ipAddrThresholdItemsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdItems())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold items failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
|
||||
buffer.Write(ipAddrThresholdItemsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值动作
|
||||
ipAddrThresholdActionsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdActions())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold actions failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
|
||||
buffer.Write(ipAddrThresholdActionsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF操作符
|
||||
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
|
||||
buffer.Write(wafOperatorsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
}
|
||||
|
||||
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fp, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
13
internal/gen/generate_test.go
Normal file
13
internal/gen/generate_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
err := Generate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
12
internal/goman/instance.go
Normal file
12
internal/goman/instance.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import "time"
|
||||
|
||||
type Instance struct {
|
||||
Id uint64
|
||||
CreatedTime time.Time
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
81
internal/goman/lib.go
Normal file
81
internal/goman/lib.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var locker = &sync.Mutex{}
|
||||
var instanceMap = map[uint64]*Instance{} // id => *Instance
|
||||
var instanceId = uint64(0)
|
||||
|
||||
// New 新创建goroutine
|
||||
func New(f func()) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f()
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// NewWithArgs 创建带有参数的goroutine
|
||||
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f(args...)
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// List 列出所有正在运行goroutine
|
||||
func List() []*Instance {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var result = []*Instance{}
|
||||
for _, instance := range instanceMap {
|
||||
result = append(result, instance)
|
||||
}
|
||||
return result
|
||||
}
|
||||
28
internal/goman/lib_test.go
Normal file
28
internal/goman/lib_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
New(func() {
|
||||
t.Log("Hello")
|
||||
|
||||
t.Log(List())
|
||||
})
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(List())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestNewWithArgs(t *testing.T) {
|
||||
NewWithArgs(func(args ...interface{}) {
|
||||
t.Log(args[0], args[1])
|
||||
}, 1, 2)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
@@ -1,23 +1,27 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/sessions"
|
||||
"io/ioutil"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
@@ -25,6 +29,7 @@ import (
|
||||
var SharedAdminNode *AdminNode = nil
|
||||
|
||||
type AdminNode struct {
|
||||
sock *gosock.Sock
|
||||
subPIDs []int
|
||||
}
|
||||
|
||||
@@ -36,13 +41,13 @@ func (this *AdminNode) Run() {
|
||||
SharedAdminNode = this
|
||||
|
||||
// 启动管理界面
|
||||
secret := this.genSecret()
|
||||
var secret = this.genSecret()
|
||||
configs.Secret = secret
|
||||
|
||||
// 本地Sock
|
||||
err := this.listenSock()
|
||||
if err != nil {
|
||||
logs.Println("NODE" + err.Error())
|
||||
logs.Println("[NODE]", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -56,6 +61,9 @@ func (this *AdminNode) Run() {
|
||||
return
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
this.addPortsToFirewall()
|
||||
|
||||
// 监听信号
|
||||
sigQueue := make(chan os.Signal)
|
||||
signal.Notify(sigQueue, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)
|
||||
@@ -77,23 +85,31 @@ func (this *AdminNode) Run() {
|
||||
// 启动API节点
|
||||
this.startAPINode()
|
||||
|
||||
// 启动IP库
|
||||
this.startIPLibrary()
|
||||
|
||||
// 启动Web服务
|
||||
sessionManager, err := NewSessionManager()
|
||||
if err != nil {
|
||||
log.Fatal("start session failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
TeaGo.NewServer(false).
|
||||
AccessLog(false).
|
||||
EndAll().
|
||||
Session(sessions.NewFileSessionManager(86400, secret), teaconst.CookieSID).
|
||||
Session(sessionManager, teaconst.CookieSID).
|
||||
ReadHeaderTimeout(3 * time.Second).
|
||||
ReadTimeout(600 * time.Second).
|
||||
ReadTimeout(1200 * time.Second).
|
||||
Start()
|
||||
}
|
||||
|
||||
// Daemon 实现守护进程
|
||||
func (this *AdminNode) Daemon() {
|
||||
path := os.TempDir() + "/edge-admin.sock"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
isDebug := lists.ContainsString(os.Args, "debug")
|
||||
isDebug = true
|
||||
for {
|
||||
conn, err := net.DialTimeout("unix", path, 1*time.Second)
|
||||
conn, err := sock.Dial()
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]starting ...")
|
||||
@@ -165,9 +181,9 @@ func (this *AdminNode) checkServer() error {
|
||||
if os.IsNotExist(err) {
|
||||
// 创建文件
|
||||
templateFile := Tea.ConfigFile("server.template.yaml")
|
||||
data, err := ioutil.ReadFile(templateFile)
|
||||
data, err := os.ReadFile(templateFile)
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(configFile, data, 0666)
|
||||
err = os.WriteFile(configFile, data, 0666)
|
||||
if err != nil {
|
||||
return errors.New("create config file failed: " + err.Error())
|
||||
}
|
||||
@@ -187,7 +203,7 @@ https:
|
||||
cert: ""
|
||||
key: ""
|
||||
`
|
||||
err = ioutil.WriteFile(configFile, []byte(templateYAML), 0666)
|
||||
err = os.WriteFile(configFile, []byte(templateYAML), 0666)
|
||||
if err != nil {
|
||||
return errors.New("create config file failed: " + err.Error())
|
||||
}
|
||||
@@ -199,6 +215,44 @@ https:
|
||||
return nil
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
func (this *AdminNode) addPortsToFirewall() {
|
||||
var configFile = Tea.ConfigFile("server.yaml")
|
||||
data, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var config = &TeaGo.ServerConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ports = []int{}
|
||||
if config.Http.On {
|
||||
for _, listen := range config.Http.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Https.On {
|
||||
for _, listen := range config.Https.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.AddPortsToFirewall(ports)
|
||||
}
|
||||
|
||||
// 启动API节点
|
||||
func (this *AdminNode) startAPINode() {
|
||||
configPath := Tea.Root + "/edge-api/configs/api.yaml"
|
||||
@@ -217,9 +271,9 @@ func (this *AdminNode) startAPINode() {
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(configPath, data, 0666)
|
||||
err = os.WriteFile(configPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/api.yaml' from '" + path + "'")
|
||||
canStart = true
|
||||
@@ -243,9 +297,9 @@ func (this *AdminNode) startAPINode() {
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(dbPath, data, 0666)
|
||||
err = os.WriteFile(dbPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/db.yaml' from '" + path + "'")
|
||||
break
|
||||
@@ -270,48 +324,101 @@ func (this *AdminNode) startAPINode() {
|
||||
// 生成Secret
|
||||
func (this *AdminNode) genSecret() string {
|
||||
tmpFile := os.TempDir() + "/edge-admin-secret.tmp"
|
||||
data, err := ioutil.ReadFile(tmpFile)
|
||||
data, err := os.ReadFile(tmpFile)
|
||||
if err == nil && len(data) == 32 {
|
||||
return string(data)
|
||||
}
|
||||
secret := rands.String(32)
|
||||
_ = ioutil.WriteFile(tmpFile, []byte(secret), 0666)
|
||||
_ = os.WriteFile(tmpFile, []byte(secret), 0666)
|
||||
return secret
|
||||
}
|
||||
|
||||
// 监听本地sock
|
||||
func (this *AdminNode) listenSock() error {
|
||||
path := os.TempDir() + "/edge-admin.sock"
|
||||
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
|
||||
// 检查是否已经存在
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
conn, err := net.Dial("unix", path)
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
// 检查是否在运行
|
||||
if this.sock.IsListening() {
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err == nil {
|
||||
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
|
||||
} else {
|
||||
_ = conn.Close()
|
||||
return errors.New("error: the process is already running")
|
||||
}
|
||||
}
|
||||
|
||||
// 新的监听任务
|
||||
listener, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
// 启动监听
|
||||
go func() {
|
||||
for {
|
||||
_, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
this.sock.OnCommand(func(cmd *gosock.Command) {
|
||||
switch cmd.Code {
|
||||
case "pid":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "pid",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
// 关闭子进程
|
||||
for _, pid := range this.subPIDs {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil && p != nil {
|
||||
_ = p.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// 停止当前目录下的API节点
|
||||
var apiSock = gosock.NewTmpSock("edge-api")
|
||||
apiReply, err := apiSock.Send(&gosock.Command{Code: "info"})
|
||||
if err == nil {
|
||||
adminExe, _ := os.Executable()
|
||||
if len(adminExe) > 0 && apiReply != nil && strings.HasPrefix(maps.NewMap(apiReply.Params).GetString("path"), filepath.Dir(filepath.Dir(adminExe))) {
|
||||
_, _ = apiSock.Send(&gosock.Command{Code: "stop"})
|
||||
}
|
||||
}
|
||||
|
||||
// 退出主进程
|
||||
events.Notify(events.EventQuit)
|
||||
os.Exit(0)
|
||||
case "recover":
|
||||
teaconst.IsRecoverMode = true
|
||||
_ = cmd.ReplyOk()
|
||||
case "demo":
|
||||
teaconst.IsDemoMode = !teaconst.IsDemoMode
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Params: map[string]interface{}{"isDemo": teaconst.IsDemoMode},
|
||||
})
|
||||
case "info":
|
||||
exePath, _ := os.Executable()
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "info",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
"version": teaconst.Version,
|
||||
"path": exePath,
|
||||
},
|
||||
})
|
||||
case "dev": // 切换到dev
|
||||
Tea.Env = Tea.EnvDev
|
||||
_ = cmd.ReplyOk()
|
||||
case "prod": // 切换到prod
|
||||
Tea.Env = Tea.EnvProd
|
||||
_ = cmd.ReplyOk()
|
||||
}
|
||||
})
|
||||
|
||||
err := this.sock.Listen()
|
||||
if err != nil {
|
||||
logs.Println("NODE", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = this.sock.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
18
internal/nodes/admin_node_ext.go
Normal file
18
internal/nodes/admin_node_ext.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
// 启动IP库
|
||||
func (this *AdminNode) startIPLibrary() {
|
||||
logs.Println("[NODE]initializing ip library ...")
|
||||
err := iplibrary.InitDefault()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
112
internal/nodes/session_manager.go
Normal file
112
internal/nodes/session_manager.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionManager SESSION管理
|
||||
type SessionManager struct {
|
||||
life uint
|
||||
}
|
||||
|
||||
func NewSessionManager() (*SessionManager, error) {
|
||||
return &SessionManager{}, nil
|
||||
}
|
||||
|
||||
func (this *SessionManager) Init(config *actions.SessionConfig) {
|
||||
this.life = config.Life
|
||||
}
|
||||
|
||||
func (this *SessionManager) Read(sid string) map[string]string {
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
var result = map[string]string{}
|
||||
|
||||
var cacheKey = "SESSION@" + sid
|
||||
var item = ttlcache.DefaultCache.Read(cacheKey)
|
||||
if item != nil && item.Value != nil {
|
||||
itemMap, ok := item.Value.(map[string]string)
|
||||
if ok {
|
||||
return itemMap
|
||||
}
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
|
||||
result["@error"] = err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
var session = resp.LoginSession
|
||||
if session == nil || len(session.ValuesJSON) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
err = json.Unmarshal(session.ValuesJSON, &result)
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "decode '"+sid+"' values failed: "+err.Error())
|
||||
}
|
||||
|
||||
// Write to cache
|
||||
ttlcache.DefaultCache.Write(cacheKey, result, time.Now().Unix()+300 /** must not be too long **/)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = rpcClient.LoginSessionRPC().WriteLoginSessionValue(rpcClient.Context(0), &pb.WriteLoginSessionValueRequest{
|
||||
Sid: sid,
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "write sid:'"+sid+"' key:'"+key+"' failed: "+err.Error())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *SessionManager) Delete(sid string) bool {
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = rpcClient.LoginSessionRPC().DeleteLoginSession(rpcClient.Context(0), &pb.DeleteLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "delete '"+sid+"' failed: "+err.Error())
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import "context"
|
||||
|
||||
type ContextInterface interface {
|
||||
AdminContext() context.Context
|
||||
}
|
||||
@@ -11,13 +11,18 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -27,16 +32,16 @@ type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
|
||||
locker sync.Mutex
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
// NewRPCClient 构造新的RPC客户端
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
func NewRPCClient(apiConfig *configs.APIConfig, isPrimary bool) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
}
|
||||
|
||||
client := &RPCClient{
|
||||
var client = &RPCClient{
|
||||
apiConfig: apiConfig,
|
||||
}
|
||||
|
||||
@@ -46,11 +51,17 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
}
|
||||
|
||||
// 设置RPC
|
||||
dao.SetRPC(client)
|
||||
if isPrimary {
|
||||
dao.SetRPC(client)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (this *RPCClient) APITokenRPC() pb.APITokenServiceClient {
|
||||
return pb.NewAPITokenServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
|
||||
return pb.NewAdminServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -67,6 +78,10 @@ func (this *RPCClient) NodeGrantRPC() pb.NodeGrantServiceClient {
|
||||
return pb.NewNodeGrantServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeLoginRPC() pb.NodeLoginServiceClient {
|
||||
return pb.NewNodeLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
|
||||
return pb.NewNodeClusterServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -83,14 +98,18 @@ func (this *RPCClient) NodeRegionRPC() pb.NodeRegionServiceClient {
|
||||
return pb.NewNodeRegionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodePriceItemRPC() pb.NodePriceItemServiceClient {
|
||||
return pb.NewNodePriceItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
|
||||
return pb.NewNodeIPAddressServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressLogRPC() pb.NodeIPAddressLogServiceClient {
|
||||
return pb.NewNodeIPAddressLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressThresholdRPC() pb.NodeIPAddressThresholdServiceClient {
|
||||
return pb.NewNodeIPAddressThresholdServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeValueRPC() pb.NodeValueServiceClient {
|
||||
return pb.NewNodeValueServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -103,6 +122,10 @@ func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
|
||||
return pb.NewServerServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerBandwidthStatRPC() pb.ServerBandwidthStatServiceClient {
|
||||
return pb.NewServerBandwidthStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
|
||||
return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -143,18 +166,14 @@ func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
|
||||
return pb.NewAPINodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
|
||||
return pb.NewUserNodeServiceClient(this.pickConn())
|
||||
func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
|
||||
return pb.NewAPIMethodStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
|
||||
return pb.NewDBNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MonitorNodeRPC() pb.MonitorNodeServiceClient {
|
||||
return pb.NewMonitorNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBRPC() pb.DBServiceClient {
|
||||
return pb.NewDBServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -195,6 +214,14 @@ func (this *RPCClient) HTTPCachePolicyRPC() pb.HTTPCachePolicyServiceClient {
|
||||
return pb.NewHTTPCachePolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskRPC() pb.HTTPCacheTaskServiceClient {
|
||||
return pb.NewHTTPCacheTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskKeyRPC() pb.HTTPCacheTaskKeyServiceClient {
|
||||
return pb.NewHTTPCacheTaskKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallPolicyRPC() pb.HTTPFirewallPolicyServiceClient {
|
||||
return pb.NewHTTPFirewallPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -207,6 +234,10 @@ func (this *RPCClient) HTTPFirewallRuleSetRPC() pb.HTTPFirewallRuleSetServiceCli
|
||||
return pb.NewHTTPFirewallRuleSetServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FirewallRPC() pb.FirewallServiceClient {
|
||||
return pb.NewFirewallServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPLocationRPC() pb.HTTPLocationServiceClient {
|
||||
return pb.NewHTTPLocationServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -280,6 +311,14 @@ func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
|
||||
return pb.NewIPLibraryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryFileRPC() pb.IPLibraryFileServiceClient {
|
||||
return pb.NewIPLibraryFileServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryArtifactRPC() pb.IPLibraryArtifactServiceClient {
|
||||
return pb.NewIPLibraryArtifactServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPListRPC() pb.IPListServiceClient {
|
||||
return pb.NewIPListServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -304,6 +343,18 @@ func (this *RPCClient) RegionProvinceRPC() pb.RegionProvinceServiceClient {
|
||||
return pb.NewRegionProvinceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionCityRPC() pb.RegionCityServiceClient {
|
||||
return pb.NewRegionCityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionTownRPC() pb.RegionTownServiceClient {
|
||||
return pb.NewRegionTownServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionProviderRPC() pb.RegionProviderServiceClient {
|
||||
return pb.NewRegionProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LogRPC() pb.LogServiceClient {
|
||||
return pb.NewLogServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -332,22 +383,34 @@ func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient {
|
||||
return pb.NewACMETaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserRPC() pb.UserServiceClient {
|
||||
return pb.NewUserServiceClient(this.pickConn())
|
||||
func (this *RPCClient) ACMEProviderRPC() pb.ACMEProviderServiceClient {
|
||||
return pb.NewACMEProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
|
||||
return pb.NewUserBillServiceClient(this.pickConn())
|
||||
func (this *RPCClient) ACMEProviderAccountRPC() pb.ACMEProviderAccountServiceClient {
|
||||
return pb.NewACMEProviderAccountServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserRPC() pb.UserServiceClient {
|
||||
return pb.NewUserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
|
||||
return pb.NewUserAccessKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserIdentityRPC() pb.UserIdentityServiceClient {
|
||||
return pb.NewUserIdentityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
|
||||
return pb.NewLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginSessionRPC() pb.LoginSessionServiceClient {
|
||||
return pb.NewLoginSessionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
|
||||
return pb.NewNodeTaskServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -364,42 +427,42 @@ func (this *RPCClient) LatestItemRPC() pb.LatestItemServiceClient {
|
||||
return pb.NewLatestItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSClusterRPC() pb.NSClusterServiceClient {
|
||||
return pb.NewNSClusterServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSNodeRPC() pb.NSNodeServiceClient {
|
||||
return pb.NewNSNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSDomainRPC() pb.NSDomainServiceClient {
|
||||
return pb.NewNSDomainServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRecordRPC() pb.NSRecordServiceClient {
|
||||
return pb.NewNSRecordServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRouteRPC() pb.NSRouteServiceClient {
|
||||
return pb.NewNSRouteServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSAccessLogRPC() pb.NSAccessLogServiceClient {
|
||||
return pb.NewNSAccessLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
|
||||
return pb.NewMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
|
||||
return pb.NewMetricStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricChartRPC() pb.MetricChartServiceClient {
|
||||
return pb.NewMetricChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
|
||||
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardRPC() pb.ServerStatBoardServiceClient {
|
||||
return pb.NewServerStatBoardServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDomainHourlyStatRPC() pb.ServerDomainHourlyStatServiceClient {
|
||||
return pb.NewServerDomainHourlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceClient {
|
||||
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient {
|
||||
return pb.NewTrafficDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// Context 构造Admin上下文
|
||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||
ctx := context.Background()
|
||||
m := maps.Map{
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "admin",
|
||||
"userId": adminId,
|
||||
@@ -414,15 +477,15 @@ func (this *RPCClient) Context(adminId int64) context.Context {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
token := base64.StdEncoding.EncodeToString(data)
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// APIContext 构造API上下文
|
||||
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
ctx := context.Background()
|
||||
m := maps.Map{
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "api",
|
||||
"userId": apiNodeId,
|
||||
@@ -437,7 +500,7 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
token := base64.StdEncoding.EncodeToString(data)
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
@@ -445,25 +508,53 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
// UpdateConfig 修改配置
|
||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
this.apiConfig = config
|
||||
return this.init()
|
||||
|
||||
this.locker.Lock()
|
||||
err := this.init()
|
||||
this.locker.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func (this *RPCClient) init() error {
|
||||
// 当前的IP地址
|
||||
var localIPAddrs = this.localIPAddrs()
|
||||
|
||||
// 重新连接
|
||||
conns := []*grpc.ClientConn{}
|
||||
var conns = []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPC.Endpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return errors.New("parse endpoint failed: " + err.Error())
|
||||
}
|
||||
|
||||
var apiHost = u.Host
|
||||
|
||||
// 如果本机,则将地址修改为回路地址
|
||||
if lists.ContainsString(localIPAddrs, u.Hostname()) {
|
||||
if strings.Contains(apiHost, "[") { // IPv6 [host]:port
|
||||
apiHost = "[::1]"
|
||||
} else {
|
||||
apiHost = "127.0.0.1"
|
||||
}
|
||||
var port = u.Port()
|
||||
if len(port) > 0 {
|
||||
apiHost += ":" + port
|
||||
}
|
||||
}
|
||||
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(128<<20),
|
||||
grpc.MaxCallSendMsgSize(128<<20),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.Dial(u.Host, grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
|
||||
})), callOptions)
|
||||
} else {
|
||||
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
||||
}
|
||||
@@ -487,39 +578,71 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查连接状态
|
||||
if len(this.conns) > 0 {
|
||||
availableConns := []*grpc.ClientConn{}
|
||||
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} {
|
||||
var countConns = len(this.conns)
|
||||
if countConns > 0 {
|
||||
if countConns == 1 {
|
||||
return this.conns[0]
|
||||
}
|
||||
for _, state := range []connectivity.State{
|
||||
connectivity.Ready,
|
||||
connectivity.Idle,
|
||||
connectivity.Connecting,
|
||||
connectivity.TransientFailure,
|
||||
} {
|
||||
var availableConns = []*grpc.ClientConn{}
|
||||
for _, conn := range this.conns {
|
||||
if conn.GetState() == state {
|
||||
availableConns = append(availableConns, conn)
|
||||
}
|
||||
}
|
||||
if len(availableConns) > 0 {
|
||||
break
|
||||
return this.randConn(availableConns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(availableConns) > 0 {
|
||||
return availableConns[rands.Int(0, len(availableConns)-1)]
|
||||
}
|
||||
|
||||
// 关闭
|
||||
for _, conn := range this.conns {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 重新初始化
|
||||
err := this.init()
|
||||
if err != nil {
|
||||
// 错误提示已经在构造对象时打印过,所以这里不再重复打印
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(this.conns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.conns[rands.Int(0, len(this.conns)-1)]
|
||||
return this.randConn(this.conns)
|
||||
}
|
||||
|
||||
// Close 关闭
|
||||
func (this *RPCClient) Close() error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
var lastErr error
|
||||
for _, conn := range this.conns {
|
||||
var err = conn.Close()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (this *RPCClient) localIPAddrs() []string {
|
||||
localInterfaceAddrs, err := net.InterfaceAddrs()
|
||||
var localIPAddrs = []string{}
|
||||
if err == nil {
|
||||
for _, addr := range localInterfaceAddrs {
|
||||
var addrString = addr.String()
|
||||
var index = strings.Index(addrString, "/")
|
||||
if index > 0 {
|
||||
localIPAddrs = append(localIPAddrs, addrString[:index])
|
||||
}
|
||||
}
|
||||
}
|
||||
return localIPAddrs
|
||||
}
|
||||
|
||||
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
|
||||
var l = len(conns)
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
if l == 1 {
|
||||
return conns[0]
|
||||
}
|
||||
return conns[rands.Int(0, l-1)]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestRPCClient_NodeRPC(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config)
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -35,13 +35,14 @@ func TestRPCClient_NodeRPC(t *testing.T) {
|
||||
func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{"http://127.0.0.1:8004"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
})
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -56,13 +57,14 @@ func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{"https://127.0.0.1:8003"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
})
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -77,13 +79,14 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
func TestRPC_Dial_HTTPS(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{"https://127.0.0.1:8004"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
})
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -94,3 +97,53 @@ func TestRPC_Dial_HTTPS(t *testing.T) {
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient_2(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var conn = rpc.AdminRPC()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := conn.LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -20,7 +23,7 @@ func SharedRPC() (*RPCClient, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := NewRPCClient(config)
|
||||
client, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -28,3 +31,39 @@ func SharedRPC() (*RPCClient, error) {
|
||||
sharedRPC = client
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
// IsConnError 是否为连接错误
|
||||
func IsConnError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否为连接错误
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
var errorCode = statusErr.Code()
|
||||
return errorCode == codes.Unavailable || errorCode == codes.Canceled
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "code = Canceled") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnimplementedError 检查是否为未实现错误
|
||||
func IsUnimplementedError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
if statusErr.Code() == codes.Unimplemented {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package setup
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"os"
|
||||
)
|
||||
|
||||
var isConfigured bool
|
||||
@@ -16,3 +18,16 @@ func IsConfigured() bool {
|
||||
isConfigured = err == nil
|
||||
return isConfigured
|
||||
}
|
||||
|
||||
// IsNewInstalled IsNew 检查是否新安装
|
||||
func IsNewInstalled() bool {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = os.Stat(homeDir + "/." + teaconst.ProcessName + "/api.yaml")
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewAuthorityTask()
|
||||
go task.Start()
|
||||
})
|
||||
}
|
||||
|
||||
type AuthorityTask struct {
|
||||
}
|
||||
|
||||
func NewAuthorityTask() *AuthorityTask {
|
||||
return &AuthorityTask{}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Start() {
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
// 快速测试
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
|
||||
// 初始化的时候先获取一次
|
||||
timeout := time.NewTimer(3 * time.Second)
|
||||
<-timeout.C
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
|
||||
// 定时获取
|
||||
for range ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.AuthorityKeyRPC().ReadAuthorityKey(rpcClient.Context(0), &pb.ReadAuthorityKeyRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.AuthorityKey != nil {
|
||||
teaconst.IsPlus = true
|
||||
} else {
|
||||
teaconst.IsPlus = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
132
internal/tasks/task_check_updates.go
Normal file
132
internal/tasks/task_check_updates.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
var task = NewCheckUpdatesTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type CheckUpdatesTask struct {
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
func NewCheckUpdatesTask() *CheckUpdatesTask {
|
||||
return &CheckUpdatesTask{}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Start() {
|
||||
this.ticker = time.NewTicker(12 * time.Hour)
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][CHECK_UPDATES_TASK]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Loop() error {
|
||||
// 检查是否开启
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeCheckUpdates})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var valueJSON = valueResp.ValueJSON
|
||||
var config = systemconfigs.NewCheckUpdatesConfig()
|
||||
if len(valueJSON) > 0 {
|
||||
err = json.Unmarshal(valueJSON, config)
|
||||
if err != nil {
|
||||
return errors.New("decode config failed: " + err.Error())
|
||||
}
|
||||
if !config.AutoCheck {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 开始检查
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// 目前支持Linux
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var apiURL = teaconst.UpdatesURL
|
||||
apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${version}", teaconst.Version)
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return errors.New("read api failed: " + err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("read api failed: " + err.Error())
|
||||
}
|
||||
|
||||
var apiResponse = &Response{}
|
||||
err = json.Unmarshal(data, apiResponse)
|
||||
if err != nil {
|
||||
return errors.New("decode version data failed: " + err.Error())
|
||||
}
|
||||
|
||||
if apiResponse.Code != 200 {
|
||||
return errors.New("invalid response: " + apiResponse.Message)
|
||||
}
|
||||
|
||||
var m = maps.NewMap(apiResponse.Data)
|
||||
var dlHost = m.GetString("host")
|
||||
var versions = m.GetSlice("versions")
|
||||
if len(versions) > 0 {
|
||||
for _, version := range versions {
|
||||
var vMap = maps.NewMap(version)
|
||||
if vMap.GetString("code") == "admin" {
|
||||
var latestVersion = vMap.GetString("version")
|
||||
if stringutil.VersionCompare(teaconst.Version, latestVersion) < 0 && (len(config.IgnoredVersion) == 0 || stringutil.VersionCompare(latestVersion, config.IgnoredVersion) > 0) {
|
||||
teaconst.NewVersionCode = latestVersion
|
||||
teaconst.NewVersionDownloadURL = dlHost + vMap.GetString("url")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,26 +1,37 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncAPINodesTask()
|
||||
go task.Start()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// API节点同步任务
|
||||
// SyncAPINodesTask API节点同步任务
|
||||
type SyncAPINodesTask struct {
|
||||
}
|
||||
|
||||
@@ -44,7 +55,17 @@ func (this *SyncAPINodesTask) Start() {
|
||||
|
||||
func (this *SyncAPINodesTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 是否禁止自动升级
|
||||
if config.RPC.DisableUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -58,8 +79,8 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
return err
|
||||
}
|
||||
|
||||
newEndpoints := []string{}
|
||||
for _, node := range resp.Nodes {
|
||||
var newEndpoints = []string{}
|
||||
for _, node := range resp.ApiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
@@ -67,14 +88,16 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if this.isSame(newEndpoints, config.RPC.Endpoints) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 测试是否有API节点可用
|
||||
hasOk := this.testEndpoints(newEndpoints)
|
||||
if !hasOk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPC.Endpoints = newEndpoints
|
||||
err = rpcClient.UpdateConfig(config)
|
||||
@@ -96,3 +119,48 @@ func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) b
|
||||
sort.Strings(endpoints2)
|
||||
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
|
||||
if len(endpoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var wg = sync.WaitGroup{}
|
||||
wg.Add(len(endpoints))
|
||||
|
||||
var ok = false
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
go func(endpoint string) {
|
||||
defer wg.Done()
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer func() {
|
||||
cancel()
|
||||
}()
|
||||
var conn *grpc.ClientConn
|
||||
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), grpc.WithBlock())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = conn.Close()
|
||||
|
||||
ok = true
|
||||
}(endpoint)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
|
||||
@@ -16,7 +18,9 @@ import (
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncClusterTask()
|
||||
go task.Start()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,7 +44,7 @@ func (this *SyncClusterTask) Start() {
|
||||
|
||||
func (this *SyncClusterTask) loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,7 +54,7 @@ func (this *SyncClusterTask) loop() error {
|
||||
}
|
||||
ctx := rpcClient.Context(0)
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 100})
|
||||
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 500})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
79
internal/utils/apinodeutils/deploy_file.go
Normal file
79
internal/utils/apinodeutils/deploy_file.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// DeployFile 部署文件描述
|
||||
type DeployFile struct {
|
||||
OS string
|
||||
Arch string
|
||||
Version string
|
||||
Path string
|
||||
}
|
||||
|
||||
// Sum 计算概要
|
||||
func (this *DeployFile) Sum() (string, error) {
|
||||
fp, err := os.Open(this.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
m := md5.New()
|
||||
buffer := make([]byte, 128*1024)
|
||||
for {
|
||||
n, err := fp.Read(buffer)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
_, err = m.Write(buffer[:n])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
sum := m.Sum(nil)
|
||||
return fmt.Sprintf("%x", sum), nil
|
||||
}
|
||||
|
||||
// Read 读取一个片段数据
|
||||
func (this *DeployFile) Read(offset int64) (data []byte, newOffset int64, err error) {
|
||||
fp, err := os.Open(this.Path)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
if offset >= stat.Size() {
|
||||
return nil, offset, io.EOF
|
||||
}
|
||||
|
||||
_, err = fp.Seek(offset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
|
||||
buffer := make([]byte, 128*1024)
|
||||
n, err := fp.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
|
||||
return buffer[:n], offset + int64(n), nil
|
||||
}
|
||||
96
internal/utils/apinodeutils/deploy_manager.go
Normal file
96
internal/utils/apinodeutils/deploy_manager.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// DeployManager 节点部署文件管理器
|
||||
// 如果节点部署文件有变化,需要重启API节点以便于生效
|
||||
type DeployManager struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// NewDeployManager 获取新节点部署文件管理器
|
||||
func NewDeployManager() *DeployManager {
|
||||
var manager = &DeployManager{
|
||||
dir: Tea.Root + "/edge-api/deploy",
|
||||
}
|
||||
manager.LoadNodeFiles()
|
||||
manager.LoadNSNodeFiles()
|
||||
return manager
|
||||
}
|
||||
|
||||
// LoadNodeFiles 加载所有边缘节点文件
|
||||
func (this *DeployManager) LoadNodeFiles() []*DeployFile {
|
||||
var keyMap = map[string]*DeployFile{} // key => File
|
||||
|
||||
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||
for _, file := range files.NewFile(this.dir).List() {
|
||||
var name = file.Name()
|
||||
if !reg.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
var matches = reg.FindStringSubmatch(name)
|
||||
var osName = matches[1]
|
||||
var arch = matches[2]
|
||||
var version = matches[3]
|
||||
|
||||
var key = osName + "_" + arch
|
||||
oldFile, ok := keyMap[key]
|
||||
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||
continue
|
||||
}
|
||||
keyMap[key] = &DeployFile{
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
Path: file.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
var result = []*DeployFile{}
|
||||
for _, v := range keyMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// LoadNSNodeFiles 加载所有NS节点安装文件
|
||||
func (this *DeployManager) LoadNSNodeFiles() []*DeployFile {
|
||||
var keyMap = map[string]*DeployFile{} // key => File
|
||||
|
||||
var reg = regexp.MustCompile(`^edge-dns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||
for _, file := range files.NewFile(this.dir).List() {
|
||||
var name = file.Name()
|
||||
if !reg.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
var matches = reg.FindStringSubmatch(name)
|
||||
var osName = matches[1]
|
||||
var arch = matches[2]
|
||||
var version = matches[3]
|
||||
|
||||
var key = osName + "_" + arch
|
||||
oldFile, ok := keyMap[key]
|
||||
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||
continue
|
||||
}
|
||||
keyMap[key] = &DeployFile{
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
Path: file.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
var result = []*DeployFile{}
|
||||
for _, v := range keyMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
30
internal/utils/apinodeutils/manager.go
Normal file
30
internal/utils/apinodeutils/manager.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
var SharedManager = NewManager()
|
||||
|
||||
type Manager struct {
|
||||
upgraderMap map[int64]*Upgrader
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
upgraderMap: map[int64]*Upgrader{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Manager) AddUpgrader(upgrader *Upgrader) {
|
||||
this.upgraderMap[upgrader.apiNodeId] = upgrader
|
||||
}
|
||||
|
||||
func (this *Manager) FindUpgrader(apiNodeId int64) *Upgrader {
|
||||
return this.upgraderMap[apiNodeId]
|
||||
}
|
||||
|
||||
func (this *Manager) RemoveUpgrader(upgrader *Upgrader) {
|
||||
if upgrader == nil {
|
||||
return
|
||||
}
|
||||
delete(this.upgraderMap, upgrader.apiNodeId)
|
||||
}
|
||||
349
internal/utils/apinodeutils/upgrader.go
Normal file
349
internal/utils/apinodeutils/upgrader.go
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Progress struct {
|
||||
Percent float64
|
||||
}
|
||||
|
||||
type Upgrader struct {
|
||||
progress *Progress
|
||||
apiExe string
|
||||
apiNodeId int64
|
||||
}
|
||||
|
||||
func NewUpgrader(apiNodeId int64) *Upgrader {
|
||||
return &Upgrader{
|
||||
apiExe: apiExe(),
|
||||
progress: &Progress{Percent: 0},
|
||||
apiNodeId: apiNodeId,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Upgrader) Upgrade() error {
|
||||
sharedClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiNodeResp, err := sharedClient.APINodeRPC().FindEnabledAPINode(sharedClient.Context(0), &pb.FindEnabledAPINodeRequest{ApiNodeId: this.apiNodeId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var apiNode = apiNodeResp.ApiNode
|
||||
if apiNode == nil {
|
||||
return errors.New("could not find api node with id '" + types.String(this.apiNodeId) + "'")
|
||||
}
|
||||
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newAPIConfig = apiConfig.Clone()
|
||||
newAPIConfig.RPC.Endpoints = apiNode.AccessAddrs
|
||||
|
||||
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级边缘节点
|
||||
err = this.upgradeNodes(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级NS节点
|
||||
err = this.upgradeNSNodes(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级API节点
|
||||
err = this.upgradeAPINode(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return errors.New("upgrade api node failed: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Progress 查看升级进程
|
||||
func (this *Upgrader) Progress() *Progress {
|
||||
return this.progress
|
||||
}
|
||||
|
||||
// 升级API节点
|
||||
func (this *Upgrader) upgradeAPINode(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(ctx, &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !Tea.IsTesting() /** 开发环境下允许突破此限制方便测试 **/ &&
|
||||
(stringutil.VersionCompare(versionResp.Version, "0.6.4" /** 从0.6.4开始支持 **/) < 0 || versionResp.Os != runtime.GOOS || versionResp.Arch != runtime.GOARCH) {
|
||||
return errors.New("could not upgrade api node v" + versionResp.Version + "/" + versionResp.Os + "/" + versionResp.Arch)
|
||||
}
|
||||
|
||||
// 检查本地文件版本
|
||||
canUpgrade, reason := CanUpgrade(versionResp.Version, versionResp.Os, versionResp.Arch)
|
||||
if !canUpgrade {
|
||||
return errors.New(reason)
|
||||
}
|
||||
|
||||
localVersion, err := lookupLocalVersion()
|
||||
if err != nil {
|
||||
return errors.New("lookup version failed: " + err.Error())
|
||||
}
|
||||
|
||||
// 检查要升级的文件
|
||||
var gzFile = this.apiExe + "." + localVersion + ".gz"
|
||||
|
||||
gzReader, err := os.Open(gzFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
err = func() error {
|
||||
// 压缩文件
|
||||
exeReader, err := os.Open(this.apiExe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = exeReader.Close()
|
||||
}()
|
||||
var tmpGzFile = gzFile + ".tmp"
|
||||
gzFileWriter, err := os.OpenFile(tmpGzFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var gzWriter = gzip.NewWriter(gzFileWriter)
|
||||
defer func() {
|
||||
_ = gzWriter.Close()
|
||||
_ = gzFileWriter.Close()
|
||||
|
||||
_ = os.Rename(tmpGzFile, gzFile)
|
||||
}()
|
||||
_, err = io.Copy(gzWriter, exeReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gzReader, err = os.Open(gzFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = gzReader.Close()
|
||||
}()
|
||||
|
||||
// 开始上传
|
||||
var hash = md5.New()
|
||||
var buf = make([]byte, 128*4096)
|
||||
var isFirst = true
|
||||
stat, err := gzReader.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var totalSize = stat.Size()
|
||||
if totalSize == 0 {
|
||||
_ = gzReader.Close()
|
||||
_ = os.Remove(gzFile)
|
||||
return errors.New("invalid gz file")
|
||||
}
|
||||
|
||||
var uploadedSize int64 = 0
|
||||
for {
|
||||
n, err := gzReader.Read(buf)
|
||||
if n > 0 {
|
||||
// 计算Hash
|
||||
hash.Write(buf[:n])
|
||||
|
||||
// 上传
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
|
||||
Filename: filepath.Base(this.apiExe),
|
||||
Sum: "",
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: false,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
// 进度
|
||||
uploadedSize += int64(n)
|
||||
this.progress = &Progress{Percent: float64(uploadedSize*100) / float64(totalSize)}
|
||||
}
|
||||
if isFirst {
|
||||
isFirst = false
|
||||
}
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if err == io.EOF {
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
|
||||
Filename: filepath.Base(this.apiExe),
|
||||
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: true,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 升级边缘节点
|
||||
func (this *Upgrader) upgradeNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
// 本地的
|
||||
var manager = NewDeployManager()
|
||||
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||
for _, deployFile := range manager.LoadNodeFiles() {
|
||||
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||
}
|
||||
|
||||
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||
for _, nodeFile := range remoteFilesResp.NodeDeployFiles {
|
||||
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||
}
|
||||
|
||||
// 对比版本
|
||||
for key, deployFile := range localFileMap {
|
||||
remoteDeployFile, ok := remoteFileMap[key]
|
||||
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||
if err != nil {
|
||||
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 升级NS节点
|
||||
func (this *Upgrader) upgradeNSNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
// 本地的
|
||||
var manager = NewDeployManager()
|
||||
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||
for _, deployFile := range manager.LoadNSNodeFiles() {
|
||||
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||
}
|
||||
|
||||
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||
for _, nodeFile := range remoteFilesResp.NsNodeDeployFiles {
|
||||
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||
}
|
||||
|
||||
// 对比版本
|
||||
for key, deployFile := range localFileMap {
|
||||
remoteDeployFile, ok := remoteFileMap[key]
|
||||
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||
if err != nil {
|
||||
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 上传节点文件
|
||||
func (this *Upgrader) uploadNodeDeployFile(ctx context.Context, rpcClient *rpc.RPCClient, path string) error {
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
var buf = make([]byte, 128*4096)
|
||||
var isFirst = true
|
||||
|
||||
var hash = md5.New()
|
||||
|
||||
for {
|
||||
n, err := fp.Read(buf)
|
||||
if n > 0 {
|
||||
hash.Write(buf[:n])
|
||||
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||
Filename: filepath.Base(path),
|
||||
Sum: "",
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: false,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
isFirst = false
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||
Filename: filepath.Base(path),
|
||||
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
ChunkData: nil,
|
||||
IsFirstChunk: false,
|
||||
IsLastChunk: true,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
22
internal/utils/apinodeutils/upgrader_test.go
Normal file
22
internal/utils/apinodeutils/upgrader_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpgrader_CanUpgrade(t *testing.T) {
|
||||
t.Log(apinodeutils.CanUpgrade("0.6.3", runtime.GOOS, runtime.GOARCH))
|
||||
}
|
||||
|
||||
func TestUpgrader_Upgrade(t *testing.T) {
|
||||
var upgrader = apinodeutils.NewUpgrader(1)
|
||||
err := upgrader.Upgrade()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
77
internal/utils/apinodeutils/utils.go
Normal file
77
internal/utils/apinodeutils/utils.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool, reason string) {
|
||||
if len(apiVersion) == 0 {
|
||||
return false, "current api version should not be empty"
|
||||
}
|
||||
|
||||
if stringutil.VersionCompare(apiVersion, "0.6.4") < 0 {
|
||||
return false, "api node version must greater than or equal to 0.6.4"
|
||||
}
|
||||
|
||||
if osName != runtime.GOOS {
|
||||
return false, "os not match: " + osName
|
||||
}
|
||||
if arch != runtime.GOARCH {
|
||||
return false, "arch not match: " + arch
|
||||
}
|
||||
|
||||
stat, err := os.Stat(apiExe())
|
||||
if err != nil {
|
||||
return false, "stat error: " + err.Error()
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return false, "is directory"
|
||||
}
|
||||
|
||||
localVersion, err := lookupLocalVersion()
|
||||
if err != nil {
|
||||
return false, "lookup version failed: " + err.Error()
|
||||
}
|
||||
if localVersion != teaconst.APINodeVersion {
|
||||
return false, "not newest api node"
|
||||
}
|
||||
if stringutil.VersionCompare(localVersion, apiVersion) <= 0 {
|
||||
return false, "need not upgrade, local '" + localVersion + "' vs remote '" + apiVersion + "'"
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func lookupLocalVersion() (string, error) {
|
||||
var cmd = exec.Command(apiExe(), "-V")
|
||||
var output = &bytes.Buffer{}
|
||||
cmd.Stdout = output
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var localVersion = strings.TrimSpace(output.String())
|
||||
|
||||
// 检查版本号
|
||||
var reg = regexp.MustCompile(`^[\d.]+$`)
|
||||
if !reg.MatchString(localVersion) {
|
||||
return "", errors.New("lookup version failed: " + localVersion)
|
||||
}
|
||||
|
||||
return localVersion, nil
|
||||
}
|
||||
|
||||
func apiExe() string {
|
||||
return Tea.Root + "/edge-api/bin/edge-api"
|
||||
}
|
||||
12
internal/utils/dateutils/utils.go
Normal file
12
internal/utils/dateutils/utils.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dateutils
|
||||
|
||||
// SplitYmd 分隔Ymd格式的日期
|
||||
// Ymd => Y-m-d
|
||||
func SplitYmd(day string) string {
|
||||
if len(day) != 8 {
|
||||
return day
|
||||
}
|
||||
return day[:4] + "-" + day[4:6] + "-" + day[6:]
|
||||
}
|
||||
12
internal/utils/email.go
Normal file
12
internal/utils/email.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
var emailReg = regexp.MustCompile(`(?i)^[a-z\d]+([._+-]*[a-z\d]+)*@([a-z\d]+[a-z\d-]*[a-z\d]+\.)+[a-z\d]+$`)
|
||||
|
||||
// ValidateEmail 校验电子邮箱格式
|
||||
func ValidateEmail(email string) bool {
|
||||
return emailReg.MatchString(email)
|
||||
}
|
||||
22
internal/utils/email_test.go
Normal file
22
internal/utils/email_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateEmail(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b.c.d@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com.cn"))
|
||||
a.IsTrue(utils.ValidateEmail("hello.world.123@gmail.123.com"))
|
||||
a.IsTrue(utils.ValidateEmail("10000@qq.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa.@gmail.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@gmail"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@123"))
|
||||
}
|
||||
28
internal/utils/firewall.go
Normal file
28
internal/utils/firewall.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func AddPortsToFirewall(ports []int) {
|
||||
for _, port := range ports {
|
||||
// Linux
|
||||
if runtime.GOOS == "linux" {
|
||||
// firewalld
|
||||
firewallCmd, _ := exec.LookPath("firewall-cmd")
|
||||
if len(firewallCmd) > 0 {
|
||||
err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run()
|
||||
if err == nil {
|
||||
logs.Println("ADMIN_NODE", "add port '"+types.String(port)+"' to firewalld")
|
||||
|
||||
_ = exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp", "--permanent").Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"math/big"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 将IP转换为整型
|
||||
// IP2Long 将IP转换为整型
|
||||
func IP2Long(ip string) uint64 {
|
||||
s := net.ParseIP(ip)
|
||||
if len(s) != 16 {
|
||||
@@ -23,7 +26,7 @@ func IP2Long(ip string) uint64 {
|
||||
return uint64(binary.BigEndian.Uint32(s.To4()))
|
||||
}
|
||||
|
||||
// 判断是否为IPv4
|
||||
// IsIPv4 判断是否为IPv4
|
||||
func IsIPv4(ip string) bool {
|
||||
if !regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`).MatchString(ip) {
|
||||
return false
|
||||
@@ -34,10 +37,120 @@ func IsIPv4(ip string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 判断是否为IPv6
|
||||
// IsIPv6 判断是否为IPv6
|
||||
func IsIPv6(ip string) bool {
|
||||
if !strings.Contains(ip, ":") {
|
||||
return false
|
||||
}
|
||||
return len(net.ParseIP(ip)) == net.IPv6len
|
||||
}
|
||||
|
||||
// ExtractIP 分解IP
|
||||
// 只支持D段掩码的CIDR
|
||||
// 最多只记录255个值
|
||||
func ExtractIP(ipStrings string) ([]string, error) {
|
||||
ipStrings = strings.ReplaceAll(ipStrings, " ", "")
|
||||
|
||||
// CIDR
|
||||
if strings.Contains(ipStrings, "/") {
|
||||
_, cidrNet, err := net.ParseCIDR(ipStrings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var index = strings.Index(ipStrings, "/")
|
||||
var ipFrom = ipStrings[:index]
|
||||
var bits = types.Int(ipStrings[index+1:])
|
||||
if bits < 24 {
|
||||
return nil, errors.New("CIDR bits should be greater than 24")
|
||||
}
|
||||
|
||||
var ipv4 = net.ParseIP(ipFrom).To4()
|
||||
if len(ipv4) == 0 {
|
||||
return nil, errors.New("support IPv4 only")
|
||||
}
|
||||
|
||||
var result = []string{}
|
||||
ipv4[3] = 0 // 从0开始
|
||||
for i := 0; i <= 255; i++ {
|
||||
if cidrNet.Contains(ipv4) {
|
||||
result = append(result, ipv4.String())
|
||||
}
|
||||
ipv4 = NextIP(ipv4)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IP Range
|
||||
if strings.Contains(ipStrings, "-") {
|
||||
var index = strings.Index(ipStrings, "-")
|
||||
var ipFromString = ipStrings[:index]
|
||||
var ipToString = ipStrings[index+1:]
|
||||
|
||||
var ipFrom = net.ParseIP(ipFromString).To4()
|
||||
if len(ipFrom) == 0 {
|
||||
return nil, errors.New("invalid ip '" + ipFromString + "'")
|
||||
}
|
||||
|
||||
var ipTo = net.ParseIP(ipToString).To4()
|
||||
if len(ipTo) == 0 {
|
||||
return nil, errors.New("invalid ip '" + ipToString + "'")
|
||||
}
|
||||
|
||||
if bytes.Compare(ipFrom, ipTo) > 0 {
|
||||
ipFrom, ipTo = ipTo, ipFrom
|
||||
}
|
||||
|
||||
var result = []string{}
|
||||
for i := 0; i < 255; i++ {
|
||||
if bytes.Compare(ipFrom, ipTo) > 0 {
|
||||
break
|
||||
}
|
||||
result = append(result, ipFrom.String())
|
||||
ipFrom = NextIP(ipFrom)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return []string{ipStrings}, nil
|
||||
}
|
||||
|
||||
// NextIP IP增加1
|
||||
func NextIP(prevIP net.IP) net.IP {
|
||||
var ip = make(net.IP, len(prevIP))
|
||||
copy(ip, prevIP)
|
||||
var index = len(ip) - 1
|
||||
for {
|
||||
if ip[index] == 255 {
|
||||
ip[index] = 0
|
||||
index--
|
||||
if index < 0 {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
ip[index]++
|
||||
break
|
||||
}
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// IsLocalIP 判断是否为本地IP
|
||||
// ip 是To4()或者To16()的结果
|
||||
func IsLocalIP(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if ip[0] == 127 ||
|
||||
ip[0] == 10 ||
|
||||
(ip[0] == 172 && ip[1]&0xf0 == 16) ||
|
||||
(ip[0] == 192 && ip[1] == 168) {
|
||||
return true
|
||||
}
|
||||
|
||||
if ip.String() == "::1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -83,3 +84,27 @@ func TestIsIPv6(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractIP(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.1.100"))
|
||||
}
|
||||
|
||||
func TestExtractIP_CIDR(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.2.100/24"))
|
||||
}
|
||||
|
||||
func TestExtractIP_Range(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.2.100 - 192.168.4.2"))
|
||||
}
|
||||
|
||||
func TestNextIP(t *testing.T) {
|
||||
for _, ip := range []string{"192.168.1.1", "0.0.0.0", "255.255.255.255", "192.168.2.255", "192.168.255.255"} {
|
||||
t.Log(ip+":", NextIP(net.ParseIP(ip).To4()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextIP_Copy(t *testing.T) {
|
||||
var ip = net.ParseIP("192.168.1.100")
|
||||
var nextIP = NextIP(ip)
|
||||
t.Log(ip, nextIP)
|
||||
}
|
||||
30
internal/utils/json.go
Normal file
30
internal/utils/json.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// JSONClone 使用JSON克隆对象
|
||||
func JSONClone(v interface{}) (interface{}, error) {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nv = reflect.New(reflect.TypeOf(v).Elem()).Interface()
|
||||
err = json.Unmarshal(data, nv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nv, nil
|
||||
}
|
||||
|
||||
// 判断JSON数据是否为null
|
||||
func JSONIsNull(jsonData []byte) bool {
|
||||
return len(jsonData) == 0 || bytes.Equal(jsonData, []byte("null"))
|
||||
}
|
||||
35
internal/utils/json_test.go
Normal file
35
internal/utils/json_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJSONClone(t *testing.T) {
|
||||
type A struct {
|
||||
B int `json:"b"`
|
||||
C string `json:"c"`
|
||||
}
|
||||
|
||||
var a = &A{B: 123, C: "456"}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
c, err := utils.JSONClone(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%p, %#v", c, c)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestJSONIsNull(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(utils.JSONIsNull(nil))
|
||||
a.IsTrue(utils.JSONIsNull([]byte{}))
|
||||
a.IsTrue(utils.JSONIsNull([]byte("null")))
|
||||
a.IsFalse(utils.JSONIsNull([]byte{1, 2, 3}))
|
||||
}
|
||||
@@ -1,28 +1,49 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// 获取CNAME
|
||||
func LookupCNAME(host string) (string, error) {
|
||||
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return "", err
|
||||
var sharedDNSClient *dns.Client
|
||||
var sharedDNSConfig *dns.ClientConfig
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
c := new(dns.Client)
|
||||
m := new(dns.Msg)
|
||||
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
logs.Println("ERROR: configure dns client failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sharedDNSConfig = config
|
||||
sharedDNSClient = &dns.Client{}
|
||||
}
|
||||
|
||||
// LookupCNAME 获取CNAME
|
||||
func LookupCNAME(host string) (string, error) {
|
||||
var m = new(dns.Msg)
|
||||
|
||||
m.SetQuestion(host+".", dns.TypeCNAME)
|
||||
m.RecursionDesired = true
|
||||
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(r.Answer) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return r.Answer[0].(*dns.CNAME).Target, nil
|
||||
var lastErr error
|
||||
for _, serverAddr := range sharedDNSConfig.Servers {
|
||||
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
if len(r.Answer) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return r.Answer[0].(*dns.CNAME).Target, nil
|
||||
}
|
||||
return "", lastErr
|
||||
}
|
||||
|
||||
12
internal/utils/lookup_test.go
Normal file
12
internal/utils/lookup_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLookupCNAME(t *testing.T) {
|
||||
t.Log(utils.LookupCNAME("www.yun4s.cn"))
|
||||
}
|
||||
28
internal/utils/nodelogutils/utils.go
Normal file
28
internal/utils/nodelogutils/utils.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package nodelogutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// FindCommonTags 查找常用的标签
|
||||
func FindNodeCommonTags(langCode langs.LangCode) []maps.Map {
|
||||
return []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.Log_TagListener),
|
||||
"code": "LISTENER",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.Log_TagWAF),
|
||||
"code": "WAF",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.Log_TagAccessLog),
|
||||
"code": "ACCESS_LOG",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package numberutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FormatInt64(value int64) string {
|
||||
@@ -13,38 +16,151 @@ func FormatInt(value int) string {
|
||||
return strconv.Itoa(value)
|
||||
}
|
||||
|
||||
func Pow1024(n int) int64 {
|
||||
if n <= 0 {
|
||||
return 1
|
||||
}
|
||||
if n == 1 {
|
||||
return 1024
|
||||
}
|
||||
return Pow1024(n-1) * 1024
|
||||
}
|
||||
|
||||
func FormatBytes(bytes int64) string {
|
||||
if bytes < 1024 {
|
||||
if bytes < Pow1024(1) {
|
||||
return FormatInt64(bytes) + "B"
|
||||
} else if bytes < 1024*1024 {
|
||||
return fmt.Sprintf("%.2fKB", float64(bytes)/1024)
|
||||
} else if bytes < 1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fMB", float64(bytes)/1024/1024)
|
||||
} else if bytes < 1024*1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fGB", float64(bytes)/1024/1024/1024)
|
||||
} else if bytes < 1024*1024*1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fTB", float64(bytes)/1024/1024/1024/1024)
|
||||
} else if bytes < 1024*1024*1024*1024*1024*1024 {
|
||||
return fmt.Sprintf("%.2fPB", float64(bytes)/1024/1024/1024/1024/1024)
|
||||
} else if bytes < Pow1024(2) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fKB", float64(bytes)/float64(Pow1024(1))))
|
||||
} else if bytes < Pow1024(3) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fMB", float64(bytes)/float64(Pow1024(2))))
|
||||
} else if bytes < Pow1024(4) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fGB", float64(bytes)/float64(Pow1024(3))))
|
||||
} else if bytes < Pow1024(5) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fTB", float64(bytes)/float64(Pow1024(4))))
|
||||
} else if bytes < Pow1024(6) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fPB", float64(bytes)/float64(Pow1024(5))))
|
||||
} else {
|
||||
return fmt.Sprintf("%.2fEB", float64(bytes)/1024/1024/1024/1024/1024/1024)
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6))))
|
||||
}
|
||||
}
|
||||
|
||||
func FormatBits(bits int64) string {
|
||||
if bits < 1000 {
|
||||
return FormatInt64(bits) + "B"
|
||||
} else if bits < 1000*1000 {
|
||||
return fmt.Sprintf("%.2fKB", float64(bits)/1000)
|
||||
} else if bits < 1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fMB", float64(bits)/1000/1000)
|
||||
} else if bits < 1000*1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fGB", float64(bits)/1000/1000/1000)
|
||||
} else if bits < 1000*1000*1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fTB", float64(bits)/1000/1000/1000/1000)
|
||||
} else if bits < 1000*1000*1000*1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fPB", float64(bits)/1000/1000/1000/1000/1000)
|
||||
if bits < Pow1024(1) {
|
||||
return FormatInt64(bits) + "bps"
|
||||
} else if bits < Pow1024(2) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fKbps", float64(bits)/float64(Pow1024(1))))
|
||||
} else if bits < Pow1024(3) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fMbps", float64(bits)/float64(Pow1024(2))))
|
||||
} else if bits < Pow1024(4) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fGbps", float64(bits)/float64(Pow1024(3))))
|
||||
} else if bits < Pow1024(5) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fTbps", float64(bits)/float64(Pow1024(4))))
|
||||
} else if bits < Pow1024(6) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fPbps", float64(bits)/float64(Pow1024(5))))
|
||||
} else {
|
||||
return fmt.Sprintf("%.2fEB", float64(bits)/1000/1000/1000/1000/1000/1000)
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.4fEbps", float64(bits)/float64(Pow1024(6))))
|
||||
}
|
||||
}
|
||||
|
||||
func FormatCount(count int64) string {
|
||||
if count < 1000 {
|
||||
return types.String(count)
|
||||
}
|
||||
if count < 1000*1000 {
|
||||
return fmt.Sprintf("%.1fK", float32(count)/1000)
|
||||
}
|
||||
if count < 1000*1000*1000 {
|
||||
return fmt.Sprintf("%.1fM", float32(count)/1000/1000)
|
||||
}
|
||||
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
|
||||
}
|
||||
|
||||
func FormatFloat(f any, decimal int) string {
|
||||
if f == nil {
|
||||
return ""
|
||||
}
|
||||
switch x := f.(type) {
|
||||
case float32, float64:
|
||||
var s = fmt.Sprintf("%."+types.String(decimal)+"f", x)
|
||||
|
||||
// 分隔
|
||||
var dotIndex = strings.Index(s, ".")
|
||||
if dotIndex > 0 {
|
||||
var d = s[:dotIndex]
|
||||
var f2 = s[dotIndex:]
|
||||
f2 = strings.TrimRight(strings.TrimRight(f2, "0"), ".")
|
||||
return formatDigit(d) + f2
|
||||
}
|
||||
|
||||
return s
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
return formatDigit(types.String(x))
|
||||
case string:
|
||||
return x
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func FormatFloat2(f any) string {
|
||||
return FormatFloat(f, 2)
|
||||
}
|
||||
|
||||
// PadFloatZero 为浮点型数字字符串填充足够的0
|
||||
func PadFloatZero(s string, countZero int) string {
|
||||
if countZero <= 0 {
|
||||
return s
|
||||
}
|
||||
if len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
var index = strings.Index(s, ".")
|
||||
if index < 0 {
|
||||
return s + "." + strings.Repeat("0", countZero)
|
||||
}
|
||||
var decimalLen = len(s) - 1 - index
|
||||
if decimalLen < countZero {
|
||||
return s + strings.Repeat("0", countZero-decimalLen)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
|
||||
|
||||
// TrimZeroSuffix 去除小数数字尾部多余的0
|
||||
func TrimZeroSuffix(s string) string {
|
||||
var matches = decimalReg.FindStringSubmatch(s)
|
||||
if len(matches) < 3 {
|
||||
return s
|
||||
}
|
||||
return strings.TrimRight(strings.TrimRight(matches[1], "0"), ".") + matches[2]
|
||||
}
|
||||
|
||||
func formatDigit(d string) string {
|
||||
if len(d) == 0 {
|
||||
return d
|
||||
}
|
||||
|
||||
var prefix = ""
|
||||
if d[0] < '0' || d[0] > '9' {
|
||||
prefix = d[:1]
|
||||
d = d[1:]
|
||||
}
|
||||
|
||||
var l = len(d)
|
||||
if l > 3 {
|
||||
var pieces = l / 3
|
||||
var commIndex = l - pieces*3
|
||||
var d2 = ""
|
||||
if commIndex > 0 {
|
||||
d2 = d[:commIndex] + ", "
|
||||
}
|
||||
for i := 0; i < pieces; i++ {
|
||||
d2 += d[commIndex+i*3 : commIndex+i*3+3]
|
||||
if i != pieces-1 {
|
||||
d2 += ", "
|
||||
}
|
||||
}
|
||||
return prefix + d2
|
||||
}
|
||||
return prefix + d
|
||||
}
|
||||
|
||||
86
internal/utils/numberutils/utils_test.go
Normal file
86
internal/utils/numberutils/utils_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package numberutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatBytes(t *testing.T) {
|
||||
t.Log(numberutils.FormatBytes(1))
|
||||
t.Log(numberutils.FormatBytes(1000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(1_000_000_000_000_000_000))
|
||||
t.Log(numberutils.FormatBytes(9_000_000_000_000_000_000))
|
||||
}
|
||||
|
||||
func TestFormatCount(t *testing.T) {
|
||||
t.Log(numberutils.FormatCount(1))
|
||||
t.Log(numberutils.FormatCount(1000))
|
||||
t.Log(numberutils.FormatCount(1500))
|
||||
t.Log(numberutils.FormatCount(1_000_000))
|
||||
t.Log(numberutils.FormatCount(1_500_000))
|
||||
t.Log(numberutils.FormatCount(1_000_000_000))
|
||||
t.Log(numberutils.FormatCount(1_500_000_000))
|
||||
}
|
||||
|
||||
func TestFormatFloat(t *testing.T) {
|
||||
t.Log(numberutils.FormatFloat(1, 2))
|
||||
t.Log(numberutils.FormatFloat(100.23456, 2))
|
||||
t.Log(numberutils.FormatFloat(100.000023, 2))
|
||||
t.Log(numberutils.FormatFloat(100.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123.012, 2))
|
||||
t.Log(numberutils.FormatFloat(1234.012, 2))
|
||||
t.Log(numberutils.FormatFloat(12345.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123456.012, 2))
|
||||
t.Log(numberutils.FormatFloat(1234567.012, 2))
|
||||
t.Log(numberutils.FormatFloat(12345678.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123456789.012, 2))
|
||||
t.Log(numberutils.FormatFloat(1234567890.012, 2))
|
||||
t.Log(numberutils.FormatFloat(123, 2))
|
||||
t.Log(numberutils.FormatFloat(1234, 2))
|
||||
t.Log(numberutils.FormatFloat(1234.00001, 4))
|
||||
t.Log(numberutils.FormatFloat(1234.56700, 4))
|
||||
t.Log(numberutils.FormatFloat(-1234.56700, 2))
|
||||
t.Log(numberutils.FormatFloat(-221745.12, 2))
|
||||
}
|
||||
|
||||
func TestFormatFloat2(t *testing.T) {
|
||||
t.Log(numberutils.FormatFloat2(0))
|
||||
t.Log(numberutils.FormatFloat2(0.0))
|
||||
t.Log(numberutils.FormatFloat2(1.23456))
|
||||
t.Log(numberutils.FormatFloat2(1.0))
|
||||
}
|
||||
|
||||
func TestPadFloatZero(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(numberutils.PadFloatZero("1", 0) == "1")
|
||||
a.IsTrue(numberutils.PadFloatZero("1", 2) == "1.00")
|
||||
a.IsTrue(numberutils.PadFloatZero("1.1", 2) == "1.10")
|
||||
a.IsTrue(numberutils.PadFloatZero("1.12", 2) == "1.12")
|
||||
a.IsTrue(numberutils.PadFloatZero("1.123", 2) == "1.123")
|
||||
a.IsTrue(numberutils.PadFloatZero("10000.123", 2) == "10000.123")
|
||||
a.IsTrue(numberutils.PadFloatZero("", 2) == "0.00")
|
||||
}
|
||||
|
||||
func TestTrimZeroSuffix(t *testing.T) {
|
||||
for _, s := range []string{
|
||||
"1",
|
||||
"1.0000",
|
||||
"1.10",
|
||||
"100",
|
||||
"100.0000",
|
||||
"100.0",
|
||||
"100.0123",
|
||||
"100.0010",
|
||||
"100.000KB",
|
||||
"100.010MB",
|
||||
} {
|
||||
t.Log(s, "=>", numberutils.TrimZeroSuffix(s))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build linux
|
||||
//go:build linux
|
||||
|
||||
package utils
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
var systemdServiceFile = "/etc/systemd/system/edge-admin.service"
|
||||
var initServiceFile = "/etc/init.d/" + teaconst.SystemdServiceName
|
||||
|
||||
// 安装服务
|
||||
// Install 安装服务
|
||||
func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can install the service")
|
||||
@@ -30,7 +29,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
return this.installSystemdService(systemd, exePath, args)
|
||||
}
|
||||
|
||||
// 启动服务
|
||||
// Start 启动服务
|
||||
func (this *ServiceManager) Start() error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can start the service")
|
||||
@@ -47,7 +46,7 @@ func (this *ServiceManager) Start() error {
|
||||
return exec.Command("service", teaconst.ProcessName, "start").Start()
|
||||
}
|
||||
|
||||
// 删除服务
|
||||
// Uninstall 删除服务
|
||||
func (this *ServiceManager) Uninstall() error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can uninstall the service")
|
||||
@@ -83,13 +82,13 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er
|
||||
return errors.New("'scripts/" + shortName + "' file not exists")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(scriptFile)
|
||||
data, err := os.ReadFile(scriptFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = regexp.MustCompile("INSTALL_DIR=.+").ReplaceAll(data, []byte("INSTALL_DIR="+Tea.Root))
|
||||
err = ioutil.WriteFile(initServiceFile, data, 0777)
|
||||
err = os.WriteFile(initServiceFile, data, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -112,7 +111,8 @@ func (this *ServiceManager) installSystemdService(systemd, exePath string, args
|
||||
shortName := teaconst.SystemdServiceName
|
||||
longName := "GoEdge Admin" // TODO 将来可以修改
|
||||
|
||||
desc := `# Provides: ` + shortName + `
|
||||
desc := `### BEGIN INIT INFO
|
||||
# Provides: ` + shortName + `
|
||||
# Required-Start: $all
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
@@ -128,7 +128,7 @@ After=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1s
|
||||
RestartSec=5s
|
||||
ExecStart=` + exePath + ` daemon
|
||||
ExecStop=` + exePath + ` stop
|
||||
ExecReload=` + exePath + ` reload
|
||||
@@ -137,7 +137,7 @@ ExecReload=` + exePath + ` reload
|
||||
WantedBy=multi-user.target`
|
||||
|
||||
// write file
|
||||
err := ioutil.WriteFile(systemdServiceFile, []byte(desc), 0777)
|
||||
err := os.WriteFile(systemdServiceFile, []byte(desc), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !linux,!windows
|
||||
//go:build !linux && !windows
|
||||
|
||||
package utils
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package utils
|
||||
|
||||
|
||||
10
internal/utils/sizes/sizes.go
Normal file
10
internal/utils/sizes/sizes.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes
|
||||
|
||||
const (
|
||||
K int64 = 1024
|
||||
M = 1024 * K
|
||||
G = 1024 * M
|
||||
T = 1024 * G
|
||||
)
|
||||
17
internal/utils/sizes/sizes_test.go
Normal file
17
internal/utils/sizes/sizes_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSizes(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(sizes.K == 1024)
|
||||
a.IsTrue(sizes.M == 1024*1024)
|
||||
a.IsTrue(sizes.G == 1024*1024*1024)
|
||||
a.IsTrue(sizes.T == 1024*1024*1024*1024)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// format address
|
||||
// FormatAddress format address
|
||||
func FormatAddress(addr string) string {
|
||||
if strings.HasSuffix(addr, "unix:") {
|
||||
return addr
|
||||
@@ -17,7 +17,7 @@ func FormatAddress(addr string) string {
|
||||
return addr
|
||||
}
|
||||
|
||||
// 分割数字
|
||||
// SplitNumbers 分割数字
|
||||
func SplitNumbers(numbers string) (result []int64) {
|
||||
if len(numbers) == 0 {
|
||||
return
|
||||
|
||||
67
internal/utils/strings_stream.go
Normal file
67
internal/utils/strings_stream.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FilterNotEmpty(item string) bool {
|
||||
return len(item) > 0
|
||||
}
|
||||
|
||||
func MapAddPrefixFunc(prefix string) func(item string) string {
|
||||
return func(item string) string {
|
||||
if !strings.HasPrefix(item, prefix) {
|
||||
return prefix + item
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
type StringsStream struct {
|
||||
s []string
|
||||
}
|
||||
|
||||
func NewStringsStream(s []string) *StringsStream {
|
||||
return &StringsStream{s: s}
|
||||
}
|
||||
|
||||
func (this *StringsStream) Map(f ...func(item string) string) *StringsStream {
|
||||
for index, item := range this.s {
|
||||
for _, f1 := range f {
|
||||
item = f1(item)
|
||||
}
|
||||
this.s[index] = item
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *StringsStream) Filter(f ...func(item string) bool) *StringsStream {
|
||||
for _, f1 := range f {
|
||||
var newStrings = []string{}
|
||||
for _, item := range this.s {
|
||||
if f1(item) {
|
||||
newStrings = append(newStrings, item)
|
||||
}
|
||||
}
|
||||
this.s = newStrings
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *StringsStream) Unique() *StringsStream {
|
||||
var newStrings = []string{}
|
||||
for _, item := range this.s {
|
||||
if !lists.ContainsString(newStrings, item) {
|
||||
newStrings = append(newStrings, item)
|
||||
}
|
||||
}
|
||||
this.s = newStrings
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *StringsStream) Result() []string {
|
||||
return this.s
|
||||
}
|
||||
25
internal/utils/strings_stream_test.go
Normal file
25
internal/utils/strings_stream_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringsStream_Filter(t *testing.T) {
|
||||
var stream = utils.NewStringsStream([]string{"a", "b", "1", "2", "", "png", "a"})
|
||||
stream.Filter(func(item string) bool {
|
||||
return len(item) > 0
|
||||
})
|
||||
t.Log(stream.Result())
|
||||
stream.Map(func(item string) string {
|
||||
return "." + item
|
||||
})
|
||||
t.Log(stream.Result())
|
||||
stream.Unique()
|
||||
t.Log(stream.Result())
|
||||
stream.Map(strings.ToUpper, strings.ToLower)
|
||||
t.Log(stream.Result())
|
||||
}
|
||||
56
internal/utils/taskutils/concurrent.go
Normal file
56
internal/utils/taskutils/concurrent.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package taskutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func RunConcurrent(tasks any, concurrent int, f func(task any)) error {
|
||||
if tasks == nil {
|
||||
return nil
|
||||
}
|
||||
var tasksValue = reflect.ValueOf(tasks)
|
||||
if tasksValue.Type().Kind() != reflect.Slice {
|
||||
return errors.New("ony works for slice")
|
||||
}
|
||||
|
||||
var countTasks = tasksValue.Len()
|
||||
if countTasks == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if concurrent <= 0 {
|
||||
concurrent = 8
|
||||
}
|
||||
if concurrent > countTasks {
|
||||
concurrent = countTasks
|
||||
}
|
||||
|
||||
var taskChan = make(chan any, countTasks)
|
||||
for i := 0; i < countTasks; i++ {
|
||||
taskChan <- tasksValue.Index(i).Interface()
|
||||
}
|
||||
|
||||
var wg = &sync.WaitGroup{}
|
||||
wg.Add(concurrent)
|
||||
for i := 0; i < concurrent; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case task := <-taskChan:
|
||||
f(task)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
17
internal/utils/taskutils/concurrent_test.go
Normal file
17
internal/utils/taskutils/concurrent_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package taskutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/taskutils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunConcurrent(t *testing.T) {
|
||||
err := taskutils.RunConcurrent([]string{"a", "b", "c", "d", "e"}, 3, func(task any) {
|
||||
t.Log("run", task)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
55
internal/utils/time.go
Normal file
55
internal/utils/time.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// RangeTimes 计算时间点
|
||||
func RangeTimes(timeFrom string, timeTo string, everyMinutes int32) (result []string, err error) {
|
||||
if everyMinutes <= 0 {
|
||||
return nil, errors.New("invalid 'everyMinutes'")
|
||||
}
|
||||
|
||||
var reg = regexp.MustCompile(`^\d{4}$`)
|
||||
if !reg.MatchString(timeFrom) {
|
||||
return nil, errors.New("invalid timeFrom '" + timeFrom + "'")
|
||||
}
|
||||
if !reg.MatchString(timeTo) {
|
||||
return nil, errors.New("invalid timeTo '" + timeTo + "'")
|
||||
}
|
||||
|
||||
if timeFrom > timeTo {
|
||||
// swap
|
||||
timeFrom, timeTo = timeTo, timeFrom
|
||||
}
|
||||
|
||||
var everyMinutesInt = int(everyMinutes)
|
||||
|
||||
var fromHour = types.Int(timeFrom[:2])
|
||||
var fromMinute = types.Int(timeFrom[2:])
|
||||
var toHour = types.Int(timeTo[:2])
|
||||
var toMinute = types.Int(timeTo[2:])
|
||||
|
||||
if fromMinute%everyMinutesInt == 0 {
|
||||
result = append(result, timeFrom)
|
||||
}
|
||||
|
||||
for {
|
||||
fromMinute += everyMinutesInt
|
||||
if fromMinute > 59 {
|
||||
fromHour += fromMinute / 60
|
||||
fromMinute = fromMinute % 60
|
||||
}
|
||||
if fromHour > toHour || (fromHour == toHour && fromMinute > toMinute) {
|
||||
break
|
||||
}
|
||||
result = append(result, fmt.Sprintf("%02d%02d", fromHour, fromMinute))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
298
internal/utils/upgrade_manager.go
Normal file
298
internal/utils/upgrade_manager.go
Normal file
@@ -0,0 +1,298 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UpgradeFileWriter struct {
|
||||
rawWriter io.Writer
|
||||
written int64
|
||||
}
|
||||
|
||||
func NewUpgradeFileWriter(rawWriter io.Writer) *UpgradeFileWriter {
|
||||
return &UpgradeFileWriter{rawWriter: rawWriter}
|
||||
}
|
||||
|
||||
func (this *UpgradeFileWriter) Write(p []byte) (n int, err error) {
|
||||
n, err = this.rawWriter.Write(p)
|
||||
this.written += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func (this *UpgradeFileWriter) TotalWritten() int64 {
|
||||
return this.written
|
||||
}
|
||||
|
||||
type UpgradeManager struct {
|
||||
client *http.Client
|
||||
|
||||
component string
|
||||
|
||||
newVersion string
|
||||
contentLength int64
|
||||
isDownloading bool
|
||||
writer *UpgradeFileWriter
|
||||
body io.ReadCloser
|
||||
isCancelled bool
|
||||
|
||||
downloadURL string
|
||||
}
|
||||
|
||||
func NewUpgradeManager(component string, downloadURL string) *UpgradeManager {
|
||||
return &UpgradeManager{
|
||||
component: component,
|
||||
downloadURL: downloadURL,
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
CheckRedirect: nil,
|
||||
Jar: nil,
|
||||
Timeout: 30 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) Start() error {
|
||||
if this.isDownloading {
|
||||
return errors.New("another process is running")
|
||||
}
|
||||
|
||||
this.isDownloading = true
|
||||
|
||||
defer func() {
|
||||
this.client.CloseIdleConnections()
|
||||
this.isDownloading = false
|
||||
}()
|
||||
|
||||
// 检查unzip
|
||||
unzipExe, _ := exec.LookPath("unzip")
|
||||
if len(unzipExe) == 0 {
|
||||
// TODO install unzip automatically or pack with a static 'unzip' file
|
||||
return errors.New("can not find 'unzip' command")
|
||||
}
|
||||
|
||||
// 检查cp
|
||||
cpExe, _ := exec.LookPath("cp")
|
||||
if len(cpExe) == 0 {
|
||||
return errors.New("can not find 'cp' command")
|
||||
}
|
||||
|
||||
// 检查新版本
|
||||
var downloadURL = this.downloadURL
|
||||
if len(downloadURL) == 0 {
|
||||
var url = teaconst.UpdatesURL
|
||||
var osName = runtime.GOOS
|
||||
if Tea.IsTesting() && osName == "darwin" {
|
||||
osName = "linux"
|
||||
}
|
||||
url = strings.ReplaceAll(url, "${os}", osName)
|
||||
url = strings.ReplaceAll(url, "${arch}", runtime.GOARCH)
|
||||
url = strings.ReplaceAll(url, "${version}", teaconst.Version)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return errors.New("create url request failed: " + err.Error())
|
||||
}
|
||||
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
|
||||
|
||||
resp, err := this.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.New("read latest version failed: " + err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("read latest version failed: invalid response code '" + types.String(resp.StatusCode) + "'")
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("read latest version failed: " + err.Error())
|
||||
}
|
||||
|
||||
var m = maps.Map{}
|
||||
err = json.Unmarshal(data, &m)
|
||||
if err != nil {
|
||||
return errors.New("invalid response data: " + err.Error() + ", origin data: " + string(data))
|
||||
}
|
||||
|
||||
var code = m.GetInt("code")
|
||||
if code != 200 {
|
||||
return errors.New(m.GetString("message"))
|
||||
}
|
||||
|
||||
var dataMap = m.GetMap("data")
|
||||
var downloadHost = dataMap.GetString("host")
|
||||
var versions = dataMap.GetSlice("versions")
|
||||
var downloadPath = ""
|
||||
for _, component := range versions {
|
||||
var componentMap = maps.NewMap(component)
|
||||
if componentMap.Has("version") {
|
||||
if componentMap.GetString("code") == this.component {
|
||||
var version = componentMap.GetString("version")
|
||||
if stringutil.VersionCompare(version, teaconst.Version) > 0 {
|
||||
this.newVersion = version
|
||||
downloadPath = componentMap.GetString("url")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(downloadPath) == 0 {
|
||||
return errors.New("no latest version to download")
|
||||
}
|
||||
|
||||
downloadURL = downloadHost + downloadPath
|
||||
}
|
||||
|
||||
{
|
||||
req, err := http.NewRequest(http.MethodGet, downloadURL, nil)
|
||||
if err != nil {
|
||||
return errors.New("create download request failed: " + err.Error())
|
||||
}
|
||||
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
|
||||
|
||||
resp, err := this.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.New("download failed: " + downloadURL + ": " + err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("download failed: " + downloadURL + ": invalid response code '" + types.String(resp.StatusCode) + "'")
|
||||
}
|
||||
|
||||
this.contentLength = resp.ContentLength
|
||||
this.body = resp.Body
|
||||
|
||||
// download to tmp
|
||||
var tmpDir = os.TempDir()
|
||||
var filename = filepath.Base(downloadURL)
|
||||
|
||||
var destFile = tmpDir + "/" + filename
|
||||
_ = os.Remove(destFile)
|
||||
|
||||
fp, err := os.Create(destFile)
|
||||
if err != nil {
|
||||
return errors.New("create file failed: " + err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// 删除安装文件
|
||||
_ = os.Remove(destFile)
|
||||
}()
|
||||
|
||||
this.writer = NewUpgradeFileWriter(fp)
|
||||
|
||||
_, err = io.Copy(this.writer, resp.Body)
|
||||
if err != nil {
|
||||
_ = fp.Close()
|
||||
if this.isCancelled {
|
||||
return nil
|
||||
}
|
||||
return errors.New("download failed: " + err.Error())
|
||||
}
|
||||
|
||||
_ = fp.Close()
|
||||
|
||||
// unzip
|
||||
var unzipDir = tmpDir + "/edge-" + this.component + "-tmp"
|
||||
stat, err := os.Stat(unzipDir)
|
||||
if err == nil && stat.IsDir() {
|
||||
err = os.RemoveAll(unzipDir)
|
||||
if err != nil {
|
||||
return errors.New("remove old dir '" + unzipDir + "' failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
|
||||
var unzipStderr = &bytes.Buffer{}
|
||||
unzipCmd.Stderr = unzipStderr
|
||||
err = unzipCmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("unzip installation file failed: " + err.Error() + ": " + unzipStderr.String())
|
||||
}
|
||||
|
||||
installationFiles, err := filepath.Glob(unzipDir + "/edge-" + this.component + "/*")
|
||||
if err != nil {
|
||||
return errors.New("lookup installation files failed: " + err.Error())
|
||||
}
|
||||
|
||||
// cp to target dir
|
||||
currentExe, err := os.Executable()
|
||||
if err != nil {
|
||||
return errors.New("reveal current executable file path failed: " + err.Error())
|
||||
}
|
||||
var targetDir = filepath.Dir(filepath.Dir(currentExe))
|
||||
if !Tea.IsTesting() {
|
||||
for _, installationFile := range installationFiles {
|
||||
var cpCmd = exec.Command(cpExe, "-R", "-f", installationFile, targetDir)
|
||||
var cpStderr = &bytes.Buffer{}
|
||||
cpCmd.Stderr = cpStderr
|
||||
err = cpCmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("overwrite installation files failed: '" + cpCmd.String() + "': " + cpStderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove tmp
|
||||
_ = os.RemoveAll(unzipDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) IsDownloading() bool {
|
||||
return this.isDownloading
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) Progress() float32 {
|
||||
if this.contentLength <= 0 {
|
||||
return -1
|
||||
}
|
||||
if this.writer == nil {
|
||||
return -1
|
||||
}
|
||||
return float32(this.writer.TotalWritten()) / float32(this.contentLength)
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) NewVersion() string {
|
||||
return this.newVersion
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) Cancel() error {
|
||||
this.isCancelled = true
|
||||
this.isDownloading = false
|
||||
|
||||
if this.body != nil {
|
||||
_ = this.body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
35
internal/utils/upgrade_manager_test.go
Normal file
35
internal/utils/upgrade_manager_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewUpgradeManager(t *testing.T) {
|
||||
var manager = utils.NewUpgradeManager("admin", "")
|
||||
|
||||
var ticker = time.NewTicker(2 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
if manager.IsDownloading() {
|
||||
t.Logf("%.2f%%", manager.Progress()*100)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
/**go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
if manager.IsDownloading() {
|
||||
t.Log("cancel downloading")
|
||||
_ = manager.Cancel()
|
||||
}
|
||||
}()**/
|
||||
|
||||
err := manager.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
15
internal/web/actions/actionutils/action_interface.go
Normal file
15
internal/web/actions/actionutils/action_interface.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type ActionInterface interface {
|
||||
RPC() *rpc.RPCClient
|
||||
|
||||
AdminContext() context.Context
|
||||
|
||||
ViewData() maps.Map
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package actionutils
|
||||
|
||||
// 子菜单定义
|
||||
// Menu 子菜单定义
|
||||
type Menu struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -11,14 +11,14 @@ type Menu struct {
|
||||
CountNormalItems int `json:"countNormalItems"`
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
// NewMenu 获取新对象
|
||||
func NewMenu() *Menu {
|
||||
return &Menu{
|
||||
Items: []*MenuItem{},
|
||||
}
|
||||
}
|
||||
|
||||
// 添加菜单项
|
||||
// Add 添加菜单项
|
||||
func (this *Menu) Add(name string, subName string, url string, isActive bool) *MenuItem {
|
||||
item := &MenuItem{
|
||||
Name: name,
|
||||
@@ -36,7 +36,7 @@ func (this *Menu) Add(name string, subName string, url string, isActive bool) *M
|
||||
return item
|
||||
}
|
||||
|
||||
// 添加特殊菜单项,不计数
|
||||
// AddSpecial 添加特殊菜单项,不计数
|
||||
func (this *Menu) AddSpecial(name string, subName string, url string, isActive bool) *MenuItem {
|
||||
item := this.Add(name, subName, url, isActive)
|
||||
this.CountNormalItems--
|
||||
|
||||
@@ -5,20 +5,20 @@ import (
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
)
|
||||
|
||||
// 菜单分组
|
||||
// MenuGroup 菜单分组
|
||||
type MenuGroup struct {
|
||||
Menus []*Menu `json:"menus"`
|
||||
AlwaysMenu *Menu `json:"alwaysMenu"`
|
||||
}
|
||||
|
||||
// 获取新菜单分组对象
|
||||
// NewMenuGroup 获取新菜单分组对象
|
||||
func NewMenuGroup() *MenuGroup {
|
||||
return &MenuGroup{
|
||||
Menus: []*Menu{},
|
||||
}
|
||||
}
|
||||
|
||||
// 查找菜单,如果找不到则自动创建
|
||||
// FindMenu 查找菜单,如果找不到则自动创建
|
||||
func (this *MenuGroup) FindMenu(menuId string, menuName string) *Menu {
|
||||
for _, m := range this.Menus {
|
||||
if m.Id == menuId {
|
||||
@@ -33,7 +33,7 @@ func (this *MenuGroup) FindMenu(menuId string, menuName string) *Menu {
|
||||
return menu
|
||||
}
|
||||
|
||||
// 排序
|
||||
// Sort 排序
|
||||
func (this *MenuGroup) Sort() {
|
||||
lists.Sort(this.Menus, func(i int, j int) bool {
|
||||
menu1 := this.Menus[i]
|
||||
@@ -42,7 +42,7 @@ func (this *MenuGroup) Sort() {
|
||||
})
|
||||
}
|
||||
|
||||
// 设置子菜单
|
||||
// SetSubMenu 设置子菜单
|
||||
func SetSubMenu(action actions.ActionWrapper, menu *MenuGroup) {
|
||||
action.Object().Data["teaSubMenus"] = menu
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package actionutils
|
||||
|
||||
// 菜单项
|
||||
// MenuItem 菜单项
|
||||
type MenuItem struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -111,7 +111,7 @@ func (this *Page) AsHTML() string {
|
||||
}
|
||||
|
||||
// 每页数
|
||||
result = append(result, `<select class="ui dropdown" style="height:34px;padding-top:0;padding-bottom:0;margin-left:1em;color:#666" onchange="ChangePageSize(this.value)">
|
||||
result = append(result, `<select class="ui dropdown" style="padding-top:0;padding-bottom:0;margin-left:1em;color:#666" onchange="ChangePageSize(this.value)">
|
||||
<option value="10">[每页]</option>`+this.renderSizeOption(10)+
|
||||
this.renderSizeOption(20)+
|
||||
this.renderSizeOption(30)+
|
||||
@@ -127,7 +127,7 @@ func (this *Page) AsHTML() string {
|
||||
return `<div class="page">` + strings.Join(result, "") + `</div>`
|
||||
}
|
||||
|
||||
// 判断是否为最后一页
|
||||
// IsLastPage 判断是否为最后一页
|
||||
func (this *Page) IsLastPage() bool {
|
||||
return this.Current == this.Max
|
||||
}
|
||||
|
||||
@@ -4,12 +4,17 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
@@ -18,6 +23,8 @@ type ParentAction struct {
|
||||
actions.ActionObject
|
||||
|
||||
rpcClient *rpc.RPCClient
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Parent 可以调用自身的一个简便方法
|
||||
@@ -31,7 +38,7 @@ func (this *ParentAction) ErrorPage(err error) {
|
||||
}
|
||||
|
||||
// 日志
|
||||
this.CreateLog(oplogs.LevelError, "系统发生错误:%s", err.Error())
|
||||
this.CreateLog(oplogs.LevelError, codes.AdminCommon_LogSystemError, err.Error())
|
||||
|
||||
if this.Request.Method == http.MethodGet {
|
||||
FailPage(this, err)
|
||||
@@ -45,14 +52,25 @@ func (this *ParentAction) ErrorText(err string) {
|
||||
}
|
||||
|
||||
func (this *ParentAction) NotFound(name string, itemId int64) {
|
||||
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
|
||||
if itemId > 0 {
|
||||
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
|
||||
} else {
|
||||
this.ErrorPage(errors.New(name + " is not found"))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
|
||||
if len(size) > 0 {
|
||||
return NewActionPage(this, total, size[0])
|
||||
}
|
||||
return NewActionPage(this, total, 10)
|
||||
|
||||
var pageSize int64 = 10
|
||||
adminConfig, err := configloaders.LoadAdminUIConfig()
|
||||
if err == nil && adminConfig.DefaultPageSize > 0 {
|
||||
pageSize = int64(adminConfig.DefaultPageSize)
|
||||
}
|
||||
|
||||
return NewActionPage(this, total, pageSize)
|
||||
}
|
||||
|
||||
func (this *ParentAction) Nav(mainMenu string, tab string, firstMenu string) {
|
||||
@@ -74,11 +92,12 @@ func (this *ParentAction) TinyMenu(menuItem string) {
|
||||
}
|
||||
|
||||
func (this *ParentAction) AdminId() int64 {
|
||||
return this.Context.GetInt64("adminId")
|
||||
return this.Context.GetInt64(teaconst.SessionAdminId)
|
||||
}
|
||||
|
||||
func (this *ParentAction) CreateLog(level string, description string, args ...interface{}) {
|
||||
desc := fmt.Sprintf(description, args...)
|
||||
func (this *ParentAction) CreateLog(level string, messageCode langs.MessageCode, args ...any) {
|
||||
var description = messageCode.For(this.LangCode())
|
||||
var desc = fmt.Sprintf(description, args...)
|
||||
if level == oplogs.LevelInfo {
|
||||
if this.Code != 200 {
|
||||
level = oplogs.LevelWarn
|
||||
@@ -87,14 +106,14 @@ func (this *ParentAction) CreateLog(level string, description string, args ...in
|
||||
}
|
||||
}
|
||||
}
|
||||
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, this.RequestRemoteIP())
|
||||
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, this.RequestRemoteIP(), messageCode, args)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ParentAction) CreateLogInfo(description string, args ...interface{}) {
|
||||
this.CreateLog(oplogs.LevelInfo, description, args...)
|
||||
func (this *ParentAction) CreateLogInfo(messageCode langs.MessageCode, args ...any) {
|
||||
this.CreateLog(oplogs.LevelInfo, messageCode, args...)
|
||||
}
|
||||
|
||||
// RPC 获取RPC
|
||||
@@ -115,6 +134,7 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
|
||||
}
|
||||
|
||||
// AdminContext 获取Context
|
||||
// 每个请求的context都必须是一个新的实例
|
||||
func (this *ParentAction) AdminContext() context.Context {
|
||||
if this.rpcClient == nil {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
@@ -124,5 +144,27 @@ func (this *ParentAction) AdminContext() context.Context {
|
||||
}
|
||||
this.rpcClient = rpcClient
|
||||
}
|
||||
return this.rpcClient.Context(this.AdminId())
|
||||
this.ctx = this.rpcClient.Context(this.AdminId())
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
// ViewData 视图里可以使用的数据
|
||||
func (this *ParentAction) ViewData() maps.Map {
|
||||
return this.Data
|
||||
}
|
||||
|
||||
func (this *ParentAction) LangCode() string {
|
||||
return configloaders.FindAdminLangForAction(this)
|
||||
}
|
||||
|
||||
func (this *ParentAction) Lang(messageCode langs.MessageCode, args ...any) string {
|
||||
return langs.Message(this.LangCode(), messageCode, args...)
|
||||
}
|
||||
|
||||
func (this *ParentAction) FailLang(messageCode langs.MessageCode, args ...any) {
|
||||
this.Fail(langs.Message(this.LangCode(), messageCode, args...))
|
||||
}
|
||||
|
||||
func (this *ParentAction) FailFieldLang(field string, messageCode langs.MessageCode, args ...any) {
|
||||
this.FailField(field, langs.Message(this.LangCode(), messageCode, args...))
|
||||
}
|
||||
|
||||
@@ -2,41 +2,53 @@ package actionutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// Tabbar定义
|
||||
type Tabbar struct {
|
||||
items []maps.Map
|
||||
type TabItem struct {
|
||||
Name string `json:"name"`
|
||||
SubName string `json:"subName"`
|
||||
URL string `json:"url"`
|
||||
Icon string `json:"icon"`
|
||||
IsActive bool `json:"isActive"`
|
||||
IsRight bool `json:"isRight"`
|
||||
IsTitle bool `json:"isTitle"`
|
||||
IsDisabled bool `json:"isDisabled"`
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
// Tabbar Tabbar定义
|
||||
type Tabbar struct {
|
||||
items []*TabItem
|
||||
}
|
||||
|
||||
// NewTabbar 获取新对象
|
||||
func NewTabbar() *Tabbar {
|
||||
return &Tabbar{
|
||||
items: []maps.Map{},
|
||||
items: []*TabItem{},
|
||||
}
|
||||
}
|
||||
|
||||
// 添加菜单项
|
||||
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) maps.Map {
|
||||
m := maps.Map{
|
||||
"name": name,
|
||||
"subName": subName,
|
||||
"url": url,
|
||||
"icon": icon,
|
||||
"active": active,
|
||||
"right": false,
|
||||
// Add 添加菜单项
|
||||
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) *TabItem {
|
||||
var m = &TabItem{
|
||||
Name: name,
|
||||
SubName: subName,
|
||||
URL: url,
|
||||
Icon: icon,
|
||||
IsActive: active,
|
||||
IsRight: false,
|
||||
IsTitle: false,
|
||||
IsDisabled: false,
|
||||
}
|
||||
this.items = append(this.items, m)
|
||||
return m
|
||||
}
|
||||
|
||||
// 取得所有的Items
|
||||
func (this *Tabbar) Items() []maps.Map {
|
||||
// Items 取得所有的Items
|
||||
func (this *Tabbar) Items() []*TabItem {
|
||||
return this.items
|
||||
}
|
||||
|
||||
// 设置子菜单
|
||||
// SetTabbar 设置子菜单
|
||||
func SetTabbar(action actions.ActionWrapper, tabbar *Tabbar) {
|
||||
action.Object().Data["teaTabbar"] = tabbar.Items()
|
||||
}
|
||||
|
||||
@@ -1,47 +1,100 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
rpcerrors "github.com/TeaOSLab/EdgeCommon/pkg/rpc/errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Fail 提示服务器错误信息
|
||||
func Fail(action actions.ActionWrapper, err error) {
|
||||
if err != nil {
|
||||
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
|
||||
func Fail(actionPtr actions.ActionWrapper, err error) {
|
||||
if err == nil {
|
||||
err = errors.New("unknown error")
|
||||
}
|
||||
action.Object().Fail(teaconst.ErrServer + "(" + err.Error() + ")")
|
||||
|
||||
var langCode = configloaders.FindAdminLangForAction(actionPtr)
|
||||
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
|
||||
|
||||
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
|
||||
|
||||
_, _, isLocalAPI, issuesHTML := parseAPIErr(actionPtr, err)
|
||||
if isLocalAPI && len(issuesHTML) > 0 {
|
||||
actionPtr.Object().Fail(serverErrString + "(" + err.Error() + ";最近一次错误提示:" + issuesHTML + ")")
|
||||
} else {
|
||||
actionPtr.Object().Fail(serverErrString + "(" + err.Error() + ")")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FailPage 提示页面错误信息
|
||||
func FailPage(action actions.ActionWrapper, err error) {
|
||||
if err != nil {
|
||||
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
|
||||
func FailPage(actionPtr actions.ActionWrapper, err error) {
|
||||
if err == nil {
|
||||
err = errors.New("unknown error")
|
||||
}
|
||||
err = rpcerrors.HumanError(err)
|
||||
action.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
if len(action.Object().Request.Header.Get("X-Requested-With")) > 0 {
|
||||
action.Object().WriteString(teaconst.ErrServer)
|
||||
|
||||
var langCode = configloaders.FindAdminLangForAction(actionPtr)
|
||||
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
|
||||
|
||||
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
|
||||
|
||||
actionPtr.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
if len(actionPtr.Object().Request.Header.Get("X-Requested-With")) > 0 {
|
||||
actionPtr.Object().WriteString(serverErrString)
|
||||
} else {
|
||||
action.Object().WriteString(`<!DOCTYPE html>
|
||||
apiNodeIsStarting, apiNodeProgress, _, issuesHTML := parseAPIErr(actionPtr, err)
|
||||
var html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head></head>
|
||||
<head>
|
||||
<title>正在处理...</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<style type="text/css">
|
||||
hr { border-top: 1px #ccc solid; }
|
||||
.red { color: red; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
|
||||
` + teaconst.ErrServer + `
|
||||
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
|
||||
<hr style="border-top: 1px #ccc solid"/>
|
||||
<div style="color: red">Error: ` + err.Error() + `</pre>
|
||||
</div>
|
||||
`
|
||||
if apiNodeIsStarting { // API节点正在启动
|
||||
html += "<div class=\"red\">API节点正在启动,请耐心等待完成"
|
||||
|
||||
if len(apiNodeProgress) > 0 {
|
||||
html += ":" + apiNodeProgress + "(刷新当前页面查看最新状态)"
|
||||
}
|
||||
|
||||
html += "</div>"
|
||||
} else {
|
||||
html += serverErrString + `
|
||||
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
|
||||
<hr/>
|
||||
<div class="red">Error: ` + err.Error() + `</div>`
|
||||
|
||||
if len(issuesHTML) > 0 {
|
||||
html += ` <hr/>
|
||||
<div class="red">` + issuesHTML + `</div>`
|
||||
}
|
||||
}
|
||||
|
||||
actionPtr.Object().WriteString(html + `
|
||||
</div>
|
||||
</body>
|
||||
</html>`)
|
||||
}
|
||||
@@ -95,3 +148,61 @@ func findStack(err string) string {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 分析API节点的错误信息
|
||||
func parseAPIErr(action actions.ActionWrapper, err error) (apiNodeIsStarting bool, apiNodeProgress string, isLocalAPI bool, issuesHTML string) {
|
||||
// 当前API终端地址
|
||||
var apiEndpoints = []string{}
|
||||
apiConfig, apiConfigErr := configs.LoadAPIConfig()
|
||||
if apiConfigErr == nil && apiConfig != nil {
|
||||
apiEndpoints = append(apiEndpoints, apiConfig.RPC.Endpoints...)
|
||||
}
|
||||
|
||||
var isRPCConnError bool
|
||||
err, isRPCConnError = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile("api.yaml"))
|
||||
if isRPCConnError {
|
||||
// API节点是否正在启动
|
||||
var sock = gosock.NewTmpSock("edge-api")
|
||||
reply, err := sock.SendTimeout(&gosock.Command{
|
||||
Code: "starting",
|
||||
Params: nil,
|
||||
}, 1*time.Second)
|
||||
if err == nil && reply != nil {
|
||||
var params = maps.NewMap(reply.Params)
|
||||
if params.GetBool("isStarting") {
|
||||
apiNodeIsStarting = true
|
||||
|
||||
var progressMap = params.GetMap("progress")
|
||||
apiNodeProgress = progressMap.GetString("description")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 本地的一些错误提示
|
||||
if isRPCConnError {
|
||||
host, _, hostErr := net.SplitHostPort(action.Object().Request.Host)
|
||||
if hostErr == nil {
|
||||
for _, endpoint := range apiEndpoints {
|
||||
if strings.HasPrefix(endpoint, "http://"+host) || strings.HasPrefix(endpoint, "https://"+host) || strings.HasPrefix(endpoint, host) {
|
||||
isLocalAPI = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isLocalAPI {
|
||||
// 读取本地API节点的issues
|
||||
issuesData, issuesErr := os.ReadFile(Tea.Root + "/edge-api/logs/issues.log")
|
||||
if issuesErr == nil {
|
||||
var issueMaps = []maps.Map{}
|
||||
issuesErr = json.Unmarshal(issuesData, &issueMaps)
|
||||
if issuesErr == nil && len(issueMaps) > 0 {
|
||||
var issueMap = issueMaps[0]
|
||||
issuesHTML = "本地API节点启动错误:" + issueMap.GetString("message") + ",处理建议:" + issueMap.GetString("suggestion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
@@ -43,7 +44,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
defer this.CreateLogInfo("创建AccessKey %d", accessKeyIdResp.UserAccessKeyId)
|
||||
defer this.CreateLogInfo(codes.UserAccessKey_LogCreateUserAccessKey, accessKeyIdResp.UserAccessKeyId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
@@ -12,7 +13,7 @@ type DeleteAction struct {
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
AccessKeyId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo("删除AccessKey %d", params.AccessKeyId)
|
||||
defer this.CreateLogInfo(codes.UserAccessKey_LogDeleteUserAccessKey, params.AccessKeyId)
|
||||
|
||||
_, err := this.RPC().UserAccessKeyRPC().DeleteUserAccessKey(this.AdminContext(), &pb.DeleteUserAccessKeyRequest{UserAccessKeyId: params.AccessKeyId})
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
@@ -13,7 +14,7 @@ func (this *UpdateIsOnAction) RunPost(params struct {
|
||||
AccessKeyId int64
|
||||
IsOn bool
|
||||
}) {
|
||||
defer this.CreateLogInfo("设置AccessKey %d 启用状态", params.AccessKeyId)
|
||||
defer this.CreateLogInfo(codes.UserAccessKey_LogUpdateUserAccessKeyIsOn, params.AccessKeyId)
|
||||
|
||||
_, err := this.RPC().UserAccessKeyRPC().UpdateUserAccessKeyIsOn(this.AdminContext(), &pb.UpdateUserAccessKeyIsOnRequest{
|
||||
UserAccessKeyId: params.AccessKeyId,
|
||||
|
||||
@@ -45,12 +45,13 @@ func (this *AdminAction) RunGet(params struct {
|
||||
"isOn": admin.IsOn,
|
||||
"isSuper": admin.IsSuper,
|
||||
"canLogin": admin.CanLogin,
|
||||
"hasWeakPassword": admin.HasWeakPassword,
|
||||
"countAccessKeys": countAccessKeys,
|
||||
}
|
||||
|
||||
// 权限
|
||||
moduleMaps := []maps.Map{}
|
||||
for _, m := range configloaders.AllModuleMaps() {
|
||||
for _, m := range configloaders.AllModuleMaps(this.LangCode()) {
|
||||
code := m.GetString("code")
|
||||
isChecked := false
|
||||
for _, module := range admin.Modules {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user