mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 10:40:52 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31a5386cfe | ||
|
|
0198ebbbff | ||
|
|
ce3df8704d | ||
|
|
1b4ca3a684 | ||
|
|
e31c2c0955 | ||
|
|
4e389865b5 | ||
|
|
e5d8adf955 | ||
|
|
9a3181bc44 | ||
|
|
2f05c95422 | ||
|
|
bc5a3a2028 | ||
|
|
1fe91508f6 | ||
|
|
b9253c8191 | ||
|
|
7516acbc41 | ||
|
|
3b3d45da29 | ||
|
|
918d68d140 | ||
|
|
a3f56be299 | ||
|
|
f8f63890e5 | ||
|
|
9e997410ab | ||
|
|
529880642f | ||
|
|
d2590edab3 | ||
|
|
32eec68278 | ||
|
|
05ed8c9f4b | ||
|
|
bc281ee4d7 | ||
|
|
3e88314d0a | ||
|
|
141defd845 | ||
|
|
b9e49b181a | ||
|
|
102e447f78 | ||
|
|
f3de8435f1 | ||
|
|
06723407d2 | ||
|
|
db8dfb32cf | ||
|
|
a7117e4442 | ||
|
|
53414e5d70 | ||
|
|
7bbc776759 | ||
|
|
f123c0bfe1 | ||
|
|
830865ff77 | ||
|
|
15fc6f975c | ||
|
|
4582711867 | ||
|
|
1eb64720d1 | ||
|
|
29f5134fb4 | ||
|
|
55ffd85d48 | ||
|
|
075165f89d | ||
|
|
42dabbf4c0 | ||
|
|
999b5d325f | ||
|
|
027c3f584c | ||
|
|
2d3ebe6087 | ||
|
|
f016e6734a | ||
|
|
9f0503f138 | ||
|
|
f74821895e | ||
|
|
872519a213 | ||
|
|
dcd39ea6d8 | ||
|
|
a4a76ec235 | ||
|
|
87a265191c | ||
|
|
6f4736bddf | ||
|
|
e85742a293 | ||
|
|
28ea160004 | ||
|
|
99d881fc0d | ||
|
|
5d6ac7f07c | ||
|
|
a23dbfe5e1 | ||
|
|
2d818a19d1 | ||
|
|
be89fd4ff7 | ||
|
|
6d2edaaaf8 | ||
|
|
19afbd06c0 | ||
|
|
718964ab86 | ||
|
|
0dadd4e6cc | ||
|
|
57e7d756cb | ||
|
|
9f5fb30334 | ||
|
|
ec8c339f6e | ||
|
|
728d84cb15 | ||
|
|
a7b5a0f4c5 | ||
|
|
b009d3e3fd | ||
|
|
559defc8d6 | ||
|
|
98cc983473 | ||
|
|
6d42a680b1 | ||
|
|
1602f829ba | ||
|
|
c4a4764762 | ||
|
|
ac32117e78 | ||
|
|
3b5bcf8f16 | ||
|
|
8735321e34 | ||
|
|
be76ff4675 | ||
|
|
15678b029c | ||
|
|
05d47be1c8 | ||
|
|
106b34e33a | ||
|
|
488fbd2bbb | ||
|
|
b82c630e3f | ||
|
|
5a01c41bbb | ||
|
|
471e778a61 | ||
|
|
68e576a5e3 | ||
|
|
8c74fca362 | ||
|
|
026fc43695 | ||
|
|
4f925b92d0 | ||
|
|
4200d5e530 | ||
|
|
32ca72133a | ||
|
|
7708638267 | ||
|
|
cb24d3c9fa | ||
|
|
07c63e285e | ||
|
|
a5e8d5cb80 | ||
|
|
8892e8833e | ||
|
|
25af639eef | ||
|
|
58383e9022 | ||
|
|
d244650432 | ||
|
|
f338537ea0 | ||
|
|
8d2c62d6d1 | ||
|
|
8b1906c786 | ||
|
|
3f29c1aa4e | ||
|
|
31ff0476a6 | ||
|
|
b3ed824b2e | ||
|
|
ec97242415 | ||
|
|
d54faf8044 | ||
|
|
5f380b55d1 | ||
|
|
8d46115823 | ||
|
|
cc24e51cd7 | ||
|
|
7a5e6dba51 | ||
|
|
ad1ee0d2ef | ||
|
|
e4f7461b62 | ||
|
|
a9e0a934a7 | ||
|
|
2a23b838f6 | ||
|
|
1afc791e0b | ||
|
|
05cf51afa0 | ||
|
|
76b6677f5a | ||
|
|
af74afd10d | ||
|
|
46512142fa | ||
|
|
6f13cc6e6f | ||
|
|
9dab6660d0 | ||
|
|
89ddfd20cd | ||
|
|
c9bd27ec4f | ||
|
|
2e937ad5ae | ||
|
|
39d3d53614 | ||
|
|
456b4c4136 | ||
|
|
a90e536403 | ||
|
|
d96206ef66 | ||
|
|
ef71e13139 | ||
|
|
189826c5db | ||
|
|
bdecdce118 | ||
|
|
460a1750f4 | ||
|
|
d2c2ea8d3b | ||
|
|
cb12d8ffec | ||
|
|
5bb1afe5c6 | ||
|
|
7caabe9063 | ||
|
|
1695b8c0fe | ||
|
|
0927682abf | ||
|
|
cde4646fe1 | ||
|
|
8c410377b9 | ||
|
|
312aaf18de | ||
|
|
ac2979381f | ||
|
|
03c5c7bbea | ||
|
|
513de1a792 | ||
|
|
c6a8b93b70 | ||
|
|
4c8e097448 | ||
|
|
4e7a5c637d | ||
|
|
f1a51638a5 | ||
|
|
9dcc547ac7 | ||
|
|
bb47d67d0b | ||
|
|
f6a122e5ea | ||
|
|
5523d537dc | ||
|
|
5a21247cbb | ||
|
|
419c5a7c5b | ||
|
|
468ff9c1af | ||
|
|
ebb6290468 | ||
|
|
85d8565241 | ||
|
|
bd35725b3c | ||
|
|
fb6a80e306 | ||
|
|
2c9b283b75 | ||
|
|
0218405998 | ||
|
|
aa59e9a44b | ||
|
|
f9e2e22b85 | ||
|
|
d27ca56a2d | ||
|
|
bf40447ba4 | ||
|
|
89f7b99a94 | ||
|
|
c71d34cbac | ||
|
|
b97ad5cb6b | ||
|
|
081793e1fb | ||
|
|
f76b7d3b35 | ||
|
|
95448418c0 | ||
|
|
199070f829 | ||
|
|
1fa296a53e | ||
|
|
caa8bd7e7f | ||
|
|
22d097aa08 | ||
|
|
5dbfc65e7f | ||
|
|
03e6f18e54 | ||
|
|
84357e9dcc | ||
|
|
8bb8a29400 | ||
|
|
1b893f35ba | ||
|
|
5be421b4ea | ||
|
|
8ad325686d | ||
|
|
54e58c1e08 | ||
|
|
8818a779b5 | ||
|
|
cc65b6c8bb | ||
|
|
523f583516 | ||
|
|
89539deff1 | ||
|
|
6907d520ed | ||
|
|
eae2d6d17a | ||
|
|
82dd9114fd | ||
|
|
58e047fe0e | ||
|
|
d703769d43 | ||
|
|
be6ca54d9c | ||
|
|
a5cadfbcab | ||
|
|
5e3be8fcd1 | ||
|
|
8b5347dd44 | ||
|
|
24f6070f9e | ||
|
|
d00f2cd33b | ||
|
|
8276a19012 | ||
|
|
c769c598b8 | ||
|
|
d687e79b6c | ||
|
|
8fac58d73f | ||
|
|
b2ba86b212 | ||
|
|
92673b4a17 | ||
|
|
85facc039a | ||
|
|
24f33431de | ||
|
|
36533ab7e2 | ||
|
|
97d170c8b1 | ||
|
|
1413999ac3 | ||
|
|
0d23f8a962 | ||
|
|
d0b13a04cc | ||
|
|
fca7b02656 | ||
|
|
2afbfa08c6 | ||
|
|
5fe63666b0 | ||
|
|
9ee08acf29 | ||
|
|
bc7f406968 | ||
|
|
00ee90792f | ||
|
|
4a8c90365e | ||
|
|
d0e46224dc | ||
|
|
54dd90e7c5 | ||
|
|
7ef3de1367 | ||
|
|
ce6aa23d5b | ||
|
|
dc819bcf8e | ||
|
|
b0595656bb | ||
|
|
1dac54b7e7 | ||
|
|
95ea085e1e | ||
|
|
ec5f6d20cb | ||
|
|
aa288c89b3 | ||
|
|
ea419a98ae | ||
|
|
0f0c79f52f | ||
|
|
38df66a5b1 | ||
|
|
0051260171 | ||
|
|
f6dc0e020f | ||
|
|
6f2e7b9459 | ||
|
|
384e3f09da | ||
|
|
747f4552e0 | ||
|
|
1db13653bf | ||
|
|
d05304f839 | ||
|
|
0b551ddc0d | ||
|
|
f117f6b39c | ||
|
|
0c54ca95d9 | ||
|
|
bcbc8a633f | ||
|
|
73963c89fc | ||
|
|
793d14f04b | ||
|
|
ef32181f36 | ||
|
|
419f59818b | ||
|
|
2bda3a276a | ||
|
|
5e5b28ef29 | ||
|
|
8dfbe93135 | ||
|
|
810c6ca76c | ||
|
|
912b4ddb76 | ||
|
|
6b41df3497 | ||
|
|
3e62fedc73 | ||
|
|
a10ffef38f | ||
|
|
816ce31279 | ||
|
|
4e58a640e4 | ||
|
|
43925d6630 | ||
|
|
b688461137 | ||
|
|
1e85528889 | ||
|
|
eb4976ed6b | ||
|
|
7dd87220ab | ||
|
|
9927461ea9 | ||
|
|
84c7ffc509 | ||
|
|
ee52f09bcb | ||
|
|
531fc48bdf | ||
|
|
0581a7ab10 | ||
|
|
f0d2d57584 | ||
|
|
cb154437ac | ||
|
|
7ea9a8aa57 | ||
|
|
7e34f4990d | ||
|
|
f0f2c6b537 | ||
|
|
f71b3d0378 | ||
|
|
7d6e1d3e3d | ||
|
|
cf5f1b6f25 | ||
|
|
903007e6ca | ||
|
|
d6614acf66 | ||
|
|
86df495b67 | ||
|
|
71b0c10997 | ||
|
|
e273c59d78 | ||
|
|
baea40f157 | ||
|
|
a809b89bb0 | ||
|
|
89e08953cb | ||
|
|
e154cf65b6 | ||
|
|
d7162a24be | ||
|
|
16532720d7 | ||
|
|
012ee2419d | ||
|
|
7e4fe2f1b3 | ||
|
|
5a95b72607 | ||
|
|
4d27055605 | ||
|
|
e2b9578253 | ||
|
|
57fd8f378c | ||
|
|
a726f224f8 | ||
|
|
d502ac6e0f | ||
|
|
f6a828f0af | ||
|
|
0a53a7001e | ||
|
|
0ab5494744 | ||
|
|
e60cf15f39 | ||
|
|
b53db97b98 | ||
|
|
f12c75f91b | ||
|
|
3df1a14f72 | ||
|
|
712f105d34 | ||
|
|
4ea757fece | ||
|
|
d8b32f63ea | ||
|
|
771b81afaf | ||
|
|
a4f4c6ac15 | ||
|
|
ae22cf064e | ||
|
|
fbb8e1bd31 | ||
|
|
47202ef66c | ||
|
|
f46c70dcbd | ||
|
|
c7a837e33d | ||
|
|
544e2e8331 | ||
|
|
a62ccbc0db | ||
|
|
891619ad55 | ||
|
|
a3a62f65dd | ||
|
|
3131f197b8 | ||
|
|
e13d9ed51b | ||
|
|
1b5f4a0f9d | ||
|
|
c9c4043a4e | ||
|
|
bb59dd977a | ||
|
|
c2b213a37c | ||
|
|
559f4418d1 | ||
|
|
2149bca7b8 | ||
|
|
be5db82b79 | ||
|
|
01163e3ea3 | ||
|
|
7f986fdb2d | ||
|
|
38e1ba67af | ||
|
|
d3d2e0887e | ||
|
|
a2650715b3 | ||
|
|
8c0685202e | ||
|
|
5b6843b5c0 | ||
|
|
ba341b69ac | ||
|
|
ca89f03724 | ||
|
|
5461325e69 | ||
|
|
e96a2818b7 | ||
|
|
355f06a3cf | ||
|
|
f6e6cfa71c | ||
|
|
9cdfa2ef79 | ||
|
|
6fab22099d | ||
|
|
925819b3aa | ||
|
|
6f96900ab9 | ||
|
|
43bc29b25c | ||
|
|
e13ff86ff6 | ||
|
|
3f3d572932 | ||
|
|
a0ce1562ac | ||
|
|
ee84cbca51 | ||
|
|
3ed5cb48b5 | ||
|
|
499ba5fb99 | ||
|
|
c2f036c370 | ||
|
|
326580b3c8 | ||
|
|
bf59351cf9 | ||
|
|
a954fc3942 | ||
|
|
160a66af23 | ||
|
|
4edbd29b19 | ||
|
|
b6b97478d1 | ||
|
|
27a871e537 | ||
|
|
738b0f9a4a | ||
|
|
1a56dd8ac6 | ||
|
|
672d440725 | ||
|
|
3625b7c0de | ||
|
|
e75a683173 | ||
|
|
89ed8527d6 | ||
|
|
5a5f4b8065 | ||
|
|
d2e23341eb | ||
|
|
233081149b | ||
|
|
5d19ee0ab5 | ||
|
|
653b1cef20 | ||
|
|
f3ecf6e7a0 | ||
|
|
35d8f0a7e8 | ||
|
|
e863dd322a | ||
|
|
fa6dec969f | ||
|
|
58cd05fd09 | ||
|
|
2e6ca5419d | ||
|
|
a29392bb08 | ||
|
|
4dc9c9aa75 | ||
|
|
03c9f79549 | ||
|
|
46aaeb4eac | ||
|
|
4d20bb0de6 | ||
|
|
e001686627 | ||
|
|
931f02ef8e | ||
|
|
a9be3e6376 | ||
|
|
aedd369fc4 | ||
|
|
a1016b7f22 | ||
|
|
603432aa87 | ||
|
|
cd23967ae0 | ||
|
|
dfb159c4ad | ||
|
|
0318f03283 | ||
|
|
9ddf6986e4 | ||
|
|
a28ed9281e | ||
|
|
9cc776aded | ||
|
|
349f14a7fc | ||
|
|
fae26efb88 | ||
|
|
64d0bb18b1 | ||
|
|
864ce071f6 | ||
|
|
5a55934029 | ||
|
|
1f061ad059 | ||
|
|
32f0774df2 | ||
|
|
3206ff6a1e | ||
|
|
2f5e946979 | ||
|
|
5a6d3739d4 | ||
|
|
fa018b84a7 | ||
|
|
a686206e21 | ||
|
|
6132d13261 | ||
|
|
f64c78919d | ||
|
|
1a1067966c | ||
|
|
ec545cabcd | ||
|
|
40b0e90c0d | ||
|
|
5e94be21aa | ||
|
|
417ae8bbde | ||
|
|
acde7ebef6 | ||
|
|
a424e8df7c | ||
|
|
2eed5cba14 | ||
|
|
19e7c238f6 | ||
|
|
f3c939e442 | ||
|
|
3bcf3431d3 | ||
|
|
20d6308bc7 | ||
|
|
96b0127811 | ||
|
|
68cc792d6c | ||
|
|
d0f7449b57 | ||
|
|
b41c1f1d2e | ||
|
|
9a8b72fe3f | ||
|
|
b16f8b96dc | ||
|
|
128429a6a9 | ||
|
|
b849d8b526 | ||
|
|
47b06c3487 | ||
|
|
40e0d143f4 | ||
|
|
c1269d9dde | ||
|
|
6a81ba9069 | ||
|
|
61883776f2 | ||
|
|
f7752cec3a | ||
|
|
6f96c84a16 | ||
|
|
a2ce3304de | ||
|
|
ee0b47d101 | ||
|
|
3b3dd4c26d | ||
|
|
bee1f9968e | ||
|
|
65691cf7c9 | ||
|
|
7b7bcf600d | ||
|
|
18cd7bfa6f | ||
|
|
8c14a31967 | ||
|
|
6c840b435e | ||
|
|
eca615c066 | ||
|
|
22bb785139 | ||
|
|
62fca3dfe2 | ||
|
|
9936097086 | ||
|
|
50dd235e39 | ||
|
|
606bac2e43 | ||
|
|
90ede97797 | ||
|
|
1545cde58a | ||
|
|
8481d81257 | ||
|
|
0688d4f318 | ||
|
|
ab5ab951d7 | ||
|
|
0ca640acbd | ||
|
|
235f99c0ca | ||
|
|
31fdf7f12c | ||
|
|
bd6d711212 | ||
|
|
62596f27ef | ||
|
|
1b88853a8d | ||
|
|
3c5925ff67 | ||
|
|
7d5687d504 | ||
|
|
6b74cccc34 | ||
|
|
1becb26aaf | ||
|
|
bd2917822e | ||
|
|
9c82db4471 | ||
|
|
e4f11156f6 | ||
|
|
c7de19d3b5 | ||
|
|
65ade4b996 | ||
|
|
2b70851074 | ||
|
|
b7b983229b | ||
|
|
ac548433c0 | ||
|
|
02a1afb477 | ||
|
|
e959466898 | ||
|
|
5c5aef64b0 | ||
|
|
86e8b1383c | ||
|
|
9f7bf1f6b8 | ||
|
|
2ff3869608 | ||
|
|
8d21401f88 | ||
|
|
b92daedea2 | ||
|
|
021d89379a | ||
|
|
2b61204233 | ||
|
|
1da1659b51 | ||
|
|
1302a6daa0 | ||
|
|
160dbe780c | ||
|
|
d047cf0d77 | ||
|
|
0c2a2af308 | ||
|
|
bd8953ea81 | ||
|
|
a479f0fbce | ||
|
|
2e00e80e96 | ||
|
|
0bf4aec74e | ||
|
|
bcac3deb6e | ||
|
|
596f93623e | ||
|
|
c74a1bc20c | ||
|
|
795d1b938d | ||
|
|
bad247c51c | ||
|
|
950aed3522 | ||
|
|
8a7bd53f24 | ||
|
|
04230cbd1c | ||
|
|
d0a7e67138 | ||
|
|
56517fd345 | ||
|
|
6315425a7b | ||
|
|
c42299766c | ||
|
|
ef9fa02145 | ||
|
|
51869b6a84 | ||
|
|
d12953a345 | ||
|
|
9f727cd54b | ||
|
|
3de567191e | ||
|
|
b212762417 | ||
|
|
d5f619ff74 | ||
|
|
87027f8a8c | ||
|
|
6fa8a678bf | ||
|
|
f2aa355389 | ||
|
|
b3ef432c6f | ||
|
|
860a1e4dc6 | ||
|
|
f5b322649d | ||
|
|
746650b49a | ||
|
|
43ddc54a70 | ||
|
|
bdfa08bb78 | ||
|
|
07e58a484c | ||
|
|
a652e1e190 | ||
|
|
ec0deeff06 | ||
|
|
7335e2c299 | ||
|
|
003cdd1e06 | ||
|
|
efa1fcfbdf | ||
|
|
7acda0c039 | ||
|
|
cb86fda169 | ||
|
|
324d106693 | ||
|
|
0644edb62e | ||
|
|
0f31cb5fcc | ||
|
|
c80a9d1e65 | ||
|
|
b26d0b4887 | ||
|
|
ed93b919ba | ||
|
|
b1077443a3 | ||
|
|
9455ca64a2 | ||
|
|
d6ebd2699e | ||
|
|
632514d36e | ||
|
|
686baa56f4 | ||
|
|
a3006af7b8 | ||
|
|
ec529bfca6 | ||
|
|
a661b6bd38 | ||
|
|
73e85c2def | ||
|
|
6819487a43 | ||
|
|
14b20873c7 | ||
|
|
bd1ca7e975 | ||
|
|
c01ec828e2 | ||
|
|
47abdb0f36 | ||
|
|
6d4568cdcd | ||
|
|
2068ef7053 | ||
|
|
5655591215 | ||
|
|
feb22f25b8 | ||
|
|
c054259806 | ||
|
|
b179649078 | ||
|
|
a75561399d | ||
|
|
fa02df0d8f | ||
|
|
f17642b243 | ||
|
|
43773222f8 | ||
|
|
91a5e13cf2 | ||
|
|
be4efbad5d | ||
|
|
da3e63e6c3 | ||
|
|
2d427c0670 | ||
|
|
97184d22c3 | ||
|
|
f1cc7641fb | ||
|
|
ca9d742359 | ||
|
|
1d82310ca1 | ||
|
|
687afce245 | ||
|
|
26d986d4a8 | ||
|
|
120599c778 | ||
|
|
54945f2183 | ||
|
|
f48b91dc0a | ||
|
|
4c1551c309 | ||
|
|
558478be16 | ||
|
|
4e403d3d8a | ||
|
|
a79fd74130 | ||
|
|
af2f3deeab | ||
|
|
c15ff89746 | ||
|
|
5b585e0cda | ||
|
|
6619374571 | ||
|
|
9fc8fdd9d1 | ||
|
|
900d279bf6 | ||
|
|
265760146f | ||
|
|
74fa8ffcd1 | ||
|
|
e5313e63a9 | ||
|
|
f482796cdf | ||
|
|
aa08f489d7 | ||
|
|
1cc54b5da6 | ||
|
|
0baf0d23ae | ||
|
|
646443deb5 | ||
|
|
39fb23c025 | ||
|
|
698c463c3a | ||
|
|
6c87634bb3 | ||
|
|
0a9e9e972d | ||
|
|
bb0ad6c325 | ||
|
|
f2dac51a62 | ||
|
|
9534cda1f9 | ||
|
|
27226e7222 | ||
|
|
c51621ee04 | ||
|
|
c78e4447fa | ||
|
|
2d7e525024 | ||
|
|
ecee9e2acd | ||
|
|
d607619f8f | ||
|
|
e8e275cd79 | ||
|
|
4147025406 | ||
|
|
35539a4ccc | ||
|
|
b7a359714f | ||
|
|
bf43bfbd92 | ||
|
|
1265e465d0 | ||
|
|
8f195b964f | ||
|
|
a285efdd85 | ||
|
|
fafb68dbf0 | ||
|
|
3dc87329f1 | ||
|
|
7fc15518b3 | ||
|
|
eb196bd40d | ||
|
|
0cbfca979e | ||
|
|
b3ac81b7b7 | ||
|
|
bb40111d38 | ||
|
|
29a3bea6e4 | ||
|
|
ab4f167bda | ||
|
|
035c69898f | ||
|
|
00cb915f48 | ||
|
|
97baff8baf | ||
|
|
e4da2edf56 | ||
|
|
c1e08ba9f0 | ||
|
|
f00566624d | ||
|
|
2bb55294fc | ||
|
|
86c3e9a843 | ||
|
|
3a998a37f0 | ||
|
|
7eed47cd8e | ||
|
|
8cef0b6216 | ||
|
|
930541d5d6 | ||
|
|
f3518d0cad | ||
|
|
45e2c43997 | ||
|
|
6c0f932ae8 | ||
|
|
5832bf9140 | ||
|
|
47bba19bec | ||
|
|
1e58254f4c | ||
|
|
7739cef7b8 | ||
|
|
9b73392c5e | ||
|
|
79e5b2fb84 | ||
|
|
8ce1a33bd3 | ||
|
|
8ae61f1b23 | ||
|
|
a973c93c04 | ||
|
|
9c0f14aa89 | ||
|
|
f78d72db4c | ||
|
|
fb0c1c6993 | ||
|
|
4d99695e4c | ||
|
|
18321943d6 | ||
|
|
67819d2817 | ||
|
|
ad74e014ef | ||
|
|
bf661dffcf | ||
|
|
11484175c8 | ||
|
|
7bc5004683 | ||
|
|
1d01508164 | ||
|
|
4cae9bcfb9 | ||
|
|
9c84c7bd38 | ||
|
|
5a5e81eae0 | ||
|
|
b0f6bb3ea6 | ||
|
|
0c004606b5 | ||
|
|
fa4978389c | ||
|
|
8b8359b08f | ||
|
|
1d4fec3979 | ||
|
|
bb4123805c | ||
|
|
f49f605c11 | ||
|
|
5496689d7c | ||
|
|
bad8bbdb7c | ||
|
|
76184227a4 | ||
|
|
bbc9ee120d | ||
|
|
85093b2856 | ||
|
|
8c355b6159 | ||
|
|
622037ee98 | ||
|
|
54bb13aad0 | ||
|
|
61e41269ba | ||
|
|
f7cffe3d2d | ||
|
|
0cd39f0c97 | ||
|
|
c09b22358b | ||
|
|
18215b8336 | ||
|
|
f0fae716cc | ||
|
|
7092be97eb | ||
|
|
d9a9d2ee4f | ||
|
|
f86892cef0 | ||
|
|
a48b915675 | ||
|
|
5e13264393 | ||
|
|
ff481ef7f3 | ||
|
|
82a4485792 | ||
|
|
538876f529 | ||
|
|
609a31455b | ||
|
|
2d6da9ebfe | ||
|
|
594e871744 | ||
|
|
95074eb028 |
+3
-3
@@ -7,6 +7,6 @@ LOG_LEVEL='debug'
|
|||||||
|
|
||||||
SECRET='whyour'
|
SECRET='whyour'
|
||||||
|
|
||||||
QINIU_AK = ''
|
QINIU_AK=''
|
||||||
QINIU_SK = ''
|
QINIU_SK=''
|
||||||
QINIU_SCOPE = ''
|
QINIU_SCOPE=''
|
||||||
@@ -1 +1,2 @@
|
|||||||
sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli
|
sentrycli_cdnurl=https://npmmirror.com/mirrors/sentry-cli/
|
||||||
|
strict-peer-dependencies=false
|
||||||
@@ -9,16 +9,12 @@ export default defineConfig({
|
|||||||
type: 'none',
|
type: 'none',
|
||||||
},
|
},
|
||||||
fastRefresh: {},
|
fastRefresh: {},
|
||||||
// antd: {
|
|
||||||
// dark: true,
|
|
||||||
// },
|
|
||||||
mfsu: {},
|
|
||||||
esbuild: {},
|
esbuild: {},
|
||||||
webpack5: {},
|
webpack5: {},
|
||||||
dynamicImport: {
|
dynamicImport: {
|
||||||
loading: '@/components/pageLoading',
|
loading: '@/components/pageLoading',
|
||||||
},
|
},
|
||||||
favicon: '/images/g5.ico',
|
favicon: '/images/favicon.svg',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/public': {
|
'/api/public': {
|
||||||
target: 'http://127.0.0.1:5400/',
|
target: 'http://127.0.0.1:5400/',
|
||||||
|
|||||||
+6
-2
@@ -57,7 +57,7 @@ https://podman.io/getting-started/installation
|
|||||||
```bash
|
```bash
|
||||||
podman run -dit \
|
podman run -dit \
|
||||||
--network bridge \
|
--network bridge \
|
||||||
-v $PWD/ql:/ql/data \
|
-v $PWD/ql/data:/ql/data \
|
||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
--name qinglong \
|
--name qinglong \
|
||||||
--hostname qinglong \
|
--hostname qinglong \
|
||||||
@@ -96,7 +96,7 @@ systemctl restart docker
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -dit \
|
docker run -dit \
|
||||||
-v $PWD/ql:/ql/data \
|
-v $PWD/ql/data:/ql/data \
|
||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
--name qinglong \
|
--name qinglong \
|
||||||
--hostname qinglong \
|
--hostname qinglong \
|
||||||
@@ -124,6 +124,10 @@ docker-compose up -d
|
|||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. access
|
||||||
|
|
||||||
|
Open your browser and visit http://{ip}:5700
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
1. built-in commands
|
1. built-in commands
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ https://podman.io/getting-started/installation
|
|||||||
```bash
|
```bash
|
||||||
podman run -dit \
|
podman run -dit \
|
||||||
--network bridge \
|
--network bridge \
|
||||||
-v $PWD/ql:/ql/data \
|
-v $PWD/ql/data:/ql/data \
|
||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
--name qinglong \
|
--name qinglong \
|
||||||
--hostname qinglong \
|
--hostname qinglong \
|
||||||
@@ -97,7 +97,7 @@ systemctl restart docker
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -dit \
|
docker run -dit \
|
||||||
-v $PWD/ql:/ql/data \
|
-v $PWD/ql/data:/ql/data \
|
||||||
-p 5700:5700 \
|
-p 5700:5700 \
|
||||||
--name qinglong \
|
--name qinglong \
|
||||||
--hostname qinglong \
|
--hostname qinglong \
|
||||||
@@ -125,6 +125,10 @@ docker-compose up -d
|
|||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. 访问
|
||||||
|
|
||||||
|
打开你的浏览器,访问 http://{ip}:5700
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
1. 内置命令
|
1. 内置命令
|
||||||
@@ -163,11 +167,11 @@ task <file_path> desi <env_name> <account_number>
|
|||||||
|
|
||||||
* file_url: 脚本地址
|
* file_url: 脚本地址
|
||||||
* repo_url: 仓库地址
|
* repo_url: 仓库地址
|
||||||
* whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串
|
* whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串,多个竖线分割
|
||||||
* blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串
|
* blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串,多个竖线分割
|
||||||
* dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响
|
* dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响,多个竖线分割
|
||||||
|
* extensions: 拉取仓库的文件后缀,多个竖线分割
|
||||||
* branch: 拉取仓库的分支
|
* branch: 拉取仓库的分支
|
||||||
* extensions: 拉取仓库的文件后缀
|
|
||||||
* days: 需要保留的日志的天数
|
* days: 需要保留的日志的天数
|
||||||
* file_path: 任务执行时的文件路径
|
* file_path: 任务执行时的文件路径
|
||||||
* env_name: 任务执行时需要并发或者指定时的环境变量名称
|
* env_name: 任务执行时需要并发或者指定时的环境变量名称
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export default (app: Router) => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -51,7 +50,6 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -76,7 +74,6 @@ export default (app: Router) => {
|
|||||||
fs.writeFileSync(path, content);
|
fs.writeFileSync(path, content);
|
||||||
res.send({ code: 200, message: '保存成功' });
|
res.send({ code: 200, message: '保存成功' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+133
-24
@@ -2,6 +2,7 @@ import { Router, Request, Response, NextFunction } from 'express';
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import CronService from '../services/cron';
|
import CronService from '../services/cron';
|
||||||
|
import CronViewService from '../services/cronView';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
@@ -9,11 +10,135 @@ const route = Router();
|
|||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/crons', route);
|
app.use('/crons', route);
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/views',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const cronViewService = Container.get(CronViewService);
|
||||||
|
const data = await cronViewService.list();
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
'/views',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
sorts: Joi.array().optional(),
|
||||||
|
filters: Joi.array().optional(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const cronViewService = Container.get(CronViewService);
|
||||||
|
const data = await cronViewService.create(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/views',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
id: Joi.number().required(),
|
||||||
|
sorts: Joi.array().optional(),
|
||||||
|
filters: Joi.array().optional(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const cronViewService = Container.get(CronViewService);
|
||||||
|
const data = await cronViewService.update(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
'/views',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.number().required()),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const cronViewService = Container.get(CronViewService);
|
||||||
|
const data = await cronViewService.remove(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/views/move',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
fromIndex: Joi.number().required(),
|
||||||
|
toIndex: Joi.number().required(),
|
||||||
|
id: Joi.number().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const cronViewService = Container.get(CronViewService);
|
||||||
|
const data = await cronViewService.move(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/views/disable',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.number().required()),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const cronViewService = Container.get(CronViewService);
|
||||||
|
const data = await cronViewService.disabled(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/views/enable',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.number().required()),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const cronViewService = Container.get(CronViewService);
|
||||||
|
const data = await cronViewService.enabled(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const cronService = Container.get(CronService);
|
const cronService = Container.get(CronService);
|
||||||
const data = await cronService.crontabs(req.query.searchValue as string);
|
const data = await cronService.crontabs(req.query as any);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
logger.error('🔥 error: %o', e);
|
||||||
@@ -42,7 +167,6 @@ export default (app: Router) => {
|
|||||||
return res.send({ code: 400, message: 'param schedule error' });
|
return res.send({ code: 400, message: 'param schedule error' });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -60,7 +184,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.run(req.body);
|
const data = await cronService.run(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -78,7 +201,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.stop(req.body);
|
const data = await cronService.stop(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -102,7 +224,6 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,7 +244,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.addLabels(req.body.ids, req.body.labels);
|
const data = await cronService.addLabels(req.body.ids, req.body.labels);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -141,7 +261,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.disabled(req.body);
|
const data = await cronService.disabled(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -159,7 +278,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.enabled(req.body);
|
const data = await cronService.enabled(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -179,7 +297,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.log(req.params.id);
|
const data = await cronService.log(req.params.id);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -190,9 +307,9 @@ export default (app: Router) => {
|
|||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
labels: Joi.array().optional().allow(null),
|
labels: Joi.array().optional().allow(null),
|
||||||
command: Joi.string().optional(),
|
command: Joi.string().required(),
|
||||||
schedule: Joi.string().optional(),
|
schedule: Joi.string().required(),
|
||||||
name: Joi.string().optional(),
|
name: Joi.string().optional().allow(null),
|
||||||
id: Joi.number().required(),
|
id: Joi.number().required(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -210,7 +327,6 @@ export default (app: Router) => {
|
|||||||
return res.send({ code: 400, message: 'param schedule error' });
|
return res.send({ code: 400, message: 'param schedule error' });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -228,7 +344,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.remove(req.body);
|
const data = await cronService.remove(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -246,7 +361,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.pin(req.body);
|
const data = await cronService.pin(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -264,7 +378,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.unPin(req.body);
|
const data = await cronService.unPin(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -279,7 +392,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.import_crontab();
|
const data = await cronService.import_crontab();
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -299,7 +411,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.getDb({ id: req.params.id });
|
const data = await cronService.getDb({ id: req.params.id });
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -311,10 +422,10 @@ export default (app: Router) => {
|
|||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
ids: Joi.array().items(Joi.number().required()),
|
ids: Joi.array().items(Joi.number().required()),
|
||||||
status: Joi.string().required(),
|
status: Joi.string().required(),
|
||||||
pid: Joi.string().optional(),
|
pid: Joi.string().optional().allow(null),
|
||||||
log_path: Joi.string().optional(),
|
log_path: Joi.string().optional().allow(null),
|
||||||
last_running_time: Joi.number().optional(),
|
last_running_time: Joi.number().optional().allow(null),
|
||||||
last_execution_time: Joi.number().optional(),
|
last_execution_time: Joi.number().optional().allow(null),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -328,7 +439,6 @@ export default (app: Router) => {
|
|||||||
});
|
});
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -348,7 +458,6 @@ export default (app: Router) => {
|
|||||||
const data = await cronService.logs(req.params.id);
|
const data = await cronService.logs(req.params.id);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export default (app: Router) => {
|
|||||||
const data = await dependenceService.create(req.body);
|
const data = await dependenceService.create(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -61,7 +60,6 @@ export default (app: Router) => {
|
|||||||
const data = await dependenceService.update(req.body);
|
const data = await dependenceService.update(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -79,7 +77,6 @@ export default (app: Router) => {
|
|||||||
const data = await dependenceService.remove(req.body);
|
const data = await dependenceService.remove(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -97,7 +94,6 @@ export default (app: Router) => {
|
|||||||
const data = await dependenceService.remove(req.body, true);
|
const data = await dependenceService.remove(req.body, true);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -117,7 +113,6 @@ export default (app: Router) => {
|
|||||||
const data = await dependenceService.getDb({ id: req.params.id });
|
const data = await dependenceService.getDb({ id: req.params.id });
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -135,7 +130,6 @@ export default (app: Router) => {
|
|||||||
const data = await dependenceService.reInstall(req.body);
|
const data = await dependenceService.reInstall(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-9
@@ -38,7 +38,6 @@ export default (app: Router) => {
|
|||||||
const data = await envService.create(req.body);
|
const data = await envService.create(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -61,7 +60,6 @@ export default (app: Router) => {
|
|||||||
const data = await envService.update(req.body);
|
const data = await envService.update(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -79,7 +77,6 @@ export default (app: Router) => {
|
|||||||
const data = await envService.remove(req.body);
|
const data = await envService.remove(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -103,7 +100,6 @@ export default (app: Router) => {
|
|||||||
const data = await envService.move(req.params.id, req.body);
|
const data = await envService.move(req.params.id, req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -121,7 +117,6 @@ export default (app: Router) => {
|
|||||||
const data = await envService.disabled(req.body);
|
const data = await envService.disabled(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -139,7 +134,6 @@ export default (app: Router) => {
|
|||||||
const data = await envService.enabled(req.body);
|
const data = await envService.enabled(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -160,7 +154,6 @@ export default (app: Router) => {
|
|||||||
const data = await envService.updateNames(req.body);
|
const data = await envService.updateNames(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -177,10 +170,9 @@ export default (app: Router) => {
|
|||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const envService = Container.get(EnvService);
|
const envService = Container.get(EnvService);
|
||||||
const data = await envService.getDb(req.params.id);
|
const data = await envService.getDb({ id: req.params.id });
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+5
-2
@@ -6,6 +6,7 @@ import config from '../config';
|
|||||||
import { getFileContentByName, readDirs } from '../config/util';
|
import { getFileContentByName, readDirs } from '../config/util';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
const blacklist = ['.tmp'];
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/logs', route);
|
app.use('/logs', route);
|
||||||
@@ -13,7 +14,7 @@ export default (app: Router) => {
|
|||||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const result = readDirs(config.logPath, config.logPath);
|
const result = readDirs(config.logPath, config.logPath, blacklist);
|
||||||
res.send({
|
res.send({
|
||||||
code: 200,
|
code: 200,
|
||||||
data: result,
|
data: result,
|
||||||
@@ -29,6 +30,9 @@ export default (app: Router) => {
|
|||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
|
if (blacklist.includes(req.path)) {
|
||||||
|
return res.send({ code: 403, message: '暂无权限' });
|
||||||
|
}
|
||||||
const filePath = join(
|
const filePath = join(
|
||||||
config.logPath,
|
config.logPath,
|
||||||
(req.query.path || '') as string,
|
(req.query.path || '') as string,
|
||||||
@@ -37,7 +41,6 @@ export default (app: Router) => {
|
|||||||
const content = getFileContentByName(filePath);
|
const content = getFileContentByName(filePath);
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-7
@@ -16,7 +16,6 @@ export default (app: Router) => {
|
|||||||
const data = await openService.list();
|
const data = await openService.list();
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -26,7 +25,7 @@ export default (app: Router) => {
|
|||||||
'/apps',
|
'/apps',
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().optional().allow(''),
|
name: Joi.string().optional().allow('').disallow('system'),
|
||||||
scopes: Joi.array().items(Joi.string().required()),
|
scopes: Joi.array().items(Joi.string().required()),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -37,7 +36,6 @@ export default (app: Router) => {
|
|||||||
const data = await openService.create(req.body);
|
const data = await openService.create(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -59,7 +57,6 @@ export default (app: Router) => {
|
|||||||
const data = await openService.update(req.body);
|
const data = await openService.update(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -77,7 +74,6 @@ export default (app: Router) => {
|
|||||||
const data = await openService.remove(req.body);
|
const data = await openService.remove(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -97,7 +93,6 @@ export default (app: Router) => {
|
|||||||
const data = await openService.resetSecret(req.params.id);
|
const data = await openService.resetSecret(req.params.id);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -118,7 +113,6 @@ export default (app: Router) => {
|
|||||||
const result = await openService.authToken(req.query as any);
|
const result = await openService.authToken(req.query as any);
|
||||||
return res.send(result);
|
return res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+44
-31
@@ -3,6 +3,7 @@ import {
|
|||||||
getFileContentByName,
|
getFileContentByName,
|
||||||
readDirs,
|
readDirs,
|
||||||
getLastModifyFilePath,
|
getLastModifyFilePath,
|
||||||
|
readDir,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
@@ -10,17 +11,38 @@ import { Logger } from 'winston';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import path, { join } from 'path';
|
import path, { join, parse } from 'path';
|
||||||
import ScriptService from '../services/script';
|
import ScriptService from '../services/script';
|
||||||
|
import multer from 'multer';
|
||||||
const route = Router();
|
const route = Router();
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, config.scriptPath);
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
cb(null, file.originalname);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const upload = multer({ storage: storage });
|
||||||
|
|
||||||
export default (app: Router) => {
|
export default (app: Router) => {
|
||||||
app.use('/scripts', route);
|
app.use('/scripts', route);
|
||||||
|
|
||||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const result = readDirs(config.scriptPath, config.scriptPath);
|
let result = [];
|
||||||
|
const blacklist = ['node_modules', '.git'];
|
||||||
|
if (req.query.path) {
|
||||||
|
const targetPath = path.join(
|
||||||
|
config.scriptPath,
|
||||||
|
req.query.path as string,
|
||||||
|
);
|
||||||
|
result = readDir(targetPath, config.scriptPath, blacklist);
|
||||||
|
} else {
|
||||||
|
result = readDirs(config.scriptPath, config.scriptPath, blacklist);
|
||||||
|
}
|
||||||
res.send({
|
res.send({
|
||||||
code: 200,
|
code: 200,
|
||||||
data: result,
|
data: result,
|
||||||
@@ -44,7 +66,6 @@ export default (app: Router) => {
|
|||||||
const content = getFileContentByName(filePath);
|
const content = getFileContentByName(filePath);
|
||||||
res.send({ code: 200, data: content });
|
res.send({ code: 200, data: content });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -52,14 +73,7 @@ export default (app: Router) => {
|
|||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
'/',
|
'/',
|
||||||
celebrate({
|
upload.single('file'),
|
||||||
body: Joi.object({
|
|
||||||
filename: Joi.string().required(),
|
|
||||||
path: Joi.string().optional().allow(''),
|
|
||||||
content: Joi.string().allow(''),
|
|
||||||
originFilename: Joi.string().allow(''),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
@@ -69,6 +83,7 @@ export default (app: Router) => {
|
|||||||
content: string;
|
content: string;
|
||||||
originFilename: string;
|
originFilename: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!path) {
|
if (!path) {
|
||||||
path = config.scriptPath;
|
path = config.scriptPath;
|
||||||
}
|
}
|
||||||
@@ -84,15 +99,18 @@ export default (app: Router) => {
|
|||||||
message: '文件路径禁止访问',
|
message: '文件路径禁止访问',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.file) {
|
||||||
|
fs.renameSync(req.file.path, join(path, req.file.filename));
|
||||||
|
return res.send({ code: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
if (!originFilename) {
|
if (!originFilename) {
|
||||||
originFilename = filename;
|
originFilename = filename;
|
||||||
}
|
}
|
||||||
const originFilePath = `${path}${originFilename.replace(/\//g, '')}`;
|
const originFilePath = `${path}${originFilename.replace(/\//g, '')}`;
|
||||||
const filePath = `${path}${filename.replace(/\//g, '')}`;
|
const filePath = `${path}${filename.replace(/\//g, '')}`;
|
||||||
if (fs.existsSync(originFilePath)) {
|
if (fs.existsSync(originFilePath)) {
|
||||||
if (!fs.existsSync(config.bakPath)) {
|
|
||||||
fs.mkdirSync(config.bakPath);
|
|
||||||
}
|
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
originFilePath,
|
originFilePath,
|
||||||
`${config.bakPath}${originFilename.replace(/\//g, '')}`,
|
`${config.bakPath}${originFilename.replace(/\//g, '')}`,
|
||||||
@@ -104,7 +122,6 @@ export default (app: Router) => {
|
|||||||
fs.writeFileSync(filePath, content);
|
fs.writeFileSync(filePath, content);
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -116,7 +133,7 @@ export default (app: Router) => {
|
|||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
path: Joi.string().optional().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
content: Joi.string().required(),
|
content: Joi.string().required().allow(''),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -131,7 +148,6 @@ export default (app: Router) => {
|
|||||||
fs.writeFileSync(filePath, content);
|
fs.writeFileSync(filePath, content);
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -156,7 +172,6 @@ export default (app: Router) => {
|
|||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -187,7 +202,6 @@ export default (app: Router) => {
|
|||||||
return next(err);
|
return next(err);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -198,22 +212,22 @@ export default (app: Router) => {
|
|||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
|
content: Joi.string().optional().allow(''),
|
||||||
path: Joi.string().optional().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
let { filename, path } = req.body as {
|
let { filename, content, path } = req.body;
|
||||||
filename: string;
|
const { name, ext } = parse(filename);
|
||||||
path: string;
|
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
|
||||||
};
|
fs.writeFileSync(filePath, content || '', { encoding: 'utf8' });
|
||||||
const filePath = join(path, filename);
|
|
||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const result = await scriptService.runScript(filePath);
|
const result = await scriptService.runScript(filePath);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -224,22 +238,21 @@ export default (app: Router) => {
|
|||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
|
content: Joi.string().optional().allow(''),
|
||||||
path: Joi.string().optional().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
let { filename, path } = req.body as {
|
let { filename, content, path } = req.body;
|
||||||
filename: string;
|
const { name, ext } = parse(filename);
|
||||||
path: string;
|
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
|
||||||
};
|
|
||||||
const filePath = join(path, filename);
|
|
||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const result = await scriptService.stopScript(filePath);
|
const result = await scriptService.stopScript(filePath);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ export default (app: Router) => {
|
|||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
type: Joi.string().required(),
|
type: Joi.string().required(),
|
||||||
schedule: Joi.string().optional().allow('').allow(null),
|
schedule: Joi.string().optional().allow('').allow(null),
|
||||||
interval_schedule: Joi.object().optional().allow('').allow(null),
|
interval_schedule: Joi.object({
|
||||||
|
type: Joi.string().required(),
|
||||||
|
value: Joi.number().min(1).required(),
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.allow('')
|
||||||
|
.allow(null),
|
||||||
name: Joi.string().optional().allow('').allow(null),
|
name: Joi.string().optional().allow('').allow(null),
|
||||||
url: Joi.string().required(),
|
url: Joi.string().required(),
|
||||||
whitelist: Joi.string().optional().allow('').allow(null),
|
whitelist: Joi.string().optional().allow('').allow(null),
|
||||||
@@ -59,7 +65,6 @@ export default (app: Router) => {
|
|||||||
return res.send({ code: 400, message: 'param schedule error' });
|
return res.send({ code: 400, message: 'param schedule error' });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -77,7 +82,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.run(req.body);
|
const data = await subscriptionService.run(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -95,7 +99,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.stop(req.body);
|
const data = await subscriptionService.stop(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -113,7 +116,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.disabled(req.body);
|
const data = await subscriptionService.disabled(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -131,7 +133,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.enabled(req.body);
|
const data = await subscriptionService.enabled(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -151,7 +152,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.log(req.params.id);
|
const data = await subscriptionService.log(req.params.id);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -195,7 +195,6 @@ export default (app: Router) => {
|
|||||||
return res.send({ code: 400, message: 'param schedule error' });
|
return res.send({ code: 400, message: 'param schedule error' });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -213,7 +212,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.remove(req.body);
|
const data = await subscriptionService.remove(req.body);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -233,7 +231,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.getDb({ id: req.params.id });
|
const data = await subscriptionService.getDb({ id: req.params.id });
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -260,7 +257,6 @@ export default (app: Router) => {
|
|||||||
});
|
});
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -280,7 +276,6 @@ export default (app: Router) => {
|
|||||||
const data = await subscriptionService.logs(req.params.id);
|
const data = await subscriptionService.logs(req.params.id);
|
||||||
return res.send({ code: 200, data });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export default (app: Router) => {
|
|||||||
const data = await systemService.getLogRemoveFrequency();
|
const data = await systemService.getLogRemoveFrequency();
|
||||||
res.send({ code: 200, data });
|
res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -76,7 +75,6 @@ export default (app: Router) => {
|
|||||||
);
|
);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -91,7 +89,6 @@ export default (app: Router) => {
|
|||||||
const result = await systemService.checkUpdate();
|
const result = await systemService.checkUpdate();
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -106,7 +103,6 @@ export default (app: Router) => {
|
|||||||
const result = await systemService.updateSystem();
|
const result = await systemService.updateSystem();
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -127,7 +123,6 @@ export default (app: Router) => {
|
|||||||
const result = await systemService.notify(req.body);
|
const result = await systemService.notify(req.body);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export default (app: Router) => {
|
|||||||
const data = await userService.login({ ...req.body }, req);
|
const data = await userService.login({ ...req.body }, req);
|
||||||
return res.send(data);
|
return res.send(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -54,7 +53,6 @@ export default (app: Router) => {
|
|||||||
await userService.logout(req.platform);
|
await userService.logout(req.platform);
|
||||||
res.send({ code: 200 });
|
res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -75,7 +73,6 @@ export default (app: Router) => {
|
|||||||
await userService.updateUsernameAndPassword(req.body);
|
await userService.updateUsernameAndPassword(req.body);
|
||||||
res.send({ code: 200, message: '更新成功' });
|
res.send({ code: 200, message: '更新成功' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -109,7 +106,6 @@ export default (app: Router) => {
|
|||||||
const data = await userService.initTwoFactor();
|
const data = await userService.initTwoFactor();
|
||||||
res.send({ code: 200, data });
|
res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -129,7 +125,6 @@ export default (app: Router) => {
|
|||||||
const data = await userService.activeTwoFactor(req.body.code);
|
const data = await userService.activeTwoFactor(req.body.code);
|
||||||
res.send({ code: 200, data });
|
res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -144,7 +139,6 @@ export default (app: Router) => {
|
|||||||
const data = await userService.deactiveTwoFactor();
|
const data = await userService.deactiveTwoFactor();
|
||||||
res.send({ code: 200, data });
|
res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -166,7 +160,6 @@ export default (app: Router) => {
|
|||||||
const data = await userService.twoFactorLogin(req.body, req);
|
const data = await userService.twoFactorLogin(req.body, req);
|
||||||
res.send(data);
|
res.send(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -181,7 +174,6 @@ export default (app: Router) => {
|
|||||||
const data = await userService.getLoginLog();
|
const data = await userService.getLoginLog();
|
||||||
res.send({ code: 200, data });
|
res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -196,7 +188,6 @@ export default (app: Router) => {
|
|||||||
const data = await userService.getNotificationMode();
|
const data = await userService.getNotificationMode();
|
||||||
res.send({ code: 200, data });
|
res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -211,7 +202,6 @@ export default (app: Router) => {
|
|||||||
const result = await userService.updateNotificationMode(req.body);
|
const result = await userService.updateNotificationMode(req.body);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -232,7 +222,6 @@ export default (app: Router) => {
|
|||||||
await userService.updateUsernameAndPassword(req.body);
|
await userService.updateUsernameAndPassword(req.body);
|
||||||
res.send({ code: 200, message: '更新成功' });
|
res.send({ code: 200, message: '更新成功' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -247,7 +236,6 @@ export default (app: Router) => {
|
|||||||
const result = await userService.updateNotificationMode(req.body);
|
const result = await userService.updateNotificationMode(req.body);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -263,7 +251,6 @@ export default (app: Router) => {
|
|||||||
const result = await userService.updateAvatar(req.file!.filename);
|
const result = await userService.updateAvatar(req.file!.filename);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-5
@@ -1,20 +1,18 @@
|
|||||||
import 'reflect-metadata'; // We need this in order to use @Decorators
|
import 'reflect-metadata'; // We need this in order to use @Decorators
|
||||||
|
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
import Logger from './loaders/logger';
|
import Logger from './loaders/logger';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
await require('./loaders/db').default();
|
||||||
|
|
||||||
await require('./loaders/initFile').default();
|
await require('./loaders/initFile').default();
|
||||||
|
|
||||||
await require('./loaders/sentry').default({ expressApp: app });
|
await require('./loaders/sentry').default({ expressApp: app });
|
||||||
|
|
||||||
await require('./loaders/db').default();
|
|
||||||
|
|
||||||
await require('./loaders/app').default({ expressApp: app });
|
await require('./loaders/app').default({ expressApp: app });
|
||||||
|
|
||||||
const server = app
|
const server = app
|
||||||
|
|||||||
+14
-2
@@ -4,10 +4,21 @@ import { createRandomString } from './util';
|
|||||||
|
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
|
if (!process.env.QL_DIR) {
|
||||||
|
// 声明QL_DIR环境变量
|
||||||
|
let qlHomePath = path.join(__dirname, '../../');
|
||||||
|
// 生产环境
|
||||||
|
if (qlHomePath.endsWith('/static/')) {
|
||||||
|
qlHomePath = path.join(qlHomePath, '../');
|
||||||
|
}
|
||||||
|
process.env.QL_DIR = qlHomePath;
|
||||||
|
}
|
||||||
|
|
||||||
const lastVersionFile = `http://qn.whyour.cn/version.ts?v=${Date.now()}`;
|
const lastVersionFile = `http://qn.whyour.cn/version.ts?v=${Date.now()}`;
|
||||||
|
|
||||||
const envFound = dotenv.config();
|
const rootPath = process.env.QL_DIR as string;
|
||||||
const rootPath = process.cwd();
|
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
|
||||||
|
|
||||||
const dataPath = path.join(rootPath, 'data/');
|
const dataPath = path.join(rootPath, 'data/');
|
||||||
const samplePath = path.join(rootPath, 'sample/');
|
const samplePath = path.join(rootPath, 'sample/');
|
||||||
const configPath = path.join(dataPath, 'config/');
|
const configPath = path.join(dataPath, 'config/');
|
||||||
@@ -69,6 +80,7 @@ export default {
|
|||||||
'cookie.sh',
|
'cookie.sh',
|
||||||
'crontab.list',
|
'crontab.list',
|
||||||
'env.sh',
|
'env.sh',
|
||||||
|
'token.json',
|
||||||
],
|
],
|
||||||
writePathList: [configPath, scriptPath],
|
writePathList: [configPath, scriptPath],
|
||||||
bakPath,
|
bakPath,
|
||||||
|
|||||||
+69
-19
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import iconv from 'iconv-lite';
|
import iconv from 'iconv-lite';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
|
||||||
export function getFileContentByName(fileName: string) {
|
export function getFileContentByName(fileName: string) {
|
||||||
if (fs.existsSync(fileName)) {
|
if (fs.existsSync(fileName)) {
|
||||||
@@ -277,31 +278,80 @@ export async function concurrentRun(
|
|||||||
return replyList;
|
return replyList;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDirs(dir: string, baseDir: string = '') {
|
enum FileType {
|
||||||
|
'directory',
|
||||||
|
'file',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readDirs(
|
||||||
|
dir: string,
|
||||||
|
baseDir: string = '',
|
||||||
|
blacklist: string[] = [],
|
||||||
|
) {
|
||||||
const relativePath = path.relative(baseDir, dir);
|
const relativePath = path.relative(baseDir, dir);
|
||||||
const files = fs.readdirSync(dir);
|
const files = fs.readdirSync(dir);
|
||||||
const result: any = files.map((file: string) => {
|
const result: any = files
|
||||||
const subPath = path.join(dir, file);
|
.filter((x) => !blacklist.includes(x))
|
||||||
const stats = fs.statSync(subPath);
|
.map((file: string) => {
|
||||||
const key = path.join(relativePath, file);
|
const subPath = path.join(dir, file);
|
||||||
if (stats.isDirectory()) {
|
const stats = fs.statSync(subPath);
|
||||||
|
const key = path.join(relativePath, file);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
return {
|
||||||
|
title: file,
|
||||||
|
key,
|
||||||
|
type: 'directory',
|
||||||
|
disabled: true,
|
||||||
|
parent: relativePath,
|
||||||
|
children: readDirs(subPath, baseDir).sort(
|
||||||
|
(a: any, b: any) =>
|
||||||
|
(FileType as any)[a.type] - (FileType as any)[b.type],
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
title: file,
|
title: file,
|
||||||
value: file,
|
type: 'file',
|
||||||
key,
|
key,
|
||||||
type: 'directory',
|
|
||||||
disabled: true,
|
|
||||||
parent: relativePath,
|
parent: relativePath,
|
||||||
children: readDirs(subPath, baseDir),
|
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
return {
|
return result.sort(
|
||||||
title: file,
|
(a: any, b: any) => (FileType as any)[a.type] - (FileType as any)[b.type],
|
||||||
value: file,
|
);
|
||||||
type: 'file',
|
}
|
||||||
key,
|
|
||||||
parent: relativePath,
|
export function readDir(
|
||||||
};
|
dir: string,
|
||||||
});
|
baseDir: string = '',
|
||||||
|
blacklist: string[] = [],
|
||||||
|
) {
|
||||||
|
const relativePath = path.relative(baseDir, dir);
|
||||||
|
const files = fs.readdirSync(dir);
|
||||||
|
const result: any = files
|
||||||
|
.filter((x) => !blacklist.includes(x))
|
||||||
|
.map((file: string) => {
|
||||||
|
const subPath = path.join(dir, file);
|
||||||
|
const stats = fs.statSync(subPath);
|
||||||
|
const key = path.join(relativePath, file);
|
||||||
|
return {
|
||||||
|
title: file,
|
||||||
|
type: stats.isDirectory() ? 'directory' : 'file',
|
||||||
|
key,
|
||||||
|
parent: relativePath,
|
||||||
|
};
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function promiseExec(command: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(
|
||||||
|
command,
|
||||||
|
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
|
||||||
|
(err, stdout, stderr) => {
|
||||||
|
resolve(stdout || stderr || JSON.stringify(err));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model } from 'sequelize';
|
||||||
|
|
||||||
|
interface SortType {
|
||||||
|
type: 'ASC' | 'DESC';
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterType {
|
||||||
|
type: 'or' | 'and';
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CrontabView {
|
||||||
|
name?: string;
|
||||||
|
id?: number;
|
||||||
|
position?: number;
|
||||||
|
isDisabled?: 1 | 0;
|
||||||
|
filters?: FilterType[];
|
||||||
|
sorts?: SortType[];
|
||||||
|
|
||||||
|
constructor(options: CrontabView) {
|
||||||
|
this.name = options.name;
|
||||||
|
this.id = options.id;
|
||||||
|
this.position = options.position;
|
||||||
|
this.isDisabled = options.isDisabled || 0;
|
||||||
|
this.filters = options.filters;
|
||||||
|
this.sorts = options.sorts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CronViewInstance
|
||||||
|
extends Model<CrontabView, CrontabView>,
|
||||||
|
CrontabView {}
|
||||||
|
export const CrontabViewModel = sequelize.define<CronViewInstance>(
|
||||||
|
'CrontabView',
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
unique: 'name',
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
position: DataTypes.NUMBER,
|
||||||
|
isDisabled: DataTypes.NUMBER,
|
||||||
|
filters: DataTypes.JSON,
|
||||||
|
sorts: DataTypes.JSON,
|
||||||
|
},
|
||||||
|
);
|
||||||
+3
-4
@@ -6,9 +6,9 @@ import { CrontabModel } from '../data/cron';
|
|||||||
import { DependenceModel } from '../data/dependence';
|
import { DependenceModel } from '../data/dependence';
|
||||||
import { AppModel } from '../data/open';
|
import { AppModel } from '../data/open';
|
||||||
import { AuthModel } from '../data/auth';
|
import { AuthModel } from '../data/auth';
|
||||||
import { sequelize } from '../data';
|
|
||||||
import { fileExist } from '../config/util';
|
import { fileExist } from '../config/util';
|
||||||
import { SubscriptionModel } from '../data/subscription';
|
import { SubscriptionModel } from '../data/subscription';
|
||||||
|
import { CrontabViewModel } from '../data/cronView';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
@@ -18,9 +18,8 @@ export default async () => {
|
|||||||
await AppModel.sync();
|
await AppModel.sync();
|
||||||
await AuthModel.sync();
|
await AuthModel.sync();
|
||||||
await EnvModel.sync();
|
await EnvModel.sync();
|
||||||
await SubscriptionModel.sync({ alter: true });
|
await SubscriptionModel.sync();
|
||||||
|
await CrontabViewModel.sync();
|
||||||
await sequelize.sync();
|
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// const queryInterface = sequelize.getQueryInterface();
|
// const queryInterface = sequelize.getQueryInterface();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import path from 'path';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
import config from '../config/index';
|
import config from '../config/index';
|
||||||
|
import { promiseExec } from '../config/util';
|
||||||
|
|
||||||
function linkToNodeModule(src: string, dst?: string) {
|
function linkToNodeModule(src: string, dst?: string) {
|
||||||
const target = path.join(config.rootPath, 'node_modules', dst || src);
|
const target = path.join(config.rootPath, 'node_modules', dst || src);
|
||||||
@@ -16,7 +17,32 @@ function linkToNodeModule(src: string, dst?: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function linkCommand() {
|
||||||
|
const commandPath = await promiseExec('which node');
|
||||||
|
const commandDir = path.dirname(commandPath);
|
||||||
|
const linkShell = [
|
||||||
|
{
|
||||||
|
src: 'update.sh',
|
||||||
|
dest: 'ql',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'task.sh',
|
||||||
|
dest: 'task',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const link of linkShell) {
|
||||||
|
const source = path.join(config.rootPath, 'shell', link.src);
|
||||||
|
const target = path.join(commandDir, link.dest);
|
||||||
|
if (fs.existsSync(target)) {
|
||||||
|
fs.unlinkSync(target);
|
||||||
|
}
|
||||||
|
fs.symlink(source, target, (err) => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async (src: string = 'deps') => {
|
export default async (src: string = 'deps') => {
|
||||||
|
await linkCommand();
|
||||||
linkToNodeModule(src);
|
linkToNodeModule(src);
|
||||||
|
|
||||||
const source = path.join(config.rootPath, src);
|
const source = path.join(config.rootPath, src);
|
||||||
|
|||||||
+23
-8
@@ -80,13 +80,6 @@ export default ({ app }: { app: Application }) => {
|
|||||||
) {
|
) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
const remoteAddress = req.socket.remoteAddress;
|
|
||||||
if (
|
|
||||||
remoteAddress === '::ffff:127.0.0.1' &&
|
|
||||||
originPath === '/api/crons/status'
|
|
||||||
) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -136,7 +129,6 @@ export default ({ app }: { app: Application }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(errors());
|
app.use(errors());
|
||||||
app.use(Sentry.Handlers.errorHandler());
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
(
|
(
|
||||||
@@ -155,6 +147,29 @@ export default ({ app }: { app: Application }) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
(
|
||||||
|
err: Error & { errors: any[] },
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
) => {
|
||||||
|
if (err.name.includes('Sequelize')) {
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.send({
|
||||||
|
code: 400,
|
||||||
|
message: `${err.name} ${err.message}`,
|
||||||
|
validation: err.errors,
|
||||||
|
})
|
||||||
|
.end();
|
||||||
|
}
|
||||||
|
return next(err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(Sentry.Handlers.errorHandler());
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
(
|
(
|
||||||
err: Error & { status: number },
|
err: Error & { status: number },
|
||||||
|
|||||||
@@ -21,14 +21,17 @@ export default async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
||||||
DependenceModel.findAll({ where: {}, raw: true }).then(async (docs) => {
|
DependenceModel.findAll({
|
||||||
|
where: {},
|
||||||
|
order: [['type', 'DESC']],
|
||||||
|
raw: true,
|
||||||
|
}).then(async (docs) => {
|
||||||
const groups = _.groupBy(docs, 'type');
|
const groups = _.groupBy(docs, 'type');
|
||||||
for (const key in groups) {
|
const keys = Object.keys(groups).sort((a, b) => parseInt(b) - parseInt(a));
|
||||||
if (Object.prototype.hasOwnProperty.call(groups, key)) {
|
for (const key of keys) {
|
||||||
const group = groups[key];
|
const group = groups[key];
|
||||||
const depIds = group.map((x) => x.id);
|
const depIds = group.map((x) => x.id);
|
||||||
await dependenceService.reInstall(depIds as number[]);
|
await dependenceService.reInstall(depIds as number[]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import dotenv from 'dotenv';
|
|||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import { fileExist } from '../config/util';
|
import { fileExist } from '../config/util';
|
||||||
|
|
||||||
const rootPath = process.cwd();
|
const rootPath = process.env.QL_DIR as string;;
|
||||||
const dataPath = path.join(rootPath, 'data/');
|
const dataPath = path.join(rootPath, 'data/');
|
||||||
const configPath = path.join(dataPath, 'config/');
|
const configPath = path.join(dataPath, 'config/');
|
||||||
const scriptPath = path.join(dataPath, 'scripts/');
|
const scriptPath = path.join(dataPath, 'scripts/');
|
||||||
const logPath = path.join(dataPath, 'log/');
|
const logPath = path.join(dataPath, 'log/');
|
||||||
const uploadPath = path.join(dataPath, 'upload/');
|
const uploadPath = path.join(dataPath, 'upload/');
|
||||||
|
const bakPath = path.join(dataPath, 'bak/');
|
||||||
const samplePath = path.join(rootPath, 'sample/');
|
const samplePath = path.join(rootPath, 'sample/');
|
||||||
const confFile = path.join(configPath, 'config.sh');
|
const confFile = path.join(configPath, 'config.sh');
|
||||||
const authConfigFile = path.join(configPath, 'auth.json');
|
const authConfigFile = path.join(configPath, 'auth.json');
|
||||||
@@ -27,6 +28,7 @@ export default async () => {
|
|||||||
const configDirExist = await fileExist(configPath);
|
const configDirExist = await fileExist(configPath);
|
||||||
const uploadDirExist = await fileExist(uploadPath);
|
const uploadDirExist = await fileExist(uploadPath);
|
||||||
const sshDirExist = await fileExist(sshPath);
|
const sshDirExist = await fileExist(sshPath);
|
||||||
|
const bakDirExist = await fileExist(bakPath);
|
||||||
|
|
||||||
if (!configDirExist) {
|
if (!configDirExist) {
|
||||||
fs.mkdirSync(configPath);
|
fs.mkdirSync(configPath);
|
||||||
@@ -56,6 +58,10 @@ export default async () => {
|
|||||||
fs.mkdirSync(sshPath);
|
fs.mkdirSync(sshPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bakDirExist) {
|
||||||
|
fs.mkdirSync(bakPath);
|
||||||
|
}
|
||||||
|
|
||||||
dotenv.config({ path: confFile });
|
dotenv.config({ path: confFile });
|
||||||
|
|
||||||
Logger.info('✌️ Init file down');
|
Logger.info('✌️ Init file down');
|
||||||
|
|||||||
@@ -3,12 +3,30 @@ import _ from 'lodash';
|
|||||||
import SystemService from '../services/system';
|
import SystemService from '../services/system';
|
||||||
import ScheduleService from '../services/schedule';
|
import ScheduleService from '../services/schedule';
|
||||||
import SubscriptionService from '../services/subscription';
|
import SubscriptionService from '../services/subscription';
|
||||||
|
import config from '../config';
|
||||||
|
import { fileExist } from '../config/util';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const scheduleService = Container.get(ScheduleService);
|
const scheduleService = Container.get(ScheduleService);
|
||||||
const subscriptionService = Container.get(SubscriptionService);
|
const subscriptionService = Container.get(SubscriptionService);
|
||||||
|
|
||||||
|
// 生成内置token
|
||||||
|
let tokenCommand = `ts-node-transpile-only ${config.rootPath}/back/token.ts`;
|
||||||
|
const tokenFile = `${config.rootPath}/static/build/token.js`;
|
||||||
|
if (await fileExist(tokenFile)) {
|
||||||
|
tokenCommand = `node ${tokenFile}`;
|
||||||
|
}
|
||||||
|
const cron = {
|
||||||
|
id: 'token',
|
||||||
|
name: '生成token',
|
||||||
|
command: tokenCommand,
|
||||||
|
};
|
||||||
|
scheduleService.createIntervalTask(cron as any, {
|
||||||
|
days: 28,
|
||||||
|
runImmediately: true,
|
||||||
|
});
|
||||||
|
|
||||||
// 运行删除日志任务
|
// 运行删除日志任务
|
||||||
const data = await systemService.getLogRemoveFrequency();
|
const data = await systemService.getLogRemoveFrequency();
|
||||||
if (data && data.info && data.info.frequency) {
|
if (data && data.info && data.info.frequency) {
|
||||||
@@ -17,7 +35,7 @@ export default async () => {
|
|||||||
name: '删除日志',
|
name: '删除日志',
|
||||||
command: `ql rmlog ${data.info.frequency}`,
|
command: `ql rmlog ${data.info.frequency}`,
|
||||||
};
|
};
|
||||||
await scheduleService.createIntervalTask(cron, {
|
scheduleService.createIntervalTask(cron, {
|
||||||
days: data.info.frequency,
|
days: data.info.frequency,
|
||||||
runImmediately: true,
|
runImmediately: true,
|
||||||
});
|
});
|
||||||
@@ -26,6 +44,11 @@ export default async () => {
|
|||||||
// 运行所有订阅
|
// 运行所有订阅
|
||||||
const subs = await subscriptionService.list();
|
const subs = await subscriptionService.list();
|
||||||
for (const sub of subs) {
|
for (const sub of subs) {
|
||||||
await subscriptionService.handleTask(sub, true, true, true);
|
await subscriptionService.handleTask(
|
||||||
|
sub,
|
||||||
|
!sub.is_disabled,
|
||||||
|
true,
|
||||||
|
!sub.is_disabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+123
-19
@@ -10,6 +10,7 @@ import { promises, existsSync } from 'fs';
|
|||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronService {
|
export default class CronService {
|
||||||
@@ -112,8 +113,56 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
private formatViewQuery(query: any, viewQuery: any) {
|
||||||
let query = {};
|
if (viewQuery.filters && viewQuery.filters.length > 0) {
|
||||||
|
for (const col of viewQuery.filters) {
|
||||||
|
const { property, value, operation } = col;
|
||||||
|
let operate = null;
|
||||||
|
switch (operation) {
|
||||||
|
case 'Reg':
|
||||||
|
operate = Op.like;
|
||||||
|
break;
|
||||||
|
case 'NotReg':
|
||||||
|
operate = Op.notLike;
|
||||||
|
break;
|
||||||
|
case 'In':
|
||||||
|
query[Op.or] = [
|
||||||
|
{
|
||||||
|
[property]: value,
|
||||||
|
},
|
||||||
|
property === 'status' && value.includes(2)
|
||||||
|
? { isDisabled: 1 }
|
||||||
|
: {},
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case 'Nin':
|
||||||
|
query[Op.and] = [
|
||||||
|
{
|
||||||
|
[property]: {
|
||||||
|
[Op.notIn]: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
property === 'status' && value.includes(2)
|
||||||
|
? { isDisabled: { [Op.ne]: 1 } }
|
||||||
|
: {},
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (operate) {
|
||||||
|
query[property] = {
|
||||||
|
[Op.or]: [
|
||||||
|
{ [operate]: `%${value}%` },
|
||||||
|
{ [operate]: `%${encodeURIComponent(value)}%` },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatSearchText(query: any, searchText: string | undefined) {
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const textArray = searchText.split(':');
|
const textArray = searchText.split(':');
|
||||||
switch (textArray[0]) {
|
switch (textArray[0]) {
|
||||||
@@ -157,12 +206,58 @@ export default class CronService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatViewSort(order: string[][], viewQuery: any) {
|
||||||
|
if (viewQuery.sorts && viewQuery.sorts.length > 0) {
|
||||||
|
for (const { property, type } of viewQuery.sorts) {
|
||||||
|
order.unshift([property, type]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async crontabs(params?: {
|
||||||
|
searchValue: string;
|
||||||
|
page: string;
|
||||||
|
size: string;
|
||||||
|
sortField: string;
|
||||||
|
sortType: string;
|
||||||
|
queryString: string;
|
||||||
|
}): Promise<{ data: Crontab[]; total: number }> {
|
||||||
|
const searchText = params?.searchValue;
|
||||||
|
const page = Number(params?.page || '0');
|
||||||
|
const size = Number(params?.size || '0');
|
||||||
|
const sortField = params?.sortField || '';
|
||||||
|
const sortType = params?.sortType || '';
|
||||||
|
const viewQuery = JSON.parse(params?.queryString || '{}');
|
||||||
|
|
||||||
|
let query: any = {};
|
||||||
|
let order = [
|
||||||
|
['isPinned', 'DESC'],
|
||||||
|
['isDisabled', 'ASC'],
|
||||||
|
['status', 'ASC'],
|
||||||
|
['createdAt', 'DESC'],
|
||||||
|
];
|
||||||
|
|
||||||
|
this.formatViewQuery(query, viewQuery);
|
||||||
|
this.formatSearchText(query, searchText);
|
||||||
|
this.formatViewSort(order, viewQuery);
|
||||||
|
|
||||||
|
if (sortType && sortField) {
|
||||||
|
order.unshift([sortField, sortType]);
|
||||||
|
}
|
||||||
|
let condition: any = {
|
||||||
|
where: query,
|
||||||
|
order: order,
|
||||||
|
};
|
||||||
|
if (page && size) {
|
||||||
|
condition.offset = (page - 1) * size;
|
||||||
|
condition.limit = size;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const result = await CrontabModel.findAll({
|
const result = await CrontabModel.findAll(condition);
|
||||||
where: query,
|
const count = await CrontabModel.count({ where: query });
|
||||||
order: [['createdAt', 'DESC']],
|
return { data: result, total: count };
|
||||||
});
|
|
||||||
return result as any;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -197,13 +292,21 @@ export default class CronService {
|
|||||||
const err = await this.killTask(doc.command);
|
const err = await this.killTask(doc.command);
|
||||||
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
|
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
|
||||||
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
||||||
|
|
||||||
|
const endTime = dayjs();
|
||||||
|
const diffTimeStr = doc.last_execution_time
|
||||||
|
? `,耗时 ${endTime.diff(
|
||||||
|
dayjs(doc.last_execution_time * 1000),
|
||||||
|
'second',
|
||||||
|
)}`
|
||||||
|
: '';
|
||||||
if (logFileExist) {
|
if (logFileExist) {
|
||||||
const str = err ? `\n${err}` : '';
|
const str = err ? `\n${err}` : '';
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(
|
||||||
`${absolutePath}`,
|
`${absolutePath}`,
|
||||||
`${str}\n## 执行结束... ${new Date()
|
`${str}\n## 执行结束... ${endTime.format(
|
||||||
.toLocaleString('zh', { hour12: false })
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
.replace(' 24:', ' 00:')} `,
|
)}${diffTimeStr}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,6 +331,7 @@ export default class CronService {
|
|||||||
const killLogs = [];
|
const killLogs = [];
|
||||||
if (pids && pids.length > 0) {
|
if (pids && pids.length > 0) {
|
||||||
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
|
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
|
||||||
|
pids = pids.slice(0, 3);
|
||||||
for (const id of pids) {
|
for (const id of pids) {
|
||||||
const c = `kill -9 ${id.slice(1)}`;
|
const c = `kill -9 ${id.slice(1)}`;
|
||||||
try {
|
try {
|
||||||
@@ -269,7 +373,13 @@ export default class CronService {
|
|||||||
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
|
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
|
||||||
cmdStr = `task ${cmdStr}`;
|
cmdStr = `task ${cmdStr}`;
|
||||||
}
|
}
|
||||||
if (cmdStr.endsWith('.js')) {
|
if (
|
||||||
|
cmdStr.endsWith('.js') ||
|
||||||
|
cmdStr.endsWith('.py') ||
|
||||||
|
cmdStr.endsWith('.pyc') ||
|
||||||
|
cmdStr.endsWith('.sh') ||
|
||||||
|
cmdStr.endsWith('.ts')
|
||||||
|
) {
|
||||||
cmdStr = `${cmdStr} now`;
|
cmdStr = `${cmdStr} now`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,16 +402,10 @@ export default class CronService {
|
|||||||
|
|
||||||
cp.on('exit', async (code, signal) => {
|
cp.on('exit', async (code, signal) => {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
|
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
|
||||||
);
|
);
|
||||||
await CrontabModel.update(
|
|
||||||
{ status: CrontabStatus.idle, pid: undefined },
|
|
||||||
{ where: { id } },
|
|
||||||
);
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
cp.on('close', async (code) => {
|
cp.on('close', async (code) => {
|
||||||
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
|
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
{ status: CrontabStatus.idle, pid: undefined },
|
{ status: CrontabStatus.idle, pid: undefined },
|
||||||
{ where: { id } },
|
{ where: { id } },
|
||||||
@@ -431,7 +535,7 @@ export default class CronService {
|
|||||||
private async set_crontab(needReloadSchedule: boolean = false) {
|
private async set_crontab(needReloadSchedule: boolean = false) {
|
||||||
const tabs = await this.crontabs();
|
const tabs = await this.crontabs();
|
||||||
var crontab_string = '';
|
var crontab_string = '';
|
||||||
tabs.forEach((tab) => {
|
tabs.data.forEach((tab) => {
|
||||||
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
||||||
if (tab.isDisabled === 1 || _schedule!.length !== 5) {
|
if (tab.isDisabled === 1 || _schedule!.length !== 5) {
|
||||||
crontab_string += '# ';
|
crontab_string += '# ';
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import { CrontabView, CrontabViewModel } from '../data/cronView';
|
||||||
|
import { initEnvPosition } from '../data/env';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class CronViewService {
|
||||||
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
|
public async create(payload: CrontabView): Promise<CrontabView> {
|
||||||
|
let position = initEnvPosition;
|
||||||
|
const views = await this.list();
|
||||||
|
if (views && views.length > 0 && views[views.length - 1].position) {
|
||||||
|
position = views[views.length - 1].position as number;
|
||||||
|
}
|
||||||
|
position = position / 2;
|
||||||
|
const tab = new CrontabView({ ...payload, position });
|
||||||
|
const doc = await this.insert(tab);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async insert(payload: CrontabView): Promise<CrontabView> {
|
||||||
|
return await CrontabViewModel.create(payload, { returning: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(payload: CrontabView): Promise<CrontabView> {
|
||||||
|
const newDoc = await this.updateDb(payload);
|
||||||
|
return newDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateDb(payload: CrontabView): Promise<CrontabView> {
|
||||||
|
await CrontabViewModel.update(payload, { where: { id: payload.id } });
|
||||||
|
return await this.getDb({ id: payload.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async remove(ids: number[]) {
|
||||||
|
await CrontabViewModel.destroy({ where: { id: ids } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<CrontabView[]> {
|
||||||
|
try {
|
||||||
|
const result = await CrontabViewModel.findAll({
|
||||||
|
where: {},
|
||||||
|
order: [['position', 'DESC']],
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDb(query: any): Promise<CrontabView> {
|
||||||
|
const doc: any = await CrontabViewModel.findOne({ where: { ...query } });
|
||||||
|
return doc && (doc.get({ plain: true }) as CrontabView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disabled(ids: number[]) {
|
||||||
|
await CrontabViewModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async enabled(ids: number[]) {
|
||||||
|
await CrontabViewModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async move({
|
||||||
|
id,
|
||||||
|
fromIndex,
|
||||||
|
toIndex,
|
||||||
|
}: {
|
||||||
|
fromIndex: number;
|
||||||
|
toIndex: number;
|
||||||
|
id: number;
|
||||||
|
}): Promise<CrontabView> {
|
||||||
|
let targetPosition: number;
|
||||||
|
const isUpward = fromIndex > toIndex;
|
||||||
|
const views = await this.list();
|
||||||
|
if (toIndex === 0 || toIndex === views.length - 1) {
|
||||||
|
targetPosition = isUpward
|
||||||
|
? views[0].position * 2
|
||||||
|
: views[toIndex].position / 2;
|
||||||
|
} else {
|
||||||
|
targetPosition = isUpward
|
||||||
|
? (views[toIndex].position + views[toIndex - 1].position) / 2
|
||||||
|
: (views[toIndex].position + views[toIndex + 1].position) / 2;
|
||||||
|
}
|
||||||
|
const newDoc = await this.update({
|
||||||
|
id,
|
||||||
|
position: targetPosition,
|
||||||
|
});
|
||||||
|
return newDoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-20
@@ -14,6 +14,7 @@ import { spawn } from 'child_process';
|
|||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
import { concurrentRun } from '../config/util';
|
import { concurrentRun } from '../config/util';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class DependenceService {
|
export default class DependenceService {
|
||||||
@@ -161,20 +162,17 @@ export default class DependenceService {
|
|||||||
)[dependencies[0].type as any];
|
)[dependencies[0].type as any];
|
||||||
const actionText = isInstall ? '安装' : '删除';
|
const actionText = isInstall ? '安装' : '删除';
|
||||||
const depIds = dependencies.map((x) => x.id) as number[];
|
const depIds = dependencies.map((x) => x.id) as number[];
|
||||||
const startTime = Date.now();
|
const startTime = dayjs();
|
||||||
|
|
||||||
|
const message = `开始${actionText}依赖 ${depNames},开始时间 ${startTime.format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
)}\n\n`;
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: socketMessageType,
|
type: socketMessageType,
|
||||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
message,
|
||||||
startTime,
|
|
||||||
).toLocaleString()}\n\n`,
|
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
await this.updateLog(
|
await this.updateLog(depIds, message);
|
||||||
depIds,
|
|
||||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
|
||||||
startTime,
|
|
||||||
).toLocaleString()}\n\n`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
@@ -207,23 +205,19 @@ export default class DependenceService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cp.on('close', async (code) => {
|
cp.on('close', async (code) => {
|
||||||
const endTime = Date.now();
|
const endTime = dayjs();
|
||||||
const isSucceed = code === 0;
|
const isSucceed = code === 0;
|
||||||
const resultText = isSucceed ? '成功' : '失败';
|
const resultText = isSucceed ? '成功' : '失败';
|
||||||
|
|
||||||
|
const message = `\n依赖${actionText}${resultText},结束时间 ${endTime.format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
)},耗时 ${endTime.diff(startTime, 'second')} 秒`;
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: socketMessageType,
|
type: socketMessageType,
|
||||||
message: `\n依赖${actionText}${resultText},结束时间 ${new Date(
|
message,
|
||||||
endTime,
|
|
||||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
await this.updateLog(
|
await this.updateLog(depIds, message);
|
||||||
depIds,
|
|
||||||
`\n依赖${actionText}${resultText},结束时间 ${new Date(
|
|
||||||
endTime,
|
|
||||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
|
||||||
);
|
|
||||||
|
|
||||||
let status = null;
|
let status = null;
|
||||||
if (isSucceed) {
|
if (isSucceed) {
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export default class EnvService {
|
|||||||
.filter((x) => x.status !== EnvStatus.disabled)
|
.filter((x) => x.status !== EnvStatus.disabled)
|
||||||
.map('value')
|
.map('value')
|
||||||
.join('&')
|
.join('&')
|
||||||
.replace(/(\\)[^\n]/g, '\\\\')
|
.replace(/(\\)[^n]/g, '\\\\')
|
||||||
.replace(/(\\$)/, '\\\\')
|
.replace(/(\\$)/, '\\\\')
|
||||||
.replace(/"/g, '\\"')
|
.replace(/"/g, '\\"')
|
||||||
.trim();
|
.trim();
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export default class NotificationService {
|
|||||||
const [corpid, corpsecret, touser, agentid, thumb_media_id = '1'] =
|
const [corpid, corpsecret, touser, agentid, thumb_media_id = '1'] =
|
||||||
weWorkAppKey.split(',');
|
weWorkAppKey.split(',');
|
||||||
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
|
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
|
||||||
const { access_token } = await got
|
const tokenRes: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
timeout: this.timeout,
|
||||||
retry: 0,
|
retry: 0,
|
||||||
@@ -296,7 +296,7 @@ export default class NotificationService {
|
|||||||
|
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(
|
.post(
|
||||||
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`,
|
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
|
||||||
{
|
{
|
||||||
timeout: this.timeout,
|
timeout: this.timeout,
|
||||||
retry: 0,
|
retry: 0,
|
||||||
|
|||||||
+26
-1
@@ -90,7 +90,9 @@ export default class OpenService {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await this.find(condition);
|
const result = await this.find(condition);
|
||||||
return result.map((x) => ({ ...x, tokens: [] }));
|
return result
|
||||||
|
.filter((x) => x.name !== 'system')
|
||||||
|
.map((x) => ({ ...x, tokens: [] }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -142,4 +144,27 @@ export default class OpenService {
|
|||||||
return { code: 400, message: 'client_id或client_seret有误' };
|
return { code: 400, message: 'client_id或client_seret有误' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async generateSystemToken(): Promise<{
|
||||||
|
value: string;
|
||||||
|
expiration: number;
|
||||||
|
}> {
|
||||||
|
let systemApp = (await AppModel.findOne({
|
||||||
|
where: { name: 'system' },
|
||||||
|
})) as App;
|
||||||
|
if (!systemApp) {
|
||||||
|
systemApp = await this.create({
|
||||||
|
name: 'system',
|
||||||
|
scopes: ['crons', 'system'],
|
||||||
|
} as App);
|
||||||
|
}
|
||||||
|
const { data } = await this.authToken({
|
||||||
|
client_id: systemApp.client_id,
|
||||||
|
client_secret: systemApp.client_secret,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
value: data.token,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ interface ScheduleTaskType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskCallbacks {
|
export interface TaskCallbacks {
|
||||||
|
onBefore?: (startTime: dayjs.Dayjs) => Promise<void>;
|
||||||
onStart?: (
|
onStart?: (
|
||||||
cp: ChildProcessWithoutNullStreams,
|
cp: ChildProcessWithoutNullStreams,
|
||||||
startTime: dayjs.Dayjs,
|
startTime: dayjs.Dayjs,
|
||||||
@@ -45,6 +46,8 @@ export default class ScheduleService {
|
|||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const startTime = dayjs();
|
const startTime = dayjs();
|
||||||
|
await callbacks.onBefore?.(startTime);
|
||||||
|
|
||||||
const cp = spawn(command, { shell: '/bin/bash' });
|
const cp = spawn(command, { shell: '/bin/bash' });
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
@@ -76,13 +79,12 @@ export default class ScheduleService {
|
|||||||
|
|
||||||
cp.on('exit', async (code, signal) => {
|
cp.on('exit', async (code, signal) => {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
|
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('close', async (code) => {
|
cp.on('close', async (code) => {
|
||||||
const endTime = dayjs();
|
const endTime = dayjs();
|
||||||
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
|
|
||||||
await callbacks.onEnd?.(
|
await callbacks.onEnd?.(
|
||||||
cp,
|
cp,
|
||||||
endTime,
|
endTime,
|
||||||
@@ -166,7 +168,7 @@ export default class ScheduleService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const job = new LongIntervalJob(
|
const job = new LongIntervalJob(
|
||||||
{ ...schedule, runImmediately: false },
|
{ runImmediately: false, ...schedule },
|
||||||
task,
|
task,
|
||||||
_id,
|
_id,
|
||||||
);
|
);
|
||||||
|
|||||||
+34
-32
@@ -1,8 +1,11 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { spawn } from 'child_process';
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import CronService from './cron';
|
import CronService from './cron';
|
||||||
|
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||||
|
import config from '../config';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ScriptService {
|
export default class ScriptService {
|
||||||
@@ -10,44 +13,43 @@ export default class ScriptService {
|
|||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
private cronService: CronService,
|
private cronService: CronService,
|
||||||
|
private scheduleService: ScheduleService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async runScript(path: string) {
|
private taskCallbacks(filePath: string): TaskCallbacks {
|
||||||
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
|
return {
|
||||||
|
onEnd: async (cp, endTime, diff) => {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
} catch (error) {}
|
||||||
|
},
|
||||||
|
onError: async (message: string) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onLog: async (message: string) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'manuallyRunScript',
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
cp.stdout.on('data', (data) => {
|
public async runScript(filePath: string) {
|
||||||
this.sockService.sendMessage({
|
const relativePath = path.relative(config.scriptPath, filePath);
|
||||||
type: 'manuallyRunScript',
|
const command = `task -l ${relativePath} now`;
|
||||||
message: data.toString(),
|
this.scheduleService.runTask(command, this.taskCallbacks(filePath));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cp.stderr.on('data', (data) => {
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'manuallyRunScript',
|
|
||||||
message: data.toString(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cp.on('error', (err) => {
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'manuallyRunScript',
|
|
||||||
message: JSON.stringify(err),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cp.on('close', (err) => {
|
|
||||||
this.sockService.sendMessage({
|
|
||||||
type: 'manuallyRunScript',
|
|
||||||
message: `执行结束`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return { code: 200 };
|
return { code: 200 };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stopScript(path: string) {
|
public async stopScript(filePath: string) {
|
||||||
const err = await this.cronService.killTask(`task -l ${path} now`);
|
const relativePath = path.relative(config.scriptPath, filePath);
|
||||||
|
const err = await this.cronService.killTask(`task -l ${relativePath} now`);
|
||||||
|
|
||||||
const str = err ? `\n${err}` : '';
|
const str = err ? `\n${err}` : '';
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'manuallyRunScript',
|
type: 'manuallyRunScript',
|
||||||
|
|||||||
+13
-5
@@ -23,6 +23,13 @@ export default class SshKeyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getConfigRegx(alias: string) {
|
||||||
|
return new RegExp(
|
||||||
|
`Host ${alias}\n.*[^StrictHostKeyChecking]*.*[\n]*.*StrictHostKeyChecking no`,
|
||||||
|
'g',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private removePrivateKeyFile(alias: string): void {
|
private removePrivateKeyFile(alias: string): void {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(`${this.sshPath}/${alias}`);
|
fs.unlinkSync(`${this.sshPath}/${alias}`);
|
||||||
@@ -47,24 +54,25 @@ export default class SshKeyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeSshConfig(config: string) {
|
private removeSshConfig(alias: string) {
|
||||||
try {
|
try {
|
||||||
|
const configRegx = this.getConfigRegx(alias);
|
||||||
const data = fs
|
const data = fs
|
||||||
.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' })
|
.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' })
|
||||||
.replace(config, '')
|
.replace(configRegx, '')
|
||||||
.replace(/\n\n+/, '\n\n');
|
.replace(/\n[\n]+/g, '\n');
|
||||||
fs.writeFileSync(this.sshConfigFilePath, data, {
|
fs.writeFileSync(this.sshConfigFilePath, data, {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`删除ssh配置文件${config}失败`, error);
|
this.logger.error(`删除ssh配置文件${alias}失败`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addSSHKey(key: string, alias: string, host: string): void {
|
public addSSHKey(key: string, alias: string, host: string): void {
|
||||||
this.generatePrivateKeyFile(alias, key);
|
this.generatePrivateKeyFile(alias, key);
|
||||||
const config = this.generateSingleSshConfig(alias, host);
|
const config = this.generateSingleSshConfig(alias, host);
|
||||||
this.removeSshConfig(config);
|
this.removeSshConfig(alias);
|
||||||
this.generateSshConfig([config]);
|
this.generateSshConfig([config]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import ScheduleService, { TaskCallbacks } from './schedule';
|
|||||||
import { SimpleIntervalSchedule } from 'toad-scheduler';
|
import { SimpleIntervalSchedule } from 'toad-scheduler';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import SshKeyService from './sshKey';
|
import SshKeyService from './sshKey';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SubscriptionService {
|
export default class SubscriptionService {
|
||||||
@@ -41,47 +42,22 @@ export default class SubscriptionService {
|
|||||||
public async list(searchText?: string): Promise<Subscription[]> {
|
public async list(searchText?: string): Promise<Subscription[]> {
|
||||||
let query = {};
|
let query = {};
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const textArray = searchText.split(':');
|
const reg = {
|
||||||
switch (textArray[0]) {
|
[Op.or]: [
|
||||||
case 'name':
|
{ [Op.like]: `%${searchText}%` },
|
||||||
case 'command':
|
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
||||||
case 'schedule':
|
],
|
||||||
case 'label':
|
};
|
||||||
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
|
query = {
|
||||||
query = {
|
[Op.or]: [
|
||||||
[column]: {
|
{
|
||||||
[Op.or]: [
|
name: reg,
|
||||||
{ [Op.like]: `%${textArray[1]}%` },
|
},
|
||||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
{
|
||||||
],
|
url: reg,
|
||||||
},
|
},
|
||||||
};
|
],
|
||||||
break;
|
};
|
||||||
default:
|
|
||||||
const reg = {
|
|
||||||
[Op.or]: [
|
|
||||||
{ [Op.like]: `%${searchText}%` },
|
|
||||||
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
query = {
|
|
||||||
[Op.or]: [
|
|
||||||
{
|
|
||||||
name: reg,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
command: reg,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
schedule: reg,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
labels: reg,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await SubscriptionModel.findAll({
|
const result = await SubscriptionModel.findAll({
|
||||||
@@ -192,14 +168,13 @@ export default class SubscriptionService {
|
|||||||
|
|
||||||
private taskCallbacks(doc: Subscription): TaskCallbacks {
|
private taskCallbacks(doc: Subscription): TaskCallbacks {
|
||||||
return {
|
return {
|
||||||
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
|
onBefore: async (startTime) => {
|
||||||
const logTime = startTime.format('YYYY-MM-DD-HH-mm-ss');
|
const logTime = startTime.format('YYYY-MM-DD-HH-mm-ss');
|
||||||
const logPath = `${doc.alias}/${logTime}.log`;
|
const logPath = `${doc.alias}/${logTime}.log`;
|
||||||
await SubscriptionModel.update(
|
await SubscriptionModel.update(
|
||||||
{
|
{
|
||||||
status: SubscriptionStatus.running,
|
status: SubscriptionStatus.running,
|
||||||
log_path: logPath,
|
log_path: logPath,
|
||||||
pid: cp.pid,
|
|
||||||
},
|
},
|
||||||
{ where: { id: doc.id } },
|
{ where: { id: doc.id } },
|
||||||
);
|
);
|
||||||
@@ -220,9 +195,17 @@ export default class SubscriptionService {
|
|||||||
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
||||||
}
|
}
|
||||||
if (beforeStr) {
|
if (beforeStr) {
|
||||||
fs.appendFileSync(absolutePath, `${beforeStr}\n\n`);
|
fs.appendFileSync(absolutePath, `${beforeStr}\n`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
|
||||||
|
await SubscriptionModel.update(
|
||||||
|
{
|
||||||
|
pid: cp.pid,
|
||||||
|
},
|
||||||
|
{ where: { id: doc.id } },
|
||||||
|
);
|
||||||
|
},
|
||||||
onEnd: async (cp, endTime, diff) => {
|
onEnd: async (cp, endTime, diff) => {
|
||||||
const sub = await this.getDb({ id: doc.id });
|
const sub = await this.getDb({ id: doc.id });
|
||||||
const absolutePath = await this.handleLogPath(sub.log_path as string);
|
const absolutePath = await this.handleLogPath(sub.log_path as string);
|
||||||
@@ -231,7 +214,7 @@ export default class SubscriptionService {
|
|||||||
let afterStr = '';
|
let afterStr = '';
|
||||||
try {
|
try {
|
||||||
if (sub.sub_after) {
|
if (sub.sub_after) {
|
||||||
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n`);
|
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n\n`);
|
||||||
afterStr = await this.promiseExec(sub.sub_after);
|
afterStr = await this.promiseExec(sub.sub_after);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -286,7 +269,7 @@ export default class SubscriptionService {
|
|||||||
|
|
||||||
public async update(payload: Subscription): Promise<Subscription> {
|
public async update(payload: Subscription): Promise<Subscription> {
|
||||||
const newDoc = await this.updateDb(payload);
|
const newDoc = await this.updateDb(payload);
|
||||||
await this.handleTask(newDoc);
|
await this.handleTask(newDoc, !newDoc.is_disabled);
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,16 +343,14 @@ export default class SubscriptionService {
|
|||||||
this.logger.silly(error);
|
this.logger.silly(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.handleTask(doc, false);
|
|
||||||
const command = this.formatCommand(doc);
|
const command = this.formatCommand(doc);
|
||||||
const err = await this.killTask(command);
|
const err = await this.killTask(command);
|
||||||
const absolutePath = await this.handleLogPath(doc.log_path as string);
|
const absolutePath = await this.handleLogPath(doc.log_path as string);
|
||||||
const str = err ? `\n${err}` : '';
|
const str = err ? `\n${err}` : '';
|
||||||
|
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(
|
||||||
`${absolutePath}`,
|
`${absolutePath}`,
|
||||||
`${str}\n## 执行结束... ${new Date()
|
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `,
|
||||||
.toLocaleString('zh', { hour12: false })
|
|
||||||
.replace(' 24:', ' 00:')} `,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { Request } from 'express';
|
|||||||
import ScheduleService from './schedule';
|
import ScheduleService from './schedule';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import got from 'got';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
@@ -111,7 +111,7 @@ export default class UserService {
|
|||||||
});
|
});
|
||||||
await this.notificationService.notify(
|
await this.notificationService.notify(
|
||||||
'登录通知',
|
'登录通知',
|
||||||
`你于${new Date(timestamp).toLocaleString()}在 ${address} ${
|
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||||
req.platform
|
req.platform
|
||||||
}端 登录成功,ip地址 ${ip}`,
|
}端 登录成功,ip地址 ${ip}`,
|
||||||
);
|
);
|
||||||
@@ -140,7 +140,7 @@ export default class UserService {
|
|||||||
});
|
});
|
||||||
await this.notificationService.notify(
|
await this.notificationService.notify(
|
||||||
'登录通知',
|
'登录通知',
|
||||||
`你于${new Date(timestamp).toLocaleString()}在 ${address} ${
|
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||||
req.platform
|
req.platform
|
||||||
}端 登录失败,ip地址 ${ip}`,
|
}端 登录失败,ip地址 ${ip}`,
|
||||||
);
|
);
|
||||||
|
|||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
import 'reflect-metadata';
|
||||||
|
import OpenService from './services/open';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
import LoggerInstance from './loaders/logger';
|
||||||
|
import fs from 'fs';
|
||||||
|
import config from './config';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const tokenFile = path.join(config.configPath, 'token.json');
|
||||||
|
|
||||||
|
async function getToken() {
|
||||||
|
try {
|
||||||
|
Container.set('logger', LoggerInstance);
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const appToken = await openService.generateSystemToken();
|
||||||
|
console.log(appToken.value);
|
||||||
|
await writeFile({
|
||||||
|
value: appToken.value,
|
||||||
|
expiration: appToken.expiration,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeFile(data: any) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
fs.writeFile(tokenFile, JSON.stringify(data), { encoding: 'utf8' }, () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getToken();
|
||||||
+8
-7
@@ -1,11 +1,12 @@
|
|||||||
FROM node:alpine
|
FROM python:alpine
|
||||||
|
|
||||||
ARG QL_MAINTAINER="whyour"
|
ARG QL_MAINTAINER="whyour"
|
||||||
LABEL maintainer="${QL_MAINTAINER}"
|
LABEL maintainer="${QL_MAINTAINER}"
|
||||||
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
||||||
ARG QL_BRANCH=master
|
ARG QL_BRANCH=develop
|
||||||
|
|
||||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules \
|
ENV PNPM_HOME=/root/.local/share/pnpm \
|
||||||
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules:$PNPM_HOME \
|
||||||
LANG=zh_CN.UTF-8 \
|
LANG=zh_CN.UTF-8 \
|
||||||
SHELL=/bin/bash \
|
SHELL=/bin/bash \
|
||||||
PS1="\u@\h:\w \$ " \
|
PS1="\u@\h:\w \$ " \
|
||||||
@@ -28,18 +29,18 @@ RUN set -x \
|
|||||||
perl \
|
perl \
|
||||||
openssl \
|
openssl \
|
||||||
nginx \
|
nginx \
|
||||||
python3 \
|
nodejs \
|
||||||
jq \
|
jq \
|
||||||
openssh \
|
openssh \
|
||||||
py3-pip \
|
npm \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
|
&& apk update \
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||||
&& git config --global user.name "qinglong" \
|
&& git config --global user.name "qinglong" \
|
||||||
|
&& git config --global http.postBuffer 524288000 \
|
||||||
&& npm install -g pnpm \
|
&& npm install -g pnpm \
|
||||||
&& pnpm setup \
|
|
||||||
&& source ~/.bashrc \
|
|
||||||
&& pnpm add -g pm2 ts-node typescript tslib \
|
&& pnpm add -g pm2 ts-node typescript tslib \
|
||||||
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||||
&& cd ${QL_DIR} \
|
&& cd ${QL_DIR} \
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ make_dir /etc/nginx/conf.d
|
|||||||
make_dir /run/nginx
|
make_dir /run/nginx
|
||||||
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||||
|
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -1,12 +1,12 @@
|
|||||||
upstream api {
|
upstream baseApi {
|
||||||
server 0.0.0.0:5600;
|
server 0.0.0.0:5600;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream public {
|
upstream publicApi {
|
||||||
server 0.0.0.0:5400;
|
server 0.0.0.0:5400;
|
||||||
}
|
}
|
||||||
|
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default keep-alive;
|
default keep-alive;
|
||||||
'websocket' upgrade;
|
'websocket' upgrade;
|
||||||
}
|
}
|
||||||
@@ -16,42 +16,42 @@ server {
|
|||||||
root /ql/static/dist;
|
root /ql/static/dist;
|
||||||
ssl_session_timeout 5m;
|
ssl_session_timeout 5m;
|
||||||
|
|
||||||
location /api/public {
|
location QL_BASE_URL/api/public/ {
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_pass http://public;
|
proxy_pass http://publicApi/api/public/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api {
|
location QL_BASE_URL/api/ {
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_pass http://api;
|
proxy_pass http://baseApi/api/;
|
||||||
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /open {
|
location QL_BASE_URL/open/ {
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_pass http://api;
|
proxy_pass http://baseApi/open/;
|
||||||
}
|
}
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_static on;
|
gzip_static on;
|
||||||
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript;
|
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript;
|
||||||
gzip_proxied any;
|
gzip_proxied any;
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_comp_level 6;
|
gzip_comp_level 6;
|
||||||
gzip_buffers 16 8k;
|
gzip_buffers 16 8k;
|
||||||
gzip_http_version 1.0;
|
gzip_http_version 1.0;
|
||||||
|
|
||||||
location / {
|
location QL_BASE_URL/ {
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ QL_BASE_URL/index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ .*\.(html)$ {
|
location ~ .*\.(html)$ {
|
||||||
|
|||||||
+19
-19
@@ -5,41 +5,41 @@ error_log /var/log/nginx/error.log warn;
|
|||||||
include /etc/nginx/modules/*.conf;
|
include /etc/nginx/modules/*.conf;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
worker_connections 1024;
|
worker_connections 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
|
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|
||||||
client_max_body_size 20m;
|
client_max_body_size 20m;
|
||||||
client_body_buffer_size 20m;
|
client_body_buffer_size 20m;
|
||||||
|
|
||||||
keepalive_timeout 65;
|
keepalive_timeout 65;
|
||||||
|
|
||||||
sendfile on;
|
sendfile on;
|
||||||
|
|
||||||
tcp_nodelay on;
|
tcp_nodelay on;
|
||||||
|
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
ssl_session_cache shared:SSL:2m;
|
ssl_session_cache shared:SSL:2m;
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_static on;
|
gzip_static on;
|
||||||
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript;
|
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript;
|
||||||
gzip_proxied any;
|
gzip_proxied any;
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_comp_level 6;
|
gzip_comp_level 6;
|
||||||
gzip_buffers 16 8k;
|
gzip_buffers 16 8k;
|
||||||
gzip_http_version 1.0;
|
gzip_http_version 1.0;
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log main;
|
access_log /var/log/nginx/access.log main;
|
||||||
include /etc/nginx/conf.d/*.conf;
|
include /etc/nginx/conf.d/*.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-10
@@ -31,17 +31,13 @@
|
|||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
"react": "17",
|
"react": "17",
|
||||||
"react-dom": "17"
|
"react-dom": "17"
|
||||||
},
|
}
|
||||||
"ignoreMissing": [
|
|
||||||
"monaco-editor",
|
|
||||||
"react-router"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@otplib/preset-default": "^12.0.1",
|
"@otplib/preset-default": "^12.0.1",
|
||||||
"@sentry/node": "^6.18.1",
|
"@sentry/node": "^7.0.0",
|
||||||
"@sentry/tracing": "^6.18.1",
|
"@sentry/tracing": "^7.0.0",
|
||||||
"body-parser": "^1.19.2",
|
"body-parser": "^1.19.2",
|
||||||
"celebrate": "^15.0.1",
|
"celebrate": "^15.0.1",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
@@ -63,10 +59,10 @@
|
|||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
"p-queue": "7.2.0",
|
"p-queue": "7.2.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sequelize": "^6.17.0",
|
"sequelize": "^6.20.1",
|
||||||
"serve-handler": "^6.1.3",
|
"serve-handler": "^6.1.3",
|
||||||
"sockjs": "^0.3.24",
|
"sockjs": "^0.3.24",
|
||||||
"sqlite3": "npm:@louislam/sqlite3@^15.0.3",
|
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
|
||||||
"toad-scheduler": "^1.6.0",
|
"toad-scheduler": "^1.6.0",
|
||||||
"typedi": "^0.10.0",
|
"typedi": "^0.10.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
@@ -77,7 +73,7 @@
|
|||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
"@ant-design/pro-layout": "^6.33.1",
|
"@ant-design/pro-layout": "^6.33.1",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "^4.3.1",
|
||||||
"@sentry/react": "^6.18.1",
|
"@sentry/react": "^7.0.0",
|
||||||
"@types/body-parser": "^1.19.2",
|
"@types/body-parser": "^1.19.2",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
@@ -121,6 +117,7 @@
|
|||||||
"react-split-pane": "^0.1.92",
|
"react-split-pane": "^0.1.92",
|
||||||
"sockjs-client": "^1.6.0",
|
"sockjs-client": "^1.6.0",
|
||||||
"ts-node": "^10.6.0",
|
"ts-node": "^10.6.0",
|
||||||
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.6.2",
|
"typescript": "^4.6.2",
|
||||||
"umi": "^3.5.21",
|
"umi": "^3.5.21",
|
||||||
"umi-request": "^1.4.0",
|
"umi-request": "^1.4.0",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 91 KiB |
@@ -29,6 +29,14 @@ MaxConcurrentNum="5"
|
|||||||
## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空
|
## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空
|
||||||
RandomDelay="300"
|
RandomDelay="300"
|
||||||
|
|
||||||
|
## 需要随机延迟运行任务的文件后缀,直接写后缀名即可,多个后缀用空格分开,例如: js py ts
|
||||||
|
## 默认仅给javascript任务加随机延迟,其它任务按定时规则准点运行。全部任务随机延迟赋值为空
|
||||||
|
RandomDelayFileExtensions="js"
|
||||||
|
|
||||||
|
## 每小时的第几分钟准点运行任务,当在这些时间运行任务时将忽略 RandomDelay 配置,不会被随机延迟
|
||||||
|
## 默认是第0分钟和第30分钟,例如21:00或21:30分的任务将会准点运行。不需要准点运行赋值为空
|
||||||
|
RandomDelayIgnoredMinutes="0 30"
|
||||||
|
|
||||||
## 如果你自己会写shell脚本,并且希望在每次运行 ql update 命令时,额外运行你的 shell 脚本,请赋值为 "true",默认为true
|
## 如果你自己会写shell脚本,并且希望在每次运行 ql update 命令时,额外运行你的 shell 脚本,请赋值为 "true",默认为true
|
||||||
EnableExtraShell="true"
|
EnableExtraShell="true"
|
||||||
|
|
||||||
@@ -39,7 +47,7 @@ AutoStartBot=""
|
|||||||
BotRepoUrl=""
|
BotRepoUrl=""
|
||||||
|
|
||||||
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
|
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
|
||||||
PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple"
|
PipMirror="https://pypi.doubanio.com/simple/"
|
||||||
|
|
||||||
## 通知环境变量
|
## 通知环境变量
|
||||||
## 1. Server酱
|
## 1. Server酱
|
||||||
|
|||||||
+10
-3
@@ -97,6 +97,13 @@ let IGOT_PUSH_KEY = '';
|
|||||||
let PUSH_PLUS_TOKEN = '';
|
let PUSH_PLUS_TOKEN = '';
|
||||||
let PUSH_PLUS_USER = '';
|
let PUSH_PLUS_USER = '';
|
||||||
|
|
||||||
|
// =======================================Cool Push设置区域=======================================
|
||||||
|
//官方文档:https://cp.xuthus.cc/docs
|
||||||
|
//QQ_SKEY: Cool Push登录授权后推送消息的调用代码Skey
|
||||||
|
//QQ_MODE: 推送模式详情请登录获取QQ_SKEY后见https://cp.xuthus.cc/feat
|
||||||
|
let QQ_SKEY = '';
|
||||||
|
let QQ_MODE = '';
|
||||||
|
|
||||||
//==========================云端环境变量的判断与接收=========================
|
//==========================云端环境变量的判断与接收=========================
|
||||||
if (process.env.GOTIFY_URL) {
|
if (process.env.GOTIFY_URL) {
|
||||||
GOTIFY_URL = process.env.GOTIFY_URL;
|
GOTIFY_URL = process.env.GOTIFY_URL;
|
||||||
@@ -723,9 +730,9 @@ function qywxamNotify(text, desp) {
|
|||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
$.post(options_accesstoken, (err, resp, data) => {
|
$.post(options_accesstoken, (err, resp, data) => {
|
||||||
html = desp.replace(/\n/g, '<br/>');
|
let html = desp.replace(/\n/g, '<br/>');
|
||||||
var json = JSON.parse(data);
|
let json = JSON.parse(data);
|
||||||
accesstoken = json.access_token;
|
let accesstoken = json.access_token;
|
||||||
let options;
|
let options;
|
||||||
|
|
||||||
switch (QYWX_AM_AY[4]) {
|
switch (QYWX_AM_AY[4]) {
|
||||||
|
|||||||
+8
-26
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
get_token() {
|
get_token() {
|
||||||
token=$(cat $file_auth_user | jq -r .token)
|
token=$(cat $file_auth_token | jq -r .value)
|
||||||
}
|
}
|
||||||
|
|
||||||
add_cron_api() {
|
add_cron_api() {
|
||||||
@@ -17,7 +17,7 @@ add_cron_api() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||||
@@ -52,7 +52,7 @@ update_cron_api() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
@@ -84,7 +84,7 @@ update_cron_command_api() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
@@ -109,7 +109,7 @@ del_cron_api() {
|
|||||||
local ids=$1
|
local ids=$1
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||||
-X 'DELETE' \
|
-X 'DELETE' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
@@ -130,24 +130,6 @@ del_cron_api() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
get_user_info() {
|
|
||||||
local currentTimeStamp=$(date +%s)
|
|
||||||
local api=$(
|
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/user?t=$currentTimeStamp" \
|
|
||||||
-H 'Accept: */*' \
|
|
||||||
-H "Authorization: Bearer $token" \
|
|
||||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \
|
|
||||||
-H 'Referer: http://0.0.0.0:5700/crontab' \
|
|
||||||
-H 'Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7' \
|
|
||||||
--compressed
|
|
||||||
)
|
|
||||||
code=$(echo $api | jq -r .code)
|
|
||||||
if [[ $code != 200 ]]; then
|
|
||||||
echo -e "请先登录!"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
update_cron() {
|
update_cron() {
|
||||||
local ids="$1"
|
local ids="$1"
|
||||||
local status="$2"
|
local status="$2"
|
||||||
@@ -157,7 +139,7 @@ update_cron() {
|
|||||||
local runningTime="${6:-0}"
|
local runningTime="${6:-0}"
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons/status?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
@@ -172,7 +154,7 @@ update_cron() {
|
|||||||
code=$(echo $api | jq -r .code)
|
code=$(echo $api | jq -r .code)
|
||||||
message=$(echo $api | jq -r .message)
|
message=$(echo $api | jq -r .message)
|
||||||
if [[ $code != 200 ]]; then
|
if [[ $code != 200 ]]; then
|
||||||
echo -e "\n## 更新任务状态失败(${message})\n" >> $log_path
|
echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +163,7 @@ notify_api() {
|
|||||||
local content=$2
|
local content=$2
|
||||||
local currentTimeStamp=$(date +%s)
|
local currentTimeStamp=$(date +%s)
|
||||||
local api=$(
|
local api=$(
|
||||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/system/notify?t=$currentTimeStamp" \
|
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
||||||
-X 'PUT' \
|
-X 'PUT' \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ copy_dep() {
|
|||||||
echo -e "---> 2. 复制nginx配置文件\n"
|
echo -e "---> 2. 复制nginx配置文件\n"
|
||||||
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||||
|
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
||||||
echo -e "---> 配置文件复制完成\n"
|
echo -e "---> 配置文件复制完成\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ check_pm2() {
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
echo -e "=====> 开始检测"
|
echo -e "=====> 开始检测"
|
||||||
|
npm i -g pnpm
|
||||||
pnpm add -g pm2
|
pnpm add -g pm2
|
||||||
copy_dep
|
copy_dep
|
||||||
check_ql
|
check_ql
|
||||||
|
|||||||
+52
-7
@@ -14,7 +14,6 @@ dir_log=$dir_data/log
|
|||||||
dir_db=$dir_data/db
|
dir_db=$dir_data/db
|
||||||
dir_dep=$dir_data/deps
|
dir_dep=$dir_data/deps
|
||||||
dir_list_tmp=$dir_log/.tmp
|
dir_list_tmp=$dir_log/.tmp
|
||||||
dir_code=$dir_log/code
|
|
||||||
dir_update_log=$dir_log/update
|
dir_update_log=$dir_log/update
|
||||||
ql_static_repo=$dir_repo/static
|
ql_static_repo=$dir_repo/static
|
||||||
|
|
||||||
@@ -25,6 +24,7 @@ file_sharecode=$dir_config/sharecode.sh
|
|||||||
file_config_user=$dir_config/config.sh
|
file_config_user=$dir_config/config.sh
|
||||||
file_auth_sample=$dir_sample/auth.sample.json
|
file_auth_sample=$dir_sample/auth.sample.json
|
||||||
file_auth_user=$dir_config/auth.json
|
file_auth_user=$dir_config/auth.json
|
||||||
|
file_auth_token=$dir_config/token.json
|
||||||
file_extra_shell=$dir_config/extra.sh
|
file_extra_shell=$dir_config/extra.sh
|
||||||
file_task_before=$dir_config/task_before.sh
|
file_task_before=$dir_config/task_before.sh
|
||||||
file_task_after=$dir_config/task_after.sh
|
file_task_after=$dir_config/task_after.sh
|
||||||
@@ -52,12 +52,10 @@ list_own_drop=$dir_list_tmp/own_drop.list
|
|||||||
link_name=(
|
link_name=(
|
||||||
task
|
task
|
||||||
ql
|
ql
|
||||||
notify
|
|
||||||
)
|
)
|
||||||
original_name=(
|
original_name=(
|
||||||
task.sh
|
task.sh
|
||||||
update.sh
|
update.sh
|
||||||
notify.sh
|
|
||||||
)
|
)
|
||||||
|
|
||||||
init_env() {
|
init_env() {
|
||||||
@@ -69,6 +67,7 @@ import_config() {
|
|||||||
[[ -f $file_config_user ]] && . $file_config_user
|
[[ -f $file_config_user ]] && . $file_config_user
|
||||||
[[ -f $file_env ]] && . $file_env
|
[[ -f $file_env ]] && . $file_env
|
||||||
|
|
||||||
|
ql_base_url=${QlBaseUrl:-""}
|
||||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
||||||
proxy_url=${ProxyUrl:-""}
|
proxy_url=${ProxyUrl:-""}
|
||||||
file_extensions=${RepoFileExtensions:-"js py"}
|
file_extensions=${RepoFileExtensions:-"js py"}
|
||||||
@@ -101,7 +100,7 @@ make_dir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
detect_termux() {
|
detect_termux() {
|
||||||
if [[ ${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT} ]] || [[ $PATH == *com.termux* ]]; then
|
if [[ $PATH == *com.termux* ]]; then
|
||||||
is_termux=1
|
is_termux=1
|
||||||
else
|
else
|
||||||
is_termux=0
|
is_termux=0
|
||||||
@@ -248,11 +247,11 @@ fix_config() {
|
|||||||
npm_install_sub() {
|
npm_install_sub() {
|
||||||
set_proxy
|
set_proxy
|
||||||
if [ $is_termux -eq 1 ]; then
|
if [ $is_termux -eq 1 ]; then
|
||||||
npm install --production --no-bin-links --registry=https://registry.npm.taobao.org || npm install --production --no-bin-links
|
npm install --production --no-bin-links --registry=https://registry.npmmirror.com || npm install --production --no-bin-links
|
||||||
elif ! type pnpm &>/dev/null; then
|
elif ! type pnpm &>/dev/null; then
|
||||||
npm install --production --registry=https://registry.npm.taobao.org || npm install --production
|
npm install --production --registry=https://registry.npmmirror.com || npm install --production
|
||||||
else
|
else
|
||||||
pnpm install --loglevel error --production --registry=https://registry.npm.taobao.org || pnpm install --production --loglevel error
|
pnpm install --loglevel error --production --registry=https://registry.npmmirror.com || pnpm install --production --loglevel error
|
||||||
fi
|
fi
|
||||||
unset_proxy
|
unset_proxy
|
||||||
}
|
}
|
||||||
@@ -380,6 +379,52 @@ reload_pm2() {
|
|||||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diff_time() {
|
||||||
|
local format="$1"
|
||||||
|
local begin_time="$2"
|
||||||
|
local end_time="$3"
|
||||||
|
|
||||||
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
|
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
|
||||||
|
else
|
||||||
|
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
||||||
|
fi
|
||||||
|
echo "$diff_time"
|
||||||
|
}
|
||||||
|
|
||||||
|
format_time() {
|
||||||
|
local format="$1"
|
||||||
|
local time="$2"
|
||||||
|
|
||||||
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
|
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
|
||||||
|
else
|
||||||
|
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
format_log_time() {
|
||||||
|
local format="$1"
|
||||||
|
local time="$2"
|
||||||
|
|
||||||
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
|
echo $(date -j -f "$format" "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||||
|
else
|
||||||
|
echo $(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
format_timestamp() {
|
||||||
|
local format="$1"
|
||||||
|
local time="$2"
|
||||||
|
|
||||||
|
if [[ $is_macos -eq 1 ]]; then
|
||||||
|
echo $(date -j -f "$format" "$time" "+%s")
|
||||||
|
else
|
||||||
|
echo $(date -d "$time" "+%s")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
init_env
|
init_env
|
||||||
detect_termux
|
detect_termux
|
||||||
detect_macos
|
detect_macos
|
||||||
|
|||||||
+58
-35
@@ -24,12 +24,29 @@ define_program() {
|
|||||||
random_delay() {
|
random_delay() {
|
||||||
local random_delay_max=$RandomDelay
|
local random_delay_max=$RandomDelay
|
||||||
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
|
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
|
||||||
local current_min=$(date "+%-M")
|
local file_param=$1
|
||||||
if [[ $current_min -ne 0 ]] && [[ $current_min -ne 30 ]]; then
|
local file_extensions=${RandomDelayFileExtensions-"js"}
|
||||||
delay_second=$(($(gen_random_num $random_delay_max) + 1))
|
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
|
||||||
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
|
|
||||||
sleep $delay_second
|
if [[ -n $file_extensions ]]; then
|
||||||
|
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
|
||||||
|
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
|
||||||
|
return
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
local current_min
|
||||||
|
current_min=$(date "+%-M")
|
||||||
|
for minute in $ignored_minutes; do
|
||||||
|
if [[ $current_min -eq $minute ]]; then
|
||||||
|
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
|
||||||
|
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
|
||||||
|
sleep $delay_second
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,14 +95,12 @@ run_nohup() {
|
|||||||
run_normal() {
|
run_normal() {
|
||||||
local file_param=$1
|
local file_param=$1
|
||||||
define_program "$file_param"
|
define_program "$file_param"
|
||||||
if [[ $file_param == *.js ]]; then
|
if [[ $# -eq 1 ]]; then
|
||||||
if [[ $# -eq 1 ]]; then
|
random_delay "$file_param"
|
||||||
random_delay
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local time=$(date)
|
local time=$(date "+$time_format")
|
||||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(format_log_time "$time_format" "$time")
|
||||||
log_dir_tmp="${file_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
if [[ $file_param =~ "/" ]]; then
|
if [[ $file_param =~ "/" ]]; then
|
||||||
if [[ $file_param == /* ]]; then
|
if [[ $file_param == /* ]]; then
|
||||||
@@ -99,12 +114,13 @@ run_normal() {
|
|||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
log_dir="${log_dir_tmp%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $dir_log/$log_path"
|
cmd=">> $dir_log/$log_path 2>&1"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
make_dir "$dir_log/$log_dir"
|
make_dir "$dir_log/$log_dir"
|
||||||
|
|
||||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(date -d "$time" "+%s")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||||
|
|
||||||
@@ -118,12 +134,13 @@ run_normal() {
|
|||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
file_param=${file_param/$relative_path\//}
|
file_param=${file_param/$relative_path\//}
|
||||||
fi
|
fi
|
||||||
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
|
|
||||||
|
eval $timeoutCmd $which_program $file_param $cmd
|
||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
eval . $file_task_after "$@" $cmd
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local end_timestamp=$(date "+%s")
|
local end_timestamp=$(date "+%s")
|
||||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
local diff_time=$(expr $end_timestamp - $begin_timestamp)
|
||||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||||
}
|
}
|
||||||
@@ -154,8 +171,8 @@ run_concurrent() {
|
|||||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||||
|
|
||||||
define_program "$file_param"
|
define_program "$file_param"
|
||||||
local time=$(date)
|
local time=$(date "+$time_format")
|
||||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(format_log_time "$time_format" "$time")
|
||||||
log_dir_tmp="${file_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
if [[ $file_param =~ "/" ]]; then
|
if [[ $file_param =~ "/" ]]; then
|
||||||
if [[ $file_param == /* ]]; then
|
if [[ $file_param == /* ]]; then
|
||||||
@@ -169,12 +186,12 @@ run_concurrent() {
|
|||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
log_dir="${log_dir_tmp%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $dir_log/$log_path"
|
cmd=">> $dir_log/$log_path 2>&1"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
make_dir "$dir_log/$log_dir"
|
make_dir "$dir_log/$log_dir"
|
||||||
|
|
||||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(date -d "$time" "+%s")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||||
@@ -196,7 +213,7 @@ run_concurrent() {
|
|||||||
for i in "${!array[@]}"; do
|
for i in "${!array[@]}"; do
|
||||||
export ${env_param}=${array[i]}
|
export ${env_param}=${array[i]}
|
||||||
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
|
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
|
||||||
timeout -k 10s $command_timeout_time $which_program $file_param &>$single_log_path &
|
eval $timeoutCmd $which_program $file_param &>$single_log_path &
|
||||||
done
|
done
|
||||||
|
|
||||||
wait
|
wait
|
||||||
@@ -224,8 +241,8 @@ run_designated() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
define_program "$file_param"
|
define_program "$file_param"
|
||||||
local time=$(date)
|
local time=$(date "+$time_format")
|
||||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(format_log_time "$time_format" "$time")
|
||||||
log_dir_tmp="${file_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
if [[ $file_param =~ "/" ]]; then
|
if [[ $file_param =~ "/" ]]; then
|
||||||
if [[ $file_param == /* ]]; then
|
if [[ $file_param == /* ]]; then
|
||||||
@@ -239,12 +256,12 @@ run_designated() {
|
|||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
log_dir="${log_dir_tmp%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $dir_log/$log_path"
|
cmd=">> $dir_log/$log_path 2>&1"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
make_dir "$dir_log/$log_dir"
|
make_dir "$dir_log/$log_dir"
|
||||||
|
|
||||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(date -d "$time" "+%s")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
|
||||||
local envs=$(eval echo "\$${env_param}")
|
local envs=$(eval echo "\$${env_param}")
|
||||||
local array=($(echo $envs | sed 's/&/ /g'))
|
local array=($(echo $envs | sed 's/&/ /g'))
|
||||||
@@ -274,7 +291,7 @@ run_designated() {
|
|||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
file_param=${file_param/$relative_path\//}
|
file_param=${file_param/$relative_path\//}
|
||||||
fi
|
fi
|
||||||
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
|
eval $timeoutCmd $which_program $file_param $cmd
|
||||||
|
|
||||||
eval . $file_task_after "$@" $cmd
|
eval . $file_task_after "$@" $cmd
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
@@ -288,8 +305,8 @@ run_designated() {
|
|||||||
run_else() {
|
run_else() {
|
||||||
local file_param="$1"
|
local file_param="$1"
|
||||||
define_program "$file_param"
|
define_program "$file_param"
|
||||||
local time=$(date)
|
local time=$(date "+$time_format")
|
||||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(format_log_time "$time_format" "$time")
|
||||||
log_dir_tmp="${file_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
if [[ $file_param =~ "/" ]]; then
|
if [[ $file_param =~ "/" ]]; then
|
||||||
if [[ $file_param == /* ]]; then
|
if [[ $file_param == /* ]]; then
|
||||||
@@ -303,12 +320,12 @@ run_else() {
|
|||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
log_dir="${log_dir_tmp%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $dir_log/$log_path"
|
cmd=">> $dir_log/$log_path 2>&1"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
make_dir "$dir_log/$log_dir"
|
make_dir "$dir_log/$log_dir"
|
||||||
|
|
||||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(date -d "$time" "+%s")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
|
||||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||||
@@ -325,7 +342,7 @@ run_else() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
shift
|
shift
|
||||||
eval timeout -k 10s $command_timeout_time $which_program "$file_param" "$@" $cmd
|
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
|
||||||
|
|
||||||
eval . $file_task_after "$file_param" "$@" $cmd
|
eval . $file_task_after "$file_param" "$@" $cmd
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
@@ -348,7 +365,13 @@ main() {
|
|||||||
done
|
done
|
||||||
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
||||||
|
|
||||||
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
timeoutCmd=""
|
||||||
|
if type timeout &>/dev/null; then
|
||||||
|
timeoutCmd="timeout -k 10s $command_timeout_time "
|
||||||
|
fi
|
||||||
|
|
||||||
|
time_format="%Y-%m-%d %H:%M:%S"
|
||||||
|
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
||||||
case $# in
|
case $# in
|
||||||
1)
|
1)
|
||||||
run_normal "$1"
|
run_normal "$1"
|
||||||
|
|||||||
+11
-9
@@ -12,14 +12,16 @@ diff_cron() {
|
|||||||
local list_task="$2"
|
local list_task="$2"
|
||||||
local list_add="$3"
|
local list_add="$3"
|
||||||
local list_drop="$4"
|
local list_drop="$4"
|
||||||
if [[ -s $list_task ]]; then
|
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||||
grep -vwf $list_task $list_scripts >$list_add
|
grep -vwf $list_task $list_scripts >$list_add
|
||||||
elif [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
grep -vwf $list_scripts $list_task >$list_drop
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||||
cp -f $list_scripts $list_add
|
cp -f $list_scripts $list_add
|
||||||
fi
|
fi
|
||||||
if [[ -s $list_scripts ]]; then
|
|
||||||
grep -vwf $list_scripts $list_task >$list_drop
|
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
|
||||||
else
|
|
||||||
cp -f $list_task $list_drop
|
cp -f $list_task $list_drop
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -305,7 +307,6 @@ patch_version() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 兼容pnpm@7
|
# 兼容pnpm@7
|
||||||
npm i -g pnpm
|
|
||||||
pnpm setup
|
pnpm setup
|
||||||
source ~/.bashrc
|
source ~/.bashrc
|
||||||
|
|
||||||
@@ -387,6 +388,9 @@ gen_list_repo() {
|
|||||||
local index=0
|
local index=0
|
||||||
if [[ $6 ]]; then
|
if [[ $6 ]]; then
|
||||||
file_extensions="$6"
|
file_extensions="$6"
|
||||||
|
if [[ $file_extensions =~ "|" ]]; then
|
||||||
|
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
for extension in $file_extensions; do
|
for extension in $file_extensions; do
|
||||||
if [[ $index -eq 0 ]]; then
|
if [[ $index -eq 0 ]]; then
|
||||||
@@ -487,7 +491,6 @@ main() {
|
|||||||
run_extra_shell >>$log_path
|
run_extra_shell >>$log_path
|
||||||
;;
|
;;
|
||||||
repo)
|
repo)
|
||||||
get_user_info
|
|
||||||
get_uniq_path "$p2" "$p6"
|
get_uniq_path "$p2" "$p6"
|
||||||
if [[ -n $p2 ]]; then
|
if [[ -n $p2 ]]; then
|
||||||
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
|
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
|
||||||
@@ -497,7 +500,6 @@ main() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
raw)
|
raw)
|
||||||
get_user_info
|
|
||||||
get_uniq_path "$p2"
|
get_uniq_path "$p2"
|
||||||
if [[ -n $p2 ]]; then
|
if [[ -n $p2 ]]; then
|
||||||
update_raw "$p2"
|
update_raw "$p2"
|
||||||
@@ -539,7 +541,7 @@ main() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
local diff_time=$(diff_time "%Y-%m-%d %H:%M:%S" "$begin_time" "$end_time")
|
||||||
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
|
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
|
||||||
echo -e "\n## 执行结束... $end_time 耗时 $diff_time 秒" >>$log_path
|
echo -e "\n## 执行结束... $end_time 耗时 $diff_time 秒" >>$log_path
|
||||||
cat $log_path
|
cat $log_path
|
||||||
|
|||||||
+26
-8
@@ -56,7 +56,13 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: @component-background;
|
background-color: @component-background;
|
||||||
padding: 16px;
|
padding: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-container-wrapper-has-tab {
|
||||||
|
.ant-pro-grid-content.wide .ant-pro-page-container-children-content {
|
||||||
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +142,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.ant-pro-grid-content-children {
|
.ant-pro-grid-content-children {
|
||||||
height: calc(100% - 36px);
|
height: 100%;
|
||||||
|
|
||||||
> div,
|
> div,
|
||||||
.log-container,
|
.log-container,
|
||||||
@@ -264,16 +270,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-page-container-children-content {
|
|
||||||
margin: 18px 18px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-pro-basicLayout-content {
|
.ant-pro-basicLayout-content {
|
||||||
margin: 18px;
|
margin: 12px;
|
||||||
|
|
||||||
.ant-pro-page-container {
|
.ant-pro-page-container {
|
||||||
margin: -18px -18px -18px;
|
margin: -12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-pro-page-container-warp .ant-page-header {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-page-container-children-content {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-global-header {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-item.ant-pro-sider-collapsed-button {
|
.ant-menu-item.ant-pro-sider-collapsed-button {
|
||||||
@@ -312,6 +326,10 @@
|
|||||||
background-color: #373739;
|
background-color: #373739;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-layout-sider {
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-pro-sider-logo {
|
.ant-pro-sider-logo {
|
||||||
padding: 16px 8px !important;
|
padding: 16px 8px !important;
|
||||||
|
|
||||||
|
|||||||
@@ -186,9 +186,8 @@ export default function (props: any) {
|
|||||||
if (
|
if (
|
||||||
['/login', '/initialization', '/error'].includes(props.location.pathname)
|
['/login', '/initialization', '/error'].includes(props.location.pathname)
|
||||||
) {
|
) {
|
||||||
document.title = `${
|
document.title = `${(config.documentTitleMap as any)[props.location.pathname]
|
||||||
(config.documentTitleMap as any)[props.location.pathname]
|
} - 控制面板`;
|
||||||
} - 控制面板`;
|
|
||||||
if (
|
if (
|
||||||
systemInfo?.isInitialized &&
|
systemInfo?.isInitialized &&
|
||||||
props.location.pathname === '/initialization'
|
props.location.pathname === '/initialization'
|
||||||
@@ -282,7 +281,7 @@ export default function (props: any) {
|
|||||||
shape="square"
|
shape="square"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<UserOutlined />}
|
icon={<UserOutlined />}
|
||||||
src={`/api/static/${user.avatar}`}
|
src={user.avatar ? `/api/static/${user.avatar}` : ''}
|
||||||
/>
|
/>
|
||||||
<span style={{ marginLeft: 5 }}>{user.username}</span>
|
<span style={{ marginLeft: 5 }}>{user.username}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -304,7 +303,7 @@ export default function (props: any) {
|
|||||||
shape="square"
|
shape="square"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<UserOutlined />}
|
icon={<UserOutlined />}
|
||||||
src={`/api/static/${user.avatar}`}
|
src={user.avatar ? `/api/static/${user.avatar}` : ''}
|
||||||
/>
|
/>
|
||||||
<span style={{ marginLeft: 5 }}>{user.username}</span>
|
<span style={{ marginLeft: 5 }}>{user.username}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
FileOutlined,
|
FileOutlined,
|
||||||
PlayCircleOutlined,
|
PlayCircleOutlined,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
|
FullscreenOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { CrontabStatus } from './index';
|
import { CrontabStatus } from './index';
|
||||||
import { diffTime } from '@/utils/date';
|
import { diffTime } from '@/utils/date';
|
||||||
@@ -106,7 +107,7 @@ const CronDetailModal = ({
|
|||||||
glyphMargin: false,
|
glyphMargin: false,
|
||||||
wordWrap: 'on',
|
wordWrap: 'on',
|
||||||
}}
|
}}
|
||||||
onMount={(editor) => {
|
onMount={(editor, monaco) => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -352,6 +353,11 @@ const CronDetailModal = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fullscreen = () => {
|
||||||
|
const editorElement = editorRef.current._domElement as HTMLElement;
|
||||||
|
editorElement.parentElement?.requestFullscreen();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cron && cron.id) {
|
if (cron && cron.id) {
|
||||||
setCurrentCron(cron);
|
setCurrentCron(cron);
|
||||||
@@ -529,13 +535,20 @@ const CronDetailModal = ({
|
|||||||
}}
|
}}
|
||||||
tabBarExtraContent={
|
tabBarExtraContent={
|
||||||
activeTabKey === 'script' && (
|
activeTabKey === 'script' && (
|
||||||
<Button
|
<>
|
||||||
type="primary"
|
<Button
|
||||||
style={{ marginRight: 8 }}
|
type="primary"
|
||||||
onClick={saveFile}
|
style={{ marginRight: 8 }}
|
||||||
>
|
onClick={saveFile}
|
||||||
保存
|
>
|
||||||
</Button>
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<FullscreenOutlined />}
|
||||||
|
onClick={fullscreen}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -101,3 +101,76 @@
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crontab-view {
|
||||||
|
.ant-tabs-nav-wrap {
|
||||||
|
flex: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav-operations {
|
||||||
|
position: absolute;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-more {
|
||||||
|
margin-left: 32px;
|
||||||
|
padding: 8px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
color: #1890ff;
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #1890ff;
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.more-active {
|
||||||
|
.ant-tabs-nav-list {
|
||||||
|
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
width: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-create-modal-filters {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.ant-space-item:nth-child(3) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.drop-over-downward td {
|
||||||
|
border-bottom: 2px dashed #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.drop-over-upward td {
|
||||||
|
border-top: 2px dashed #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-manage-modal {
|
||||||
|
.ant-modal-body {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+238
-73
@@ -12,6 +12,8 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Input,
|
Input,
|
||||||
Popover,
|
Popover,
|
||||||
|
Tabs,
|
||||||
|
TablePaginationConfig,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
@@ -27,6 +29,11 @@ import {
|
|||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
FieldTimeOutlined,
|
FieldTimeOutlined,
|
||||||
PushpinOutlined,
|
PushpinOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
UnorderedListOutlined,
|
||||||
|
CheckOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
@@ -39,6 +46,10 @@ import { diffTime } from '@/utils/date';
|
|||||||
import { getTableScroll } from '@/utils/index';
|
import { getTableScroll } from '@/utils/index';
|
||||||
import { history } from 'umi';
|
import { history } from 'umi';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
import ViewCreateModal from './viewCreateModal';
|
||||||
|
import ViewManageModal from './viewManageModal';
|
||||||
|
import pagination from 'antd/lib/pagination';
|
||||||
|
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -122,8 +133,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a: any, b: any) => a.name.localeCompare(b.name),
|
compare: (a: any, b: any) => a?.name?.localeCompare(b?.name),
|
||||||
multiple: 2,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -148,7 +158,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
},
|
},
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a: any, b: any) => a.command.localeCompare(b.command),
|
compare: (a: any, b: any) => a.command.localeCompare(b.command),
|
||||||
multiple: 3,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -159,7 +168,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a: any, b: any) => a.schedule.localeCompare(b.schedule),
|
compare: (a: any, b: any) => a.schedule.localeCompare(b.schedule),
|
||||||
multiple: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -346,12 +354,24 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||||
const [logCron, setLogCron] = useState<any>();
|
const [logCron, setLogCron] = useState<any>();
|
||||||
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [pageConf, setPageConf] = useState<{
|
||||||
const [pageSize, setPageSize] = useState(20);
|
page: number;
|
||||||
|
size: number;
|
||||||
|
sorter: any;
|
||||||
|
}>({} as any);
|
||||||
|
const [viewConf, setViewConf] = useState<any>();
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||||
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
||||||
const [detailCron, setDetailCron] = useState<any>();
|
const [detailCron, setDetailCron] = useState<any>();
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const [total, setTotal] = useState<number>();
|
||||||
|
const [isCreateViewModalVisible, setIsCreateViewModalVisible] =
|
||||||
|
useState(false);
|
||||||
|
const [isViewManageModalVisible, setIsViewManageModalVisible] =
|
||||||
|
useState(false);
|
||||||
|
const [cronViews, setCronViews] = useState<any[]>([]);
|
||||||
|
const [enabledCronViews, setEnabledCronViews] = useState<any[]>([]);
|
||||||
|
const [moreMenuActive, setMoreMenuActive] = useState(false);
|
||||||
|
|
||||||
const goToScriptManager = (record: any) => {
|
const goToScriptManager = (record: any) => {
|
||||||
const cmd = record.command.split(' ') as string[];
|
const cmd = record.command.split(' ') as string[];
|
||||||
@@ -373,37 +393,35 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const getCrons = () => {
|
const getCrons = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
const { page, size, sorter } = pageConf;
|
||||||
|
let url = `${config.apiPrefix}crons?searchValue=${searchText}&page=${page}&size=${size}`;
|
||||||
|
if (sorter && sorter.field) {
|
||||||
|
url += `&sortField=${sorter.field}&sortType=${
|
||||||
|
sorter.order === 'ascend' ? 'ASC' : 'DESC'
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
if (viewConf) {
|
||||||
|
url += `&queryString=${JSON.stringify({
|
||||||
|
filters: viewConf.filters,
|
||||||
|
sorts: viewConf.sorts,
|
||||||
|
})}`;
|
||||||
|
}
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}crons?searchValue=${searchText}`)
|
.get(url)
|
||||||
.then((data: any) => {
|
.then((_data: any) => {
|
||||||
|
const { data, total } = _data.data;
|
||||||
setValue(
|
setValue(
|
||||||
data.data
|
data.map((x) => {
|
||||||
.sort((a: any, b: any) => {
|
return {
|
||||||
const sortA =
|
...x,
|
||||||
a.isPinned && a.status !== 0
|
nextRunTime: cron_parser
|
||||||
? 5
|
.parseExpression(x.schedule)
|
||||||
: a.isDisabled && a.status !== 0
|
.next()
|
||||||
? 4
|
.toDate(),
|
||||||
: a.status;
|
};
|
||||||
const sortB =
|
}),
|
||||||
b.isPinned && b.status !== 0
|
|
||||||
? 5
|
|
||||||
: b.isDisabled && b.status !== 0
|
|
||||||
? 4
|
|
||||||
: b.status;
|
|
||||||
return CrontabSort[sortA] - CrontabSort[sortB];
|
|
||||||
})
|
|
||||||
.map((x) => {
|
|
||||||
return {
|
|
||||||
...x,
|
|
||||||
nextRunTime: cron_parser
|
|
||||||
.parseExpression(x.schedule)
|
|
||||||
.next()
|
|
||||||
.toDate(),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
setCurrentPage(1);
|
setTotal(total);
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
@@ -743,11 +761,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
selectedRowIds,
|
selectedRowIds,
|
||||||
onChange: onSelectChange,
|
onChange: onSelectChange,
|
||||||
selections: [
|
|
||||||
Table.SELECTION_ALL,
|
|
||||||
Table.SELECTION_INVERT,
|
|
||||||
Table.SELECTION_NONE,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const delCrons = () => {
|
const delCrons = () => {
|
||||||
@@ -796,10 +809,14 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPageChange = (page: number, pageSize: number | undefined) => {
|
const onPageChange = (
|
||||||
setCurrentPage(page);
|
pagination: TablePaginationConfig,
|
||||||
setPageSize(pageSize as number);
|
filters: Record<string, FilterValue | null>,
|
||||||
localStorage.setItem('pageSize', pageSize + '');
|
sorter: SorterResult<any> | SorterResult<any>[],
|
||||||
|
) => {
|
||||||
|
const { current, pageSize } = pagination;
|
||||||
|
setPageConf({ page: current as number, size: pageSize as number, sorter });
|
||||||
|
localStorage.setItem('pageSize', String(pageSize));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRowClassName = (record: any, index: number) => {
|
const getRowClassName = (record: any, index: number) => {
|
||||||
@@ -814,39 +831,36 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
}, [logCron]);
|
}, [logCron]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getCrons();
|
setPageConf({ ...pageConf, page: 1 });
|
||||||
}, [searchText]);
|
}, [searchText]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
|
if (pageConf.page && pageConf.size) {
|
||||||
|
getCrons();
|
||||||
|
}
|
||||||
|
}, [pageConf, viewConf]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (viewConf && enabledCronViews && enabledCronViews.length > 0) {
|
||||||
|
const view = enabledCronViews.slice(2).find((x) => x.id === viewConf.id);
|
||||||
|
setMoreMenuActive(!!view);
|
||||||
|
}
|
||||||
|
}, [viewConf, enabledCronViews]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPageConf({
|
||||||
|
page: 1,
|
||||||
|
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
||||||
|
sorter: {},
|
||||||
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setTableScrollHeight(getTableScroll());
|
setTableScrollHeight(getTableScroll());
|
||||||
});
|
});
|
||||||
|
getCronViews();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
const panelContent = (
|
||||||
<PageContainer
|
<>
|
||||||
className="ql-container-wrapper crontab-wrapper"
|
|
||||||
title="定时任务"
|
|
||||||
extra={[
|
|
||||||
<Search
|
|
||||||
placeholder="请输入名称或者关键词"
|
|
||||||
style={{ width: 'auto' }}
|
|
||||||
enterButton
|
|
||||||
allowClear
|
|
||||||
loading={loading}
|
|
||||||
value={searchValue}
|
|
||||||
onChange={(e) => setSearchValue(e.target.value)}
|
|
||||||
onSearch={onSearch}
|
|
||||||
/>,
|
|
||||||
<Button key="2" type="primary" onClick={() => addCron()}>
|
|
||||||
新建任务
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
header={{
|
|
||||||
style: headerStyle,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedRowIds.length > 0 && (
|
{selectedRowIds.length > 0 && (
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
|
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
|
||||||
@@ -906,15 +920,16 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: pageConf.page,
|
||||||
onChange: onPageChange,
|
pageSize: pageConf.size,
|
||||||
pageSize: pageSize,
|
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
simple: isPhone,
|
simple: isPhone,
|
||||||
defaultPageSize: 20,
|
total,
|
||||||
showTotal: (total: number, range: number[]) =>
|
showTotal: (total: number, range: number[]) =>
|
||||||
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
||||||
pageSizeOptions: [20, 100, 500, 1000] as any,
|
pageSizeOptions: [10, 20, 50, 100, 200, 500, total || 10000].sort(
|
||||||
|
(a, b) => a - b,
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
onRow={(record) => {
|
onRow={(record) => {
|
||||||
return {
|
return {
|
||||||
@@ -931,7 +946,134 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
rowClassName={getRowClassName}
|
rowClassName={getRowClassName}
|
||||||
|
onChange={onPageChange}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewAction = (key: string) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'new':
|
||||||
|
setIsCreateViewModalVisible(true);
|
||||||
|
break;
|
||||||
|
case 'manage':
|
||||||
|
setIsViewManageModalVisible(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
tabClick(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const menu = (
|
||||||
|
<Menu
|
||||||
|
onClick={({ key, domEvent }) => {
|
||||||
|
domEvent.stopPropagation();
|
||||||
|
viewAction(key);
|
||||||
|
}}
|
||||||
|
items={[
|
||||||
|
...[...enabledCronViews].slice(2).map((x) => ({
|
||||||
|
label: (
|
||||||
|
<Space style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<span>{x.name}</span>
|
||||||
|
{viewConf?.id === x.id && (
|
||||||
|
<CheckOutlined style={{ color: '#1890ff' }} />
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
key: x.id,
|
||||||
|
icon: <UnorderedListOutlined />,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '新建视图',
|
||||||
|
key: 'new',
|
||||||
|
icon: <PlusOutlined />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '视图管理',
|
||||||
|
key: 'manage',
|
||||||
|
icon: <SettingOutlined />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const getCronViews = () => {
|
||||||
|
setLoading(true);
|
||||||
|
request
|
||||||
|
.get(`${config.apiPrefix}crons/views`)
|
||||||
|
.then((data: any) => {
|
||||||
|
setCronViews(data.data);
|
||||||
|
setEnabledCronViews(data.data.filter((x) => !x.isDisabled));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabClick = (key: string) => {
|
||||||
|
const view = enabledCronViews.find((x) => x.id == key);
|
||||||
|
setViewConf(view ? view : null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
className="ql-container-wrapper crontab-wrapper ql-container-wrapper-has-tab"
|
||||||
|
title="定时任务"
|
||||||
|
extra={[
|
||||||
|
<Search
|
||||||
|
placeholder="请输入名称或者关键词"
|
||||||
|
style={{ width: 'auto' }}
|
||||||
|
enterButton
|
||||||
|
allowClear
|
||||||
|
loading={loading}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
onSearch={onSearch}
|
||||||
|
/>,
|
||||||
|
<Button key="2" type="primary" onClick={() => addCron()}>
|
||||||
|
新建任务
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
header={{
|
||||||
|
style: headerStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="all"
|
||||||
|
size="small"
|
||||||
|
tabPosition="top"
|
||||||
|
className={`crontab-view ${moreMenuActive ? 'more-active' : ''}`}
|
||||||
|
tabBarExtraContent={
|
||||||
|
<Dropdown
|
||||||
|
overlay={menu}
|
||||||
|
trigger={['click']}
|
||||||
|
overlayStyle={{ minWidth: 200 }}
|
||||||
|
>
|
||||||
|
<div className={`view-more ${moreMenuActive ? 'active' : ''}`}>
|
||||||
|
<Space>
|
||||||
|
更多
|
||||||
|
<DownOutlined />
|
||||||
|
</Space>
|
||||||
|
<div className="ant-tabs-ink-bar ant-tabs-ink-bar-animated"></div>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
onTabClick={tabClick}
|
||||||
|
>
|
||||||
|
<Tabs.TabPane tab="全部任务" key="all">
|
||||||
|
{panelContent}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
{[...enabledCronViews].slice(0, 2).map((x) => (
|
||||||
|
<Tabs.TabPane tab={x.name} key={x.id}>
|
||||||
|
{panelContent}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
<CronLogModal
|
<CronLogModal
|
||||||
visible={isLogModalVisible}
|
visible={isLogModalVisible}
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
@@ -964,6 +1106,29 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
theme={theme}
|
theme={theme}
|
||||||
isPhone={isPhone}
|
isPhone={isPhone}
|
||||||
/>
|
/>
|
||||||
|
<ViewCreateModal
|
||||||
|
visible={isCreateViewModalVisible}
|
||||||
|
handleCancel={(data) => {
|
||||||
|
setIsCreateViewModalVisible(false);
|
||||||
|
getCronViews();
|
||||||
|
if (data && data.id === viewConf.id) {
|
||||||
|
setViewConf({ ...viewConf, ...data });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ViewManageModal
|
||||||
|
cronViews={cronViews}
|
||||||
|
visible={isViewManageModalVisible}
|
||||||
|
handleCancel={() => {
|
||||||
|
setIsViewManageModalVisible(false);
|
||||||
|
}}
|
||||||
|
cronViewChange={(data) => {
|
||||||
|
getCronViews();
|
||||||
|
if (data && data.id === viewConf.id) {
|
||||||
|
setViewConf({ ...viewConf, ...data });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
message,
|
||||||
|
Input,
|
||||||
|
Form,
|
||||||
|
Statistic,
|
||||||
|
Button,
|
||||||
|
Space,
|
||||||
|
Select,
|
||||||
|
} from 'antd';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const PROPERTIES = [
|
||||||
|
{ name: '命令', value: 'command' },
|
||||||
|
{ name: '名称', value: 'name' },
|
||||||
|
{ name: '定时规则', value: 'schedule' },
|
||||||
|
{ name: '状态', value: 'status' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const OPERATIONS = [
|
||||||
|
{ name: '包含', value: 'Reg' },
|
||||||
|
{ name: '不包含', value: 'NotReg' },
|
||||||
|
{ name: '属于', value: 'In' },
|
||||||
|
{ name: '不属于', value: 'Nin' },
|
||||||
|
// { name: '等于', value: 'Eq' },
|
||||||
|
// { name: '不等于', value: 'Ne' },
|
||||||
|
// { name: '为空', value: 'IsNull' },
|
||||||
|
// { name: '不为空', value: 'NotNull' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const SORTTYPES = [
|
||||||
|
{ name: '顺序', value: 'ASC' },
|
||||||
|
{ name: '倒序', value: 'DESC' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const STATUS = [
|
||||||
|
{ name: '运行中', value: 0 },
|
||||||
|
{ name: '空闲中', value: 1 },
|
||||||
|
{ name: '已禁用', value: 2 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ViewCreateModal = ({
|
||||||
|
view,
|
||||||
|
handleCancel,
|
||||||
|
visible,
|
||||||
|
}: {
|
||||||
|
view?: any;
|
||||||
|
visible: boolean;
|
||||||
|
handleCancel: (param?: any) => void;
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [operationMap, setOperationMap] = useState<any>();
|
||||||
|
|
||||||
|
const handleOk = async (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
|
const method = view ? 'put' : 'post';
|
||||||
|
try {
|
||||||
|
const { code, data } = await request[method](
|
||||||
|
`${config.apiPrefix}crons/views`,
|
||||||
|
{
|
||||||
|
data: view ? { ...values, id: view.id } : values,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (code !== 200) {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
handleCancel(data);
|
||||||
|
} catch (error: any) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!view) {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
form.setFieldsValue(
|
||||||
|
view || {
|
||||||
|
filters: [{ property: 'command', operation: 'Reg' }],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, [view, visible]);
|
||||||
|
|
||||||
|
const operationElement = (
|
||||||
|
<Select
|
||||||
|
style={{ width: 100 }}
|
||||||
|
onChange={() => {
|
||||||
|
setOperationMap({});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{OPERATIONS.map((x) => (
|
||||||
|
<Select.Option key={x.name} value={x.value}>
|
||||||
|
{x.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
|
||||||
|
const propertyElement = (props: any) => {
|
||||||
|
return (
|
||||||
|
<Select style={{ width: 120 }}>
|
||||||
|
{props.map((x) => (
|
||||||
|
<Select.Option key={x.name} value={x.value}>
|
||||||
|
{x.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeElement = (
|
||||||
|
<Select style={{ width: 120 }}>
|
||||||
|
{SORTTYPES.map((x) => (
|
||||||
|
<Select.Option key={x.name} value={x.value}>
|
||||||
|
{x.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
|
||||||
|
const statusElement = (
|
||||||
|
<Select mode="multiple" allowClear placeholder="请选择状态">
|
||||||
|
{STATUS.map((x) => (
|
||||||
|
<Select.Option key={x.name} value={x.value}>
|
||||||
|
{x.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={view ? '编辑视图' : '新建视图'}
|
||||||
|
visible={visible}
|
||||||
|
forceRender
|
||||||
|
width={580}
|
||||||
|
centered
|
||||||
|
maskClosable={false}
|
||||||
|
onOk={() => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then((values) => {
|
||||||
|
handleOk(values);
|
||||||
|
})
|
||||||
|
.catch((info) => {
|
||||||
|
console.log('Validate Failed:', info);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancel={() => handleCancel()}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical" name="env_modal">
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="视图名称"
|
||||||
|
rules={[{ required: true, message: '请输入视图名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入视图名称" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.List name="filters">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }, index) => (
|
||||||
|
<Form.Item
|
||||||
|
label={index === 0 ? '筛选条件' : ''}
|
||||||
|
key={key}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
<Space className="view-create-modal-filters" align="baseline">
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'property']}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
{propertyElement(PROPERTIES)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'operation']}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
{operationElement}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'value']}
|
||||||
|
rules={[{ required: true, message: '请输入内容' }]}
|
||||||
|
>
|
||||||
|
{['In', 'Nin'].includes(
|
||||||
|
form.getFieldValue(['filters', index, 'operation']),
|
||||||
|
) ? (
|
||||||
|
statusElement
|
||||||
|
) : (
|
||||||
|
<Input placeholder="请输入内容" />
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
{index !== 0 && (
|
||||||
|
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<a
|
||||||
|
onClick={() => add({ property: 'command', operation: 'Reg' })}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
新增筛选条件
|
||||||
|
</a>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
<Form.List name="sorts">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }, index) => (
|
||||||
|
<Form.Item
|
||||||
|
label={index === 0 ? '排序方式' : ''}
|
||||||
|
key={key}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
<Space className="view-create-modal-filters" align="baseline">
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'property']}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
{propertyElement(PROPERTIES)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'type']}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
{typeElement}
|
||||||
|
</Form.Item>
|
||||||
|
{index !== 0 && (
|
||||||
|
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<a onClick={() => add({ property: 'command', type: 'ASC' })}>
|
||||||
|
<PlusOutlined />
|
||||||
|
新增排序方式
|
||||||
|
</a>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewCreateModal;
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
message,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
Switch,
|
||||||
|
} from 'antd';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||||
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
import ViewCreateModal from './viewCreateModal';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const type = 'DragableBodyRow';
|
||||||
|
|
||||||
|
const DragableBodyRow = ({
|
||||||
|
index,
|
||||||
|
moveRow,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
...restProps
|
||||||
|
}: any) => {
|
||||||
|
const ref = useRef();
|
||||||
|
const [{ isOver, dropClassName }, drop] = useDrop({
|
||||||
|
accept: type,
|
||||||
|
collect: (monitor) => {
|
||||||
|
const { index: dragIndex } = (monitor.getItem() as any) || {};
|
||||||
|
if (dragIndex === index) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isOver: monitor.isOver(),
|
||||||
|
dropClassName:
|
||||||
|
dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
drop: (item: any) => {
|
||||||
|
moveRow(item.index, index);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [, drag] = useDrag({
|
||||||
|
type,
|
||||||
|
item: { index },
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
drop(drag(ref));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
ref={ref}
|
||||||
|
className={`${className}${isOver ? dropClassName : ''}`}
|
||||||
|
style={{ cursor: 'move', ...style }}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ViewManageModal = ({
|
||||||
|
cronViews,
|
||||||
|
handleCancel,
|
||||||
|
visible,
|
||||||
|
cronViewChange,
|
||||||
|
}: {
|
||||||
|
cronViews: any[];
|
||||||
|
visible: boolean;
|
||||||
|
handleCancel: () => void;
|
||||||
|
cronViewChange: (data?: any) => void;
|
||||||
|
}) => {
|
||||||
|
const columns: any = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
align: 'center' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '显示',
|
||||||
|
key: 'isDisabled',
|
||||||
|
dataIndex: 'isDisabled',
|
||||||
|
align: 'center' as const,
|
||||||
|
width: 100,
|
||||||
|
render: (text: string, record: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={!record.isDisabled}
|
||||||
|
onChange={(checked) => onShowChange(checked, record, index)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 140,
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (text: string, record: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<Space size="middle">
|
||||||
|
<a onClick={() => editView(record, index)}>
|
||||||
|
<EditOutlined />
|
||||||
|
</a>
|
||||||
|
<a onClick={() => deleteView(record, index)}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</a>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [list, setList] = useState<any[]>([]);
|
||||||
|
const [isCreateViewModalVisible, setIsCreateViewModalVisible] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
const [editedView, setEditedView] = useState<any>(null);
|
||||||
|
|
||||||
|
const editView = (record: any, index: number) => {
|
||||||
|
setEditedView(record);
|
||||||
|
setIsCreateViewModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteView = (record: any, index: number) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认删除视图{' '}
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{record.name}
|
||||||
|
</Text>{' '}
|
||||||
|
吗
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.delete(`${config.apiPrefix}crons/views`, { data: [record.id] })
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
message.success('删除成功');
|
||||||
|
cronViewChange();
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onShowChange = (checked: boolean, record: any, index: number) => {
|
||||||
|
console.log(checked);
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
|
||||||
|
data: [record.id],
|
||||||
|
})
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
const _list = [...list];
|
||||||
|
_list.splice(index, 1, { ...list[index], isDisabled: !checked });
|
||||||
|
setList(_list);
|
||||||
|
cronViewChange();
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
body: {
|
||||||
|
row: DragableBodyRow,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveRow = useCallback(
|
||||||
|
(dragIndex, hoverIndex) => {
|
||||||
|
if (dragIndex === hoverIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dragRow = list[dragIndex];
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}crons/views/move`, {
|
||||||
|
data: { fromIndex: dragIndex, toIndex: hoverIndex, id: dragRow.id },
|
||||||
|
})
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
const newData = [...list];
|
||||||
|
newData.splice(dragIndex, 1);
|
||||||
|
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
||||||
|
setList(newData);
|
||||||
|
cronViewChange();
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[list],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setList(cronViews);
|
||||||
|
}, [cronViews]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="视图管理"
|
||||||
|
visible={visible}
|
||||||
|
centered
|
||||||
|
width={620}
|
||||||
|
onCancel={() => handleCancel()}
|
||||||
|
className="view-manage-modal"
|
||||||
|
forceRender
|
||||||
|
footer={false}
|
||||||
|
maskClosable={false}
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
key="2"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => setIsCreateViewModalVisible(true)}
|
||||||
|
>
|
||||||
|
新建视图
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
<Table
|
||||||
|
bordered
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={list}
|
||||||
|
rowKey="id"
|
||||||
|
size="middle"
|
||||||
|
style={{ marginBottom: 20 }}
|
||||||
|
components={components}
|
||||||
|
onRow={(record: any, index: number) => {
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
moveRow,
|
||||||
|
} as any;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DndProvider>
|
||||||
|
<ViewCreateModal
|
||||||
|
view={editedView}
|
||||||
|
visible={isCreateViewModalVisible}
|
||||||
|
handleCancel={(data) => {
|
||||||
|
cronViewChange(data);
|
||||||
|
setIsCreateViewModalVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewManageModal;
|
||||||
@@ -444,7 +444,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper dependence-wrapper"
|
className="ql-container-wrapper dependence-wrapper ql-container-wrapper-has-tab"
|
||||||
title="依赖管理"
|
title="依赖管理"
|
||||||
extra={[
|
extra={[
|
||||||
<Search
|
<Search
|
||||||
|
|||||||
Vendored
+15
-1
@@ -25,7 +25,7 @@ import EditNameModal from './editNameModal';
|
|||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { exportJson, getTableScroll } from '@/utils/index';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -462,6 +462,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportEnvs = () => {
|
||||||
|
const envs = value
|
||||||
|
.filter((x) => selectedRowIds.includes(x.id))
|
||||||
|
.map((x) => ({ value: x.value, name: x.name, remarks: x.remarks }));
|
||||||
|
exportJson('env.json', JSON.stringify(envs));
|
||||||
|
};
|
||||||
|
|
||||||
const modifyName = () => {
|
const modifyName = () => {
|
||||||
setIsEditNameModalVisible(true);
|
setIsEditNameModalVisible(true);
|
||||||
};
|
};
|
||||||
@@ -516,6 +523,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
>
|
>
|
||||||
批量删除
|
批量删除
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => exportEnvs()}
|
||||||
|
style={{ marginLeft: 8, marginRight: 8 }}
|
||||||
|
>
|
||||||
|
批量导出
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => operateEnvs(0)}
|
onClick={() => operateEnvs(0)}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const getLog = (node: any) => {
|
const getLog = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}logs/${node.value}?path=${node.parent || ''}`)
|
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setValue(data.data);
|
setValue(data.data);
|
||||||
});
|
});
|
||||||
@@ -113,6 +113,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
placeholder="请选择日志"
|
placeholder="请选择日志"
|
||||||
|
fieldNames={{ value: 'key', label: 'title' }}
|
||||||
showSearch
|
showSearch
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ const Login = ({ reloadUser }: any) => {
|
|||||||
message: '验证码为6位数字',
|
message: '验证码为6位数字',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
validateTrigger="onBlur"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="6位数字"
|
placeholder="6位数字"
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const EditModal = ({
|
|||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
|
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setValue(data.data);
|
setValue(data.data);
|
||||||
});
|
});
|
||||||
@@ -74,11 +74,13 @@ const EditModal = ({
|
|||||||
|
|
||||||
const run = () => {
|
const run = () => {
|
||||||
setLog('');
|
setLog('');
|
||||||
|
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}scripts/run`, {
|
.put(`${config.apiPrefix}scripts/run`, {
|
||||||
data: {
|
data: {
|
||||||
filename: cNode.value,
|
filename: cNode.title,
|
||||||
path: cNode.parent || '',
|
path: cNode.parent || '',
|
||||||
|
content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -87,14 +89,16 @@ const EditModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
if (!cNode || !cNode.value) {
|
if (!cNode || !cNode.title) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}scripts/stop`, {
|
.put(`${config.apiPrefix}scripts/stop`, {
|
||||||
data: {
|
data: {
|
||||||
filename: cNode.value,
|
filename: cNode.title,
|
||||||
path: cNode.parent || '',
|
path: cNode.parent || '',
|
||||||
|
content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -113,8 +117,7 @@ const EditModal = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_message === '执行结束') {
|
if (_message.includes('执行结束')) {
|
||||||
_message = '';
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsRunning(false);
|
setIsRunning(false);
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -146,6 +149,7 @@ const EditModal = ({
|
|||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={treeData}
|
treeData={treeData}
|
||||||
placeholder="请选择脚本文件"
|
placeholder="请选择脚本文件"
|
||||||
|
fieldNames={{ value: 'key', label: 'title' }}
|
||||||
showSearch
|
showSearch
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>
|
/>
|
||||||
@@ -243,7 +247,7 @@ const EditModal = ({
|
|||||||
content:
|
content:
|
||||||
editorRef.current &&
|
editorRef.current &&
|
||||||
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
||||||
filename: cNode?.value,
|
filename: cNode?.title,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SettingModal
|
<SettingModal
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Modal, message, Input, Form, Select } from 'antd';
|
import {
|
||||||
|
Modal,
|
||||||
|
message,
|
||||||
|
Input,
|
||||||
|
Form,
|
||||||
|
Select,
|
||||||
|
Upload,
|
||||||
|
Radio,
|
||||||
|
TreeSelect,
|
||||||
|
} from 'antd';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@@ -21,21 +31,30 @@ const EditScriptNameModal = ({
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [dirs, setDirs] = useState<any[]>([]);
|
const [dirs, setDirs] = useState<any[]>([]);
|
||||||
|
const [file, setFile] = useState<File>();
|
||||||
|
const [type, setType] = useState<'blank' | 'upload'>('blank');
|
||||||
|
|
||||||
const handleOk = async (values: any) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
values.path = values.path || '';
|
values.path = values.path || '';
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file as any);
|
||||||
|
formData.append('filename', values.filename);
|
||||||
|
formData.append('path', values.path);
|
||||||
|
formData.append('content', '');
|
||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}scripts`, {
|
.post(`${config.apiPrefix}scripts`, {
|
||||||
data: { filename: values.filename, path: values.path, content: '' },
|
data: formData,
|
||||||
})
|
})
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success('保存文件成功');
|
message.success('新建文件成功');
|
||||||
|
const key = values.path ? `${values.path}/` : '';
|
||||||
|
const filename = file ? file.name : values.filename;
|
||||||
handleCancel({
|
handleCancel({
|
||||||
filename: values.filename,
|
filename,
|
||||||
path: values.path,
|
path: values.path,
|
||||||
key: `${values.path}-${values.filename}`,
|
key: `${key}${filename}`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
@@ -45,15 +64,42 @@ const EditScriptNameModal = ({
|
|||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
setFile(file);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeChange = (e) => {
|
||||||
|
setType(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDirs = (data) => {
|
||||||
|
for (const item of data) {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
item.children = item.children
|
||||||
|
.filter((x) => x.type === 'directory')
|
||||||
|
.map((x) => ({ ...x, disabled: false }));
|
||||||
|
getDirs(item.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const originDirs = treeData
|
||||||
|
.filter((x) => x.type === 'directory')
|
||||||
|
.map((x) => ({ ...x, disabled: false }));
|
||||||
|
const dirs = getDirs(originDirs);
|
||||||
|
setDirs(dirs);
|
||||||
|
}, [treeData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
const originDirs = treeData.filter((x) => x.disabled);
|
|
||||||
setDirs([{ key: '' }, ...originDirs]);
|
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="新建文件"
|
title="新建脚本"
|
||||||
visible={visible}
|
visible={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
@@ -73,23 +119,44 @@ const EditScriptNameModal = ({
|
|||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" name="edit_name_modal">
|
<Form form={form} layout="vertical" name="edit_name_modal">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="filename"
|
name="type"
|
||||||
label="文件名"
|
label="类型"
|
||||||
rules={[{ required: true, message: '请输入文件名' }]}
|
rules={[{ required: true }]}
|
||||||
|
initialValue={'blank'}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入文件名" />
|
<Radio.Group onChange={typeChange}>
|
||||||
|
<Radio value="blank">空文件</Radio>
|
||||||
|
<Radio value="upload">本地上传</Radio>
|
||||||
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
{type === 'blank' && (
|
||||||
label="父目录"
|
<Form.Item
|
||||||
name="path"
|
name="filename"
|
||||||
initialValue={dirs && dirs.length > 0 ? dirs[0].key : ''}
|
label="文件名"
|
||||||
>
|
rules={[{ required: true, message: '请输入文件名' }]}
|
||||||
<Select placeholder="请选择父目录">
|
>
|
||||||
{dirs.map((x) => (
|
<Input placeholder="请输入文件名" />
|
||||||
<Option value={x.key}>{x.key || '根'}</Option>
|
</Form.Item>
|
||||||
))}
|
)}
|
||||||
</Select>
|
<Form.Item label="父目录" name="path">
|
||||||
|
<TreeSelect
|
||||||
|
allowClear
|
||||||
|
treeData={dirs}
|
||||||
|
fieldNames={{ value: 'key', label: 'title' }}
|
||||||
|
placeholder="请选择父目录"
|
||||||
|
treeDefaultExpandAll
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
{type === 'upload' && (
|
||||||
|
<Form.Item label="文件" name="file">
|
||||||
|
<Upload.Dragger beforeUpload={beforeUpload} maxCount={1}>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<UploadOutlined />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">点击或者拖拽文件到此区域上传</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
|
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setValue(data.data);
|
setValue(data.data);
|
||||||
});
|
});
|
||||||
@@ -117,7 +117,6 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
const obj = {
|
const obj = {
|
||||||
node: {
|
node: {
|
||||||
title: s,
|
title: s,
|
||||||
value: s,
|
|
||||||
key: p ? vkey : s,
|
key: p ? vkey : s,
|
||||||
parent: p,
|
parent: p,
|
||||||
},
|
},
|
||||||
@@ -209,7 +208,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
<>
|
<>
|
||||||
确认保存文件
|
确认保存文件
|
||||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
{currentNode.value}
|
{currentNode.title}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
,保存后不可恢复
|
,保存后不可恢复
|
||||||
</>
|
</>
|
||||||
@@ -222,7 +221,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}scripts`, {
|
.put(`${config.apiPrefix}scripts`, {
|
||||||
data: {
|
data: {
|
||||||
filename: currentNode.value,
|
filename: currentNode.title,
|
||||||
path: currentNode.parent || '',
|
path: currentNode.parent || '',
|
||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
@@ -262,7 +261,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}scripts`, {
|
.delete(`${config.apiPrefix}scripts`, {
|
||||||
data: {
|
data: {
|
||||||
filename: currentNode.value,
|
filename: currentNode.title,
|
||||||
path: currentNode.parent || '',
|
path: currentNode.parent || '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -315,8 +314,9 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
) => {
|
) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const newData = [...data];
|
const newData = [...data];
|
||||||
const _file = { title: filename, key, value: filename, parent: path };
|
const _file = { title: filename, key, parent: path };
|
||||||
if (path) {
|
if (path) {
|
||||||
|
// TODO: 更新左侧树数据
|
||||||
const parentNodeIndex = newData.findIndex((x) => x.key === path);
|
const parentNodeIndex = newData.findIndex((x) => x.key === path);
|
||||||
if (parentNodeIndex !== -1) {
|
if (parentNodeIndex !== -1) {
|
||||||
const parentNode = newData[parentNodeIndex];
|
const parentNode = newData[parentNodeIndex];
|
||||||
@@ -331,7 +331,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
newData.unshift(_file);
|
newData.unshift(_file);
|
||||||
}
|
}
|
||||||
setData(newData);
|
setData(newData);
|
||||||
onSelect(_file.value, _file);
|
onSelect(_file.title, _file);
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
}
|
}
|
||||||
setIsAddFileModalVisible(false);
|
setIsAddFileModalVisible(false);
|
||||||
@@ -341,7 +341,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}scripts/download`, {
|
.post(`${config.apiPrefix}scripts/download`, {
|
||||||
data: {
|
data: {
|
||||||
filename: currentNode.value,
|
filename: currentNode.title,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then((_data: any) => {
|
||||||
@@ -349,7 +349,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = currentNode.value;
|
a.download = currentNode.title;
|
||||||
document.documentElement.appendChild(a);
|
document.documentElement.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.documentElement.removeChild(a);
|
document.documentElement.removeChild(a);
|
||||||
@@ -451,6 +451,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
placeholder="请选择脚本"
|
placeholder="请选择脚本"
|
||||||
|
fieldNames={{ value: 'key', label: 'title' }}
|
||||||
showSearch
|
showSearch
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ const Setting = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper"
|
className="ql-container-wrapper ql-container-wrapper-has-tab"
|
||||||
title="系统设置"
|
title="系统设置"
|
||||||
header={{
|
header={{
|
||||||
style: headerStyle,
|
style: headerStyle,
|
||||||
|
|||||||
@@ -107,12 +107,18 @@ const SubscriptionModal = ({
|
|||||||
const [intervalNumber, setIntervalNumber] = useState<number>();
|
const [intervalNumber, setIntervalNumber] = useState<number>();
|
||||||
const intervalTypeChange = (type: string) => {
|
const intervalTypeChange = (type: string) => {
|
||||||
setIntervalType(type);
|
setIntervalType(type);
|
||||||
onChange?.({ type, value: intervalNumber });
|
if (intervalNumber && intervalNumber > 0) {
|
||||||
|
onChange?.({ type, value: intervalNumber });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const numberChange = (value: number) => {
|
const numberChange = (value: number) => {
|
||||||
setIntervalNumber(value);
|
setIntervalNumber(value);
|
||||||
onChange?.({ type: intervalType, value });
|
if (!value) {
|
||||||
|
onChange?.(null);
|
||||||
|
} else {
|
||||||
|
onChange?.({ type: intervalType, value });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -198,13 +204,15 @@ const SubscriptionModal = ({
|
|||||||
] = text
|
] = text
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map((x) => x.trim().replace(/\"/g, '').replace(/\'/, ''));
|
.map((x) => x.trim().replace(/\"/g, '').replace(/\'/, ''));
|
||||||
|
const _type =
|
||||||
|
type === 'raw'
|
||||||
|
? 'file'
|
||||||
|
: url.startsWith('http')
|
||||||
|
? 'public-repo'
|
||||||
|
: 'private-repo';
|
||||||
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
type:
|
type: _type,
|
||||||
type === 'raw'
|
|
||||||
? 'file'
|
|
||||||
: url.startsWith('http')
|
|
||||||
? 'public-repo'
|
|
||||||
: 'private-repo',
|
|
||||||
url,
|
url,
|
||||||
whitelist,
|
whitelist,
|
||||||
blacklist,
|
blacklist,
|
||||||
@@ -213,6 +221,7 @@ const SubscriptionModal = ({
|
|||||||
extensions,
|
extensions,
|
||||||
alias: formatAlias(url, branch),
|
alias: formatAlias(url, branch),
|
||||||
});
|
});
|
||||||
|
setType(_type);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
+38
-2
@@ -181,8 +181,8 @@ export function getTableScroll({
|
|||||||
id,
|
id,
|
||||||
}: { extraHeight?: number; id?: string } = {}) {
|
}: { extraHeight?: number; id?: string } = {}) {
|
||||||
if (typeof extraHeight == 'undefined') {
|
if (typeof extraHeight == 'undefined') {
|
||||||
// 47 + 40 + 10 + 24
|
// 47 + 40 + 12
|
||||||
extraHeight = 121;
|
extraHeight = 99;
|
||||||
}
|
}
|
||||||
let tHeader = null;
|
let tHeader = null;
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -205,3 +205,39 @@ export function getTableScroll({
|
|||||||
let height = document.body.clientHeight - mainTop - extraHeight;
|
let height = document.body.clientHeight - mainTop - extraHeight;
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自动触发点击事件
|
||||||
|
function automaticClick(elment: HTMLElement) {
|
||||||
|
const ev = document.createEvent('MouseEvents');
|
||||||
|
ev.initMouseEvent(
|
||||||
|
'click',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
window,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
elment.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出文件
|
||||||
|
export function exportJson(name: string, data: string) {
|
||||||
|
const urlObject = window.URL || window.webkitURL || window;
|
||||||
|
const export_blob = new Blob([data]);
|
||||||
|
const createA = document.createElementNS(
|
||||||
|
'http://www.w3.org/1999/xhtml',
|
||||||
|
'a',
|
||||||
|
) as any;
|
||||||
|
createA.href = urlObject.createObjectURL(export_blob);
|
||||||
|
createA.download = name;
|
||||||
|
automaticClick(createA);
|
||||||
|
}
|
||||||
|
|||||||
+8
-2
@@ -7,9 +7,15 @@ export function init() {
|
|||||||
// sentry监控 init
|
// sentry监控 init
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
||||||
integrations: [new Integrations.BrowserTracing()],
|
integrations: [
|
||||||
|
new Integrations.BrowserTracing({
|
||||||
|
shouldCreateSpanForRequest(url) {
|
||||||
|
return !url.includes('/api/ws') && !url.includes('/api/static');
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
release: version,
|
release: version,
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 0.1,
|
||||||
beforeBreadcrumb(breadcrumb, hint?) {
|
beforeBreadcrumb(breadcrumb, hint?) {
|
||||||
if (breadcrumb.data && breadcrumb.data.url) {
|
if (breadcrumb.data && breadcrumb.data.url) {
|
||||||
const url = breadcrumb.data.url.replace(/token=.*/, '');
|
const url = breadcrumb.data.url.replace(/token=.*/, '');
|
||||||
|
|||||||
+7
-7
@@ -1,8 +1,8 @@
|
|||||||
export const version = '2.13.1';
|
export const version = '2.14.0';
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/303';
|
export const changeLogLink = 'https://t.me/jiao_long/325';
|
||||||
export const changeLog = `2.13.1 版本说明
|
export const changeLog = `2.14.0 版本说明
|
||||||
1. 修复新建订阅repo命令快捷导入
|
1. 定时任务自定义视图功能来啦,支持自定义筛选条件、排序规则等
|
||||||
2. 修复执行订阅任务含有before/after命令时,造成面板无法访问
|
2. 配置文件可设置需要延迟的脚本后缀,默认只有js脚本随机延迟执行,感谢 https://github.com/cddjr
|
||||||
3. 修复任务详情日志列表访问
|
3. 修复定时任务搜索及搜索分页
|
||||||
4. 修复安装依赖造成CPU满负荷,服务器崩溃
|
4. 其他bug修复
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user