mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-10 03:16:48 +08:00
Compare commits
641 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 325482cbb1 | |||
| 45e02a55c3 | |||
| 6b8ef75b51 | |||
| f2535ece76 | |||
| b2afa65cb1 | |||
| 3a62cc37ff | |||
| 93c69826ad | |||
| 70727d0e26 | |||
| 2578aae0ba | |||
| 616e56ea95 | |||
| 96ae20de68 | |||
| 6975ff4aa1 | |||
| 1d8a1e9c4a | |||
| 2719280cdc | |||
| 575e41172f | |||
| bd50b1e976 | |||
| dfb2572b4f | |||
| 7746b18023 | |||
| 05c738c7c6 | |||
| e561e12356 | |||
| 595b28ba62 | |||
| 366caed74e | |||
| 2e26a8c24e | |||
| c09c375066 | |||
| 4f93905507 | |||
| f152136f0b | |||
| c24d3d6e2f | |||
| 33d820acdb | |||
| 78ef75bbfe | |||
| 31541fb87e | |||
| 0c169e3f6c | |||
| 73c15cb3bc | |||
| 4373a2df49 | |||
| cb004cfbf5 | |||
| 7f2d6d159c | |||
| 4bba024e4a | |||
| acb43f3ad8 | |||
| b160f914ad | |||
| 6116aa6d12 | |||
| d43cee53c9 | |||
| defea8ff9b | |||
| b290b60d46 | |||
| 706b5e8108 | |||
| b55c7d5747 | |||
| 6b8fd94f0c | |||
| e18710a30a | |||
| c0f0da4e96 | |||
| 49ef5cd61f | |||
| 99d7eaeff0 | |||
| c8f5eb7c2e | |||
| 109e16e8fb | |||
| 555f6e04e8 | |||
| bcde0a54bf | |||
| eee7f25bc3 | |||
| b10157b09a | |||
| a09a21fa5d | |||
| ec2918b427 | |||
| 57bfc19ecf | |||
| aa110d4187 | |||
| 9e8febfc11 | |||
| 5905c2ce25 | |||
| b03ef0d494 | |||
| 585f77ed49 | |||
| 1c580ea2f9 | |||
| b1af6575b9 | |||
| b24c5fdee1 | |||
| a256ada847 | |||
| a8cdab629d | |||
| 09cca0a920 | |||
| 6a0aa6aae2 | |||
| 565cc06c27 | |||
| 29caa4b770 | |||
| 953cf4795a | |||
| 31bc8395aa | |||
| eb94b39e40 | |||
| c16bbe0f9a | |||
| b60caa60d5 | |||
| 1ea97e9206 | |||
| 32970bca71 | |||
| 1fc9f87eaa | |||
| b2f5960097 | |||
| 1cfcc66548 | |||
| 60152a313f | |||
| d50ff20ff8 | |||
| 3aed5ef955 | |||
| 61e0e8dbe2 | |||
| 1ba5d52ee7 | |||
| 03bc5bb882 | |||
| 8011b4b793 | |||
| deb124ae23 | |||
| a3114e40d1 | |||
| 0b9ecfc3a5 | |||
| c32bb4157b | |||
| 7f9c9f05b8 | |||
| 906a074634 | |||
| 286ec0e714 | |||
| 0f81414497 | |||
| b451831db2 | |||
| ae13185319 | |||
| f2bcbaefd2 | |||
| 96ddcf49c5 | |||
| 322a4e2729 | |||
| 8e48960253 | |||
| 3b419a8e96 | |||
| bf1685bf79 | |||
| 3ab84428f0 | |||
| fdda732c4c | |||
| f46b30498b | |||
| d7ee2576f2 | |||
| d2c03bc298 | |||
| 6283e23d29 | |||
| dc9923ac13 | |||
| 62ac80257a | |||
| 868452f13c | |||
| 6c368f7ea7 | |||
| 732d63d588 | |||
| ae40947801 | |||
| 079d4dfa06 | |||
| 67c50bf622 | |||
| f1e494b1fc | |||
| f4f275490e | |||
| baad96106f | |||
| 15fa7754b5 | |||
| 8f175f8447 | |||
| 9c45cfd7ed | |||
| f2426c3f66 | |||
| 36ae6e460d | |||
| 7ae93efe07 | |||
| 8b97d92d0a | |||
| e5c06335ec | |||
| 2bd8e49302 | |||
| c0d401e296 | |||
| e07d2b6639 | |||
| 220a87627d | |||
| 87bca2ac4e | |||
| c320906149 | |||
| 27958a1a90 | |||
| 82c7011522 | |||
| fa6d57d809 | |||
| 1060fc7476 | |||
| 56bc38e9e7 | |||
| 18be65d6fe | |||
| 3409085195 | |||
| 17e17ac077 | |||
| c655e1da38 | |||
| 0ea158724f | |||
| fb521498ff | |||
| 399550ccad | |||
| c7c30c86f2 | |||
| 5f6c0882d6 | |||
| 6e05a4f3a5 | |||
| ea8aa6a231 | |||
| ae2570c677 | |||
| c76d952d41 | |||
| cdf9fb24be | |||
| 2131e16781 | |||
| b0c8425639 | |||
| f0869567de | |||
| be2a19949b | |||
| 8627cccb58 | |||
| 4a8eaebf17 | |||
| e7edd0a5a4 | |||
| 9ccbea0a04 | |||
| ab715ef840 | |||
| 117822fe7c | |||
| da922678a1 | |||
| 133e9e0e1f | |||
| 7cc6081d15 | |||
| 5a24f62a62 | |||
| be597355a7 | |||
| 51afcb6c5b | |||
| 92ebb70eaa | |||
| ab19f30342 | |||
| fc9bc7bea2 | |||
| 793da29f22 | |||
| 5afe4da607 | |||
| a7a56c03ce | |||
| 7639b588d8 | |||
| 1d536cdd53 | |||
| 9f08245b6d | |||
| ff9bf20e57 | |||
| b201730100 | |||
| 6ca098f9e2 | |||
| e0eef05f5c | |||
| 6398e5ec70 | |||
| 6f93219c44 | |||
| 9b957ac019 | |||
| e0d1b127aa | |||
| 64072b3435 | |||
| f81f7720f0 | |||
| 69fab610e6 | |||
| d693e1f9ea | |||
| 2a9ca67355 | |||
| 3fb01860d4 | |||
| d92a46d09c | |||
| ec605bfba4 | |||
| d2f999a5e2 | |||
| 627f541f17 | |||
| c457dca555 | |||
| ff3ec7395e | |||
| fdaba146ca | |||
| 31a6d30c23 | |||
| fa3b185fd5 | |||
| 101d4fb497 | |||
| ccd308f5b0 | |||
| 999d9f8e01 | |||
| 313db0d74e | |||
| 634e9705a7 | |||
| c959cec738 | |||
| 95330e04a3 | |||
| a099d09af7 | |||
| a0d0197ad7 | |||
| d49e711a34 | |||
| e3b8cd75b9 | |||
| 7a240eaabb | |||
| 01564e96d3 | |||
| 03f4b5c219 | |||
| 99e1d44f7d | |||
| 8c55659c18 | |||
| 3ea980b2f8 | |||
| f5fc987b51 | |||
| 9cfa4af2b2 | |||
| 3efdf1fa87 | |||
| 25fc832605 | |||
| a7e404c5d5 | |||
| 90b27f0564 | |||
| 632440965d | |||
| aedfac446b | |||
| 3372b0025a | |||
| 0112b92e77 | |||
| 0caf53b147 | |||
| 4d1354d5f4 | |||
| fc4562eca2 | |||
| 5b048a5510 | |||
| d23049b2fe | |||
| caac5607b9 | |||
| fb2e69b12d | |||
| fc1838ff39 | |||
| 25689cf40f | |||
| c746a3d215 | |||
| 548d924fa0 | |||
| 18a6be3ecd | |||
| afef0cb72f | |||
| 1221b47eca | |||
| a356eeb5da | |||
| 25bb0972b9 | |||
| 0914075cf6 | |||
| 74b258e1b6 | |||
| 27eb0a21e4 | |||
| d77c6d50d8 | |||
| 6a64c18a7b | |||
| b97ff714ae | |||
| 1f23eea805 | |||
| cb0cb7df21 | |||
| f3e8628664 | |||
| 4758471f9f | |||
| c10bd5a549 | |||
| d53faf96eb | |||
| 2307b8ed1a | |||
| 711af8d4a5 | |||
| 458c9c456b | |||
| 29069df48c | |||
| 300e84ae2c | |||
| bf11768b92 | |||
| 91bd1d9263 | |||
| ac40f9ff12 | |||
| f930837220 | |||
| 1d5e3acc93 | |||
| 2aec1f9846 | |||
| b56a681b98 | |||
| 2713419b32 | |||
| 764a3492cc | |||
| 29fc471ac4 | |||
| 54a597aad3 | |||
| 778961d0b6 | |||
| 14b01ac761 | |||
| f87848c05b | |||
| 153e5a806d | |||
| ee1b8a906f | |||
| 734685d45a | |||
| e3b540c212 | |||
| 201f578bbc | |||
| ce9aa369d2 | |||
| c5c41c0859 | |||
| d3009f82dd | |||
| 0eb9a1fdb0 | |||
| 83f6d9926a | |||
| 14134842b0 | |||
| 4c094c4710 | |||
| 3ac41075fd | |||
| fdc97773d1 | |||
| 917a739595 | |||
| 21475f81ae | |||
| a2a9a0560c | |||
| b879846cdb | |||
| a24011cd72 | |||
| 410dd2ecdc | |||
| 96b111b894 | |||
| 630572c8f9 | |||
| 6a381a6366 | |||
| fd04f12677 | |||
| 86b8cd17a4 | |||
| bb8ee01693 | |||
| 5fb0bca3af | |||
| 1e4d1f373d | |||
| 87c78d0fcc | |||
| 041d2b7947 | |||
| 5e7b90d995 | |||
| fadc3dcb55 | |||
| 4eea091944 | |||
| 9f8d46f395 | |||
| 2c54a057c9 | |||
| e1e5cacc6d | |||
| edf9066967 | |||
| 7097d12b87 | |||
| 08aaeb6372 | |||
| 4310f80ec0 | |||
| 01577f6494 | |||
| 9aa570b2c6 | |||
| d611d7d698 | |||
| f05549b06c | |||
| 8c4db8dd40 | |||
| ddee69d291 | |||
| 28960c9fab | |||
| 449301e519 | |||
| 28dab3dc2f | |||
| 7995d98db5 | |||
| 1f319f3533 | |||
| 7d2dbaa62d | |||
| ed5999526b | |||
| 2409f75491 | |||
| 0c840095d1 | |||
| 3eaf0b4856 | |||
| 394735a5d9 | |||
| 5bfa10bfe7 | |||
| 3f12d9cbd5 | |||
| 54d89754df | |||
| 9470524615 | |||
| b5faa6eaaa | |||
| eab476cfe8 | |||
| b773def012 | |||
| a9f8b23a7a | |||
| 61bb6535fb | |||
| eb0bdffe06 | |||
| e129485285 | |||
| aac7de172e | |||
| e19d2412fc | |||
| 7cba2cf76c | |||
| 55d7a49dbc | |||
| f695864fd8 | |||
| ae4883fbe7 | |||
| 836dfd2861 | |||
| 6e0523c6d7 | |||
| cd9ba084ae | |||
| c8a0c0de67 | |||
| 5dd8d9df91 | |||
| 2dbee94e85 | |||
| 9001797037 | |||
| 5e7104d5a3 | |||
| 7c33b22d63 | |||
| b7cb1e379c | |||
| 274a6ce52e | |||
| 8079e4e7b8 | |||
| 920efb8fab | |||
| 92aeb2b9da | |||
| ced76cf343 | |||
| 3750d6ecd5 | |||
| 7444f30384 | |||
| b0fd5ba47f | |||
| cc098bc2e5 | |||
| 1cfcf8d194 | |||
| d1e9f741d0 | |||
| 0cde9d07ee | |||
| a266425348 | |||
| 6dbe02e321 | |||
| 39979879c9 | |||
| ba3f604a5c | |||
| 9572582401 | |||
| 2bcef0ba75 | |||
| 31cd7c4007 | |||
| 1c4c1799b0 | |||
| 2d558df15e | |||
| 7b769da4e2 | |||
| 4e95d62933 | |||
| 83255280b9 | |||
| 2546cebdea | |||
| f14a3fe0f1 | |||
| c3eb845be4 | |||
| 1eef77dca5 | |||
| d7d9f47fe5 | |||
| fce6a996d9 | |||
| 48a484d7de | |||
| 4b81d3c612 | |||
| 21b6edd7eb | |||
| 9a43e022e9 | |||
| 62bef3f13d | |||
| c87f5fa004 | |||
| 8d99f3ffbb | |||
| 100ca7b54b | |||
| 4a2624f12a | |||
| 21b01a85a8 | |||
| e039ae436e | |||
| b13a1a45f8 | |||
| 667fe2bbba | |||
| f083871ca0 | |||
| 782e1ba235 | |||
| b92c6edc4e | |||
| bdf4657c73 | |||
| 8559438611 | |||
| 04045fd410 | |||
| 1c71a9d8a8 | |||
| 9a9ae9782c | |||
| da2569850b | |||
| b475035415 | |||
| 7baee27585 | |||
| 2cb2f1ce0a | |||
| 8c1407e2a9 | |||
| 7bcb113085 | |||
| 42dc27addf | |||
| aac30398c4 | |||
| ceae888372 | |||
| 61df7050db | |||
| af5561cbbf | |||
| bce30f922d | |||
| d6ed394557 | |||
| 7b512a1681 | |||
| 7af0597cd5 | |||
| 2f5cf6d93c | |||
| d672d2d104 | |||
| c0d1fb1ef4 | |||
| 3c882ad0ce | |||
| 58acd2eb68 | |||
| 590473e7d2 | |||
| 21139157c8 | |||
| 8f8296ab64 | |||
| 40bf07d2a4 | |||
| fd2b4fb86d | |||
| 783e867ef6 | |||
| 3873d9f978 | |||
| 7dece938c8 | |||
| 0842d66c79 | |||
| d071e50d62 | |||
| 337f01aff1 | |||
| 5614e41eb0 | |||
| 4f0fdbe453 | |||
| 49c068db78 | |||
| fbba44d482 | |||
| 10c07b0a09 | |||
| 20893698ba | |||
| 31de659ed3 | |||
| 6c08a16c66 | |||
| 21e7178f48 | |||
| aefd267eb2 | |||
| fb1a3980d4 | |||
| cfc467f8a5 | |||
| 48120f012d | |||
| c907a78437 | |||
| 915c0d09a9 | |||
| 80638862cc | |||
| a7350948cd | |||
| 1e1be013b5 | |||
| 4062584eae | |||
| abedddffa6 | |||
| 38858ccdb2 | |||
| d56e6359d0 | |||
| 5886991464 | |||
| 838326616b | |||
| fcf80fe946 | |||
| 2e63a7056c | |||
| 7c807e3ec8 | |||
| b558aa0934 | |||
| 38757758cb | |||
| 84dd3d8fd1 | |||
| 19423c2d30 | |||
| 98bba7d7fa | |||
| dcaa9ce000 | |||
| 3cae44a136 | |||
| df8a3d2692 | |||
| 56e43f1808 | |||
| 2f6b95cccf | |||
| ad9f6b2b64 | |||
| 165c3dc976 | |||
| cbe6390b41 | |||
| 026eaf6606 | |||
| ee184f8e62 | |||
| 5745231dc9 | |||
| cbdc3a9861 | |||
| ba5c42ad6d | |||
| b056a731a1 | |||
| b4cd94f2d9 | |||
| 3b8cb43f02 | |||
| 82cb11c3c4 | |||
| eb7522a90b | |||
| 3898201b1e | |||
| bb6ab0c17b | |||
| 652c40ab1f | |||
| ca0d6215a9 | |||
| 61d155257c | |||
| 91399f1a1f | |||
| 2e738c5977 | |||
| c0bd51cf03 | |||
| ca5afe04f8 | |||
| 47db0b9f4e | |||
| 27f17a377a | |||
| ecaf731ecf | |||
| 9fba2a85f6 | |||
| 93f75be182 | |||
| d537901f59 | |||
| 260c1ed4a4 | |||
| ac2bd52463 | |||
| 16c6f8fee1 | |||
| 8a0db21f17 | |||
| 9ab08a71c0 | |||
| 381c32a3b4 | |||
| 350c0f10c4 | |||
| d5b982f813 | |||
| 6587375ff2 | |||
| f5ecb81bad | |||
| 0141cd81c4 | |||
| ff412c7497 | |||
| 623f239f91 | |||
| c7ba304c6a | |||
| c8393604d1 | |||
| c5d47a67aa | |||
| 3a1423cd59 | |||
| d57f4cf6cd | |||
| af420942c4 | |||
| eac4e5e66f | |||
| b8822a98f7 | |||
| 95322b42c0 | |||
| 33f5a06d32 | |||
| a7e5126c92 | |||
| c62f3e5b8e | |||
| 5d3f413a05 | |||
| 644154ca38 | |||
| cccce5ff74 | |||
| aa1fe98188 | |||
| b6a78550d6 | |||
| 70b7f2c493 | |||
| ea44e5f209 | |||
| 41ef87d9f8 | |||
| c0b4a70aa0 | |||
| 955875cb7a | |||
| 827e8571aa | |||
| aed08b0f59 | |||
| 3ddaf41e27 | |||
| 38a9cce6bf | |||
| ae255ef381 | |||
| 4316bef01e | |||
| 8491c5706d | |||
| 7b7fab1f32 | |||
| 5a6fccdc2f | |||
| 23668be46d | |||
| f92f2c1666 | |||
| 895740a3cb | |||
| 4c76c3231f | |||
| ca9d3f9559 | |||
| 3ca3c5e75c | |||
| c2c0b1dfdc | |||
| c633774487 | |||
| c2d86fc517 | |||
| d7c8d150ca | |||
| 6b8a4e65f2 | |||
| 482e43dd1f | |||
| 1437babc6c | |||
| 5ed882943a | |||
| e678b3fb3a | |||
| a5770bc4e4 | |||
| 59821bb939 | |||
| b58040be44 | |||
| be3b8ad52e | |||
| 0fdaf97461 | |||
| f5f036372f | |||
| 3fceb983e6 | |||
| ae6a39196d | |||
| 2810ff2753 | |||
| 205c0cf8d3 | |||
| d9c801a091 | |||
| 0833aa3b30 | |||
| 9c90bbc146 | |||
| 231b387076 | |||
| 3de432652f | |||
| cad714493e | |||
| 6a690a6108 | |||
| 1b85c52557 | |||
| b37be92ea9 | |||
| f95979f445 | |||
| 09e07218ae | |||
| 085f5a9451 | |||
| eb4e935554 | |||
| 57986cf425 | |||
| 7bf14b9acc | |||
| 3ba0be34d9 | |||
| 0f828a3c75 | |||
| 16a768a142 | |||
| f380d20279 | |||
| be908ab36c | |||
| b545454537 | |||
| a5ff8dd1d6 | |||
| 59f23e518b | |||
| 858b3f0176 | |||
| 89b8494ad0 | |||
| 64ecd75bd5 | |||
| 138eb3393d | |||
| 1ad51d2925 | |||
| 317e175bca | |||
| 0e287aa7d1 | |||
| 28018b705b | |||
| 46ec72d6be | |||
| 82494a87af | |||
| 95c19b447a | |||
| 9c6eae936a | |||
| 77605a9360 | |||
| 82880c3f06 | |||
| 11143c771b | |||
| 0968eaa5a8 | |||
| 0687f9d472 | |||
| 25f0f5951a | |||
| eec0ce65ad | |||
| be4e100168 | |||
| 59e4f1063c | |||
| 359e21f02b | |||
| 6894bd9c0e | |||
| 7040f02823 | |||
| 41408de337 | |||
| 72e243dce8 | |||
| 77d4840e2a | |||
| 0d5961821c | |||
| f34980b173 | |||
| 5b21a6e529 | |||
| 6dbb737d4b | |||
| 76ecfbaaa2 | |||
| d9aacbcb92 | |||
| e088325d95 | |||
| b29dd2c580 | |||
| 89e82fd5b0 | |||
| 9f0b671d42 | |||
| 7de7c724dc | |||
| 3525b1d92a | |||
| 30cea84886 | |||
| 0c2cc7f575 |
@@ -14,7 +14,7 @@ export default defineConfig({
|
||||
dynamicImport: {
|
||||
loading: '@/components/pageLoading',
|
||||
},
|
||||
favicon: '/images/favicon.svg',
|
||||
favicon: '/images/g5.ico',
|
||||
proxy: {
|
||||
'/api/public': {
|
||||
target: 'http://127.0.0.1:5400/',
|
||||
|
||||
+2
-6
@@ -57,7 +57,7 @@ https://podman.io/getting-started/installation
|
||||
```bash
|
||||
podman run -dit \
|
||||
--network bridge \
|
||||
-v $PWD/ql/data:/ql/data \
|
||||
-v $PWD/ql:/ql/data \
|
||||
-p 5700:5700 \
|
||||
--name qinglong \
|
||||
--hostname qinglong \
|
||||
@@ -96,7 +96,7 @@ systemctl restart docker
|
||||
|
||||
```bash
|
||||
docker run -dit \
|
||||
-v $PWD/ql/data:/ql/data \
|
||||
-v $PWD/ql:/ql/data \
|
||||
-p 5700:5700 \
|
||||
--name qinglong \
|
||||
--hostname qinglong \
|
||||
@@ -124,10 +124,6 @@ docker-compose up -d
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
3. access
|
||||
|
||||
Open your browser and visit http://{ip}:5700
|
||||
|
||||
## Use
|
||||
|
||||
1. built-in commands
|
||||
|
||||
@@ -57,7 +57,7 @@ https://podman.io/getting-started/installation
|
||||
```bash
|
||||
podman run -dit \
|
||||
--network bridge \
|
||||
-v $PWD/ql/data:/ql/data \
|
||||
-v $PWD/ql:/ql/data \
|
||||
-p 5700:5700 \
|
||||
--name qinglong \
|
||||
--hostname qinglong \
|
||||
@@ -97,7 +97,7 @@ systemctl restart docker
|
||||
|
||||
```bash
|
||||
docker run -dit \
|
||||
-v $PWD/ql/data:/ql/data \
|
||||
-v $PWD/ql:/ql/data \
|
||||
-p 5700:5700 \
|
||||
--name qinglong \
|
||||
--hostname qinglong \
|
||||
@@ -125,10 +125,6 @@ docker-compose up -d
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
3. 访问
|
||||
|
||||
打开你的浏览器,访问 http://{ip}:5700
|
||||
|
||||
## 使用
|
||||
|
||||
1. 内置命令
|
||||
@@ -167,11 +163,11 @@ task <file_path> desi <env_name> <account_number>
|
||||
|
||||
* file_url: 脚本地址
|
||||
* repo_url: 仓库地址
|
||||
* whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串,多个竖线分割
|
||||
* blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串,多个竖线分割
|
||||
* dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响,多个竖线分割
|
||||
* extensions: 拉取仓库的文件后缀,多个竖线分割
|
||||
* whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串
|
||||
* blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串
|
||||
* dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响
|
||||
* branch: 拉取仓库的分支
|
||||
* extensions: 拉取仓库的文件后缀
|
||||
* days: 需要保留的日志的天数
|
||||
* file_path: 任务执行时的文件路径
|
||||
* env_name: 任务执行时需要并发或者指定时的环境变量名称
|
||||
|
||||
+1
-126
@@ -2,7 +2,6 @@ import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import { Logger } from 'winston';
|
||||
import CronService from '../services/cron';
|
||||
import CronViewService from '../services/cronView';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import cron_parser from 'cron-parser';
|
||||
const route = Router();
|
||||
@@ -10,135 +9,11 @@ const route = Router();
|
||||
export default (app: Router) => {
|
||||
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) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.crontabs(req.query as any);
|
||||
const data = await cronService.crontabs(req.query.searchValue as string);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
import 'reflect-metadata'; // We need this in order to use @Decorators
|
||||
|
||||
import config from './config';
|
||||
|
||||
import express from 'express';
|
||||
|
||||
import Logger from './loaders/logger';
|
||||
import path from 'path';
|
||||
|
||||
async function startServer() {
|
||||
const app = express();
|
||||
|
||||
+2
-13
@@ -4,21 +4,10 @@ import { createRandomString } from './util';
|
||||
|
||||
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 rootPath = process.env.QL_DIR as string;
|
||||
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
|
||||
|
||||
const envFound = dotenv.config();
|
||||
const rootPath = process.cwd();
|
||||
const dataPath = path.join(rootPath, 'data/');
|
||||
const samplePath = path.join(rootPath, 'sample/');
|
||||
const configPath = path.join(dataPath, 'config/');
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
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,
|
||||
},
|
||||
);
|
||||
@@ -4,7 +4,6 @@ export enum NotificationMode {
|
||||
'serverChan' = 'serverChan',
|
||||
'pushDeer' = 'pushDeer',
|
||||
'bark' = 'bark',
|
||||
'chat' = 'chat',
|
||||
'telegramBot' = 'telegramBot',
|
||||
'dingtalkBot' = 'dingtalkBot',
|
||||
'weWorkBot' = 'weWorkBot',
|
||||
@@ -38,11 +37,6 @@ export class PushDeerNotification extends NotificationBaseInfo {
|
||||
public pushDeerKey = '';
|
||||
}
|
||||
|
||||
export class ChatNotification extends NotificationBaseInfo {
|
||||
public chatUrl = '';
|
||||
public chatToken = '';
|
||||
}
|
||||
|
||||
export class BarkNotification extends NotificationBaseInfo {
|
||||
public barkPush = '';
|
||||
public barkIcon = 'http://qn.whyour.cn/logo.png';
|
||||
@@ -92,7 +86,6 @@ export interface NotificationInfo
|
||||
GotifyNotification,
|
||||
ServerChanNotification,
|
||||
PushDeerNotification,
|
||||
ChatNotification,
|
||||
BarkNotification,
|
||||
TelegramBotNotification,
|
||||
DingtalkBotNotification,
|
||||
|
||||
+1
-2
@@ -6,9 +6,9 @@ import { CrontabModel } from '../data/cron';
|
||||
import { DependenceModel } from '../data/dependence';
|
||||
import { AppModel } from '../data/open';
|
||||
import { AuthModel } from '../data/auth';
|
||||
import { sequelize } from '../data';
|
||||
import { fileExist } from '../config/util';
|
||||
import { SubscriptionModel } from '../data/subscription';
|
||||
import { CrontabViewModel } from '../data/cronView';
|
||||
import config from '../config';
|
||||
|
||||
export default async () => {
|
||||
@@ -19,7 +19,6 @@ export default async () => {
|
||||
await AuthModel.sync();
|
||||
await EnvModel.sync();
|
||||
await SubscriptionModel.sync();
|
||||
await CrontabViewModel.sync();
|
||||
|
||||
// try {
|
||||
// const queryInterface = sequelize.getQueryInterface();
|
||||
|
||||
@@ -5,7 +5,7 @@ import dotenv from 'dotenv';
|
||||
import Logger from './logger';
|
||||
import { fileExist } from '../config/util';
|
||||
|
||||
const rootPath = process.env.QL_DIR as string;;
|
||||
const rootPath = process.cwd();
|
||||
const dataPath = path.join(rootPath, 'data/');
|
||||
const configPath = path.join(dataPath, 'config/');
|
||||
const scriptPath = path.join(dataPath, 'scripts/');
|
||||
@@ -64,5 +64,13 @@ export default async () => {
|
||||
|
||||
dotenv.config({ path: confFile });
|
||||
|
||||
// 声明QL_DIR环境变量
|
||||
let qlHomePath = path.join(__dirname, '../../');
|
||||
// 生产环境
|
||||
if (qlHomePath.endsWith('/static/')) {
|
||||
qlHomePath = path.join(qlHomePath, '../');
|
||||
}
|
||||
process.env.QL_DIR = qlHomePath;
|
||||
|
||||
Logger.info('✌️ Init file down');
|
||||
};
|
||||
|
||||
@@ -3,30 +3,12 @@ import _ from 'lodash';
|
||||
import SystemService from '../services/system';
|
||||
import ScheduleService from '../services/schedule';
|
||||
import SubscriptionService from '../services/subscription';
|
||||
import config from '../config';
|
||||
import { fileExist } from '../config/util';
|
||||
|
||||
export default async () => {
|
||||
const systemService = Container.get(SystemService);
|
||||
const scheduleService = Container.get(ScheduleService);
|
||||
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();
|
||||
if (data && data.info && data.info.frequency) {
|
||||
@@ -35,7 +17,7 @@ export default async () => {
|
||||
name: '删除日志',
|
||||
command: `ql rmlog ${data.info.frequency}`,
|
||||
};
|
||||
scheduleService.createIntervalTask(cron, {
|
||||
await scheduleService.createIntervalTask(cron, {
|
||||
days: data.info.frequency,
|
||||
runImmediately: true,
|
||||
});
|
||||
|
||||
+39
-129
@@ -113,56 +113,8 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
|
||||
private formatViewQuery(query: any, viewQuery: any) {
|
||||
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) {
|
||||
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
||||
let query = {};
|
||||
if (searchText) {
|
||||
const textArray = searchText.split(':');
|
||||
switch (textArray[0]) {
|
||||
@@ -171,11 +123,13 @@ export default class CronService {
|
||||
case 'schedule':
|
||||
case 'label':
|
||||
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
|
||||
query[column] = {
|
||||
[Op.or]: [
|
||||
{ [Op.like]: `%${textArray[1]}%` },
|
||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
||||
],
|
||||
query = {
|
||||
[column]: {
|
||||
[Op.or]: [
|
||||
{ [Op.like]: `%${textArray[1]}%` },
|
||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
||||
],
|
||||
},
|
||||
};
|
||||
break;
|
||||
default:
|
||||
@@ -185,75 +139,31 @@ export default class CronService {
|
||||
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
||||
],
|
||||
};
|
||||
query[Op.or] = [
|
||||
{
|
||||
name: reg,
|
||||
},
|
||||
{
|
||||
command: reg,
|
||||
},
|
||||
{
|
||||
schedule: reg,
|
||||
},
|
||||
{
|
||||
labels: reg,
|
||||
},
|
||||
];
|
||||
query = {
|
||||
[Op.or]: [
|
||||
{
|
||||
name: reg,
|
||||
},
|
||||
{
|
||||
command: reg,
|
||||
},
|
||||
{
|
||||
schedule: reg,
|
||||
},
|
||||
{
|
||||
labels: reg,
|
||||
},
|
||||
],
|
||||
};
|
||||
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 {
|
||||
const result = await CrontabModel.findAll(condition);
|
||||
const count = await CrontabModel.count({ where: query });
|
||||
return { data: result, total: count };
|
||||
const result = await CrontabModel.findAll({
|
||||
where: query,
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
return result as any;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@@ -369,13 +279,7 @@ export default class CronService {
|
||||
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
|
||||
cmdStr = `task ${cmdStr}`;
|
||||
}
|
||||
if (
|
||||
cmdStr.endsWith('.js') ||
|
||||
cmdStr.endsWith('.py') ||
|
||||
cmdStr.endsWith('.pyc') ||
|
||||
cmdStr.endsWith('.sh') ||
|
||||
cmdStr.endsWith('.ts')
|
||||
) {
|
||||
if (cmdStr.endsWith('.js')) {
|
||||
cmdStr = `${cmdStr} now`;
|
||||
}
|
||||
|
||||
@@ -398,10 +302,16 @@ export default class CronService {
|
||||
|
||||
cp.on('exit', async (code, signal) => {
|
||||
this.logger.info(
|
||||
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
|
||||
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
|
||||
);
|
||||
await CrontabModel.update(
|
||||
{ status: CrontabStatus.idle, pid: undefined },
|
||||
{ where: { id } },
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
cp.on('close', async (code) => {
|
||||
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
|
||||
await CrontabModel.update(
|
||||
{ status: CrontabStatus.idle, pid: undefined },
|
||||
{ where: { id } },
|
||||
@@ -531,7 +441,7 @@ export default class CronService {
|
||||
private async set_crontab(needReloadSchedule: boolean = false) {
|
||||
const tabs = await this.crontabs();
|
||||
var crontab_string = '';
|
||||
tabs.data.forEach((tab) => {
|
||||
tabs.forEach((tab) => {
|
||||
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
||||
if (tab.isDisabled === 1 || _schedule!.length !== 5) {
|
||||
crontab_string += '# ';
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export default class EnvService {
|
||||
.filter((x) => x.status !== EnvStatus.disabled)
|
||||
.map('value')
|
||||
.join('&')
|
||||
.replace(/(\\)[^n]/g, '\\\\')
|
||||
.replace(/(\\)[^\n]/g, '\\\\')
|
||||
.replace(/(\\$)/, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.trim();
|
||||
|
||||
+2
-17
@@ -17,7 +17,6 @@ export default class NotificationService {
|
||||
['goCqHttpBot', this.goCqHttpBot],
|
||||
['serverChan', this.serverChan],
|
||||
['pushDeer', this.pushDeer],
|
||||
['chat', this.chat],
|
||||
['bark', this.bark],
|
||||
['telegramBot', this.telegramBot],
|
||||
['dingtalkBot', this.dingtalkBot],
|
||||
@@ -136,20 +135,6 @@ export default class NotificationService {
|
||||
);
|
||||
}
|
||||
|
||||
private async chat() {
|
||||
const { chatUrl, chatToken } = this.params;
|
||||
const url = `${chatUrl}${chatToken}`;
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
timeout: this.timeout,
|
||||
retry: 0,
|
||||
body: `payload={"text":"${this.title}\n${this.content}"}`,
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
.json();
|
||||
return res.success;
|
||||
}
|
||||
|
||||
private async bark() {
|
||||
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
||||
if (!barkPush.startsWith('http') && !barkPush.startsWith('https')) {
|
||||
@@ -260,7 +245,7 @@ export default class NotificationService {
|
||||
const [corpid, corpsecret, touser, agentid, thumb_media_id = '1'] =
|
||||
weWorkAppKey.split(',');
|
||||
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
|
||||
const tokenRes: any = await got
|
||||
const { access_token } = await got
|
||||
.post(url, {
|
||||
timeout: this.timeout,
|
||||
retry: 0,
|
||||
@@ -311,7 +296,7 @@ export default class NotificationService {
|
||||
|
||||
const res: any = await got
|
||||
.post(
|
||||
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
|
||||
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`,
|
||||
{
|
||||
timeout: this.timeout,
|
||||
retry: 0,
|
||||
|
||||
+17
-9
@@ -145,7 +145,7 @@ export default class OpenService {
|
||||
}
|
||||
}
|
||||
|
||||
public async generateSystemToken(): Promise<{
|
||||
public async findSystemToken(): Promise<{
|
||||
value: string;
|
||||
expiration: number;
|
||||
}> {
|
||||
@@ -158,13 +158,21 @@ export default class OpenService {
|
||||
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,
|
||||
};
|
||||
const nowTime = Math.round(Date.now() / 1000);
|
||||
let token;
|
||||
if (
|
||||
!systemApp.tokens ||
|
||||
!systemApp.tokens.length ||
|
||||
nowTime > [...systemApp.tokens].pop()!.expiration
|
||||
) {
|
||||
const authToken = await this.authToken({
|
||||
client_id: systemApp.client_id,
|
||||
client_secret: systemApp.client_secret,
|
||||
});
|
||||
token = authToken.data;
|
||||
} else {
|
||||
token = [...systemApp.tokens].pop();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,12 +79,13 @@ export default class ScheduleService {
|
||||
|
||||
cp.on('exit', async (code, signal) => {
|
||||
this.logger.info(
|
||||
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
|
||||
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
|
||||
);
|
||||
});
|
||||
|
||||
cp.on('close', async (code) => {
|
||||
const endTime = dayjs();
|
||||
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
|
||||
await callbacks.onEnd?.(
|
||||
cp,
|
||||
endTime,
|
||||
@@ -168,7 +169,7 @@ export default class ScheduleService {
|
||||
);
|
||||
|
||||
const job = new LongIntervalJob(
|
||||
{ runImmediately: false, ...schedule },
|
||||
{ ...schedule, runImmediately: false },
|
||||
task,
|
||||
_id,
|
||||
);
|
||||
|
||||
@@ -42,22 +42,47 @@ export default class SubscriptionService {
|
||||
public async list(searchText?: string): Promise<Subscription[]> {
|
||||
let query = {};
|
||||
if (searchText) {
|
||||
const reg = {
|
||||
[Op.or]: [
|
||||
{ [Op.like]: `%${searchText}%` },
|
||||
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
||||
],
|
||||
};
|
||||
query = {
|
||||
[Op.or]: [
|
||||
{
|
||||
name: reg,
|
||||
},
|
||||
{
|
||||
url: reg,
|
||||
},
|
||||
],
|
||||
};
|
||||
const textArray = searchText.split(':');
|
||||
switch (textArray[0]) {
|
||||
case 'name':
|
||||
case 'command':
|
||||
case 'schedule':
|
||||
case 'label':
|
||||
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
|
||||
query = {
|
||||
[column]: {
|
||||
[Op.or]: [
|
||||
{ [Op.like]: `%${textArray[1]}%` },
|
||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
||||
],
|
||||
},
|
||||
};
|
||||
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 {
|
||||
const result = await SubscriptionModel.findAll({
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
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();
|
||||
+4
-6
@@ -1,9 +1,9 @@
|
||||
FROM python:alpine
|
||||
FROM node:alpine
|
||||
|
||||
ARG QL_MAINTAINER="whyour"
|
||||
LABEL maintainer="${QL_MAINTAINER}"
|
||||
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
||||
ARG QL_BRANCH=develop
|
||||
ARG QL_BRANCH=master
|
||||
|
||||
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 \
|
||||
@@ -29,17 +29,15 @@ RUN set -x \
|
||||
perl \
|
||||
openssl \
|
||||
nginx \
|
||||
nodejs \
|
||||
python3 \
|
||||
jq \
|
||||
openssh \
|
||||
npm \
|
||||
py3-pip \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& apk update \
|
||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||
&& git config --global user.name "qinglong" \
|
||||
&& git config --global http.postBuffer 524288000 \
|
||||
&& npm install -g pnpm \
|
||||
&& pnpm add -g pm2 ts-node typescript tslib \
|
||||
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||
|
||||
@@ -9,7 +9,6 @@ make_dir /etc/nginx/conf.d
|
||||
make_dir /run/nginx
|
||||
cp -fv $nginx_conf /etc/nginx/nginx.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
|
||||
echo
|
||||
|
||||
|
||||
+15
-15
@@ -1,12 +1,12 @@
|
||||
upstream baseApi {
|
||||
upstream api {
|
||||
server 0.0.0.0:5600;
|
||||
}
|
||||
|
||||
upstream publicApi {
|
||||
upstream public {
|
||||
server 0.0.0.0:5400;
|
||||
}
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default keep-alive;
|
||||
'websocket' upgrade;
|
||||
}
|
||||
@@ -16,42 +16,42 @@ server {
|
||||
root /ql/static/dist;
|
||||
ssl_session_timeout 5m;
|
||||
|
||||
location QL_BASE_URL/api/public/ {
|
||||
location /api/public {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://publicApi/api/public/;
|
||||
proxy_pass http://public;
|
||||
}
|
||||
|
||||
location QL_BASE_URL/api/ {
|
||||
location /api {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://baseApi/api/;
|
||||
proxy_pass http://api;
|
||||
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
location QL_BASE_URL/open/ {
|
||||
location /open {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://baseApi/open/;
|
||||
proxy_pass http://api;
|
||||
}
|
||||
|
||||
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_proxied any;
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.0;
|
||||
gzip_http_version 1.0;
|
||||
|
||||
location QL_BASE_URL/ {
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ QL_BASE_URL/index.html;
|
||||
location / {
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~ .*\.(html)$ {
|
||||
|
||||
+19
-19
@@ -5,41 +5,41 @@ error_log /var/log/nginx/error.log warn;
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
server_tokens off;
|
||||
server_tokens off;
|
||||
|
||||
client_max_body_size 20m;
|
||||
client_body_buffer_size 20m;
|
||||
client_max_body_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_static on;
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
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_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.0;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
access_log /var/log/nginx/access.log main;
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,6 @@
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.2",
|
||||
"umi": "^3.5.21",
|
||||
"umi-request": "^1.4.0",
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
@@ -29,14 +29,6 @@ MaxConcurrentNum="5"
|
||||
## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-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
|
||||
EnableExtraShell="true"
|
||||
|
||||
@@ -137,10 +129,4 @@ export GOTIFY_PRIORITY=0
|
||||
## deer_key 填写PushDeer的key
|
||||
export DEER_KEY=""
|
||||
|
||||
## 12. Chat
|
||||
## chat_url 填写synology chat地址,http://IP:PORT/webapi/***token=
|
||||
## chat_token 填写后面的token
|
||||
export CHAT_URL=""
|
||||
export CHAT_TOKEN=""
|
||||
|
||||
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
||||
|
||||
+8
-70
@@ -39,12 +39,6 @@ let SCKEY = '';
|
||||
//(环境变量名 DEER_KEY)
|
||||
let PUSHDEER_KEY = '';
|
||||
|
||||
// =======================================Synology Chat通知设置区域===========================================
|
||||
//此处填你申请的CHAT_URL与CHAT_TOKEN
|
||||
//(环境变量名 CHAT_URL CHAT_TOKEN)
|
||||
let CHAT_URL = '';
|
||||
let CHAT_TOKEN = '';
|
||||
|
||||
// =======================================Bark App通知设置区域===========================================
|
||||
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
||||
let BARK_PUSH = '';
|
||||
@@ -103,13 +97,6 @@ let IGOT_PUSH_KEY = '';
|
||||
let PUSH_PLUS_TOKEN = '';
|
||||
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) {
|
||||
GOTIFY_URL = process.env.GOTIFY_URL;
|
||||
@@ -139,14 +126,6 @@ if (process.env.DEER_KEY) {
|
||||
PUSHDEER_KEY = process.env.DEER_KEY;
|
||||
}
|
||||
|
||||
if (process.env.CHAT_URL) {
|
||||
CHAT_URL = process.env.CHAT_URL;
|
||||
}
|
||||
|
||||
if (process.env.CHAT_TOKEN) {
|
||||
CHAT_TOKEN = process.env.CHAT_TOKEN;
|
||||
}
|
||||
|
||||
if (process.env.QQ_SKEY) {
|
||||
QQ_SKEY = process.env.QQ_SKEY;
|
||||
}
|
||||
@@ -253,8 +232,6 @@ async function sendNotify(
|
||||
iGotNotify(text, desp, params), //iGot
|
||||
gobotNotify(text, desp), //go-cqhttp
|
||||
gotifyNotify(text, desp), //gotify
|
||||
ChatNotify(text, desp), //synolog chat
|
||||
PushDeerNotify(text, desp), //PushDeer
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -385,7 +362,7 @@ function serverNotify(text, desp, time = 2100) {
|
||||
});
|
||||
}
|
||||
|
||||
function PushDeerNotify(text, desp) {
|
||||
function PushDeerNotify(text, desp, time = 2100) {
|
||||
return new Promise((resolve) => {
|
||||
if (PUSHDEER_KEY) {
|
||||
// PushDeer 建议对消息内容进行 urlencode
|
||||
@@ -398,9 +375,8 @@ function PushDeerNotify(text, desp) {
|
||||
},
|
||||
timeout,
|
||||
};
|
||||
$.post(
|
||||
options,
|
||||
(err, resp, data) => {
|
||||
setTimeout(() => {
|
||||
$.post(options, (err, resp, data) => {
|
||||
try {
|
||||
if (err) {
|
||||
console.log('发送通知调用API失败!!\n');
|
||||
@@ -424,46 +400,8 @@ function PushDeerNotify(text, desp) {
|
||||
} finally {
|
||||
resolve(data);
|
||||
}
|
||||
},
|
||||
time,
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ChatNotify(text, desp) {
|
||||
return new Promise((resolve) => {
|
||||
if (CHAT_URL && CHAT_TOKEN) {
|
||||
// 对消息内容进行 urlencode
|
||||
desp = encodeURI(desp);
|
||||
const options = {
|
||||
url: `${CHAT_URL}${CHAT_TOKEN}`,
|
||||
body: `payload={"text":"${text}\n${desp}"}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
};
|
||||
$.post(options, (err, resp, data) => {
|
||||
try {
|
||||
if (err) {
|
||||
console.log('发送通知调用API失败!!\n');
|
||||
console.log(err);
|
||||
} else {
|
||||
data = JSON.parse(data);
|
||||
if (data.success) {
|
||||
console.log('Chat发送通知消息成功🎉\n');
|
||||
} else {
|
||||
console.log(`Chat发送通知消息异常\n${JSON.stringify(data)}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
$.logErr(e);
|
||||
} finally {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, time);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
@@ -785,9 +723,9 @@ function qywxamNotify(text, desp) {
|
||||
timeout,
|
||||
};
|
||||
$.post(options_accesstoken, (err, resp, data) => {
|
||||
let html = desp.replace(/\n/g, '<br/>');
|
||||
let json = JSON.parse(data);
|
||||
let accesstoken = json.access_token;
|
||||
html = desp.replace(/\n/g, '<br/>');
|
||||
var json = JSON.parse(data);
|
||||
accesstoken = json.access_token;
|
||||
let options;
|
||||
|
||||
switch (QYWX_AM_AY[4]) {
|
||||
|
||||
+1
-25
@@ -61,10 +61,7 @@ push_config = {
|
||||
'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版
|
||||
|
||||
'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY
|
||||
|
||||
'CHAT_URL': '', # synology chat url
|
||||
'CHAT_TOKEN': '', # synology chat token
|
||||
|
||||
|
||||
'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌
|
||||
'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码
|
||||
|
||||
@@ -282,27 +279,8 @@ def pushdeer(title: str, content: str) -> None:
|
||||
print("PushDeer 推送成功!")
|
||||
else:
|
||||
print("PushDeer 推送失败!错误信息:", response)
|
||||
|
||||
|
||||
def chat(title: str, content: str) -> None:
|
||||
"""
|
||||
通过Chat 推送消息
|
||||
"""
|
||||
if not push_config.get("CHAT_URL") or not push_config.get("CHAT_TOKEN"):
|
||||
print("chat 服务的 CHAT_URL或CHAT_TOKEN 未设置!!\n取消推送")
|
||||
return
|
||||
print("chat 服务启动")
|
||||
data = 'payload=' + json.dumps({'text': title + '\n' + content})
|
||||
url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN")
|
||||
response = requests.post(url, data=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("Chat 推送成功!")
|
||||
else:
|
||||
print("Chat 推送失败!错误信息:", response)
|
||||
|
||||
|
||||
|
||||
def pushplus_bot(title: str, content: str) -> None:
|
||||
"""
|
||||
通过 push+ 推送消息。
|
||||
@@ -549,8 +527,6 @@ if push_config.get("PUSH_KEY"):
|
||||
notify_function.append(serverJ)
|
||||
if push_config.get("DEER_KEY"):
|
||||
notify_function.append(pushdeer)
|
||||
if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"):
|
||||
notify_function.append(chat)
|
||||
if push_config.get("PUSH_PLUS_TOKEN"):
|
||||
notify_function.append(pushplus_bot)
|
||||
if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"):
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
get_token() {
|
||||
token=$(cat $file_auth_token | jq -r .value)
|
||||
token=$(ts-node-transpile-only "$dir_shell/token.ts")
|
||||
}
|
||||
|
||||
add_cron_api() {
|
||||
|
||||
@@ -31,7 +31,6 @@ copy_dep() {
|
||||
echo -e "---> 2. 复制nginx配置文件\n"
|
||||
cp -fv $nginx_conf /etc/nginx/nginx.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"
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ file_sharecode=$dir_config/sharecode.sh
|
||||
file_config_user=$dir_config/config.sh
|
||||
file_auth_sample=$dir_sample/auth.sample.json
|
||||
file_auth_user=$dir_config/auth.json
|
||||
file_auth_token=$dir_config/token.json
|
||||
file_extra_shell=$dir_config/extra.sh
|
||||
file_task_before=$dir_config/task_before.sh
|
||||
file_task_after=$dir_config/task_after.sh
|
||||
@@ -67,7 +66,6 @@ import_config() {
|
||||
[[ -f $file_config_user ]] && . $file_config_user
|
||||
[[ -f $file_env ]] && . $file_env
|
||||
|
||||
ql_base_url=${QlBaseUrl:-""}
|
||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
||||
proxy_url=${ProxyUrl:-""}
|
||||
file_extensions=${RepoFileExtensions:-"js py"}
|
||||
@@ -403,17 +401,6 @@ format_time() {
|
||||
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"
|
||||
|
||||
+26
-43
@@ -24,29 +24,12 @@ define_program() {
|
||||
random_delay() {
|
||||
local random_delay_max=$RandomDelay
|
||||
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
|
||||
local file_param=$1
|
||||
local file_extensions=${RandomDelayFileExtensions-"js"}
|
||||
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
|
||||
|
||||
if [[ -n $file_extensions ]]; then
|
||||
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
|
||||
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
|
||||
return
|
||||
fi
|
||||
local current_min=$(date "+%-M")
|
||||
if [[ $current_min -ne 0 ]] && [[ $current_min -ne 30 ]]; then
|
||||
delay_second=$(($(gen_random_num $random_delay_max) + 1))
|
||||
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
|
||||
sleep $delay_second
|
||||
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
|
||||
}
|
||||
|
||||
@@ -95,12 +78,14 @@ run_nohup() {
|
||||
run_normal() {
|
||||
local file_param=$1
|
||||
define_program "$file_param"
|
||||
if [[ $# -eq 1 ]]; then
|
||||
random_delay "$file_param"
|
||||
if [[ $file_param == *.js ]]; then
|
||||
if [[ $# -eq 1 ]]; then
|
||||
random_delay
|
||||
fi
|
||||
fi
|
||||
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
local time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||
log_time="$time"
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -118,9 +103,8 @@ run_normal() {
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
|
||||
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
|
||||
@@ -171,8 +155,8 @@ run_concurrent() {
|
||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||
|
||||
define_program "$file_param"
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
local time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||
log_time="$time"
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -190,8 +174,8 @@ run_concurrent() {
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
@@ -241,8 +225,8 @@ run_designated() {
|
||||
fi
|
||||
|
||||
define_program "$file_param"
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
local time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||
log_time="$time"
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -260,8 +244,8 @@ run_designated() {
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
|
||||
local envs=$(eval echo "\$${env_param}")
|
||||
local array=($(echo $envs | sed 's/&/ /g'))
|
||||
@@ -305,8 +289,8 @@ run_designated() {
|
||||
run_else() {
|
||||
local file_param="$1"
|
||||
define_program "$file_param"
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
local time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||
log_time="$time"
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -324,8 +308,8 @@ run_else() {
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
local begin_time=$(format_time "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
local begin_timestamp=$(format_timestamp "%Y-%m-%d-%H-%M-%S" "$time")
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
@@ -370,8 +354,7 @@ main() {
|
||||
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
|
||||
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
||||
case $# in
|
||||
1)
|
||||
run_normal "$1"
|
||||
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
import 'reflect-metadata';
|
||||
import OpenService from '../back/services/open';
|
||||
import { Container } from 'typedi';
|
||||
import LoggerInstance from '../back/loaders/logger';
|
||||
import fs from 'fs';
|
||||
import config from '../back/config';
|
||||
import path from 'path';
|
||||
|
||||
const tokenFile = path.join(config.configPath, 'token.json');
|
||||
|
||||
async function getToken() {
|
||||
try {
|
||||
const data = await readFile();
|
||||
const nowTime = Math.round(Date.now() / 1000);
|
||||
if (data.value && data.expiration > nowTime) {
|
||||
console.log(data.value);
|
||||
} else {
|
||||
Container.set('logger', LoggerInstance);
|
||||
const openService = Container.get(OpenService);
|
||||
const appToken = await openService.findSystemToken();
|
||||
console.log(appToken.value);
|
||||
await writeFile({
|
||||
value: appToken.value,
|
||||
expiration: appToken.expiration,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function readFile() {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
fs.readFile(
|
||||
path.join(config.configPath, 'token.json'),
|
||||
{ encoding: 'utf8' },
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
resolve({});
|
||||
} else {
|
||||
resolve(JSON.parse(data));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function writeFile(data: any) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(tokenFile, JSON.stringify(data), { encoding: 'utf8' }, () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getToken();
|
||||
@@ -388,9 +388,6 @@ gen_list_repo() {
|
||||
local index=0
|
||||
if [[ $6 ]]; then
|
||||
file_extensions="$6"
|
||||
if [[ $file_extensions =~ "|" ]]; then
|
||||
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
|
||||
fi
|
||||
fi
|
||||
for extension in $file_extensions; do
|
||||
if [[ $index -eq 0 ]]; then
|
||||
|
||||
+10
-28
@@ -56,13 +56,7 @@
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
background-color: @component-background;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-container-wrapper-has-tab {
|
||||
.ant-pro-grid-content.wide .ant-pro-page-container-children-content {
|
||||
padding-top: 0;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +136,7 @@
|
||||
flex: 1;
|
||||
|
||||
.ant-pro-grid-content-children {
|
||||
height: 100%;
|
||||
height: calc(100% - 36px);
|
||||
|
||||
> div,
|
||||
.log-container,
|
||||
@@ -270,24 +264,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-basicLayout-content {
|
||||
margin: 12px;
|
||||
|
||||
.ant-pro-page-container {
|
||||
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-page-container-children-content {
|
||||
margin: 18px 18px 18px;
|
||||
}
|
||||
|
||||
.ant-pro-global-header {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
|
||||
.ant-pro-basicLayout-content {
|
||||
margin: 18px;
|
||||
|
||||
.ant-pro-page-container {
|
||||
margin: -18px -18px -18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-item.ant-pro-sider-collapsed-button {
|
||||
@@ -326,10 +312,6 @@
|
||||
background-color: #373739;
|
||||
}
|
||||
|
||||
.ant-layout-sider {
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||
}
|
||||
|
||||
.ant-pro-sider-logo {
|
||||
padding: 16px 8px !important;
|
||||
|
||||
|
||||
@@ -186,8 +186,9 @@ export default function (props: any) {
|
||||
if (
|
||||
['/login', '/initialization', '/error'].includes(props.location.pathname)
|
||||
) {
|
||||
document.title = `${(config.documentTitleMap as any)[props.location.pathname]
|
||||
} - 控制面板`;
|
||||
document.title = `${
|
||||
(config.documentTitleMap as any)[props.location.pathname]
|
||||
} - 控制面板`;
|
||||
if (
|
||||
systemInfo?.isInitialized &&
|
||||
props.location.pathname === '/initialization'
|
||||
@@ -281,7 +282,7 @@ export default function (props: any) {
|
||||
shape="square"
|
||||
size="small"
|
||||
icon={<UserOutlined />}
|
||||
src={user.avatar ? `/api/static/${user.avatar}` : ''}
|
||||
src={`/api/static/${user.avatar}`}
|
||||
/>
|
||||
<span style={{ marginLeft: 5 }}>{user.username}</span>
|
||||
</span>
|
||||
@@ -303,7 +304,7 @@ export default function (props: any) {
|
||||
shape="square"
|
||||
size="small"
|
||||
icon={<UserOutlined />}
|
||||
src={user.avatar ? `/api/static/${user.avatar}` : ''}
|
||||
src={`/api/static/${user.avatar}`}
|
||||
/>
|
||||
<span style={{ marginLeft: 5 }}>{user.username}</span>
|
||||
</span>
|
||||
|
||||
@@ -108,6 +108,7 @@ const CronDetailModal = ({
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
onMount={(editor, monaco) => {
|
||||
console.log(monaco);
|
||||
editorRef.current = editor;
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -101,76 +101,3 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+73
-239
@@ -12,8 +12,6 @@ import {
|
||||
Typography,
|
||||
Input,
|
||||
Popover,
|
||||
Tabs,
|
||||
TablePaginationConfig,
|
||||
} from 'antd';
|
||||
import {
|
||||
ClockCircleOutlined,
|
||||
@@ -29,11 +27,6 @@ import {
|
||||
PauseCircleOutlined,
|
||||
FieldTimeOutlined,
|
||||
PushpinOutlined,
|
||||
DownOutlined,
|
||||
SettingOutlined,
|
||||
PlusOutlined,
|
||||
UnorderedListOutlined,
|
||||
CheckOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
@@ -46,10 +39,6 @@ import { diffTime } from '@/utils/date';
|
||||
import { getTableScroll } from '@/utils/index';
|
||||
import { history } from 'umi';
|
||||
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 { Search } = Input;
|
||||
@@ -133,7 +122,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
</>
|
||||
),
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a?.name?.localeCompare(b?.name),
|
||||
compare: (a: any, b: any) => a.name.localeCompare(b.name),
|
||||
multiple: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -158,6 +148,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
},
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.command.localeCompare(b.command),
|
||||
multiple: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -168,6 +159,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
align: 'center' as const,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.schedule.localeCompare(b.schedule),
|
||||
multiple: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -354,24 +346,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||
const [logCron, setLogCron] = useState<any>();
|
||||
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
||||
const [pageConf, setPageConf] = useState<{
|
||||
page: number;
|
||||
size: number;
|
||||
sorter: any;
|
||||
}>({} as any);
|
||||
const [viewConf, setViewConf] = useState<any>();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
||||
const [detailCron, setDetailCron] = useState<any>();
|
||||
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 cmd = record.command.split(' ') as string[];
|
||||
@@ -393,35 +373,37 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
|
||||
const getCrons = () => {
|
||||
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
|
||||
.get(url)
|
||||
.then((_data: any) => {
|
||||
const { data, total } = _data.data;
|
||||
.get(`${config.apiPrefix}crons?searchValue=${searchText}`)
|
||||
.then((data: any) => {
|
||||
setValue(
|
||||
data.map((x) => {
|
||||
return {
|
||||
...x,
|
||||
nextRunTime: cron_parser
|
||||
.parseExpression(x.schedule)
|
||||
.next()
|
||||
.toDate(),
|
||||
};
|
||||
}),
|
||||
data.data
|
||||
.sort((a: any, b: any) => {
|
||||
const sortA =
|
||||
a.isPinned && a.status !== 0
|
||||
? 5
|
||||
: a.isDisabled && a.status !== 0
|
||||
? 4
|
||||
: 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(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
setTotal(total);
|
||||
setCurrentPage(1);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -761,6 +743,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const rowSelection = {
|
||||
selectedRowIds,
|
||||
onChange: onSelectChange,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE,
|
||||
],
|
||||
};
|
||||
|
||||
const delCrons = () => {
|
||||
@@ -809,14 +796,10 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onPageChange = (
|
||||
pagination: TablePaginationConfig,
|
||||
filters: Record<string, FilterValue | null>,
|
||||
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 onPageChange = (page: number, pageSize: number | undefined) => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize as number);
|
||||
localStorage.setItem('pageSize', pageSize + '');
|
||||
};
|
||||
|
||||
const getRowClassName = (record: any, index: number) => {
|
||||
@@ -831,36 +814,39 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
}, [logCron]);
|
||||
|
||||
useEffect(() => {
|
||||
setPageConf({ ...pageConf, page: 1 });
|
||||
getCrons();
|
||||
}, [searchText]);
|
||||
|
||||
useEffect(() => {
|
||||
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: {},
|
||||
});
|
||||
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
|
||||
setTimeout(() => {
|
||||
setTableScrollHeight(getTableScroll());
|
||||
});
|
||||
getCronViews();
|
||||
}, []);
|
||||
|
||||
const panelContent = (
|
||||
<>
|
||||
return (
|
||||
<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 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
|
||||
@@ -920,16 +906,15 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
<Table
|
||||
columns={columns}
|
||||
pagination={{
|
||||
current: pageConf.page,
|
||||
pageSize: pageConf.size,
|
||||
current: currentPage,
|
||||
onChange: onPageChange,
|
||||
pageSize: pageSize,
|
||||
showSizeChanger: true,
|
||||
simple: isPhone,
|
||||
total,
|
||||
defaultPageSize: 20,
|
||||
showTotal: (total: number, range: number[]) =>
|
||||
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
||||
pageSizeOptions: [10, 20, 50, 100, 200, 500, total || 10000].sort(
|
||||
(a, b) => a - b,
|
||||
),
|
||||
pageSizeOptions: [20, 100, 500, 1000] as any,
|
||||
}}
|
||||
onRow={(record) => {
|
||||
return {
|
||||
@@ -946,135 +931,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
loading={loading}
|
||||
rowSelection={rowSelection}
|
||||
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);
|
||||
setPageConf({ ...pageConf, page: 1 });
|
||||
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
|
||||
visible={isLogModalVisible}
|
||||
handleCancel={() => {
|
||||
@@ -1107,29 +964,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
theme={theme}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
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;
|
||||
@@ -1,269 +0,0 @@
|
||||
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 (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper dependence-wrapper ql-container-wrapper-has-tab"
|
||||
className="ql-container-wrapper dependence-wrapper"
|
||||
title="依赖管理"
|
||||
extra={[
|
||||
<Search
|
||||
|
||||
@@ -154,7 +154,6 @@ const Login = ({ reloadUser }: any) => {
|
||||
message: '验证码为6位数字',
|
||||
},
|
||||
]}
|
||||
validateTrigger="onBlur"
|
||||
>
|
||||
<Input
|
||||
placeholder="6位数字"
|
||||
|
||||
@@ -313,7 +313,7 @@ const Setting = ({
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper ql-container-wrapper-has-tab"
|
||||
className="ql-container-wrapper"
|
||||
title="系统设置"
|
||||
header={{
|
||||
style: headerStyle,
|
||||
|
||||
@@ -90,7 +90,6 @@ export default {
|
||||
{ value: 'weWorkApp', label: '企业微信应用' },
|
||||
{ value: 'iGot', label: 'IGot' },
|
||||
{ value: 'pushPlus', label: 'PushPlus' },
|
||||
{ value: 'chat', label: '群辉chat' },
|
||||
{ value: 'email', label: '邮箱' },
|
||||
{ value: 'closed', label: '已关闭' },
|
||||
],
|
||||
@@ -104,14 +103,6 @@ export default {
|
||||
{ label: 'gotifyToken', tip: 'gotify的消息应用token码', required: true },
|
||||
{ label: 'gotifyPriority', tip: '推送消息的优先级' },
|
||||
],
|
||||
chat: [
|
||||
{
|
||||
label: 'chatUrl',
|
||||
tip: 'chat的url地址',
|
||||
required: true,
|
||||
},
|
||||
{ label: 'chatToken', tip: 'chat的token码', required: true },
|
||||
],
|
||||
goCqHttpBot: [
|
||||
{
|
||||
label: 'goCqHttpBotUrl',
|
||||
|
||||
+2
-2
@@ -181,8 +181,8 @@ export function getTableScroll({
|
||||
id,
|
||||
}: { extraHeight?: number; id?: string } = {}) {
|
||||
if (typeof extraHeight == 'undefined') {
|
||||
// 47 + 40 + 12
|
||||
extraHeight = 99;
|
||||
// 47 + 40 + 10 + 24
|
||||
extraHeight = 121;
|
||||
}
|
||||
let tHeader = null;
|
||||
if (id) {
|
||||
|
||||
+13
-6
@@ -1,7 +1,14 @@
|
||||
export const version = '2.14.1';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/326';
|
||||
export const changeLog = `2.14.1 版本说明
|
||||
1. 修复定时任务搜索
|
||||
2. 修复视图切换默认页码
|
||||
3. 增加群辉chat通知方式,感谢 https://github.com/Appoip
|
||||
export const version = '2.13.4';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/303';
|
||||
export const changeLog = `2.13.4 版本说明
|
||||
1. 增加系统token,防止未登录时无法更新仓库和任务状态
|
||||
2. 脚本管理按照类型排序
|
||||
3. 任务详情中编辑脚本支持全屏
|
||||
4. 环境变量增加导出功能
|
||||
5. 修复无法更新脚本内容为空
|
||||
6. 修复停止node脚本报错
|
||||
7. 修改手动停止任务耗时
|
||||
8. 修复脚本管理、日志管理树
|
||||
9. 修复获取环境变量详情
|
||||
10. 区分原生termux和termux中安装的Linux发,感谢 erma0
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user