mirror of
https://github.com/midoks/mdserver-web
synced 2026-11-05 21:36:00 +08:00
Compare commits
2913 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e18bd098d9 | |||
| 8aaa3cfab6 | |||
| 02a9f84c13 | |||
| 23320865b0 | |||
| 6f365e122a | |||
| e3229efe1b | |||
| 710acecff7 | |||
| 9c7a155be0 | |||
| 157a5da6f1 | |||
| 7ee5ed5808 | |||
| e8a96959a5 | |||
| a4845acbb6 | |||
| fec638007c | |||
| 80f7a0de38 | |||
| a3bb9ac80a | |||
| 3dcc6e3349 | |||
| 60870a4e5d | |||
| ea635bc960 | |||
| 749c8443ee | |||
| 052120c25e | |||
| a2b3b86c43 | |||
| 6d57e3dd40 | |||
| a67689d890 | |||
| b6bffedf06 | |||
| e07eaf4226 | |||
| dd9e9d4899 | |||
| cd1d80915a | |||
| 40f64ab3e8 | |||
| 69ba921ec4 | |||
| 255fbcd688 | |||
| 24e12b70c8 | |||
| 0feee48ea8 | |||
| 5cd8c5ae2c | |||
| 28a75eaedc | |||
| 50d0794f4a | |||
| 755e53f4d7 | |||
| 1876027bd2 | |||
| 26b06f9bb2 | |||
| 8f67d35260 | |||
| 7fa78c78e2 | |||
| ae7276300f | |||
| 420816ff4e | |||
| df34a7ef9b | |||
| e1b199c8d5 | |||
|
|
e6060847ef | ||
| 7ff1530684 | |||
| cd0ad7488e | |||
| 6a0f81154a | |||
|
|
096c736c28 | ||
| 7d01a487dc | |||
| ba95ef1537 | |||
| 01035018c4 | |||
| 8aa12b979a | |||
| 98dc9e5bd8 | |||
| 4ebadc28c9 | |||
| 02115226fa | |||
| f661aea83c | |||
| cfd3abf97b | |||
| b5d1c8cfcf | |||
| 98156cc1cb | |||
| 33a0dc6014 | |||
| 95a6f86e5d | |||
| 74ac55b01d | |||
| 518cc78670 | |||
| 43727decf0 | |||
| 599b3a7f4a | |||
| bf6c18c57a | |||
| 34838bb238 | |||
| 1a356e516c | |||
| 15e273aa6f | |||
|
|
4c3d3bf41d | ||
| f782bacc64 | |||
| 7e62ab62f9 | |||
| aaa8644348 | |||
| 53797c7f56 | |||
| c9302f406e | |||
| 7ad881c8ad | |||
| 015e57f96d | |||
| 84b94a172e | |||
| c1380945ef | |||
| 2d80df003e | |||
| 568cdaa0ad | |||
| d11e4807df | |||
|
|
f6bc08cec1 | ||
| d723afb96e | |||
|
|
6c2aa089f3 | ||
| 35e1ff2e96 | |||
| bee9e89ed5 | |||
| d0b622906f | |||
| 56f7edcb55 | |||
| 4acbc0363f | |||
| 79013db685 | |||
|
|
eb5b5e5ce8 | ||
| b9e6469635 | |||
| 56ab927f8c | |||
| 3d74320e76 | |||
| 0371eaa37a | |||
| 713dbfcda5 | |||
| 93ef2848ed | |||
| 8750cf0f74 | |||
| af32a1e54d | |||
| 2a7c63029a | |||
| f8fe7bea19 | |||
| ef2814bafe | |||
| 220fef5f4d | |||
| 2835c111d0 | |||
| 6d0dcf3112 | |||
| 28307ce8e8 | |||
| 282fbed37d | |||
| 101b6d06bc | |||
| 8de4c74696 | |||
| ba62972820 | |||
| ec512f50af | |||
| 3261f54649 | |||
| ec38ba9e21 | |||
| 9e1dc94a08 | |||
| 40a76ac81e | |||
| 721245b9ad | |||
| cb4ae9b74f | |||
|
|
5524ecf4e0 | ||
| dbb2718892 | |||
| 88fc903d80 | |||
|
|
242c30f7cb | ||
| c944b7c585 | |||
| dc2e2695be | |||
| f5b86ea189 | |||
| b6c2f7f3a1 | |||
| 81003f75b7 | |||
| d6dc330083 | |||
| fd0cbe10fc | |||
| 4ed77e82be | |||
|
|
de0e89f056 | ||
| 29da5146f6 | |||
| e951089b45 | |||
| 909d65ad83 | |||
| 27cac55b8f | |||
| 0ee6ae3713 | |||
| fb97160cfb | |||
| 96b00526fe | |||
| 909fe6d0f1 | |||
| f2e9f4c5e8 | |||
| 3b629b1296 | |||
| 5cee415030 | |||
|
|
141e52d42a | ||
| 1b21f3c814 | |||
| 4ddc2fef8e | |||
| 18b259a5a5 | |||
| 2e2288a691 | |||
| d03ae1db7b | |||
| 418355a571 | |||
| 340b53aa62 | |||
| dd0628a7fb | |||
| 3e5b48d2ad | |||
|
|
815148a722 | ||
| 47aadfd877 | |||
| 42bfd9a934 | |||
| 55df1b7613 | |||
| 6bc3fc5707 | |||
| 19771181dd | |||
| f0b65332bd | |||
| 7dd8a1eb4f | |||
| f584c18d7e | |||
| b023044473 | |||
| dfa7ffdb53 | |||
| a5a6eb7e5f | |||
| eac88f1d26 | |||
| 518f693948 | |||
| 43684e9b20 | |||
| 59a7bb5584 | |||
| f623fd9dcf | |||
| 9db36d45af | |||
| ff5670797a | |||
| 54ad7e5fd0 | |||
| 2970aa4b8c | |||
| d2e5751542 | |||
| ba5f6f4958 | |||
| b220552f92 | |||
| d02981cee5 | |||
| 96d5246415 | |||
| ea284049c0 | |||
| 5c44bb10d1 | |||
| c7c834fb75 | |||
| 2ade5e490c | |||
| 6c61d3f3c0 | |||
| ab19878a80 | |||
| c27abfde3c | |||
| 411b21cdec | |||
| 26e21e631d | |||
|
|
653068f2c2 | ||
| 188c63826f | |||
| 1820fc724f | |||
| 645519b983 | |||
| f97e015bbc | |||
| 9d47a385f5 | |||
| ca06c68d24 | |||
| 841873418c | |||
| 05a6f093ab | |||
| a916104d33 | |||
| 4609c360ac | |||
| b3acf3647f | |||
| 400c9d1276 | |||
|
|
59bd655bb1 | ||
| 8227253b74 | |||
| 4e5604dc27 | |||
| 2e238e6982 | |||
| c5df0f256c | |||
| 6bd73131ce | |||
| 309422a41c | |||
| 6aa7d9dc20 | |||
| 565e1cc882 | |||
|
|
5c1e292da9 | ||
| 6db1ee133d | |||
|
|
383cec2ee5 | ||
| dfa2163407 | |||
| ed9a9b7860 | |||
| 5af49dc081 | |||
| d4f425abf8 | |||
| 1867ee01f1 | |||
| 81fe83e0a1 | |||
| f266ec86c0 | |||
| e5d6357fae | |||
| 7967b36088 | |||
| d7471fe882 | |||
| b4b67c8f48 | |||
| 63ae895f12 | |||
| 5dd77ee747 | |||
| 6071dfa5f1 | |||
| 11d8d813d2 | |||
| f337a60dc1 | |||
| 33f66ab3b1 | |||
| eb71732d20 | |||
| fbc9340960 | |||
| a2f14f4121 | |||
| ce8c7474ce | |||
| 85d9f1409a | |||
| c29dfee004 | |||
| 618f938462 | |||
| 4e7927f9bc | |||
| 212f29438d | |||
| 3b6d9c3310 | |||
| c01cb01d71 | |||
| 712863b766 | |||
| 601a80950f | |||
| 9f46ceac33 | |||
| ad9b9be7fe | |||
| 2be6ac36ef | |||
| fdb8ca205f | |||
| f1024c165a | |||
| 9efc85f10d | |||
| dd592b40ee | |||
| 9103646150 | |||
| cae2680862 | |||
| e3099a946b | |||
| 3f1da50025 | |||
| fbd70eb4f9 | |||
| ef0d6d5265 | |||
| 1e6932cd01 | |||
| d173586d49 | |||
| 717269dadc | |||
| 21c4f93e02 | |||
| c36a03bfb3 | |||
| a71927917c | |||
| 2f5d5835c5 | |||
| e95b42df77 | |||
| 5c6dbbbb0c | |||
| a151516eab | |||
| 3a590c7feb | |||
| e9f0e1962e | |||
| c4fda1362d | |||
| f31343b2a5 | |||
| 90b414d373 | |||
| 3eab70db4c | |||
| e0810bb8d6 | |||
| 96a32d5c8f | |||
| d45cfb5fe8 | |||
| 08753ae96d | |||
| 465e55848f | |||
| 4492a97155 | |||
| ba46d568bf | |||
| d87d5d5349 | |||
| 76ea3e3375 | |||
| ef47145135 | |||
| 43a9ee7368 | |||
| 6646e0c46c | |||
| 535dbb2e37 | |||
| 035953c7f5 | |||
| 6a9ce81d46 | |||
| f2a6824632 | |||
| 594976373e | |||
| 45704f1c31 | |||
| ce57885343 | |||
| 4d143ef959 | |||
| 9a0f9a759c | |||
| 6341f00f46 | |||
| f517568251 | |||
| 1ff6d0c2cb | |||
| 5e28039573 | |||
|
|
d7757bee0d | ||
| 6b18511708 | |||
| 4a3e0791d3 | |||
| 7c25d3f6c3 | |||
| 8ef38674d7 | |||
| e84cbf1d76 | |||
| 14f622d252 | |||
| 8bb2bfacdc | |||
| 191c4b1331 | |||
| 7bed2b977e | |||
| b6c09b2b37 | |||
|
|
1bd5251dc0 | ||
| ecf1b6b600 | |||
| 22cca82967 | |||
| cd66e42b1a | |||
| d887fa1b50 | |||
| 55870ca08a | |||
| 2ce1d49386 | |||
| a0226ccbcd | |||
| de7c14ec62 | |||
| 0c2cfd6d9a | |||
| 4ca78b9a91 | |||
| 8219c2d2ed | |||
| 9f22ce949c | |||
| d76c21c54b | |||
| 30c5cee81a | |||
| e2d0ac6d89 | |||
| 3578a65d6e | |||
| c8331b800b | |||
| 1e7dc18c21 | |||
| 046da1868e | |||
| ef7068ebd2 | |||
| 0525667032 | |||
| 6198ca02da | |||
| 4d4fd90fc6 | |||
| acfc4d710b | |||
| 4fd12f4d73 | |||
| 9a8bb9f94b | |||
| 170f758bc9 | |||
| 8b7d563fac | |||
| 6f9e0cdd8f | |||
| 0a3af7a0c0 | |||
| c90a2facac | |||
| 92d56bddab | |||
| a3bab2da47 | |||
| d51a3ac1f7 | |||
| 04774bddc3 | |||
| 770f1532da | |||
| d09872c2f3 | |||
| e77369cd25 | |||
| 69897a208c | |||
| 1f5c5ac66e | |||
| 3a7f8f09cb | |||
| 77d2b9e323 | |||
| d56c271cc4 | |||
| b4aecf5d0c | |||
| e55e23c845 | |||
| e2977dbbc4 | |||
| 8ad94ffeff | |||
| c94ff04dcf | |||
| 65f10ca22f | |||
| a1127b3bc0 | |||
| 81c16d1a3d | |||
| b1b4c82457 | |||
| 515b22b103 | |||
| c200165057 | |||
| 64c30893d5 | |||
| 98b8dce5fd | |||
| 0d9b7b6edf | |||
| b72bb1fa87 | |||
| d85131266c | |||
| 22da275f5b | |||
| f6d65b9e33 | |||
| ebc07becd0 | |||
| 024f2c2369 | |||
| 2b93fb684b | |||
| b2daf6bf65 | |||
| 3ee0f553e9 | |||
| f206da8278 | |||
| edbaeb37f5 | |||
| 46847833d0 | |||
| 4303a62b1b | |||
| d6d5babf81 | |||
| 8e5b10bda3 | |||
| 8e54ee987c | |||
| 929b30ce73 | |||
| c085fa7066 | |||
| 783e71f189 | |||
| ae1ffd313c | |||
| 122c0b5f64 | |||
| 21e496f22e | |||
| d92b3039e2 | |||
| ea57812191 | |||
| d72238df45 | |||
| 55aab8d86d | |||
| f8d4d9e4cf | |||
| 90afa6446c | |||
| 5e468c548a | |||
| 48f4521ef6 | |||
| ccd48cecfb | |||
| 0757638b2e | |||
| 68cb547d5f | |||
| 1f7c494d82 | |||
|
|
8e58783964 | ||
| bfdc0639b6 | |||
| 337a7d87e4 | |||
| 38c062220c | |||
| f639acec86 | |||
| 02ba38a01a | |||
| ea8487b14c | |||
|
|
f0b613865b | ||
| b142f37045 | |||
| 45eeb3c045 | |||
| ab99d972cd | |||
| 08562a12a2 | |||
| d7e62bbfa1 | |||
| 5829c48995 | |||
| cd133fc278 | |||
| 8a2a4d1a84 | |||
|
|
a225b98432 | ||
| 1acd50b3f3 | |||
| 5279fe0ba7 | |||
| a73f7d86a9 | |||
| 5fe7e4b43d | |||
| d50d666fb2 | |||
| 86229586f1 | |||
| 2947dea3dd | |||
| 264534ff2c | |||
| 82655592d7 | |||
|
|
85f90e8ce6 | ||
| 450ae37f05 | |||
| 07d0eff75b | |||
| 8697bea0d3 | |||
| a06839fe20 | |||
| 6542113a21 | |||
| 63d04b8c75 | |||
| 71b409a970 | |||
| e0bcc720cc | |||
| 8bcabd055b | |||
| 94597476ac | |||
| a5172d4b5a | |||
| ee79ea55db | |||
| 2230c2b632 | |||
| 9b231554ef | |||
| ccc499359b | |||
| c5ba0dd8a2 | |||
| 3811979f7e | |||
| fb17b7e3a1 | |||
| 6ea79b8b28 | |||
| 037e360e3d | |||
| 3e4a995d5a | |||
| 9d7056184a | |||
| 8aad54b2eb | |||
| 9639dc0e19 | |||
|
|
1f816c4c09 | ||
| d88c3d812e | |||
| de3f060e8b | |||
| 2c740726e2 | |||
| 01fcadf7c9 | |||
| 21378014c4 | |||
| 765e3d7d1a | |||
| 2bd77e5830 | |||
| b5c61af33c | |||
| ac7df862c1 | |||
| ae30e8c740 | |||
| db4cd40e35 | |||
| 592cdc3e17 | |||
|
|
9a84276abf | ||
| 1c70d6381b | |||
| 556da39be8 | |||
| 59c8d02c7d | |||
| 3b0b53d1b5 | |||
| 996cbfb279 | |||
| e47ad14cb1 | |||
| 8921647180 | |||
|
|
2d7933e715 | ||
| cffe463779 | |||
| 2a0e7c090d | |||
| eab8ed9551 | |||
|
|
e9c3b5d135 | ||
| 59076d4750 | |||
| 8aa84d22a8 | |||
| cfb26034b1 | |||
| 953980ee47 | |||
| 994198517a | |||
| 98728b2eb8 | |||
| 6498606e69 | |||
|
|
d17ba4d785 | ||
| e4eeed3559 | |||
| b100204b37 | |||
| 82ff3e3d0a | |||
| 0b2223bce0 | |||
| a43b723b09 | |||
| 96551f49ca | |||
| 8e32d37985 | |||
| d1da238ddc | |||
| 7260a2be21 | |||
| 7562d50e58 | |||
| e77ae8763b | |||
| a0e51c75fe | |||
| 6021a5e6e2 | |||
| e50e222955 | |||
| 531dd665c9 | |||
| a79c0a1a9b | |||
| 9ef144c304 | |||
| 5402af8df7 | |||
| 99802125dc | |||
| 705647cd3b | |||
| 0ea2eeab7f | |||
| 727cde1352 | |||
| 6ef6358cbb | |||
| 6acb303ac9 | |||
| a80e5492b8 | |||
| 4358b1a520 | |||
| ad1a7c22a6 | |||
| 2f3af23e6d | |||
| 89a26d294d | |||
| f83faeacac | |||
| fb7c27c205 | |||
| 3f958c7083 | |||
| 60b6689318 | |||
| ba94e81367 | |||
| 89c8c68b88 | |||
| e78ae7e4de | |||
| f87cadd3ce | |||
| 1631e149ea | |||
| d84f585bff | |||
| 7e9091b341 | |||
| 7020d3e198 | |||
| 9d8e0ded8f | |||
| 090e8fa160 | |||
| 83746675e7 | |||
| 74d73d76cd | |||
| 6bfedde66d | |||
| 554bee4040 | |||
| a1005adf04 | |||
| cbed47d825 | |||
| abb7da896b | |||
| e62a69ac45 | |||
| 769285736b | |||
| 8d2eaa94bd | |||
| a31143e0e8 | |||
| 584551f764 | |||
| 8b60a65bbd | |||
| c13df7c25b | |||
| ad30ba776c | |||
| de3451993f | |||
| fde9b49f39 | |||
| cff0e9442c | |||
| 5cb57d89de | |||
| fadcbaa1bb | |||
| 911554f6df | |||
| 77b525d955 | |||
| b872a2bb53 | |||
| 818444d4f4 | |||
| 63e16e05aa | |||
| 06743894a3 | |||
| 2d665235c8 | |||
| 72311a2801 | |||
| d7ba290ed3 | |||
| e0d2984bae | |||
| ecdbe8d014 | |||
| 7d57e320b8 | |||
| cf16b729f6 | |||
| 366c5c91aa | |||
| 173cf2985e | |||
| 0bdba73f25 | |||
| 24d25e69ad | |||
| d3e6297503 | |||
| 76b5f15115 | |||
| cfa92b637a | |||
| f07e75e508 | |||
| 4220300731 | |||
|
|
43b464866f | ||
| 7bf5dbb4f3 | |||
| 9fe8453898 | |||
| b421cf261f | |||
| 75d28763ee | |||
| 25533fa8bc | |||
| 04294e6cbe | |||
| f0550ba6b3 | |||
| 88d0733d95 | |||
| 74633e155a | |||
| 7d8d7a8c6f | |||
|
|
32956578b2 | ||
| 4efdf7606c | |||
| a61644861b | |||
| e60570e380 | |||
| ad160cb23e | |||
| 73f58ced9a | |||
| 36ca82c1b4 | |||
| acd46f4724 | |||
| 370ff99134 | |||
| aa317ee2f5 | |||
| 44600b5657 | |||
| ea45316a32 | |||
| 6cd13022f1 | |||
| 91ff2f7550 | |||
| 483b5b090d | |||
| 6087931c58 | |||
| 9014fd8911 | |||
| 76f7f51d80 | |||
| 0c4902e01e | |||
| 175fb089cd | |||
| 7b8a4a38b3 | |||
| ebf920ed12 | |||
| bb8591f5e5 | |||
| b8573e1f1f | |||
| dacc1e5ad8 | |||
| 8cc64e32dd | |||
| 1d294cc3b1 | |||
| f9da2e4391 | |||
| 3b78079cd2 | |||
| 07a0a1be48 | |||
| 837f95e571 | |||
|
|
909d818caa | ||
|
|
355ceebbd7 | ||
| 6ae1370709 | |||
|
|
93b5d8b8b4 | ||
|
|
be461a15a9 | ||
| c239f35382 | |||
| 49d57af719 | |||
|
|
19fc0fdc51 | ||
| c1fd4fa24c | |||
| 7239b22759 | |||
| f5fa94ae95 | |||
|
|
b679ea035d | ||
| 49bbca8faa | |||
| 8b86cd5e63 | |||
| 698e3ffd81 | |||
|
|
c2284dcee5 | ||
| b68c9edc3e | |||
| 650365997a | |||
| 3279404595 | |||
| d32a94dccf | |||
| 80d846f247 | |||
| eadcbe336d | |||
| 474d0f075f | |||
| 2d6a41e19f | |||
| 1f24944417 | |||
| 4ab3409e54 | |||
| c3ad146303 | |||
| 745cd304fb | |||
| e6efefd1fd | |||
| 1350404c58 | |||
| 9cdd2274fa | |||
| cc204ad721 | |||
| 8eb1614b31 | |||
| 9c2e9627ec | |||
| 87123c526a | |||
| 7c3ebc6e35 | |||
| 6c5bf63424 | |||
| 13fd47e7d4 | |||
| fc1321584a | |||
| bab3ae16d3 | |||
| ab8498e519 | |||
| 0e480c7ad8 | |||
| 7ed8558ff0 | |||
| 4ed2f0caa9 | |||
| f275eabe1b | |||
| fe13ab1e5f | |||
| 6ed045cf94 | |||
| ef333ff142 | |||
| dac2f5240f | |||
| cebfc2fc91 | |||
| 1da865c1b2 | |||
| 2abdaaa7ce | |||
| c62142cf6b | |||
| 7c1820b6cd | |||
| a24af95d24 | |||
| 1326c282c8 | |||
| b7e84749f1 | |||
| 8a65158540 | |||
| a576c521a2 | |||
| 736a387a71 | |||
| 090205c372 | |||
| fda0bddc8f | |||
| 292b18468d | |||
| 47aab245fc | |||
| 39bd601825 | |||
| b744adeb13 | |||
| 7b70f9fc99 | |||
|
|
0723f751b3 | ||
| 1fd8617a12 | |||
|
|
2fec8fcef4 | ||
|
|
51a48afa71 | ||
|
|
76919faca7 | ||
|
|
9bcbeaf1a5 | ||
| d0de121721 | |||
| 957e2db3d0 | |||
| fb0be5549d | |||
| 8a9132d557 | |||
| 04d10435c2 | |||
| 92543e10fa | |||
| cd427dcbf3 | |||
|
|
ddb5e0285b | ||
| 75610a4559 | |||
| b8f7a45f61 | |||
|
|
37d5ce719e | ||
| ef9053aa7e | |||
| 018fcb7afb | |||
| 5cc9a67152 | |||
| 54895f1d0a | |||
| 91dfecf882 | |||
| 2f09e227d1 | |||
| 5d0463836e | |||
|
|
fb6499cb35 | ||
| 0bbfb58e67 | |||
|
|
fcd780a7d3 | ||
| 4f4d1e7580 | |||
| 6e2a8e0a12 | |||
| 817b4b36c7 | |||
|
|
0ba89cbb9e | ||
| 4e3e7864be | |||
| 6f51eaa298 | |||
| 39bd87e868 | |||
| 18443aab60 | |||
| b88ea7aa3c | |||
| 6a6f6e6062 | |||
| 5839dbcd6b | |||
| b46f45569e | |||
| 5cdc5af8cd | |||
| 4f38a9f3ff | |||
| 9c83695187 | |||
| f9be534101 | |||
| 58ab48ac15 | |||
| f34e93192d | |||
| 31106f2b13 | |||
| 468cc54cf5 | |||
| 728a38c55c | |||
|
|
b2b23b0e3b | ||
| 167dd11548 | |||
| 03fdbf2604 | |||
| edbdec8370 | |||
| 8f1c9f9471 | |||
| d35d16943d | |||
| 3ac4270dee | |||
| 90777e08ff | |||
| b47d1262ac | |||
| e28081a7c5 | |||
| 5f7d6c1488 | |||
| 59385e11be | |||
|
|
a668d88791 | ||
| e62bd7c5a6 | |||
| 1ad3d2f187 | |||
| bf06af14ed | |||
|
|
805c96f260 | ||
| 8ccba2f6d5 | |||
| 2e9f01d3ee | |||
| d1dcdd4f25 | |||
| 60958d1403 | |||
| 5530b14f36 | |||
| 64c9942ee1 | |||
| bc1437dac4 | |||
| 1d9576e838 | |||
| 8b275a5df4 | |||
| 40d9334dcf | |||
| 98c0242819 | |||
| a2fb03f9b8 | |||
| 98815ac5c3 | |||
| 073dd20c96 | |||
| 23af9085a9 | |||
| bcaec6b6ee | |||
| 4063bea922 | |||
| 9f864b3407 | |||
| 01ac598e39 | |||
| 77a0d3af36 | |||
| 8d34bbea47 | |||
| be18e98001 | |||
| 754e0f98f9 | |||
| 07f893b6a1 | |||
|
|
c1497ec44e | ||
| 55d4784fe2 | |||
| f26e8bc520 | |||
| 7b5f1a4253 | |||
| 1058fb7e1f | |||
| 0f1b95cdfc | |||
| 8ea16fcd85 | |||
| b4b9496980 | |||
| 8dfd26361f | |||
| cb5bd32b86 | |||
| 2584330d40 | |||
| ebe6333ac9 | |||
| 534892280e | |||
| df05cf69cf | |||
| 2f782cf6a2 | |||
| 6f483c17e6 | |||
| e64610a2dc | |||
|
|
7673a16779 | ||
| d73fe4b29c | |||
| 2c61be084b | |||
| fe5f8d1997 | |||
| 2653291ded | |||
| 417f91a2e8 | |||
|
|
08625e85bb | ||
| c554c19a12 | |||
| d7d08f2f12 | |||
|
|
bafe45a2f9 | ||
| a11d24723d | |||
| 3b858476f1 | |||
| 5560b68ac1 | |||
| 8bfd381363 | |||
| c6cfe2a22c | |||
| 4ed274dedc | |||
|
|
283c815de7 | ||
| c72c1bc889 | |||
| 6e61a535ca | |||
| c1f69ea1c7 | |||
| 984599c56f | |||
| 038bb31ec3 | |||
| 1ac541d16e | |||
| ecaa0e99c5 | |||
| 666ebeef0d | |||
| b39a936fa4 | |||
| d434dce568 | |||
| 6acb390133 | |||
| 3c6f3c50c4 | |||
| c52e707421 | |||
| 5fb998c221 | |||
| a0989ea833 | |||
| 5e565c06a0 | |||
| 69b96aa5fa | |||
| bce9de1494 | |||
| 3de7928be7 | |||
| 41630ef01a | |||
| bf29cb1a4d | |||
| 8f79a4ab86 | |||
| 0ad933c5ad | |||
| 5ed9470d83 | |||
| cac468f454 | |||
| f0f9dcf4de | |||
| 3e0e84a4f6 | |||
| ef8080b420 | |||
| b5fab1fb10 | |||
| c3f019d4ff | |||
| 701f172276 | |||
| 9e8c736cb4 | |||
| c8589d750c | |||
| 5bb20a01de | |||
| 6e4bf9da3e | |||
| c21ddb5c63 | |||
| 431295cec9 | |||
| 180cb84b2c | |||
| 66b5f322d9 | |||
| f00dc248bd | |||
| dbba5db9f7 | |||
| 3a8b799df6 | |||
| 0b99a4ae74 | |||
| 99b77d347a | |||
| 8235a88ecc | |||
| 564ba30f91 | |||
| aa8535d513 | |||
| a02ef2575b | |||
| a0be16c0fa | |||
| 548d87e339 | |||
| c704aa0817 | |||
| c6a060fde5 | |||
| 762e88cc44 | |||
| b7f671d1d5 | |||
| 48706d01f9 | |||
| fb53354956 | |||
| 5995147054 | |||
| fed9f458d1 | |||
| 7987564401 | |||
| 90ae21fb8b | |||
| 56a78d174d | |||
| 37da947b90 | |||
| f23e272027 | |||
| 10afc0a2d5 | |||
| f59c8ea533 | |||
| 4153ea7431 | |||
| 2c73577798 | |||
| 0b5c53ef7e | |||
| 0fca0d54f8 | |||
| 3da0c5b2b6 | |||
| d8519bc1c3 | |||
| 33a4a799c5 | |||
| 358a021ba6 | |||
| 6dba9c25b6 | |||
|
|
1f724c802b | ||
|
|
67e3dd0035 | ||
|
|
de657829fd | ||
| a2c27134a8 | |||
| 66c60ec10e | |||
| 867b39d8e1 | |||
| 1c8997d44a | |||
| 88b5112e30 | |||
| 3874b6a25a | |||
|
|
8ba6587c57 | ||
| 3ebe36f785 | |||
|
|
34ed0b96d3 | ||
| ab353d2268 | |||
|
|
fd6f013480 | ||
| 4d0b7bc127 | |||
| ce917b227f | |||
| dec1bb04e8 | |||
|
|
a1093c17bc | ||
| 8110fbf701 | |||
| cf502ebf05 | |||
| 7c7837b967 | |||
| 3bfc1cacb0 | |||
| cd2ea26608 | |||
| dcf8159d1c | |||
|
|
1bdab80b5d | ||
| 241c4dc600 | |||
| 18e93d2b24 | |||
| 084cf6b761 | |||
| 47ddf5f4e6 | |||
| 325321f5d4 | |||
| ecbeca3d35 | |||
| 862b434c72 | |||
| 6376ed3b7b | |||
| 42e40d1718 | |||
| 1c1cc12058 | |||
| e648b88a74 | |||
|
|
6fea46f7c8 | ||
| a06e5a6e55 | |||
| 85bf429a08 | |||
| dd6ba45cdd | |||
| ab967efa64 | |||
| 369505e1a0 | |||
| 86d858a0fb | |||
| d1f5158276 | |||
|
|
db83b0f78c | ||
| 24fcc5a0c6 | |||
|
|
9cb3d474aa | ||
| 0adfebfad6 | |||
| 66c4225aa3 | |||
| 6ddef9ffda | |||
| 27a1b22e6f | |||
| f9c18cb225 | |||
| b97733328a | |||
|
|
2fe17aefd9 | ||
| acddca54cd | |||
| 7d80fb31ce | |||
|
|
8ac1d78053 | ||
| 468d6d7752 | |||
| f49d789411 | |||
| 1f56115fbb | |||
| 20d277102e | |||
| 47b4036f09 | |||
|
|
ee9e154fec | ||
| 85256b4fb6 | |||
|
|
16be671f1d | ||
| af880115ab | |||
| e64ac8f616 | |||
| a7d4206b77 | |||
| 5a80690404 | |||
| 9b0d71d4ac | |||
| 729f77d8db | |||
| e89f96aa2d | |||
|
|
f05f81cace | ||
| f4883ee708 | |||
| 043fb739d9 | |||
| b19aa8702b | |||
| f4e0744879 | |||
|
|
d7c822f86d | ||
| c8f8e217e5 | |||
|
|
bfec7f1fe8 | ||
| 21fce1f618 | |||
| b5987b57ad | |||
| e8c1cb08a7 | |||
| 0de3a16898 | |||
| 278bd7f38e | |||
| 4a3b9db324 | |||
| b3e8e96875 | |||
| f5e50c6d76 | |||
| c551c2ee1a | |||
| 3fb8406e7b | |||
| 50d12407c9 | |||
| 17931bb0ea | |||
| 6d30940920 | |||
| 3a4c985baf | |||
|
|
960d8b6cb8 | ||
| 9b7e9d4ffa | |||
| c1a646c0d7 | |||
| 48ef0e7a65 | |||
| 8dff306a91 | |||
| a935acbc95 | |||
| b1f9c99eb1 | |||
| 0851f749c3 | |||
| 3a7d9f84c7 | |||
| b43b35ccab | |||
| 5fc6eacc53 | |||
| bd782362e8 | |||
| 32151a176f | |||
| f7c88d2086 | |||
| 1eb76f2b90 | |||
| 226b3e049d | |||
|
|
7f0e9851cd | ||
| 15df77940e | |||
| 9781516552 | |||
| a23264a542 | |||
| 0e7845985a | |||
| 93efe45347 | |||
| a46f824e1e | |||
|
|
0de9f036bc | ||
| 275f2515d6 | |||
| b4604928ec | |||
| 5a79b4021a | |||
| 280fd38039 | |||
|
|
b032364616 | ||
| a430aaee97 | |||
| f4ee2ca3b9 | |||
| 92b9212b31 | |||
| 73656e60ff | |||
| 401329d80c | |||
|
|
6131970724 | ||
| 85dcc4b78a | |||
| 34a1bf7e28 | |||
|
|
afdccefa98 | ||
| e1fd0a5535 | |||
| 15d2cd7342 | |||
|
|
f5fda8b71f | ||
| 8586dbbe8b | |||
| c508c71f60 | |||
|
|
33cabc8e2d | ||
| e244d477e2 | |||
| c2f81e182a | |||
| d7c68cfaa9 | |||
| b1aa5b8183 | |||
| 877b1dacf8 | |||
| 8eef664e50 | |||
| f0581f5038 | |||
| 0b584d48b8 | |||
| 9199875c51 | |||
| e062cc4870 | |||
| 2ba4d87b75 | |||
| 4b4c0df77d | |||
| b5e4f32fa5 | |||
| 27ca45b088 | |||
| 345e38a560 | |||
| 176e4c8b3e | |||
| 81083dd30c | |||
| 9a5a09438e | |||
| f17447b2f1 | |||
| 930eb14a8a | |||
| 08951f8c2e | |||
| c74bb01c3c | |||
| 34b9b189d4 | |||
| 960aca266e | |||
| 840e04dc55 | |||
| defaf3f970 | |||
| b8b2349b6c | |||
| c3c4671f76 | |||
| d9811d9d50 | |||
| da515f8595 | |||
| 467a8af504 | |||
| ed16571d44 | |||
| 6d06f1fbce | |||
| 9a59b2ad3f | |||
| 1dc03ca5ae | |||
| 260de6eae5 | |||
| 2458377010 | |||
| d4a7f2bbc8 | |||
| 1b8b009b58 | |||
| a6623058b5 | |||
| aa385a5e30 | |||
| 67e9113b48 | |||
| 9667279bf6 | |||
| 84e94c1122 | |||
| 12fd04b296 | |||
| 2e5ab39fee | |||
| b975fedfee | |||
| 0670bca67d | |||
| 9a976fdd3c | |||
| 91d189cf00 | |||
| b97e0df6c5 | |||
| e966df976f | |||
| 530347c847 | |||
| fc5acaf0be | |||
| affea597bf | |||
| 732ad976bf | |||
| b0133162f0 | |||
| 0fa462c385 | |||
| f7d7ee6b43 | |||
| b769b79b9a | |||
| 320001b3a3 | |||
| 5c53bb3853 | |||
| 584ac773b3 | |||
| be6e034153 | |||
| b85bb698d0 | |||
| ecab1a45b9 | |||
| d3a7f7a732 | |||
| 30b106c5c2 | |||
| 5b8148b83d | |||
| a973696501 | |||
| 5bb5ad7ba8 | |||
| 72188223ae | |||
| 4fbd037af1 | |||
| 9789ab6e37 | |||
| f766039139 | |||
| 9aad09be9a | |||
|
|
ea07a3e3ce | ||
| 4e9ea630d7 | |||
| c4aeecdf3a | |||
| ab36c69729 | |||
| ade7d1f735 | |||
| 5c7d62c59b | |||
| 772de4e67b | |||
|
|
2505f3904d | ||
| ea06ede236 | |||
| d9132fad5a | |||
| b20148b93f | |||
| 41dde8a164 | |||
| c83c4b052b | |||
| 3dad8258df | |||
| 60f358510c | |||
|
|
46e5e5fbdc | ||
| 6561a63956 | |||
| 9a390616ad | |||
| 815ede7e1a | |||
| 4960657be7 | |||
|
|
0b18aa4906 | ||
| f040827bc8 | |||
|
|
876c3b2ecd | ||
| 52c30a1e0e | |||
| f2d93564a5 | |||
| f19213d6f7 | |||
| 0e378ea982 | |||
| 51d8d48218 | |||
| 0f560a3594 | |||
|
|
07ea3e39b9 | ||
| 50851ad62a | |||
| d45a956520 | |||
| 7e9e95b7d2 | |||
| c02255dd08 | |||
| c465e15e96 | |||
|
|
1da99434b3 | ||
| 368db8d94c | |||
| a05c8cc3ea | |||
| 7d7ef2788e | |||
| 93844b0e20 | |||
|
|
45b8e7cc28 | ||
| 59aea26e72 | |||
|
|
a3a3a737ed | ||
| 69c048cfa6 | |||
| 045543f27a | |||
|
|
3369821875 | ||
| e880edd680 | |||
| c5d516949d | |||
| cd695e5024 | |||
| f97dd9747f | |||
| 67ed60731b | |||
| d2c756a08a | |||
| 976d8a16e4 | |||
| 55f7a181cd | |||
| 55d0ebad03 | |||
|
|
5db9a064ca | ||
| 2a5fe72a34 | |||
| fd6d36b6d9 | |||
|
|
2aafc85fae | ||
| 3905f4ed28 | |||
|
|
c6570ac499 | ||
| 9241be1f5f | |||
| 4fbd695f4e | |||
| 05b72a0c7e | |||
| 09ab982a4a | |||
| 70b2f20ca8 | |||
| 4f12a096e7 | |||
| ea5df706a8 | |||
| d92dae066d | |||
|
|
40345f6cb9 | ||
| 8aaf0eb99e | |||
| cd1885477d | |||
| 28bb578170 | |||
| 2965df19cb | |||
| 36929f3ab4 | |||
| e1f7ea8f18 | |||
| 7e4b603582 | |||
| f82eaee49e | |||
| e1fe3d959a | |||
| 86d3dd2c88 | |||
| 127e3ae325 | |||
| 16c8c3c873 | |||
| 58fbd9c581 | |||
| 91f75c60c6 | |||
| dee60a5a34 | |||
| 013d53c346 | |||
| d42e28f713 | |||
| dc69407d76 | |||
| ab228bb8d4 | |||
| 772ba40108 | |||
| f143e2ea49 | |||
| dd32fc58eb | |||
| 30f499f3cd | |||
| c7325645be | |||
| dfca75f19a | |||
| 70005b3b82 | |||
| e51e2dfe27 | |||
| 57ddcb47f2 | |||
| 0ec2764369 | |||
| 2215a7895e | |||
| bac86bd8e0 | |||
| 3f9540a415 | |||
| e5be78e832 | |||
| 8a03d1c704 | |||
| 1655fd33eb | |||
| af93e5d00e | |||
| f2f1f1c879 | |||
| fd7179c3f5 | |||
| bbf4200b42 | |||
| e50877ba5d | |||
| 61cf4eb964 | |||
| 87ca7fdc8d | |||
| 7745bbe60c | |||
| 949ebf83e3 | |||
| 6744282bf1 | |||
| 5b374b0410 | |||
| efaa97b9bd | |||
| 87c5dd5fa7 | |||
| 023a5b75b4 | |||
| 7e6a5de0f1 | |||
| 348500a288 | |||
| 27be26d12d | |||
| 4932eab08a | |||
| 341e2ed7ff | |||
| 7af6dac964 | |||
| f94d23723c | |||
| 170005fb79 | |||
| 5ebe3fd95c | |||
| d49d2247c9 | |||
| 593c90f605 | |||
| fa41140aed | |||
| e246509ebf | |||
| 81db94ac8a | |||
| de29c766b3 | |||
| 3e96586c8e | |||
| 7eac6cadb0 | |||
| 414ba32541 | |||
| 8d4db5b574 | |||
| 422b904e00 | |||
| 5ce627ee0b | |||
| 3ddb6f9abc | |||
| 583f2a17b5 | |||
| e628f1f6d5 | |||
| 89f21867a5 | |||
| 4dd557feee | |||
| cc315db34b | |||
| 086692e668 | |||
| e7528205f6 | |||
| af735e196e | |||
| 7dd59c3346 | |||
| 1c6ade5b77 | |||
| f714da7662 | |||
| 907ae1db04 | |||
| 691dd4a7bd | |||
| b1f531446a | |||
| c5aeaf1452 | |||
| c27914c957 | |||
| 6424e1e416 | |||
| 0fc0e12c08 | |||
| dfc3e79709 | |||
| f0b515ff5c | |||
| 2f39e3277f | |||
| 6576794456 | |||
| bdc8223c6c | |||
| d68cf3b557 | |||
| 11787d290b | |||
| 047a14dcbc | |||
| f9d441a97f | |||
|
|
9cee124afd | ||
| 43cf348b72 | |||
| 86eb344492 | |||
| 9030628090 | |||
| f6d39c7be3 | |||
| a9931b114e | |||
| 87d9ce4486 | |||
| b61256221a | |||
| e9f43cb6f4 | |||
| be79a9b4f9 | |||
| a3ff2fbefe | |||
| 8d032cd867 | |||
| 71392cd54f | |||
| 92f14b1c12 | |||
| 7e9674c7a7 | |||
| ccf0b070b0 | |||
| a9536cccd1 | |||
| 5f26bdcefb | |||
| 015c901047 | |||
| 201ac15aea | |||
| 04725db699 | |||
| 9b72c785b5 | |||
| ca20d585da | |||
| 48bcb80dd7 | |||
| e4bf1f0d53 | |||
| 769764ad17 | |||
|
|
cbf732d072 | ||
| 9a60ded31c | |||
| f7fa4a6dc5 | |||
|
|
d2f653decb | ||
| 7471265010 | |||
|
|
60e5dc0e57 | ||
| 9aacf8ca41 | |||
| b91b4cd5b2 | |||
| e5843a4f40 | |||
| 97259b5a14 | |||
| e0e56faa5d | |||
| f837fb31fc | |||
| d1eef0a8c5 | |||
| 4a657451ed | |||
|
|
68a5af4bef | ||
|
|
d0018d4de8 | ||
|
|
38dd92754a | ||
| 1f2d68ffd2 | |||
| f18d0c998f | |||
| 374e2e6b91 | |||
| b2e439c94e | |||
| 875d616cbf | |||
| 837e229d11 | |||
| 8fd038cd11 | |||
| ca8b44dd8d | |||
| e8f767a56b | |||
| b531c48083 | |||
| bbab1c13a2 | |||
| 4334b48ab1 | |||
| ab1ca7af80 | |||
| ac54b65c87 | |||
| 35db1ff6b2 | |||
|
|
126bdcca73 | ||
| 5cfcb71759 | |||
| e6a54b08f1 | |||
|
|
434ac7b577 | ||
| 7e3b3fae1c | |||
| 4c52629ba7 | |||
| 135b9b6e09 | |||
| 1647065b04 | |||
| bf6b39707a | |||
| 4a4450260f | |||
|
|
4701234c94 | ||
| 8c4c4a25cb | |||
| 7be14d2d8c | |||
| d8b387fbad | |||
| 6087377e8e | |||
| e5797ef6a8 | |||
| 6817cafbfa | |||
| bcdfeb03a8 | |||
|
|
8854328697 | ||
| 0d83b2a59f | |||
| 691b4b5e59 | |||
| c4bffbda11 | |||
| ef7da60ec0 | |||
| 95275c1b39 | |||
| 944714e4ba | |||
| aa7962c959 | |||
| 97c13d5f44 | |||
| 004faffc4c | |||
| eeef2abaf3 | |||
| 3a70c388cc | |||
|
|
b81044106e | ||
| a5230d1da4 | |||
| c1223aaee4 | |||
| 7175315abc | |||
| 00f4a722e1 | |||
| 664d847a71 | |||
| 21dedb7748 | |||
| 88a0c5dc57 | |||
| 4677c39b97 | |||
| 00b145d673 | |||
| f04fff354a | |||
| ca4264b838 | |||
| 0e4047c36f | |||
| 1f3bb11a5d | |||
|
|
f7a103ff89 | ||
| 555c326b0c | |||
| 4f74a770af | |||
| b26b14a0f2 | |||
|
|
7d0e8c96ee | ||
| a14439f128 | |||
|
|
4de8aba54a | ||
| 3814dda46e | |||
| 923716dd08 | |||
| fa0f86c8a0 | |||
| efb8d29f78 | |||
| 22c71009df | |||
| 4e48bae0ad | |||
|
|
ce55c5c011 | ||
| 7daf02e94c | |||
| e4fdf60b8a | |||
| b9ae73d865 | |||
|
|
e9583db6a7 | ||
| 89eda3c702 | |||
|
|
245e0e038b | ||
| 010579aa1b | |||
| 24e8337b41 | |||
| d5f6224236 | |||
| 9ec8fe921d | |||
| 6fd1d294e5 | |||
| d48c7271c0 | |||
| d155c675f2 | |||
| 3a56faf123 | |||
|
|
bc74608271 | ||
| 484a615981 | |||
| efe92ba356 | |||
| 25f4a815fd | |||
| 74f774bc3b | |||
|
|
e871beda8e | ||
| ddc3011dd9 | |||
| 9bf9322994 | |||
| 325d1567a7 | |||
| e3ed7fde45 | |||
| e8810cc2a7 | |||
| bf101519d8 | |||
| 58e1302b75 | |||
| 750a070fbb | |||
| e8b3131d2e | |||
| c00cc887e4 | |||
| 6fb8a5bbe5 | |||
| 952fcc2a1e | |||
| d070592491 | |||
| 4fe64ddcb1 | |||
| a10f10aa61 | |||
| db2ad358cf | |||
| 124b19525f | |||
| a64b9e1ab2 | |||
| a936daf95b | |||
| dba64f9ea7 | |||
| 2e6555a8cb | |||
| 52003c2d58 | |||
| da028d6665 | |||
| 01a7746ae9 | |||
| 827fe3578f | |||
| d4cf1d9aca | |||
| c2a4be71c4 | |||
| 404fd0dfdd | |||
| 9ffd7c91b7 | |||
| fc20046aaf | |||
| 934ccba81f | |||
| 4db502e0f1 | |||
| dd9dbd8321 | |||
| 47102edfef | |||
| 9b86a6ee33 | |||
| 9cb4dbc79d | |||
| 4789e40574 | |||
| 39109ae40b | |||
| b6ffe579e0 | |||
| d2b94a9a0e | |||
| 809022b388 | |||
| 5c2c614a65 | |||
| a0934d9aaf | |||
| 19ac2d9a0a | |||
| ae5870556a | |||
| 87c42c810e | |||
| d7d36b44b2 | |||
| 9bb44d3ae5 | |||
| f216c03a7b | |||
| d091fc48f6 | |||
| 0eeae3a7ff | |||
| 22c26c97f7 | |||
|
|
86c6416090 | ||
|
|
123c07efe0 | ||
| d64f01bc54 | |||
| 415d7988b9 | |||
| f846a1ac83 | |||
| e95269d539 | |||
| 4161a3afc1 | |||
| f842ed7b3d | |||
| 66c8bb7c78 | |||
| cd499edd6c | |||
| 51d4e187b6 | |||
| a75f648b71 | |||
| 66a253b4e9 | |||
| 076f8e0365 | |||
| 37c850cf6c | |||
| dc93213811 | |||
| 56d8c6c236 | |||
| 0a0b114baf | |||
| 7c444b3c53 | |||
| f4c72266ea | |||
| f45e6c885e | |||
| c4646a7de6 | |||
| 37be254f25 | |||
| 21d6244c3c | |||
| d141e67f52 | |||
| d2c4a79791 | |||
| 240058005f | |||
| 6d31f3791b | |||
| 7fba05d0d5 | |||
|
|
44ead9f469 | ||
| 7ef3971fe1 | |||
| 216a3d5d60 | |||
| ffb6844a41 | |||
| 575723b860 | |||
| 50a000f1c7 | |||
| 476c73fed3 | |||
| 471e655eb3 | |||
| 934a436065 | |||
| ba1508a82a | |||
| 0f67d14153 | |||
| 832d8f44f1 | |||
| 33343e1b1c | |||
|
|
28694cd2b7 | ||
| c65f66decc | |||
| 73a4743e88 | |||
| 2bcbf6365e | |||
|
|
43301108ba | ||
| 7898d26461 | |||
| 13b3158bd6 | |||
| f4f327f25d | |||
| 45ff09dadb | |||
|
|
bec78bc44c | ||
| 16e35935f9 | |||
| d6603488b6 | |||
| 717c2d2ece | |||
|
|
e704af5494 | ||
| 5399d26c94 | |||
| a32e29a378 | |||
| a62c474f99 | |||
|
|
c507bb0238 | ||
| 30902eeac8 | |||
| 9442d58f4c | |||
|
|
55a14cfedc | ||
| 3d0ea980c8 | |||
| c9f7696519 | |||
|
|
623d435758 | ||
| 05f06f09d4 | |||
| e19402c80c | |||
| cb87c5ba71 | |||
| 657eca08bf | |||
| 4c58c86f84 | |||
| 9b73a057fa | |||
| 3c9bf95728 | |||
| 2f48af5afe | |||
|
|
f7f5ba15ff | ||
| 5fd014f023 | |||
|
|
caf3f4a783 | ||
| b264bf6f58 | |||
| 06b07cf9a0 | |||
| e5804b22cd | |||
|
|
85f292e1d2 | ||
| 057f05a88a | |||
| a700267ee6 | |||
| 331e33ec70 | |||
| c9dc118af6 | |||
| e1e24a3350 | |||
|
|
339dfd1c0b | ||
|
|
5bcc81c00a | ||
| e7212ed506 | |||
|
|
c705c282d1 | ||
|
|
20ea1f8209 | ||
| fa73171d7d | |||
| a27675a783 | |||
| 2ddae1f0b9 | |||
| 415acc7780 | |||
|
|
7590cf824f | ||
| 3ee88ddfb4 | |||
| 4d5f90e0ec | |||
| 694a65e9de | |||
| 7e33103758 | |||
| e04f26223e | |||
| cd76b72d3c | |||
| 57372fe922 | |||
| 82ca7fdcdc | |||
| c6d9fb89ba | |||
| 2ecd88586b | |||
| d8779bed14 | |||
| 7ad988bceb | |||
| 04fc897a60 | |||
| 7922ba15ce | |||
| fae27f7e19 | |||
| 7ba2f29efc | |||
| 0e8439d52b | |||
| 17c60b6b4e | |||
| 4ec54b2dbc | |||
| 409b1fa00d | |||
| ad5fc52d66 | |||
| 4b4d21a0de | |||
| 3642a3fec5 | |||
| cac097ea66 | |||
| 4a2b0d9d9f | |||
|
|
6e53e7a23b | ||
| 1e1e86dc16 | |||
| e7777877fa | |||
| 3a685eb316 | |||
| 344509ec28 | |||
| b70a2951bd | |||
| 63282f42b2 | |||
|
|
03786817ed | ||
| 680bffa9e8 | |||
| 6f07ec52ad | |||
| daecb76f8e | |||
| 7f653d900d | |||
| 4cd3e682b9 | |||
| 143cceeade | |||
| fb8ca67ac1 | |||
| 80fc1c4112 | |||
| eecdeb0af4 | |||
| 5e9e11694d | |||
| bb41f89d44 | |||
| 4158f25b35 | |||
| 3694cd0694 | |||
| a5a06fbaab | |||
| a5ee38d4fd | |||
| eca95c36dd | |||
| 554b4047b9 | |||
| b1e8b4eb11 | |||
| b3f693556c | |||
| e2fd50d152 | |||
| f1cb9a824d | |||
| da0e718276 | |||
| 4519a4175b | |||
| 4fa375f4f4 | |||
| 3ca5fdb082 | |||
| 32ae726050 | |||
| 83950cbecb | |||
| 5b9f63bc91 | |||
| 7b93072093 | |||
| 90c312ea5c | |||
| 2bec38d85f | |||
| b8ab1780d3 | |||
| 997ad34151 | |||
| e960d4f00b | |||
| 52fe9e2c13 | |||
| e6b386dabf | |||
| f558095a0e | |||
| 014a604c86 | |||
| 5e7f2f0d5a | |||
| 11b52cb212 | |||
| aa7abab742 | |||
| f5ce6e47e2 | |||
| 05258e6fce | |||
| 7675ead837 | |||
| 6e80a19933 | |||
| 3a6cf05647 | |||
| 147cd32129 | |||
| 9789117759 | |||
| aeee5658d3 | |||
| 949ac2de98 | |||
| 5b4333dacf | |||
| 561cd7111e | |||
| 784ee84fb3 | |||
| b0cf548775 | |||
|
|
55c315eb98 | ||
| 9bcb41ab5d | |||
| 007b1bd21b | |||
| 9dae7988fd | |||
| 8877d7ea55 | |||
| f35a23a53d | |||
| 64484ef18b | |||
| 0d9381b528 | |||
| d57465d067 | |||
| 5039dac045 | |||
| 6073357bb6 | |||
| a00343d444 | |||
| a0b4681369 | |||
| ea340f3fc2 | |||
| db39483f97 | |||
| e575511849 | |||
| 9dc3030c18 | |||
| f990d7584e | |||
| 133d560e64 | |||
| b3ba764d89 | |||
|
|
3ff1124c30 | ||
| da927c8f7b | |||
| cb211d8ba1 | |||
|
|
9cbebbdd6a | ||
| 8684e21da3 | |||
|
|
3f9a90f758 | ||
|
|
661a58b549 | ||
|
|
a47b049dfd | ||
| 58b2918ea4 | |||
|
|
484b4cb280 | ||
| a77d512bac | |||
|
|
e295354aa1 | ||
| 7aeae1e998 | |||
| 9b98afd6cf | |||
| bad97971db | |||
| 6f8c122503 | |||
| 8b25aecb3a | |||
| 400846cab4 | |||
| 72708bdef0 | |||
|
|
23d817d1f3 | ||
| daf4086bc9 | |||
| ab2e9ca43e | |||
| 222d69218a | |||
| ae03da927e | |||
| 9cb311a6c9 | |||
| dfd665088e | |||
| 51afd70fff | |||
| 203abc1ff9 | |||
| a0487671d2 | |||
| 88e605fc5a | |||
| 130c390916 | |||
| 93389633ba | |||
| 53897cf779 | |||
| 9192ac2c81 | |||
| d54b8ab756 | |||
| 67210ce23b | |||
| a89f27abb9 | |||
| 37eccd8a58 | |||
| 452fd810ac | |||
|
|
cd91a79578 | ||
| 4c70fb9cfa | |||
| 62dcd672fe | |||
| fde40577ee | |||
|
|
963633328c | ||
| bddf214f91 | |||
| c7ef2eeec7 | |||
|
|
8b988a3f02 | ||
| 792337ed73 | |||
| d1e5f9d0a4 | |||
| d202746829 | |||
| 72724c6014 | |||
| 8cdbe8a491 | |||
|
|
4617161f3d | ||
| ffed4f56bf | |||
|
|
19f9390020 | ||
| 10a4401259 | |||
|
|
ed7ab76cd6 | ||
| ccfe1bac74 | |||
|
|
0c2071bc5d | ||
| 2d503ab05a | |||
| 0f204b48dd | |||
| ac586267c5 | |||
| 897619f126 | |||
| 5fef08797d | |||
| b43c3a7294 | |||
| 423b145b7f | |||
|
|
93333a4915 | ||
| b3708abcb6 | |||
|
|
f8c2580773 | ||
| 4609d5588d | |||
| cbeb44779f | |||
| 7c848aa372 | |||
| 015fca8145 | |||
| 54cc5fbad5 | |||
| 66f66dd408 | |||
|
|
24de9d5988 | ||
| 7dc5fdc88e | |||
|
|
2f912b4ddd | ||
| 0931cb9f9c | |||
| bc98a80de6 | |||
| 9e3ad237b5 | |||
|
|
40a6ce7aa7 | ||
| 85bedbf56b | |||
| 5575710d7e | |||
| df442f7fff | |||
| 89e665b369 | |||
| 42f845a7e2 | |||
| a226bc9fbb | |||
| b2f9935abb | |||
| d8a22ba5ee | |||
| be09821778 | |||
| 4f0f440f2c | |||
| 9cd46a7f9f | |||
| 66c875584f | |||
| f14d2fddc7 | |||
| fa2dda6ee4 | |||
| 73c8117a67 | |||
| 5f42e94da7 | |||
| c31a95dd4b | |||
| 19ec0ddcb9 | |||
| 5e24086476 | |||
| 72934000b0 | |||
| 8a240e2c9c | |||
| 9f081359b0 | |||
| 679a1f0d05 | |||
| 4533e87bef | |||
| 47a60b41a8 | |||
| b06100d0dc | |||
| e5591cc59e | |||
| 9831216ca4 | |||
| 57e36c5fdb | |||
| 35904bd79d | |||
| f16a2a7fec | |||
| 0b4b8a62aa | |||
| ca4fa30cdb | |||
| 90c024dfe5 | |||
| e634c44797 | |||
| 8eea4072f6 | |||
| a0886e1a3d | |||
| b93d77c907 | |||
| a148e27211 | |||
| 3cd9b52d2a | |||
| 37206916e4 | |||
| d36d2fa4b4 | |||
| 4591c9006b | |||
| 5b4ceb49df | |||
| ba937c1e77 | |||
| 2b77254322 | |||
| b27cb78f62 | |||
| 106af5b177 | |||
| 4b31160670 | |||
| 0593be9928 | |||
| 6602119ee7 | |||
| 2943d1f1b7 | |||
| 485ab6793c | |||
| 164a450784 | |||
| f12db7e55f | |||
| 23e000afb3 | |||
| 5c77dd91e7 | |||
| 1c4140daec | |||
| ad1035547d | |||
| e61be60f71 | |||
| 9d6db1e847 | |||
|
|
76a417fee7 | ||
| 9e836148bb | |||
| 8d32e404e7 | |||
| fb3b0f0ae6 | |||
| 6ad415fa5a | |||
| 79d2cc26b0 | |||
| df0ad48348 | |||
| b1024d1da1 | |||
| 5db1ad1535 | |||
| b45fef8b0b | |||
| 22d4a9a809 | |||
| 0478356ea5 | |||
| 45e35c4396 | |||
| eb5079a5ef | |||
| a62e01e02e | |||
| c58db08e5f | |||
| 6476fe1d2c | |||
| 8ea34d5736 | |||
| 4d4f43fa93 | |||
| 3e7b2ee6c1 | |||
| 0d16d3abf1 | |||
| 3923ea9dd6 | |||
| e1292620c7 | |||
| 4e2b2e1f54 | |||
| fcf75b652c | |||
| 9765b51969 | |||
| 0769183f61 | |||
| 845cc1a2ef | |||
| 16a95c12b4 | |||
| 0516aeb94f | |||
| 6b92ecebf9 | |||
| 9010c71051 | |||
| fcca331224 | |||
| e3a2f76f78 | |||
| 7e221454a3 | |||
| ebecffca03 | |||
| 42f99fd7a2 | |||
| b31ab0ae16 | |||
| bec041aa01 | |||
| 8ded811295 | |||
| f1c0b9f1ed | |||
| d32a371b3a | |||
| c0860cd275 | |||
| bfd125fc4f | |||
| a115ab28ba | |||
| d21b126401 | |||
| 410bf03682 | |||
| 0a9f3de5c3 | |||
| 0ecadb918b | |||
| e90b055f12 | |||
| 02877dc23c | |||
| e22439af4c | |||
| 298c777971 | |||
| 39cf12f51a | |||
| 276bc480cf | |||
| f0b2820034 | |||
| ba65103df4 | |||
| ae64b70f10 | |||
| 56ea2dd6f4 | |||
| 1633b9fca7 | |||
| cb7a79953f | |||
| 5c12d9d6a6 | |||
| 244ae40969 | |||
| e2b7fec286 | |||
| 4cc2bc16c0 | |||
| 24a4872058 | |||
| 91a9788706 | |||
| 2a6ef8853d | |||
| 56358508c8 | |||
| 976b94464e | |||
| d166a0ccfb | |||
| 7c5c389629 | |||
| f447b527eb | |||
| a251714989 | |||
| ea27f817e3 | |||
| b6fb47fc16 | |||
| fa540d516d | |||
| 4b068d2a52 | |||
| 50f5289e2e | |||
| 1ba20a4ea7 | |||
| e18067b2f0 | |||
| 025f19728f | |||
| 248496021a | |||
| d56a73f0f7 | |||
| a2b68ebce1 | |||
| 93b92f2b17 | |||
| f5219bf79b | |||
| cc1d99d9c9 | |||
| f7101db942 | |||
| 97ae9d17bf | |||
| a5624e7e3c | |||
| 0be42900c1 | |||
| 9722e1ca64 | |||
| 63a20506a0 | |||
| 9b334e416f | |||
| 7805956cdf | |||
| 04ffd0ad45 | |||
| f6e7055c2b | |||
| 77167655e8 | |||
|
|
24e2d0a3db | ||
| 29795074fe | |||
| e017675b4d | |||
| 74a9897deb | |||
| f593e77d35 | |||
| 8b9ab7f0b8 | |||
| 02951833cc | |||
| 08e300d065 | |||
| 776e4c2418 | |||
| cbe439815d | |||
| 5c43222e4f | |||
| a6fe7e2fe3 | |||
| 6f6523836d | |||
| 650a57350c | |||
| 736b34e834 | |||
| d29682fd19 | |||
| 59c9058426 | |||
| dbd32201c5 | |||
| 55158a67fc | |||
| 49dfe99a14 | |||
| 165672f9fd | |||
| 3bf03aa0b2 | |||
| 8ccbe8e7a6 | |||
| 1d54f90c95 | |||
| 000de62093 | |||
| 7ed82a796a | |||
| 4ad7e4c3f3 | |||
| e1c17d8926 | |||
| 47cc40d0dd | |||
| 1a66d91885 | |||
| 01fbe11831 | |||
| d834965d79 | |||
| b6eeb79500 | |||
| 460d6c4e02 | |||
| c6089bd480 | |||
| 3d18d4bc6f | |||
| 61954b820f | |||
| dee67c6ea2 | |||
| 2fdaf1d482 | |||
| 8af986a4c5 | |||
| c3a13b66e6 | |||
|
|
78111fd408 | ||
| efeaa3f0b5 | |||
| aed5e1aa63 | |||
| 44211065a0 | |||
| 364d38cc05 | |||
| b38bfd7ada | |||
| f05efd1693 | |||
| c64b03135c | |||
| 860a50f025 | |||
| 5e9706ca23 | |||
| d3d23fe7b9 | |||
| af2aa2e083 | |||
| bb52b17a12 | |||
| 17c68cb9b7 | |||
| 5c3fe16e9c | |||
| 81ce4e3445 | |||
| 9aaa5174bb | |||
| 8d9c89d281 | |||
|
|
62fa42f1ba | ||
| 502cb1595f | |||
| c1e358e9fd | |||
| 7600ef1b30 | |||
| b9057da526 | |||
| 99e143b686 | |||
|
|
0414f1f4de | ||
| 80f651d17a | |||
| 1a373eb876 | |||
| 5882b6b58e | |||
| f876e56a2d | |||
| ac98ec92e4 | |||
| 6cf6445c01 | |||
| 062a6def14 | |||
| 282a5451f8 | |||
| b410f782e6 | |||
| f4d997957c | |||
| 5551c7d341 | |||
| 642e028fca | |||
| ffe5cd21e5 | |||
| 93679924f4 | |||
| 212e3c1066 | |||
| ebd4baa6ff | |||
| 6d846144dc | |||
| e9767ee6c7 | |||
| b4a77f25f9 | |||
| 08098f48a0 | |||
| 478ebe95f0 | |||
| bc48b85478 | |||
| a5f7610230 | |||
| 98157bbc8b | |||
| 4e0792b139 | |||
| a171970c96 | |||
| 38490fd462 | |||
| b9726cc23f | |||
| e047696fba | |||
| bea5a0896a | |||
| c15ddb64e8 | |||
| e4e3cbd68e | |||
| fd8b27d3f1 | |||
| 497a1632c0 | |||
| 5e991a9708 | |||
| 0e49874d38 | |||
| a93b99bca7 | |||
| 4d541c175c | |||
| 588de95004 | |||
| 8850d73ea3 | |||
| feca7c7d7c | |||
| 1507368ea7 | |||
| ce74ec97fb | |||
| 1fd5e2c3f6 | |||
| 79e23fe0c7 | |||
| ab0c62c73a | |||
| e72de994ad | |||
| 364a84cc6d | |||
| 40072c6423 | |||
| 2d56f71588 | |||
| d7a8b87473 | |||
| 7c8c2c655c | |||
| 8ea870b2a7 | |||
| 7e1317a400 | |||
| 6569a4af3b | |||
| 31ee85a523 | |||
| acf6384318 | |||
| 539426d585 | |||
| f8401414d3 | |||
| e3cbdb9420 | |||
| 823126dad6 | |||
| 6d3c8d6679 | |||
| da2ee9c6f4 | |||
| 0b4299d6c3 | |||
| 0830419658 | |||
| 85bfcd7447 | |||
| 729b7e0bcc | |||
| 4dbaf5d0f8 | |||
|
|
cbad208be6 | ||
| f7992d8eae | |||
| 8c7c344ea9 | |||
|
|
2a52aa61c1 | ||
| 8d06bd94b8 | |||
| 8f53747515 | |||
| d7a997052d | |||
| 324e98b639 | |||
| 7aebb728f8 | |||
| c386a2a57a | |||
| 6e5c4917d9 | |||
| d5d9ae51c5 | |||
| ca39f0da3a | |||
| 41cdbb43fc | |||
| 6f3897886e | |||
| 6b1bfdfeca | |||
| a07a37f256 | |||
| 896512cf3e | |||
| 60602e68f5 | |||
| 7066eb2632 | |||
| ddfdebced2 | |||
| 4913cc9ed6 | |||
| 6427bb794d | |||
| 77bc7c85f8 | |||
| 54a90a2e99 | |||
| e4a9c32caa | |||
| 13b9711d4a | |||
| af15d6a592 | |||
| 132580ea6c | |||
| 262ab0ab73 | |||
| 1e19d70fa1 | |||
| 48b8d530ca | |||
| 1f35c5b8fa | |||
| 574b87f5b5 | |||
| 88be69ffc7 | |||
|
|
87e49e3244 | ||
| 4a392126cd | |||
| c0489ebb44 | |||
| ed65f63a37 | |||
|
|
2b4ff60de0 | ||
| 8069bf51ab | |||
| 369b53e2fd | |||
| 1cf0c1636b | |||
| 060fe39331 | |||
| 8bec4058e2 | |||
|
|
61d30d6f77 | ||
| 7daae8a129 | |||
|
|
ab2c9e450e | ||
| 24c269bf4c | |||
| 8f0c8fd800 | |||
|
|
a0e29a2bc2 | ||
| 94cab2e71a | |||
| 266b9fa5c2 | |||
|
|
31de8edc4a | ||
| 1e59eda9a9 | |||
|
|
36c3fcb395 | ||
| ea8f0523d5 | |||
| 71625b7c0e | |||
| 408a62c755 | |||
|
|
da4a61b716 | ||
| 996c4474ec | |||
| cdc05d8d98 | |||
| 52892ef5df | |||
| ce0340689b | |||
| 58cdb0db95 | |||
| c2c49d09e9 | |||
| c9a2997cea | |||
| 94aba71ae4 | |||
| b80c7bdc23 | |||
| 279231c08c | |||
| ef47b839ad | |||
| ffc8ae7d04 | |||
| 7d8269832f | |||
| 6ba516f66e | |||
| 53d4fcbd6d | |||
| 237ef10545 | |||
| 23650aa800 | |||
| 9998691524 | |||
| bd61a953ad | |||
| f53379829c | |||
|
|
04a4faba22 | ||
| 65d4369306 | |||
| 6ab650d498 | |||
|
|
2a06c108ac | ||
| 3bd31c55fd | |||
|
|
ea2fb6d4cd | ||
| d4dccc8016 | |||
| 3cdc78af1f | |||
| 7784f0c929 | |||
| a399d3d04a | |||
| d261dd8fb4 | |||
| 273f475732 | |||
|
|
35cce62b9b | ||
| 7b5d2820ca | |||
| 46374cfb2d | |||
| 020bfb2abc | |||
| d3ab317b34 | |||
| da3dd52484 | |||
| 9514cd9cce | |||
| 654a66062a | |||
| 5ac5b13f9d | |||
|
|
7f39bf5c6e | ||
| 0e07a10dcf | |||
|
|
8cbedd00b8 | ||
| d9d3d3fdb4 | |||
| 364c142f0a | |||
| 92c0177c44 | |||
| 6bdca8c37e | |||
| fcb51e727a | |||
| 4523a10898 | |||
| 4e155c8039 | |||
| 8b9f861f30 | |||
|
|
3595ae6066 | ||
| 351d4a0ad6 | |||
| c6e6771839 | |||
| fb51e0fcd1 | |||
| 4fbf9c345d | |||
| f3557e9acf | |||
| cb7367264b | |||
| 2fb722edbf | |||
| fce1608403 | |||
| c6117cb599 | |||
| c68e056267 | |||
| 4c9924580a | |||
| 058b3b8d62 | |||
| 1eb29acf7c | |||
| 48ac052de8 | |||
| 4863ff2a19 | |||
| 0b0fd8ea67 | |||
|
|
b8f4ed2767 | ||
| 732eba7d7d | |||
|
|
8b676f3c1b | ||
| 960ca4deca | |||
|
|
63ecec4edb | ||
| c6f04aa26f | |||
| 8f29df66d5 | |||
| 2d9e62aedb | |||
| eba511add2 | |||
|
|
e9e38d5224 | ||
| b131ba8b93 | |||
|
|
a12046fe93 | ||
| 769f065bdf | |||
| 81f9ee5eca | |||
| 42fb44dd15 | |||
| 600bb8b516 | |||
|
|
8831d91cbe | ||
| d7d734565c | |||
| 791e9bf156 | |||
| 284812b4e3 | |||
| 505d59bc77 | |||
|
|
02eda25aaf | ||
| d19521810e | |||
| f8d17724f9 | |||
|
|
6548125747 | ||
| ef375619b6 | |||
| fe89ae1846 | |||
| 4ccf142917 | |||
|
|
89fbeb85a2 | ||
| 62b3d15991 | |||
| 52a305f5cd | |||
|
|
658305ef28 | ||
| 19295e2832 | |||
| 719e8165f1 | |||
|
|
d5ba280654 | ||
| 00dc48b439 | |||
| 8cc87ba7a0 | |||
| 8b2f7ee188 | |||
| 167c551b89 | |||
| b57968d106 | |||
| 68514b8eae | |||
| 88a8e35793 | |||
| ec4dcb2720 | |||
| f01b64edd5 | |||
| ef0b2af84b | |||
| 90af2e0c3c | |||
| 344393244e | |||
| 96c75eb2d3 | |||
| 5594cbd1a8 | |||
|
|
cb87abf19a | ||
| bf21399113 | |||
|
|
f0fc8a644b | ||
| a668234f2a | |||
| 6e80450e37 | |||
| 5639be6713 | |||
| d36da3dc59 | |||
| e83c743908 | |||
| 3410103859 | |||
| 2685d9ebec | |||
| c5574b3d93 | |||
| 832d65bac8 | |||
| 8394f43b3f | |||
|
|
c77ab16f3b | ||
| 2d10a3a69e | |||
| 90714961eb | |||
| b2586adda0 | |||
| d11dbbccb7 | |||
| 1390afae0e | |||
| 3ec2d5e757 | |||
| cc0d0407af | |||
| 1e9e437b1c | |||
| eae75e24f9 | |||
| 045d672d68 | |||
| caeff7de55 | |||
| b2785070bc | |||
| 545dbeecca | |||
| b4fa69642c | |||
| f84fcc035c | |||
| f556eb9607 | |||
| b8aa2ffc38 | |||
| 2be79ead78 | |||
| c900048329 | |||
| b7850eaa29 | |||
| ec5a5c8d15 | |||
| 94c51196cf | |||
| 35c18c807f | |||
| da3d44fc2a | |||
|
|
b9b9ab9006 | ||
| 5df23c79e7 | |||
|
|
ee4d49752d | ||
| d26bf25359 | |||
| b1edc7b2fb | |||
| 459fb086d0 | |||
|
|
7872391c54 | ||
| c2f4171ee9 | |||
| 9fdfa1c22f | |||
| 8ad3018fe7 | |||
| 64883aa424 | |||
| 6a41f68289 | |||
| ffd96b03e5 | |||
| 961dd5a56e | |||
|
|
f5cb8ba23d | ||
| 6bfe9ea2af | |||
| 134b9ce2f2 | |||
| 54582fb676 | |||
|
|
2e9864be2a | ||
| 73f01522ed | |||
|
|
73a67ebdd6 | ||
| 92c000f63e | |||
| 665d278b52 | |||
| 9393775e90 | |||
|
|
fe441c7afe | ||
| c865ff433b | |||
| 7eb729e809 | |||
|
|
f667fbff55 | ||
| 7c6b5bb200 | |||
| d00ee1a35a | |||
|
|
7364e19051 | ||
| 97263ba55d | |||
| a9cccbb85f | |||
|
|
732bdd5a1c | ||
| 2934d8fd7d | |||
| 992555e80e | |||
| 4a1348403a | |||
|
|
6353a4dc58 | ||
| 9e45f493c6 | |||
| 66fc8c43db | |||
| e4761d52bc | |||
|
|
893eccfa07 | ||
| fc3dc8276a | |||
|
|
9ab5e3354d | ||
| ec607798b5 | |||
| b01a2d15de | |||
| 7e3f40cb20 | |||
| 7a38224198 | |||
| 5a4f1c3cce | |||
| f5287e42ca | |||
|
|
3ec02b8ead | ||
| d25e4b3093 | |||
| 0b81a1402e | |||
| 891ba787e1 | |||
| 85a85a83e4 | |||
| 449b4e6882 | |||
| fba98f5ed6 | |||
| fda19b6103 | |||
|
|
d6d2325506 | ||
| d4354f1a65 | |||
| 7ebe82cf9c | |||
| 3029f8d263 | |||
|
|
00d3aaeb22 | ||
| a8bd5cbbeb | |||
| 48bcf77deb | |||
|
|
a24f3b72bd | ||
| e916e134dc | |||
| b9b18b063c | |||
| 753460c594 | |||
| e6f8e3e6e3 | |||
| 952c6da41c | |||
| adb1d0b26d | |||
|
|
15efadfa9e | ||
| f09cc4996b | |||
| 01a81156cc | |||
| 89ee88d734 | |||
|
|
23880fdb91 | ||
| 10628d1c69 | |||
| 897e54cb86 | |||
| 85da840688 | |||
| 10348168eb | |||
| 4a2570f96a | |||
|
|
65a4635b09 | ||
| 3a5264b783 | |||
|
|
b5866f2a90 | ||
| a35913ab78 | |||
| cc27602454 | |||
| 8920a1132c | |||
| 536c76b000 | |||
| d054f7f9cd | |||
|
|
16bab8f0fa | ||
| b7aa774eb7 | |||
| cef521390a | |||
| f4cda4ec36 | |||
| 628b65a569 | |||
| 142f0fc6c9 | |||
| fe0f3f63a2 | |||
|
|
dd3f5fdb0c | ||
|
|
b7dd2fa05d | ||
|
|
1cf471704c | ||
| 54f8fdeec2 | |||
| 7ea45e4c0a | |||
| 5b93e616dc | |||
| 2c4176de32 | |||
| 114dcea023 | |||
| 3c6b5f3573 | |||
|
|
258b9b610f | ||
| 717d2b1ad0 | |||
| 3cdd7027d6 | |||
| c5dc755cb6 | |||
| 89d13494c0 | |||
| 6fe6f55df8 | |||
| 2f38ecf505 | |||
| c4ba4b4702 | |||
| eab13531d4 | |||
| 62d78dbed8 | |||
| f5a4f1b275 | |||
| ebe761b1cf | |||
| 6e754f2f7b | |||
| 39cd3d1f4d | |||
| 778d595e6e | |||
| 5f4622fdcd | |||
| 9664609bbd | |||
| aa92699562 | |||
| edc45d5d3b | |||
| ed732232f2 | |||
| db04b3f98a | |||
| fb7c5d9d47 | |||
| 7d7df74500 | |||
| 909ba46f66 | |||
| df84ad25d6 | |||
| 178cfb17f3 | |||
| fcef7ec3ca | |||
| c1e758c9df | |||
| a06c98d52f | |||
| bf49153973 | |||
|
|
f40cfb7d25 | ||
| dcd4527c75 | |||
|
|
225ea91f43 | ||
| 172ca05efa | |||
| b3e2c8d9df | |||
| 934327052d | |||
| 24a94b4965 | |||
| c5385541a7 | |||
| 5be2d5eb22 | |||
| 3688cf6145 | |||
|
|
1328699da0 | ||
|
|
1b0e42c79e | ||
|
|
6ca80e3d8c | ||
|
|
d992541a78 | ||
| d13adc707e | |||
| 90d00d0d9a | |||
|
|
fbc9685b3c | ||
| 5507cfdb7b | |||
| 2c8de3e7be | |||
| cbba4adb19 | |||
| b1e36d8a71 | |||
| af6403d752 | |||
| 232db27a15 | |||
| e04fc75be0 | |||
| 5fd4c84a18 | |||
| ce65fb9234 | |||
| d8d8c630a5 | |||
| 08558b1581 | |||
| 21637f170f | |||
|
|
5013ffd38d | ||
| f471368d21 | |||
|
|
8ddf0168fd | ||
| 5a4ba1297f | |||
| 0608c531ed | |||
| 5d242de4e2 | |||
| b7efc0394b | |||
|
|
16681d0193 | ||
| 31ea0865d1 | |||
| 602ad3390c | |||
|
|
d8891a1767 | ||
| 2400a0b353 | |||
|
|
de0248d59a | ||
| 3eb72d23f8 | |||
| c0606112b2 | |||
| 5decb77f1e | |||
| a04b9487f1 | |||
| 8835cd4ab5 | |||
| b17452a949 | |||
| 2072eee3a4 | |||
| 4321c0e6b3 | |||
| f1f98b2624 | |||
| ec37cee26c | |||
| a8adaf7e5a | |||
| 84c763c39e | |||
| 33e86c4f79 | |||
| c00f93a1d3 | |||
| 81100f21b4 | |||
| b8a720919f | |||
| cba37ae2fc | |||
| 5d15cbd4ea | |||
| 5f87606cf7 | |||
| 9b22efe3d4 | |||
| 675c3b27aa | |||
| ddd9e70d05 | |||
| 30ed2049e4 | |||
| b8092ed6dd | |||
| c80c0962dd | |||
| 63c223b237 | |||
| 07aa49accc | |||
| c595c541fb | |||
| e6ec8b65ce | |||
| bc8c455094 | |||
| 507820d25e | |||
| 35e08295ce | |||
| 9e581d8608 | |||
| dcb8e69aac | |||
| 617b354d03 | |||
| 76b095b344 | |||
|
|
5ff05e8025 | ||
| b973a16247 | |||
| 0cb5d7507f | |||
| 2851ebf126 | |||
| 2f5b1bb205 | |||
| 5528336698 | |||
| 4eda80baa8 | |||
| 02918d4bcd | |||
| 8315e3ea96 | |||
| cae4a2ee13 | |||
| 26762eee73 | |||
| 2f05fb12a0 | |||
| befdf2e1d3 | |||
| aac7b98913 | |||
| baa9b5e6bb | |||
| 99c919811a | |||
| ac7a4e739e | |||
| f8936aa7bc | |||
| 6933c44571 | |||
| dfb2f4b3af | |||
| 6601aebddf | |||
| 4d2ca9587d | |||
| 694a5ceab2 | |||
| e522093f6c | |||
| feefa2e90a | |||
| 3709569cd8 | |||
| 052b4870bf | |||
| 5cb413a911 | |||
| 80e77dda3f | |||
| 7a3d554d6c | |||
| ab0fb063cf | |||
| caec558bea | |||
| 2d44b62fef | |||
| f9cc72ba4a | |||
| f8544d0600 | |||
| c96d361b4d | |||
| fa5305f332 | |||
| b996bdea93 | |||
| 1bae6538f7 | |||
| 5673964de4 | |||
| 25623170bb | |||
| 2aa5739cd8 | |||
| bdff56843f | |||
| 6e563e6d74 | |||
| 334a3fb6fe | |||
| a1d130a674 | |||
| e14f2ea247 | |||
| 3f478eef8e | |||
| e929274b51 | |||
| 526c0301f6 | |||
| 7e7a19829d | |||
| bc3854b4c4 | |||
| 193bf20a3e | |||
|
|
4cc5194939 | ||
| 46a525ae09 | |||
| 6d006a357e | |||
| fad73b0922 | |||
| d2d2d4658e | |||
| 84e6ce2b48 | |||
| b91a9963e2 | |||
|
|
25fcf93185 | ||
|
|
26bb2005c1 | ||
|
|
ee58805ae9 | ||
| 1bfc02a892 | |||
| 0c7dc4dbee | |||
| 42e9859ccb | |||
| bf9b6b6046 | |||
|
|
c10eac5190 | ||
| d01ccf1208 | |||
| 703b9be83e | |||
| e0fea93983 | |||
| 84ceef47cb | |||
| 255ed5944f | |||
|
|
0dc2b635ae | ||
| ebd1cfe203 | |||
|
|
43b9297476 | ||
| aa2f4a0167 | |||
|
|
ad31d046d4 | ||
| a67ad5a7db | |||
| 90d4fb245a | |||
| 19f55fde6e | |||
| 227be5258e | |||
|
|
913190bfeb | ||
| 0b0097f367 | |||
|
|
97ac8264f2 | ||
| d9cca6ccc9 | |||
|
|
28d0b1fd25 | ||
| cbcfe4cb9d | |||
|
|
70c14d3369 | ||
| 0bcf7edab5 | |||
| 0f410c29f7 | |||
| b451a6da25 | |||
|
|
e6c7bc1544 | ||
| d1ff8f1cac | |||
| d967100c14 | |||
| 8461c20e40 | |||
|
|
52924f06d2 | ||
| fad4e7bd7d | |||
|
|
666ee1a083 | ||
| 8f05e47363 | |||
|
|
67d8abf7bf | ||
| 30842a6659 | |||
| 3ee500d339 | |||
|
|
c5ade905b0 | ||
| 8e6784bf21 | |||
| 4eacbca674 | |||
|
|
149fe72324 | ||
| 43f752afbc | |||
| 19efde1fda | |||
| 98f267057b | |||
| 4f98894ce4 | |||
| 8cb225779e | |||
| 57ff303c39 | |||
| 29cae6ae6c | |||
| e0c761bdf0 | |||
| 6ee1a8dc52 | |||
| db69532dde | |||
| d4c47c16e4 | |||
| a28733521b | |||
| b957595944 | |||
| b985d42336 | |||
| 9d467a13bb | |||
| 4629d39980 | |||
| 11d245dadf | |||
| bb72b53f19 | |||
| 3bef89d625 | |||
| 792b1348c1 | |||
| 7399cd7d45 | |||
| d6347f6d4a | |||
| 3b5585193f | |||
| 5efa218247 | |||
| e5d312dbf9 | |||
| 38e1c07e0b | |||
| 809c9ea237 | |||
| 08a41f039b | |||
| 3dd14307d0 | |||
| 4122c11372 | |||
| 9360a9cd68 | |||
| 24746dab08 | |||
| 8879cba15d | |||
|
|
1b38c60f57 | ||
| 7c944b27dd | |||
| 523e54eeb2 | |||
| 7b08a3f1bc | |||
| 018826e1a3 | |||
|
|
a36b0b78b3 | ||
| 19964d39c1 | |||
| 31fd04456b | |||
| 76cf2e8fa5 | |||
| f6b61c935e | |||
| c736a04b4f | |||
| 14432fd6f9 | |||
|
|
047d84acb5 | ||
| 8de4e6a0ee | |||
| 90fbb0e9ac | |||
| 041f36ba00 | |||
| 9e6cc873cb | |||
| 20a6ced7b5 | |||
| 34d9682cc1 | |||
| 4ac797406a | |||
| bb3cdb5d85 | |||
|
|
245923d825 | ||
| 41c048594e | |||
| 1360d2d066 | |||
|
|
6286f03432 | ||
| c7a99cc61b | |||
| e4aae5147c | |||
| 2a2e37ca97 | |||
| a1489959d1 | |||
| 4561e37167 | |||
| 9fa04c9a90 | |||
| aa4951b7fc | |||
| 53d08c261c | |||
| 74058243c6 | |||
| 62223d53be | |||
|
|
06d33758eb | ||
| af85245b06 | |||
| 01d8ebbd40 | |||
| 1c1cd1f434 | |||
|
|
aaa801964c | ||
| 01b490dff1 | |||
|
|
1be1441352 | ||
| 0b47b56fb5 | |||
| 0a1251ad0b | |||
|
|
a506bebc94 | ||
| 26147acfa0 | |||
|
|
f1d0be9f61 | ||
| 45c1e9d28c | |||
|
|
df3db9e632 | ||
| 8c1e672da7 | |||
|
|
4bf5e68a9d | ||
| 0d4b157b1b | |||
| 5fd207c84c | |||
| 6829baba67 | |||
| b9983929fc | |||
|
|
d540e7497b | ||
| 0e0cc248fa | |||
| bcf6808e49 | |||
| e2e27765ee | |||
| f3b07bda65 | |||
|
|
f555f1084f | ||
| 212be88efe | |||
| c131f0ecda | |||
|
|
e3afb4b481 | ||
| ca61f70d37 | |||
| 90850ebfa3 | |||
| 771a6d6e08 | |||
| 55a6d17bc8 | |||
| 649990f2e1 | |||
| 48468a1c2d | |||
| dd685b664d | |||
| 36898e3e11 | |||
| 737450ba6e | |||
| 0f63145319 | |||
| 6a942db113 | |||
| 6119adbe2d | |||
| c31af5cbf4 | |||
| 784629f11b | |||
|
|
a581b7cdef | ||
| d6918621fa | |||
| dc54ec241e | |||
| 2f94674479 | |||
| 10b94514a7 | |||
|
|
f5eacd6016 | ||
| 7383317f0b | |||
| 26fa9378f5 | |||
| 9f37033e5c | |||
| dc4ff9b694 | |||
| 3c9dc0a127 | |||
| 2afbe1c6fd | |||
|
|
309b5b9563 | ||
| f64f0ebf4e | |||
| dd646a155b | |||
| 6db38826d8 | |||
| a18fd9b05c | |||
| 611c7b7b21 | |||
| 3961e95a5f | |||
| d6e6a53301 | |||
| e500026017 | |||
| 84632b6296 | |||
| 9b79e93967 | |||
|
|
88e7ad0411 | ||
| d0e96eab91 | |||
| 6ac78d9eb6 | |||
| 5d830ae2f8 | |||
| 178f844420 | |||
| adf3df510e | |||
| f58c03c429 | |||
| 15f9edc5c7 | |||
| 07f77024ab | |||
| 5b8ff7e612 | |||
|
|
1182e7405e | ||
| 9c6d8ef8e6 | |||
| eb807fb205 | |||
|
|
0602fb5569 | ||
| 1dd78c3ea1 | |||
| 3175de4576 | |||
| dfd1f172bb | |||
|
|
0f6cf01fd3 | ||
| 3e9e7922b4 | |||
| eb480c8838 | |||
| a9bc4fe914 | |||
| 3bf1573e36 | |||
| de6fd60f41 | |||
| cf72ebdfd8 | |||
| 6e44452807 | |||
| 88c6abb53c | |||
| 27fd93f590 | |||
| 8b391f03c2 | |||
| ea3033e22a | |||
|
|
c88dff350e | ||
| 73e1a312b5 | |||
| 28c2b6dc77 | |||
| 0348cc55c6 | |||
| e439918e05 | |||
|
|
a5a7f405b3 | ||
| 4cd620fe2b | |||
| b1826a60d2 | |||
| 50ec1c59d2 | |||
| 36d604ec49 | |||
|
|
7d6407afcf | ||
| 92358db548 | |||
|
|
161c6a69b1 | ||
| 215e053b6d | |||
| e8a99af37f | |||
| de258a2568 | |||
| 728db39ab7 | |||
|
|
d885870302 | ||
| 2c87079686 | |||
| e89785c1d2 | |||
| 38f8eff1c5 | |||
| 077394ec99 | |||
| 7c23f85f75 | |||
|
|
20c1ceab92 | ||
| e5585caa09 | |||
| a11b49befe | |||
|
|
58027e4607 | ||
| 1285641746 | |||
| 843ac18d2d | |||
| 4383a202f2 | |||
| cda412f6a9 | |||
| f26b84c799 | |||
| 2fc3fd3ef1 | |||
| 57d04de8a5 | |||
| 3e43c22a42 | |||
| ea643b58d8 | |||
| d366490fe5 | |||
| 83093f4c85 | |||
|
|
99fcfa5223 | ||
| f6786b367b | |||
| eb373af6ed | |||
| 037e7e907b | |||
| 80844562e9 | |||
| 2a935845bf | |||
|
|
3acbad4606 | ||
| 1973f6a2b0 | |||
| 85e6a678dd | |||
| 798c52f23d | |||
| aee405f9e2 | |||
| f82ff2d482 | |||
| d9d9721249 | |||
| 8ef84763c6 | |||
| c4f6db4431 | |||
| 60a7e7f885 | |||
| 7e3c36cd27 | |||
| 5304abcd00 | |||
| 19d94893cb | |||
| 485424b5c9 | |||
| a8af7bed62 | |||
| f63e7df28d | |||
| 7e81da6741 | |||
| 0c8e69c252 | |||
| da8881dd1e | |||
| d10c0a2fb9 | |||
| 34ed4b2f25 | |||
| a109b148f6 | |||
| 65c7eeb9c9 | |||
| e45b9b4101 | |||
| 8c9bfd7e2d | |||
| 208e418bba | |||
| 05278bdacb | |||
| 5b11b35d5d | |||
| 9f5844c4de | |||
| ae888e6d48 | |||
| f5b4af6e38 | |||
| 84035e0cb1 | |||
| 6d5c3e81c8 | |||
| 16db84793d | |||
| 8b8a63c352 | |||
| 698cf0d263 | |||
| 6385d49eeb | |||
| 43e69909cd | |||
| aeb0f866c8 | |||
| 3e5c005f25 | |||
| bde27382b7 | |||
|
|
c17df789de | ||
| ca6a84fec4 | |||
| 1c952ca4cd | |||
| c2c7073cd9 | |||
|
|
6c84be1a82 | ||
| 16c90290f9 | |||
| cf707df5e0 | |||
| 078f1b763b | |||
| 6060faa3ac | |||
| 5059157515 | |||
| 49924c6b2b | |||
| 6ff1781372 | |||
| 29fd4d5220 | |||
| 484dc13977 | |||
| 59fc82c788 | |||
| 9a4c9154f1 | |||
| 9949bb2493 | |||
| 07fc6ac8c4 | |||
| 6a93c3e90f | |||
| 7bd88335c4 | |||
| 9893e3bac2 | |||
| 946e886799 | |||
| b84301debc | |||
| cb5c95ce03 | |||
| c0a7e1ddba | |||
| 220af054bb | |||
| a9634f0249 | |||
| fd77048d4b | |||
| 40cb632c80 | |||
| 2313af825e | |||
| 762efdaa9a | |||
| b58cafb47a | |||
| 308d909e72 | |||
| c88eb37b1a | |||
| e3189d39f4 | |||
|
|
de351c7473 | ||
| 9d08b7de34 | |||
| 5cf0745da0 | |||
| 10009b65be | |||
|
|
80c1c0b8b4 | ||
| 9169d8899f | |||
| 5166e05dbe | |||
| e6e9dcb48c | |||
|
|
712550cec8 | ||
| f9061088fe | |||
|
|
c53390d038 | ||
| 6fb93b19c9 | |||
| e07e8391e1 | |||
| ccd8c056b1 | |||
| 32ee0cf14c | |||
| 9a6c02dd9a | |||
| 3dd77715d8 | |||
| 0f99cdb115 | |||
| aecd0726e5 | |||
| 258b3495a1 | |||
| eb52d1a79a | |||
| 1ad16b2b94 | |||
| fc28136936 | |||
| 575f3c9ce4 | |||
| 7c9ca83b46 | |||
| 0d6c4010ff | |||
| cb6d9762d5 | |||
| a4414d2083 | |||
| 5bcae1b00c | |||
| 5c01f36fe7 | |||
| 84b7d1123e | |||
|
|
b819f345cf | ||
| 5278116f49 | |||
| af36366a89 | |||
| cabba7e2d2 | |||
| ef6cc77dc2 | |||
| 4b6de5cd9f | |||
| 403e35c6c5 | |||
| e1dbdc5886 | |||
| 9e0a64a51f | |||
| 971cc3a42a | |||
| 1c109ee86f | |||
| b84106ae70 | |||
| 05881be9b9 | |||
| 06a7b3da19 | |||
| 9d77f66e47 | |||
| 85afff4848 | |||
| 8498eaa16d | |||
| 1e78341559 | |||
| e4ed5a1d19 | |||
| 7b247d1e6f | |||
| f98a7298c5 | |||
| 8e4e9ddf62 | |||
| aea7fbe385 | |||
|
|
392442b7b7 | ||
| cce3102beb | |||
| fdf2e3854e | |||
|
|
95011544c1 | ||
| fc97eb1c14 | |||
| fc6ec859fe | |||
| 582ea54ed5 | |||
|
|
6caedb5b06 | ||
| 307135d495 | |||
| f786be41e7 | |||
| e18ae543ee | |||
| ab606e0629 | |||
| b6cda24231 | |||
| ad4714418d |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -176,7 +176,6 @@ plugins/op_auth
|
||||
plugins/l2tp
|
||||
plugins/openlitespeed
|
||||
plugins/tamper_proof
|
||||
plugins/cryptocurrency_trade
|
||||
plugins/zimg
|
||||
plugins/bk_demo
|
||||
plugins/mail
|
||||
@@ -189,7 +188,12 @@ plugins/tidb
|
||||
plugins/goedge-admin
|
||||
plugins/goedge-node
|
||||
plugins/goedge-happy
|
||||
plugins/dztasks
|
||||
|
||||
/logs
|
||||
/data
|
||||
/plugins/flexcdn
|
||||
/plugins/tools
|
||||
/plugins/choose-linux-python
|
||||
*.md5
|
||||
/bak
|
||||
*.tmp
|
||||
|
||||
66
README.md
66
README.md
@@ -22,7 +22,6 @@
|
||||
|
||||
* SSH终端工具
|
||||
* 面板收藏功能
|
||||
* 网站子目录绑定
|
||||
* 网站备份功能
|
||||
* 插件方式管理
|
||||
|
||||
@@ -37,20 +36,21 @@ Talk is cheap, show me the code. -- linus
|
||||
```
|
||||
|
||||
- [兼容性测试报告](/compatibility.md)
|
||||
- [常用命令说明](/cmd.md) [ mw default ] [ mw update_dev ]
|
||||
- [常用命令说明](/cmd.md) [ mw default ] [ mw dev ]
|
||||
|
||||
### 特别赞助
|
||||
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan1.cc)
|
||||
- [虚位以待](https://bbs.midoks.icu)
|
||||
|
||||
### 主要插件介绍
|
||||
|
||||
* OpenResty - 轻量级,占有内存少,并发能力强。
|
||||
* PHP[53-84] - PHP是世界上最好的编程语言。
|
||||
* PHP[53-85] - PHP是世界上最好的编程语言。
|
||||
* MySQL - 一种关系数据库管理系统。
|
||||
* MariaDB - 是MySQL的一个重要分支。
|
||||
* MySQL[APT/YUM] - 一种关系数据库管理系统。
|
||||
* MySQL[community] - 一种关系数据库管理系统。
|
||||
* MongoDB - 一种非关系NOSQL数据库管理系统。
|
||||
* PostgreSQL - 功能强大的开源数据库。
|
||||
* phpMyAdmin - 著名Web端MySQL管理工具。
|
||||
* Memcached - 一个高性能的分布式内存对象缓存系统。
|
||||
* Redis - 一个高性能的KV数据库。
|
||||
@@ -94,8 +94,6 @@ PHP[72-84]支持phpMyAdmin[5.2.1]
|
||||
|
||||
# AD - VPS推荐 - 🙏
|
||||
|
||||
- [ZZZ评测](https://www.zzzvps.com/)
|
||||
|
||||
| 服务商 | LOGO | 推广地址 | 优惠码 |
|
||||
| ------------- |----------|-----------|-------|
|
||||
| digitalvirt |[](https://digitalvirt.com/aff.php?aff=154) | https://digitalvirt.com/aff.php?aff=154 | mdserver-web |
|
||||
@@ -110,28 +108,22 @@ docker run -itd --name mw-server --privileged=true -p 7200:7200 -p 80:80 -p 443:
|
||||
```
|
||||
|
||||
|
||||
### 版本更新 0.18.0
|
||||
### 版本更新 0.19.0
|
||||
|
||||
- 重新架构面板。
|
||||
- PHP84正式版。
|
||||
- 加入alist插件。
|
||||
- 数据管理-常用命令。
|
||||
- 插件读取优化(加入缓存+定时检查状态)。
|
||||
- 恢复面板书签。
|
||||
- 常规更新。
|
||||
- 常规优化。
|
||||
|
||||
### JSDelivr安装地址
|
||||
### 增加在线更新支持。
|
||||
|
||||
- 初始安装
|
||||
|
||||
```
|
||||
curl --insecure -fsSL https://cdn.jsdelivr.net/gh/midoks/mdserver-web@latest/scripts/install.sh | bash
|
||||
bash <(curl --insecure -fsSL https://cdn.jsdelivr.net/gh/midoks/mdserver-web@latest/scripts/install.sh)
|
||||
```
|
||||
|
||||
- 直接更新
|
||||
|
||||
```
|
||||
curl --insecure -fsSL https://cdn.jsdelivr.net/gh/midoks/mdserver-web@latest/scripts/update.sh | bash
|
||||
bash <(curl --insecure -fsSL https://cdn.jsdelivr.net/gh/midoks/mdserver-web@latest/scripts/update.sh)
|
||||
```
|
||||
|
||||
- 卸载脚本
|
||||
@@ -145,14 +137,15 @@ wget --no-check-certificate -O uninstall.sh https://cdn.jsdelivr.net/gh/midoks/m
|
||||
- 初始安装
|
||||
|
||||
```
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/master/scripts/install.sh | bash
|
||||
curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/master/scripts/install.sh | bash
|
||||
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/install.sh)
|
||||
bash <(curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/master/scripts/install.sh)
|
||||
```
|
||||
|
||||
- 直接更新
|
||||
|
||||
```
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/master/scripts/update.sh | bash
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/master/scripts/update.sh)
|
||||
```
|
||||
|
||||
- 卸载脚本
|
||||
@@ -167,32 +160,49 @@ wget --no-check-certificate -O uninstall.sh https://raw.githubusercontent.com/mi
|
||||
- 需已经安装mdserver-web
|
||||
|
||||
```
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/quick/app.sh | bash
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/quick/app.sh)
|
||||
```
|
||||
|
||||
|
||||
### DEV使用
|
||||
|
||||
```
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/install_dev.sh | bash
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/update_dev.sh | bash
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/install.sh)
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/update.sh)
|
||||
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/install_dev.sh)
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/update_dev.sh)
|
||||
|
||||
wget --no-check-certificate -O install.sh https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/install.sh && bash install.sh
|
||||
|
||||
wget --no-check-certificate -O uninstall.sh https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/uninstall.sh && bash uninstall.sh
|
||||
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/quick/debug.sh | bash
|
||||
bash <(curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/quick/debug.sh)
|
||||
|
||||
curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/dev/scripts/install_dev.sh | bash
|
||||
curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/dev/scripts/update_dev.sh | bash
|
||||
bash <(curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/dev/scripts/install.sh)
|
||||
bash <(curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/dev/scripts/update.sh)
|
||||
|
||||
bash <(curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/dev/scripts/install_dev.sh)
|
||||
bash <(curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/dev/scripts/update_dev.sh)
|
||||
```
|
||||
|
||||
### 旧版安装
|
||||
### 旧版安装/更新
|
||||
```
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/old/install.sh | bash
|
||||
curl --insecure -fsSL https://raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/old/update.sh | bash
|
||||
```
|
||||
|
||||
### 偶然问题
|
||||
- 在安装/更新时,突然出现python插件pip,psutil,可尝试如下解决方案
|
||||
```
|
||||
cd /www/server/mdserver-web rm -rf lib
|
||||
cd /www/server/mdserver-web && rm -rf lib64
|
||||
cd /www/server/mdserver-web && rm -rf bin
|
||||
cd /www/server/mdserver-web && rm -rf include
|
||||
|
||||
mw update/mw update_dev/mw dev
|
||||
```
|
||||
|
||||
### 捐赠地址 USDT(TRC20)
|
||||
|
||||
TVbNgrpeGBGZVm5gTLa21ADP7RpnPFhjya
|
||||
|
||||
27
cli.sh
27
cli.sh
@@ -19,12 +19,12 @@ fi
|
||||
mw_start_task()
|
||||
{
|
||||
isStart=$(ps aux |grep 'panel_task.py'|grep -v grep|awk '{print $2}')
|
||||
if [ "$isStart" == '' ];then
|
||||
if [ "$isStart" = '' ];then
|
||||
echo -e "starting mw-tasks... \c"
|
||||
cd $DIR && python3 panel_task.py >> ${DIR}/logs/panel_task.log 2>&1 &
|
||||
sleep 0.3
|
||||
isStart=$(ps aux |grep 'panel_task.py'|grep -v grep|awk '{print $2}')
|
||||
if [ "$isStart" == '' ];then
|
||||
if [ "$isStart" = '' ];then
|
||||
echo -e "\033[31mfailed\033[0m"
|
||||
echo '------------------------------------------------------'
|
||||
tail -n 20 $DIR/logs/panel_task.log
|
||||
@@ -60,7 +60,7 @@ mw_start_debug(){
|
||||
if [ -f ${DIR}/data/port.pl ];then
|
||||
port=$(cat ${DIR}/data/port.pl)
|
||||
fi
|
||||
cd ${DIR}/web && gunicorn -b :${port} -k eventlet -w 1 app:app
|
||||
cd ${DIR}/web && gunicorn -b :${port} -k gthread -w 1 app:app
|
||||
# cd ${DIR}/web && gunicorn -b :${port} -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 app:app
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ mw_start_panel(){
|
||||
if [ -f ${DIR}/data/port.pl ];then
|
||||
port=$(cat ${DIR}/data/port.pl)
|
||||
fi
|
||||
cd ${DIR}/web && gunicorn -b :${port} -k eventlet -w 1 app:app
|
||||
cd ${DIR}/web && gunicorn -b :${port} -k gthread -w 1 app:app
|
||||
# cd ${DIR}/web && gunicorn -b :${port} -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 app:app
|
||||
|
||||
}
|
||||
@@ -82,17 +82,26 @@ mw_start_bgtask(){
|
||||
|
||||
mw_stop()
|
||||
{
|
||||
PLIST=`ps -ef|grep app:app |grep -v grep|awk '{print $2}'`
|
||||
for i in $PLIST
|
||||
APP_LIST=`ps -ef|grep app:app |grep -v grep|awk '{print $2}'`
|
||||
APP_LIST=($APP_LIST)
|
||||
for p in ${APP_LIST[@]}
|
||||
do
|
||||
kill -9 $i > /dev/null 2>&1
|
||||
kill -9 $p > /dev/null 2>&1
|
||||
done
|
||||
|
||||
PIDS=`ps -ef|grep panel_task.py | grep -v grep |awk '{print $2}'`
|
||||
for p in $PIDS
|
||||
TASK_LIST=`ps -ef|grep panel_task.py | grep -v grep |awk '{print $2}'`
|
||||
TASK_LIST=($TASK_LIST)
|
||||
for p in ${TASK_LIST[@]}
|
||||
do
|
||||
kill -9 $p > /dev/null 2>&1
|
||||
done
|
||||
|
||||
zzpids=`ps -A -o stat,ppid,pid | grep -e '^[Zz]' | awk '{print $2}'`
|
||||
zzpids=($zzpids)
|
||||
for p in ${zzpids[@]}
|
||||
do
|
||||
kill -9 ${p} > /dev/null 2>&1
|
||||
done
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
|
||||
5
cmd.md
5
cmd.md
@@ -4,11 +4,16 @@
|
||||
- 面板相关命令
|
||||
|
||||
```
|
||||
mw start | 启动面板服务器
|
||||
mw stop | 停止面板服务器
|
||||
mw restart | 重启面板服务器
|
||||
mw default | 显示登录信息
|
||||
mw db | 快捷连接MySQL
|
||||
mw redis | 快捷连接Redis
|
||||
mw valkey | 快捷连接valkey
|
||||
mw mongodb | 快捷连接MongoDB
|
||||
mw pgdb | 快捷连接PostgreSQL
|
||||
mw ssh | 快捷连接ssh
|
||||
----------------------------------------
|
||||
mw open | 开启面板
|
||||
mw close | 关闭面板
|
||||
|
||||
@@ -80,6 +80,7 @@ def runPanelTask():
|
||||
# 站点过期检查
|
||||
siteEdateCheck()
|
||||
|
||||
# echo "True" > /www/server/mdserver-web/logs/panel_task.lock
|
||||
lock_file = mw.getTriggerTaskLockFile()
|
||||
try:
|
||||
if os.path.exists(lock_file):
|
||||
@@ -140,7 +141,9 @@ def systemTask():
|
||||
from utils.system import monitor
|
||||
try:
|
||||
while True:
|
||||
monitor.instance().run()
|
||||
monitor_status = thisdb.getOption('monitor_status',type='monitor',default='open')
|
||||
if monitor_status == 'open':
|
||||
monitor.instance().run()
|
||||
time.sleep(5)
|
||||
except Exception as ex:
|
||||
print('systemTask:',mw.getTracebackInfo())
|
||||
@@ -181,7 +184,7 @@ def check502():
|
||||
verlist = [
|
||||
'52', '53', '54', '55', '56', '70',
|
||||
'71', '72', '73', '74', '80', '81',
|
||||
'82', '83', '84'
|
||||
'82', '83', '84', '85'
|
||||
]
|
||||
for ver in verlist:
|
||||
server_dir = mw.getServerDir()
|
||||
|
||||
@@ -57,7 +57,9 @@ def mwcli(mw_input=0):
|
||||
'(2) 停止面板服务',
|
||||
'(3) 启动面板服务',
|
||||
'(4) 重载面板服务',
|
||||
'(5) 修改面板端口',
|
||||
'(5) 修改面板IP',
|
||||
'(6) 修改面板端口',
|
||||
'(7) 关闭安全入口',
|
||||
'(10) 查看面板默认信息',
|
||||
'(11) 修改面板密码',
|
||||
'(12) 修改面板用户名',
|
||||
@@ -73,10 +75,12 @@ def mwcli(mw_input=0):
|
||||
'(26) 关闭二次验证',
|
||||
'(27) 查看防火墙信息',
|
||||
'(28) 自动识别防火墙端口到面板',
|
||||
'(29) 自动识别配置站点信息',
|
||||
'(100) 开启PHP52显示',
|
||||
'(101) 关闭PHP52显示',
|
||||
'(200) 切换Linux系统软件源',
|
||||
'(201) 简单速度测试',
|
||||
'(202) SSH终端管理',
|
||||
'(0) 取消'
|
||||
]
|
||||
cmd_list_num = len(cmd_list)
|
||||
@@ -98,10 +102,11 @@ def mwcli(mw_input=0):
|
||||
mw_input = 0
|
||||
|
||||
nums = [
|
||||
1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28,
|
||||
1, 2, 3, 4, 5, 6, 7,
|
||||
10, 11, 12, 13, 14, 15,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
100, 101,
|
||||
200, 201
|
||||
200, 201, 202
|
||||
]
|
||||
if not mw_input in nums:
|
||||
print(raw_tip)
|
||||
@@ -117,6 +122,16 @@ def mwcli(mw_input=0):
|
||||
elif mw_input == 4:
|
||||
os.system(INIT_CMD + " reload")
|
||||
elif mw_input == 5:
|
||||
in_ip = mw_input_cmd("请输入设置的面板IP:")
|
||||
in_ip = in_ip.strip()
|
||||
ip_text = panel_dir + '/data/iplist.txt'
|
||||
if not mw.isVaildIp(in_ip):
|
||||
mw.echoInfo("【"+in_ip+"】: IP不合法")
|
||||
return
|
||||
mw.writeFile(ip_text, in_ip)
|
||||
thisdb.setOption('server_ip', in_ip)
|
||||
mw.echoInfo("设置面板IP: " + in_ip)
|
||||
elif mw_input == 6:
|
||||
in_port = mw_input_cmd("请输入新的面板端口:")
|
||||
in_port_int = int(in_port.strip())
|
||||
if in_port_int < 65536 and in_port_int > 0:
|
||||
@@ -128,6 +143,9 @@ def mwcli(mw_input=0):
|
||||
else:
|
||||
mw.echoInfo("端口范围在0-65536之间")
|
||||
return
|
||||
elif mw_input == 7:
|
||||
thisdb.setOption('admin_path', '')
|
||||
mw.echoInfo("关闭安全入口成功!")
|
||||
elif mw_input == 10:
|
||||
os.system(INIT_CMD + " default")
|
||||
elif mw_input == 11:
|
||||
@@ -205,14 +223,14 @@ def mwcli(mw_input=0):
|
||||
else:
|
||||
mw.echoInfo("二次验证已关闭!")
|
||||
elif mw_input == 27:
|
||||
cmd = 'which ufw'
|
||||
cmd = 'command -v ufw'
|
||||
run_cmd = False
|
||||
find_cmd = mw.execShell(cmd)
|
||||
if find_cmd[0].strip() != '':
|
||||
run_cmd = True
|
||||
os.system('ufw status')
|
||||
|
||||
cmd = 'which firewall-cmd'
|
||||
cmd = 'command -v firewall-cmd'
|
||||
find_cmd = mw.execShell(cmd)
|
||||
if find_cmd[0].strip() != '':
|
||||
run_cmd = True
|
||||
@@ -220,9 +238,12 @@ def mwcli(mw_input=0):
|
||||
if not run_cmd:
|
||||
mw.echoInfo("未检测到防火墙!")
|
||||
elif mw_input == 28:
|
||||
from utils.firewall import Firewall as MwFirewall
|
||||
MwFirewall.instance().aIF()
|
||||
mw.echoInfo("执行自动识别防火墙端口到面板成功!")
|
||||
elif mw_input == 29:
|
||||
from utils.site_reflect import parse as MwParse
|
||||
MwParse()
|
||||
mw.echoInfo("自动识别配置站点信息成功!")
|
||||
elif mw_input == 100:
|
||||
php_conf = panel_dir + '/plugins/php/info.json'
|
||||
if os.path.exists(php_conf):
|
||||
@@ -240,10 +261,52 @@ def mwcli(mw_input=0):
|
||||
mw.writeFile(php_conf, cont)
|
||||
mw.echoInfo("执行PHP52隐藏成功!")
|
||||
elif mw_input == 200:
|
||||
os.system(INIT_CMD + " mirror")
|
||||
os.system("bash <(curl -sSL https://linuxmirrors.cn/main.sh)")
|
||||
# os.system(INIT_CMD + " mirror")
|
||||
elif mw_input == 201:
|
||||
os.system('curl -Lso- bench.sh | bash')
|
||||
elif mw_input == 202:
|
||||
package = mw.getPanelDir()+'/plugins'
|
||||
|
||||
dst_plugin = mw.getServerDir() + "/webssh"
|
||||
if not os.path.exists(dst_plugin):
|
||||
mw.echoInfo("未安装!")
|
||||
exit(1)
|
||||
|
||||
if not package in sys.path:
|
||||
sys.path.append(package)
|
||||
from webssh.index import App
|
||||
|
||||
obj = App()
|
||||
data = obj.get_server_list()
|
||||
data = json.loads(data)
|
||||
|
||||
if data['status']:
|
||||
wlist = data['data']
|
||||
wlist_len = len(wlist)
|
||||
if wlist_len == 0:
|
||||
mw.echoInfo("SSH终端管理,数据为空!")
|
||||
exit(1)
|
||||
|
||||
if len(sys.argv) == 4:
|
||||
pos = int(sys.argv[3])
|
||||
if pos >= wlist_len:
|
||||
mw.echoInfo("SSH终端管理,超过限制!")
|
||||
exit(1)
|
||||
dst_data = wlist[pos]
|
||||
tmp_host = dst_data["host"]
|
||||
info = obj.get_server_by_host_data(tmp_host)
|
||||
pp_info = tmp_host+"|"+info["port"]+"|"+info['username']+"|"+info['password']+""
|
||||
print(pp_info)
|
||||
exit(0)
|
||||
|
||||
mw.echoInfo("请选择:")
|
||||
for x in range(wlist_len):
|
||||
dst_data = wlist[x]
|
||||
tag = dst_data['host']
|
||||
if dst_data['ps'] != "":
|
||||
tag = dst_data['ps']
|
||||
print(str(x) +") " + tag)
|
||||
|
||||
def open_ssh_port():
|
||||
|
||||
@@ -283,7 +346,10 @@ def show_panel_pwd():
|
||||
|
||||
def show_panel_adminpath():
|
||||
admin_path = thisdb.getOption('admin_path')
|
||||
print('/'+admin_path)
|
||||
if admin_path == '':
|
||||
print('/login')
|
||||
else:
|
||||
print('/'+admin_path)
|
||||
|
||||
|
||||
def set_panel_username(username=None):
|
||||
|
||||
@@ -21,8 +21,8 @@ CREATE TABLE IF NOT EXISTS `domain` (
|
||||
`dnsapi_id` TEXT,
|
||||
`email` TEXT,
|
||||
`remark` TEXT,
|
||||
`effective_date` TEXT,
|
||||
`expiration_date` TEXT,
|
||||
`effective_date` TEXT default '',
|
||||
`expiration_date` TEXT default '',
|
||||
`error` TEXT,
|
||||
`status` INTEGER default '0',
|
||||
`addtime` TEXT
|
||||
|
||||
@@ -17,8 +17,16 @@ access_key = "xxx"
|
||||
# 指定用户
|
||||
userId = 1
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
if sys.platform != "darwin":
|
||||
os.chdir("/www/server/mdserver-web")
|
||||
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
|
||||
domain = sys.argv[1]
|
||||
ssl_path = sys.argv[2]
|
||||
|
||||
@@ -186,24 +186,25 @@ def initDreplace():
|
||||
|
||||
def apaOp(method):
|
||||
file = initDreplace()
|
||||
return 'ok'
|
||||
|
||||
current_os = mw.getOs()
|
||||
if current_os == "darwin":
|
||||
data = mw.execShell(file + ' ' + method)
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
# current_os = mw.getOs()
|
||||
# if current_os == "darwin":
|
||||
# data = mw.execShell(file + ' ' + method)
|
||||
# if data[1] == '':
|
||||
# return 'ok'
|
||||
# return data[1]
|
||||
|
||||
if current_os.startswith("freebsd"):
|
||||
data = mw.execShell('service ' + getPluginName() + ' ' + method)
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
# if current_os.startswith("freebsd"):
|
||||
# data = mw.execShell('service ' + getPluginName() + ' ' + method)
|
||||
# if data[1] == '':
|
||||
# return 'ok'
|
||||
# return data[1]
|
||||
|
||||
data = mw.execShell('systemctl ' + method + ' ' + getPluginName())
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
# data = mw.execShell('systemctl ' + method + ' ' + getPluginName())
|
||||
# if data[1] == '':
|
||||
# return 'ok'
|
||||
# return data[1]
|
||||
|
||||
|
||||
def start():
|
||||
@@ -600,10 +601,12 @@ def runHookDstDomain(row):
|
||||
|
||||
hookWriteLog('开始申请【'+domain+'】SSL证书')
|
||||
cmd = "#!/bin/bash\n"
|
||||
cmd += "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin:/root/.acme.sh\n"
|
||||
cmd += "export PATH\n"
|
||||
|
||||
if mw.isAppleSystem():
|
||||
user = getRunUser()
|
||||
cmd += "source /Users/"+user+"/.zshrc\n"
|
||||
cmd += ". /Users/"+user+"/.zshrc\n"
|
||||
|
||||
cmd_data = getDnsapiData(row['dnsapi_id'])
|
||||
# print(cmd_data)
|
||||
|
||||
@@ -13,7 +13,7 @@ VERSION=$2
|
||||
# acme.sh --uninstall
|
||||
|
||||
|
||||
# source /Users/xxx/.zshrc
|
||||
# . /Users/xxx/.zshrc
|
||||
|
||||
# https://github.com/acmesh-official/acme.sh/wiki/dnsapi
|
||||
# https://docs.dnspod.com/api-legacy/domains.html#get-the-domain-list
|
||||
@@ -24,7 +24,7 @@ VERSION=$2
|
||||
|
||||
|
||||
if [ -f ${rootPath}/bin/activate ];then
|
||||
source ${rootPath}/bin/activate
|
||||
. ${rootPath}/bin/activate
|
||||
fi
|
||||
|
||||
# pip install cloudflare
|
||||
@@ -47,7 +47,7 @@ Uninstall_App()
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
|
||||
@@ -274,7 +274,7 @@ function dnsapiAdd(row){
|
||||
// console.log(row);
|
||||
var option_name = '';
|
||||
var option_remark = '';
|
||||
var option_type = 'cf';
|
||||
var option_type = 'dns_cf';
|
||||
var option_val = '';
|
||||
var option_id = 0;
|
||||
if (typeof(row) != 'undefined'){
|
||||
@@ -286,7 +286,6 @@ function dnsapiAdd(row){
|
||||
|
||||
}
|
||||
|
||||
// console.log(option_name);
|
||||
function renderDnsapiOption(name, val){
|
||||
var vlist = {};
|
||||
if (val != ''){
|
||||
@@ -303,18 +302,16 @@ function dnsapiAdd(row){
|
||||
}
|
||||
|
||||
|
||||
|
||||
var key = getDnsapiKey(name);
|
||||
var klist = key.split(':');
|
||||
// console.log(klist);
|
||||
var option_html = '';
|
||||
for (var i = 0; i < klist.length; i++) {
|
||||
var klist_val = '';
|
||||
if (klist[i] in vlist){
|
||||
klist_val = vlist[klist[i]];
|
||||
}
|
||||
|
||||
option_html += "\
|
||||
<span class='tname'>"+klist[i]+"</span>\
|
||||
option_html += "<span class='tname'>"+klist[i]+"</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='"+klist[i]+"' class='bt-input-text mr5' style='width:100%;' value='"+klist_val+"' placeholder='请输入对应值' type='text'>\
|
||||
</div>";
|
||||
@@ -379,7 +376,6 @@ function dnsapiAdd(row){
|
||||
$('select[name="type"]').html(option);
|
||||
$('select[name="type"]').change(function(){
|
||||
var name = $(this).val();
|
||||
|
||||
if (option_type == name){
|
||||
renderDnsapiOption(name, option_val);
|
||||
} else {
|
||||
@@ -667,11 +663,24 @@ function domainAdd(row){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
for (var i = 0; i < rdata.length; i++) {
|
||||
if (option_dnsapi_id == rdata[i]['id']){
|
||||
dnsapi_id_html += "<option value='"+rdata[i]['id']+"' selected>"+rdata[i]['name']+"</option>";
|
||||
dnsapi_id_html += "<option value='"+rdata[i]['id']+"' val='"+rdata[i]['val']+"' selected>"+rdata[i]['name']+"</option>";
|
||||
} else {
|
||||
dnsapi_id_html += "<option value='"+rdata[i]['id']+"'>"+rdata[i]['name']+"</option>";
|
||||
dnsapi_id_html += "<option value='"+rdata[i]['id']+"' val='"+rdata[i]['val']+"'>"+rdata[i]['name']+"</option>";
|
||||
}
|
||||
}
|
||||
|
||||
$('select[name="dnsapi_id"]').change(function(){
|
||||
var val = $('select[name="dnsapi_id"]').find("option:selected").attr('val');
|
||||
var val_arr = val.split("|");
|
||||
for (var i = 0; i < val_arr.length; i++) {
|
||||
var param = val_arr[i];
|
||||
var strictEmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
if(strictEmailRegex.test(param)){
|
||||
$('input[name="email"]').val(param);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
$('select[name="dnsapi_id"]').html(dnsapi_id_html);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -6,8 +6,18 @@ import os
|
||||
import time
|
||||
import json
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
# print(sys.platform)
|
||||
if sys.platform != "darwin":
|
||||
os.chdir("/www/server/mdserver-web")
|
||||
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
from utils.crontab import crontab as MwCrontab
|
||||
|
||||
|
||||
app_debug = False
|
||||
@@ -69,8 +79,6 @@ def createBgTaskByName(name):
|
||||
if res and res["id"] == args["task_id"]:
|
||||
print("计划任务已经存在!")
|
||||
return True
|
||||
import crontab_api
|
||||
api = crontab_api.crontab_api()
|
||||
|
||||
mw_dir = mw.getPanelDir()
|
||||
cmd = '''
|
||||
@@ -99,10 +107,10 @@ logs_file=$plugin_path/${rname}.log
|
||||
'stype': "toShell",
|
||||
'sname': '',
|
||||
'sbody': cmd,
|
||||
'urladdress': '',
|
||||
'url_address': '',
|
||||
}
|
||||
|
||||
task_id = api.add(params)
|
||||
task_id = MwCrontab.instance().add(params)
|
||||
if task_id > 0:
|
||||
args["task_id"] = task_id
|
||||
args["name"] = name
|
||||
|
||||
@@ -122,7 +122,7 @@ def openPort():
|
||||
return True
|
||||
|
||||
def status():
|
||||
cmd = "ps aux|grep alist |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'"
|
||||
cmd = "ps -ef|grep alist |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'"
|
||||
data = mw.execShell(cmd)
|
||||
if data[0] == '':
|
||||
return 'stop'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "alist",
|
||||
"title": "Alist",
|
||||
"shell": "install.sh",
|
||||
"versions":["3.37.4","3.38.0"],
|
||||
"versions":["3.37.4","3.38.0","3.45.0"],
|
||||
"tip": "soft",
|
||||
"checks": "server/alist",
|
||||
"path": "server/alist",
|
||||
|
||||
@@ -23,7 +23,7 @@ app_start(){
|
||||
|
||||
app_stop(){
|
||||
echo "dztasks stopped"
|
||||
ps -ef| grep alist | grep -v grep | grep -v python | grep -v sh | awk '{print $2}'| xargs kill
|
||||
ps -ef| grep alist | grep -v grep | grep -v python | grep -v sh | awk '{print $2}'| xargs kill -9
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/alist && bash install.sh install 3.37.4
|
||||
# cd /www/server/mdserver-web/plugins/alist && bash install.sh install 3.37.4
|
||||
# cd /www/server/mdserver-web/plugins/alist && bash install.sh install 3.45.0
|
||||
|
||||
VERSION=$2
|
||||
|
||||
@@ -16,16 +16,16 @@ sysArch=`arch`
|
||||
sysName=`uname`
|
||||
|
||||
ALIST_ARCH_NAME=amd64
|
||||
if [ "$sysArch" == "arm64" ];then
|
||||
if [ "$sysArch" = "arm64" ];then
|
||||
ALIST_ARCH_NAME=arm64
|
||||
elif [ "$sysArch" == "x86_64" ]; then
|
||||
elif [ "$sysArch" = "x86_64" ]; then
|
||||
ALIST_ARCH_NAME=amd64
|
||||
elif [ "$sysArch" == "aarch64" ]; then
|
||||
elif [ "$sysArch" = "aarch64" ]; then
|
||||
ALIST_ARCH_NAME=arm64
|
||||
fi
|
||||
|
||||
ALIST_NAME=linux
|
||||
if [ "$sysName" == "Darwin" ];then
|
||||
if [ "$sysName" = "Darwin" ];then
|
||||
ALIST_NAME=darwin
|
||||
fi
|
||||
|
||||
@@ -41,7 +41,7 @@ Install_App()
|
||||
ALIST_DIR=$serverPath/source/alist
|
||||
|
||||
if [ ! -f $ALIST_DIR/${FILE_TGZ} ];then
|
||||
wget -O $ALIST_DIR/${FILE_TGZ} https://github.com/alist-org/alist/releases/download/v${VERSION}/${FILE_TGZ}
|
||||
wget --no-check-certificate -O $ALIST_DIR/${FILE_TGZ} https://github.com/alist-org/alist/releases/download/v${VERSION}/${FILE_TGZ}
|
||||
fi
|
||||
|
||||
mkdir -p $serverPath/alist
|
||||
@@ -86,7 +86,7 @@ Uninstall_App()
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
|
||||
35
plugins/apache/check.sh
Executable file
35
plugins/apache/check.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin
|
||||
export PATH
|
||||
|
||||
# OpenResty服务名称
|
||||
service_name="openresty"
|
||||
|
||||
# 检查OpenResty是否正在运行
|
||||
if systemctl is-active --quiet "$service_name"; then
|
||||
# 检查是否存在僵尸进程
|
||||
zombie_processes=$(ps -ef | grep -i openresty | grep -v grep | awk '{print $2}' | xargs ps -o state= -p 2>/dev/null | grep -c Z)
|
||||
if [ "$zombie_processes" -gt 0 ]; then
|
||||
echo "kill nginx 僵尸进程"
|
||||
ps -ef|grep nginx| grep -v grep| awk '{print $2}' | xargs kill -9
|
||||
echo "检测到OpenResty僵尸进程,正在重启服务..."
|
||||
systemctl restart "$service_name"
|
||||
echo "服务已重启"
|
||||
else
|
||||
echo "OpenResty运行正常"
|
||||
fi
|
||||
else
|
||||
echo "kill nginx"
|
||||
ps -ef|grep nginx| grep -v grep| awk '{print $2}' | xargs kill -9
|
||||
echo "OpenResty未运行,正在启动服务..."
|
||||
systemctl start "$service_name"
|
||||
echo "服务已启动"
|
||||
fi
|
||||
|
||||
NGINX_IDS=`ps -ef|grep nginx | grep -v grep| awk '{print $2}'`
|
||||
if [ "$NGINX_IDS" = "" ];then
|
||||
ps -ef|grep nginx| grep -v grep| awk '{print $2}' | xargs kill -9
|
||||
systemctl start "$service_name"
|
||||
echo "OpenResty未运行,正在启动服务..."
|
||||
fi
|
||||
|
||||
57
plugins/apache/conf/httpd-mpm.conf
Normal file
57
plugins/apache/conf/httpd-mpm.conf
Normal file
@@ -0,0 +1,57 @@
|
||||
<IfModule !mpm_netware_module>
|
||||
PidFile "logs/httpd.pid"
|
||||
</IfModule>
|
||||
|
||||
<IfModule mpm_prefork_module>
|
||||
StartServers 5
|
||||
MinSpareServers 5
|
||||
MaxSpareServers 10
|
||||
MaxRequestWorkers 250
|
||||
MaxConnectionsPerChild 0
|
||||
</IfModule>
|
||||
|
||||
<IfModule mpm_worker_module>
|
||||
StartServers 3
|
||||
MinSpareThreads 75
|
||||
MaxSpareThreads 250
|
||||
ThreadsPerChild 25
|
||||
MaxRequestWorkers 400
|
||||
MaxConnectionsPerChild 0
|
||||
</IfModule>
|
||||
|
||||
<IfModule mpm_event_module>
|
||||
StartServers 3
|
||||
MinSpareThreads 75
|
||||
MaxSpareThreads 250
|
||||
ThreadsPerChild 25
|
||||
MaxRequestWorkers 400
|
||||
MaxConnectionsPerChild 0
|
||||
</IfModule>
|
||||
|
||||
<IfModule mpm_netware_module>
|
||||
ThreadStackSize 65536
|
||||
StartThreads 250
|
||||
MinSpareThreads 25
|
||||
MaxSpareThreads 250
|
||||
MaxThreads 1000
|
||||
MaxConnectionsPerChild 0
|
||||
</IfModule>
|
||||
|
||||
<IfModule mpm_mpmt_os2_module>
|
||||
StartServers 2
|
||||
MinSpareThreads 5
|
||||
MaxSpareThreads 10
|
||||
MaxConnectionsPerChild 0
|
||||
</IfModule>
|
||||
|
||||
<IfModule mpm_winnt_module>
|
||||
ThreadsPerChild 150
|
||||
MaxConnectionsPerChild 0
|
||||
</IfModule>
|
||||
|
||||
<IfModule !mpm_netware_module>
|
||||
MaxMemFree 2048
|
||||
</IfModule>
|
||||
<IfModule mpm_netware_module>
|
||||
MaxMemFree 100
|
||||
</IfModule>
|
||||
105
plugins/apache/conf/httpd.conf
Normal file
105
plugins/apache/conf/httpd.conf
Normal file
@@ -0,0 +1,105 @@
|
||||
ServerRoot "{$SERVER_PATH}/apache/httpd"
|
||||
Listen 8280
|
||||
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module modules/mod_authz_user.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
LoadModule access_compat_module modules/mod_access_compat.so
|
||||
LoadModule auth_basic_module modules/mod_auth_basic.so
|
||||
LoadModule reqtimeout_module modules/mod_reqtimeout.so
|
||||
LoadModule filter_module modules/mod_filter.so
|
||||
LoadModule brotli_module modules/mod_brotli.so
|
||||
LoadModule mime_module modules/mod_mime.so
|
||||
LoadModule log_config_module modules/mod_log_config.so
|
||||
LoadModule env_module modules/mod_env.so
|
||||
LoadModule expires_module modules/mod_expires.so
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
LoadModule unique_id_module modules/mod_unique_id.so
|
||||
LoadModule setenvif_module modules/mod_setenvif.so
|
||||
LoadModule version_module modules/mod_version.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
LoadModule status_module modules/mod_status.so
|
||||
LoadModule dir_module modules/mod_dir.so
|
||||
LoadModule alias_module modules/mod_alias.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
<IfModule unixd_module>
|
||||
User daemon
|
||||
Group daemon
|
||||
</IfModule>
|
||||
|
||||
ServerAdmin you@example.com
|
||||
ServerName localhost:8280
|
||||
|
||||
<Directory />
|
||||
AllowOverride none
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
DocumentRoot "{$SERVER_PATH}/apache/httpd/htdocs"
|
||||
<Directory "{$SERVER_PATH}/apache/httpd/htdocs">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<IfModule dir_module>
|
||||
DirectoryIndex index.html
|
||||
</IfModule>
|
||||
|
||||
<Files ".ht*">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
ErrorLog "logs/error.log"
|
||||
LogLevel warn
|
||||
|
||||
<IfModule log_config_module>
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b" common
|
||||
|
||||
<IfModule logio_module>
|
||||
# You need to enable mod_logio.c to use %I and %O
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
|
||||
</IfModule>
|
||||
|
||||
CustomLog "logs/access.log" common
|
||||
</IfModule>
|
||||
|
||||
<IfModule alias_module>
|
||||
ScriptAlias /cgi-bin/ "{$SERVER_PATH}/apache/httpd/cgi-bin/"
|
||||
</IfModule>
|
||||
|
||||
<IfModule cgid_module>
|
||||
</IfModule>
|
||||
|
||||
<Directory "{$SERVER_PATH}/apache/httpd/cgi-bin">
|
||||
AllowOverride None
|
||||
Options None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<IfModule headers_module>
|
||||
RequestHeader unset Proxy early
|
||||
</IfModule>
|
||||
|
||||
<IfModule mime_module>
|
||||
AddType application/x-compress .Z
|
||||
AddType application/x-gzip .gz .tgz
|
||||
</IfModule>
|
||||
|
||||
EnableSendfile on
|
||||
|
||||
Include conf/extra/httpd-info.conf
|
||||
Include conf/extra/httpd-mpm.conf
|
||||
|
||||
<IfModule ssl_module>
|
||||
SSLRandomSeed startup builtin
|
||||
SSLRandomSeed connect builtin
|
||||
</IfModule>
|
||||
|
||||
|
||||
|
||||
506
plugins/apache/conf/httpd.conf.tpl
Normal file
506
plugins/apache/conf/httpd.conf.tpl
Normal file
@@ -0,0 +1,506 @@
|
||||
#
|
||||
# This is the main Apache HTTP server configuration file. It contains the
|
||||
# configuration directives that give the server its instructions.
|
||||
# See <URL:http://httpd.apache.org/docs/2.4/> for detailed information.
|
||||
# In particular, see
|
||||
# <URL:http://httpd.apache.org/docs/2.4/mod/directives.html>
|
||||
# for a discussion of each configuration directive.
|
||||
#
|
||||
# Do NOT simply read the instructions in here without understanding
|
||||
# what they do. They're here only as hints or reminders. If you are unsure
|
||||
# consult the online docs. You have been warned.
|
||||
#
|
||||
# Configuration and logfile names: If the filenames you specify for many
|
||||
# of the server's control files begin with "/" (or "drive:/" for Win32), the
|
||||
# server will use that explicit path. If the filenames do *not* begin
|
||||
# with "/", the value of ServerRoot is prepended -- so "logs/access_log"
|
||||
# with ServerRoot set to "/usr/local/apache2" will be interpreted by the
|
||||
# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log"
|
||||
# will be interpreted as '/logs/access_log'.
|
||||
|
||||
#
|
||||
# ServerRoot: The top of the directory tree under which the server's
|
||||
# configuration, error, and log files are kept.
|
||||
#
|
||||
# Do not add a slash at the end of the directory path. If you point
|
||||
# ServerRoot at a non-local disk, be sure to specify a local disk on the
|
||||
# Mutex directive, if file-based mutexes are used. If you wish to share the
|
||||
# same ServerRoot for multiple httpd daemons, you will need to change at
|
||||
# least PidFile.
|
||||
#
|
||||
ServerRoot "/Users/midoks/Desktop/mwdev/server/apache/httpd"
|
||||
|
||||
#
|
||||
# Mutex: Allows you to set the mutex mechanism and mutex file directory
|
||||
# for individual mutexes, or change the global defaults
|
||||
#
|
||||
# Uncomment and change the directory if mutexes are file-based and the default
|
||||
# mutex file directory is not on a local disk or is not appropriate for some
|
||||
# other reason.
|
||||
#
|
||||
# Mutex default:logs
|
||||
|
||||
#
|
||||
# Listen: Allows you to bind Apache to specific IP addresses and/or
|
||||
# ports, instead of the default. See also the <VirtualHost>
|
||||
# directive.
|
||||
#
|
||||
# Change this to Listen on specific IP addresses as shown below to
|
||||
# prevent Apache from glomming onto all bound IP addresses.
|
||||
#
|
||||
#Listen 12.34.56.78:80
|
||||
Listen 80
|
||||
|
||||
#
|
||||
# Dynamic Shared Object (DSO) Support
|
||||
#
|
||||
# To be able to use the functionality of a module which was built as a DSO you
|
||||
# have to place corresponding `LoadModule' lines at this location so the
|
||||
# directives contained in it are actually available _before_ they are used.
|
||||
# Statically compiled modules (those listed by `httpd -l') do not need
|
||||
# to be loaded here.
|
||||
#
|
||||
# Example:
|
||||
# LoadModule foo_module modules/mod_foo.so
|
||||
#
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
#LoadModule authn_dbm_module modules/mod_authn_dbm.so
|
||||
#LoadModule authn_anon_module modules/mod_authn_anon.so
|
||||
#LoadModule authn_dbd_module modules/mod_authn_dbd.so
|
||||
#LoadModule authn_socache_module modules/mod_authn_socache.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module modules/mod_authz_user.so
|
||||
#LoadModule authz_dbm_module modules/mod_authz_dbm.so
|
||||
#LoadModule authz_owner_module modules/mod_authz_owner.so
|
||||
#LoadModule authz_dbd_module modules/mod_authz_dbd.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
LoadModule access_compat_module modules/mod_access_compat.so
|
||||
LoadModule auth_basic_module modules/mod_auth_basic.so
|
||||
#LoadModule auth_form_module modules/mod_auth_form.so
|
||||
#LoadModule auth_digest_module modules/mod_auth_digest.so
|
||||
#LoadModule allowmethods_module modules/mod_allowmethods.so
|
||||
#LoadModule file_cache_module modules/mod_file_cache.so
|
||||
#LoadModule cache_module modules/mod_cache.so
|
||||
#LoadModule cache_disk_module modules/mod_cache_disk.so
|
||||
#LoadModule cache_socache_module modules/mod_cache_socache.so
|
||||
#LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
|
||||
#LoadModule socache_dbm_module modules/mod_socache_dbm.so
|
||||
#LoadModule socache_memcache_module modules/mod_socache_memcache.so
|
||||
#LoadModule socache_redis_module modules/mod_socache_redis.so
|
||||
#LoadModule watchdog_module modules/mod_watchdog.so
|
||||
#LoadModule macro_module modules/mod_macro.so
|
||||
#LoadModule dbd_module modules/mod_dbd.so
|
||||
#LoadModule dumpio_module modules/mod_dumpio.so
|
||||
#LoadModule buffer_module modules/mod_buffer.so
|
||||
#LoadModule ratelimit_module modules/mod_ratelimit.so
|
||||
LoadModule reqtimeout_module modules/mod_reqtimeout.so
|
||||
#LoadModule ext_filter_module modules/mod_ext_filter.so
|
||||
#LoadModule request_module modules/mod_request.so
|
||||
#LoadModule include_module modules/mod_include.so
|
||||
LoadModule filter_module modules/mod_filter.so
|
||||
#LoadModule substitute_module modules/mod_substitute.so
|
||||
#LoadModule sed_module modules/mod_sed.so
|
||||
#LoadModule brotli_module modules/mod_brotli.so
|
||||
LoadModule mime_module modules/mod_mime.so
|
||||
LoadModule log_config_module modules/mod_log_config.so
|
||||
#LoadModule log_debug_module modules/mod_log_debug.so
|
||||
#LoadModule logio_module modules/mod_logio.so
|
||||
LoadModule env_module modules/mod_env.so
|
||||
#LoadModule expires_module modules/mod_expires.so
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
#LoadModule unique_id_module modules/mod_unique_id.so
|
||||
LoadModule setenvif_module modules/mod_setenvif.so
|
||||
LoadModule version_module modules/mod_version.so
|
||||
#LoadModule remoteip_module modules/mod_remoteip.so
|
||||
#LoadModule proxy_module modules/mod_proxy.so
|
||||
#LoadModule proxy_connect_module modules/mod_proxy_connect.so
|
||||
#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
|
||||
#LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
|
||||
#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
|
||||
#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
|
||||
#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
|
||||
#LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
|
||||
#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
|
||||
#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
|
||||
#LoadModule proxy_express_module modules/mod_proxy_express.so
|
||||
#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
|
||||
#LoadModule session_module modules/mod_session.so
|
||||
#LoadModule session_cookie_module modules/mod_session_cookie.so
|
||||
#LoadModule session_dbd_module modules/mod_session_dbd.so
|
||||
#LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
|
||||
#LoadModule ssl_module modules/mod_ssl.so
|
||||
#LoadModule http2_module modules/mod_http2.so
|
||||
#LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
|
||||
#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
|
||||
#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
|
||||
#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
#LoadModule dav_module modules/mod_dav.so
|
||||
LoadModule status_module modules/mod_status.so
|
||||
LoadModule autoindex_module modules/mod_autoindex.so
|
||||
#LoadModule info_module modules/mod_info.so
|
||||
#LoadModule cgid_module modules/mod_cgid.so
|
||||
#LoadModule dav_fs_module modules/mod_dav_fs.so
|
||||
#LoadModule vhost_alias_module modules/mod_vhost_alias.so
|
||||
#LoadModule negotiation_module modules/mod_negotiation.so
|
||||
LoadModule dir_module modules/mod_dir.so
|
||||
#LoadModule actions_module modules/mod_actions.so
|
||||
#LoadModule speling_module modules/mod_speling.so
|
||||
#LoadModule userdir_module modules/mod_userdir.so
|
||||
LoadModule alias_module modules/mod_alias.so
|
||||
#LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
<IfModule unixd_module>
|
||||
#
|
||||
# If you wish httpd to run as a different user or group, you must run
|
||||
# httpd as root initially and it will switch.
|
||||
#
|
||||
# User/Group: The name (or #number) of the user/group to run httpd as.
|
||||
# It is usually good practice to create a dedicated user and group for
|
||||
# running httpd, as with most system services.
|
||||
#
|
||||
User daemon
|
||||
Group daemon
|
||||
|
||||
</IfModule>
|
||||
|
||||
# 'Main' server configuration
|
||||
#
|
||||
# The directives in this section set up the values used by the 'main'
|
||||
# server, which responds to any requests that aren't handled by a
|
||||
# <VirtualHost> definition. These values also provide defaults for
|
||||
# any <VirtualHost> containers you may define later in the file.
|
||||
#
|
||||
# All of these directives may appear inside <VirtualHost> containers,
|
||||
# in which case these default settings will be overridden for the
|
||||
# virtual host being defined.
|
||||
#
|
||||
|
||||
#
|
||||
# ServerAdmin: Your address, where problems with the server should be
|
||||
# e-mailed. This address appears on some server-generated pages, such
|
||||
# as error documents. e.g. admin@your-domain.com
|
||||
#
|
||||
ServerAdmin you@example.com
|
||||
|
||||
#
|
||||
# ServerName gives the name and port that the server uses to identify itself.
|
||||
# This can often be determined automatically, but we recommend you specify
|
||||
# it explicitly to prevent problems during startup.
|
||||
#
|
||||
# If your host doesn't have a registered DNS name, enter its IP address here.
|
||||
#
|
||||
#ServerName www.example.com:80
|
||||
|
||||
#
|
||||
# Deny access to the entirety of your server's filesystem. You must
|
||||
# explicitly permit access to web content directories in other
|
||||
# <Directory> blocks below.
|
||||
#
|
||||
<Directory />
|
||||
AllowOverride none
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
#
|
||||
# Note that from this point forward you must specifically allow
|
||||
# particular features to be enabled - so if something's not working as
|
||||
# you might expect, make sure that you have specifically enabled it
|
||||
# below.
|
||||
#
|
||||
|
||||
#
|
||||
# DocumentRoot: The directory out of which you will serve your
|
||||
# documents. By default, all requests are taken from this directory, but
|
||||
# symbolic links and aliases may be used to point to other locations.
|
||||
#
|
||||
DocumentRoot "/Users/midoks/Desktop/mwdev/server/apache/httpd/htdocs"
|
||||
<Directory "/Users/midoks/Desktop/mwdev/server/apache/httpd/htdocs">
|
||||
#
|
||||
# Possible values for the Options directive are "None", "All",
|
||||
# or any combination of:
|
||||
# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
|
||||
#
|
||||
# Note that "MultiViews" must be named *explicitly* --- "Options All"
|
||||
# doesn't give it to you.
|
||||
#
|
||||
# The Options directive is both complicated and important. Please see
|
||||
# http://httpd.apache.org/docs/2.4/mod/core.html#options
|
||||
# for more information.
|
||||
#
|
||||
Options Indexes FollowSymLinks
|
||||
|
||||
#
|
||||
# AllowOverride controls what directives may be placed in .htaccess files.
|
||||
# It can be "All", "None", or any combination of the keywords:
|
||||
# AllowOverride FileInfo AuthConfig Limit
|
||||
#
|
||||
AllowOverride None
|
||||
|
||||
#
|
||||
# Controls who can get stuff from this server.
|
||||
#
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
#
|
||||
# DirectoryIndex: sets the file that Apache will serve if a directory
|
||||
# is requested.
|
||||
#
|
||||
<IfModule dir_module>
|
||||
DirectoryIndex index.html
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# The following lines prevent .htaccess and .htpasswd files from being
|
||||
# viewed by Web clients.
|
||||
#
|
||||
<Files ".ht*">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
#
|
||||
# ErrorLog: The location of the error log file.
|
||||
# If you do not specify an ErrorLog directive within a <VirtualHost>
|
||||
# container, error messages relating to that virtual host will be
|
||||
# logged here. If you *do* define an error logfile for a <VirtualHost>
|
||||
# container, that host's errors will be logged there and not here.
|
||||
#
|
||||
ErrorLog "logs/error_log"
|
||||
|
||||
#
|
||||
# LogLevel: Control the number of messages logged to the error_log.
|
||||
# Possible values include: debug, info, notice, warn, error, crit,
|
||||
# alert, emerg.
|
||||
#
|
||||
LogLevel warn
|
||||
|
||||
<IfModule log_config_module>
|
||||
#
|
||||
# The following directives define some format nicknames for use with
|
||||
# a CustomLog directive (see below).
|
||||
#
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b" common
|
||||
|
||||
<IfModule logio_module>
|
||||
# You need to enable mod_logio.c to use %I and %O
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# The location and format of the access logfile (Common Logfile Format).
|
||||
# If you do not define any access logfiles within a <VirtualHost>
|
||||
# container, they will be logged here. Contrariwise, if you *do*
|
||||
# define per-<VirtualHost> access logfiles, transactions will be
|
||||
# logged therein and *not* in this file.
|
||||
#
|
||||
CustomLog "logs/access_log" common
|
||||
|
||||
#
|
||||
# If you prefer a logfile with access, agent, and referer information
|
||||
# (Combined Logfile Format) you can use the following directive.
|
||||
#
|
||||
#CustomLog "logs/access_log" combined
|
||||
</IfModule>
|
||||
|
||||
<IfModule alias_module>
|
||||
#
|
||||
# Redirect: Allows you to tell clients about documents that used to
|
||||
# exist in your server's namespace, but do not anymore. The client
|
||||
# will make a new request for the document at its new location.
|
||||
# Example:
|
||||
# Redirect permanent /foo http://www.example.com/bar
|
||||
|
||||
#
|
||||
# Alias: Maps web paths into filesystem paths and is used to
|
||||
# access content that does not live under the DocumentRoot.
|
||||
# Example:
|
||||
# Alias /webpath /full/filesystem/path
|
||||
#
|
||||
# If you include a trailing / on /webpath then the server will
|
||||
# require it to be present in the URL. You will also likely
|
||||
# need to provide a <Directory> section to allow access to
|
||||
# the filesystem path.
|
||||
|
||||
#
|
||||
# ScriptAlias: This controls which directories contain server scripts.
|
||||
# ScriptAliases are essentially the same as Aliases, except that
|
||||
# documents in the target directory are treated as applications and
|
||||
# run by the server when requested rather than as documents sent to the
|
||||
# client. The same rules about trailing "/" apply to ScriptAlias
|
||||
# directives as to Alias.
|
||||
#
|
||||
ScriptAlias /cgi-bin/ "/Users/midoks/Desktop/mwdev/server/apache/httpd/cgi-bin/"
|
||||
|
||||
</IfModule>
|
||||
|
||||
<IfModule cgid_module>
|
||||
#
|
||||
# ScriptSock: On threaded servers, designate the path to the UNIX
|
||||
# socket used to communicate with the CGI daemon of mod_cgid.
|
||||
#
|
||||
#Scriptsock cgisock
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# "/Users/midoks/Desktop/mwdev/server/apache/httpd/cgi-bin" should be changed to whatever your ScriptAliased
|
||||
# CGI directory exists, if you have that configured.
|
||||
#
|
||||
<Directory "/Users/midoks/Desktop/mwdev/server/apache/httpd/cgi-bin">
|
||||
AllowOverride None
|
||||
Options None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<IfModule headers_module>
|
||||
#
|
||||
# Avoid passing HTTP_PROXY environment to CGI's on this or any proxied
|
||||
# backend servers which have lingering "httpoxy" defects.
|
||||
# 'Proxy' request header is undefined by the IETF, not listed by IANA
|
||||
#
|
||||
RequestHeader unset Proxy early
|
||||
</IfModule>
|
||||
|
||||
<IfModule mime_module>
|
||||
#
|
||||
# TypesConfig points to the file containing the list of mappings from
|
||||
# filename extension to MIME-type.
|
||||
#
|
||||
TypesConfig conf/mime.types
|
||||
|
||||
#
|
||||
# AddType allows you to add to or override the MIME configuration
|
||||
# file specified in TypesConfig for specific file types.
|
||||
#
|
||||
#AddType application/x-gzip .tgz
|
||||
#
|
||||
# AddEncoding allows you to have certain browsers uncompress
|
||||
# information on the fly. Note: Not all browsers support this.
|
||||
#
|
||||
#AddEncoding x-compress .Z
|
||||
#AddEncoding x-gzip .gz .tgz
|
||||
#
|
||||
# If the AddEncoding directives above are commented-out, then you
|
||||
# probably should define those extensions to indicate media types:
|
||||
#
|
||||
AddType application/x-compress .Z
|
||||
AddType application/x-gzip .gz .tgz
|
||||
|
||||
#
|
||||
# AddHandler allows you to map certain file extensions to "handlers":
|
||||
# actions unrelated to filetype. These can be either built into the server
|
||||
# or added with the Action directive (see below)
|
||||
#
|
||||
# To use CGI scripts outside of ScriptAliased directories:
|
||||
# (You will also need to add "ExecCGI" to the "Options" directive.)
|
||||
#
|
||||
#AddHandler cgi-script .cgi
|
||||
|
||||
# For type maps (negotiated resources):
|
||||
#AddHandler type-map var
|
||||
|
||||
#
|
||||
# Filters allow you to process content before it is sent to the client.
|
||||
#
|
||||
# To parse .shtml files for server-side includes (SSI):
|
||||
# (You will also need to add "Includes" to the "Options" directive.)
|
||||
#
|
||||
#AddType text/html .shtml
|
||||
#AddOutputFilter INCLUDES .shtml
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# The mod_mime_magic module allows the server to use various hints from the
|
||||
# contents of the file itself to determine its type. The MIMEMagicFile
|
||||
# directive tells the module where the hint definitions are located.
|
||||
#
|
||||
#MIMEMagicFile conf/magic
|
||||
|
||||
#
|
||||
# Customizable error responses come in three flavors:
|
||||
# 1) plain text 2) local redirects 3) external redirects
|
||||
#
|
||||
# Some examples:
|
||||
#ErrorDocument 500 "The server made a boo boo."
|
||||
#ErrorDocument 404 /missing.html
|
||||
#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
|
||||
#ErrorDocument 402 http://www.example.com/subscription_info.html
|
||||
#
|
||||
|
||||
#
|
||||
# MaxRanges: Maximum number of Ranges in a request before
|
||||
# returning the entire resource, or one of the special
|
||||
# values 'default', 'none' or 'unlimited'.
|
||||
# Default setting is to accept 200 Ranges.
|
||||
#MaxRanges unlimited
|
||||
|
||||
#
|
||||
# EnableMMAP and EnableSendfile: On systems that support it,
|
||||
# memory-mapping or the sendfile syscall may be used to deliver
|
||||
# files. This usually improves server performance, but must
|
||||
# be turned off when serving from networked-mounted
|
||||
# filesystems or if support for these functions is otherwise
|
||||
# broken on your system.
|
||||
# Defaults: EnableMMAP On, EnableSendfile Off
|
||||
#
|
||||
#EnableMMAP off
|
||||
#EnableSendfile on
|
||||
|
||||
# Supplemental configuration
|
||||
#
|
||||
# The configuration files in the conf/extra/ directory can be
|
||||
# included to add extra features or to modify the default configuration of
|
||||
# the server, or you may simply copy their contents here and change as
|
||||
# necessary.
|
||||
|
||||
# Server-pool management (MPM specific)
|
||||
#Include conf/extra/httpd-mpm.conf
|
||||
|
||||
# Multi-language error messages
|
||||
#Include conf/extra/httpd-multilang-errordoc.conf
|
||||
|
||||
# Fancy directory listings
|
||||
#Include conf/extra/httpd-autoindex.conf
|
||||
|
||||
# Language settings
|
||||
#Include conf/extra/httpd-languages.conf
|
||||
|
||||
# User home directories
|
||||
#Include conf/extra/httpd-userdir.conf
|
||||
|
||||
# Real-time info on requests and configuration
|
||||
#Include conf/extra/httpd-info.conf
|
||||
|
||||
# Virtual hosts
|
||||
#Include conf/extra/httpd-vhosts.conf
|
||||
|
||||
# Local access to the Apache HTTP Server Manual
|
||||
#Include conf/extra/httpd-manual.conf
|
||||
|
||||
# Distributed authoring and versioning (WebDAV)
|
||||
#Include conf/extra/httpd-dav.conf
|
||||
|
||||
# Various default settings
|
||||
#Include conf/extra/httpd-default.conf
|
||||
|
||||
# Configure mod_proxy_html to understand HTML4/XHTML1
|
||||
<IfModule proxy_html_module>
|
||||
Include conf/extra/proxy-html.conf
|
||||
</IfModule>
|
||||
|
||||
# Secure (SSL/TLS) connections
|
||||
#Include conf/extra/httpd-ssl.conf
|
||||
#
|
||||
# Note: The following must must be present to support
|
||||
# starting without SSL on platforms with no /dev/random equivalent
|
||||
# but a statically compiled-in mod_ssl.
|
||||
#
|
||||
<IfModule ssl_module>
|
||||
SSLRandomSeed startup builtin
|
||||
SSLRandomSeed connect builtin
|
||||
</IfModule>
|
||||
|
||||
BIN
plugins/apache/ico.png
Normal file
BIN
plugins/apache/ico.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
25
plugins/apache/index.html
Executable file
25
plugins/apache/index.html
Executable file
@@ -0,0 +1,25 @@
|
||||
<div class="bt-form">
|
||||
<!-- <div class='plugin_version'></div> -->
|
||||
<div class="bt-w-main">
|
||||
<div class="bt-w-menu">
|
||||
<p class="bgw" onclick="httpPluginService('apache',$('.plugin_version').attr('version'));">服务</p>
|
||||
<p onclick="pluginInitD('apache', $('.plugin_version').attr('version'));">自启动</p>
|
||||
<p onclick="pluginConfig('apache');">配置修改</p>
|
||||
<p onclick="getHttpdStatus();">负载状态</p>
|
||||
<p onclick="setOpCfg();">MPM性能调整</p>
|
||||
<p onclick="pluginLogs('apache');">错误日志</p>
|
||||
<p onclick="otherFunc();">维护功能</p>
|
||||
</div>
|
||||
<div class="bt-w-con pd15">
|
||||
<div class="soft-man-con" style="height: 500px; overflow-y: auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
// resetPluginWinHeight(600);
|
||||
$.getScript( "/plugins/file?name=apache&f=js/httpd.js", function(){
|
||||
httpPluginService('apache');
|
||||
});
|
||||
</script>
|
||||
593
plugins/apache/index.py
Executable file
593
plugins/apache/index.py
Executable file
@@ -0,0 +1,593 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'apache'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getInitDFile():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return '/tmp/' + getPluginName()
|
||||
|
||||
if current_os.startswith('freebsd'):
|
||||
return '/etc/rc.d/' + getPluginName()
|
||||
|
||||
return '/etc/init.d/' + getPluginName()
|
||||
|
||||
|
||||
def getArgs():
|
||||
args = sys.argv[2:]
|
||||
# print(args)
|
||||
tmp = {}
|
||||
args_len = len(args)
|
||||
|
||||
if args_len == 1:
|
||||
t = args[0].strip('{').strip('}')
|
||||
t = t.split(':',2)
|
||||
tmp[t[0]] = t[1]
|
||||
elif args_len > 1:
|
||||
for i in range(len(args)):
|
||||
t = args[i].split(':',2)
|
||||
tmp[t[0]] = t[1]
|
||||
# print(tmp)
|
||||
return tmp
|
||||
|
||||
|
||||
def checkArgs(data, ck=[]):
|
||||
for i in range(len(ck)):
|
||||
if not ck[i] in data:
|
||||
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
|
||||
return (True, mw.returnJson(True, 'ok'))
|
||||
|
||||
|
||||
def clearTemp():
|
||||
path_bin = getServerDir() + "/httpd"
|
||||
|
||||
|
||||
def getConf():
|
||||
path = getServerDir() + "/httpd/conf/httpd.conf"
|
||||
return path
|
||||
|
||||
|
||||
def getConfMpm():
|
||||
path = getServerDir() + "/httpd/conf/extra/httpd-mpm.conf"
|
||||
return path
|
||||
|
||||
|
||||
def getConfTpl():
|
||||
path = getPluginDir() + '/conf/httpd.conf'
|
||||
return path
|
||||
|
||||
|
||||
def getOs():
|
||||
data = {}
|
||||
data['os'] = mw.getOs()
|
||||
data['auth'] = True
|
||||
return mw.getJson(data)
|
||||
|
||||
|
||||
def getInitDTpl():
|
||||
path = getPluginDir() + "/init.d/httpd.tpl"
|
||||
return path
|
||||
|
||||
|
||||
def getPidFile():
|
||||
file = getConf()
|
||||
content = mw.readFile(file)
|
||||
rep = r'pid\s*(.*);'
|
||||
tmp = re.search(rep, content)
|
||||
return tmp.groups()[0].strip()
|
||||
|
||||
|
||||
def getFileOwner(filename):
|
||||
import pwd
|
||||
stat = os.lstat(filename)
|
||||
uid = stat.st_uid
|
||||
pw = pwd.getpwuid(uid)
|
||||
return pw.pw_name
|
||||
|
||||
|
||||
def checkAuthEq(file, owner='root'):
|
||||
fowner = getFileOwner(file)
|
||||
if (fowner == owner):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def confReplace():
|
||||
service_path = mw.getServerDir()
|
||||
content = mw.readFile(getConfTpl())
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
|
||||
# 主配置文件
|
||||
nconf = getServerDir() + '/httpd/conf/httpd.conf'
|
||||
mw.writeFile(nconf, content)
|
||||
|
||||
|
||||
def initDreplace():
|
||||
|
||||
file_tpl = getInitDTpl()
|
||||
service_path = mw.getServerDir()
|
||||
|
||||
initD_path = getServerDir() + '/init.d'
|
||||
|
||||
# OpenResty is not installed
|
||||
if not os.path.exists(getServerDir()):
|
||||
print("ok")
|
||||
exit(0)
|
||||
|
||||
# init.d
|
||||
file_bin = initD_path + '/' + getPluginName()
|
||||
if not os.path.exists(initD_path):
|
||||
os.mkdir(initD_path)
|
||||
|
||||
# initd replace
|
||||
content = mw.readFile(file_tpl)
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
mw.writeFile(file_bin, content)
|
||||
mw.execShell('chmod +x ' + file_bin)
|
||||
|
||||
# config replace
|
||||
confReplace()
|
||||
|
||||
# systemd
|
||||
# /usr/lib/systemd/system
|
||||
systemDir = mw.systemdCfgDir()
|
||||
systemService = systemDir + '/httpd.service'
|
||||
if os.path.exists(systemDir) and not os.path.exists(systemService):
|
||||
systemServiceTpl = getPluginDir() + '/init.d/httpd.service.tpl'
|
||||
se_content = mw.readFile(systemServiceTpl)
|
||||
se_content = se_content.replace('{$SERVER_PATH}', service_path)
|
||||
mw.writeFile(systemService, se_content)
|
||||
mw.execShell('systemctl daemon-reload')
|
||||
|
||||
return file_bin
|
||||
|
||||
|
||||
def status():
|
||||
cmd = "ps -ef|grep 'httpd' |grep -v grep | grep -v python | awk '{print $2}'"
|
||||
data = mw.execShell(cmd)
|
||||
if data[0] == '':
|
||||
return 'stop'
|
||||
return 'start'
|
||||
|
||||
|
||||
def restyOp(method):
|
||||
file = initDreplace()
|
||||
|
||||
# 启动时,先检查一下配置文件
|
||||
check = getServerDir() + "/httpd/bin/httpd -t"
|
||||
check_data = mw.execShell(check)
|
||||
if not check_data[1].find('Syntax OK') > -1:
|
||||
return check_data[1]
|
||||
|
||||
current_os = mw.getOs()
|
||||
if current_os == "darwin":
|
||||
data = mw.execShell(file + ' ' + method)
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
|
||||
if current_os.startswith("freebsd"):
|
||||
mw.execShell('service httpd '+method)
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
|
||||
data = mw.execShell('systemctl ' + method + ' httpd')
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
|
||||
|
||||
def op_submit_systemctl_restart():
|
||||
current_os = mw.getOs()
|
||||
if current_os.startswith("freebsd"):
|
||||
mw.execShell('service httpd restart')
|
||||
return True
|
||||
|
||||
mw.execShell('systemctl restart httpd')
|
||||
return True
|
||||
|
||||
|
||||
def op_submit_init_restart(file):
|
||||
mw.execShell(file + ' restart')
|
||||
|
||||
|
||||
def restyOp_restart():
|
||||
file = initDreplace()
|
||||
|
||||
# 启动时,先检查一下配置文件
|
||||
check = getServerDir() + "/httpd/bin/httpd -t"
|
||||
check_data = mw.execShell(check)
|
||||
if not check_data[1].find('Syntax OK') > -1:
|
||||
return 'ERROR: 配置出错<br><a style="color:red;">' + check_data[1].replace("\n", '<br>') + '</a>'
|
||||
|
||||
if not mw.isAppleSystem():
|
||||
threading.Timer(2, op_submit_systemctl_restart).start()
|
||||
return 'ok'
|
||||
|
||||
threading.Timer(2, op_submit_init_restart, args=(file,)).start()
|
||||
return 'ok'
|
||||
|
||||
|
||||
def start():
|
||||
return restyOp('start')
|
||||
|
||||
|
||||
def stop():
|
||||
r = restyOp('stop')
|
||||
|
||||
mw.execShell("ps -ef|grep httpd | grep -v grep | awk '{print $2}'|xargs -r kill")
|
||||
return r
|
||||
|
||||
|
||||
def restart():
|
||||
return restyOp_restart()
|
||||
|
||||
|
||||
def reload():
|
||||
confReplace()
|
||||
return restyOp('reload')
|
||||
|
||||
|
||||
def initdStatus():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return "Apple Computer does not support"
|
||||
|
||||
if current_os.startswith('freebsd'):
|
||||
initd_bin = getInitDFile()
|
||||
if os.path.exists(initd_bin):
|
||||
return 'ok'
|
||||
|
||||
shell_cmd = 'systemctl status httpd | grep loaded | grep "enabled;"'
|
||||
data = mw.execShell(shell_cmd)
|
||||
if data[0] == '':
|
||||
return 'fail'
|
||||
return 'ok'
|
||||
|
||||
|
||||
def initdInstall():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return "Apple Computer does not support"
|
||||
|
||||
# freebsd initd install
|
||||
if current_os.startswith('freebsd'):
|
||||
import shutil
|
||||
source_bin = initDreplace()
|
||||
initd_bin = getInitDFile()
|
||||
shutil.copyfile(source_bin, initd_bin)
|
||||
mw.execShell('chmod +x ' + initd_bin)
|
||||
mw.execShell('sysrc httpd_enable="YES"')
|
||||
return 'ok'
|
||||
|
||||
mw.execShell('systemctl enable httpd')
|
||||
return 'ok'
|
||||
|
||||
|
||||
def initdUinstall():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return "Apple Computer does not support"
|
||||
|
||||
if current_os.startswith('freebsd'):
|
||||
initd_bin = getInitDFile()
|
||||
os.remove(initd_bin)
|
||||
mw.execShell('sysrc httpd_enable="NO"')
|
||||
return 'ok'
|
||||
|
||||
mw.execShell('systemctl disable httpd')
|
||||
return 'ok'
|
||||
|
||||
def getHttpdStatusPort():
|
||||
conf = mw.getServerDir() + '/apache/httpd/conf/httpd.conf'
|
||||
content = mw.readFile(conf)
|
||||
if not content:
|
||||
return None
|
||||
rep = r'^\s*Listen\s*(?:\d+\.\d+\.\d+\.\d+:)?(\d+)' # 匹配非注释行的 Listen 指令,忽略大小写
|
||||
tmp = re.search(rep, content, re.IGNORECASE | re.MULTILINE)
|
||||
if tmp:
|
||||
port = tmp.groups()[0].strip()
|
||||
return port
|
||||
return None
|
||||
|
||||
|
||||
def runInfoDone(data):
|
||||
result = {}
|
||||
if not data:
|
||||
return result
|
||||
|
||||
# 解析服务器状态数据
|
||||
lines = data.strip().split('\n')
|
||||
for line in lines:
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
result[key] = value
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def runInfo():
|
||||
op_status = status()
|
||||
if op_status == 'stop':
|
||||
return mw.returnJson(False, "未启动!")
|
||||
|
||||
port = getHttpdStatusPort()
|
||||
if not port:
|
||||
return mw.returnJson(False, "无法获取端口信息!")
|
||||
|
||||
# 取Openresty负载状态
|
||||
try:
|
||||
url = 'http://127.0.0.1:%s/server-status?auto' % port
|
||||
result = mw.httpGet(url, timeout=3)
|
||||
data = runInfoDone(result)
|
||||
return mw.getJson(data)
|
||||
except Exception as e:
|
||||
try:
|
||||
url = 'http://' + mw.getHostAddr() + ':%s/server-status?auto' % port
|
||||
result = mw.httpGet(url)
|
||||
data = runInfoDone(result)
|
||||
return mw.getJson(data)
|
||||
except Exception as e:
|
||||
return mw.returnJson(False, "apache异常!")
|
||||
|
||||
except Exception as e:
|
||||
return mw.returnJson(False, "apache not started!")
|
||||
|
||||
|
||||
def errorLogPath():
|
||||
return getServerDir() + '/httpd/logs/error.log'
|
||||
|
||||
|
||||
def getCfg():
|
||||
cfg = getConfMpm()
|
||||
content = mw.readFile(cfg)
|
||||
|
||||
unitrep = "[kmgKMG]"
|
||||
|
||||
# 获取当前 MPM 模块
|
||||
mpm_module = ""
|
||||
mpm_match = re.search(r"mpm_(\w+)_module", content)
|
||||
if mpm_match:
|
||||
mpm_module = mpm_match.group(1)
|
||||
|
||||
# MPM 配置参数
|
||||
mpm_cfg_args = {
|
||||
"prefork": [
|
||||
{"name": "StartServers", "ps": "服务器进程启动数量", 'type': 2},
|
||||
{"name": "MinSpareServers", "ps": "保持空闲的最小服务器进程数", 'type': 2},
|
||||
{"name": "MaxSpareServers", "ps": "保持空闲的最大服务器进程数", 'type': 2},
|
||||
{"name": "MaxRequestWorkers", "ps": "允许启动的最大服务器进程数", 'type': 2},
|
||||
{"name": "MaxConnectionsPerChild", "ps": "服务器进程服务的最大连接数", 'type': 2},
|
||||
],
|
||||
"worker": [
|
||||
{"name": "StartServers", "ps": "初始服务器进程数", 'type': 2},
|
||||
{"name": "MinSpareThreads", "ps": "保持空闲的最小工作线程数", 'type': 2},
|
||||
{"name": "MaxSpareThreads", "ps": "保持空闲的最大工作线程数", 'type': 2},
|
||||
{"name": "ThreadsPerChild", "ps": "每个服务器进程的工作线程数", 'type': 2},
|
||||
{"name": "MaxRequestWorkers", "ps": "最大工作线程数", 'type': 2},
|
||||
{"name": "MaxConnectionsPerChild", "ps": "服务器进程服务的最大连接数", 'type': 2},
|
||||
],
|
||||
"event": [
|
||||
{"name": "StartServers", "ps": "初始服务器进程数", 'type': 2},
|
||||
{"name": "MinSpareThreads", "ps": "保持空闲的最小工作线程数", 'type': 2},
|
||||
{"name": "MaxSpareThreads", "ps": "保持空闲的最大工作线程数", 'type': 2},
|
||||
{"name": "ThreadsPerChild", "ps": "每个服务器进程的工作线程数", 'type': 2},
|
||||
{"name": "MaxRequestWorkers", "ps": "最大工作线程数", 'type': 2},
|
||||
{"name": "MaxConnectionsPerChild", "ps": "服务器进程服务的最大连接数", 'type': 2},
|
||||
],
|
||||
"netware": [
|
||||
{"name": "ThreadStackSize", "ps": "每个工作线程分配的堆栈大小", 'type': 2},
|
||||
{"name": "StartThreads", "ps": "服务器启动时启动的工作线程数", 'type': 2},
|
||||
{"name": "MinSpareThreads", "ps": "保持空闲的最小线程数", 'type': 2},
|
||||
{"name": "MaxSpareThreads", "ps": "保持空闲的最大线程数", 'type': 2},
|
||||
{"name": "MaxThreads", "ps": "同时活跃的最大工作线程数", 'type': 2},
|
||||
{"name": "MaxConnectionsPerChild", "ps": "线程服务的最大连接数", 'type': 2},
|
||||
],
|
||||
"mpmt_os2": [
|
||||
{"name": "StartServers", "ps": "维护的服务器进程数", 'type': 2},
|
||||
{"name": "MinSpareThreads", "ps": "每个进程的最小空闲线程数", 'type': 2},
|
||||
{"name": "MaxSpareThreads", "ps": "每个进程的最大空闲线程数", 'type': 2},
|
||||
{"name": "MaxConnectionsPerChild", "ps": "每个服务器进程的最大连接数", 'type': 2},
|
||||
],
|
||||
"winnt": [
|
||||
{"name": "ThreadsPerChild", "ps": "服务器进程中的工作线程数", 'type': 2},
|
||||
{"name": "MaxConnectionsPerChild", "ps": "服务器进程服务的最大连接数", 'type': 2},
|
||||
]
|
||||
}
|
||||
|
||||
# 通用配置参数
|
||||
common_cfg_args = [
|
||||
{"name": "MaxMemFree", "ps": "每个分配器允许持有的最大空闲KB数", 'type': 2},
|
||||
]
|
||||
|
||||
# 合并配置参数
|
||||
cfg_args = []
|
||||
if mpm_module in mpm_cfg_args:
|
||||
cfg_args.extend(mpm_cfg_args[mpm_module])
|
||||
cfg_args.extend(common_cfg_args)
|
||||
|
||||
rdata = []
|
||||
for i in cfg_args:
|
||||
# 匹配 MPM 特定配置
|
||||
rep = r"<IfModule mpm_%s_module>.*?(%s)\s+(\w+).*?</IfModule>" % (mpm_module, i["name"])
|
||||
k = re.search(rep, content, re.DOTALL)
|
||||
|
||||
# 如果没有找到 MPM 特定配置,尝试匹配通用配置
|
||||
if not k:
|
||||
rep = r"(%s)\s+(\w+)" % i["name"]
|
||||
k = re.search(rep, content)
|
||||
|
||||
if not k:
|
||||
continue
|
||||
|
||||
key = k.group(1)
|
||||
v = k.group(2) if len(k.groups()) > 1 else ""
|
||||
|
||||
if re.search(unitrep, v):
|
||||
u = str.upper(v[-1])
|
||||
v = v[:-1]
|
||||
if len(u) == 1:
|
||||
psstr = u + "B," + i["ps"]
|
||||
else:
|
||||
psstr = u + "," + i["ps"]
|
||||
else:
|
||||
u = ""
|
||||
|
||||
kv = {"name": key, "value": v, "unit": u,
|
||||
"ps": i["ps"], "type": i["type"]}
|
||||
rdata.append(kv)
|
||||
return mw.returnJson(True, "ok", rdata)
|
||||
|
||||
def replaceChar(value, index, new_char):
|
||||
return value[:index] + new_char + value[index+1:]
|
||||
|
||||
def setCfg():
|
||||
args = getArgs()
|
||||
|
||||
# 检查参数,允许动态参数
|
||||
cfg = getConfMpm()
|
||||
mw.backFile(cfg)
|
||||
content = mw.readFile(cfg)
|
||||
|
||||
# 获取当前 MPM 模块
|
||||
mpm_module = ""
|
||||
mpm_match = re.search(r"mpm_(\w+)_module", content)
|
||||
if mpm_match:
|
||||
mpm_module = mpm_match.group(1)
|
||||
|
||||
# 验证参数值
|
||||
for k, v in args.items():
|
||||
# 检查是否为数字参数
|
||||
if not re.search(r"\d+", v):
|
||||
return mw.returnJson(False, '参数值错误,请输入数字整数')
|
||||
|
||||
# 替换 MPM 特定配置
|
||||
if mpm_module:
|
||||
def replace_mpm_config(match):
|
||||
return match.group(1) + k + match.group(2) + v + match.group(3)
|
||||
rep = r"(<IfModule mpm_%s_module>.*?)%s(\s+)\d+(.*?</IfModule>)" % (mpm_module, k)
|
||||
if re.search(rep, content, re.DOTALL):
|
||||
content = re.sub(rep, replace_mpm_config, content, flags=re.DOTALL)
|
||||
|
||||
# 替换通用配置
|
||||
def replace_common_config(match):
|
||||
return k + match.group(1) + v
|
||||
rep = r"%s(\s+)\d+" % k
|
||||
if re.search(rep, content):
|
||||
content = re.sub(rep, replace_common_config, content)
|
||||
|
||||
mw.writeFile(cfg, content)
|
||||
isError = mw.checkHttpdConfig()
|
||||
if (isError != True):
|
||||
mw.restoreFile(cfg)
|
||||
return mw.returnJson(False, 'ERROR: 配置出错<br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
|
||||
|
||||
mw.restartWeb()
|
||||
return mw.returnJson(True, '设置成功')
|
||||
|
||||
|
||||
def cronAddCheck():
|
||||
try:
|
||||
import tool_task
|
||||
tool_task.createBgTask()
|
||||
return mw.returnJson(True, '添加检查任务成功')
|
||||
except Exception as e:
|
||||
return mw.returnJson(False, '添加检查任务失败:'+str(e))
|
||||
|
||||
def cronDelCheck():
|
||||
try:
|
||||
import tool_task
|
||||
tool_task.removeBgTask()
|
||||
return mw.returnJson(True, '删除检查任务成功')
|
||||
except Exception as e:
|
||||
return mw.returnJson(False, '删除检查任务失败:'+str(e))
|
||||
|
||||
def cronCheck():
|
||||
return 'ok'
|
||||
|
||||
|
||||
def installPreInspection():
|
||||
return 'ok'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
version = '2.4'
|
||||
version_pl = getServerDir() + "/version.pl"
|
||||
if os.path.exists(version_pl):
|
||||
version = mw.readFile(version_pl)
|
||||
|
||||
|
||||
func = sys.argv[1]
|
||||
if func == 'status':
|
||||
print(status())
|
||||
elif func == 'start':
|
||||
print(start())
|
||||
elif func == 'stop':
|
||||
print(stop())
|
||||
elif func == 'restart':
|
||||
print(restart())
|
||||
elif func == 'reload':
|
||||
print(reload())
|
||||
elif func == 'initd_status':
|
||||
print(initdStatus())
|
||||
elif func == 'initd_install':
|
||||
print(initdInstall())
|
||||
elif func == 'initd_uninstall':
|
||||
print(initdUinstall())
|
||||
elif func == 'install_pre_inspection':
|
||||
print(installPreInspection())
|
||||
elif func == 'conf':
|
||||
print(getConf())
|
||||
elif func == 'get_os':
|
||||
print(getOs())
|
||||
elif func == 'run_info':
|
||||
print(runInfo())
|
||||
elif func == 'error_log':
|
||||
print(errorLogPath())
|
||||
elif func == 'get_cfg':
|
||||
print(getCfg())
|
||||
elif func == 'set_cfg':
|
||||
print(setCfg())
|
||||
elif func == 'check':
|
||||
print(cronCheck())
|
||||
elif func == 'cron_add_check':
|
||||
print(cronAddCheck())
|
||||
elif func == 'cron_del_check':
|
||||
print(cronDelCheck())
|
||||
else:
|
||||
print('error')
|
||||
18
plugins/apache/info.json
Executable file
18
plugins/apache/info.json
Executable file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"sort": 0,
|
||||
"title":"apache",
|
||||
"tip":"soft",
|
||||
"name":"apache",
|
||||
"type":"其他插件",
|
||||
"ps":"世界排名第一,快速、可靠并且可通过简单的API扩充",
|
||||
"shell":"install.sh",
|
||||
"install_pre_inspection":true,
|
||||
"checks":"server/apache",
|
||||
"path":"server/apache",
|
||||
"author":"apache",
|
||||
"home":"https://downloads.apache.org/httpd/",
|
||||
"date":"2026-04-11",
|
||||
"pid": "1",
|
||||
"versions": ["2.4"],
|
||||
"updates": ["2.4:66"]
|
||||
}
|
||||
16
plugins/apache/init.d/httpd.service.tpl
Normal file
16
plugins/apache/init.d/httpd.service.tpl
Normal file
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Apache Web Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=LANG=C
|
||||
ExecStart={$SERVER_PATH}/apache/httpd/bin/httpd $OPTIONS -DFOREGROUND
|
||||
ExecReload={$SERVER_PATH}/apache/httpd/bin/httpd $OPTIONS -k graceful
|
||||
KillSignal=SIGWINCH
|
||||
|
||||
KillMode=mixed
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
17
plugins/apache/init.d/httpd.service2.tpl
Normal file
17
plugins/apache/init.d/httpd.service2.tpl
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Apache Web Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
PIDFile={$SERVER_PATH}/apache/httpd/logs/httpd.pid
|
||||
ExecStart={$SERVER_PATH}/apache/httpd/bin/apachectl start
|
||||
ExecReload={$SERVER_PATH}/apache/httpd/bin/apachectl graceful
|
||||
ExecStop={$SERVER_PATH}/apache/httpd/bin/apachectl stop
|
||||
PrivateTmp=true
|
||||
User=www
|
||||
Group=www
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
47
plugins/apache/init.d/httpd.tpl
Normal file
47
plugins/apache/init.d/httpd.tpl
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# chkconfig: 345 85 15
|
||||
# description: Apache Server
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: apache
|
||||
# Required-Start: $network
|
||||
# Required-Stop: $network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: Apache Server
|
||||
### END INIT INFO
|
||||
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/homebrew/bin
|
||||
# Apache 可执行文件的路径
|
||||
DAEMON={$SERVER_PATH}/apache/httpd/bin/httpd
|
||||
# 进程的 PID 文件路径
|
||||
PIDFILE={$SERVER_PATH}/apache/httpd/logs/httpd.pid
|
||||
|
||||
# 根据传入的参数执行不同操作
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Starting Apache..."
|
||||
$DAEMON -k start
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping Apache..."
|
||||
$DAEMON -k stop
|
||||
;;
|
||||
restart)
|
||||
echo "Restarting Apache..."
|
||||
$DAEMON -k restart
|
||||
;;
|
||||
status)
|
||||
if [ -f $PIDFILE ]; then
|
||||
echo "Apache is running."
|
||||
else
|
||||
echo "Apache is not running."
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit 0
|
||||
87
plugins/apache/install.sh
Executable file
87
plugins/apache/install.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin
|
||||
export PATH
|
||||
|
||||
# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/apache && bash install.sh install 2.4
|
||||
# cd /www/server/mdserver-web/plugins/apache && bash install.sh install 2.4
|
||||
# cd /www/server/mdserver-web/plugins/apache && bash install.sh upgrade 2.4
|
||||
|
||||
# curl -I -H "Accept-Encoding: br" http://localhost
|
||||
# curl -I -H "Accept-Encoding: zstd" http://localhost
|
||||
# curl --http3 -v https://www.xxx.com
|
||||
|
||||
# apt install ncat -y
|
||||
# nc -u -v www.xx.com 443
|
||||
|
||||
# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/apache && python3 plugins/apache/index.py start
|
||||
# cd /www/server/mdserver-web && python3 plugins/apache/index.py start
|
||||
# cd /www/server/mdserver-web && python3 plugins/apache/index.py run_info
|
||||
# ps -ef|grep httpd | grep -v grep | awk '{print $2}'|xargs kill
|
||||
|
||||
curPath=`pwd`
|
||||
rootPath=$(dirname "$curPath")
|
||||
rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
sysName=`uname`
|
||||
action=$1
|
||||
type=$2
|
||||
|
||||
VERSION=$2
|
||||
openrestyDir=${serverPath}/source/apache
|
||||
|
||||
if id www &> /dev/null ;then
|
||||
echo "www uid is `id -u www`"
|
||||
echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `"
|
||||
else
|
||||
groupadd www
|
||||
useradd -g www -s /bin/bash www
|
||||
fi
|
||||
|
||||
if [ "${action}" = "upgrade" ];then
|
||||
sh -x $curPath/versions/$2/install.sh $1
|
||||
|
||||
echo "${VERSION}" > $serverPath/apache/version.pl
|
||||
|
||||
mkdir -p $serverPath/web_conf/php/conf
|
||||
echo 'set $PHP_ENV 0;' > $serverPath/web_conf/php/conf/enable-php-00.conf
|
||||
|
||||
#初始化
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/apache/index.py start
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/apache/index.py initd_install
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
if [ "${2}" = "" ];then
|
||||
echo '缺少安装脚本版本...'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${action}" = "uninstall" ];then
|
||||
if [ -f /usr/lib/systemd/system/httpd.service ] || [ -f /lib/systemd/system/httpd.service ];then
|
||||
systemctl stop httpd
|
||||
rm -rf /usr/systemd/system/httpd.service
|
||||
rm -rf /lib/systemd/system/httpd.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
if [ -f $serverPath/apache/init.d/httpd ];then
|
||||
$serverPath/apache/init.d/httpd stop
|
||||
fi
|
||||
|
||||
rm -rf $serverPath/apache
|
||||
fi
|
||||
|
||||
sh -x $curPath/versions/$2/install.sh $1
|
||||
|
||||
if [ "${action}" = "install" ] && [ -d $serverPath/apache ];then
|
||||
echo "${VERSION}" > $serverPath/apache/version.pl
|
||||
|
||||
mkdir -p $serverPath/web_conf/php/conf
|
||||
echo 'set $PHP_ENV 0;' > $serverPath/web_conf/php/conf/enable-php-00.conf
|
||||
|
||||
#初始化
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/apache/index.py start
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/apache/index.py initd_install
|
||||
fi
|
||||
278
plugins/apache/js/httpd.js
Executable file
278
plugins/apache/js/httpd.js
Executable file
@@ -0,0 +1,278 @@
|
||||
function httpPost(method, args, callback){
|
||||
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
|
||||
$.post('/plugins/run', {name:'apache', func:method, args:JSON.stringify(args)}, function(data) {
|
||||
layer.close(loadT);
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
function httpPluginService(_name, version){
|
||||
var data = {name:_name, func:'status'}
|
||||
if ( typeof(version) != 'undefined' ){
|
||||
data['version'] = version;
|
||||
} else {
|
||||
version = '';
|
||||
}
|
||||
|
||||
httpPost('status', data, function(data){
|
||||
if (data.data == 'start'){
|
||||
orPluginSetService(_name, true, version);
|
||||
} else {
|
||||
orPluginSetService(_name, false, version);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function orPluginSetService(_name ,status, version){
|
||||
var serviceCon ='<p class="status">当前状态:<span>'+(status ? '开启' : '关闭' )+
|
||||
'</span><span style="color: '+
|
||||
(status?'#20a53a;':'red;')+
|
||||
' margin-left: 3px;" class="glyphicon ' + (status?'glyphicon glyphicon-play':'glyphicon-pause')+'"></span></p><div class="sfm-opt">\
|
||||
<button class="btn btn-default btn-sm" onclick="orPluginOpService(\''+_name+'\',\''+(status?'stop':'start')+'\',\''+version+'\')">'+(status?'停止':'启动')+'</button>\
|
||||
<button class="btn btn-default btn-sm" onclick="orPluginOpService(\''+_name+'\',\'restart\',\''+version+'\',\'yes\')">重启</button>\
|
||||
<button class="btn btn-default btn-sm" onclick="orPluginOpService(\''+_name+'\',\'reload\',\''+version+'\')">重载配置</button>\
|
||||
</div>';
|
||||
$(".soft-man-con").html(serviceCon);
|
||||
}
|
||||
|
||||
|
||||
function orPluginOpService(a, b, v,request_callback) {
|
||||
|
||||
var c = "name=" + a + "&func=" + b;
|
||||
if(v != ''){
|
||||
c = c + '&version='+v;
|
||||
}
|
||||
|
||||
var d = "";
|
||||
|
||||
switch(b) {
|
||||
case "stop":d = '停止';break;
|
||||
case "start":d = '启动';break;
|
||||
case "restart":d = '重启';break;
|
||||
case "reload":d = '重载';break;
|
||||
}
|
||||
layer.confirm( msgTpl('您真的要{1}{2}{3}服务吗?', [d,a,v]), {icon:3,closeBtn: 2}, function() {
|
||||
httpPost('get_os',{},function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
if (!rdata['auth']){
|
||||
layer.prompt({title: '检查到权限不足,需要输入密码!', formType: 1},function(pwd, index){
|
||||
|
||||
layer.close(index);
|
||||
var data = {'pwd':pwd};
|
||||
c += '&args='+JSON.stringify(data);
|
||||
orPluginOpServiceOp(a,b,c,d,a,v,request_callback);
|
||||
});
|
||||
} else {
|
||||
orPluginOpServiceOp(a,b,c,d,a,v,request_callback);
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function orPluginOpServiceOp(a,b,c,d,a,v,request_callback){
|
||||
|
||||
var request_path = "/plugins/run";
|
||||
if (request_callback == 'yes'){
|
||||
request_path = "/plugins/callback";
|
||||
}
|
||||
|
||||
var e = layer.msg(msgTpl('正在{1}{2}{3}服务,请稍候...',[d,a,v]), {icon: 16,time: 0});
|
||||
$.post(request_path, c, function(g) {
|
||||
layer.close(e);
|
||||
|
||||
var f = g.data == 'ok' ? msgTpl('{1}{2}服务已{3}',[a,v,d]) : msgTpl('{1}{2}服务{3}失败!',[a,v,d]);
|
||||
layer.msg(f, {icon: g.data == 'ok' ? 1 : 2});
|
||||
|
||||
if( b != "reload" && g.data == 'ok' ) {
|
||||
if ( b == 'start' ) {
|
||||
orPluginSetService(a, true, v);
|
||||
} else if ( b == 'stop' ){
|
||||
orPluginSetService(a, false, v);
|
||||
}
|
||||
}
|
||||
|
||||
if( g.status && g.data != 'ok' ) {
|
||||
layer.msg(g.data, {icon: 2,time: 10000,shade: 0.3});
|
||||
}
|
||||
|
||||
},'json').error(function() {
|
||||
layer.close(e);
|
||||
layer.msg('操作异常!', {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 定时器变量
|
||||
var httpdStatusTimer = null;
|
||||
|
||||
//查看Apache负载状态
|
||||
function getHttpdStatus() {
|
||||
$.post('/plugins/run', {name:'apache', func:'run_info'}, function(data) {
|
||||
try {
|
||||
var rdata = $.parseJSON(data.data);
|
||||
|
||||
console.log(rdata);
|
||||
if ('status' in rdata && !rdata.status){
|
||||
showMsg(rdata.msg, function(){}, null,3000);
|
||||
return;
|
||||
}
|
||||
|
||||
var con = "<div><table class='table table-hover table-bordered'>\
|
||||
<tr><th>服务器版本(ServerVersion)</th><td>" + (rdata.ServerVersion || '-') + "</td></tr>\
|
||||
<tr><th>服务器MPM(ServerMPM)</th><td>" + (rdata.ServerMPM || '-') + "</td></tr>\
|
||||
<tr><th>服务器构建时间(Server Built)</th><td>" + (rdata['Server Built'] || '-') + "</td></tr>\
|
||||
<tr><th>当前时间(CurrentTime)</th><td>" + (rdata.CurrentTime || '-') + "</td></tr>\
|
||||
<tr><th>重启时间(RestartTime)</th><td>" + (rdata.RestartTime || '-') + "</td></tr>\
|
||||
<tr><th>服务器运行时间(ServerUptime)</th><td>" + (rdata.ServerUptime || '-') + "</td></tr>\
|
||||
<tr><th>服务器运行秒数(ServerUptimeSeconds)</th><td>" + (rdata.ServerUptimeSeconds || '-') + "</td></tr>\
|
||||
<tr><th>1分钟负载(Load1)</th><td>" + (rdata.Load1 || '-') + "</td></tr>\
|
||||
<tr><th>5分钟负载(Load5)</th><td>" + (rdata.Load5 || '-') + "</td></tr>\
|
||||
<tr><th>15分钟负载(Load15)</th><td>" + (rdata.Load15 || '-') + "</td></tr>\
|
||||
<tr><th>总访问次数(Total Accesses)</th><td>" + (rdata['Total Accesses'] || '-') + "</td></tr>\
|
||||
<tr><th>总流量(Total kBytes)</th><td>" + (rdata['Total kBytes'] || '-') + " KB</td></tr>\
|
||||
<tr><th>总请求时间(Total Duration)</th><td>" + (rdata['Total Duration'] || '-') + "</td></tr>\
|
||||
<tr><th>CPU用户时间(CPUUser)</th><td>" + (rdata.CPUUser || '-') + "</td></tr>\
|
||||
<tr><th>CPU系统时间(CPUSystem)</th><td>" + (rdata.CPUSystem || '-') + "</td></tr>\
|
||||
<tr><th>CPU负载(CPULoad)</th><td>" + (rdata.CPULoad || '-') + "</td></tr>\
|
||||
<tr><th>每秒请求数(ReqPerSec)</th><td>" + (rdata.ReqPerSec || '-') + "</td></tr>\
|
||||
<tr><th>每秒流量(BytesPerSec)</th><td>" + (rdata.BytesPerSec || '-') + "</td></tr>\
|
||||
<tr><th>每请求流量(BytesPerReq)</th><td>" + (rdata.BytesPerReq || '-') + "</td></tr>\
|
||||
<tr><th>每请求时间(DurationPerReq)</th><td>" + (rdata.DurationPerReq || '-') + "</td></tr>\
|
||||
<tr><th>活动工作进程(BusyWorkers)</th><td>" + (rdata.BusyWorkers || '-') + "</td></tr>\
|
||||
<tr><th>优雅关闭进程(GracefulWorkers)</th><td>" + (rdata.GracefulWorkers || '-') + "</td></tr>\
|
||||
<tr><th>空闲工作进程(IdleWorkers)</th><td>" + (rdata.IdleWorkers || '-') + "</td></tr>\
|
||||
<tr><th>进程数(Processes)</th><td>" + (rdata.Processes || '-') + "</td></tr>\
|
||||
<tr><th>总连接数(ConnsTotal)</th><td>" + (rdata.ConnsTotal || '-') + "</td></tr>\
|
||||
</table></div>";
|
||||
$(".soft-man-con").html(con);
|
||||
}catch(err){
|
||||
showMsg(data.data, function(){}, null,3000);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
// 启动自动刷新
|
||||
function startHttpdStatusAutoRefresh() {
|
||||
// 先清除现有的定时器
|
||||
if (httpdStatusTimer) {
|
||||
clearInterval(httpdStatusTimer);
|
||||
}
|
||||
|
||||
// 立即执行一次
|
||||
getHttpdStatus();
|
||||
|
||||
// 设置定时器,每5秒刷新一次
|
||||
httpdStatusTimer = setInterval(getHttpdStatus, 5000);
|
||||
}
|
||||
|
||||
// 停止自动刷新
|
||||
function stopHttpdStatusAutoRefresh() {
|
||||
if (httpdStatusTimer) {
|
||||
clearInterval(httpdStatusTimer);
|
||||
httpdStatusTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setOpCfg(){
|
||||
httpPost('get_cfg', {}, function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
var rdata = rdata.data;
|
||||
// console.log(rdata);
|
||||
|
||||
var mlist = '';
|
||||
for (var i = 0; i < rdata.length; i++) {
|
||||
var w = '70'
|
||||
var ibody = '<input style="width: ' + w + 'px;" class="bt-input-text mr5" name="' + rdata[i].name + '" value="' + rdata[i].value + '" type="text" >';
|
||||
switch (rdata[i].type) {
|
||||
case 0:
|
||||
var selected_1 = (rdata[i].value == 1) ? 'selected' : '';
|
||||
var selected_0 = (rdata[i].value == 0) ? 'selected' : '';
|
||||
ibody = '<select class="bt-input-text mr5" name="' + rdata[i].name + '" style="width: ' + w + 'px;">\
|
||||
<option value="1" ' + selected_1 + '>开启</option>\
|
||||
<option value="0" ' + selected_0 + '>关闭</option>\
|
||||
</select>';
|
||||
break;
|
||||
case 1:
|
||||
var selected_1 = (rdata[i].value == 'on') ? 'selected' : '';
|
||||
var selected_0 = (rdata[i].value == 'off') ? 'selected' : '';
|
||||
ibody = '<select class="bt-input-text mr5" name="' + rdata[i].name + '" style="width: ' + w + 'px;">\
|
||||
<option value="on" ' + selected_1 + '>开启</option>\
|
||||
<option value="off" ' + selected_0 + '>关闭</option>\
|
||||
</select>';
|
||||
break;
|
||||
}
|
||||
mlist += '<p style="margin-top:15px;"><span>' + rdata[i].name + '</span>' + ibody + "<b class='unit c9'>"+rdata[i].unit+"</b>" +', <font class="c9">' + rdata[i].ps + '</font></p>';
|
||||
}
|
||||
var con = '<style>.conf_p p{margin-bottom: 2px}</style><div class="conf_p" style="margin-bottom:0">\
|
||||
' + mlist + '\
|
||||
<div style="margin-top:10px; padding-right:15px" class="text-right">\
|
||||
<button class="btn btn-success btn-sm mr5" onclick="setOpCfg()">刷新</button>\
|
||||
<button class="btn btn-success btn-sm" onclick="submitConf()">保存</button>\
|
||||
</div>\
|
||||
</div>'
|
||||
$(".soft-man-con").html(con);
|
||||
});
|
||||
}
|
||||
|
||||
function submitConf() {
|
||||
var data = {};
|
||||
|
||||
// 收集所有配置参数
|
||||
$("input[name]").each(function() {
|
||||
data[$(this).attr('name')] = $(this).val();
|
||||
});
|
||||
|
||||
$("select[name]").each(function() {
|
||||
data[$(this).attr('name')] = $(this).val() || 'on';
|
||||
});
|
||||
|
||||
// console.log(data);
|
||||
httpPost('set_cfg', data, function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
// console.log(rdata);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
});
|
||||
}
|
||||
|
||||
function otherFunc(){
|
||||
var con = '<p class="conf_p" style="text-align:center;">\
|
||||
<button class="btn btn-default btn-sm" onclick="cronAddCheck()">添加检查任务</button> \
|
||||
<button class="btn btn-default btn-sm" onclick="cronDelCheck()">删除检查任务</button>\
|
||||
</p>';
|
||||
$(".soft-man-con").html(con);
|
||||
}
|
||||
|
||||
function cronAddCheck(){
|
||||
httpPost('cron_add_check', {}, function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
});
|
||||
}
|
||||
|
||||
function cronDelCheck(){
|
||||
httpPost('cron_del_check', {}, function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
124
plugins/apache/tool_task.py
Normal file
124
plugins/apache/tool_task.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
from utils.crontab import crontab as MwCrontab
|
||||
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'apache'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getTaskConf():
|
||||
conf = getServerDir() + "/task_config.json"
|
||||
return conf
|
||||
|
||||
|
||||
def getConfigData():
|
||||
conf = getTaskConf()
|
||||
if os.path.exists(conf):
|
||||
return json.loads(mw.readFile(getTaskConf()))
|
||||
return {
|
||||
"task_id": -1,
|
||||
"period": "minute-n",
|
||||
"where1": "3",
|
||||
"hour": "0",
|
||||
"minute": "0",
|
||||
}
|
||||
|
||||
|
||||
def createBgTask():
|
||||
removeBgTask()
|
||||
createBgTaskByName(getPluginName())
|
||||
|
||||
|
||||
def createBgTaskByName(name):
|
||||
args = getConfigData()
|
||||
_name = "[apache]检查任务"
|
||||
res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find()
|
||||
if res:
|
||||
return True
|
||||
|
||||
if "task_id" in args and args["task_id"] > 0:
|
||||
res = mw.M("crontab").field("id, name").where(
|
||||
"id=?", (args["task_id"],)).find()
|
||||
if res and res["id"] == args["task_id"]:
|
||||
print("计划任务已经存在!")
|
||||
return True
|
||||
|
||||
mw_dir = mw.getPanelDir()
|
||||
cmd = '''
|
||||
mw_dir=%s
|
||||
rname=%s
|
||||
plugin_path=%s
|
||||
script_path=%s
|
||||
''' % (mw_dir, name, getServerDir(), getPluginDir())
|
||||
cmd += 'echo "bash $script_path/check.sh"' + "\n"
|
||||
cmd += 'cd $mw_dir && bash $script_path/check.sh' + "\n"
|
||||
|
||||
params = {
|
||||
'name': _name,
|
||||
'type': args['period'],
|
||||
'week': "",
|
||||
'where1': args['where1'],
|
||||
'hour': args['hour'],
|
||||
'minute': args['minute'],
|
||||
'save': "",
|
||||
'backup_to': "",
|
||||
'stype': "toShell",
|
||||
'sname': '',
|
||||
'sbody': cmd,
|
||||
'url_address': '',
|
||||
}
|
||||
|
||||
task_id = MwCrontab.instance().add(params)
|
||||
if task_id > 0:
|
||||
args["task_id"] = task_id
|
||||
args["name"] = name
|
||||
mw.writeFile(getTaskConf(), json.dumps(args))
|
||||
|
||||
|
||||
def removeBgTask():
|
||||
cfg = getConfigData()
|
||||
if "task_id" in cfg and cfg["task_id"] > 0:
|
||||
res = mw.M("crontab").field("id, name").where(
|
||||
"id=?", (cfg["task_id"],)).find()
|
||||
if res and res["id"] == cfg["task_id"]:
|
||||
data = MwCrontab.instance().delete(cfg["task_id"])
|
||||
if data["status"]:
|
||||
cfg["task_id"] = -1
|
||||
mw.writeFile(getTaskConf(), json.dumps(cfg))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
action = sys.argv[1]
|
||||
if action == "remove":
|
||||
removeBgTask()
|
||||
elif action == "add":
|
||||
createBgTask()
|
||||
172
plugins/apache/versions/2.4/install.sh
Normal file
172
plugins/apache/versions/2.4/install.sh
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin
|
||||
export PATH
|
||||
|
||||
# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/apache && bash install.sh install 2.4
|
||||
# cd /www/server/mdserver-web/plugins/apache && bash install.sh install 2.4
|
||||
|
||||
curPath=`pwd`
|
||||
rootPath=$(dirname "$curPath")
|
||||
rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
sysName=`uname`
|
||||
action=$1
|
||||
type=$2
|
||||
|
||||
VERSION=2.4.68
|
||||
apacheDir=${serverPath}/source/apache
|
||||
|
||||
Install_App()
|
||||
{
|
||||
if [ "${action}" = "install" ];then
|
||||
if [ -d $serverPath/apache ];then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# ----- cpu start ------
|
||||
if [ -z "${cpuCore}" ]; then
|
||||
cpuCore="1"
|
||||
fi
|
||||
|
||||
if [ -f /proc/cpuinfo ];then
|
||||
cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l`
|
||||
fi
|
||||
|
||||
MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}')
|
||||
if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then
|
||||
if [ "${cpuCore}" -gt "${MEM_INFO}" ];then
|
||||
cpuCore="${MEM_INFO}"
|
||||
fi
|
||||
else
|
||||
cpuCore="1"
|
||||
fi
|
||||
|
||||
if [ "$cpuCore" -gt "2" ];then
|
||||
cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'`
|
||||
else
|
||||
cpuCore="1"
|
||||
fi
|
||||
# ----- cpu end ------
|
||||
|
||||
mkdir -p ${apacheDir}
|
||||
echo 'install script ...'
|
||||
OPTIONS=''
|
||||
|
||||
# 安装 APR 和 APR-util
|
||||
APR_VERSION=1.7.6
|
||||
APR_UTIL_VERSION=1.6.3
|
||||
|
||||
if [ ! -f ${apacheDir}/apr-${APR_VERSION}.tar.bz2 ];then
|
||||
wget --no-check-certificate -O ${apacheDir}/apr-${APR_VERSION}.tar.bz2 https://downloads.apache.org/apr/apr-${APR_VERSION}.tar.bz2 -T 3
|
||||
fi
|
||||
|
||||
if [ ! -f ${apacheDir}/apr-util-${APR_UTIL_VERSION}.tar.bz2 ];then
|
||||
wget --no-check-certificate -O ${apacheDir}/apr-util-${APR_UTIL_VERSION}.tar.bz2 https://downloads.apache.org/apr/apr-util-${APR_UTIL_VERSION}.tar.bz2 -T 3
|
||||
fi
|
||||
|
||||
# 安装 APR
|
||||
if [ ! -d ${serverPath}/apache/apr ];then
|
||||
cd ${apacheDir} && tar -jxf apr-${APR_VERSION}.tar.bz2
|
||||
cd ${apacheDir}/apr-${APR_VERSION} && ./configure --prefix=${serverPath}/apache/apr
|
||||
make -j${cpuCore} && make install
|
||||
if [ "$?" = "0" ];then
|
||||
# 检查 APR 配置文件
|
||||
APR_CONFIG=$(find ${serverPath}/apache/apr -name "apr-*config" | head -1)
|
||||
if [ -z "$APR_CONFIG" ];then
|
||||
echo "APR installation failed: apr-config not found"
|
||||
exit 1
|
||||
fi
|
||||
echo "APR installed successfully: $APR_CONFIG"
|
||||
else
|
||||
echo "APR configure failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 安装 APR-util
|
||||
if [ ! -d ${serverPath}/apache/apr-util ];then
|
||||
cd ${apacheDir} && tar -jxf apr-util-${APR_UTIL_VERSION}.tar.bz2
|
||||
cd ${apacheDir}/apr-util-${APR_UTIL_VERSION} && ./configure --prefix=${serverPath}/apache/apr-util --with-apr=${serverPath}/apache/apr
|
||||
make -j${cpuCore} && make install
|
||||
if [ "$?" = "0" ];then
|
||||
# 检查 APR-util 配置文件
|
||||
APU_CONFIG=$(find ${serverPath}/apache/apr-util -name "apu-*config" | head -1)
|
||||
if [ -z "$APU_CONFIG" ];then
|
||||
echo "APR-util installation failed: apu-config not found"
|
||||
find ${serverPath}/apache/apr-util -name "apu-*config"
|
||||
exit 1
|
||||
fi
|
||||
echo "APR-util installed successfully: $APU_CONFIG"
|
||||
else
|
||||
echo "APR-util configure failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f ${apacheDir}/httpd-${VERSION}.tar.bz2 ];then
|
||||
wget --no-check-certificate -O ${apacheDir}/httpd-${VERSION}.tar.bz2 https://downloads.apache.org/httpd/httpd-${VERSION}.tar.bz2 -T 3
|
||||
fi
|
||||
|
||||
if [ ! -d ${apacheDir}/httpd-${VERSION} ];then
|
||||
cd ${apacheDir} && tar -jxf ${apacheDir}/httpd-${VERSION}.tar.bz2
|
||||
fi
|
||||
|
||||
|
||||
# 检查 APR 和 APR-util 可执行文件
|
||||
APR_CONFIG=$(find ${serverPath}/apache/apr -name "apr-*config" | head -1)
|
||||
APU_CONFIG=$(find ${serverPath}/apache/apr-util -name "apu-*config" | head -1)
|
||||
|
||||
if [ -z "$APR_CONFIG" ];then
|
||||
echo "APR config not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$APU_CONFIG" ];then
|
||||
echo "APR-util config not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OPTIONS="${OPTIONS} --with-apr=$APR_CONFIG"
|
||||
OPTIONS="${OPTIONS} --with-apr-util=$APU_CONFIG"
|
||||
|
||||
# if [ "$sysName" != "Darwin" ];then
|
||||
# OPTIONS="${OPTIONS} --enable-systemd"
|
||||
# fi
|
||||
# echo "cd ${apacheDir}/httpd-${VERSION} && ./configure --prefix=$serverPath/apache $OPTIONS"
|
||||
cd ${apacheDir}/httpd-${VERSION} && ./configure \
|
||||
--prefix=$serverPath/apache/httpd \
|
||||
$OPTIONS
|
||||
|
||||
make -j${cpuCore} && make install && make clean
|
||||
|
||||
|
||||
if [ -d ${apacheDir}/apr-${APR_VERSION} ];then
|
||||
rm -rf ${apacheDir}/apr-${APR_VERSION}
|
||||
fi
|
||||
|
||||
if [ -d ${apacheDir}/apr-util-${APR_UTIL_VERSION} ];then
|
||||
rm -rf ${apacheDir}/apr-util-${APR_UTIL_VERSION}
|
||||
fi
|
||||
|
||||
if [ -d ${apacheDir}/httpd-${APR_UTIL_VERSION} ];then
|
||||
rm -rf ${apacheDir}/httpd-${APR_UTIL_VERSION}
|
||||
fi
|
||||
|
||||
echo 'installation of apache completed'
|
||||
}
|
||||
|
||||
Uninstall_App()
|
||||
{
|
||||
echo 'uninstalling apache completed'
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" = "install" ];then
|
||||
Install_App
|
||||
elif [ "${1}" = "upgrade" ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
fi
|
||||
@@ -20,16 +20,6 @@ if os.path.exists(web_dir):
|
||||
import core.mw as mw
|
||||
import core.db as db
|
||||
|
||||
_ver = sys.version_info
|
||||
is_py2 = (_ver[0] == 2)
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
DEBUG = False
|
||||
|
||||
if is_py2:
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf-8')
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
@@ -81,7 +71,10 @@ def status():
|
||||
def getConf():
|
||||
cfg = getServerDir() + "/cfg.json"
|
||||
if not os.path.exists(cfg):
|
||||
return mw.returnJson(False, "未配置", [])
|
||||
rdata = {
|
||||
'use_sftp':False,
|
||||
}
|
||||
return mw.returnJson(False, "未配置", rdata)
|
||||
data = mw.readFile(cfg)
|
||||
data = json.loads(data)
|
||||
return mw.returnJson(True, "OK", data)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"sort":6,
|
||||
"title":"FTP存储空间",
|
||||
"hook":["backup"],
|
||||
"tip":"soft",
|
||||
@@ -13,5 +14,5 @@
|
||||
"author":"midoks",
|
||||
"home":"https://github.com/midoks/mdserver-web",
|
||||
"date":"2022-10-23",
|
||||
"pid": "4"
|
||||
"pid": "5"
|
||||
}
|
||||
@@ -24,7 +24,7 @@ Uninstall_App()
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_App $2
|
||||
else
|
||||
Uninstall_App $2
|
||||
|
||||
35
plugins/caddy/check.sh
Executable file
35
plugins/caddy/check.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin
|
||||
export PATH
|
||||
|
||||
# OpenResty服务名称
|
||||
service_name="caddy"
|
||||
|
||||
# 检查OpenResty是否正在运行
|
||||
if systemctl is-active --quiet "$service_name"; then
|
||||
# 检查是否存在僵尸进程
|
||||
zombie_processes=$(ps -ef | grep -i caddy | grep -v grep | awk '{print $2}' | xargs ps -o state= -p 2>/dev/null | grep -c Z)
|
||||
if [ "$zombie_processes" -gt 0 ]; then
|
||||
echo "kill caddy 僵尸进程"
|
||||
ps -ef|grep caddy| grep -v grep| awk '{print $2}' | xargs kill -9
|
||||
echo "检测到OpenResty僵尸进程,正在重启服务..."
|
||||
systemctl restart "$service_name"
|
||||
echo "服务已重启"
|
||||
else
|
||||
echo "OpenResty运行正常"
|
||||
fi
|
||||
else
|
||||
echo "kill caddy"
|
||||
ps -ef|grep caddy| grep -v grep| awk '{print $2}' | xargs kill -9
|
||||
echo "caddy未运行,正在启动服务..."
|
||||
systemctl start "$service_name"
|
||||
echo "服务已启动"
|
||||
fi
|
||||
|
||||
NGINX_IDS=`ps -ef|grep caddy | grep -v grep| awk '{print $2}'`
|
||||
if [ "$NGINX_IDS" = "" ];then
|
||||
ps -ef|grep caddy| grep -v grep| awk '{print $2}' | xargs kill -9
|
||||
systemctl start "$service_name"
|
||||
echo "caddy未运行,正在启动服务..."
|
||||
fi
|
||||
|
||||
13
plugins/caddy/conf/Caddyfile
Normal file
13
plugins/caddy/conf/Caddyfile
Normal file
@@ -0,0 +1,13 @@
|
||||
:8080 {
|
||||
# Set this path to your site's directory.
|
||||
root * /tmp
|
||||
|
||||
# Enable the static file server.
|
||||
file_server
|
||||
|
||||
# Another common task is to set up a reverse proxy:
|
||||
# reverse_proxy localhost:8080
|
||||
|
||||
# Or serve a PHP site through php-fpm:
|
||||
# php_fastcgi localhost:9000
|
||||
}
|
||||
BIN
plugins/caddy/ico.png
Normal file
BIN
plugins/caddy/ico.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
24
plugins/caddy/index.html
Executable file
24
plugins/caddy/index.html
Executable file
@@ -0,0 +1,24 @@
|
||||
<div class="bt-form">
|
||||
<!-- <div class='plugin_version'></div> -->
|
||||
<div class="bt-w-main">
|
||||
<div class="bt-w-menu">
|
||||
<p class="bgw" onclick="orPluginService('caddy',$('.plugin_version').attr('version'));">服务</p>
|
||||
<p onclick="pluginInitD('caddy', $('.plugin_version').attr('version'));">自启动</p>
|
||||
<p onclick="pluginConfig('caddy');">配置修改</p>
|
||||
<p onclick="getOpStatus();">负载状态</p>
|
||||
<p onclick="setOpCfg();">性能调整</p>
|
||||
<p onclick="pluginLogs('caddy');">错误日志</p>
|
||||
<p onclick="otherFunc();">维护功能</p>
|
||||
</div>
|
||||
<div class="bt-w-con pd15">
|
||||
<div class="soft-man-con"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$.getScript( "/plugins/file?name=caddy&f=js/caddy.js", function(){
|
||||
orPluginService('caddy');
|
||||
});
|
||||
</script>
|
||||
445
plugins/caddy/index.py
Executable file
445
plugins/caddy/index.py
Executable file
@@ -0,0 +1,445 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'caddy'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getInitDFile():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return '/tmp/' + getPluginName()
|
||||
|
||||
if current_os.startswith('freebsd'):
|
||||
return '/etc/rc.d/' + getPluginName()
|
||||
|
||||
return '/etc/init.d/' + getPluginName()
|
||||
|
||||
|
||||
def getArgs():
|
||||
args = sys.argv[2:]
|
||||
# print(args)
|
||||
tmp = {}
|
||||
args_len = len(args)
|
||||
|
||||
if args_len == 1:
|
||||
t = args[0].strip('{').strip('}')
|
||||
t = t.split(':',2)
|
||||
tmp[t[0]] = t[1]
|
||||
elif args_len > 1:
|
||||
for i in range(len(args)):
|
||||
t = args[i].split(':',2)
|
||||
tmp[t[0]] = t[1]
|
||||
# print(tmp)
|
||||
return tmp
|
||||
|
||||
|
||||
def checkArgs(data, ck=[]):
|
||||
for i in range(len(ck)):
|
||||
if not ck[i] in data:
|
||||
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
|
||||
return (True, mw.returnJson(True, 'ok'))
|
||||
|
||||
|
||||
def getOs():
|
||||
data = {}
|
||||
data['os'] = mw.getOs()
|
||||
data['auth'] = True
|
||||
return mw.getJson(data)
|
||||
|
||||
def getConf():
|
||||
path = getServerDir() + "/Caddyfile"
|
||||
return path
|
||||
|
||||
|
||||
def getConfTpl():
|
||||
path = getPluginDir() + '/conf/Caddyfile'
|
||||
return path
|
||||
|
||||
|
||||
def getInitDTpl():
|
||||
path = getPluginDir() + "/init.d/caddy.tpl"
|
||||
return path
|
||||
|
||||
def initDreplace():
|
||||
file_tpl = getInitDTpl()
|
||||
service_path = mw.getServerDir()
|
||||
initD_path = getServerDir() + '/init.d'
|
||||
|
||||
# init.d
|
||||
file_bin = initD_path + '/' + getPluginName()
|
||||
|
||||
if not os.path.exists(initD_path):
|
||||
os.mkdir(initD_path)
|
||||
|
||||
# initd replace
|
||||
content = mw.readFile(file_tpl)
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
mw.writeFile(file_bin, content)
|
||||
mw.execShell('chmod +x ' + file_bin)
|
||||
|
||||
caddy_file = getConf()
|
||||
if not os.path.exists(caddy_file):
|
||||
caddy_file_tpl = getConfTpl()
|
||||
content = mw.readFile(caddy_file_tpl)
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
mw.writeFile(caddy_file, content)
|
||||
|
||||
# systemd
|
||||
# /usr/lib/systemd/system
|
||||
systemDir = mw.systemdCfgDir()
|
||||
systemService = systemDir + '/caddy.service'
|
||||
if os.path.exists(systemDir) and not os.path.exists(systemService):
|
||||
systemServiceTpl = getPluginDir() + '/init.d/caddy.service.tpl'
|
||||
se_content = mw.readFile(systemServiceTpl)
|
||||
se_content = se_content.replace('{$SERVER_PATH}', service_path)
|
||||
mw.writeFile(systemService, se_content)
|
||||
mw.execShell('systemctl daemon-reload')
|
||||
|
||||
return file_bin
|
||||
|
||||
|
||||
def status():
|
||||
cmd = "ps -ef|grep 'caddy run' |grep -v grep | grep -v python | awk '{print $2}'"
|
||||
data = mw.execShell(cmd)
|
||||
if data[0] == '':
|
||||
return 'stop'
|
||||
return 'start'
|
||||
|
||||
|
||||
def caddyOp(method):
|
||||
file = initDreplace()
|
||||
|
||||
current_os = mw.getOs()
|
||||
if current_os == "darwin":
|
||||
data = mw.execShell(file + ' ' + method)
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
|
||||
if current_os.startswith("freebsd"):
|
||||
data = mw.execShell('service caddy ' + method)
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
|
||||
data = mw.execShell('systemctl ' + method + ' caddy')
|
||||
if data[1] == '':
|
||||
return 'ok'
|
||||
return data[1]
|
||||
|
||||
def start():
|
||||
return caddyOp('start')
|
||||
|
||||
|
||||
def stop():
|
||||
r = caddyOp('stop')
|
||||
return r
|
||||
|
||||
|
||||
def restart():
|
||||
return restyOp_restart()
|
||||
|
||||
|
||||
def reload():
|
||||
confReplace()
|
||||
return caddyOp('reload')
|
||||
|
||||
|
||||
def initdStatus():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return "Apple Computer does not support"
|
||||
|
||||
if current_os.startswith('freebsd'):
|
||||
initd_bin = getInitDFile()
|
||||
if os.path.exists(initd_bin):
|
||||
return 'ok'
|
||||
|
||||
shell_cmd = 'systemctl status caddy | grep loaded | grep "enabled;"'
|
||||
data = mw.execShell(shell_cmd)
|
||||
if data[0] == '':
|
||||
return 'fail'
|
||||
return 'ok'
|
||||
|
||||
|
||||
def initdInstall():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return "Apple Computer does not support"
|
||||
|
||||
# freebsd initd install
|
||||
if current_os.startswith('freebsd'):
|
||||
import shutil
|
||||
source_bin = initDreplace()
|
||||
initd_bin = getInitDFile()
|
||||
shutil.copyfile(source_bin, initd_bin)
|
||||
mw.execShell('chmod +x ' + initd_bin)
|
||||
mw.execShell('sysrc ' + getPluginName() + '_enable="YES"')
|
||||
return 'ok'
|
||||
|
||||
mw.execShell('systemctl enable caddy')
|
||||
return 'ok'
|
||||
|
||||
|
||||
def initdUinstall():
|
||||
current_os = mw.getOs()
|
||||
if current_os == 'darwin':
|
||||
return "Apple Computer does not support"
|
||||
|
||||
if current_os.startswith('freebsd'):
|
||||
initd_bin = getInitDFile()
|
||||
os.remove(initd_bin)
|
||||
mw.execShell('sysrc ' + getPluginName() + '_enable="NO"')
|
||||
return 'ok'
|
||||
|
||||
mw.execShell('systemctl disable caddy')
|
||||
return 'ok'
|
||||
|
||||
|
||||
def runInfo():
|
||||
op_status = status()
|
||||
if op_status == 'stop':
|
||||
return mw.returnJson(False, "未启动!")
|
||||
|
||||
|
||||
def errorLogPath():
|
||||
return getServerDir() + '/nginx/logs/error.log'
|
||||
|
||||
|
||||
def getCfg():
|
||||
cfg = getConf()
|
||||
content = mw.readFile(cfg)
|
||||
|
||||
unitrep = "[kmgKMG]"
|
||||
cfg_args = [
|
||||
{"name": "worker_processes", "ps": "处理进程,auto表示自动,数字表示进程数", 'type': 2},
|
||||
{"name": "worker_connections", "ps": "最大并发链接数", 'type': 2},
|
||||
{"name": "keepalive_timeout", "ps": "连接超时时间", 'type': 2},
|
||||
{"name": "zstd", "ps": "是否开启zstd压缩传输", 'type': 1},
|
||||
{"name": "brotli", "ps": "是否开启brotli压缩传输", 'type': 1},
|
||||
{"name": "gzip", "ps": "是否开启gzip压缩传输", 'type': 1},
|
||||
{"name": "gzip_min_length", "ps": "最小压缩文件", 'type': 2},
|
||||
{"name": "gzip_comp_level", "ps": "压缩率", 'type': 2},
|
||||
{"name": "client_max_body_size", "ps": "最大上传文件", 'type': 2},
|
||||
{"name": "server_names_hash_bucket_size",
|
||||
"ps": "服务器名字的hash表大小", 'type': 2},
|
||||
{"name": "client_header_buffer_size", "ps": "客户端请求头buffer大小", 'type': 2},
|
||||
]
|
||||
|
||||
# {"name": "client_body_buffer_size", "ps": "请求主体缓冲区"}
|
||||
rdata = []
|
||||
for i in cfg_args:
|
||||
rep = r"(%s)\s+(\w+)" % i["name"]
|
||||
k = re.search(rep, content)
|
||||
if not k:
|
||||
return mw.returnJson(False, "获取 key {} 失败".format(k))
|
||||
k = k.group(1)
|
||||
v = re.search(rep, content)
|
||||
if not v:
|
||||
return mw.returnJson(False, "获取 value {} 失败".format(v))
|
||||
v = v.group(2)
|
||||
|
||||
if re.search(unitrep, v):
|
||||
u = str.upper(v[-1])
|
||||
v = v[:-1]
|
||||
if len(u) == 1:
|
||||
psstr = u + "B," + i["ps"]
|
||||
else:
|
||||
psstr = u + "," + i["ps"]
|
||||
else:
|
||||
u = ""
|
||||
|
||||
kv = {"name": k, "value": v, "unit": u,
|
||||
"ps": i["ps"], "type": i["type"]}
|
||||
rdata.append(kv)
|
||||
|
||||
return mw.returnJson(True, "ok", rdata)
|
||||
|
||||
def replaceChar(value, index, new_char):
|
||||
return value[:index] + new_char + value[index+1:]
|
||||
|
||||
def makeWorkerCpuAffinity(val):
|
||||
if val == "auto":
|
||||
return "auto"
|
||||
|
||||
if mw.isNumber(val):
|
||||
core_num = int(val)
|
||||
default_core_str = "0"*core_num
|
||||
core_num_arr = []
|
||||
for x in range(core_num):
|
||||
t = replaceChar(default_core_str, x , "1")
|
||||
core_num_arr.append(t)
|
||||
return " ".join(core_num_arr)
|
||||
|
||||
return 'auto'
|
||||
|
||||
def setCfg():
|
||||
|
||||
args = getArgs()
|
||||
data = checkArgs(args, [
|
||||
'worker_processes', 'worker_connections', 'keepalive_timeout','zstd','brotli',
|
||||
'gzip', 'gzip_min_length', 'gzip_comp_level', 'client_max_body_size',
|
||||
'server_names_hash_bucket_size', 'client_header_buffer_size'
|
||||
])
|
||||
if not data[0]:
|
||||
return data[1]
|
||||
|
||||
cfg = getConf()
|
||||
mw.backFile(cfg)
|
||||
content = mw.readFile(cfg)
|
||||
|
||||
unitrep = "[kmgKMG]"
|
||||
cfg_args = [
|
||||
{"name": "worker_processes", "ps": "处理进程,auto表示自动,数字表示进程数", 'type': 2},
|
||||
{"name": "worker_connections", "ps": "最大并发链接数", 'type': 2},
|
||||
{"name": "keepalive_timeout", "ps": "连接超时时间", 'type': 2},
|
||||
{"name": "zstd", "ps": "是否开启zstd压缩传输", 'type': 1},
|
||||
{"name": "brotli", "ps": "是否开启brotli压缩传输", 'type': 1},
|
||||
{"name": "gzip", "ps": "是否开启压缩传输", 'type': 1},
|
||||
{"name": "gzip_min_length", "ps": "最小压缩文件", 'type': 2},
|
||||
{"name": "gzip_comp_level", "ps": "压缩率", 'type': 2},
|
||||
{"name": "client_max_body_size", "ps": "最大上传文件", 'type': 2},
|
||||
{"name": "server_names_hash_bucket_size",
|
||||
"ps": "服务器名字的hash表大小", 'type': 2},
|
||||
{"name": "client_header_buffer_size", "ps": "客户端请求头buffer大小", 'type': 2},
|
||||
]
|
||||
|
||||
# print(args)
|
||||
for k, v in args.items():
|
||||
# print(k, v)
|
||||
rep = r"%s\s+[^kKmMgG\;\n]+" % k
|
||||
if k == "worker_processes" or k == "gzip":
|
||||
if not re.search(r"auto|on|off|\d+", v):
|
||||
return mw.returnJson(False, '参数值错误')
|
||||
elif k == "zstd" or k == "brotli":
|
||||
if not re.search(r"auto|on|off|\d+", v):
|
||||
return mw.returnJson(False, '参数值错误')
|
||||
else:
|
||||
if not re.search(r"\d+", v):
|
||||
return mw.returnJson(False, '参数值错误,请输入数字整数')
|
||||
|
||||
if k == "worker_processes" :
|
||||
k_wca = "worker_cpu_affinity"
|
||||
rep_wca = r"%s\s+[^\;\n]+" % k_wca
|
||||
v_wca = makeWorkerCpuAffinity(v)
|
||||
newconf = "%s %s" % (k_wca, v_wca)
|
||||
content = re.sub(rep_wca, newconf, content)
|
||||
|
||||
|
||||
if re.search(rep, content):
|
||||
newconf = "%s %s" % (k, v)
|
||||
content = re.sub(rep, newconf, content)
|
||||
elif re.search(rep, content):
|
||||
newconf = "%s %s" % (k, v)
|
||||
content = re.sub(rep, newconf, content)
|
||||
|
||||
mw.writeFile(cfg, content)
|
||||
isError = mw.checkWebConfig()
|
||||
if (isError != True):
|
||||
mw.restoreFile(cfg)
|
||||
return mw.returnJson(False, 'ERROR: 配置出错<br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
|
||||
|
||||
mw.restartWeb()
|
||||
return mw.returnJson(True, '设置成功')
|
||||
|
||||
|
||||
def cronAddCheck():
|
||||
try:
|
||||
import tool_task
|
||||
tool_task.createBgTask()
|
||||
return mw.returnJson(True, '添加检查任务成功')
|
||||
except Exception as e:
|
||||
return mw.returnJson(False, '添加检查任务失败:'+str(e))
|
||||
|
||||
def cronDelCheck():
|
||||
try:
|
||||
import tool_task
|
||||
tool_task.removeBgTask()
|
||||
return mw.returnJson(True, '删除检查任务成功')
|
||||
except Exception as e:
|
||||
return mw.returnJson(False, '删除检查任务失败:'+str(e))
|
||||
|
||||
def cronCheck():
|
||||
return 'ok'
|
||||
|
||||
|
||||
def installPreInspection():
|
||||
return 'ok'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
version = '1.27.1'
|
||||
version_pl = getServerDir() + "/version.pl"
|
||||
if os.path.exists(version_pl):
|
||||
version = mw.readFile(version_pl)
|
||||
|
||||
|
||||
func = sys.argv[1]
|
||||
if func == 'status':
|
||||
print(status())
|
||||
elif func == 'start':
|
||||
print(start())
|
||||
elif func == 'stop':
|
||||
print(stop())
|
||||
elif func == 'restart':
|
||||
print(restart())
|
||||
elif func == 'reload':
|
||||
print(reload())
|
||||
elif func == 'initd_status':
|
||||
print(initdStatus())
|
||||
elif func == 'initd_install':
|
||||
print(initdInstall())
|
||||
elif func == 'initd_uninstall':
|
||||
print(initdUinstall())
|
||||
elif func == 'install_pre_inspection':
|
||||
print(installPreInspection())
|
||||
elif func == 'conf':
|
||||
print(getConf())
|
||||
elif func == 'get_os':
|
||||
print(getOs())
|
||||
elif func == 'run_info':
|
||||
print(runInfo())
|
||||
elif func == 'error_log':
|
||||
print(errorLogPath())
|
||||
elif func == 'get_cfg':
|
||||
print(getCfg())
|
||||
elif func == 'set_cfg':
|
||||
print(setCfg())
|
||||
elif func == 'check':
|
||||
print(cronCheck())
|
||||
elif func == 'cron_add_check':
|
||||
print(cronAddCheck())
|
||||
elif func == 'cron_del_check':
|
||||
print(cronDelCheck())
|
||||
else:
|
||||
print('error')
|
||||
18
plugins/caddy/info.json
Executable file
18
plugins/caddy/info.json
Executable file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"sort": 0,
|
||||
"title":"caddy",
|
||||
"tip":"soft",
|
||||
"name":"caddy",
|
||||
"type":"其他插件",
|
||||
"ps":"一款功能强大、企业级、开源 Web 服务器,具有用 Go 编写的自动 HTTPS",
|
||||
"shell":"install.sh",
|
||||
"install_pre_inspection":true,
|
||||
"checks":"server/caddy",
|
||||
"path":"server/caddy",
|
||||
"author":"caddyserver",
|
||||
"home":"https://caddyserver.com/",
|
||||
"date":"2026-04-11",
|
||||
"pid": "1",
|
||||
"versions": ["2.11"],
|
||||
"updates": ["2.11.2"]
|
||||
}
|
||||
36
plugins/caddy/init.d/caddy.service.tpl
Normal file
36
plugins/caddy/init.d/caddy.service.tpl
Normal file
@@ -0,0 +1,36 @@
|
||||
# caddy.service
|
||||
#
|
||||
# For using Caddy with a config file.
|
||||
#
|
||||
# Make sure the ExecStart and ExecReload commands are correct
|
||||
# for your installation.
|
||||
#
|
||||
# See https://caddyserver.com/docs/install for instructions.
|
||||
#
|
||||
# WARNING: This service does not use the --resume flag, so if you
|
||||
# use the API to make changes, they will be overwritten by the
|
||||
# Caddyfile next time the service is restarted. If you intend to
|
||||
# use Caddy's API to configure it, add the --resume flag to the
|
||||
# `caddy run` command or use the caddy-api.service file instead.
|
||||
|
||||
[Unit]
|
||||
Description=Caddy
|
||||
Documentation=https://caddyserver.com/docs/
|
||||
After=network.target network-online.target
|
||||
Requires=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www
|
||||
Group=www
|
||||
ExecStart={$SERVER_PATH}/caddy/bin/caddy run --environ --config {$SERVER_PATH}/caddy/Caddyfile
|
||||
ExecReload={$SERVER_PATH}/caddy/bin/caddy reload --config {$SERVER_PATH}/caddy/Caddyfile --force
|
||||
TimeoutStopSec=5s
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=512
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
111
plugins/caddy/init.d/caddy.tpl
Normal file
111
plugins/caddy/init.d/caddy.tpl
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# /etc/rc.d/init.d/caddy
|
||||
#
|
||||
# Runs the caddy
|
||||
#
|
||||
#
|
||||
# chkconfig: - 85 15
|
||||
#
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: caddy
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Should-Start: caddy
|
||||
# Should-Stop: caddy
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start caddy at boot time.
|
||||
# Description: Control caddy.
|
||||
### END INIT INFO
|
||||
|
||||
# Source function library.
|
||||
if [ -f /etc/init.d/functions ];then
|
||||
. /etc/init.d/functions
|
||||
fi
|
||||
|
||||
if [ -f /etc/rc.d/init.d/functions ];then
|
||||
. /etc/rc.d/init.d/functions
|
||||
fi
|
||||
|
||||
# Default values
|
||||
export HOME={$HOME_DIR}
|
||||
export USER={$RUN_USER}
|
||||
NAME=caddy
|
||||
CADDY_HOME={$SERVER_PATH}/caddy
|
||||
CADDY_PATH=${CADDY_HOME}/bin/$NAME
|
||||
CADDY_USER={$RUN_USER}
|
||||
SERVICENAME="caddy"
|
||||
LOCKFILE=/tmp/caddy.lock
|
||||
LOGPATH=${CADDY_HOME}/log
|
||||
LOGFILE=${LOGPATH}/caddy.log
|
||||
RETVAL=0
|
||||
|
||||
if [ -d $LOGPATH ];then
|
||||
mkdir -p $LOGPATH
|
||||
fi
|
||||
|
||||
[ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME
|
||||
DAEMON_OPTS="--check $NAME"
|
||||
[ ! -z "$CADDY_USER" ] && DAEMON_OPTS="$DAEMON_OPTS --user=${CADDY_USER}"
|
||||
|
||||
status(){
|
||||
isStart=`ps -ef|grep 'caddy run' |grep -v grep|awk '{print $2}'`
|
||||
if [ "$isStart" == '' ];then
|
||||
echo -e "${SERVICENAME} not running"
|
||||
else
|
||||
echo -e "${SERVICENAME}(pid $(echo $isStart)) already running"
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
isStart=`ps -ef | grep 'caddy run' | grep -v grep | awk '{print $2}'`
|
||||
if [ "$isStart" != '' ];then
|
||||
echo "${SERVICENAME}(pid $(echo $isStart)) already running"
|
||||
return $RETVAL
|
||||
fi
|
||||
|
||||
cd ${CADDY_HOME}
|
||||
echo -e "starting ${SERVICENAME}: \c"
|
||||
${CADDY_PATH} run --environ --config ${CADDY_HOME}/Caddyfile > ${LOGFILE} 2>&1 &
|
||||
RETVAL=$?
|
||||
[ $RETVAL = 0 ] && touch ${LOCKFILE} && echo -e "\033[32mdone\033[0m"
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
stop() {
|
||||
pids=`ps -ef | grep 'caddy run' | grep -v grep | awk '{print $2}'`
|
||||
arr=($pids)
|
||||
echo -e "stopping caddy... \c"
|
||||
for p in ${arr[@]}
|
||||
do
|
||||
kill -9 $p
|
||||
done
|
||||
echo -e "\033[32mdone\033[0m"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
reload)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: ${NAME} {start|stop|status|restart}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit $RETVAL
|
||||
76
plugins/caddy/install.sh
Executable file
76
plugins/caddy/install.sh
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin
|
||||
export PATH
|
||||
|
||||
# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/caddy && bash install.sh install 1.21.4
|
||||
# cd /www/server/mdserver-web/plugins/caddy && bash install.sh install 2.11
|
||||
# cd /www/server/mdserver-web/plugins/caddy && bash install.sh install 1.29.2
|
||||
# cd /www/server/mdserver-web/plugins/caddy && bash install.sh upgrade 1.29.2
|
||||
|
||||
# curl -I -H "Accept-Encoding: br" http://localhost
|
||||
# curl -I -H "Accept-Encoding: zstd" http://localhost
|
||||
# curl --http3 -v https://www.xxx.com
|
||||
|
||||
# cd /www/server/mdserver-web && python3 plugins/caddy/index.py run_info
|
||||
|
||||
curPath=`pwd`
|
||||
rootPath=$(dirname "$curPath")
|
||||
rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
sysName=`uname`
|
||||
action=$1
|
||||
type=$2
|
||||
|
||||
VERSION=$2
|
||||
openrestyDir=${serverPath}/source/caddy
|
||||
|
||||
if id www &> /dev/null ;then
|
||||
echo "www uid is `id -u www`"
|
||||
echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `"
|
||||
else
|
||||
groupadd www
|
||||
useradd -g www -s /bin/bash www
|
||||
fi
|
||||
|
||||
if [ "${action}" = "upgrade" ];then
|
||||
sh -x $curPath/versions/$2/install.sh $1
|
||||
|
||||
echo "${VERSION}" > $serverPath/caddy/version.pl
|
||||
|
||||
#初始化
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/caddy/index.py start
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/caddy/index.py initd_install
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
if [ "${2}" = "" ];then
|
||||
echo '缺少安装脚本版本...'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${action}" = "uninstall" ];then
|
||||
if [ -f /usr/lib/systemd/system/caddy.service ] || [ -f /lib/systemd/system/caddy.service ];then
|
||||
systemctl stop caddy
|
||||
rm -rf /usr/systemd/system/caddy.service
|
||||
rm -rf /lib/systemd/system/caddy.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
if [ -f $serverPath/caddy/init.d/caddy ];then
|
||||
$serverPath/caddy/init.d/caddy stop
|
||||
fi
|
||||
|
||||
rm -rf $serverPath/caddy
|
||||
fi
|
||||
|
||||
sh -x $curPath/versions/$2/install.sh $1
|
||||
|
||||
if [ "${action}" = "install" ] && [ -d $serverPath/caddy ];then
|
||||
echo "${VERSION}" > $serverPath/caddy/version.pl
|
||||
|
||||
#初始化
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/caddy/index.py start
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/caddy/index.py initd_install
|
||||
fi
|
||||
238
plugins/caddy/js/caddy.js
Executable file
238
plugins/caddy/js/caddy.js
Executable file
@@ -0,0 +1,238 @@
|
||||
function caddyPost(method, args, callback){
|
||||
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
|
||||
$.post('/plugins/run', {name:'caddy', func:method, args:JSON.stringify(args)}, function(data) {
|
||||
layer.close(loadT);
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
function orPluginService(_name, version){
|
||||
var data = {name:_name, func:'status'}
|
||||
if ( typeof(version) != 'undefined' ){
|
||||
data['version'] = version;
|
||||
} else {
|
||||
version = '';
|
||||
}
|
||||
|
||||
caddyPost('status', data, function(data){
|
||||
if (data.data == 'start'){
|
||||
orPluginSetService(_name, true, version);
|
||||
} else {
|
||||
orPluginSetService(_name, false, version);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function orPluginSetService(_name ,status, version){
|
||||
var serviceCon ='<p class="status">当前状态:<span>'+(status ? '开启' : '关闭' )+
|
||||
'</span><span style="color: '+
|
||||
(status?'#20a53a;':'red;')+
|
||||
' margin-left: 3px;" class="glyphicon ' + (status?'glyphicon glyphicon-play':'glyphicon-pause')+'"></span></p><div class="sfm-opt">\
|
||||
<button class="btn btn-default btn-sm" onclick="orPluginOpService(\''+_name+'\',\''+(status?'stop':'start')+'\',\''+version+'\')">'+(status?'停止':'启动')+'</button>\
|
||||
<button class="btn btn-default btn-sm" onclick="orPluginOpService(\''+_name+'\',\'restart\',\''+version+'\',\'yes\')">重启</button>\
|
||||
<button class="btn btn-default btn-sm" onclick="orPluginOpService(\''+_name+'\',\'reload\',\''+version+'\')">重载配置</button>\
|
||||
</div>';
|
||||
$(".soft-man-con").html(serviceCon);
|
||||
}
|
||||
|
||||
|
||||
function orPluginOpService(a, b, v,request_callback) {
|
||||
|
||||
var c = "name=" + a + "&func=" + b;
|
||||
if(v != ''){
|
||||
c = c + '&version='+v;
|
||||
}
|
||||
|
||||
var d = "";
|
||||
|
||||
switch(b) {
|
||||
case "stop":d = '停止';break;
|
||||
case "start":d = '启动';break;
|
||||
case "restart":d = '重启';break;
|
||||
case "reload":d = '重载';break;
|
||||
}
|
||||
layer.confirm( msgTpl('您真的要{1}{2}{3}服务吗?', [d,a,v]), {icon:3,closeBtn: 2}, function() {
|
||||
caddyPost('get_os',{},function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
if (!rdata['auth']){
|
||||
layer.prompt({title: '检查到权限不足,需要输入密码!', formType: 1},function(pwd, index){
|
||||
|
||||
layer.close(index);
|
||||
var data = {'pwd':pwd};
|
||||
c += '&args='+JSON.stringify(data);
|
||||
orPluginOpServiceOp(a,b,c,d,a,v,request_callback);
|
||||
});
|
||||
} else {
|
||||
orPluginOpServiceOp(a,b,c,d,a,v,request_callback);
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function orPluginOpServiceOp(a,b,c,d,a,v,request_callback){
|
||||
|
||||
var request_path = "/plugins/run";
|
||||
if (request_callback == 'yes'){
|
||||
request_path = "/plugins/callback";
|
||||
}
|
||||
|
||||
var e = layer.msg(msgTpl('正在{1}{2}{3}服务,请稍候...',[d,a,v]), {icon: 16,time: 0});
|
||||
$.post(request_path, c, function(g) {
|
||||
layer.close(e);
|
||||
|
||||
var f = g.data == 'ok' ? msgTpl('{1}{2}服务已{3}',[a,v,d]) : msgTpl('{1}{2}服务{3}失败!',[a,v,d]);
|
||||
layer.msg(f, {icon: g.data == 'ok' ? 1 : 2});
|
||||
|
||||
if( b != "reload" && g.data == 'ok' ) {
|
||||
if ( b == 'start' ) {
|
||||
orPluginSetService(a, true, v);
|
||||
} else if ( b == 'stop' ){
|
||||
orPluginSetService(a, false, v);
|
||||
}
|
||||
}
|
||||
|
||||
if( g.status && g.data != 'ok' ) {
|
||||
layer.msg(g.data, {icon: 2,time: 10000,shade: 0.3});
|
||||
}
|
||||
|
||||
},'json').error(function() {
|
||||
layer.close(e);
|
||||
layer.msg('操作异常!', {icon: 2});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//查看Nginx负载状态
|
||||
function getOpStatus() {
|
||||
var loadT = layer.msg('正在处理,请稍后...', { icon: 16, time: 0, shade: 0.3 });
|
||||
$.post('/plugins/run', {name:'openresty', func:'run_info'}, function(data) {
|
||||
layer.close(loadT);
|
||||
try {
|
||||
var rdata = $.parseJSON(data.data);
|
||||
if ('status' in rdata && !rdata.status){
|
||||
showMsg(rdata.msg, function(){}, null,3000);
|
||||
return;
|
||||
}
|
||||
|
||||
var con = "<div><table class='table table-hover table-bordered'>\
|
||||
<tr><th>活动连接(Active connections)</th><td>" + rdata.active + "</td></tr>\
|
||||
<tr><th>总连接次数(accepts)</th><td>" + rdata.accepts + "</td></tr>\
|
||||
<tr><th>总握手次数(handled)</th><td>" + rdata.handled + "</td></tr>\
|
||||
<tr><th>总请求数(requests)</th><td>" + rdata.requests + "</td></tr>\
|
||||
<tr><th>请求数(Reading)</th><td>" + rdata.Reading + "</td></tr>\
|
||||
<tr><th>响应数(Writing)</th><td>" + rdata.Writing + "</td></tr>\
|
||||
<tr><th>驻留进程(Waiting)</th><td>" + rdata.Waiting + "</td></tr>\
|
||||
</table></div>";
|
||||
$(".soft-man-con").html(con);
|
||||
}catch(err){
|
||||
showMsg(data.data, function(){}, null,3000);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
|
||||
function setOpCfg(){
|
||||
caddyPost('get_cfg', {}, function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
var rdata = rdata.data;
|
||||
// console.log(rdata);
|
||||
|
||||
var mlist = '';
|
||||
for (var i = 0; i < rdata.length; i++) {
|
||||
var w = '70'
|
||||
var ibody = '<input style="width: ' + w + 'px;" class="bt-input-text mr5" name="' + rdata[i].name + '" value="' + rdata[i].value + '" type="text" >';
|
||||
switch (rdata[i].type) {
|
||||
case 0:
|
||||
var selected_1 = (rdata[i].value == 1) ? 'selected' : '';
|
||||
var selected_0 = (rdata[i].value == 0) ? 'selected' : '';
|
||||
ibody = '<select class="bt-input-text mr5" name="' + rdata[i].name + '" style="width: ' + w + 'px;">\
|
||||
<option value="1" ' + selected_1 + '>开启</option>\
|
||||
<option value="0" ' + selected_0 + '>关闭</option>\
|
||||
</select>';
|
||||
break;
|
||||
case 1:
|
||||
var selected_1 = (rdata[i].value == 'on') ? 'selected' : '';
|
||||
var selected_0 = (rdata[i].value == 'off') ? 'selected' : '';
|
||||
ibody = '<select class="bt-input-text mr5" name="' + rdata[i].name + '" style="width: ' + w + 'px;">\
|
||||
<option value="on" ' + selected_1 + '>开启</option>\
|
||||
<option value="off" ' + selected_0 + '>关闭</option>\
|
||||
</select>';
|
||||
break;
|
||||
}
|
||||
mlist += '<p style="margin-top:15px;"><span>' + rdata[i].name + '</span>' + ibody + "<b class='unit c9'>"+rdata[i].unit+"</b>" +', <font class="c9">' + rdata[i].ps + '</font></p>';
|
||||
}
|
||||
var con = '<style>.conf_p p{margin-bottom: 2px}</style><div class="conf_p" style="margin-bottom:0">\
|
||||
' + mlist + '\
|
||||
<div style="margin-top:10px; padding-right:15px" class="text-right">\
|
||||
<button class="btn btn-success btn-sm mr5" onclick="setOpCfg()">刷新</button>\
|
||||
<button class="btn btn-success btn-sm" onclick="submitConf()">保存</button>\
|
||||
</div>\
|
||||
</div>'
|
||||
$(".soft-man-con").html(con);
|
||||
});
|
||||
}
|
||||
|
||||
function submitConf() {
|
||||
var data = {
|
||||
worker_processes: $("input[name='worker_processes']").val(),
|
||||
worker_connections: $("input[name='worker_connections']").val(),
|
||||
keepalive_timeout: $("input[name='keepalive_timeout']").val(),
|
||||
zstd: $("select[name='zstd']").val() || 'on',
|
||||
brotli: $("select[name='brotli']").val() || 'on',
|
||||
gzip: $("select[name='gzip']").val() || 'on',
|
||||
gzip_min_length: $("input[name='gzip_min_length']").val(),
|
||||
gzip_comp_level: $("input[name='gzip_comp_level']").val(),
|
||||
client_max_body_size: $("input[name='client_max_body_size']").val(),
|
||||
server_names_hash_bucket_size: $("input[name='server_names_hash_bucket_size']").val(),
|
||||
client_header_buffer_size: $("input[name='client_header_buffer_size']").val(),
|
||||
};
|
||||
|
||||
// console.log(data);
|
||||
caddyPost('set_cfg', data, function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
// console.log(rdata);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
});
|
||||
}
|
||||
|
||||
function otherFunc(){
|
||||
var con = '<p class="conf_p" style="text-align:center;">\
|
||||
<button class="btn btn-default btn-sm" onclick="cronAddCheck()">添加检查任务</button> \
|
||||
<button class="btn btn-default btn-sm" onclick="cronDelCheck()">删除检查任务</button>\
|
||||
</p>';
|
||||
$(".soft-man-con").html(con);
|
||||
}
|
||||
|
||||
function cronAddCheck(){
|
||||
caddyPost('cron_add_check', {}, function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
});
|
||||
}
|
||||
|
||||
function cronDelCheck(){
|
||||
caddyPost('cron_del_check', {}, function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
124
plugins/caddy/tool_task.py
Normal file
124
plugins/caddy/tool_task.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
from utils.crontab import crontab as MwCrontab
|
||||
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'caddy'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getTaskConf():
|
||||
conf = getServerDir() + "/task_config.json"
|
||||
return conf
|
||||
|
||||
|
||||
def getConfigData():
|
||||
conf = getTaskConf()
|
||||
if os.path.exists(conf):
|
||||
return json.loads(mw.readFile(getTaskConf()))
|
||||
return {
|
||||
"task_id": -1,
|
||||
"period": "minute-n",
|
||||
"where1": "3",
|
||||
"hour": "0",
|
||||
"minute": "0",
|
||||
}
|
||||
|
||||
|
||||
def createBgTask():
|
||||
removeBgTask()
|
||||
createBgTaskByName(getPluginName())
|
||||
|
||||
|
||||
def createBgTaskByName(name):
|
||||
args = getConfigData()
|
||||
_name = "[caddy]检查任务"
|
||||
res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find()
|
||||
if res:
|
||||
return True
|
||||
|
||||
if "task_id" in args and args["task_id"] > 0:
|
||||
res = mw.M("crontab").field("id, name").where(
|
||||
"id=?", (args["task_id"],)).find()
|
||||
if res and res["id"] == args["task_id"]:
|
||||
print("计划任务已经存在!")
|
||||
return True
|
||||
|
||||
mw_dir = mw.getPanelDir()
|
||||
cmd = '''
|
||||
mw_dir=%s
|
||||
rname=%s
|
||||
plugin_path=%s
|
||||
script_path=%s
|
||||
''' % (mw_dir, name, getServerDir(), getPluginDir())
|
||||
cmd += 'echo "bash $script_path/check.sh"' + "\n"
|
||||
cmd += 'cd $mw_dir && bash $script_path/check.sh' + "\n"
|
||||
|
||||
params = {
|
||||
'name': _name,
|
||||
'type': args['period'],
|
||||
'week': "",
|
||||
'where1': args['where1'],
|
||||
'hour': args['hour'],
|
||||
'minute': args['minute'],
|
||||
'save': "",
|
||||
'backup_to': "",
|
||||
'stype': "toShell",
|
||||
'sname': '',
|
||||
'sbody': cmd,
|
||||
'url_address': '',
|
||||
}
|
||||
|
||||
task_id = MwCrontab.instance().add(params)
|
||||
if task_id > 0:
|
||||
args["task_id"] = task_id
|
||||
args["name"] = name
|
||||
mw.writeFile(getTaskConf(), json.dumps(args))
|
||||
|
||||
|
||||
def removeBgTask():
|
||||
cfg = getConfigData()
|
||||
if "task_id" in cfg and cfg["task_id"] > 0:
|
||||
res = mw.M("crontab").field("id, name").where(
|
||||
"id=?", (cfg["task_id"],)).find()
|
||||
if res and res["id"] == cfg["task_id"]:
|
||||
data = MwCrontab.instance().delete(cfg["task_id"])
|
||||
if data["status"]:
|
||||
cfg["task_id"] = -1
|
||||
mw.writeFile(getTaskConf(), json.dumps(cfg))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
action = sys.argv[1]
|
||||
if action == "remove":
|
||||
removeBgTask()
|
||||
elif action == "add":
|
||||
createBgTask()
|
||||
88
plugins/caddy/versions/2.11/install.sh
Normal file
88
plugins/caddy/versions/2.11/install.sh
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin
|
||||
export PATH
|
||||
|
||||
# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/caddy && bash install.sh install 2.11
|
||||
# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/caddy && bash install.sh upgrade 2.11
|
||||
# cd /www/server/mdserver-web/plugins/caddy && bash install.sh install 2.11
|
||||
|
||||
curPath=`pwd`
|
||||
rootPath=$(dirname "$curPath")
|
||||
rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
sysName=`uname`
|
||||
action=$1
|
||||
type=$2
|
||||
|
||||
OS=`uname`
|
||||
OSNAME=''
|
||||
case $(uname) in
|
||||
Darwin) OSNAME="mac" ;;
|
||||
Linux) OSNAME="linux" ;;
|
||||
esac
|
||||
|
||||
ARCH=`uname -m`
|
||||
ARCH_NAME=''
|
||||
case $(uname -m) in
|
||||
i386) ARCH_NAME="386" ;;
|
||||
i686) ARCH_NAME="386" ;;
|
||||
x86_64) ARCH_NAME="amd64" ;;
|
||||
arm) ARCH_NAME="arm64" ;;
|
||||
arm64) ARCH_NAME="arm64" ;;
|
||||
aarch64) ARCH_NAME="arm64" ;;
|
||||
esac
|
||||
|
||||
VERSION=2.11.2
|
||||
|
||||
caddyDir=${serverPath}/source/caddy
|
||||
|
||||
Install_App()
|
||||
{
|
||||
if [ "${action}" = "install" ];then
|
||||
if [ -d $serverPath/caddy ];then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p ${caddyDir}
|
||||
# mkdir -p ${serverPath}/caddy
|
||||
echo 'install scripts ...'
|
||||
|
||||
FILE_NAME="caddy_${VERSION}_${OSNAME}_${ARCH_NAME}.tar.gz"
|
||||
|
||||
if [ ! -f ${caddyDir}/${FILE_NAME} ];then
|
||||
wget --no-check-certificate -O ${caddyDir}/${FILE_NAME} "https://github.com/caddyserver/caddy/releases/download/v${VERSION}/${FILE_NAME}"
|
||||
fi
|
||||
|
||||
|
||||
echo "cd $serverPath/source/caddy/caddy && tar -zxvf ${caddyDir}/$FILE_NAME"
|
||||
|
||||
mkdir -p cd $serverPath/source/caddy/caddy
|
||||
cd $serverPath/source/caddy/caddy && tar -zxvf ${caddyDir}/$FILE_NAME
|
||||
if [ "$OSNAME" = "mac" ];then
|
||||
xattr -cr caddy
|
||||
fi
|
||||
|
||||
if [ ! -f $serverPath/caddy/bin ];then
|
||||
mkdir -p $serverPath/caddy/bin
|
||||
mv $serverPath/source/caddy/caddy/* $serverPath/caddy/bin
|
||||
chmod +x $serverPath/caddy/bin/caddy
|
||||
fi
|
||||
|
||||
echo 'Installation of caddy completed'
|
||||
}
|
||||
|
||||
Uninstall_App()
|
||||
{
|
||||
echo 'Uninstalling caddy completed'
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" = "install" ];then
|
||||
Install_App
|
||||
elif [ "${1}" = "upgrade" ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
fi
|
||||
@@ -89,6 +89,11 @@ def initConf():
|
||||
"/var/log/messages.*",
|
||||
"/var/log/debug.*",
|
||||
"/var/log/syslog.*",
|
||||
"/var/log/syslog",
|
||||
"/var/log/mail.*",
|
||||
"/var/log/mail.err.*",
|
||||
"/var/log/mail.info.*",
|
||||
"/var/log/mail.warn.*",
|
||||
"/var/log/btmp.*",
|
||||
"/var/log/sa/sa*",
|
||||
"/var/log/sysstat/sa*",
|
||||
@@ -131,11 +136,13 @@ def initConf():
|
||||
"/var/log/wtmp",
|
||||
"/var/log/secure",
|
||||
"/var/log/lastlog",
|
||||
"/var/log/syslog",
|
||||
"/var/log/cron",
|
||||
"/www/wwwlogs",
|
||||
"/www/server/rsyncd",
|
||||
"/www/server/acme_pandominassl_apply",
|
||||
"/www/server/sphinx/index",
|
||||
"/www/server/manticoresearch",
|
||||
"/www/server/mongodb/logs",
|
||||
"/www/server/php/53/var/log",
|
||||
"/www/server/php/54/var/log",
|
||||
@@ -151,17 +158,21 @@ def initConf():
|
||||
"/www/server/php/82/var/log",
|
||||
"/www/server/php/83/var/log",
|
||||
"/www/server/php/84/var/log",
|
||||
"/www/server/php/85/var/log",
|
||||
"/www/server/openresty/nginx/logs",
|
||||
"/www/server/phpmyadmin",
|
||||
"/www/server/apache/httpd/logs",
|
||||
"/www/server/redis/data",
|
||||
"/www/server/alist/data/log",
|
||||
"/www/server/dztasks/logs",
|
||||
"/www/server/cron",
|
||||
"/www/server/rsyncd/lsyncd.log",
|
||||
"/www/server/mdserver-web/logs",
|
||||
]
|
||||
for i in clogcom:
|
||||
if os.path.exists(i):
|
||||
content += i + "\n"
|
||||
|
||||
content += "/www/server/cron\n"
|
||||
# 清理日志
|
||||
rootDir = "/var/log"
|
||||
|
||||
@@ -241,7 +252,7 @@ def cleanDirLog(path):
|
||||
def cleanRun():
|
||||
plugin_dir = getPluginDir()
|
||||
log_file = getServerDir()+'/clean.log'
|
||||
cmd = 'python3 '+plugin_dir+'/index.py clean > '+log_file
|
||||
cmd = 'cd '+mw.getPanelDir()+' && python3 '+plugin_dir+'/index.py clean > '+log_file
|
||||
os.system(cmd)
|
||||
return mw.returnJson(True, '执行成功!')
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"sort":2,
|
||||
"title":"日志清理",
|
||||
"tip":"soft",
|
||||
"name":"clean",
|
||||
|
||||
@@ -13,7 +13,7 @@ serverPath=$(dirname "$rootPath")
|
||||
VERSION=$2
|
||||
|
||||
if [ -f ${rootPath}/bin/activate ];then
|
||||
source ${rootPath}/bin/activate
|
||||
. ${rootPath}/bin/activate
|
||||
fi
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Uninstall_app()
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_app
|
||||
else
|
||||
Uninstall_app
|
||||
|
||||
@@ -113,7 +113,7 @@ def removeBgTask():
|
||||
"id=?", (cfg["task_id"],)).find()
|
||||
if res and res["id"] == cfg["task_id"]:
|
||||
data = MwCrontab.instance().delete(cfg["task_id"])
|
||||
if data[0]:
|
||||
if data["status"]:
|
||||
cfg["task_id"] = -1
|
||||
mw.writeFile(getTaskConf(), json.dumps(cfg))
|
||||
return True
|
||||
|
||||
@@ -99,7 +99,7 @@ def readConfigTpl():
|
||||
|
||||
def status():
|
||||
data = mw.execShell(
|
||||
"ps aux|grep cloudreve |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'")
|
||||
"ps -ef|grep cloudreve |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'")
|
||||
|
||||
if data[0] == '':
|
||||
return 'stop'
|
||||
|
||||
@@ -16,16 +16,16 @@ sysArch=`arch`
|
||||
sysName=`uname`
|
||||
|
||||
ALIST_ARCH_NAME=amd64
|
||||
if [ "$sysArch" == "arm64" ];then
|
||||
if [ "$sysArch" = "arm64" ];then
|
||||
ALIST_ARCH_NAME=arm64
|
||||
elif [ "$sysArch" == "x86_64" ]; then
|
||||
elif [ "$sysArch" = "x86_64" ]; then
|
||||
ALIST_ARCH_NAME=amd64
|
||||
elif [ "$sysArch" == "aarch64" ]; then
|
||||
elif [ "$sysArch" = "aarch64" ]; then
|
||||
ALIST_ARCH_NAME=aarch64
|
||||
fi
|
||||
|
||||
ALIST_NAME=linux
|
||||
if [ "$sysName" == "Darwin" ];then
|
||||
if [ "$sysName" = "Darwin" ];then
|
||||
ALIST_NAME=darwin
|
||||
fi
|
||||
|
||||
@@ -83,7 +83,7 @@ Uninstall_App()
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
|
||||
78
plugins/cryptocurrency_trade/README.md
Normal file
78
plugins/cryptocurrency_trade/README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# plugins_cryptocurrency_trade
|
||||
|
||||
- https://docs.ccxt.com/en/latest/install.html#python-proxies
|
||||
|
||||
数字货币量化交易插件
|
||||
|
||||
```
|
||||
cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/okex/strategy/st_demo.py
|
||||
|
||||
python3 plugins/cryptocurrency_trade/ccxt/okex/strategy/st_demo.py
|
||||
|
||||
miniconda3
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
# 免费TV策略
|
||||
https://cn.tradingview.com/scripts/?script_type=strategies
|
||||
|
||||
|
||||
### tradingview pine 指标
|
||||
```
|
||||
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
|
||||
// © TenCloud
|
||||
|
||||
//@version=5
|
||||
indicator("第一个指标",shorttitle = "开心吧")
|
||||
|
||||
ema10 = ta.ema(close, 25)
|
||||
ema100 = ta.ema(close, 100)
|
||||
l1=plot(ema10, color=color.red,title = "ema10")
|
||||
l2=plot(ema100,color = color.green, title = "ema100")
|
||||
|
||||
|
||||
buy = ta.crossover(ema10,ema100)
|
||||
sell = ta.crossunder(ema10,ema100)
|
||||
|
||||
plotchar(buy, text = "buy", color=color.green)
|
||||
plotchar(sell, text="sell", color=color.red)
|
||||
|
||||
l_color = ema10>ema100 ? color.green:color.red
|
||||
|
||||
fill(l1,l2,color=color.new(l_color,70))
|
||||
|
||||
```
|
||||
|
||||
### tradingview pine 策略
|
||||
```
|
||||
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
|
||||
// © TenCloud
|
||||
|
||||
//@version=5
|
||||
strategy("第一个指标",shorttitle = "开心吧", overlay = true, initial_capital = 1000)
|
||||
|
||||
ema10 = ta.ema(close, 25)
|
||||
ema100 = ta.ema(close, 100)
|
||||
l1=plot(ema10, color=color.red,title = "ema10")
|
||||
l2=plot(ema100,color = color.green, title = "ema100")
|
||||
|
||||
|
||||
buy = ta.crossover(ema10,ema100)
|
||||
sell = ta.crossunder(ema10,ema100)
|
||||
|
||||
if buy
|
||||
strategy.entry("long1", strategy.long,1)
|
||||
|
||||
if sell
|
||||
strategy.close("long1", qty_percent = 100,comment = "平仓多单")
|
||||
// plotchar(buy, text = "buy", color=color.green)
|
||||
// plotchar(sell, text="sell", color=color.red)
|
||||
|
||||
// l_color = ema10>ema100 ? color.green:color.red
|
||||
|
||||
// fill(l1,l2,color=color.new(l_color,70))
|
||||
|
||||
|
||||
```
|
||||
555
plugins/cryptocurrency_trade/ccxt/public_data/data.py
Normal file
555
plugins/cryptocurrency_trade/ccxt/public_data/data.py
Normal file
@@ -0,0 +1,555 @@
|
||||
import ccxt
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
import time
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
import glob
|
||||
import threading
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
# print(os.getcwd())
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py run
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py long
|
||||
|
||||
# 查看支持的交易所
|
||||
# print(ccxt.exchanges)
|
||||
|
||||
# 代理设置
|
||||
# exchange = ccxt.poloniex({
|
||||
# 'proxies': {
|
||||
# 'http': 'http://127.0.0.1:1088'
|
||||
# },
|
||||
# })
|
||||
|
||||
# exchange = ccxt.poloniex()
|
||||
exchange = ccxt.okex()
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'cryptocurrency_trade'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getConfigData():
|
||||
cfg_path = getServerDir() + "/data.cfg"
|
||||
if not os.path.exists(cfg_path):
|
||||
mw.writeFile(cfg_path, '{}')
|
||||
t = mw.readFile(cfg_path)
|
||||
return json.loads(t)
|
||||
|
||||
|
||||
def writeLog(log_str):
|
||||
if __name__ == "__main__":
|
||||
print(log_str)
|
||||
|
||||
log_file = getServerDir() + '/logs/datasource.log'
|
||||
mw.writeFileLog(log_str, log_file, 1 * 1024 * 1024)
|
||||
return True
|
||||
|
||||
|
||||
def isSetDbConf():
|
||||
data = getConfigData()
|
||||
if 'db' in data:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def beforeDate(day=360):
|
||||
dd = time.time() - day * 86400
|
||||
d = datetime.fromtimestamp(dd)
|
||||
day = d.strftime("%Y-%m-%d")
|
||||
return day
|
||||
|
||||
|
||||
def getTextTimeShow(time):
|
||||
d = datetime.fromtimestamp(time)
|
||||
day = d.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return day
|
||||
|
||||
|
||||
def isSqlError(mysqlMsg):
|
||||
# 检测数据库执行错误
|
||||
mysqlMsg = str(mysqlMsg)
|
||||
if "MySQLdb" in mysqlMsg:
|
||||
return mw.returnJson(False, 'MySQLdb组件缺失! <br>进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3')
|
||||
if "2002," in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!')
|
||||
if "2003," in mysqlMsg:
|
||||
return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)")
|
||||
if "using password:" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库密码错误')
|
||||
if "1045" in mysqlMsg:
|
||||
return mw.returnJson(False, '连接错误!')
|
||||
if "SQL syntax" in mysqlMsg:
|
||||
return mw.returnJson(False, 'SQL语法错误!')
|
||||
if "Connection refused" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!')
|
||||
if "1133" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库用户不存在!')
|
||||
if "1007" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库已经存在!')
|
||||
return None
|
||||
|
||||
|
||||
def pMysqlDb():
|
||||
# pymysql
|
||||
db = mw.getMyORM()
|
||||
data = getConfigData()
|
||||
db_data = data['db']
|
||||
db.setHost(db_data['db_host'])
|
||||
db.setPort(db_data['db_port'])
|
||||
db.setUser(db_data['db_user'])
|
||||
db.setPwd(db_data['db_pass'])
|
||||
db.setDbName(db_data['db_name'])
|
||||
return db
|
||||
|
||||
|
||||
def makeInsertSql(table, item):
|
||||
sql = "insert into " + table
|
||||
keyStr = '('
|
||||
valueStr = ' values('
|
||||
for i in item:
|
||||
# print i, item[i]
|
||||
keyStr += '`' + str(i) + '`,'
|
||||
valueStr += "'" + str(item[i]) + "',"
|
||||
|
||||
keyStrLen = len(keyStr)
|
||||
keyStr = keyStr[0:keyStrLen - 1]
|
||||
keyStr += ') '
|
||||
|
||||
valueStrLen = len(valueStr)
|
||||
valueStr = valueStr[0:valueStrLen - 1]
|
||||
valueStr += ') '
|
||||
|
||||
sql += keyStr
|
||||
sql += valueStr
|
||||
|
||||
return sql
|
||||
|
||||
|
||||
def makeReplaceSql(table, item):
|
||||
sql = "replace into " + table
|
||||
keyStr = '('
|
||||
valueStr = ' values('
|
||||
for i in item:
|
||||
# print i, item[i]
|
||||
keyStr += '`' + str(i) + '`,'
|
||||
valueStr += "'" + str(item[i]) + "',"
|
||||
|
||||
keyStrLen = len(keyStr)
|
||||
keyStr = keyStr[0:keyStrLen - 1]
|
||||
keyStr += ') '
|
||||
|
||||
valueStrLen = len(valueStr)
|
||||
valueStr = valueStr[0:valueStrLen - 1]
|
||||
valueStr += ') '
|
||||
|
||||
sql += keyStr
|
||||
sql += valueStr
|
||||
|
||||
return sql
|
||||
|
||||
|
||||
def makeUpdateSql(table, item, mid):
|
||||
sql = "update " + table + " set "
|
||||
keyStr = ''
|
||||
for i in item:
|
||||
keyStr += '`' + str(i) + '`=' + "'" + item[i] + "',"
|
||||
|
||||
keyStrLen = len(keyStr)
|
||||
keyStr = keyStr[0:keyStrLen - 1]
|
||||
sql += keyStr
|
||||
sql += " where id = '" + str(mid) + "'"
|
||||
return sql
|
||||
|
||||
|
||||
def dataToDb(table_name, data):
|
||||
pdb = pMysqlDb()
|
||||
for i in data:
|
||||
rdata = {
|
||||
'addtime': int(i[0] / 1000),
|
||||
'open': i[1],
|
||||
'high': i[2],
|
||||
'low': i[3],
|
||||
'close': i[4],
|
||||
'vol': i[5],
|
||||
}
|
||||
|
||||
# sql = """SELECT id FROM %s where addtime='%s' LIMIT 1""" % (
|
||||
# table_name, rdata['addtime'])
|
||||
# fdata = pdb.query(sql)
|
||||
# if fdata:
|
||||
# print(table_name + ":" + str(rdata['addtime']) + ", old to db ok")
|
||||
# else:
|
||||
# isql = makeReplaceSql(table_name, rdata)
|
||||
# r = pdb.execute(isql)
|
||||
# print(table_name + ":" + str(rdata['addtime']) + ", go to db ok")
|
||||
|
||||
isql = makeReplaceSql(table_name, rdata)
|
||||
r = pdb.execute(isql)
|
||||
# print(table_name + ":" + str(rdata['addtime']) + ", go to db ok")
|
||||
return True
|
||||
|
||||
|
||||
def makeTableName(input_type="btc", input_tf="1m"):
|
||||
table_name = "ct_%s_%s" % (input_type, input_tf)
|
||||
return table_name
|
||||
|
||||
|
||||
def isHasTable(input_type="btc", input_tf="1m"):
|
||||
pdb = pMysqlDb()
|
||||
|
||||
# input_type = 'btc'
|
||||
# input_tf = '1m'
|
||||
table_name = makeTableName(input_type, input_tf)
|
||||
mtable = pdb.query("show tables like '%s'" % (table_name,))
|
||||
if len(mtable) != 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def createSql(input_type="btc", input_tf="1m"):
|
||||
pdb = pMysqlDb()
|
||||
|
||||
# input_type = 'btc'
|
||||
# input_tf = '1m'
|
||||
table_name = makeTableName(input_type, input_tf)
|
||||
|
||||
mtable = pdb.query("show tables like '%s'" % (table_name,))
|
||||
if len(mtable) != 0:
|
||||
return True
|
||||
|
||||
sql_tpl = getPluginDir() + "/conf/create.sql"
|
||||
content = mw.readFile(sql_tpl)
|
||||
content = content.replace("xx1", input_type)
|
||||
content = content.replace("xx2", input_tf)
|
||||
|
||||
res = pdb.execute(content)
|
||||
# pprint(res)
|
||||
return True
|
||||
|
||||
|
||||
def getDataFetch(symbol, start_time="2023-3-1", input_tf="1m", limit=500):
|
||||
start_time = datetime.strptime(start_time, '%Y-%m-%d')
|
||||
end_time = datetime.strptime(end_time, '%Y-%m-%d')
|
||||
|
||||
start_time_stamp = int(time.mktime(start_time.timetuple())) * 1000
|
||||
data = exchange.fetch_ohlcv(symbol, timeframe=input_tf,
|
||||
since=start_time_stamp, limit=limit)
|
||||
return data
|
||||
|
||||
|
||||
def getPointData(symbol, start_time=1677713220000, input_tf="1m", limit=500):
|
||||
data = exchange.fetch_ohlcv(
|
||||
symbol, timeframe=input_tf, since=start_time, limit=limit)
|
||||
return data
|
||||
|
||||
|
||||
def toUnixTimeSecond(tf="1m"):
|
||||
if tf.find("m") > -1:
|
||||
v = int(tf.replace("m", ''))
|
||||
return v * 60
|
||||
|
||||
if tf.find("h") > -1:
|
||||
v = int(tf.replace("h", ''))
|
||||
return v * 3600
|
||||
|
||||
if tf.find("d") > -1:
|
||||
v = int(tf.replace("d", ''))
|
||||
return v * 86400
|
||||
return 0
|
||||
|
||||
|
||||
def findAndUpdateData(tag, input_tf="1m", start_time="2023-1-1", limit=300):
|
||||
pdb = pMysqlDb()
|
||||
table_name = makeTableName(tag, input_tf)
|
||||
sql = 'SELECT addtime FROM ' + table_name + ' order by addtime desc LIMIT 1'
|
||||
qdata = pdb.query(sql)
|
||||
|
||||
start_time = datetime.strptime(start_time, '%Y-%m-%d')
|
||||
start_time = int(time.mktime(start_time.timetuple())) * 1000
|
||||
if len(qdata) != 0:
|
||||
start_time = int(qdata[0]['addtime']) * 1000
|
||||
|
||||
pre_time = toUnixTimeSecond(input_tf) * 5 * 1000
|
||||
start_time = start_time - pre_time
|
||||
# pprint(start_time)
|
||||
symbol = tag.upper() + "/USDT"
|
||||
data = getPointData(symbol, start_time, input_tf, limit)
|
||||
|
||||
# pprint(data)
|
||||
# print("------------lini===========")
|
||||
# pprint(data[1:])
|
||||
|
||||
if len(qdata) == 1:
|
||||
data = data[1:]
|
||||
|
||||
dataToDb(table_name, data)
|
||||
|
||||
now = time.strftime("%m/%d %H:%M:%S")
|
||||
data_len = len(data)
|
||||
msg_head = now + "|虚拟币:" + tag + ",tf:" + \
|
||||
input_tf + "!,总数:" + str(data_len)
|
||||
writeLog(msg_head)
|
||||
if data_len > 0:
|
||||
# print("new data time", data[data_len - 1][0])
|
||||
dt = getTextTimeShow(data[data_len - 1][0] / 1000)
|
||||
msg = now + "|最新日期:" + dt
|
||||
writeLog(msg)
|
||||
|
||||
table_name = makeTableName(tag, input_tf)
|
||||
|
||||
sql = "SELECT addtime FROM " + table_name + \
|
||||
" order by addtime desc limit 10000,1"
|
||||
qdata = pdb.query(sql)
|
||||
|
||||
if len(qdata) > 0:
|
||||
print(qdata)
|
||||
|
||||
# input_tf_time = toUnixTimeSecond(input_tf)
|
||||
# now_t = int(time.time())
|
||||
|
||||
# del_before_t = now_t - input_tf_time * 1000
|
||||
|
||||
sql = "delete from %s where addtime<'%d' " % (
|
||||
table_name, qdata[0]['addtime'],)
|
||||
writeLog("删除冗余数据:" + str(sql))
|
||||
pdb.execute(sql)
|
||||
return True
|
||||
|
||||
|
||||
def dataToDbTpl(tag='btc', input_tf="1m", start_time="2023-1-1", limit=300):
|
||||
# tag = "btc"
|
||||
# tf = "1m"
|
||||
# start_time = "2023-1-1"
|
||||
# end_time = "2023-3-2"
|
||||
symbol = tag.lower() + '/USDT'
|
||||
if not isHasTable(tag, input_tf):
|
||||
createSql(tag, input_tf)
|
||||
data = getDataFetch(symbol, start_time, input_tf, limit)
|
||||
table_name = makeTableName(tag, input_tf)
|
||||
|
||||
dataToDb(table_name, data)
|
||||
now = time.strftime("%m/%d %H:%M:%S")
|
||||
data_len = len(data)
|
||||
writeLog(now + "|数据获取成功!,总数:" + str(data_len))
|
||||
else:
|
||||
findAndUpdateData(tag, input_tf, start_time, limit)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def dataToDbList(input_tf="1m", start_time="2023-1-1"):
|
||||
|
||||
data = getConfigData()
|
||||
if not 'token' in data:
|
||||
writeLog("未设置同步配置,需要添加币种!")
|
||||
return
|
||||
|
||||
for t in data['token']:
|
||||
dataToDbTpl(t, input_tf, start_time)
|
||||
time.sleep(4)
|
||||
|
||||
|
||||
def dataRunToDb():
|
||||
data = getConfigData()
|
||||
if not 'db' in data:
|
||||
writeLog("数据库未设置!")
|
||||
return
|
||||
|
||||
tag = "btc"
|
||||
symbol = tag.upper() + '/USDT'
|
||||
|
||||
# pprint(exchange.fetch_ticker('XRP/USDT'))
|
||||
limit_count = 1
|
||||
start_time = "2023-1-1"
|
||||
end_time = "2023-3-2"
|
||||
tf = "1m"
|
||||
|
||||
if not isHasTable(tag, tf):
|
||||
createSql(tag, tf)
|
||||
data = getDataFetch(symbol, start_time, end_time, tf, limit_count)
|
||||
table_name = makeTableName(tag, tf)
|
||||
|
||||
dataToDb(table_name, data)
|
||||
china_datetime = str(datetime.now())
|
||||
data_len = len(data)
|
||||
writeLog(china_datetime + ":数据获取成功!,总数:" + str(data_len))
|
||||
else:
|
||||
findAndUpdateData(tag, tf, start_time, 30)
|
||||
return data
|
||||
|
||||
|
||||
def startTask():
|
||||
# 任务队列
|
||||
try:
|
||||
while True:
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
time.sleep(60)
|
||||
startTask()
|
||||
|
||||
|
||||
def setDaemon(t):
|
||||
if sys.version_info.major == 3 and sys.version_info.minor >= 10:
|
||||
t.daemon = True
|
||||
else:
|
||||
t.setDaemon(True)
|
||||
return t
|
||||
|
||||
|
||||
def dataDay():
|
||||
try:
|
||||
while True:
|
||||
if not isSetDbConf():
|
||||
print("数据库未设置!")
|
||||
else:
|
||||
start_time = beforeDate(2 * 365)
|
||||
dataToDbList("1d", start_time=start_time)
|
||||
time.sleep(3600)
|
||||
# time.sleep(10)
|
||||
except Exception as e:
|
||||
time.sleep(60)
|
||||
dataDay()
|
||||
|
||||
|
||||
def data4h():
|
||||
try:
|
||||
while True:
|
||||
if not isSetDbConf():
|
||||
print("数据库未设置!")
|
||||
else:
|
||||
start_time = beforeDate(365)
|
||||
dataToDbList("4h", start_time=start_time)
|
||||
time.sleep(10)
|
||||
except Exception as e:
|
||||
time.sleep(60)
|
||||
data4h()
|
||||
|
||||
|
||||
def data1h():
|
||||
try:
|
||||
while True:
|
||||
if not isSetDbConf():
|
||||
print("数据库未设置!")
|
||||
else:
|
||||
start_time = beforeDate(365)
|
||||
dataToDbList("1h", start_time=start_time)
|
||||
time.sleep(10)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
time.sleep(60)
|
||||
data1h()
|
||||
|
||||
|
||||
def data15m():
|
||||
try:
|
||||
while True:
|
||||
if not isSetDbConf():
|
||||
print("数据库未设置!")
|
||||
else:
|
||||
start_time = beforeDate(365)
|
||||
dataToDbList("15m", start_time=start_time)
|
||||
time.sleep(10)
|
||||
except Exception as e:
|
||||
time.sleep(60)
|
||||
data15m()
|
||||
|
||||
|
||||
def data5m():
|
||||
try:
|
||||
while True:
|
||||
if not isSetDbConf():
|
||||
print("数据库未设置!")
|
||||
else:
|
||||
start_time = beforeDate(180)
|
||||
dataToDbList("5m", start_time=start_time)
|
||||
time.sleep(10)
|
||||
except Exception as e:
|
||||
time.sleep(60)
|
||||
data5m()
|
||||
|
||||
|
||||
def data1m():
|
||||
try:
|
||||
while True:
|
||||
if not isSetDbConf():
|
||||
print("数据库未设置!")
|
||||
else:
|
||||
start_time = beforeDate(30)
|
||||
dataToDbList("1m", start_time=start_time)
|
||||
time.sleep(10)
|
||||
except Exception as e:
|
||||
time.sleep(60)
|
||||
data1m()
|
||||
|
||||
|
||||
def longRun():
|
||||
|
||||
# 日线
|
||||
d1_tt = threading.Thread(target=dataDay)
|
||||
d1_tt = setDaemon(d1_tt)
|
||||
d1_tt.start()
|
||||
|
||||
# 4h
|
||||
h4_tt = threading.Thread(target=data4h)
|
||||
h4_tt = setDaemon(h4_tt)
|
||||
h4_tt.start()
|
||||
|
||||
# 1h
|
||||
h1_tt = threading.Thread(target=data1h)
|
||||
h1_tt = setDaemon(h1_tt)
|
||||
h1_tt.start()
|
||||
|
||||
# 15m
|
||||
m15_tt = threading.Thread(target=data15m)
|
||||
m15_tt = setDaemon(m15_tt)
|
||||
m15_tt.start()
|
||||
|
||||
# 5m
|
||||
m5_tt = threading.Thread(target=data5m)
|
||||
m5_tt = setDaemon(m5_tt)
|
||||
m5_tt.start()
|
||||
|
||||
# 1m
|
||||
# h1m_tt = threading.Thread(target=data1m)
|
||||
# h1m_tt = setDaemon(h1m_tt)
|
||||
# h1m_tt.start()
|
||||
|
||||
startTask()
|
||||
|
||||
|
||||
def dataRun():
|
||||
do_num = 0
|
||||
while True:
|
||||
if do_num > 1:
|
||||
break
|
||||
do_num = do_num + 1
|
||||
dataRunToDb()
|
||||
time.sleep(6)
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'run':
|
||||
data5m()()
|
||||
elif func == 'long':
|
||||
longRun()
|
||||
elif func == 'demo':
|
||||
writeLog('111')
|
||||
else:
|
||||
print('error')
|
||||
12
plugins/cryptocurrency_trade/ccxt/public_data/data.sh
Normal file
12
plugins/cryptocurrency_trade/ccxt/public_data/data.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
curPath=`pwd`
|
||||
|
||||
# bash plugins/cryptocurrency_trade/ccxt/public_data/data.sh
|
||||
|
||||
if [ -f ${curPath}/bin/activate ];then
|
||||
. ${curPath}/bin/activate
|
||||
fi
|
||||
|
||||
python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py long
|
||||
19
plugins/cryptocurrency_trade/ccxt/public_data/demo.py
Normal file
19
plugins/cryptocurrency_trade/ccxt/public_data/demo.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import time
|
||||
|
||||
from datetime import datetime
|
||||
china_datetime = datetime.now()
|
||||
print(china_datetime)
|
||||
|
||||
|
||||
# date()
|
||||
tt = time.time() - 180 * 86400
|
||||
|
||||
# print(date("%Y-%m-%d", time.time()))
|
||||
# t = datetime.strptime(str(time.time() - 180 * 86400), "%Y-%m-%d")
|
||||
# print(t)
|
||||
|
||||
|
||||
d = datetime.fromtimestamp(tt)
|
||||
# 精确到毫秒
|
||||
str1 = d.strftime("%Y-%m-%d")
|
||||
print(str1)
|
||||
352
plugins/cryptocurrency_trade/ccxt/strategy/common.py
Normal file
352
plugins/cryptocurrency_trade/ccxt/strategy/common.py
Normal file
@@ -0,0 +1,352 @@
|
||||
import ccxt
|
||||
|
||||
|
||||
import time
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
import glob
|
||||
import threading
|
||||
|
||||
import pandas as pd
|
||||
from decimal import Decimal
|
||||
from pprint import pprint
|
||||
from datetime import datetime
|
||||
|
||||
# print(os.getcwd())
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
exchange = ccxt.poloniex()
|
||||
|
||||
|
||||
def calc_ClosingPriceWithOutStopLossPrice(open_price, stype='buy', profit=0.5):
|
||||
# profit 百分比 %
|
||||
v = 0
|
||||
vf_len = len(str(open_price).split('.')[1])
|
||||
if stype == 'buy':
|
||||
v = open_price * (100 + profit) / 100
|
||||
else:
|
||||
v = open_price * (100 - profit) / 100
|
||||
return round(v, vf_len)
|
||||
|
||||
|
||||
def roundVal(price, compare_price):
|
||||
price = float(price)
|
||||
csplite = str(compare_price).split('.')
|
||||
clen = len(csplite)
|
||||
if clen == 2:
|
||||
vf_len = len(csplite[1])
|
||||
return round(price, vf_len)
|
||||
return price
|
||||
|
||||
|
||||
def roundValCeil(price, compare_price):
|
||||
price = float(price)
|
||||
csplite = str(compare_price).split('.')
|
||||
clen = len(csplite)
|
||||
if clen == 2:
|
||||
vf_len = len(csplite[1]) - 1
|
||||
return round(price, vf_len)
|
||||
return price
|
||||
|
||||
|
||||
def multiply(a1, a2):
|
||||
v = Decimal(str(a1)) * Decimal(str(a2))
|
||||
return v
|
||||
|
||||
|
||||
def addition(a1, a2):
|
||||
v = Decimal(str(a1)) + Decimal(str(a2))
|
||||
return v
|
||||
|
||||
|
||||
def subtract(a1, a2):
|
||||
v = Decimal(str(a1)) - Decimal(str(a2))
|
||||
return v
|
||||
|
||||
|
||||
def divided(a1, a2):
|
||||
v = Decimal(str(a1)) / Decimal(str(a2))
|
||||
return v
|
||||
|
||||
|
||||
def calc_ClosingPrice(open_price, stop_loss_price, stype='buy', profit=0.5):
|
||||
# profit 百分比 %
|
||||
v = 0
|
||||
vf_len = len(str(open_price).split('.')[1])
|
||||
if stype == 'buy':
|
||||
v = open_price * (100 + profit) / 100
|
||||
diff = open_price - stop_loss_price
|
||||
profit_price = open_price + diff * 1.5
|
||||
if profit_price > v:
|
||||
return round(profit_price, vf_len)
|
||||
else:
|
||||
v = open_price * (100 - profit) / 100
|
||||
diff = stop_loss_price - open_price
|
||||
profit_price = open_price - diff * 1.5
|
||||
if profit_price < v:
|
||||
return round(profit_price, vf_len)
|
||||
return round(v, vf_len)
|
||||
|
||||
|
||||
def toDateFromInt(time_unix, tf_format="%Y-%m-%d %H:%M:%S", time_zone="Asia/Shanghai"):
|
||||
# 取格式时间
|
||||
import time
|
||||
os.environ['TZ'] = time_zone
|
||||
time_str = time.localtime(time_unix)
|
||||
time.tzset()
|
||||
|
||||
return time.strftime(tf_format, time_str)
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'cryptocurrency_trade'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getConfigData():
|
||||
cfg_path = getServerDir() + "/data.cfg"
|
||||
if not os.path.exists(cfg_path):
|
||||
mw.writeFile(cfg_path, '{}')
|
||||
t = mw.readFile(cfg_path)
|
||||
return json.loads(t)
|
||||
|
||||
|
||||
def getUserCfgData():
|
||||
data = getConfigData()
|
||||
if 'user' in data:
|
||||
try:
|
||||
udata = mw.deDoubleCrypt('mw', data['user'])
|
||||
udata = json.loads(udata)
|
||||
return udata
|
||||
except Exception as e:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
def initEx():
|
||||
data = getUserCfgData()
|
||||
# print(data)
|
||||
if (len(data) > 0):
|
||||
exchange = ccxt.okex({
|
||||
"apiKey": data['app_key'],
|
||||
"secret": data['secret'],
|
||||
"password": data['password'],
|
||||
})
|
||||
return exchange
|
||||
else:
|
||||
print("初始化失败,检查原因!")
|
||||
exchange = ccxt.poloniex()
|
||||
return exchange
|
||||
|
||||
|
||||
def toUnixTimeSecond(tf="1m"):
|
||||
if tf.find("m") > -1:
|
||||
v = int(tf.replace("m", ''))
|
||||
return v * 60
|
||||
|
||||
if tf.find("h") > -1:
|
||||
v = int(tf.replace("h", ''))
|
||||
return v * 3600
|
||||
|
||||
if tf.find("d") > -1:
|
||||
v = int(tf.replace("d", ''))
|
||||
return v * 86400
|
||||
return 0
|
||||
|
||||
|
||||
def notifyMsg(msg, tf='15m', tag='btc'):
|
||||
trigger_time = toUnixTimeSecond(tf)
|
||||
return mw.notifyMessage(msg, '量化交易/' + tag, trigger_time)
|
||||
|
||||
|
||||
def makeTableName(input_type="btc", input_tf="1m"):
|
||||
table_name = "ct_%s_%s" % (input_type, input_tf,)
|
||||
return table_name
|
||||
|
||||
|
||||
def writeLog(log_str):
|
||||
if __name__ == "__main__":
|
||||
print(log_str)
|
||||
|
||||
log_file = getServerDir() + '/logs/strategy.log'
|
||||
mw.writeFileLog(log_str, log_file)
|
||||
return True
|
||||
|
||||
|
||||
def writeLogEx(log_str, tag='btc'):
|
||||
tag = tag.replace('/', '_')
|
||||
# 各种币的详细API日志
|
||||
if __name__ == "__main__":
|
||||
print(log_str)
|
||||
|
||||
log_file = getServerDir() + '/logs/strategy_' + tag + '.log'
|
||||
mw.writeFileLog(log_str, log_file)
|
||||
return True
|
||||
|
||||
|
||||
def writeLogErrorEx(log_str, tag='btc'):
|
||||
tag = tag.replace('/', '_')
|
||||
# 各种币的详细API日志
|
||||
if __name__ == "__main__":
|
||||
print(log_str)
|
||||
|
||||
log_file = getServerDir() + '/logs/strategy_' + tag + '.err.log'
|
||||
mw.writeFileLog(log_str, log_file)
|
||||
return True
|
||||
|
||||
|
||||
def pMysqlDb():
|
||||
# pymysql
|
||||
db = mw.getMyORM()
|
||||
data = getConfigData()
|
||||
db_data = data['db']
|
||||
|
||||
# print(db_data)
|
||||
db.setHost(db_data['db_host'])
|
||||
db.setPort(db_data['db_port'])
|
||||
db.setUser(db_data['db_user'])
|
||||
db.setPwd(db_data['db_pass'])
|
||||
db.setDbName(db_data['db_name'])
|
||||
return db
|
||||
|
||||
|
||||
def getOnlineData(symbol, input_tf="15m", limit=200):
|
||||
bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit)
|
||||
df = pd.DataFrame(bars[:], columns=['timestamp',
|
||||
'open', 'high', 'low', 'close', 'volume'])
|
||||
df['dt'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||
return df
|
||||
|
||||
|
||||
def toDataFrame(data):
|
||||
# print(data)
|
||||
data = sorted(data, key=lambda x: x["addtime"], reverse=False)
|
||||
|
||||
dfield = ['addtime', 'open', 'high', 'low', 'close']
|
||||
v = {}
|
||||
for dx in dfield:
|
||||
v[dx] = []
|
||||
|
||||
for x in data:
|
||||
for i in range(len(x)):
|
||||
field = dfield[i]
|
||||
# print(i, field)
|
||||
v[field].append(x[field])
|
||||
# pprint(data)
|
||||
frame = pd.DataFrame(v)
|
||||
# frame = frame.sort_values(by=['addtime'])
|
||||
|
||||
frame['dt'] = pd.to_datetime(frame['addtime'], unit='s')
|
||||
frame.set_index('dt', inplace=True)
|
||||
frame.index = frame.index.tz_localize('UTC').tz_convert('Asia/Shanghai')
|
||||
return frame
|
||||
|
||||
|
||||
def getDataFromDb(tf="1m", tag='btc', limit=1000):
|
||||
tn = makeTableName(tag, tf)
|
||||
sql = 'select addtime,open,high,low,close from ' + \
|
||||
tn + ' order by addtime desc limit ' + str(limit)
|
||||
|
||||
pdb = pMysqlDb()
|
||||
fdata = pdb.query(sql)
|
||||
# print(fdata)
|
||||
return fdata
|
||||
|
||||
|
||||
def getDataFromDb_DF(tf="5m", tag='btc', limit=1000):
|
||||
data = getDataFromDb(tf, tag, limit)
|
||||
rdata = toDataFrame(data)
|
||||
return rdata
|
||||
|
||||
|
||||
# 消息模板类
|
||||
class MsgTpl():
|
||||
|
||||
__name = ''
|
||||
__strategy_name = ''
|
||||
__time_frame = ''
|
||||
__content = ''
|
||||
__open_time = ''
|
||||
__strategy_dt = ''
|
||||
|
||||
__stop_loss_price = ''
|
||||
__closing_price = ''
|
||||
__open_price = ''
|
||||
|
||||
__msg = ''
|
||||
|
||||
def setName(self, name):
|
||||
self.__name = name
|
||||
|
||||
def setStrategyName(self, name):
|
||||
self.__strategy_name = name
|
||||
|
||||
def setTimeFrame(self, tf):
|
||||
self.__time_frame = tf
|
||||
|
||||
def setContent(self, content):
|
||||
self.__content = content
|
||||
|
||||
def setOpenTime(self, time):
|
||||
self.__open_time = toDateFromInt(time)
|
||||
|
||||
def setStrategicDt(self, stype='buy'):
|
||||
if stype == 'buy':
|
||||
self.__strategy_dt = '做多'
|
||||
else:
|
||||
self.__strategy_dt = '做空'
|
||||
|
||||
def setStopLossPrice(self, price):
|
||||
self.__stop_loss_price = price
|
||||
|
||||
def setClosingPrice(self, price):
|
||||
self.__closing_price = price
|
||||
|
||||
def setOpenPrice(self, price):
|
||||
self.__open_price = price
|
||||
|
||||
def setMsg(self, msg):
|
||||
self.__msg = msg
|
||||
|
||||
def toText(self):
|
||||
msg = ''
|
||||
msg += '名称:' + self.__name + "\n"
|
||||
|
||||
if self.__strategy_name != '':
|
||||
msg += '策略名称:' + self.__strategy_name + "\n"
|
||||
|
||||
if self.__time_frame != '':
|
||||
msg += '时间周期:' + self.__time_frame + "\n"
|
||||
|
||||
if self.__content != '':
|
||||
msg += '策略描述:' + self.__content + "\n"
|
||||
|
||||
if self.__open_time != '':
|
||||
msg += '开盘时间:' + self.__open_time + "\n"
|
||||
|
||||
if self.__strategy_dt != '':
|
||||
msg += '开仓方向:' + self.__strategy_dt + "\n"
|
||||
|
||||
if self.__open_price != '':
|
||||
msg += '开仓价:' + str(self.__open_price) + "\n"
|
||||
|
||||
if self.__stop_loss_price != '':
|
||||
msg += '止损价:' + str(self.__stop_loss_price) + "\n"
|
||||
|
||||
if self.__closing_price != '':
|
||||
msg += '止盈价:' + str(self.__closing_price) + "\n"
|
||||
|
||||
msg += '发送时间:' + mw.getDateFromNow() + "\n"
|
||||
|
||||
if self.__msg != '':
|
||||
msg += __msg
|
||||
return msg
|
||||
57
plugins/cryptocurrency_trade/ccxt/strategy/func_test.py
Normal file
57
plugins/cryptocurrency_trade/ccxt/strategy/func_test.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# import ccxt
|
||||
# import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
# import pandas as pd
|
||||
from pprint import pprint
|
||||
from decimal import Decimal
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/func_test.py run
|
||||
|
||||
# common.notifyMsg("任务开始")
|
||||
|
||||
|
||||
def toUnixTimeSecond(tf="1m"):
|
||||
if tf.find("m") > -1:
|
||||
v = int(tf.replace("m", ''))
|
||||
return v * 60
|
||||
|
||||
if tf.find("h") > -1:
|
||||
v = int(tf.replace("h", ''))
|
||||
return v * 3600
|
||||
|
||||
if tf.find("d") > -1:
|
||||
v = int(tf.replace("d", ''))
|
||||
return v * 86400
|
||||
return 0
|
||||
|
||||
|
||||
def multiply(a1, a2):
|
||||
v = Decimal(str(a1)) * Decimal(str(a2))
|
||||
return v
|
||||
|
||||
# print(toUnixTimeSecond("1d"))
|
||||
|
||||
# f = 19911.2
|
||||
# s = common.calc_ClosingPrice(f, 19890.2, 'buy')
|
||||
# print(s)
|
||||
|
||||
# print(sys.version_info)
|
||||
# os.environ['TZ'] = 'Europe/London'
|
||||
# time.tzset()
|
||||
# t = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
||||
# print(t)
|
||||
|
||||
# v = multiply(26236.8, 0.003)
|
||||
# print(float(v))
|
||||
# print(v)
|
||||
|
||||
print(float('0.00255234') + float('-0.00000255234'))
|
||||
v1 = common.addition('0.00255234', '-0.00000255234')
|
||||
print(v1)
|
||||
440
plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py
Normal file
440
plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py
Normal file
@@ -0,0 +1,440 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py run
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py long
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import pandas as pd
|
||||
# import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
exchange = ccxt.gate({
|
||||
"apiKey": '756e1ff80526cb0ac9620c75680fc506',
|
||||
"secret": '95ac89362056bcf12ce37aabff6eb7ec78185483105f39b4a35e8dc8db8b4d3c',
|
||||
})
|
||||
|
||||
exchange.load_markets()
|
||||
|
||||
# 默认开仓数据
|
||||
# default_open_num = 10
|
||||
|
||||
default_open = {
|
||||
"BTC/USDT": 0.01,
|
||||
'ETH/USDT': 30,
|
||||
'DOT/USDT': 30,
|
||||
'CEL/USDT': 30,
|
||||
}
|
||||
|
||||
default_sell = {
|
||||
"BTC/USDT": 0.01,
|
||||
'ETH/USDT': 0.01,
|
||||
'DOT/USDT': 5,
|
||||
'CEL/USDT': 85,
|
||||
}
|
||||
|
||||
|
||||
# 做多开仓
|
||||
def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
|
||||
common.writeLogEx('------做多----------------------------------', symbol)
|
||||
|
||||
default_open_num = default_open[symbol]
|
||||
# 做多开仓 | 市价
|
||||
data = exchange.createMarketBuyOrder(
|
||||
symbol, default_open_num, {"tdMode": "cross"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
# 做多-止损价大于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 - 0.003))
|
||||
|
||||
# property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee'])
|
||||
property_val = common.addition(
|
||||
order_data['info']['accFillSz'], order_data['info']['fee'])
|
||||
property_val = float(property_val)
|
||||
|
||||
# 可平仓的数量
|
||||
property_val = common.roundValCeil(
|
||||
property_val, order_data['info']['accFillSz'])
|
||||
|
||||
common.writeLogEx('可平仓资产:' + str(property_val), symbol)
|
||||
|
||||
# 止盈价
|
||||
diff = float(open_price) - float(stop_loss_price)
|
||||
closing_price_c = float(open_price) + (diff * 2)
|
||||
closing_price = float(open_price) * float((1 + profit))
|
||||
# 选择盈利多的
|
||||
if closing_price_c > closing_price:
|
||||
closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, stop_loss_price)
|
||||
# stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价/止盈价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
# 止损条件单
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
# 止赢条件单
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止赢数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做多 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做多开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
common.writeLogEx('------做空----------------------------------', symbol)
|
||||
|
||||
# 计算借币卖币多多少,以USDT为基准
|
||||
# sell_num = float(default_open_num) / float(stop_loss_price)
|
||||
# sell_num = round(sell_num, 8)
|
||||
|
||||
sell_num = default_sell[symbol]
|
||||
# 做空开仓 | 市价
|
||||
data = exchange.createMarketSellOrder(
|
||||
symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
common.writeLogEx('可平仓资产:' + str(sell_num), symbol)
|
||||
|
||||
# 做空-止损价小于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 + 0.003))
|
||||
|
||||
# 止盈价
|
||||
diff = float(stop_loss_price) - float(open_price)
|
||||
closing_price_c = float(open_price) - (diff * 2)
|
||||
closing_price = float(open_price) * float((1 - profit))
|
||||
# 选择盈利多的
|
||||
if closing_price_c < closing_price:
|
||||
closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
sl_amount = common.multiply(stop_loss_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
sl_amount = common.addition(sl_amount, 0.1)
|
||||
common.writeLogEx('止损总价值:' + str(sl_amount), symbol)
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
sl_amount), stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', sl_amount, stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
# 设置 -止盈价
|
||||
tp_amount = common.multiply(closing_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
tp_amount = common.addition(tp_amount, 0.1)
|
||||
common.writeLogEx('止盈总价值:' + str(tp_amount), symbol)
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
tp_amount), closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', tp_amount, closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止盈价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做空 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做空开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onSellOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def getOnlineData(symbol, input_tf="15m", limit=230):
|
||||
bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit)
|
||||
df = pd.DataFrame(bars[:], columns=['timestamp',
|
||||
'open', 'high', 'low', 'close', 'volume'])
|
||||
df['dt'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||
df.set_index('dt', inplace=True)
|
||||
df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai')
|
||||
return df
|
||||
|
||||
|
||||
def isKdj(last, last_pre):
|
||||
# 判断是否是金叉
|
||||
if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isDeadFork(last, last_pre):
|
||||
# 判断是否是死叉
|
||||
if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def getMACD(df, lenght=-60):
|
||||
if len(df['close'].values) < 100:
|
||||
return False, None
|
||||
|
||||
close_p = df['close'].values
|
||||
df['dif'], df['dea'], df['macd'] = talib.MACD(close_p,
|
||||
fastperiod=12,
|
||||
slowperiod=26,
|
||||
signalperiod=9)
|
||||
return df[lenght:]
|
||||
|
||||
|
||||
def getEMA(df):
|
||||
close_p = df['close'].values
|
||||
df['ema'] = talib.EMA(np.array(close_p), timeperiod=200)
|
||||
return df
|
||||
|
||||
|
||||
def doneMacd(data, tag, timeframe):
|
||||
data = getEMA(data)
|
||||
|
||||
ma_data = getMACD(data)
|
||||
# alen = len(data)
|
||||
# print(ma_data)
|
||||
|
||||
t_data = ma_data.tail(3)
|
||||
print(t_data)
|
||||
|
||||
last_pre = t_data.iloc[0]
|
||||
last = t_data.iloc[1]
|
||||
|
||||
print(last)
|
||||
# print(last.index)
|
||||
|
||||
# print('close:', last['close'], 'ema:', last['ema'])
|
||||
|
||||
obj = common.MsgTpl()
|
||||
symbol = tag.upper() + '/USDT'
|
||||
obj.setName(symbol)
|
||||
obj.setStrategyName("MACD检查")
|
||||
obj.setTimeFrame(timeframe)
|
||||
obj.setOpenTime(last['timestamp'] / 1000)
|
||||
|
||||
now_data = t_data.iloc[2]
|
||||
# print('now_data:', now_data)
|
||||
|
||||
# msg = obj.toText()
|
||||
# print(msg)
|
||||
|
||||
if isKdj(last, last_pre) and last['close'] > last['ema']:
|
||||
# if isKdj(last, last_pre):
|
||||
|
||||
# closing_price = common.calc_ClosingPrice(
|
||||
# now_data['close'], last_pre['low'], 'buy')
|
||||
# # print('closing_price:', closing_price)
|
||||
|
||||
# 做多止损点
|
||||
stop_loss_price = last_pre['low']
|
||||
buy_status, open_price, closing_price = onBuyOrder(
|
||||
symbol, stop_loss_price, 0.005, timeframe)
|
||||
|
||||
if buy_status:
|
||||
obj.setStrategicDt('buy')
|
||||
# 做多止损点
|
||||
obj.setStopLossPrice(str(stop_loss_price))
|
||||
obj.setOpenPrice(str(open_price))
|
||||
obj.setClosingPrice(str(closing_price))
|
||||
obj.setContent("检查到金叉状态")
|
||||
msg = obj.toText()
|
||||
print(msg)
|
||||
common.notifyMsg(msg, timeframe, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
if isDeadFork(last, last_pre) and last['close'] < last['ema']:
|
||||
# if isDeadFork(last, last_pre):
|
||||
# closing_price = common.calc_ClosingPrice(
|
||||
# now_data['close'], last_pre['high'], 'sell')
|
||||
|
||||
# 做空止损点
|
||||
stop_loss_price = last_pre['high']
|
||||
sell_status, open_price, closing_price = onSellOrder(
|
||||
symbol, stop_loss_price, 0.005, timeframe)
|
||||
if sell_status:
|
||||
obj.setStopLossPrice(str(stop_loss_price))
|
||||
obj.setOpenPrice(str(open_price))
|
||||
obj.setClosingPrice(str(closing_price))
|
||||
obj.setStrategicDt('sell')
|
||||
obj.setContent("检查到死叉状态")
|
||||
msg = obj.toText()
|
||||
print(msg)
|
||||
common.notifyMsg(msg, timeframe, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
|
||||
def mainProcess(tag, timeframe='15m'):
|
||||
symbol = tag.upper() + '/USDT'
|
||||
data = getOnlineData(symbol, timeframe)
|
||||
|
||||
doneMacd(data, tag, timeframe)
|
||||
|
||||
|
||||
def foreachList():
|
||||
tag_list = ['btc']
|
||||
|
||||
for tag in tag_list:
|
||||
mainProcess(tag, '15m')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def longRun():
|
||||
while True:
|
||||
foreachList()
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def debug():
|
||||
while True:
|
||||
mainProcess('xrp', '1m')
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'long':
|
||||
longRun()
|
||||
elif func == 'run':
|
||||
debug()
|
||||
else:
|
||||
print('error')
|
||||
138
plugins/cryptocurrency_trade/ccxt/strategy/hammer_robot.py
Normal file
138
plugins/cryptocurrency_trade/ccxt/strategy/hammer_robot.py
Normal file
@@ -0,0 +1,138 @@
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import pandas as pd
|
||||
import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/hammer_robot.py run
|
||||
|
||||
|
||||
pd.set_option('display.max_rows', None)
|
||||
|
||||
exchange = common.initEx()
|
||||
|
||||
exchange.load_markets()
|
||||
|
||||
entry_rsi = 30
|
||||
exit_rsi = 40
|
||||
|
||||
|
||||
symbol = 'XRP/USDT'
|
||||
timeframe = '15m'
|
||||
|
||||
tf_mult = exchange.parse_timeframe(timeframe) * 1000
|
||||
|
||||
|
||||
def indicators(data):
|
||||
|
||||
data['rsi'] = data.ta.rsi(length=10)
|
||||
data['ema'] = data.ta.ema(length=200)
|
||||
|
||||
# close_p = data['close'].values
|
||||
# data['rsi'] = talib.RSI(close_p, timeperiod=10)
|
||||
# data['ema'] = talib.EMA(close_p, timeperiod=200)
|
||||
return data
|
||||
|
||||
|
||||
def check_buy_sell_signals(df):
|
||||
last_row_index = len(df.index) - 1
|
||||
lastest_rsi = round(df['rsi'].iloc[-1], 2)
|
||||
lastest_price = round(df['close'].iloc[-1], 5)
|
||||
lastest_ema = round(df['ema'].iloc[-1], 5)
|
||||
lastest_ts = df['timestamp'].iloc[-1]
|
||||
|
||||
msg = "lastest_rsi:" + str(lastest_rsi) + " < entry_rsi:" + str(entry_rsi)
|
||||
msg += ",lastest_price:" + \
|
||||
str(lastest_price) + " > lastest_ema:" + str(lastest_ema)
|
||||
print(msg)
|
||||
|
||||
long_cond = (lastest_rsi < entry_rsi) and (lastest_price > lastest_ema)
|
||||
if long_cond:
|
||||
print("买入")
|
||||
order = exchange.create_market_buy_order(symbol, 1)
|
||||
|
||||
closed_orders = exchange.fetchClosedOrders(symbol, limit=2)
|
||||
if len(closed_orders) > 0:
|
||||
print("closed_orders:", closed_orders)
|
||||
most_recent_closed_order = closed_orders[-1]
|
||||
diff = lastest_ts - most_recent_closed_order['timestamp']
|
||||
last_buy_signal_cnt = int(diff / tf_mult)
|
||||
|
||||
exit_cond = (lastest_rsi > exit_rsi) and (last_buy_signal_cnt > 10)
|
||||
if exit_cond:
|
||||
print("卖出")
|
||||
order = exchange.create_market_sell_order(symbol, 1)
|
||||
return
|
||||
|
||||
|
||||
def get_hammer(df, lenght):
|
||||
# 影线要大于body的多少倍
|
||||
factor = 2
|
||||
hl_range = df['high'] - df['low']
|
||||
|
||||
body_hi = df.apply(lambda x: max(x['close'], x['open']), axis=1)
|
||||
body_lo = df.apply(lambda x: min(x['close'], x['open']), axis=1)
|
||||
body = body_hi - body_lo
|
||||
|
||||
body_avg = ta.ema(body, lenght=lenght)
|
||||
small_body = body < body_avg
|
||||
|
||||
# 上下影线站body的百分比
|
||||
shadow_percent = 10
|
||||
|
||||
# 上影线
|
||||
up_shadow = df['high'] - body_hi
|
||||
dn_shadow = body_lo - df['low']
|
||||
has_up_shadow = up_shadow > shadow_percent / 100 * body
|
||||
has_dn_shadow = dn_shadow > shadow_percent / 100 * body
|
||||
|
||||
downtrend = df['close'] < ta.ema(df['close'], 50)
|
||||
bullish_hammer = downtrend & small_body & (body > 0) & (
|
||||
dn_shadow >= factor * body) & (has_up_shadow == False)
|
||||
return bullish_hammer
|
||||
|
||||
|
||||
def runBot():
|
||||
bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=200)
|
||||
df = pd.DataFrame(bars[:], columns=['timestamp',
|
||||
'open', 'high', 'low', 'close', 'volume'])
|
||||
|
||||
# format='%Y-%m-%d %H:%M:%S',
|
||||
df['dt'] = pd.to_datetime(
|
||||
df['timestamp'], unit="ms")
|
||||
|
||||
df['hammer'] = get_hammer(df, 10)
|
||||
|
||||
lastest_hammer = df.iloc[-1, -1]
|
||||
lastest_price = df.iloc[-1, 0]
|
||||
|
||||
print("lastest_price:" + str(lastest_price))
|
||||
print("lastest_hammer:" + str(lastest_hammer))
|
||||
print(df.tail())
|
||||
|
||||
if lastest_hammer:
|
||||
print("购买,做多")
|
||||
notifyMsg("购买,做多")
|
||||
|
||||
|
||||
def longRunBot():
|
||||
common.notifyMsg("任务开始")
|
||||
while True:
|
||||
runBot()
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'run':
|
||||
longRunBot()
|
||||
else:
|
||||
print('error')
|
||||
33
plugins/cryptocurrency_trade/ccxt/strategy/info.json
Normal file
33
plugins/cryptocurrency_trade/ccxt/strategy/info.json
Normal file
@@ -0,0 +1,33 @@
|
||||
[
|
||||
{
|
||||
"id":"01",
|
||||
"name": "MACD-KDJ策略检测|1h",
|
||||
"file": "macd_kdj.py"
|
||||
},
|
||||
{
|
||||
"id":"02",
|
||||
"name": "通知测试|60s",
|
||||
"file": "notify_demo.py"
|
||||
},
|
||||
{
|
||||
"id":"03",
|
||||
"name": "okex|macd交易策略|15m",
|
||||
"file": "online_macd_trade.py"
|
||||
},
|
||||
{
|
||||
"id":"04",
|
||||
"name": "okex|动量交易策略|5m",
|
||||
"file": "momentun_trade.py"
|
||||
},
|
||||
{
|
||||
"id":"05",
|
||||
"name": "gate.io|动量交易策略|5m",
|
||||
"file": "gate_100.py"
|
||||
},
|
||||
{
|
||||
"id":"06",
|
||||
"name": "仅提醒|Vega交易策略|5m",
|
||||
"file": "vega_trade.py"
|
||||
}
|
||||
|
||||
]
|
||||
121
plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py
Normal file
121
plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py run
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py long
|
||||
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
import pandas as pd
|
||||
import time
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pprint import pprint
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
|
||||
pd.set_option('display.max_rows', None)
|
||||
|
||||
# import warnings
|
||||
# warnings.filterwarnings('error')
|
||||
|
||||
|
||||
def getMACD(df, lenght=-60):
|
||||
if len(df['close'].values) < 100:
|
||||
return False, None
|
||||
|
||||
close_p = df['close'].values
|
||||
df['dif'], df['dea'], df['macd'] = talib.MACD(close_p,
|
||||
fastperiod=12,
|
||||
slowperiod=26,
|
||||
signalperiod=9)
|
||||
return True, df[lenght:]
|
||||
|
||||
|
||||
def isKdj(last, last_pre):
|
||||
# 判断是否是金叉
|
||||
if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isDeadFork(last, last_pre):
|
||||
# 判断是否是死叉
|
||||
if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def checkData():
|
||||
tag = 'btc'
|
||||
tf_frame = '1h'
|
||||
|
||||
data = common.getDataFromDb_DF(tf_frame, tag, 300)
|
||||
|
||||
# print(data)
|
||||
b, r = getMACD(data)
|
||||
if b:
|
||||
# rlen = len(r)
|
||||
# r = r.copy()
|
||||
# r['dt'] = pd.to_datetime(
|
||||
# r['addtime'], unit='s')
|
||||
|
||||
# r['dt'] = r['dt'].dt.tz_convert('Asia/Shanghai')
|
||||
# print(r)
|
||||
rlen = len(r)
|
||||
|
||||
t_data = r.tail(2)
|
||||
# print(r.tail(2))
|
||||
|
||||
last_pre = t_data.iloc[0]
|
||||
last = t_data.iloc[1]
|
||||
|
||||
# print(last_pre)
|
||||
# print(last)
|
||||
# print(last_pre['addtime'])
|
||||
# print(last_pre['open'])
|
||||
# print(last_pre['high'])
|
||||
# print(last_pre['low'])
|
||||
# print(last_pre['close'])
|
||||
|
||||
# print(last['addtime'])
|
||||
# print(last['open'])
|
||||
# print(last['high'])
|
||||
# print(last['low'])
|
||||
# print(last['close'])
|
||||
|
||||
now = mw.getDateFromNow()
|
||||
if isKdj(last, last_pre):
|
||||
msg = now + "|{}|{}|检查到金叉状态!".format(tag, tf_frame)
|
||||
common.notifyMsg(msg, tf_frame, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
if isDeadFork(last, last_pre):
|
||||
msg = now + "|{}|{}|检查到死叉状态!".format(tag, tf_frame)
|
||||
common.notifyMsg(msg, tf_frame, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
|
||||
def run():
|
||||
checkData()
|
||||
|
||||
|
||||
def longRun():
|
||||
while True:
|
||||
checkData()
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'run':
|
||||
run()
|
||||
elif func == 'long':
|
||||
longRun()
|
||||
else:
|
||||
print('error')
|
||||
505
plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py
Normal file
505
plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py
Normal file
@@ -0,0 +1,505 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py run
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py long
|
||||
|
||||
|
||||
# 动量策略交易
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import pandas as pd
|
||||
# import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
exchange = common.initEx()
|
||||
exchange.load_markets()
|
||||
|
||||
# 默认开仓数据
|
||||
# default_open_num = 70
|
||||
# default_sell_num = 0.003
|
||||
|
||||
default_open = {
|
||||
'BTC/USDT': 30,
|
||||
'XRP/USDT': 30,
|
||||
}
|
||||
|
||||
default_sell = {
|
||||
'BTC/USDT': 0.001,
|
||||
'XRP/USDT': 100,
|
||||
}
|
||||
|
||||
|
||||
# 做多开仓
|
||||
def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
|
||||
common.writeLogEx('------做多----------------------------------', symbol)
|
||||
|
||||
default_open_num = default_open[symbol]
|
||||
# 做多开仓 | 市价
|
||||
data = exchange.createMarketBuyOrder(
|
||||
symbol, default_open_num, {"tdMode": "cross"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
# 修正小数点位数
|
||||
open_price = common.roundVal(open_price, stop_loss_price)
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
|
||||
# 做多-止损价大于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 - 0.003))
|
||||
|
||||
# property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee'])
|
||||
|
||||
property_val = common.addition(order_data['info'][
|
||||
'accFillSz'], order_data['info']['fee'])
|
||||
property_val = float(property_val)
|
||||
# 可平仓的数量
|
||||
# property_val = common.roundValCeil(
|
||||
# property_val, order_data['info']['accFillSz'])
|
||||
|
||||
common.writeLogEx('可平仓资产:' + str(property_val), symbol)
|
||||
|
||||
# 止盈价
|
||||
diff = float(open_price) - float(stop_loss_price)
|
||||
closing_price = float(open_price) + (diff * 1.5)
|
||||
# closing_price = float(open_price) * float((1 + profit))
|
||||
# # 选择盈利多的
|
||||
# if closing_price_c > closing_price:
|
||||
# closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价/止盈价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
# 止损条件单
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
# 止赢条件单
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止盈数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做多 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做多开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
common.writeLogEx('------做空----------------------------------', symbol)
|
||||
|
||||
# 计算借币卖币多多少,以USDT为基准
|
||||
# sell_num = float(default_open_num) / float(stop_loss_price)
|
||||
# sell_num = round(sell_num, 8)
|
||||
# sell_num = default_sell_num
|
||||
sell_num = default_sell[symbol]
|
||||
|
||||
# 做空开仓 | 市价
|
||||
data = exchange.createMarketSellOrder(
|
||||
symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
# 修正
|
||||
open_price = common.roundVal(open_price, stop_loss_price)
|
||||
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
common.writeLogEx('可平仓资产:' + str(sell_num), symbol)
|
||||
|
||||
# 做空-止损价小于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 + 0.003))
|
||||
|
||||
# 止盈价
|
||||
diff = float(stop_loss_price) - float(open_price)
|
||||
closing_price = float(open_price) - (diff * 1.5)
|
||||
# closing_price = float(open_price) * float((1 - profit))
|
||||
# 选择盈利多的
|
||||
# if closing_price_c < closing_price:
|
||||
# closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
sl_amount = common.multiply(stop_loss_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
sl_amount = common.addition(sl_amount, 0.1)
|
||||
common.writeLogEx('止损总价值:' + str(sl_amount), symbol)
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
sl_amount), stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', float(sl_amount), stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
# 设置 -止盈价
|
||||
# tp_amount = closing_price * sell_num
|
||||
tp_amount = common.multiply(closing_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
tp_amount = common.addition(tp_amount, 0.1)
|
||||
common.writeLogEx('止盈总价值:' + str(tp_amount), symbol)
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
tp_amount), closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', float(tp_amount), closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止盈价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做空 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做空开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onSellOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def getOnlineData(symbol, input_tf="15m", limit=230):
|
||||
bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit)
|
||||
df = pd.DataFrame(bars[:], columns=['timestamp',
|
||||
'open', 'high', 'low', 'close', 'volume'])
|
||||
df['dt'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||
df.set_index('dt', inplace=True)
|
||||
df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai')
|
||||
return df
|
||||
|
||||
|
||||
def isKdj(last, last_pre):
|
||||
# 判断是否是金叉
|
||||
if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isDeadFork(last, last_pre):
|
||||
# 判断是否是死叉
|
||||
if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def getMACD(df, lenght=-60):
|
||||
if len(df['close'].values) < 100:
|
||||
return False, None
|
||||
|
||||
close_p = df['close'].values
|
||||
df['dif'], df['dea'], df['macd'] = talib.MACD(close_p,
|
||||
fastperiod=12,
|
||||
slowperiod=26,
|
||||
signalperiod=9)
|
||||
return df[lenght:]
|
||||
|
||||
|
||||
def getTarget(df):
|
||||
close = df['close'].values
|
||||
df['ema'] = talib.EMA(np.array(close), timeperiod=10)
|
||||
df['ema_200'] = talib.EMA(np.array(close), timeperiod=200)
|
||||
df['ma'] = talib.MA(close, timeperiod=10)
|
||||
df['rsi'] = talib.RSI(close, timeperiod=14)
|
||||
return df
|
||||
|
||||
|
||||
# 多头信号
|
||||
def isBuyCrondSignal(last):
|
||||
if last['ema'] > last['ma'] and (last['rsi'] > 50 and last['rsi'] < 70) and last['low'] >= last['ma'] and last['close'] > last['open']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isBuyFristSignal(data):
|
||||
data = data.sort_values(by=['timestamp'], ascending=False)
|
||||
# print(data)
|
||||
data_len = len(data)
|
||||
signal_num = 0
|
||||
|
||||
first_data = data.iloc[1]
|
||||
# print(1, first_data['close'], first_data['ema'])
|
||||
is_buy_signal = isBuyCrondSignal(first_data)
|
||||
|
||||
for x in range(2, data_len):
|
||||
tmp = data.iloc[x]
|
||||
# print(data.iloc[x])
|
||||
# print(x, tmp['close'], tmp['ema'])
|
||||
if isBuyCrondSignal(tmp):
|
||||
signal_num = + 1
|
||||
|
||||
# print('signal_num:', signal_num)
|
||||
|
||||
if (tmp['ema'] < tmp['ma']):
|
||||
break
|
||||
|
||||
if str(tmp['ema']) == 'nan':
|
||||
break
|
||||
|
||||
print("is_buy_signal:", is_buy_signal, 'signal_num:', signal_num)
|
||||
if is_buy_signal and signal_num == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# 空头信号
|
||||
def isSellCrondSignal(last):
|
||||
if last['ema'] < last['ma'] and (last['rsi'] > 30 and last['rsi'] < 50) and last['high'] <= last['ema'] and last['close'] < last['open']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isSellFristSignal(data):
|
||||
data = data.sort_values(by=['timestamp'], ascending=False)
|
||||
# print(data)
|
||||
data_len = len(data)
|
||||
signal_num = 0
|
||||
|
||||
first_data = data.iloc[1]
|
||||
# print(1, first_data['close'], first_data['ema'])
|
||||
is_sell_signal = isSellCrondSignal(first_data)
|
||||
|
||||
for x in range(2, data_len):
|
||||
tmp = data.iloc[x]
|
||||
# print(data.iloc[x])
|
||||
# print(x, tmp['close'], tmp['ema'])
|
||||
if isSellCrondSignal(tmp):
|
||||
signal_num = + 1
|
||||
|
||||
# print('signal_num:', signal_num)
|
||||
|
||||
if (tmp['ema'] < tmp['ma']):
|
||||
break
|
||||
|
||||
if str(tmp['ema']) == 'nan':
|
||||
break
|
||||
|
||||
print("is_sell_signal:", is_sell_signal, 'signal_num:', signal_num)
|
||||
if is_sell_signal and signal_num == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def monentunTrade(data, tag, timeframe):
|
||||
data = getTarget(data)
|
||||
# print(data)
|
||||
|
||||
key_data = data.tail(3)
|
||||
print(key_data)
|
||||
last_pre = key_data.iloc[0]
|
||||
last = key_data.iloc[1]
|
||||
|
||||
obj = common.MsgTpl()
|
||||
symbol = tag.upper() + '/USDT'
|
||||
obj.setName(symbol)
|
||||
obj.setStrategyName("动量交易策略")
|
||||
obj.setTimeFrame(timeframe)
|
||||
obj.setOpenTime(last['timestamp'] / 1000)
|
||||
|
||||
# 买入信号,并且收盘价要大于200 ema
|
||||
if isBuyFristSignal(data) and last['close'] > last['ema_200']:
|
||||
obj.setStrategicDt('buy')
|
||||
|
||||
# 做多止损点
|
||||
stop_loss_price = last['ma']
|
||||
stop_loss_price = common.roundVal(stop_loss_price, last['open'])
|
||||
buy_status, open_price, closing_price = onBuyOrder(
|
||||
symbol, stop_loss_price, 0.005, timeframe)
|
||||
|
||||
if buy_status:
|
||||
# 做多止损点
|
||||
obj.setStopLossPrice(str(stop_loss_price))
|
||||
obj.setOpenPrice(str(open_price))
|
||||
obj.setClosingPrice(str(closing_price))
|
||||
obj.setContent("动量交易策略做多!")
|
||||
msg = obj.toText()
|
||||
print(msg)
|
||||
common.notifyMsg(msg, timeframe, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
# 卖出信号,并且收盘价要小于200 ema
|
||||
# if isSellFristSignal(data) and last['close'] < last['ema_200']:
|
||||
# obj.setStrategicDt('sell')
|
||||
|
||||
# stop_loss_price = last['ma']
|
||||
# stop_loss_price = common.roundVal(stop_loss_price, last['open'])
|
||||
# sell_status, open_price, closing_price = onSellOrder(
|
||||
# symbol, stop_loss_price, 0.005, timeframe)
|
||||
# if sell_status:
|
||||
# obj.setStopLossPrice(str(stop_loss_price))
|
||||
# obj.setOpenPrice(str(open_price))
|
||||
# obj.setClosingPrice(str(closing_price))
|
||||
# obj.setStrategicDt('sell')
|
||||
# obj.setContent("动量交易策略作空!")
|
||||
# msg = obj.toText()
|
||||
# print(msg)
|
||||
# common.notifyMsg(msg, timeframe, tag)
|
||||
# common.writeLog(msg)
|
||||
|
||||
|
||||
def mainProcess(tag, timeframe='15m'):
|
||||
symbol = tag.upper() + '/USDT'
|
||||
data = getOnlineData(symbol, timeframe)
|
||||
|
||||
monentunTrade(data, tag, timeframe)
|
||||
|
||||
|
||||
def foreachList():
|
||||
tag_list = ['btc', 'xrp']
|
||||
for tag in tag_list:
|
||||
mainProcess(tag, '15m')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def longRun():
|
||||
while True:
|
||||
foreachList()
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def debug():
|
||||
while True:
|
||||
mainProcess('btc', '5m')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'long':
|
||||
longRun()
|
||||
elif func == 'run':
|
||||
debug()
|
||||
else:
|
||||
print('error')
|
||||
48
plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py
Normal file
48
plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py run
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py long
|
||||
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
import pandas as pd
|
||||
import time
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pprint import pprint
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
|
||||
def run():
|
||||
print('debug')
|
||||
|
||||
|
||||
def longRun():
|
||||
while True:
|
||||
|
||||
obj = common.MsgTpl()
|
||||
obj.setName("通知测试")
|
||||
obj.setStrategyName("无")
|
||||
obj.setTimeFrame("1m")
|
||||
obj.setContent("60s通知测试")
|
||||
msg = obj.toText()
|
||||
print(msg)
|
||||
common.notifyMsg(msg, '1m', 'debug')
|
||||
common.writeLog(msg)
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'run':
|
||||
run()
|
||||
if func == 'long':
|
||||
longRun()
|
||||
else:
|
||||
print('error')
|
||||
294
plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py
Normal file
294
plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py
Normal file
@@ -0,0 +1,294 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_buy_open
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_buy_close
|
||||
|
||||
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_sell_open
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_sell_cloe
|
||||
|
||||
# 获取仓位数据
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_get_trade
|
||||
|
||||
# API地址
|
||||
# https://www.gate.io/docs/developers/apiv4/zh_CN/#api
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import pandas as pd
|
||||
# import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
exchange = ccxt.gate({
|
||||
"apiKey": '756e1ff80526cb0ac9620c75680fc506',
|
||||
"secret": '95ac89362056bcf12ce37aabff6eb7ec78185483105f39b4a35e8dc8db8b4d3c',
|
||||
})
|
||||
|
||||
exchange.load_markets()
|
||||
|
||||
|
||||
def t_get_trade():
|
||||
data = exchange.fetchPositions()
|
||||
print(data)
|
||||
|
||||
|
||||
def btc_test():
|
||||
closing_price = 24599.9
|
||||
stop_loss_price = 24740.4
|
||||
|
||||
# ---------------------------
|
||||
stop_loss_args = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
print('---------止损价 执行 START ----------------------------------')
|
||||
|
||||
sl_amount = stop_loss_price * 0.001
|
||||
print(amount)
|
||||
data = exchange.create_order(
|
||||
'BTC/USDT', 'limit', 'buy', sl_amount, stop_loss_price, stop_loss_args)
|
||||
print(data)
|
||||
print('---------止损价 执行 END ----------------------------------')
|
||||
|
||||
print('---------止盈价 执行 START ----------------------------------')
|
||||
|
||||
closing_price_args = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
cp_amount = closing_price * 0.001
|
||||
print(amount)
|
||||
data = exchange.create_order(
|
||||
'BTC/USDT', 'limit', 'buy', cp_amount, closing_price, closing_price_args)
|
||||
print(data)
|
||||
print('---------止盈价 执行 END ----------------------------------')
|
||||
|
||||
|
||||
def testK_buy_open_sz():
|
||||
# 做多开仓
|
||||
|
||||
data = exchange.fetchTicker('BTC/USDT')
|
||||
print(data)
|
||||
# data = exchange.createMarketBuyOrder('DOT/USDT', 1, {"tdMode": "cross"})
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
|
||||
def testK_buy_open():
|
||||
# 做多开仓
|
||||
|
||||
# print(dir(exchange))
|
||||
|
||||
# exchange['options']['createMarketBuyOrderRequiresPrice'] = False
|
||||
data = exchange.fetch_ticker('BTC/USDT')
|
||||
print("now price:", data['ask'])
|
||||
|
||||
amount = 0.001
|
||||
price = data['ask']
|
||||
cost = amount * float(price)
|
||||
print('total price:', cost)
|
||||
print('amount price:', amount)
|
||||
# a_amount = round(amount / data['ask'], 5)
|
||||
# print('amount :', amount)
|
||||
# print('amount :', str(amount))
|
||||
|
||||
#
|
||||
data = exchange.createOrder(
|
||||
"BTC/USDT", type="limit", side="buy", amount=amount, price=price, params={'account': "cross_margin"})
|
||||
# data = exchange.createMarketBuyOrder('BTC/USDT', amount)
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
# 数据
|
||||
# {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []}
|
||||
|
||||
# 查询委托单
|
||||
# order_id = data['info']['ordId']
|
||||
# order_id = '554437054223302656'
|
||||
# data = exchange.fetchOrder(order_id, 'BTC/USDT')
|
||||
# print(data['info'])
|
||||
# {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]}
|
||||
|
||||
# open_price = data['info']['avgPx']
|
||||
# print("开仓平均价", open_price)
|
||||
|
||||
# # 止盈价
|
||||
# closing_price = float(open_price) * float((1 + 0.005))
|
||||
# closing_price = common.roundVal(closing_price, open_price)
|
||||
# print("止盈价", closing_price)
|
||||
|
||||
# # 止损价
|
||||
# stop_loss_price = float(open_price) * float((1 - 0.01))
|
||||
# stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
# print("止损价", stop_loss_price)
|
||||
|
||||
# property_val = float(data['info']['accFillSz']) + \
|
||||
# float(data['info']['fee'])
|
||||
|
||||
# # 相同位数
|
||||
# property_val = common.roundValCeil(property_val, data['info']['accFillSz'])
|
||||
# print("可平仓资产", property_val)
|
||||
|
||||
# closed_orders = exchange.fetchClosedOrders('DOT/USDT', limit=2)
|
||||
# print('closed_orders', closed_orders)
|
||||
|
||||
# print(exchange.fetchBalance())
|
||||
|
||||
# 可以用,限价单
|
||||
# time.sleep(1)
|
||||
# data = exchange.createLimitSellOrder(
|
||||
# 'DOT/USDT', property_val, closing_price, {"tdMode": "cross", 'ccy': 'USDT', "reduceOnly": True})
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
# 止损价
|
||||
# 止盈价
|
||||
|
||||
# exchange_params = {
|
||||
# 'ccy': "USDT",
|
||||
# 'reduceOnly': True,
|
||||
# 'tdMode': "cross",
|
||||
# 'tpOrdPx': "-1",
|
||||
# 'tpTriggerPx': closing_price,
|
||||
# 'slOrdPx': "-1",
|
||||
# 'slTriggerPx': stop_loss_price,
|
||||
# }
|
||||
# print('---------止损价 执行----------------------------------')
|
||||
|
||||
# data = exchange.create_order(
|
||||
# 'BTC/USDT', 'limit', 'sell', property_val, closing_price, exchange_params)
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
# print('---------止盈价 执行----------------------------------')
|
||||
|
||||
# exchange_params = {
|
||||
# 'stopPrice': closing_price,
|
||||
# 'type': 'stopLimit',
|
||||
# }
|
||||
# data = exchange.create_order(
|
||||
# 'DOT/USDT', 'limit', 'sell', property_val, closing_price, exchange_params)
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
|
||||
def testK_buy_close():
|
||||
# 平多
|
||||
data = exchange.create_limit_buy_order(
|
||||
'DOT/USDT', 1, 5, {"tdMode": "cross"})
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
# closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
# closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
|
||||
def testK_sell_open():
|
||||
# 做空开仓
|
||||
|
||||
# data = exchange.createMarketSellOrder(
|
||||
# 'DOT/USDT', 1, {"tdMode": "cross", 'ccy': "USDT", })
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
# 数据
|
||||
# {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []}
|
||||
|
||||
# # 查询委托单
|
||||
# order_id = data['info']['ordId']
|
||||
order_id = '554516809819832320'
|
||||
data = exchange.fetchOrder(order_id, 'DOT/USDT')
|
||||
print(data['info'])
|
||||
# {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]}
|
||||
|
||||
open_price = data['info']['avgPx']
|
||||
|
||||
print("开仓平均价", open_price)
|
||||
|
||||
# 止盈价
|
||||
closing_price = float(open_price) * float((1 - 0.005))
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
print("止盈价", closing_price)
|
||||
|
||||
# 止损价
|
||||
stop_loss_price = float(open_price) * float((1 + 0.01))
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
print("止损价", stop_loss_price)
|
||||
|
||||
property_val = float(data['info']['accFillSz'])
|
||||
# 相同位数
|
||||
print("可平仓资产", property_val)
|
||||
|
||||
# 止损价
|
||||
# 止盈价
|
||||
exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
print('---------止损价 执行----------------------------------')
|
||||
|
||||
data = exchange.create_order(
|
||||
'DOT/USDT', 'limit', 'buy', property_val, closing_price, exchange_params)
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
|
||||
def testK_sell_close():
|
||||
# 平多
|
||||
data = exchange.create_limit_buy_order(
|
||||
'DOT/USDT', 1, 5, {"tdMode": "cross"})
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
# closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
# closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'long':
|
||||
longRun()
|
||||
elif func == 'run':
|
||||
debug()
|
||||
elif func == 'test':
|
||||
testKdan()
|
||||
elif func == 't_get_trade':
|
||||
t_get_trade()
|
||||
elif func == 't_buy_open':
|
||||
testK_buy_open()
|
||||
elif func == 't_buy_close':
|
||||
testK_buy_close()
|
||||
elif func == 't_sell_open':
|
||||
testK_sell_open()
|
||||
elif func == 't_sell_cloe':
|
||||
testK_sell_close()
|
||||
else:
|
||||
print('error')
|
||||
437
plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py
Normal file
437
plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py
Normal file
@@ -0,0 +1,437 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py run
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py long
|
||||
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import pandas as pd
|
||||
# import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
exchange = common.initEx()
|
||||
exchange.load_markets()
|
||||
|
||||
# 默认开仓数据
|
||||
# default_open_num = 10
|
||||
|
||||
default_open = {
|
||||
'ETH/USDT': 30,
|
||||
'DOT/USDT': 30,
|
||||
'CEL/USDT': 30,
|
||||
}
|
||||
|
||||
default_sell = {
|
||||
'ETH/USDT': 0.02,
|
||||
'DOT/USDT': 5,
|
||||
'CEL/USDT': 85,
|
||||
}
|
||||
|
||||
|
||||
# 做多开仓
|
||||
def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
|
||||
common.writeLogEx('------做多----------------------------------', symbol)
|
||||
|
||||
default_open_num = default_open[symbol]
|
||||
# 做多开仓 | 市价
|
||||
data = exchange.createMarketBuyOrder(
|
||||
symbol, default_open_num, {"tdMode": "cross"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
# 做多-止损价大于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 - 0.003))
|
||||
|
||||
# property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee'])
|
||||
property_val = common.addition(
|
||||
order_data['info']['accFillSz'], order_data['info']['fee'])
|
||||
property_val = float(property_val)
|
||||
|
||||
# 可平仓的数量
|
||||
property_val = common.roundValCeil(
|
||||
property_val, order_data['info']['accFillSz'])
|
||||
|
||||
common.writeLogEx('可平仓资产:' + str(property_val), symbol)
|
||||
|
||||
# 止盈价
|
||||
diff = float(open_price) - float(stop_loss_price)
|
||||
closing_price_c = float(open_price) + (diff * 2)
|
||||
closing_price = float(open_price) * float((1 + profit))
|
||||
# 选择盈利多的
|
||||
if closing_price_c > closing_price:
|
||||
closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, stop_loss_price)
|
||||
# stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价/止盈价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
# 止损条件单
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
# 止赢条件单
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止赢数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做多 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做多开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
common.writeLogEx('------做空----------------------------------', symbol)
|
||||
|
||||
# 计算借币卖币多多少,以USDT为基准
|
||||
# sell_num = float(default_open_num) / float(stop_loss_price)
|
||||
# sell_num = round(sell_num, 8)
|
||||
|
||||
sell_num = default_sell[symbol]
|
||||
# 做空开仓 | 市价
|
||||
data = exchange.createMarketSellOrder(
|
||||
symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
common.writeLogEx('可平仓资产:' + str(sell_num), symbol)
|
||||
|
||||
# 做空-止损价小于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 + 0.003))
|
||||
|
||||
# 止盈价
|
||||
diff = float(stop_loss_price) - float(open_price)
|
||||
closing_price_c = float(open_price) - (diff * 2)
|
||||
closing_price = float(open_price) * float((1 - profit))
|
||||
# 选择盈利多的
|
||||
if closing_price_c < closing_price:
|
||||
closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
sl_amount = common.multiply(stop_loss_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
sl_amount = common.addition(sl_amount, 0.1)
|
||||
common.writeLogEx('止损总价值:' + str(sl_amount), symbol)
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
sl_amount), stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', sl_amount, stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
# 设置 -止盈价
|
||||
tp_amount = common.multiply(closing_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
tp_amount = common.addition(tp_amount, 0.1)
|
||||
common.writeLogEx('止盈总价值:' + str(tp_amount), symbol)
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
tp_amount), closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', tp_amount, closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止盈价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做空 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做空开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onSellOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def getOnlineData(symbol, input_tf="15m", limit=230):
|
||||
bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit)
|
||||
df = pd.DataFrame(bars[:], columns=['timestamp',
|
||||
'open', 'high', 'low', 'close', 'volume'])
|
||||
df['dt'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||
df.set_index('dt', inplace=True)
|
||||
df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai')
|
||||
return df
|
||||
|
||||
|
||||
def isKdj(last, last_pre):
|
||||
# 判断是否是金叉
|
||||
if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isDeadFork(last, last_pre):
|
||||
# 判断是否是死叉
|
||||
if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def getMACD(df, lenght=-60):
|
||||
if len(df['close'].values) < 100:
|
||||
return False, None
|
||||
|
||||
close_p = df['close'].values
|
||||
df['dif'], df['dea'], df['macd'] = talib.MACD(close_p,
|
||||
fastperiod=12,
|
||||
slowperiod=26,
|
||||
signalperiod=9)
|
||||
return df[lenght:]
|
||||
|
||||
|
||||
def getEMA(df):
|
||||
close_p = df['close'].values
|
||||
df['ema'] = talib.EMA(np.array(close_p), timeperiod=200)
|
||||
return df
|
||||
|
||||
|
||||
def doneMacd(data, tag, timeframe):
|
||||
data = getEMA(data)
|
||||
|
||||
ma_data = getMACD(data)
|
||||
# alen = len(data)
|
||||
# print(ma_data)
|
||||
|
||||
t_data = ma_data.tail(3)
|
||||
# print(t_data)
|
||||
|
||||
last_pre = t_data.iloc[0]
|
||||
last = t_data.iloc[1]
|
||||
|
||||
# print(last)
|
||||
# print(last.index)
|
||||
|
||||
# print('close:', last['close'], 'ema:', last['ema'])
|
||||
|
||||
obj = common.MsgTpl()
|
||||
symbol = tag.upper() + '/USDT'
|
||||
obj.setName(symbol)
|
||||
obj.setStrategyName("MACD检查")
|
||||
obj.setTimeFrame(timeframe)
|
||||
obj.setOpenTime(last['timestamp'] / 1000)
|
||||
|
||||
now_data = t_data.iloc[2]
|
||||
# print('now_data:', now_data)
|
||||
|
||||
# msg = obj.toText()
|
||||
# print(msg)
|
||||
|
||||
if isKdj(last, last_pre) and last['close'] > last['ema']:
|
||||
# if isKdj(last, last_pre):
|
||||
|
||||
# closing_price = common.calc_ClosingPrice(
|
||||
# now_data['close'], last_pre['low'], 'buy')
|
||||
# # print('closing_price:', closing_price)
|
||||
|
||||
# 做多止损点
|
||||
stop_loss_price = last_pre['low']
|
||||
buy_status, open_price, closing_price = onBuyOrder(
|
||||
symbol, stop_loss_price, 0.005, timeframe)
|
||||
|
||||
if buy_status:
|
||||
obj.setStrategicDt('buy')
|
||||
# 做多止损点
|
||||
obj.setStopLossPrice(str(stop_loss_price))
|
||||
obj.setOpenPrice(str(open_price))
|
||||
obj.setClosingPrice(str(closing_price))
|
||||
obj.setContent("检查到金叉状态")
|
||||
msg = obj.toText()
|
||||
print(msg)
|
||||
common.notifyMsg(msg, timeframe, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
if isDeadFork(last, last_pre) and last['close'] < last['ema']:
|
||||
# if isDeadFork(last, last_pre):
|
||||
# closing_price = common.calc_ClosingPrice(
|
||||
# now_data['close'], last_pre['high'], 'sell')
|
||||
|
||||
# 做空止损点
|
||||
stop_loss_price = last_pre['high']
|
||||
sell_status, open_price, closing_price = onSellOrder(
|
||||
symbol, stop_loss_price, 0.005, timeframe)
|
||||
if sell_status:
|
||||
obj.setStopLossPrice(str(stop_loss_price))
|
||||
obj.setOpenPrice(str(open_price))
|
||||
obj.setClosingPrice(str(closing_price))
|
||||
obj.setStrategicDt('sell')
|
||||
obj.setContent("检查到死叉状态")
|
||||
msg = obj.toText()
|
||||
print(msg)
|
||||
common.notifyMsg(msg, timeframe, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
|
||||
def mainProcess(tag, timeframe='15m'):
|
||||
symbol = tag.upper() + '/USDT'
|
||||
data = getOnlineData(symbol, timeframe)
|
||||
|
||||
doneMacd(data, tag, timeframe)
|
||||
|
||||
|
||||
def foreachList():
|
||||
tag_list = [
|
||||
'eth', 'dot'
|
||||
]
|
||||
|
||||
for tag in tag_list:
|
||||
mainProcess(tag, '15m')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def longRun():
|
||||
while True:
|
||||
foreachList()
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def debug():
|
||||
while True:
|
||||
mainProcess('xrp', '1m')
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'long':
|
||||
longRun()
|
||||
elif func == 'run':
|
||||
debug()
|
||||
else:
|
||||
print('error')
|
||||
273
plugins/cryptocurrency_trade/ccxt/strategy/online_test.py
Normal file
273
plugins/cryptocurrency_trade/ccxt/strategy/online_test.py
Normal file
@@ -0,0 +1,273 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_buy_open
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_buy_close
|
||||
|
||||
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_sell_open
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_sell_cloe
|
||||
|
||||
# 获取仓位数据
|
||||
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_get_trade
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import pandas as pd
|
||||
# import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
exchange = common.initEx()
|
||||
|
||||
exchange.load_markets()
|
||||
|
||||
|
||||
def t_get_trade():
|
||||
data = exchange.fetchPositions()
|
||||
print(data)
|
||||
|
||||
|
||||
def btc_test():
|
||||
closing_price = 24599.9
|
||||
stop_loss_price = 24740.4
|
||||
|
||||
# ---------------------------
|
||||
stop_loss_args = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
print('---------止损价 执行 START ----------------------------------')
|
||||
|
||||
sl_amount = stop_loss_price * 0.003
|
||||
print(amount)
|
||||
data = exchange.create_order(
|
||||
'BTC/USDT', 'limit', 'buy', sl_amount, stop_loss_price, stop_loss_args)
|
||||
print(data)
|
||||
print('---------止损价 执行 END ----------------------------------')
|
||||
|
||||
print('---------止盈价 执行 START ----------------------------------')
|
||||
|
||||
closing_price_args = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
cp_amount = closing_price * 0.003
|
||||
print(amount)
|
||||
data = exchange.create_order(
|
||||
'BTC/USDT', 'limit', 'buy', cp_amount, closing_price, closing_price_args)
|
||||
print(data)
|
||||
print('---------止盈价 执行 END ----------------------------------')
|
||||
|
||||
|
||||
def testK_buy_open_sz():
|
||||
# 做多开仓
|
||||
|
||||
data = exchange.fetchTicker('DOT/USDT')
|
||||
print(data)
|
||||
# data = exchange.createMarketBuyOrder('DOT/USDT', 1, {"tdMode": "cross"})
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
|
||||
def testK_buy_open():
|
||||
# 做多开仓
|
||||
|
||||
data = exchange.createMarketBuyOrder(
|
||||
'DOT/USDT', 1, {"tdMode": "cross"})
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
# 数据
|
||||
# {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []}
|
||||
|
||||
# 查询委托单
|
||||
order_id = data['info']['ordId']
|
||||
# order_id = '554437054223302656'
|
||||
data = exchange.fetchOrder(order_id, 'DOT/USDT')
|
||||
print(data['info'])
|
||||
# {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]}
|
||||
|
||||
open_price = data['info']['avgPx']
|
||||
|
||||
print("开仓平均价", open_price)
|
||||
|
||||
# 止盈价
|
||||
closing_price = float(open_price) * float((1 + 0.005))
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
print("止盈价", closing_price)
|
||||
|
||||
# 止损价
|
||||
stop_loss_price = float(open_price) * float((1 - 0.01))
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
print("止损价", stop_loss_price)
|
||||
|
||||
property_val = float(data['info']['accFillSz']) + \
|
||||
float(data['info']['fee'])
|
||||
|
||||
# 相同位数
|
||||
property_val = common.roundValCeil(property_val, data['info']['accFillSz'])
|
||||
print("可平仓资产", property_val)
|
||||
|
||||
# closed_orders = exchange.fetchClosedOrders('DOT/USDT', limit=2)
|
||||
# print('closed_orders', closed_orders)
|
||||
|
||||
# print(exchange.fetchBalance())
|
||||
|
||||
# 可以用,限价单
|
||||
# time.sleep(1)
|
||||
# data = exchange.createLimitSellOrder(
|
||||
# 'DOT/USDT', property_val, closing_price, {"tdMode": "cross", 'ccy': 'USDT', "reduceOnly": True})
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
# 止损价
|
||||
# 止盈价
|
||||
|
||||
exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
print('---------止损价 执行----------------------------------')
|
||||
|
||||
data = exchange.create_order(
|
||||
'DOT/USDT', 'limit', 'sell', property_val, closing_price, exchange_params)
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
# print('---------止盈价 执行----------------------------------')
|
||||
|
||||
# exchange_params = {
|
||||
# 'stopPrice': closing_price,
|
||||
# 'type': 'stopLimit',
|
||||
# }
|
||||
# data = exchange.create_order(
|
||||
# 'DOT/USDT', 'limit', 'sell', property_val, closing_price, exchange_params)
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
|
||||
def testK_buy_close():
|
||||
# 平多
|
||||
data = exchange.create_limit_buy_order(
|
||||
'DOT/USDT', 1, 5, {"tdMode": "cross"})
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
# closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
# closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
|
||||
def testK_sell_open():
|
||||
# 做空开仓
|
||||
|
||||
# data = exchange.createMarketSellOrder(
|
||||
# 'DOT/USDT', 1, {"tdMode": "cross", 'ccy': "USDT", })
|
||||
# print(data)
|
||||
# print(type(data))
|
||||
|
||||
# 数据
|
||||
# {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []}
|
||||
|
||||
# # 查询委托单
|
||||
# order_id = data['info']['ordId']
|
||||
order_id = '554516809819832320'
|
||||
data = exchange.fetchOrder(order_id, 'DOT/USDT')
|
||||
print(data['info'])
|
||||
# {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]}
|
||||
|
||||
open_price = data['info']['avgPx']
|
||||
|
||||
print("开仓平均价", open_price)
|
||||
|
||||
# 止盈价
|
||||
closing_price = float(open_price) * float((1 - 0.005))
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
print("止盈价", closing_price)
|
||||
|
||||
# 止损价
|
||||
stop_loss_price = float(open_price) * float((1 + 0.01))
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
print("止损价", stop_loss_price)
|
||||
|
||||
property_val = float(data['info']['accFillSz'])
|
||||
# 相同位数
|
||||
print("可平仓资产", property_val)
|
||||
|
||||
# 止损价
|
||||
# 止盈价
|
||||
exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
print('---------止损价 执行----------------------------------')
|
||||
|
||||
data = exchange.create_order(
|
||||
'DOT/USDT', 'limit', 'buy', property_val, closing_price, exchange_params)
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
|
||||
def testK_sell_close():
|
||||
# 平多
|
||||
data = exchange.create_limit_buy_order(
|
||||
'DOT/USDT', 1, 5, {"tdMode": "cross"})
|
||||
print(data)
|
||||
print(type(data))
|
||||
|
||||
# closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
# closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2)
|
||||
# print(closed_orders)
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'long':
|
||||
longRun()
|
||||
elif func == 'run':
|
||||
debug()
|
||||
elif func == 'test':
|
||||
testKdan()
|
||||
elif func == 't_get_trade':
|
||||
t_get_trade()
|
||||
elif func == 't_buy_open':
|
||||
btc_test()
|
||||
elif func == 't_buy_close':
|
||||
testK_buy_close()
|
||||
elif func == 't_sell_open':
|
||||
testK_sell_open()
|
||||
elif func == 't_sell_cloe':
|
||||
testK_sell_close()
|
||||
else:
|
||||
print('error')
|
||||
102
plugins/cryptocurrency_trade/ccxt/strategy/rsi_robot.py
Normal file
102
plugins/cryptocurrency_trade/ccxt/strategy/rsi_robot.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import pandas as pd
|
||||
import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/rsi_robot.py run
|
||||
|
||||
|
||||
pd.set_option('display.max_rows', None)
|
||||
|
||||
exchange = common.initEx()
|
||||
|
||||
# 查看隐形方法
|
||||
# print(dir(exchange))
|
||||
|
||||
|
||||
exchange.load_markets()
|
||||
|
||||
entry_rsi = 30
|
||||
exit_rsi = 40
|
||||
|
||||
|
||||
symbol = 'XRP/USDT'
|
||||
timeframe = '15m'
|
||||
|
||||
tf_mult = exchange.parse_timeframe(timeframe) * 1000
|
||||
|
||||
|
||||
def indicators(data):
|
||||
|
||||
data['rsi'] = data.ta.rsi(length=10)
|
||||
data['ema'] = data.ta.ema(length=200)
|
||||
|
||||
# close_p = data['close'].values
|
||||
# data['rsi'] = talib.RSI(close_p, timeperiod=10)
|
||||
# data['ema'] = talib.EMA(close_p, timeperiod=200)
|
||||
return data
|
||||
|
||||
|
||||
def check_buy_sell_signals(df):
|
||||
last_row_index = len(df.index) - 1
|
||||
lastest_rsi = round(df['rsi'].iloc[-1], 2)
|
||||
lastest_price = round(df['close'].iloc[-1], 5)
|
||||
lastest_ema = round(df['ema'].iloc[-1], 5)
|
||||
lastest_ts = df['timestamp'].iloc[-1]
|
||||
|
||||
msg = "lastest_rsi:" + str(lastest_rsi) + " < entry_rsi:" + str(entry_rsi)
|
||||
msg += ",lastest_price:" + \
|
||||
str(lastest_price) + " > lastest_ema:" + str(lastest_ema)
|
||||
print(msg)
|
||||
|
||||
long_cond = (lastest_rsi < entry_rsi) and (lastest_price > lastest_ema)
|
||||
if long_cond:
|
||||
print("买入")
|
||||
order = exchange.create_market_buy_order(symbol, 1)
|
||||
|
||||
closed_orders = exchange.fetchClosedOrders(symbol, limit=2)
|
||||
if len(closed_orders) > 0:
|
||||
print("closed_orders:", closed_orders)
|
||||
most_recent_closed_order = closed_orders[-1]
|
||||
diff = lastest_ts - most_recent_closed_order['timestamp']
|
||||
last_buy_signal_cnt = int(diff / tf_mult)
|
||||
|
||||
exit_cond = (lastest_rsi > exit_rsi) and (last_buy_signal_cnt > 10)
|
||||
if exit_cond:
|
||||
print("卖出")
|
||||
order = exchange.create_market_sell_order(symbol, 1)
|
||||
return
|
||||
|
||||
|
||||
def runBot():
|
||||
bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=200)
|
||||
df = pd.DataFrame(bars[:], columns=['timestamp',
|
||||
'open', 'high', 'low', 'close', 'volume'])
|
||||
df['dt'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||
|
||||
df = indicators(df).tail(30)
|
||||
|
||||
check_buy_sell_signals(df)
|
||||
|
||||
|
||||
def longRunBot():
|
||||
while True:
|
||||
runBot()
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'run':
|
||||
longRunBot()
|
||||
else:
|
||||
print('error')
|
||||
536
plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py
Normal file
536
plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py
Normal file
@@ -0,0 +1,536 @@
|
||||
# cd /www/server/mdserver-web && . bin/activate
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py run
|
||||
# python3 plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py long
|
||||
|
||||
|
||||
# 动量策略交易
|
||||
################
|
||||
|
||||
|
||||
import ccxt
|
||||
import talib
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import pandas as pd
|
||||
# import pandas_ta as ta
|
||||
from pprint import pprint
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy")
|
||||
import common
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
exchange = common.initEx()
|
||||
exchange.load_markets()
|
||||
|
||||
# 默认开仓数据
|
||||
# default_open_num = 70
|
||||
# default_sell_num = 0.003
|
||||
|
||||
default_open = {
|
||||
'BTC/USDT': 70,
|
||||
'XRP/USDT': 70,
|
||||
}
|
||||
|
||||
default_sell = {
|
||||
'BTC/USDT': 0.003,
|
||||
'XRP/USDT': 185,
|
||||
}
|
||||
|
||||
|
||||
# 做多开仓
|
||||
def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
|
||||
common.writeLogEx('------做多----------------------------------', symbol)
|
||||
|
||||
default_open_num = default_open[symbol]
|
||||
# 做多开仓 | 市价
|
||||
data = exchange.createMarketBuyOrder(
|
||||
symbol, default_open_num, {"tdMode": "cross"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
# 修正小数点位数
|
||||
open_price = common.roundVal(open_price, stop_loss_price)
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
|
||||
# 做多-止损价大于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 - 0.003))
|
||||
|
||||
# property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee'])
|
||||
|
||||
property_val = common.addition(order_data['info'][
|
||||
'accFillSz'], order_data['info']['fee'])
|
||||
property_val = float(property_val)
|
||||
# 可平仓的数量
|
||||
# property_val = common.roundValCeil(
|
||||
# property_val, order_data['info']['accFillSz'])
|
||||
|
||||
common.writeLogEx('可平仓资产:' + str(property_val), symbol)
|
||||
|
||||
# 止盈价
|
||||
diff = float(open_price) - float(stop_loss_price)
|
||||
closing_price = float(open_price) + (diff * 1.5)
|
||||
# closing_price = float(open_price) * float((1 + profit))
|
||||
# # 选择盈利多的
|
||||
# if closing_price_c > closing_price:
|
||||
# closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价/止盈价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
# 止损条件单
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
# 止赢条件单
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell',
|
||||
property_val, closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止盈数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做多 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做多开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
|
||||
# 信号只在一个周期内执行一次|start
|
||||
lock_file = common.getServerDir() + '/signal.json'
|
||||
if not os.path.exists(lock_file):
|
||||
mw.writeFile(lock_file, '{}')
|
||||
|
||||
stype = symbol.replace('/', '_') + '_' + timeframe
|
||||
trigger_time = common.toUnixTimeSecond(timeframe)
|
||||
lock_data = json.loads(mw.readFile(lock_file))
|
||||
if stype in lock_data:
|
||||
diff_time = time.time() - lock_data[stype]['do_time']
|
||||
if diff_time >= trigger_time:
|
||||
lock_data[stype]['do_time'] = time.time()
|
||||
else:
|
||||
return False, 0, 0
|
||||
else:
|
||||
lock_data[stype] = {'do_time': time.time()}
|
||||
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
common.writeLogEx('------做空----------------------------------', symbol)
|
||||
|
||||
# 计算借币卖币多多少,以USDT为基准
|
||||
# sell_num = float(default_open_num) / float(stop_loss_price)
|
||||
# sell_num = round(sell_num, 8)
|
||||
# sell_num = default_sell_num
|
||||
sell_num = default_sell[symbol]
|
||||
|
||||
# 做空开仓 | 市价
|
||||
data = exchange.createMarketSellOrder(
|
||||
symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"})
|
||||
|
||||
common.writeLogEx('开仓数据:', symbol)
|
||||
common.writeLogEx(json.dumps(data), symbol)
|
||||
|
||||
order_id = data['info']['ordId']
|
||||
order_data = exchange.fetchOrder(order_id, symbol)
|
||||
|
||||
common.writeLogEx('订单数据:', symbol)
|
||||
common.writeLogEx(json.dumps(order_data), symbol)
|
||||
|
||||
# 实际开场平均价
|
||||
open_price = order_data['info']['avgPx']
|
||||
|
||||
# 修正
|
||||
open_price = common.roundVal(open_price, stop_loss_price)
|
||||
|
||||
common.writeLogEx('实际开仓价:' + str(open_price), symbol)
|
||||
common.writeLogEx('可平仓资产:' + str(sell_num), symbol)
|
||||
|
||||
# 做空-止损价小于开仓价,重设止损价
|
||||
if float(stop_loss_price) <= float(open_price):
|
||||
stop_loss_price = float(open_price) * float((1 + 0.003))
|
||||
|
||||
# 止盈价
|
||||
diff = float(stop_loss_price) - float(open_price)
|
||||
closing_price = float(open_price) - (diff * 1.5)
|
||||
# closing_price = float(open_price) * float((1 - profit))
|
||||
# 选择盈利多的
|
||||
# if closing_price_c < closing_price:
|
||||
# closing_price = closing_price_c
|
||||
|
||||
closing_price = common.roundVal(closing_price, open_price)
|
||||
stop_loss_price = common.roundVal(stop_loss_price, open_price)
|
||||
|
||||
common.writeLogEx('止盈价:' + str(closing_price), symbol)
|
||||
common.writeLogEx('止损价:' + str(stop_loss_price), symbol)
|
||||
|
||||
# 设置 - 止损价
|
||||
sl_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'slOrdPx': "-1",
|
||||
'slTriggerPx': stop_loss_price,
|
||||
}
|
||||
|
||||
sl_amount = common.multiply(stop_loss_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
sl_amount = common.addition(sl_amount, 0.1)
|
||||
common.writeLogEx('止损总价值:' + str(sl_amount), symbol)
|
||||
common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
sl_amount), stop_loss_price, sl_exchange_params]), symbol)
|
||||
sl_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', float(sl_amount), stop_loss_price, sl_exchange_params)
|
||||
|
||||
common.writeLogEx('止损价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(sl_cond_data), symbol)
|
||||
|
||||
tp_exchange_params = {
|
||||
'ccy': "USDT",
|
||||
'reduceOnly': True,
|
||||
'tdMode': "cross",
|
||||
'tpOrdPx': "-1",
|
||||
'tpTriggerPx': closing_price,
|
||||
}
|
||||
|
||||
# 设置 -止盈价
|
||||
# tp_amount = closing_price * sell_num
|
||||
tp_amount = common.multiply(closing_price, sell_num)
|
||||
# 解决平仓时,未全部平仓
|
||||
tp_amount = common.addition(tp_amount, 0.1)
|
||||
common.writeLogEx('止盈总价值:' + str(tp_amount), symbol)
|
||||
common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float(
|
||||
tp_amount), closing_price, tp_exchange_params]), symbol)
|
||||
tp_cond_data = exchange.create_order(
|
||||
symbol, 'limit', 'buy', float(tp_amount), closing_price, tp_exchange_params)
|
||||
|
||||
common.writeLogEx('止盈价数据:', symbol)
|
||||
common.writeLogEx(json.dumps(tp_cond_data), symbol)
|
||||
|
||||
common.writeLogEx('------做空 end----------------------------------', symbol)
|
||||
return True, open_price, closing_price
|
||||
|
||||
|
||||
def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'):
|
||||
# 做空开仓
|
||||
# profit 百分比
|
||||
try:
|
||||
return onSellOrderTry(symbol, stop_loss_price, profit, timeframe)
|
||||
except Exception as e:
|
||||
common.writeLogErrorEx(mw.getTracebackInfo(), symbol)
|
||||
return False, 0, 0
|
||||
|
||||
|
||||
def getOnlineData(symbol, input_tf="15m", limit=500):
|
||||
bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=500)
|
||||
df = pd.DataFrame(bars[:], columns=['timestamp',
|
||||
'open', 'high', 'low', 'close', 'volume'])
|
||||
df['dt'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||
df.set_index('dt', inplace=True)
|
||||
df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai')
|
||||
return df
|
||||
|
||||
|
||||
def isKdj(last, last_pre):
|
||||
# 判断是否是金叉
|
||||
if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isDeadFork(last, last_pre):
|
||||
# 判断是否是死叉
|
||||
if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def getTarget(df):
|
||||
close = df['close'].values
|
||||
|
||||
# vega
|
||||
df['ema144'] = talib.EMA(np.array(close), timeperiod=144)
|
||||
df['ema169'] = talib.EMA(np.array(close), timeperiod=169)
|
||||
|
||||
df['ema288'] = talib.EMA(np.array(close), timeperiod=288)
|
||||
df['ema388'] = talib.EMA(np.array(close), timeperiod=388)
|
||||
|
||||
df['rsi'] = talib.RSI(close, timeperiod=14)
|
||||
return df
|
||||
|
||||
|
||||
# 多头信号
|
||||
def isBuyCrondSignal(last):
|
||||
if last['ema'] > last['ma'] and (last['rsi'] > 50 and last['rsi'] < 70) and last['low'] >= last['ma'] and last['close'] > last['open']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isBuyFristSignal(data):
|
||||
data = data.sort_values(by=['timestamp'], ascending=False)
|
||||
# print(data)
|
||||
data_len = len(data)
|
||||
signal_num = 0
|
||||
|
||||
first_data = data.iloc[1]
|
||||
# print(1, first_data['close'], first_data['ema'])
|
||||
is_buy_signal = isBuyCrondSignal(first_data)
|
||||
|
||||
for x in range(2, data_len):
|
||||
tmp = data.iloc[x]
|
||||
# print(data.iloc[x])
|
||||
# print(x, tmp['close'], tmp['ema'])
|
||||
if isBuyCrondSignal(tmp):
|
||||
signal_num = + 1
|
||||
|
||||
# print('signal_num:', signal_num)
|
||||
|
||||
if (tmp['ema'] < tmp['ma']):
|
||||
break
|
||||
|
||||
if str(tmp['ema']) == 'nan':
|
||||
break
|
||||
|
||||
print("is_buy_signal:", is_buy_signal, 'signal_num:', signal_num)
|
||||
if is_buy_signal and signal_num == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# 空头信号
|
||||
def isSellCrondSignal(last):
|
||||
if last['ema'] < last['ma'] and (last['rsi'] > 30 and last['rsi'] < 50) and last['high'] <= last['ema'] and last['close'] < last['open']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isSellFristSignal(data):
|
||||
data = data.sort_values(by=['timestamp'], ascending=False)
|
||||
# print(data)
|
||||
data_len = len(data)
|
||||
signal_num = 0
|
||||
|
||||
first_data = data.iloc[1]
|
||||
# print(1, first_data['close'], first_data['ema'])
|
||||
is_sell_signal = isSellCrondSignal(first_data)
|
||||
|
||||
for x in range(2, data_len):
|
||||
tmp = data.iloc[x]
|
||||
# print(data.iloc[x])
|
||||
# print(x, tmp['close'], tmp['ema'])
|
||||
if isSellCrondSignal(tmp):
|
||||
signal_num = + 1
|
||||
|
||||
# print('signal_num:', signal_num)
|
||||
|
||||
if (tmp['ema'] < tmp['ma']):
|
||||
break
|
||||
|
||||
if str(tmp['ema']) == 'nan':
|
||||
break
|
||||
|
||||
print("is_sell_signal:", is_sell_signal, 'signal_num:', signal_num)
|
||||
if is_sell_signal and signal_num == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def isDownTrendCover(data, tag, timeframe):
|
||||
# print(tag, timeframe, "开始检测是否下跌趋势")
|
||||
# 下跌趋势回调
|
||||
key_data = data.tail(2)
|
||||
last_pre = key_data.iloc[0]
|
||||
last = key_data.iloc[1]
|
||||
|
||||
pdata = data.tail(11)
|
||||
|
||||
for x in range(10):
|
||||
t = pdata.iloc[x]
|
||||
if t['low'] > t['ema388']:
|
||||
print(tag, timeframe, "上升震荡行情过滤")
|
||||
# print(t)
|
||||
return False
|
||||
|
||||
if last['ema144'] > last['ema388']:
|
||||
return False
|
||||
|
||||
print(tag, timeframe, "检查是下跌趋势!")
|
||||
# 检查是否是下跌趋势
|
||||
if last['high'] > last['ema388']:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def isUpTrendAndCover(data, tag, timeframe):
|
||||
# print(tag, timeframe, "开始检测是否上升趋势")
|
||||
# 上升趋势回调
|
||||
key_data = data.tail(2)
|
||||
# print(key_data)
|
||||
last_pre = key_data.iloc[0]
|
||||
last = key_data.iloc[1]
|
||||
|
||||
pdata = data.tail(11)
|
||||
|
||||
for x in range(10):
|
||||
t = pdata.iloc[x]
|
||||
if t['low'] < t['ema144']:
|
||||
print(tag, timeframe, "上升震荡行情过滤")
|
||||
return False
|
||||
|
||||
if last['ema144'] < last['ema388']:
|
||||
return False
|
||||
|
||||
print(tag, timeframe, "检查是上升趋势")
|
||||
|
||||
# 检查是否是上升趋势
|
||||
if last['low'] < last['ema144']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def vegaTrade(data, tag, timeframe):
|
||||
data = getTarget(data)
|
||||
# print(data)
|
||||
|
||||
key_data = data.tail(2)
|
||||
# print(key_data)
|
||||
# last_pre = key_data.iloc[0]
|
||||
last = key_data.iloc[1]
|
||||
# print(last)
|
||||
|
||||
obj = common.MsgTpl()
|
||||
symbol = tag.upper() + '/USDT'
|
||||
obj.setName(symbol)
|
||||
obj.setStrategyName("VEGA交易策略|仅具有指导作用")
|
||||
obj.setTimeFrame(timeframe)
|
||||
obj.setOpenTime(last['addtime'])
|
||||
|
||||
if isUpTrendAndCover(data, tag, timeframe):
|
||||
obj.setStrategicDt('buy')
|
||||
obj.setContent("VEGA上升趋势!回调做多~建议!")
|
||||
|
||||
msg = obj.toText()
|
||||
common.notifyMsg(msg, timeframe, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
if isDownTrendCover(data, tag, timeframe):
|
||||
obj.setStrategicDt('sell')
|
||||
obj.setContent("Vega下跌趋势!回调做空~建议!")
|
||||
msg = obj.toText()
|
||||
common.notifyMsg(msg, timeframe, tag)
|
||||
common.writeLog(msg)
|
||||
|
||||
|
||||
def mainProcess(tag, timeframe='5m'):
|
||||
symbol = tag.upper() + '/USDT'
|
||||
data = common.getDataFromDb_DF(timeframe, tag.lower())
|
||||
vegaTrade(data, tag, timeframe)
|
||||
return True
|
||||
|
||||
|
||||
def foreachList():
|
||||
tag_list = ['btc', 'xrp', 'eth', 'ltc', 'okb']
|
||||
for tag in tag_list:
|
||||
trame_list = ['5m', '15m', '1h', '4h']
|
||||
for tf in trame_list:
|
||||
mainProcess(tag, tf)
|
||||
# time.sleep(1)
|
||||
|
||||
|
||||
def longRun():
|
||||
while True:
|
||||
try:
|
||||
foreachList()
|
||||
time.sleep(1)
|
||||
except Exception as e:
|
||||
print(mw.getTracebackInfo())
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def debug():
|
||||
while True:
|
||||
mainProcess('btc', '5m')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'long':
|
||||
longRun()
|
||||
elif func == 'run':
|
||||
debug()
|
||||
else:
|
||||
print('error')
|
||||
128
plugins/cryptocurrency_trade/ccxt/test/p1.py
Normal file
128
plugins/cryptocurrency_trade/ccxt/test/p1.py
Normal file
@@ -0,0 +1,128 @@
|
||||
'''
|
||||
pip install git+https://github.com/catalyst-team/catalyst@master --upgrade
|
||||
|
||||
|
||||
cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p1.py
|
||||
|
||||
|
||||
cd /Users/midoks/Desktop/mwdev/server/mdserver-web && . bin/activate
|
||||
|
||||
python3 plugins/cryptocurrency_trade/ccxt/test/p1.py
|
||||
'''
|
||||
|
||||
from datetime import datetime
|
||||
import akshare as ak
|
||||
import pandas as pd
|
||||
import backtrader as bt
|
||||
|
||||
|
||||
class BollStrategy(bt.Strategy): # BOLL策略程序
|
||||
params = (("nk", 13), # 求均值的天数
|
||||
('printlog', False),) # 打印log
|
||||
|
||||
def __init__(self): # 初始化
|
||||
self.data_close = self.datas[0].close # 指定价格序列
|
||||
# 初始化交易指令、买卖价格和手续费
|
||||
self.order = None
|
||||
self.buy_price = None
|
||||
self.buy_comm = None
|
||||
# Boll指标计算
|
||||
self.top = bt.indicators.BollingerBands(
|
||||
self.datas[0], period=self.params.nk).top
|
||||
self.bot = bt.indicators.BollingerBands(
|
||||
self.datas[0], period=self.params.nk).bot
|
||||
# 添加移动均线指标
|
||||
self.sma = bt.indicators.SimpleMovingAverage(
|
||||
self.datas[0], period=self.params.nk)
|
||||
|
||||
def next(self): # 买卖策略
|
||||
if self.order: # 检查是否有指令等待执行
|
||||
return
|
||||
# 检查是否持仓
|
||||
"""
|
||||
if not self.position: # 没有持仓
|
||||
if self.data_close[0] > self.sma[0]: # 执行买入条件判断:收盘价格上涨突破20日均线
|
||||
self.order = self.buy(size=100) # 执行买入
|
||||
else:
|
||||
if self.data_close[0] < self.sma[0]: # 执行卖出条件判断:收盘价格跌破20日均线
|
||||
self.order = self.sell(size=100) # 执行卖出
|
||||
"""
|
||||
if not self.position: # 没有持仓
|
||||
if self.data_close[0] < self.bot[0]: # 收盘价格跌破下轨
|
||||
self.log("BUY CREATE, %.2f" % self.data_close[0])
|
||||
self.order = self.buy() # 执行买入
|
||||
else:
|
||||
if self.data_close[0] > self.top[0]: # 收盘价格上涨突破上轨
|
||||
self.log("SELL CREATE, %.2f" % self.data_close[0])
|
||||
self.order = self.sell() # 执行卖出
|
||||
|
||||
def log(self, txt, dt=None, do_print=False): # 日志函数
|
||||
if self.params.printlog or do_print:
|
||||
dt = dt or self.datas[0].datetime.date(0)
|
||||
print('%s, %s' % (dt.isoformat(), txt))
|
||||
|
||||
def notify_order(self, order): # 记录交易执行情况
|
||||
# 如果order为submitted/accepted,返回空
|
||||
if order.status in [order.Submitted, order.Accepted]:
|
||||
return
|
||||
# 指令为buy/sell,报告价格结果
|
||||
if order.status in [order.Completed]:
|
||||
if order.isbuy():
|
||||
self.log(
|
||||
f"买入:\n价格:{order.executed.price},\
|
||||
成本:{order.executed.value},\
|
||||
手续费:{order.executed.comm}"
|
||||
)
|
||||
self.buyprice = order.executed.price
|
||||
self.buycomm = order.executed.comm
|
||||
else:
|
||||
self.log(
|
||||
f"卖出:\n价格:{order.executed.price},\
|
||||
成本: {order.executed.value},\
|
||||
手续费{order.executed.comm}"
|
||||
)
|
||||
self.bar_executed = len(self)
|
||||
|
||||
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
|
||||
self.log("交易失败") # 指令取消/交易失败, 报告结果
|
||||
self.order = None
|
||||
|
||||
def notify_trade(self, trade): # 记录交易收益情况
|
||||
if not trade.isclosed:
|
||||
return
|
||||
self.log(f"策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}")
|
||||
|
||||
def stop(self): # 回测结束后输出结果
|
||||
self.log("(BOLL线: %2d日) 期末总资金 %.2f" %
|
||||
(self.params.nk, self.broker.getvalue()), do_print=True)
|
||||
|
||||
code = "600036" # 股票代码
|
||||
start_cash = 1000000 # 初始自己为1000000
|
||||
stake = 100 # 单次交易数量为1手
|
||||
commfee = 0.0005 # 佣金为万5
|
||||
sdate = '20210101' # 回测时间段
|
||||
edate = '20220930'
|
||||
cerebro = bt.Cerebro() # 创建回测系统实例
|
||||
# 利用AKShare获取股票的前复权数据的前6列
|
||||
df_qfq = ak.stock_zh_a_hist(
|
||||
symbol=code, adjust="qfq", start_date=sdate, end_date=edate).iloc[:, :6]
|
||||
# 处理字段命名,以符合Backtrader的要求
|
||||
df_qfq.columns = ['date', 'open', 'close', 'high', 'low', 'volume', ]
|
||||
# 把date作为日期索引,以符合Backtrader的要求
|
||||
df_qfq.index = pd.to_datetime(df_qfq['date'])
|
||||
start_date = datetime.strptime(sdate, "%Y%m%d") # 转换日期格式
|
||||
end_date = datetime.strptime(edate, "%Y%m%d")
|
||||
# start_date=datetime(2022,1,4)
|
||||
# end_date=datetime(2022,9,16)
|
||||
data = bt.feeds.PandasData(
|
||||
dataname=df_qfq, fromdate=start_date, todate=end_date) # 规范化数据格式
|
||||
cerebro.adddata(data) # 加载数据
|
||||
cerebro.addstrategy(BollStrategy, nk=13, printlog=True) # 加载交易策略
|
||||
cerebro.broker.setcash(start_cash) # broker设置资金
|
||||
cerebro.broker.setcommission(commission=commfee) # broker手续费
|
||||
cerebro.addsizer(bt.sizers.FixedSize, stake=stake) # 设置买入数量
|
||||
print("期初总资金: %.2f" % start_cash)
|
||||
cerebro.run() # 运行回测
|
||||
end_value = cerebro.broker.getvalue() # 获取回测结束后的总资金
|
||||
print("期末总资金: %.2f" % end_value)
|
||||
cerebro.plot()
|
||||
154
plugins/cryptocurrency_trade/ccxt/test/p2.py
Normal file
154
plugins/cryptocurrency_trade/ccxt/test/p2.py
Normal file
@@ -0,0 +1,154 @@
|
||||
'''
|
||||
pip install git+https://github.com/catalyst-team/catalyst@master --upgrade
|
||||
|
||||
|
||||
cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p1.py
|
||||
|
||||
|
||||
cd /Users/midoks/Desktop/mwdev/server/mdserver-web && . bin/activate
|
||||
|
||||
python3 plugins/cryptocurrency_trade/ccxt/test/p1.py
|
||||
'''
|
||||
|
||||
from datetime import datetime
|
||||
import akshare as ak
|
||||
import pandas as pd
|
||||
import backtrader as bt
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/ccxt/strategy")
|
||||
import common
|
||||
|
||||
|
||||
class BollStrategy(bt.Strategy): # BOLL策略程序
|
||||
params = (("nk", 13), # 求均值的天数
|
||||
('printlog', False),) # 打印log
|
||||
|
||||
def __init__(self): # 初始化
|
||||
self.data_close = self.datas[0].close # 指定价格序列
|
||||
# 初始化交易指令、买卖价格和手续费
|
||||
self.order = None
|
||||
self.buy_price = None
|
||||
self.buy_comm = None
|
||||
# Boll指标计算
|
||||
self.top = bt.indicators.BollingerBands(
|
||||
self.datas[0], period=self.params.nk).top
|
||||
self.bot = bt.indicators.BollingerBands(
|
||||
self.datas[0], period=self.params.nk).bot
|
||||
# 添加移动均线指标
|
||||
self.sma = bt.indicators.SimpleMovingAverage(
|
||||
self.datas[0], period=self.params.nk)
|
||||
|
||||
def next(self): # 买卖策略
|
||||
if self.order: # 检查是否有指令等待执行
|
||||
return
|
||||
# 检查是否持仓
|
||||
"""
|
||||
if not self.position: # 没有持仓
|
||||
if self.data_close[0] > self.sma[0]: # 执行买入条件判断:收盘价格上涨突破20日均线
|
||||
self.order = self.buy(size=100) # 执行买入
|
||||
else:
|
||||
if self.data_close[0] < self.sma[0]: # 执行卖出条件判断:收盘价格跌破20日均线
|
||||
self.order = self.sell(size=100) # 执行卖出
|
||||
"""
|
||||
if not self.position: # 没有持仓
|
||||
if self.data_close[0] < self.bot[0]: # 收盘价格跌破下轨
|
||||
self.log("BUY CREATE, %.2f" % self.data_close[0])
|
||||
self.order = self.buy() # 执行买入
|
||||
else:
|
||||
if self.data_close[0] > self.top[0]: # 收盘价格上涨突破上轨
|
||||
self.log("SELL CREATE, %.2f" % self.data_close[0])
|
||||
self.order = self.sell() # 执行卖出
|
||||
|
||||
def log(self, txt, dt=None, do_print=False): # 日志函数
|
||||
if self.params.printlog or do_print:
|
||||
dt = dt or self.datas[0].datetime.date(0)
|
||||
print('%s, %s' % (dt.isoformat(), txt))
|
||||
|
||||
def notify_order(self, order): # 记录交易执行情况
|
||||
# 如果order为submitted/accepted,返回空
|
||||
if order.status in [order.Submitted, order.Accepted]:
|
||||
return
|
||||
# 指令为buy/sell,报告价格结果
|
||||
if order.status in [order.Completed]:
|
||||
if order.isbuy():
|
||||
self.log(
|
||||
f"买入:\n价格:{order.executed.price},\
|
||||
成本:{order.executed.value},\
|
||||
手续费:{order.executed.comm}"
|
||||
)
|
||||
self.buyprice = order.executed.price
|
||||
self.buycomm = order.executed.comm
|
||||
else:
|
||||
self.log(
|
||||
f"卖出:\n价格:{order.executed.price},\
|
||||
成本: {order.executed.value},\
|
||||
手续费{order.executed.comm}"
|
||||
)
|
||||
self.bar_executed = len(self)
|
||||
|
||||
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
|
||||
self.log("交易失败") # 指令取消/交易失败, 报告结果
|
||||
self.order = None
|
||||
|
||||
def notify_trade(self, trade): # 记录交易收益情况
|
||||
if not trade.isclosed:
|
||||
return
|
||||
self.log(f"策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}")
|
||||
|
||||
def stop(self): # 回测结束后输出结果
|
||||
self.log("(BOLL线: %2d日) 期末总资金 %.2f" %
|
||||
(self.params.nk, self.broker.getvalue()), do_print=True)
|
||||
|
||||
code = "600036" # 股票代码
|
||||
start_cash = 1000000 # 初始自己为1000000
|
||||
stake = 100 # 单次交易数量为1手
|
||||
commfee = 0.0005 # 佣金为万5
|
||||
sdate = '20210101' # 回测时间段
|
||||
edate = '20220930'
|
||||
cerebro = bt.Cerebro() # 创建回测系统实例
|
||||
# 利用AKShare获取股票的前复权数据的前6列
|
||||
# df_qfq = ak.stock_zh_a_hist(
|
||||
# symbol=code, adjust="qfq", start_date=sdate, end_date=edate).iloc[:, :6]
|
||||
# 日期 开盘 收盘 最高 最低 成交量
|
||||
# 0 2021-01-04 40.46 40.40 41.01 39.25 1549523
|
||||
# 1 2021-01-05 39.99 39.41 40.03 38.67 1387177
|
||||
# 2 2021-01-06 39.33 41.38 41.43 39.23 1200646
|
||||
# 3 2021-01-07 41.52 43.13 43.20 41.49 1078322
|
||||
# 4 2021-01-08 43.52 43.83 44.25 43.04 1299595
|
||||
# .. ... ... ... ... ... ...
|
||||
# 420 2022-09-26 34.02 33.95 34.52 33.84 525491
|
||||
# 421 2022-09-27 33.89 33.90 34.05 33.44 475414
|
||||
# 422 2022-09-28 33.79 33.67 34.00 33.42 487299
|
||||
# 423 2022-09-29 33.99 33.15 34.32 33.00 617859
|
||||
# 424 2022-09-30 33.26 33.65 33.95 33.12 539717
|
||||
|
||||
tag = 'btc'
|
||||
symbol = tag.upper() + '/USDT'
|
||||
df_qfq = common.getDataFromDb_DF('15m', tag.lower())
|
||||
print(df_qfq)
|
||||
|
||||
# # 处理字段命名,以符合Backtrader的要求
|
||||
# df_qfq.columns = ['date', 'open', 'close', 'high', 'low', 'volume', ]
|
||||
# # 把date作为日期索引,以符合Backtrader的要求
|
||||
# df_qfq.index = pd.to_datetime(df_qfq['date'])
|
||||
# start_date = datetime.strptime(sdate, "%Y%m%d") # 转换日期格式
|
||||
# end_date = datetime.strptime(edate, "%Y%m%d")
|
||||
start_date = datetime(2023, 4, 6)
|
||||
end_date = datetime(2023, 4, 9)
|
||||
data = bt.feeds.PandasData(dataname=df_qfq, fromdate=start_date,
|
||||
todate=end_date) # 规范化数据格式
|
||||
|
||||
cerebro.adddata(data) # 加载数据
|
||||
cerebro.addstrategy(BollStrategy, nk=13, printlog=True) # 加载交易策略
|
||||
cerebro.broker.setcash(start_cash) # broker设置资金
|
||||
cerebro.broker.setcommission(commission=commfee) # broker手续费
|
||||
cerebro.addsizer(bt.sizers.FixedSize, stake=stake) # 设置买入数量
|
||||
print("期初总资金: %.2f" % start_cash)
|
||||
cerebro.run() # 运行回测
|
||||
end_value = cerebro.broker.getvalue() # 获取回测结束后的总资金
|
||||
print("期末总资金: %.2f" % end_value)
|
||||
# cerebro.plot(style='candle')
|
||||
61
plugins/cryptocurrency_trade/ccxt/test/p_test.py
Normal file
61
plugins/cryptocurrency_trade/ccxt/test/p_test.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# coding:utf-8
|
||||
|
||||
import pandas as pd
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
from catalyst import run_algorithm
|
||||
from catalyst.api import order, record, symbol
|
||||
|
||||
'''
|
||||
cd /Users/midoks/Desktop/mwdev/server/mdserver-web && / bin/activate
|
||||
|
||||
|
||||
cd /www/server/mdserver-web && . bin/activate && source activate catalys
|
||||
|
||||
cd /Users/midoks/Desktop/mwdev/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p_test.py
|
||||
catalyst ingest-exchange -x binance -i btc_usdt -f minute
|
||||
|
||||
cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p_test.py
|
||||
'''
|
||||
|
||||
|
||||
def initialize(context):
|
||||
# 初始化
|
||||
context.asset = symbol('btc_usdt')
|
||||
|
||||
|
||||
def handle_data(context, data):
|
||||
# 循环策略
|
||||
order(context.asset, 1)
|
||||
record(btc=data.current(context.asset, 'price'))
|
||||
|
||||
|
||||
def analyze(context, perf):
|
||||
|
||||
print(perf.portfolio_value)
|
||||
|
||||
ax1 = plt.subplot(211)
|
||||
perf.portfolio_value.plot(ax=ax1)
|
||||
|
||||
ax1.set_ylabel('portfolio value')
|
||||
|
||||
ax2 = plt.subplot(212, sharex=ax1)
|
||||
perf.btc.plot(ax=ax2)
|
||||
ax2.set_ylabel('bitcoin value')
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_algorithm(
|
||||
capital_base=10000,
|
||||
data_frequency='daily',
|
||||
initialize=initialize,
|
||||
handle_data=handle_data,
|
||||
analyze=analyze,
|
||||
exchange_name='binance',
|
||||
quote_currenty='usdt',
|
||||
start=pd.to_datetime("2018-01-01", utc=True),
|
||||
end=pd.to_datetime("2018-10-01", utc=True),
|
||||
)
|
||||
9
plugins/cryptocurrency_trade/conf/create.sql
Normal file
9
plugins/cryptocurrency_trade/conf/create.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS `ct_xx1_xx2` (
|
||||
`addtime` BIGINT(20) not NULL,
|
||||
`open` float NOT NULL,
|
||||
`high` float NOT NULL,
|
||||
`low` float NOT NULL,
|
||||
`close` float NOT NULL,
|
||||
`vol` float NOT NULL,
|
||||
UNIQUE KEY `addtime` (`addtime`)
|
||||
);
|
||||
11
plugins/cryptocurrency_trade/conf/order.sql
Normal file
11
plugins/cryptocurrency_trade/conf/order.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS `ct_order_list` (
|
||||
`id` BIGINT(20) not NULL,
|
||||
'strategy_name' varchar(50) NULL,
|
||||
`symbol` varchar(50) NOT NULL,
|
||||
`fee` float NOT NULL,
|
||||
`price` float NOT NULL,
|
||||
`closing_price` float NOT NULL comment '',
|
||||
`profit` float NOT NULL,
|
||||
`source` TEXT,
|
||||
`addtime` BIGINT(20) not NULL
|
||||
);
|
||||
14
plugins/cryptocurrency_trade/conf/sup_strategy.tpl
Normal file
14
plugins/cryptocurrency_trade/conf/sup_strategy.tpl
Normal file
@@ -0,0 +1,14 @@
|
||||
[program:{$NAME}]
|
||||
command=bash -c "cd {$RUN_ROOT} && . bin/activate && python3 {$ABS_FILE} long"
|
||||
directory={$RUN_ROOT}
|
||||
autorestart=true
|
||||
startsecs=3
|
||||
startretries=3
|
||||
stdout_logfile={$SUP_ROOT}/log/{$NAME}.out.log
|
||||
stderr_logfile={$SUP_ROOT}/log/{$NAME}.err.log
|
||||
stdout_logfile_maxbytes=2MB
|
||||
stderr_logfile_maxbytes=2MB
|
||||
user=root
|
||||
priority=999
|
||||
numprocs=1
|
||||
process_name=%(program_name)s
|
||||
14
plugins/cryptocurrency_trade/conf/sup_task.tpl
Normal file
14
plugins/cryptocurrency_trade/conf/sup_task.tpl
Normal file
@@ -0,0 +1,14 @@
|
||||
[program:{$NAME}]
|
||||
command=bash -c "cd {$RUN_ROOT} && . bin/activate && python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py long"
|
||||
directory={$RUN_ROOT}
|
||||
autorestart=true
|
||||
startsecs=3
|
||||
startretries=3
|
||||
stdout_logfile={$SUP_ROOT}/log/{$NAME}.out.log
|
||||
stderr_logfile={$SUP_ROOT}/log/{$NAME}.err.log
|
||||
stdout_logfile_maxbytes=2MB
|
||||
stderr_logfile_maxbytes=2MB
|
||||
user=root
|
||||
priority=999
|
||||
numprocs=1
|
||||
process_name=%(program_name)s
|
||||
350
plugins/cryptocurrency_trade/cryptocurrency_trade.py
Normal file
350
plugins/cryptocurrency_trade/cryptocurrency_trade.py
Normal file
@@ -0,0 +1,350 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
import string
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'cryptocurrency_trade'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def contentReplace(content):
|
||||
service_path = mw.getServerDir()
|
||||
content = content.replace('{$ROOT_PATH}', mw.getFatherDir())
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
content = content.replace(
|
||||
'{$SERVER_APP}', service_path + '/' + getPluginName())
|
||||
return content
|
||||
|
||||
|
||||
def getArgs():
|
||||
args = sys.argv[2:]
|
||||
tmp = {}
|
||||
args_len = len(args)
|
||||
if args_len == 1:
|
||||
t = args[0].strip('{').strip('}')
|
||||
if t.strip() == '':
|
||||
tmp = []
|
||||
else:
|
||||
t = t.split(':', 1)
|
||||
tmp[t[0]] = t[1]
|
||||
tmp[t[0]] = t[1]
|
||||
elif args_len > 1:
|
||||
|
||||
for i in range(len(args)):
|
||||
t = args[i].split(':', 1)
|
||||
tmp[t[0]] = t[1]
|
||||
return tmp
|
||||
|
||||
|
||||
def checkArgs(data, ck=[]):
|
||||
for i in range(len(ck)):
|
||||
if not ck[i] in data:
|
||||
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
|
||||
return (True, mw.returnJson(True, 'ok'))
|
||||
|
||||
|
||||
def isSqlError(mysqlMsg):
|
||||
# 检测数据库执行错误
|
||||
mysqlMsg = str(mysqlMsg)
|
||||
if "MySQLdb" in mysqlMsg:
|
||||
return mw.returnJson(False, 'MySQLdb组件缺失! <br>进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3')
|
||||
if "2002," in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!')
|
||||
if "2003," in mysqlMsg:
|
||||
return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)")
|
||||
if "using password:" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库密码错误')
|
||||
if "1045" in mysqlMsg:
|
||||
return mw.returnJson(False, '连接错误!')
|
||||
if "SQL syntax" in mysqlMsg:
|
||||
return mw.returnJson(False, 'SQL语法错误!')
|
||||
if "Connection refused" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!')
|
||||
if "1133," in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库用户不存在!')
|
||||
if "1007," in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库已经存在!')
|
||||
return None
|
||||
|
||||
|
||||
def getConfigData():
|
||||
cfg_path = getServerDir() + "/data.cfg"
|
||||
if not os.path.exists(cfg_path):
|
||||
mw.writeFile(cfg_path, '{}')
|
||||
t = mw.readFile(cfg_path)
|
||||
return json.loads(t)
|
||||
|
||||
|
||||
def writeConf(data):
|
||||
cfg_path = getServerDir() + "/data.cfg"
|
||||
mw.writeFile(cfg_path, json.dumps(data))
|
||||
return True
|
||||
|
||||
|
||||
def getDbConf():
|
||||
data = getConfigData()
|
||||
if 'db' in data:
|
||||
return mw.returnJson(True, 'ok', data['db'])
|
||||
return mw.returnJson(False, 'ok', {})
|
||||
|
||||
|
||||
def getUserConf():
|
||||
data = getConfigData()
|
||||
if 'user' in data:
|
||||
return mw.returnJson(True, 'ok', data['user'])
|
||||
return mw.returnJson(False, 'ok', {})
|
||||
|
||||
|
||||
def restartSup():
|
||||
cmd = 'python3 plugins/supervisor/index.py restart'
|
||||
mw.execShell(cmd)
|
||||
|
||||
|
||||
def restartSupDst(name):
|
||||
cmd = 'python3 plugins/supervisor/index.py restart_job {"name":"' + \
|
||||
name + '","status":"stop"}'
|
||||
mw.execShell(cmd)
|
||||
|
||||
|
||||
def syncDataAddTaskUninstall():
|
||||
sup_path = mw.getServerDir() + '/supervisor'
|
||||
if not os.path.exists(sup_path):
|
||||
return mw.returnJson(False, '需要安装并启动supervisor插件')
|
||||
|
||||
name = "ct_task"
|
||||
sup_task_dst = sup_path + '/conf.d/' + name + '.ini'
|
||||
if os.path.exists(sup_task_dst):
|
||||
mw.execShell('rm -rf ' + sup_task_dst)
|
||||
|
||||
restartSup()
|
||||
return mw.returnJson(True, '删除同步数据任务成功!')
|
||||
|
||||
|
||||
def syncDataAddTaskInstall():
|
||||
sup_path = mw.getServerDir() + '/supervisor'
|
||||
if not os.path.exists(sup_path):
|
||||
return mw.returnJson(False, '需要安装并启动supervisor插件')
|
||||
|
||||
name = "ct_task"
|
||||
sup_task_tpl = getPluginDir() + '/conf/sup_task.tpl'
|
||||
sup_task_dst = sup_path + '/conf.d/' + name + '.ini'
|
||||
content = mw.readFile(sup_task_tpl)
|
||||
content = content.replace(
|
||||
'{$RUN_ROOT}', mw.getServerDir() + '/mdserver-web')
|
||||
content = content.replace(
|
||||
'{$SUP_ROOT}', sup_path)
|
||||
content = content.replace(
|
||||
'{$NAME}', name)
|
||||
|
||||
mw.writeFile(sup_task_dst, content)
|
||||
restartSup()
|
||||
return mw.returnJson(True, '添加同步数据任务成功!')
|
||||
|
||||
|
||||
def syncDataAddTask():
|
||||
args = getArgs()
|
||||
data_args = checkArgs(args, ['check'])
|
||||
if not data_args[0]:
|
||||
return data_args[1]
|
||||
|
||||
if args['check'] == "0":
|
||||
return syncDataAddTaskUninstall()
|
||||
return syncDataAddTaskInstall()
|
||||
|
||||
|
||||
def syncDataDelete():
|
||||
args = getArgs()
|
||||
data_args = checkArgs(args, ['token'])
|
||||
if not data_args[0]:
|
||||
return data_args[1]
|
||||
|
||||
del_token = args['token']
|
||||
data = getConfigData()
|
||||
|
||||
if 'token' in data:
|
||||
data['token'].remove(del_token)
|
||||
writeConf(data)
|
||||
|
||||
return mw.returnJson(True, '删除成功!')
|
||||
|
||||
|
||||
# callback ---------------------------------- start
|
||||
def get_datasource_logs(args):
|
||||
log_file = getServerDir() + '/logs/datasource.log'
|
||||
if not os.path.exists(log_file):
|
||||
return '暂无日志'
|
||||
data = mw.getLastLine(log_file, 10)
|
||||
return data
|
||||
|
||||
|
||||
def get_strategy_logs(args):
|
||||
log_file = getServerDir() + '/logs/strategy.log'
|
||||
if not os.path.exists(log_file):
|
||||
return '暂无日志'
|
||||
data = mw.getLastLine(log_file, 10)
|
||||
return data
|
||||
|
||||
|
||||
def save_body(args):
|
||||
|
||||
path = args['path']
|
||||
encoding = args['encoding']
|
||||
data = args['data']
|
||||
|
||||
tag = args['tag']
|
||||
|
||||
if not os.path.exists(path):
|
||||
return mw.returnData(False, '文件不存在')
|
||||
try:
|
||||
if encoding == 'ascii':
|
||||
encoding = 'utf-8'
|
||||
|
||||
data = data.encode(
|
||||
encoding, errors='ignore').decode(encoding)
|
||||
|
||||
fp = open(path, 'w+', encoding=encoding)
|
||||
fp.write(data)
|
||||
fp.close()
|
||||
|
||||
set_strategy_restart({'id': tag})
|
||||
return mw.returnData(True, '文件保存成功')
|
||||
except Exception as ex:
|
||||
return mw.returnData(False, '文件保存错误:' + str(ex))
|
||||
|
||||
|
||||
def get_strategy_path(args):
|
||||
abs_id = args['id']
|
||||
name = "ct_strategy_" + abs_id
|
||||
|
||||
abs_file = get_strategy_absfile(abs_id)
|
||||
return mw.returnData(True, abs_file)
|
||||
|
||||
|
||||
def set_strategy_restart(args):
|
||||
sup_path = mw.getServerDir() + '/supervisor'
|
||||
if not os.path.exists(sup_path):
|
||||
return mw.returnData(False, '需要安装并启动supervisor插件')
|
||||
|
||||
abs_id = args['id']
|
||||
name = "ct_strategy_" + abs_id
|
||||
|
||||
sup_strategy_dst = sup_path + '/conf.d/' + name + '.ini'
|
||||
|
||||
if not os.path.exists(sup_strategy_dst):
|
||||
return mw.returnData(False, '策略任务' + abs_id + '未添加!')
|
||||
|
||||
restartSupDst(name)
|
||||
return mw.returnData(True, '重启策略任务' + abs_id + '成功!')
|
||||
|
||||
|
||||
def set_strategy_status(args):
|
||||
sup_path = mw.getServerDir() + '/supervisor'
|
||||
if not os.path.exists(sup_path):
|
||||
return mw.returnData(False, '需要安装并启动supervisor插件')
|
||||
|
||||
abs_id = args['id']
|
||||
name = "ct_strategy_" + abs_id
|
||||
sup_strategy_dst = sup_path + '/conf.d/' + name + '.ini'
|
||||
|
||||
if args['status'] == 'stop':
|
||||
if os.path.exists(sup_strategy_dst):
|
||||
os.remove(sup_strategy_dst)
|
||||
restartSup()
|
||||
return mw.returnData(True, '删除策略任务' + abs_id + '成功!')
|
||||
|
||||
abs_file = get_strategy_absfile(abs_id)
|
||||
sup_strategy_tpl = getPluginDir() + '/conf/sup_strategy.tpl'
|
||||
|
||||
content = mw.readFile(sup_strategy_tpl)
|
||||
content = content.replace(
|
||||
'{$RUN_ROOT}', mw.getServerDir() + '/mdserver-web')
|
||||
content = content.replace(
|
||||
'{$SUP_ROOT}', sup_path)
|
||||
content = content.replace(
|
||||
'{$NAME}', name)
|
||||
content = content.replace(
|
||||
'{$ABS_FILE}', abs_file)
|
||||
|
||||
mw.writeFile(sup_strategy_dst, content)
|
||||
restartSup()
|
||||
return mw.returnData(True, '添加策略任务' + abs_id + '成功!')
|
||||
|
||||
|
||||
def get_strategy_absfile(abs_id):
|
||||
info = getPluginDir() + '/ccxt/strategy/info.json'
|
||||
info = json.loads(mw.readFile(info))
|
||||
|
||||
path = getPluginDir() + '/ccxt/strategy'
|
||||
for x in range(len(info)):
|
||||
if info[x]['id'] == abs_id:
|
||||
return path + '/' + info[x]['file']
|
||||
|
||||
return path + '/abs.py'
|
||||
|
||||
|
||||
def get_strategy_list(args):
|
||||
info = getPluginDir() + '/ccxt/strategy/info.json'
|
||||
info = json.loads(mw.readFile(info))
|
||||
|
||||
st_path = mw.getServerDir() + '/supervisor/conf.d'
|
||||
|
||||
page = 1
|
||||
page_size = 5
|
||||
search = ''
|
||||
data = {}
|
||||
if 'page' in args:
|
||||
page = int(args['page'])
|
||||
|
||||
if 'page_size' in args:
|
||||
page_size = int(args['page_size'])
|
||||
|
||||
dlist_sum = len(info)
|
||||
|
||||
page_start = int((page - 1) * page_size)
|
||||
page_end = page_start + page_size
|
||||
|
||||
if page_end >= dlist_sum:
|
||||
ret_data = info[page_start:]
|
||||
else:
|
||||
ret_data = info[page_start:page_end]
|
||||
|
||||
for x in range(len(ret_data)):
|
||||
strategy_dst = st_path + '/ct_strategy_' + ret_data[x]['id'] + '.ini'
|
||||
if os.path.exists(strategy_dst):
|
||||
ret_data[x]['status'] = 'start'
|
||||
else:
|
||||
ret_data[x]['status'] = 'stop'
|
||||
|
||||
data['data'] = ret_data
|
||||
data['args'] = args
|
||||
data['list'] = mw.getPage(
|
||||
{'count': dlist_sum, 'p': page, 'row': page_size, 'tojs': 'getStrategyList'})
|
||||
|
||||
return data
|
||||
# callback ---------------------------------- end
|
||||
BIN
plugins/cryptocurrency_trade/ico.png
Normal file
BIN
plugins/cryptocurrency_trade/ico.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
26
plugins/cryptocurrency_trade/index.html
Executable file
26
plugins/cryptocurrency_trade/index.html
Executable file
@@ -0,0 +1,26 @@
|
||||
<style type="text/css">
|
||||
.conf_p span{width: 100px;}
|
||||
.code{padding: 20px 5px;border: 1px solid #e1e1e1;background: #fcfcfc;border-radius: 4px;line-height: 24px;}
|
||||
.code span {display: block;margin-left: 15px;margin-bottom: 0;}
|
||||
</style>
|
||||
<div class="bt-form">
|
||||
<div class="bt-w-main">
|
||||
<div class="bt-w-menu">
|
||||
<p class="bgw" onclick="pluginService('cryptocurrency_trade');">服务</p>
|
||||
<p onclick="dbConf();">数据库配置</p>
|
||||
<p onclick="syncDataList();">同步数据配置</p>
|
||||
<p onclick="userConf();">账户配置</p>
|
||||
</div>
|
||||
<div class="bt-w-con pd15">
|
||||
<div class="soft-man-con"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
// resetPluginWinWidth(480);
|
||||
resetPluginWinHeight(400);
|
||||
$.getScript( "/plugins/file?name=cryptocurrency_trade&f=static/js/cryptocurrency_trade.js",function(){
|
||||
pluginService('cryptocurrency_trade');
|
||||
});
|
||||
</script>
|
||||
347
plugins/cryptocurrency_trade/index.py
Normal file
347
plugins/cryptocurrency_trade/index.py
Normal file
@@ -0,0 +1,347 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
import string
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'cryptocurrency_trade'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def contentReplace(content):
|
||||
service_path = mw.getServerDir()
|
||||
content = content.replace('{$ROOT_PATH}', mw.getFatherDir())
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
content = content.replace(
|
||||
'{$SERVER_APP}', service_path + '/' + getPluginName())
|
||||
return content
|
||||
|
||||
|
||||
def getArgs():
|
||||
args = sys.argv[2:]
|
||||
tmp = {}
|
||||
args_len = len(args)
|
||||
if args_len == 1:
|
||||
t = args[0].strip('{').strip('}')
|
||||
if t.strip() == '':
|
||||
tmp = []
|
||||
else:
|
||||
t = t.split(':', 1)
|
||||
tmp[t[0]] = t[1]
|
||||
tmp[t[0]] = t[1]
|
||||
elif args_len > 1:
|
||||
|
||||
for i in range(len(args)):
|
||||
t = args[i].split(':', 1)
|
||||
tmp[t[0]] = t[1]
|
||||
return tmp
|
||||
|
||||
|
||||
def checkArgs(data, ck=[]):
|
||||
for i in range(len(ck)):
|
||||
if not ck[i] in data:
|
||||
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
|
||||
return (True, mw.returnJson(True, 'ok'))
|
||||
|
||||
|
||||
def isSqlError(mysqlMsg):
|
||||
# 检测数据库执行错误
|
||||
mysqlMsg = str(mysqlMsg)
|
||||
if "MySQLdb" in mysqlMsg:
|
||||
return mw.returnJson(False, 'MySQLdb组件缺失! <br>进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3')
|
||||
if "2002," in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!')
|
||||
if "2003," in mysqlMsg:
|
||||
return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)")
|
||||
if "using password:" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库密码错误')
|
||||
if "1045" in mysqlMsg:
|
||||
return mw.returnJson(False, '连接错误!')
|
||||
if "SQL syntax" in mysqlMsg:
|
||||
return mw.returnJson(False, 'SQL语法错误!')
|
||||
if "Connection refused" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!')
|
||||
if "1133" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库用户不存在!')
|
||||
if "1007" in mysqlMsg:
|
||||
return mw.returnJson(False, '数据库已经存在!')
|
||||
return None
|
||||
|
||||
|
||||
def initDreplace():
|
||||
log_dir = getServerDir() + '/logs'
|
||||
if not os.path.exists(log_dir):
|
||||
d = mw.execShell('mkdir -p ' + log_dir)
|
||||
|
||||
data = getConfigData()
|
||||
data['token'] = ['btc']
|
||||
writeConf(data)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def status():
|
||||
initDreplace()
|
||||
return 'start'
|
||||
|
||||
|
||||
def start():
|
||||
syncDataAddTaskInstall()
|
||||
return 'ok'
|
||||
|
||||
|
||||
def stop():
|
||||
syncDataAddTaskUninstall()
|
||||
return 'ok'
|
||||
|
||||
|
||||
def op():
|
||||
return 'ok'
|
||||
|
||||
|
||||
def getConfigData():
|
||||
cfg_path = getServerDir() + "/data.cfg"
|
||||
if not os.path.exists(cfg_path):
|
||||
mw.writeFile(cfg_path, '{}')
|
||||
t = mw.readFile(cfg_path)
|
||||
return json.loads(t)
|
||||
|
||||
|
||||
def writeConf(data):
|
||||
cfg_path = getServerDir() + "/data.cfg"
|
||||
mw.writeFile(cfg_path, json.dumps(data))
|
||||
return True
|
||||
|
||||
|
||||
def getDbConf():
|
||||
data = getConfigData()
|
||||
if 'db' in data:
|
||||
return mw.returnJson(True, 'ok', data['db'])
|
||||
return mw.returnJson(False, 'ok', {})
|
||||
|
||||
|
||||
def setDbConf():
|
||||
args = getArgs()
|
||||
data_args = checkArgs(args, ['db_host', 'db_port',
|
||||
'db_name', 'db_user', 'db_pass'])
|
||||
if not data_args[0]:
|
||||
return data_args[1]
|
||||
|
||||
data = getConfigData()
|
||||
data['db'] = args
|
||||
writeConf(data)
|
||||
|
||||
db = mw.getMyORM()
|
||||
|
||||
db.setHost(args['db_host'])
|
||||
db.setPort(args['db_port'])
|
||||
db.setUser(args['db_user'])
|
||||
db.setPwd(args['db_pass'])
|
||||
testdata = db.query('select version()')
|
||||
|
||||
isError = isSqlError(testdata)
|
||||
if isError != None:
|
||||
return isError
|
||||
|
||||
return mw.returnJson(True, '保存成功,并连通成功!', [])
|
||||
|
||||
|
||||
def getUserConf():
|
||||
data = getConfigData()
|
||||
if 'user' in data:
|
||||
return mw.returnJson(True, 'ok', data['user'])
|
||||
return mw.returnJson(False, 'ok', {})
|
||||
|
||||
|
||||
def getUserConf():
|
||||
data = getConfigData()
|
||||
if 'user' in data:
|
||||
try:
|
||||
udata = mw.deDoubleCrypt('mw', data['user'])
|
||||
udata = json.loads(udata)
|
||||
|
||||
return mw.returnJson(True, 'ok', udata)
|
||||
except Exception as e:
|
||||
pass
|
||||
return mw.returnJson(False, 'ok', {})
|
||||
|
||||
|
||||
def setUserConf():
|
||||
args = getArgs()
|
||||
data_args = checkArgs(args, ['app_key', 'secret',
|
||||
'password', 'uid', 'exchange'])
|
||||
if not data_args[0]:
|
||||
return data_args[1]
|
||||
|
||||
data = getConfigData()
|
||||
data['user'] = mw.enDoubleCrypt('mw', json.dumps(args))
|
||||
writeConf(data)
|
||||
|
||||
return mw.returnJson(True, '保存成功!', [])
|
||||
|
||||
|
||||
def syncDataList():
|
||||
data = getConfigData()
|
||||
|
||||
name = "ct_task"
|
||||
if 'token' in data:
|
||||
|
||||
rdata = {}
|
||||
rdata['task_status'] = False
|
||||
sup_path = mw.getServerDir() + '/supervisor'
|
||||
sup_task_dst = sup_path + '/conf.d/' + name + '.ini'
|
||||
if os.path.exists(sup_task_dst):
|
||||
rdata['task_status'] = True
|
||||
|
||||
rdata['list'] = data['token']
|
||||
return mw.returnJson(True, 'ok', rdata)
|
||||
return mw.returnJson(False, 'ok')
|
||||
|
||||
|
||||
def syncDataAdd():
|
||||
args = getArgs()
|
||||
data_args = checkArgs(args, ['token'])
|
||||
if not data_args[0]:
|
||||
return data_args[1]
|
||||
|
||||
add_token = args['token']
|
||||
data = getConfigData()
|
||||
|
||||
if 'token' in data and data['token']:
|
||||
if not add_token in data['token']:
|
||||
data['token'].append(add_token)
|
||||
else:
|
||||
data['token'] = [add_token]
|
||||
writeConf(data)
|
||||
|
||||
return mw.returnJson(True, '保存成功!')
|
||||
|
||||
|
||||
def restartSup():
|
||||
cmd = 'python3 plugins/supervisor/index.py restart'
|
||||
mw.execShell(cmd)
|
||||
|
||||
|
||||
def restartSupDst(name):
|
||||
cmd = 'python3 plugins/supervisor/index.py restart_job {"name":"' + \
|
||||
name + '","status":"stop"}'
|
||||
mw.execShell(cmd)
|
||||
|
||||
|
||||
def syncDataAddTaskUninstall():
|
||||
sup_path = mw.getServerDir() + '/supervisor'
|
||||
if not os.path.exists(sup_path):
|
||||
return mw.returnJson(False, '需要安装并启动supervisor插件')
|
||||
|
||||
name = "ct_task"
|
||||
sup_task_dst = sup_path + '/conf.d/' + name + '.ini'
|
||||
if os.path.exists(sup_task_dst):
|
||||
mw.execShell('rm -rf ' + sup_task_dst)
|
||||
|
||||
restartSup()
|
||||
return mw.returnJson(True, '删除同步数据任务成功!')
|
||||
|
||||
|
||||
def syncDataAddTaskInstall():
|
||||
sup_path = mw.getServerDir() + '/supervisor'
|
||||
if not os.path.exists(sup_path):
|
||||
return mw.returnJson(False, '需要安装并启动supervisor插件')
|
||||
|
||||
name = "ct_task"
|
||||
sup_task_tpl = getPluginDir() + '/conf/sup_task.tpl'
|
||||
sup_task_dst = sup_path + '/conf.d/' + name + '.ini'
|
||||
content = mw.readFile(sup_task_tpl)
|
||||
content = content.replace(
|
||||
'{$RUN_ROOT}', mw.getServerDir() + '/mdserver-web')
|
||||
content = content.replace(
|
||||
'{$SUP_ROOT}', sup_path)
|
||||
content = content.replace(
|
||||
'{$NAME}', name)
|
||||
|
||||
mw.writeFile(sup_task_dst, content)
|
||||
restartSup()
|
||||
return mw.returnJson(True, '添加同步数据任务成功!')
|
||||
|
||||
|
||||
def syncDataAddTask():
|
||||
args = getArgs()
|
||||
data_args = checkArgs(args, ['check'])
|
||||
if not data_args[0]:
|
||||
return data_args[1]
|
||||
|
||||
if args['check'] == "0":
|
||||
return syncDataAddTaskUninstall()
|
||||
return syncDataAddTaskInstall()
|
||||
|
||||
|
||||
def syncDataDelete():
|
||||
args = getArgs()
|
||||
data_args = checkArgs(args, ['token'])
|
||||
if not data_args[0]:
|
||||
return data_args[1]
|
||||
|
||||
del_token = args['token']
|
||||
data = getConfigData()
|
||||
|
||||
if 'token' in data:
|
||||
data['token'].remove(del_token)
|
||||
writeConf(data)
|
||||
|
||||
return mw.returnJson(True, '删除成功!')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'status':
|
||||
print(status())
|
||||
elif func == 'start':
|
||||
print(start())
|
||||
elif func == 'stop':
|
||||
print(stop())
|
||||
elif func == 'restart':
|
||||
print(op())
|
||||
elif func == 'reload':
|
||||
print(op())
|
||||
elif func == 'get_db_conf':
|
||||
print(getDbConf())
|
||||
elif func == 'set_db_conf':
|
||||
print(setDbConf())
|
||||
elif func == 'get_user_conf':
|
||||
print(getUserConf())
|
||||
elif func == 'set_user_conf':
|
||||
print(setUserConf())
|
||||
elif func == 'sync_data_list':
|
||||
print(syncDataList())
|
||||
elif func == 'sync_data_add':
|
||||
print(syncDataAdd())
|
||||
elif func == 'sync_data_delete':
|
||||
print(syncDataDelete())
|
||||
elif func == 'sync_data_add_task':
|
||||
print(syncDataAddTask())
|
||||
else:
|
||||
print('error')
|
||||
36
plugins/cryptocurrency_trade/info.json
Executable file
36
plugins/cryptocurrency_trade/info.json
Executable file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"hook":[
|
||||
{
|
||||
"tag":"menu",
|
||||
"menu": {
|
||||
"title":"量化交易",
|
||||
"name":"cryptocurrency_trade",
|
||||
"path":"static/html/index.html",
|
||||
"css_path":"static/css/cryptocurrency_trade.css",
|
||||
"js_path":"static/js/cryptocurrency_trade.js"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag":"global_static",
|
||||
"global_static": {
|
||||
"title":"量化交易",
|
||||
"name":"cryptocurrency_trade",
|
||||
"css_path":"static/css/ico.css"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ps": "基于CCXT的数字货币量化交易插件",
|
||||
"name": "cryptocurrency_trade",
|
||||
"title": "量化交易",
|
||||
"versions": ["1.0"],
|
||||
"tip": "soft",
|
||||
"checks": "server/cryptocurrency_trade",
|
||||
"path":"server/cryptocurrency_trade",
|
||||
"author": "midoks",
|
||||
"date": "2022-02-25",
|
||||
"home": "https://github.com/ccxt/ccxt",
|
||||
"type": "量化交易",
|
||||
"shell": "install.sh",
|
||||
"pid": "5",
|
||||
"sort": 7
|
||||
}
|
||||
63
plugins/cryptocurrency_trade/install.sh
Executable file
63
plugins/cryptocurrency_trade/install.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
|
||||
# cd /www/server/mdserver-web/plugins/cryptocurrency_trade && bash install.sh install
|
||||
|
||||
curPath=`pwd`
|
||||
rootPath=$(dirname "$curPath")
|
||||
rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
|
||||
VERSION=$2
|
||||
|
||||
# pip3 install ccxt
|
||||
if [ -f ${rootPath}/bin/activate ];then
|
||||
. ${rootPath}/bin/activate
|
||||
fi
|
||||
pip3 install ccxt
|
||||
pip3 install pandas
|
||||
pip3 install pandas_ta
|
||||
pip3 install pyTelegramBotAPI
|
||||
pip3 install catalyst
|
||||
pip3 install matplotlib==3.2.2
|
||||
|
||||
|
||||
Install_App()
|
||||
{
|
||||
echo '正在安装脚本文件...'
|
||||
mkdir -p $serverPath/source/cryptocurrency_trade
|
||||
mkdir -p $serverPath/cryptocurrency_trade
|
||||
echo "${VERSION}" > $serverPath/cryptocurrency_trade/version.pl
|
||||
|
||||
if [ ! -f $serverPath/source/cryptocurrency_trade/ta-lib-0.4.0-src.tar.gz ];then
|
||||
wget -O $serverPath/source/cryptocurrency_trade/ta-lib-0.4.0-src.tar.gz https://sourceforge.net/projects/ta-lib/files/ta-lib/0.4.0/ta-lib-0.4.0-src.tar.gz
|
||||
fi
|
||||
|
||||
if [ ! -d $serverPath/source/cryptocurrency_trade/ta-lib ];then
|
||||
cd $serverPath/source/cryptocurrency_trade/ta-lib && tar -xzf ta-lib-0.4.0-src.tar.gz
|
||||
cd ta-lib && ./configure --prefix=/usr && make && make install
|
||||
rm -rf $serverPath/source/cryptocurrency_trade/ta-lib
|
||||
|
||||
pip3 install ta-lib
|
||||
fi
|
||||
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/cryptocurrency_trade/index.py start
|
||||
echo '安装完成'
|
||||
|
||||
}
|
||||
|
||||
Uninstall_App()
|
||||
{
|
||||
rm -rf $serverPath/cryptocurrency_trade
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/cryptocurrency_trade/index.py stop
|
||||
echo "卸载完成"
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
fi
|
||||
@@ -0,0 +1,11 @@
|
||||
.screen-all {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.s-right .panel-default{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.s-left .panel-default{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
11
plugins/cryptocurrency_trade/static/css/ico.css
Normal file
11
plugins/cryptocurrency_trade/static/css/ico.css
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
/* menu start */
|
||||
.menu .current .menu_plugin_cryptocurrency_trade:hover {
|
||||
background-image: url("/plugins/file?name=cryptocurrency_trade&f=ico.png");
|
||||
}
|
||||
|
||||
.menu .menu_plugin_cryptocurrency_trade {
|
||||
background-image: url("/plugins/file?name=cryptocurrency_trade&f=ico.png");
|
||||
}
|
||||
|
||||
/* menu end */
|
||||
65
plugins/cryptocurrency_trade/static/html/index.html
Normal file
65
plugins/cryptocurrency_trade/static/html/index.html
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
<div class="main-content">
|
||||
<div class="screen-all">
|
||||
<div class="content-screen safe bgw mtb15 ptb10 radius4 row" style="height: 300px;">
|
||||
<!-- start -->
|
||||
|
||||
<div class="s-left col-md-8" style="padding-left: 2.5px;padding-right: 5px; overflow-y: scroll;height: 400px;">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">订单数据</div>
|
||||
<div class="panel-body">Panel content</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">交易图表</div>
|
||||
<div class="panel-body" style="padding: 0px;">
|
||||
<div></div>
|
||||
<div id="k_echarts" style="width: auto;height:400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">策略交易日志</div>
|
||||
<div class="panel-body" style="padding: 0px;">
|
||||
<pre id="strategy_log" style="border:none;font-size:12px;white-space: pre;margin: 0px;width: auto;height: 200px;background-color: #333;color:#fff; padding:0 5px">当前没有日志.</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="s-right col-md-4" style="padding-left: 2.5px;padding-right: 5px; overflow-y: scroll;height: 400px;">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">策略配置</div>
|
||||
<div class="panel-body divtable pd15 relative" style="padding: 2px 2px 5px;">
|
||||
<table class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20">序列</th>
|
||||
<th width="120">说明</th>
|
||||
<th width="10">状态</th>
|
||||
<th style="text-align: right;" width="50">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="strategy_list"></tbody>
|
||||
</table>
|
||||
<div class="dataTables_paginate paging_bootstrap pagination">
|
||||
<ul id="strategy_list_page" class="page"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">数据源日志</div>
|
||||
<div class="panel-body" style="padding: 0px;">
|
||||
<pre id="datasource_log" style="border:none;font-size:12px;white-space: pre;margin: 0px;width: auto;height: 200px;background-color: #333;color:#fff; padding:0 5px">当前没有日志.</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- end -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
964
plugins/cryptocurrency_trade/static/js/cryptocurrency_trade.js
Normal file
964
plugins/cryptocurrency_trade/static/js/cryptocurrency_trade.js
Normal file
@@ -0,0 +1,964 @@
|
||||
|
||||
function appPost(method,args,callback, title){
|
||||
var _args = null;
|
||||
if (typeof(args) == 'string'){
|
||||
_args = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
_args = JSON.stringify(args);
|
||||
}
|
||||
|
||||
var _title = '正在获取...';
|
||||
if (typeof(title) != 'undefined'){
|
||||
_title = title;
|
||||
}
|
||||
|
||||
var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 });
|
||||
$.post('/plugins/run', {name:'cryptocurrency_trade', func:method, args:_args}, function(data) {
|
||||
layer.close(loadT);
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
function appPostN(method,args,callback, title){
|
||||
|
||||
var _args = null;
|
||||
if (typeof(args) == 'string'){
|
||||
_args = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
_args = JSON.stringify(args);
|
||||
}
|
||||
|
||||
var _title = '正在获取...';
|
||||
if (typeof(title) != 'undefined'){
|
||||
_title = title;
|
||||
}
|
||||
$.post('/plugins/run', {name:'cryptocurrency_trade', func:method, args:_args}, function(data) {
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
function appAsyncPost(method,args){
|
||||
var _args = null;
|
||||
if (typeof(args) == 'string'){
|
||||
_args = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
_args = JSON.stringify(args);
|
||||
}
|
||||
|
||||
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
|
||||
return syncPost('/plugins/run', {name:'cryptocurrency_trade', func:method, args:_args});
|
||||
}
|
||||
|
||||
|
||||
function appPostCallbak(method, args,callback, script){
|
||||
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
|
||||
|
||||
var req_data = {};
|
||||
req_data['name'] = 'cryptocurrency_trade';
|
||||
req_data['func'] = method;
|
||||
|
||||
if (typeof(script) != 'undefined'){
|
||||
req_data['script'] = script;
|
||||
}
|
||||
|
||||
|
||||
if (typeof(args) == 'string'){
|
||||
req_data['args'] = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
req_data['args'] = JSON.stringify(args);
|
||||
}
|
||||
|
||||
$.post('/plugins/callback', req_data, function(data) {
|
||||
layer.close(loadT);
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
|
||||
function appPostCallbakNoMsg(method, args,callback, script){
|
||||
|
||||
var req_data = {};
|
||||
req_data['name'] = 'cryptocurrency_trade';
|
||||
req_data['func'] = method;
|
||||
|
||||
if (typeof(script) != 'undefined'){
|
||||
req_data['script'] = script;
|
||||
} else {
|
||||
req_data['script'] = req_data['name'];
|
||||
}
|
||||
|
||||
|
||||
if (typeof(args) == 'string'){
|
||||
req_data['args'] = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
req_data['args'] = JSON.stringify(args);
|
||||
}
|
||||
|
||||
$.post('/plugins/callback', req_data, function(data) {
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
|
||||
function dbConf(){
|
||||
appPost('get_db_conf','',function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
// console.log(rdata);
|
||||
var db_host = '127.0.0.1';
|
||||
var db_port = '3306';
|
||||
var db_name = 'cryptocurrency_trade';
|
||||
var db_user = 'cryptocurrency_trade';
|
||||
var db_pass = 'cryptocurrency_trade';
|
||||
if(rdata['status']){
|
||||
db_data = rdata['data'];
|
||||
db_host = db_data['db_host'];
|
||||
db_port = db_data['db_port'];
|
||||
db_name = db_data['db_name'];
|
||||
db_user = db_data['db_user'];
|
||||
db_pass = db_data['db_pass'];
|
||||
}
|
||||
|
||||
var mlist = '';
|
||||
mlist += '<p><span>数据库地址</span><input style="width: 250px;" class="bt-input-text mr5" name="db_host" value="'+db_host+'" type="text"></p>'
|
||||
mlist += '<p><span>数据库端口</span><input style="width: 250px;" class="bt-input-text mr5" name="db_port" value="'+db_port+'" type="text"></p>'
|
||||
mlist += '<p><span>数据库名称</span><input style="width: 250px;" class="bt-input-text mr5" name="db_name" value="'+db_name+'" type="text"></p>'
|
||||
mlist += '<p><span>用户名</span><input style="width: 250px;" class="bt-input-text mr5" name="db_user" value="'+db_user+'" type="text"></p>'
|
||||
mlist += '<p><span>密码</span><input style="width: 250px;" class="bt-input-text mr5" name="db_pass" value="'+db_pass+'" type="text"></p>'
|
||||
|
||||
var option = '<style>.conf_p p{margin-bottom: 2px}</style>\
|
||||
<div class="conf_p" style="margin-bottom:0">\
|
||||
' + mlist + '\
|
||||
<div style="margin-top:10px; padding-right:15px" class="text-right">\
|
||||
<button class="btn btn-success btn-sm" onclick="submitDbConf()">保存</button>\
|
||||
</div>\
|
||||
</div>';
|
||||
$(".soft-man-con").html(option);
|
||||
});
|
||||
}
|
||||
|
||||
function submitDbConf(){
|
||||
|
||||
var pull_data = {};
|
||||
|
||||
pull_data['db_host'] = $('input[name="db_host"]').val();
|
||||
pull_data['db_port'] = $('input[name="db_port"]').val();
|
||||
pull_data['db_name'] = $('input[name="db_name"]').val();
|
||||
pull_data['db_user'] = $('input[name="db_user"]').val();
|
||||
pull_data['db_pass'] = $('input[name="db_pass"]').val();
|
||||
|
||||
appPost('set_db_conf',pull_data,function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function userConf(){
|
||||
appPost('get_user_conf','',function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
var app_key = 'app_key';
|
||||
var secret = 'secret';
|
||||
var password = 'password';
|
||||
var uid = 'uid';
|
||||
var exchange = 'okex';
|
||||
if(rdata['status']){
|
||||
db_data = rdata['data'];
|
||||
app_key = db_data['app_key'];
|
||||
secret = db_data['secret'];
|
||||
password = db_data['password'];
|
||||
uid = db_data['uid'];
|
||||
exchange = db_data['exchange'];;
|
||||
}
|
||||
|
||||
var mlist = '';
|
||||
mlist += '<p><span>交易所</span><input style="width: 250px;" class="bt-input-text mr5" name="exchange" value="'+exchange+'" type="text"><font>必填写[okex, binance]</font></p>'
|
||||
mlist += '<p><span>apiKey</span><input style="width: 250px;" class="bt-input-text mr5" name="app_key" value="'+app_key+'" type="text"><font>必填写</font></p>'
|
||||
mlist += '<p><span>secret</span><input style="width: 250px;" class="bt-input-text mr5" name="secret" value="'+secret+'" type="text"><font>必填写</font></p>'
|
||||
mlist += '<p><span>password</span><input style="width: 250px;" class="bt-input-text mr5" name="password" value="'+password+'" type="text"><font>根据情况填写</font></p>'
|
||||
mlist += '<p><span>uid</span><input style="width: 250px;" class="bt-input-text mr5" name="uid" value="'+uid+'" type="text"><font>根据情况填写</font></p>'
|
||||
|
||||
var option = '<style>.conf_p p{margin-bottom: 2px}</style>\
|
||||
<div class="conf_p" style="margin-bottom:0">\
|
||||
' + mlist + '\
|
||||
<div style="margin-top:10px; padding-right:15px" class="text-right">\
|
||||
<button class="btn btn-success btn-sm" onclick="submitUserConf()">保存</button>\
|
||||
</div>\
|
||||
</div>';
|
||||
$(".soft-man-con").html(option);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function submitUserConf(){
|
||||
var pull_data = {};
|
||||
|
||||
pull_data['app_key'] = $('input[name="app_key"]').val();
|
||||
pull_data['secret'] = $('input[name="secret"]').val();
|
||||
pull_data['password'] = $('input[name="password"]').val();
|
||||
pull_data['uid'] = $('input[name="uid"]').val();
|
||||
pull_data['exchange'] = $('input[name="exchange"]').val();
|
||||
|
||||
appPost('set_user_conf',pull_data,function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']});
|
||||
});
|
||||
}
|
||||
|
||||
function syncDataList(){
|
||||
appPost('sync_data_list', {}, function(data){
|
||||
var rdata = $.parseJSON(data.data);
|
||||
|
||||
|
||||
var list = '';
|
||||
if (rdata['status']){
|
||||
var dlist = rdata['data']['list'];
|
||||
for(i in dlist){
|
||||
|
||||
list += '<tr>';
|
||||
list += '<td>' + dlist[i] +'</td>';
|
||||
list += '<td style="text-align:right">' +
|
||||
'<a href="javascript:;" class="btlink" onclick="syncDataDelete(\''+dlist[i]+'\')" title="删除">删除</a>' +
|
||||
'</td>';
|
||||
list += '</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if( list == '' ){
|
||||
list = "<tr><td colspan='2'>当前没有数据</td></tr>";
|
||||
}
|
||||
|
||||
var task_status = rdata['data']['task_status'];
|
||||
var task_status_check = '';
|
||||
if (task_status){
|
||||
task_status_check = 'checked';
|
||||
}
|
||||
|
||||
var con = '<div class="safe bgw">\
|
||||
<div>\
|
||||
<button onclick="syncDataAdd()" title="添加币种" class="btn btn-success btn-sm" type="button" style="margin-right: 5px;">添加币种</button>\
|
||||
<div class="ss-text pull-left mr50">\
|
||||
<em>是否启动</em>\
|
||||
<div class="ssh-item">\
|
||||
<input class="btswitch btswitch-ios" id="add_task" type="checkbox" '+task_status_check+'>\
|
||||
<label class="btswitch-btn" for="add_task" onclick="syncDataAddTask()"></label>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="divtable mtb10">\
|
||||
<div class="tablescroll">\
|
||||
<table id="DataBody" class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0" style="border: 0 none;">\
|
||||
<thead>\
|
||||
<th>名称</th>\
|
||||
<th style="text-align:right;">操作</th></tr>\
|
||||
</thead>\
|
||||
<tbody>'+ list +'</tbody>\
|
||||
</table>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>';
|
||||
|
||||
con += '<div class="code">\
|
||||
<span>详细如下:</span>\
|
||||
<span>*:添加同步的币种,都小写,以USDT为本币同步数据。</span>\
|
||||
<span>*:需要提前安装supervisor插件。</span>\
|
||||
</div>'
|
||||
|
||||
$(".soft-man-con").html(con);
|
||||
$('#databasePage').html(rdata.page);
|
||||
});
|
||||
}
|
||||
|
||||
function syncDataAddTask(){
|
||||
var at_check = $('#add_task').prop('checked');
|
||||
appPost("sync_data_add_task", {'check':at_check?'0':'1'}, function(data){
|
||||
rdata = $.parseJSON(data.data);
|
||||
|
||||
showMsg(rdata.msg,function(){
|
||||
if (rdata.status){
|
||||
syncDataList();
|
||||
}
|
||||
},{icon:rdata.status?1:2});
|
||||
});
|
||||
}
|
||||
|
||||
function syncDataDelete(name){
|
||||
appPost("sync_data_delete", {"token":name}, function(data){
|
||||
rdata = $.parseJSON(data.data);
|
||||
|
||||
showMsg(rdata.msg,function(){
|
||||
if (rdata.status){
|
||||
syncDataList();
|
||||
}
|
||||
},{icon:rdata.status?1:2});
|
||||
});
|
||||
}
|
||||
|
||||
function syncDataAdd() {
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: '500px',
|
||||
title: '添加同步数据',
|
||||
closeBtn: 2,
|
||||
shift: 0,
|
||||
shadeClose: false,
|
||||
btn: ['确定', '取消'],
|
||||
content: "<div class='bt_conter bt-form pd15' style='height:auto;width:100%;'>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>名称</span>\
|
||||
<div class='info-r c4'>\
|
||||
<input id='name' class='bt-input-text' type='text' name='name' placeholder='请输入名称' style='width:270px' />\
|
||||
</div>\
|
||||
</div>\
|
||||
<ul class='help-info-text c7' style='padding-left: 29px;margin-top:5px;'>\
|
||||
<li style='color:#F00'>注意:币种名称用小写!</li>\
|
||||
</ul>\
|
||||
</div>",
|
||||
yes: function(index, layero){
|
||||
|
||||
var token = $('input[name="name"]').val();
|
||||
appPost("sync_data_add", {"token":token}, function(data){
|
||||
rdata = $.parseJSON(data.data);
|
||||
showMsg(rdata.msg,function(){
|
||||
if (rdata.status){
|
||||
layer.close(index);
|
||||
syncDataList();
|
||||
}
|
||||
},{icon:rdata.status?1:2});
|
||||
})
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function onlineEditStrategyFile(k, f, tag) {
|
||||
if(k != 0) {
|
||||
var l = $("#PathPlace input").val();
|
||||
var h = $("#textBody").val();
|
||||
var a = $("select[name=encoding]").val();
|
||||
var loadT = layer.msg("正在保存中...", {icon: 16,time: 0});
|
||||
appPostCallbakNoMsg('save_body',{'data':h,'path':f,'encoding':a,"tag":tag}, function(data){
|
||||
var rdata = data.data;
|
||||
showMsg(rdata.msg, function(){
|
||||
if (rdata.status){
|
||||
layer.close(loadT);
|
||||
}
|
||||
},{icon: rdata.status ? 1 : 2});
|
||||
});
|
||||
return
|
||||
}
|
||||
var e = layer.msg("正在读取文件,请稍候...", {icon: 16,time: 0});
|
||||
var g = f.split(".");
|
||||
var b = g[g.length - 1];
|
||||
var d;
|
||||
switch(b) {
|
||||
case "html":
|
||||
var j = {
|
||||
name: "htmlmixed",
|
||||
scriptTypes: [{
|
||||
matches: /\/x-handlebars-template|\/x-mustache/i,
|
||||
mode: null
|
||||
}, {
|
||||
matches: /(text|application)\/(x-)?vb(a|script)/i,
|
||||
mode: "vbscript"
|
||||
}]
|
||||
};
|
||||
d = j;
|
||||
break;
|
||||
case "htm":
|
||||
var j = {
|
||||
name: "htmlmixed",
|
||||
scriptTypes: [{
|
||||
matches: /\/x-handlebars-template|\/x-mustache/i,
|
||||
mode: null
|
||||
}, {
|
||||
matches: /(text|application)\/(x-)?vb(a|script)/i,
|
||||
mode: "vbscript"
|
||||
}]
|
||||
};
|
||||
d = j;
|
||||
break;
|
||||
case "js":
|
||||
d = "text/javascript";
|
||||
break;
|
||||
case "json":
|
||||
d = "application/ld+json";
|
||||
break;
|
||||
case "css":
|
||||
d = "text/css";
|
||||
break;
|
||||
case "php":
|
||||
d = "application/x-httpd-php";
|
||||
break;
|
||||
case "tpl":
|
||||
d = "application/x-httpd-php";
|
||||
break;
|
||||
case "xml":
|
||||
d = "application/xml";
|
||||
break;
|
||||
case "sql":
|
||||
d = "text/x-sql";
|
||||
break;
|
||||
case "conf":
|
||||
d = "text/x-nginx-conf";
|
||||
break;
|
||||
default:
|
||||
var j = {
|
||||
name: "htmlmixed",
|
||||
scriptTypes: [{
|
||||
matches: /\/x-handlebars-template|\/x-mustache/i,
|
||||
mode: null
|
||||
}, {
|
||||
matches: /(text|application)\/(x-)?vb(a|script)/i,
|
||||
mode: "vbscript"
|
||||
}]
|
||||
};
|
||||
d = j
|
||||
}
|
||||
$.post("/files/get_body", "path=" + encodeURIComponent(f), function(s) {
|
||||
if(s.status === false){
|
||||
layer.msg(s.msg,{icon:5});
|
||||
return;
|
||||
}
|
||||
layer.close(e);
|
||||
var u = ["utf-8", "GBK", "GB2312", "BIG5"];
|
||||
var n = "";
|
||||
var m = "";
|
||||
var o = "";
|
||||
for(var p = 0; p < u.length; p++) {
|
||||
m = s.data.encoding == u[p] ? "selected" : "";
|
||||
n += '<option value="' + u[p] + '" ' + m + ">" + u[p] + "</option>";
|
||||
}
|
||||
var code_mirror = null;
|
||||
var r = layer.open({
|
||||
type: 1,
|
||||
shift: 5,
|
||||
closeBtn: 1,
|
||||
area: ["90%", "90%"],
|
||||
title: "在线编辑[" + f + "]",
|
||||
btn:['保存','关闭'],
|
||||
content: '<form class="bt-form pd20">\
|
||||
<div class="line">\
|
||||
<p style="color:red;margin-bottom:10px">提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!\
|
||||
<select class="bt-input-text" name="encoding" style="width: 74px;position: absolute;top: 31px;right: 19px;height: 22px;z-index: 9999;border-radius: 0;">' + n + '</select>\
|
||||
</p>\
|
||||
<textarea class="mCustomScrollbar bt-input-text" id="textBody" style="width:100%;margin:0 auto;line-height: 1.8;position: relative;top: 10px;" value="" />\
|
||||
</div>\
|
||||
</form>',
|
||||
success:function(){
|
||||
$("#textBody").text(s.data.data);
|
||||
var q = $(window).height() * 0.9;
|
||||
$("#textBody").height(q - 160);
|
||||
code_mirror = CodeMirror.fromTextArea(document.getElementById("textBody"), {
|
||||
extraKeys: {
|
||||
"Ctrl-F": "findPersistent",
|
||||
"Ctrl-H": "replaceAll",
|
||||
"Ctrl-S": function() {
|
||||
$("#textBody").text(code_mirror.getValue());
|
||||
onlineEditStrategyFile(2, f,tag);
|
||||
},
|
||||
"Cmd-S":function() {
|
||||
$("#textBody").text(code_mirror.getValue());
|
||||
onlineEditStrategyFile(2, f,tag);
|
||||
},
|
||||
},
|
||||
mode: d,
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
matchtags: true,
|
||||
autoMatchParens: true
|
||||
});
|
||||
code_mirror.focus();
|
||||
code_mirror.setSize("auto", q - 150);
|
||||
$(window).resize(function(){
|
||||
var q = $(window).height() * 0.9;
|
||||
code_mirror.setSize("auto", q - 150);
|
||||
});
|
||||
},
|
||||
yes:function(){
|
||||
$("#textBody").text(code_mirror.getValue());
|
||||
onlineEditStrategyFile(1, f, tag);
|
||||
}
|
||||
});
|
||||
//////////////////
|
||||
},'json');
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// 大屏页功能 --------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function changeDivH(){
|
||||
var l = $(window).height();
|
||||
$('.content-screen').css('height',l-80);
|
||||
$('.s-right').css('height',l-80-10);
|
||||
$('.s-left').css('height',l-80-10);
|
||||
}
|
||||
|
||||
|
||||
function dataSourceLog(){
|
||||
appPostCallbakNoMsg('get_datasource_logs',{}, function(rdata){
|
||||
$('#datasource_log').html(rdata.data);
|
||||
});
|
||||
}
|
||||
|
||||
function dataStrategyLog(){
|
||||
appPostCallbakNoMsg('get_strategy_logs',{}, function(rdata){
|
||||
$('#strategy_log').html(rdata.data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// $('#strategy_list').find('span[data-id="01"]')
|
||||
function setStrategyStatus(id,status){
|
||||
appPostCallbakNoMsg('set_strategy_status',{"id":id,"status":status}, function(data){
|
||||
var rdata = data.data;
|
||||
showMsg(rdata.msg,function(){
|
||||
if (rdata.status){
|
||||
if (status == 'start'){
|
||||
$('#strategy_list').find('tr[data-id="'+id+'"] td span').removeClass('glyphicon-pause').addClass('glyphicon-play').css('color','#20a53a');
|
||||
} else{
|
||||
$('#strategy_list').find('tr[data-id="'+id+'"] td span').removeClass('glyphicon-play').addClass('glyphicon-pause').css('color','red');
|
||||
}
|
||||
}
|
||||
},{icon:rdata.status?1:2},2000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function setStrategyRestart(id){
|
||||
appPostCallbakNoMsg('set_strategy_restart',{"id":id}, function(data){
|
||||
console.log(data);
|
||||
var rdata = data.data;
|
||||
showMsg(rdata.msg,function(){
|
||||
if (rdata.status){
|
||||
}
|
||||
},{icon:rdata.status?1:2},2000);
|
||||
});
|
||||
}
|
||||
|
||||
function setStrategyEdit(id){
|
||||
appPostCallbakNoMsg('get_strategy_path',{"id":id}, function(data){
|
||||
onlineEditStrategyFile(0,data.data.msg,id);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getStrategyList(p=1){
|
||||
appPostCallbakNoMsg('get_strategy_list',{'page':p}, function(rdata){
|
||||
// console.log(rdata);
|
||||
|
||||
ldata = rdata.data.data;
|
||||
var tBody = '';
|
||||
|
||||
for (var i = 0; i < ldata.length; i++) {
|
||||
tBody += '<tr data-id="'+ldata[i]['id']+'">'
|
||||
tBody += '<td>'+ldata[i]['id']+'</td>';
|
||||
tBody += '<td>'+ldata[i]['name']+'</td>';
|
||||
|
||||
if (ldata[i]['status'] == 'start'){
|
||||
tBody += '<td><span style="color:#20a53a;cursor: pointer;" class="strategy_status glyphicon glyphicon-play"></span></td>';
|
||||
} else{
|
||||
tBody += '<td><span style="color:red;cursor: pointer;" class="strategy_status glyphicon glyphicon-pause"></span></td>';
|
||||
}
|
||||
|
||||
tBody += "<td style='text-align: right;'><a class='btlink restart'>重启</a> | <a class='btlink edit'>编辑</a></td>";
|
||||
tBody +='<tr>';
|
||||
}
|
||||
|
||||
// console.log(tBody);
|
||||
$('#strategy_list').html(tBody);
|
||||
$('#strategy_list_page').html(rdata.data.list);
|
||||
|
||||
|
||||
$('#strategy_list .strategy_status').click(function(){
|
||||
var id = $(this).parent().parent().data('id');
|
||||
var status = 'stop';
|
||||
if ($(this).hasClass('glyphicon-pause')){
|
||||
status = 'start';
|
||||
}
|
||||
setStrategyStatus(id,status);
|
||||
});
|
||||
|
||||
$('#strategy_list .restart').click(function(){
|
||||
var id = $(this).parent().parent().data('id');
|
||||
setStrategyRestart(id);
|
||||
});
|
||||
|
||||
$('#strategy_list .edit').click(function(){
|
||||
var id = $(this).parent().parent().data('id');
|
||||
setStrategyEdit(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function calcKLineChats(){
|
||||
|
||||
var chartDom = document.getElementById('k_echarts');
|
||||
var myChart = echarts.init(chartDom);
|
||||
var option;
|
||||
|
||||
const upColor = '#ec0000';
|
||||
const upBorderColor = '#8A0000';
|
||||
const downColor = '#00da3c';
|
||||
const downBorderColor = '#008F28';
|
||||
// Each item: open,close,lowest,highest
|
||||
const data0 = splitData([
|
||||
['2013/1/24', 2320.26, 2320.26, 2287.3, 2362.94],
|
||||
['2013/1/25', 2300, 2291.3, 2288.26, 2308.38],
|
||||
['2013/1/28', 2295.35, 2346.5, 2295.35, 2346.92],
|
||||
['2013/1/29', 2347.22, 2358.98, 2337.35, 2363.8],
|
||||
['2013/1/30', 2360.75, 2382.48, 2347.89, 2383.76],
|
||||
['2013/1/31', 2383.43, 2385.42, 2371.23, 2391.82],
|
||||
['2013/2/1', 2377.41, 2419.02, 2369.57, 2421.15],
|
||||
['2013/2/4', 2425.92, 2428.15, 2417.58, 2440.38],
|
||||
['2013/2/5', 2411, 2433.13, 2403.3, 2437.42],
|
||||
['2013/2/6', 2432.68, 2434.48, 2427.7, 2441.73],
|
||||
['2013/2/7', 2430.69, 2418.53, 2394.22, 2433.89],
|
||||
['2013/2/8', 2416.62, 2432.4, 2414.4, 2443.03],
|
||||
['2013/2/18', 2441.91, 2421.56, 2415.43, 2444.8],
|
||||
['2013/2/19', 2420.26, 2382.91, 2373.53, 2427.07],
|
||||
['2013/2/20', 2383.49, 2397.18, 2370.61, 2397.94],
|
||||
['2013/2/21', 2378.82, 2325.95, 2309.17, 2378.82],
|
||||
['2013/2/22', 2322.94, 2314.16, 2308.76, 2330.88],
|
||||
['2013/2/25', 2320.62, 2325.82, 2315.01, 2338.78],
|
||||
['2013/2/26', 2313.74, 2293.34, 2289.89, 2340.71],
|
||||
['2013/2/27', 2297.77, 2313.22, 2292.03, 2324.63],
|
||||
['2013/2/28', 2322.32, 2365.59, 2308.92, 2366.16],
|
||||
['2013/3/1', 2364.54, 2359.51, 2330.86, 2369.65],
|
||||
['2013/3/4', 2332.08, 2273.4, 2259.25, 2333.54],
|
||||
['2013/3/5', 2274.81, 2326.31, 2270.1, 2328.14],
|
||||
['2013/3/6', 2333.61, 2347.18, 2321.6, 2351.44],
|
||||
['2013/3/7', 2340.44, 2324.29, 2304.27, 2352.02],
|
||||
['2013/3/8', 2326.42, 2318.61, 2314.59, 2333.67],
|
||||
['2013/3/11', 2314.68, 2310.59, 2296.58, 2320.96],
|
||||
['2013/3/12', 2309.16, 2286.6, 2264.83, 2333.29],
|
||||
['2013/3/13', 2282.17, 2263.97, 2253.25, 2286.33],
|
||||
['2013/3/14', 2255.77, 2270.28, 2253.31, 2276.22],
|
||||
['2013/3/15', 2269.31, 2278.4, 2250, 2312.08],
|
||||
['2013/3/18', 2267.29, 2240.02, 2239.21, 2276.05],
|
||||
['2013/3/19', 2244.26, 2257.43, 2232.02, 2261.31],
|
||||
['2013/3/20', 2257.74, 2317.37, 2257.42, 2317.86],
|
||||
['2013/3/21', 2318.21, 2324.24, 2311.6, 2330.81],
|
||||
['2013/3/22', 2321.4, 2328.28, 2314.97, 2332],
|
||||
['2013/3/25', 2334.74, 2326.72, 2319.91, 2344.89],
|
||||
['2013/3/26', 2318.58, 2297.67, 2281.12, 2319.99],
|
||||
['2013/3/27', 2299.38, 2301.26, 2289, 2323.48],
|
||||
['2013/3/28', 2273.55, 2236.3, 2232.91, 2273.55],
|
||||
['2013/3/29', 2238.49, 2236.62, 2228.81, 2246.87],
|
||||
['2013/4/1', 2229.46, 2234.4, 2227.31, 2243.95],
|
||||
['2013/4/2', 2234.9, 2227.74, 2220.44, 2253.42],
|
||||
['2013/4/3', 2232.69, 2225.29, 2217.25, 2241.34],
|
||||
['2013/4/8', 2196.24, 2211.59, 2180.67, 2212.59],
|
||||
['2013/4/9', 2215.47, 2225.77, 2215.47, 2234.73],
|
||||
['2013/4/10', 2224.93, 2226.13, 2212.56, 2233.04],
|
||||
['2013/4/11', 2236.98, 2219.55, 2217.26, 2242.48],
|
||||
['2013/4/12', 2218.09, 2206.78, 2204.44, 2226.26],
|
||||
['2013/4/15', 2199.91, 2181.94, 2177.39, 2204.99],
|
||||
['2013/4/16', 2169.63, 2194.85, 2165.78, 2196.43],
|
||||
['2013/4/17', 2195.03, 2193.8, 2178.47, 2197.51],
|
||||
['2013/4/18', 2181.82, 2197.6, 2175.44, 2206.03],
|
||||
['2013/4/19', 2201.12, 2244.64, 2200.58, 2250.11],
|
||||
['2013/4/22', 2236.4, 2242.17, 2232.26, 2245.12],
|
||||
['2013/4/23', 2242.62, 2184.54, 2182.81, 2242.62],
|
||||
['2013/4/24', 2187.35, 2218.32, 2184.11, 2226.12],
|
||||
['2013/4/25', 2213.19, 2199.31, 2191.85, 2224.63],
|
||||
['2013/4/26', 2203.89, 2177.91, 2173.86, 2210.58],
|
||||
['2013/5/2', 2170.78, 2174.12, 2161.14, 2179.65],
|
||||
['2013/5/3', 2179.05, 2205.5, 2179.05, 2222.81],
|
||||
['2013/5/6', 2212.5, 2231.17, 2212.5, 2236.07],
|
||||
['2013/5/7', 2227.86, 2235.57, 2219.44, 2240.26],
|
||||
['2013/5/8', 2242.39, 2246.3, 2235.42, 2255.21],
|
||||
['2013/5/9', 2246.96, 2232.97, 2221.38, 2247.86],
|
||||
['2013/5/10', 2228.82, 2246.83, 2225.81, 2247.67],
|
||||
['2013/5/13', 2247.68, 2241.92, 2231.36, 2250.85],
|
||||
['2013/5/14', 2238.9, 2217.01, 2205.87, 2239.93],
|
||||
['2013/5/15', 2217.09, 2224.8, 2213.58, 2225.19],
|
||||
['2013/5/16', 2221.34, 2251.81, 2210.77, 2252.87],
|
||||
['2013/5/17', 2249.81, 2282.87, 2248.41, 2288.09],
|
||||
['2013/5/20', 2286.33, 2299.99, 2281.9, 2309.39],
|
||||
['2013/5/21', 2297.11, 2305.11, 2290.12, 2305.3],
|
||||
['2013/5/22', 2303.75, 2302.4, 2292.43, 2314.18],
|
||||
['2013/5/23', 2293.81, 2275.67, 2274.1, 2304.95],
|
||||
['2013/5/24', 2281.45, 2288.53, 2270.25, 2292.59],
|
||||
['2013/5/27', 2286.66, 2293.08, 2283.94, 2301.7],
|
||||
['2013/5/28', 2293.4, 2321.32, 2281.47, 2322.1],
|
||||
['2013/5/29', 2323.54, 2324.02, 2321.17, 2334.33],
|
||||
['2013/5/30', 2316.25, 2317.75, 2310.49, 2325.72],
|
||||
['2013/5/31', 2320.74, 2300.59, 2299.37, 2325.53],
|
||||
['2013/6/3', 2300.21, 2299.25, 2294.11, 2313.43],
|
||||
['2013/6/4', 2297.1, 2272.42, 2264.76, 2297.1],
|
||||
['2013/6/5', 2270.71, 2270.93, 2260.87, 2276.86],
|
||||
['2013/6/6', 2264.43, 2242.11, 2240.07, 2266.69],
|
||||
['2013/6/7', 2242.26, 2210.9, 2205.07, 2250.63],
|
||||
['2013/6/13', 2190.1, 2148.35, 2126.22, 2190.1]
|
||||
]);
|
||||
function splitData(rawData) {
|
||||
const categoryData = [];
|
||||
const values = [];
|
||||
for (var i = 0; i < rawData.length; i++) {
|
||||
categoryData.push(rawData[i].splice(0, 1)[0]);
|
||||
values.push(rawData[i]);
|
||||
}
|
||||
return {
|
||||
categoryData: categoryData,
|
||||
values: values
|
||||
};
|
||||
}
|
||||
function calculateMA(dayCount) {
|
||||
var result = [];
|
||||
for (var i = 0, len = data0.values.length; i < len; i++) {
|
||||
if (i < dayCount) {
|
||||
result.push('-');
|
||||
continue;
|
||||
}
|
||||
var sum = 0;
|
||||
for (var j = 0; j < dayCount; j++) {
|
||||
sum += +data0.values[i - j][1];
|
||||
}
|
||||
result.push(sum / dayCount);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
option = {
|
||||
title: {
|
||||
text: '上证指数',
|
||||
left: 0
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['日K', 'MA5', 'MA10', 'MA20', 'MA30']
|
||||
},
|
||||
grid: {
|
||||
left: '10%',
|
||||
right: '10%',
|
||||
bottom: '15%'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data0.categoryData,
|
||||
boundaryGap: false,
|
||||
axisLine: { onZero: false },
|
||||
splitLine: { show: false },
|
||||
min: 'dataMin',
|
||||
max: 'dataMax'
|
||||
},
|
||||
yAxis: {
|
||||
scale: true,
|
||||
splitArea: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 50,
|
||||
end: 100
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
type: 'slider',
|
||||
top: '90%',
|
||||
start: 50,
|
||||
end: 100
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '日K',
|
||||
type: 'candlestick',
|
||||
data: data0.values,
|
||||
itemStyle: {
|
||||
color: upColor,
|
||||
color0: downColor,
|
||||
borderColor: upBorderColor,
|
||||
borderColor0: downBorderColor
|
||||
},
|
||||
markPoint: {
|
||||
label: {
|
||||
formatter: function (param) {
|
||||
return param != null ? Math.round(param.value) + '' : '';
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Mark',
|
||||
coord: ['2013/5/31', 2300],
|
||||
value: 2300,
|
||||
itemStyle: {
|
||||
color: 'rgb(41,60,85)'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'highest value',
|
||||
type: 'max',
|
||||
valueDim: 'highest'
|
||||
},
|
||||
{
|
||||
name: 'lowest value',
|
||||
type: 'min',
|
||||
valueDim: 'lowest'
|
||||
},
|
||||
{
|
||||
name: 'average value on close',
|
||||
type: 'average',
|
||||
valueDim: 'close'
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
formatter: function (param) {
|
||||
return param.name + '<br>' + (param.data.coord || '');
|
||||
}
|
||||
}
|
||||
},
|
||||
markLine: {
|
||||
symbol: ['none', 'none'],
|
||||
data: [
|
||||
[
|
||||
{
|
||||
name: 'from lowest to highest',
|
||||
type: 'min',
|
||||
valueDim: 'lowest',
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'max',
|
||||
valueDim: 'highest',
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
name: 'min line on close',
|
||||
type: 'min',
|
||||
valueDim: 'close'
|
||||
},
|
||||
{
|
||||
name: 'max line on close',
|
||||
type: 'max',
|
||||
valueDim: 'close'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MA5',
|
||||
type: 'line',
|
||||
data: calculateMA(5),
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
opacity: 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MA10',
|
||||
type: 'line',
|
||||
data: calculateMA(10),
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
opacity: 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MA20',
|
||||
type: 'line',
|
||||
data: calculateMA(20),
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
opacity: 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MA30',
|
||||
type: 'line',
|
||||
data: calculateMA(30),
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
var tag = $.getUrlParam('tag');
|
||||
if(tag == 'cryptocurrency_trade'){
|
||||
changeDivH();
|
||||
|
||||
// 获取数据源更新日志
|
||||
dataSourceLog();
|
||||
setInterval(function(){
|
||||
dataSourceLog();
|
||||
},3000);
|
||||
|
||||
// 获取策略更新日志
|
||||
dataStrategyLog();
|
||||
setInterval(function(){
|
||||
dataStrategyLog();
|
||||
},5000);
|
||||
|
||||
getStrategyList(1);
|
||||
|
||||
calcKLineChats();
|
||||
}
|
||||
});
|
||||
|
||||
$(window).resize(function(){
|
||||
changeDivH();
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Uninstall_App()
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
|
||||
@@ -37,6 +37,7 @@ class nosqlMemcached():
|
||||
__DB_HOST = '127.0.0.1'
|
||||
__DB_CONN = None
|
||||
__DB_ERR = None
|
||||
__DB_STATUS = None
|
||||
|
||||
__DB_LOCAL = None
|
||||
|
||||
@@ -48,19 +49,36 @@ class nosqlMemcached():
|
||||
|
||||
if self.__DB_HOST in ['127.0.0.1', 'localhost']:
|
||||
mem_path = "{}/memcached".format(mw.getServerDir())
|
||||
if not os.path.exists(mem_path): return False
|
||||
if not os.path.exists(mem_path):
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
|
||||
if not self.__DB_LOCAL:
|
||||
if not self.__config:
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
self.__DB_PORT = int(self.__config['port'])
|
||||
try:
|
||||
self.__DB_CONN = pymemcache.client.base.PooledClient((self.__DB_HOST,self.__DB_PORT), max_pool_size=4)
|
||||
# PooledClient 是惰性连接,探测一次,服务未启动时及时返回 False
|
||||
self.__DB_CONN.version()
|
||||
self.__DB_STATUS = None
|
||||
return self.__DB_CONN
|
||||
except pymemcache.exceptions.MemcacheError:
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
except (ConnectionRefusedError, OSError):
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
except Exception:
|
||||
self.__DB_ERR = mw.getTracebackInfo()
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
|
||||
# 连接状态:not_installed 未安装 / not_running 未启动 / None 正常
|
||||
def get_status(self):
|
||||
return self.__DB_STATUS
|
||||
|
||||
# 获取配置项
|
||||
def get_options(self, get=None):
|
||||
|
||||
@@ -95,11 +113,20 @@ class nosqlMemcachedCtr():
|
||||
instance = nosqlMemcached()
|
||||
return instance
|
||||
|
||||
def _connect(self, sid):
|
||||
obj = self.getInstanceBySid(sid)
|
||||
conn = obj.conn()
|
||||
if conn is False:
|
||||
if obj.get_status() == 'not_installed':
|
||||
return None, mw.returnData(False, '未安装')
|
||||
return None, mw.returnData(False, '未启动')
|
||||
return conn, None
|
||||
|
||||
def getItems(self, args):
|
||||
sid = args['sid']
|
||||
mem_instance = self.getInstanceBySid(sid).conn()
|
||||
if mem_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mem_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
result = {}
|
||||
m_items = mem_instance.stats('items')
|
||||
@@ -122,9 +149,9 @@ class nosqlMemcachedCtr():
|
||||
|
||||
def getKeyList(self, args):
|
||||
sid = args['sid']
|
||||
mem_instance = self.getInstanceBySid(sid).conn()
|
||||
if mem_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mem_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
|
||||
p = 1
|
||||
@@ -194,9 +221,9 @@ class nosqlMemcachedCtr():
|
||||
def delVal(self, args):
|
||||
|
||||
sid = args['sid']
|
||||
mem_instance = self.getInstanceBySid(sid).conn()
|
||||
if mem_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mem_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
key = args['key']
|
||||
mem_instance.delete(key)
|
||||
@@ -205,9 +232,9 @@ class nosqlMemcachedCtr():
|
||||
def setKv(self, args):
|
||||
|
||||
sid = args['sid']
|
||||
mem_instance = self.getInstanceBySid(sid).conn()
|
||||
if mem_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mem_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
key = args['key']
|
||||
val = args['val']
|
||||
@@ -217,9 +244,9 @@ class nosqlMemcachedCtr():
|
||||
|
||||
def clear(self, args):
|
||||
sid = args['sid']
|
||||
mem_instance = self.getInstanceBySid(sid).conn()
|
||||
if mem_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mem_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
mem_instance.flush_all()
|
||||
return mw.returnData(True,'清空成功!')
|
||||
|
||||
@@ -127,6 +127,7 @@ class nosqlMongodb():
|
||||
__DB_HOST = '127.0.0.1'
|
||||
__DB_CONN = None
|
||||
__DB_ERR = None
|
||||
__DB_STATUS = None
|
||||
|
||||
__DB_LOCAL = None
|
||||
|
||||
@@ -138,29 +139,51 @@ class nosqlMongodb():
|
||||
|
||||
if self.__DB_HOST in ['127.0.0.1', 'localhost']:
|
||||
mgdb_path = "{}/mongodb".format(mw.getServerDir())
|
||||
if not os.path.exists(mgdb_path): return False
|
||||
if not os.path.exists(mgdb_path):
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
|
||||
if not self.__DB_LOCAL:
|
||||
if not self.__config:
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
self.__DB_PORT = int(self.__config['port'])
|
||||
|
||||
auth = getConfAuth()
|
||||
port = getConfPort()
|
||||
mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root')
|
||||
# print(auth,self.__DB_HOST,port, self.__DB_PASS)
|
||||
try:
|
||||
if auth == 'disabled':
|
||||
self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=port, directConnection=True)
|
||||
self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=port, directConnection=True, serverSelectionTimeoutMS=3000)
|
||||
else:
|
||||
self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=port, directConnection=True, username='root',password=mg_root)
|
||||
self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=port, directConnection=True, username='root',password=mg_root, serverSelectionTimeoutMS=3000)
|
||||
self.__DB_CONN.admin.command('ping')
|
||||
# 节点处于 recovering/startup 时 ping 仍会成功,但读写会抛 NotPrimaryError
|
||||
try:
|
||||
hello = self.__DB_CONN.admin.command('hello')
|
||||
except Exception:
|
||||
# MongoDB < 5.0 没有 hello 命令
|
||||
hello = self.__DB_CONN.admin.command('isMaster')
|
||||
if not (hello.get('isWritablePrimary') or hello.get('ismaster') or hello.get('secondary')):
|
||||
self.__DB_ERR = 'MongoDB 节点当前不是 primary 或 secondary 状态'
|
||||
return False
|
||||
self.__DB_STATUS = None
|
||||
self.__DB_ERR = None
|
||||
return self.__DB_CONN
|
||||
except pymongo.errors.ConnectionFailure:
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
except Exception as e:
|
||||
# print(e)
|
||||
self.__DB_ERR = mw.getTracebackInfo()
|
||||
except Exception:
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
|
||||
# 连接状态:not_installed 未安装 / not_running 未启动 / None 正常
|
||||
def get_status(self):
|
||||
return self.__DB_STATUS
|
||||
|
||||
def get_err(self):
|
||||
return self.__DB_ERR
|
||||
|
||||
# 获取配置项
|
||||
def get_options(self, get=None):
|
||||
result = {}
|
||||
@@ -170,7 +193,7 @@ class nosqlMongodb():
|
||||
keys = ["bind_ip", "port"]
|
||||
|
||||
result['host'] = '127.0.0.1'
|
||||
rep = 'port\s*=\s*(.*)'
|
||||
rep = r'port\s*=\s*(.*)'
|
||||
ip_re = re.search(rep, mgdb_content)
|
||||
if ip_re:
|
||||
result['port'] = int(ip_re.groups()[0].strip())
|
||||
@@ -202,11 +225,23 @@ class nosqlMongodbCtr():
|
||||
instance = nosqlMongodb()
|
||||
return instance
|
||||
|
||||
def _connect(self, sid):
|
||||
obj = self.getInstanceBySid(sid)
|
||||
conn = obj.mgdb_conn()
|
||||
if conn is False:
|
||||
if obj.get_status() == 'not_installed':
|
||||
return None, mw.returnData(False, '未安装')
|
||||
err = obj.get_err()
|
||||
if err:
|
||||
return None, mw.returnData(False, err)
|
||||
return None, mw.returnData(False, '未启动')
|
||||
return conn, None
|
||||
|
||||
def getDbList(self, args):
|
||||
sid = args['sid']
|
||||
mgdb_instance = self.getInstanceBySid(sid).mgdb_conn()
|
||||
if mgdb_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mgdb_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
result = {}
|
||||
doc_list = mgdb_instance.list_database_names()
|
||||
@@ -221,9 +256,9 @@ class nosqlMongodbCtr():
|
||||
sid = args['sid']
|
||||
name = args['name']
|
||||
|
||||
mgdb_instance = self.getInstanceBySid(sid).mgdb_conn()
|
||||
if mgdb_instance is False:
|
||||
return mw.returnData(False,'无法链接.')
|
||||
mgdb_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
result = {}
|
||||
collections = mgdb_instance[name].list_collection_names()
|
||||
@@ -242,9 +277,9 @@ class nosqlMongodbCtr():
|
||||
if 'size' in args:
|
||||
size = args['size']
|
||||
|
||||
mgdb_instance = self.getInstanceBySid(sid).mgdb_conn()
|
||||
if mgdb_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mgdb_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
db_instance = mgdb_instance[db]
|
||||
collection_instance = db_instance[collection]
|
||||
@@ -303,9 +338,9 @@ class nosqlMongodbCtr():
|
||||
db = args['db']
|
||||
collection = args['collection']
|
||||
|
||||
mgdb_instance = self.getInstanceBySid(sid).mgdb_conn()
|
||||
if mgdb_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
mgdb_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
db_instance = mgdb_instance[db]
|
||||
collection_instance = db_instance[collection]
|
||||
|
||||
@@ -32,6 +32,7 @@ class nosqlRedis():
|
||||
__DB_HOST = '127.0.0.1'
|
||||
__DB_CONN = None
|
||||
__DB_ERR = None
|
||||
__DB_STATUS = None
|
||||
|
||||
__DB_LOCAL = None
|
||||
|
||||
@@ -44,9 +45,13 @@ class nosqlRedis():
|
||||
if self.__DB_HOST in ['127.0.0.1', 'localhost']:
|
||||
redis_path = "{}/redis".format(mw.getServerDir())
|
||||
if not os.path.exists(redis_path):
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
|
||||
if not self.__DB_LOCAL:
|
||||
if not self.__config:
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
self.__DB_PASS = self.__config['requirepass']
|
||||
self.__DB_PORT = int(self.__config['port'])
|
||||
|
||||
@@ -55,13 +60,20 @@ class nosqlRedis():
|
||||
redis_pool = redis.ConnectionPool(host=self.__DB_HOST, port=self.__DB_PORT, password=self.__DB_PASS, db=db_idx, socket_timeout=3)
|
||||
self.__DB_CONN = redis.Redis(connection_pool=redis_pool)
|
||||
self.__DB_CONN.ping()
|
||||
self.__DB_STATUS = None
|
||||
return self.__DB_CONN
|
||||
except redis.exceptions.ConnectionError:
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
except Exception:
|
||||
self.__DB_ERR = mw.getTracebackInfo()
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
|
||||
# 连接状态:not_installed 未安装 / not_running 未启动 / None 正常
|
||||
def get_status(self):
|
||||
return self.__DB_STATUS
|
||||
|
||||
# 获取配置项
|
||||
def get_options(self, get=None):
|
||||
|
||||
@@ -112,12 +124,21 @@ class nosqlRedisCtr():
|
||||
instance = nosqlRedis()
|
||||
return instance
|
||||
|
||||
def _connect(self, sid, idx=0):
|
||||
obj = self.getInstanceBySid(sid)
|
||||
conn = obj.redis_conn(idx)
|
||||
if conn is False:
|
||||
if obj.get_status() == 'not_installed':
|
||||
return None, mw.returnData(False, '未安装')
|
||||
return None, mw.returnData(False, '未启动')
|
||||
return conn, None
|
||||
|
||||
def getList(self, args):
|
||||
|
||||
sid = args['sid']
|
||||
redis_instance = self.getInstanceBySid(sid).redis_conn(0)
|
||||
if redis_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
redis_instance, err = self._connect(sid, 0)
|
||||
if err:
|
||||
return err
|
||||
|
||||
|
||||
redis_info = redis_instance.info()
|
||||
@@ -166,7 +187,9 @@ class nosqlRedisCtr():
|
||||
if 'search' in args and args['search'] != '':
|
||||
search = args['search']
|
||||
|
||||
redis_instance = self.getInstanceBySid(sid).redis_conn(idx)
|
||||
redis_instance, err = self._connect(sid, idx)
|
||||
if err:
|
||||
return err
|
||||
|
||||
total = redis_instance.dbsize()
|
||||
|
||||
@@ -251,7 +274,9 @@ class nosqlRedisCtr():
|
||||
val = args["val"]
|
||||
endtime = args["endtime"]
|
||||
|
||||
redis_instance = self.getInstanceBySid(sid).redis_conn(idx)
|
||||
redis_instance, err = self._connect(sid, idx)
|
||||
if err:
|
||||
return err
|
||||
|
||||
redis_info = redis_instance.info()
|
||||
if redis_info['role'] == 'slave':
|
||||
@@ -268,7 +293,9 @@ class nosqlRedisCtr():
|
||||
sid = args['sid']
|
||||
idx = args['idx']
|
||||
name = args["name"]
|
||||
redis_instance = self.getInstanceBySid(sid).redis_conn(idx)
|
||||
redis_instance, err = self._connect(sid, idx)
|
||||
if err:
|
||||
return err
|
||||
|
||||
redis_info = redis_instance.info()
|
||||
if redis_info['role'] == 'slave':
|
||||
@@ -281,7 +308,9 @@ class nosqlRedisCtr():
|
||||
sid = args['sid']
|
||||
idx = args['idx']
|
||||
keys = args["keys"]
|
||||
redis_instance = self.getInstanceBySid(sid).redis_conn(idx)
|
||||
redis_instance, err = self._connect(sid, idx)
|
||||
if err:
|
||||
return err
|
||||
|
||||
redis_info = redis_instance.info()
|
||||
if redis_info['role'] == 'slave':
|
||||
@@ -297,7 +326,9 @@ class nosqlRedisCtr():
|
||||
idxs = args['idxs']
|
||||
|
||||
for idx in idxs:
|
||||
redis_instance = self.getInstanceBySid(sid).redis_conn(idx)
|
||||
redis_instance, err = self._connect(sid, idx)
|
||||
if err:
|
||||
return err
|
||||
|
||||
redis_info = redis_instance.info()
|
||||
if redis_info['role'] == 'slave':
|
||||
|
||||
@@ -23,12 +23,15 @@ def singleton(cls):
|
||||
@singleton
|
||||
class nosqlMySQL():
|
||||
|
||||
__sid = None
|
||||
|
||||
__DB_PASS = None
|
||||
__DB_USER = None
|
||||
__DB_PORT = 3306
|
||||
__DB_HOST = '127.0.0.1'
|
||||
__DB_CONN = None
|
||||
__DB_ERR = None
|
||||
__DB_STATUS = None
|
||||
__DB_SOCKET = None
|
||||
|
||||
__DB_LOCAL = None
|
||||
@@ -37,9 +40,25 @@ class nosqlMySQL():
|
||||
pass
|
||||
|
||||
def setSid(self, sid):
|
||||
self.__sid = sid
|
||||
self.__config = self.get_options(sid=sid)
|
||||
|
||||
def conn(self):
|
||||
|
||||
if self.__sid is None:
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
|
||||
if self.__sid is not None:
|
||||
mycnf_path = "{}/{}/etc/my.cnf".format(mw.getServerDir(),self.__sid)
|
||||
if not os.path.exists(mycnf_path):
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
|
||||
if not self.__config:
|
||||
self.__DB_STATUS = 'not_installed'
|
||||
return False
|
||||
|
||||
self.__DB_PORT = int(self.__config['port'])
|
||||
self.__DB_USER = self.__config['username']
|
||||
self.__DB_PASS = self.__config['password']
|
||||
@@ -53,11 +72,19 @@ class nosqlMySQL():
|
||||
if self.__DB_SOCKET != '':
|
||||
db.setSocket(self.__DB_SOCKET)
|
||||
|
||||
# 实际连通性检查:服务未启动或账号错误时返回 False,调用方统一提示
|
||||
db.query('select 1')
|
||||
self.__DB_STATUS = None
|
||||
return db
|
||||
except Exception:
|
||||
self.__DB_ERR = mw.get_error_info()
|
||||
self.__DB_ERR = mw.getTracebackInfo()
|
||||
self.__DB_STATUS = 'not_running'
|
||||
return False
|
||||
|
||||
# 连接状态:not_installed 未安装 / not_running 未启动 / None 正常
|
||||
def get_status(self):
|
||||
return self.__DB_STATUS
|
||||
|
||||
def sqliteDb(self, db_pos_name, dbname='databases'):
|
||||
mydb_path = mw.getServerDir() +'/'+db_pos_name
|
||||
name = 'mysql'
|
||||
@@ -72,8 +99,10 @@ class nosqlMySQL():
|
||||
result['host'] = '127.0.0.1'
|
||||
result['username'] = 'root'
|
||||
|
||||
if sid in ['mysql', 'mysql-apt', 'mysql-yum']:
|
||||
if sid in ['mysql', 'mysql-apt', 'mysql-yum', 'mysql-community','mariadb']:
|
||||
my_cnf_path = "{}/{}/etc/my.cnf".format(mw.getServerDir(),sid)
|
||||
if not os.path.exists(my_cnf_path):
|
||||
return False
|
||||
|
||||
mydb_content = mw.readFile(my_cnf_path)
|
||||
if not mydb_content: return False
|
||||
@@ -118,6 +147,14 @@ class nosqlMySQL():
|
||||
local_mysql_yum = "{}/mysql-yum/etc/my.cnf".format(mw.getServerDir())
|
||||
if os.path.exists(local_mysql_yum):
|
||||
data.append({'name':'本地服务器[yum]', 'val':'mysql-yum'})
|
||||
|
||||
local_mysql_yum = "{}/mysql-community/etc/my.cnf".format(mw.getServerDir())
|
||||
if os.path.exists(local_mysql_yum):
|
||||
data.append({'name':'本地服务器[Tar]', 'val':'mysql-community'})
|
||||
|
||||
local_mysql_yum = "{}/mariadb/etc/my.cnf".format(mw.getServerDir())
|
||||
if os.path.exists(local_mysql_yum):
|
||||
data.append({'name':'本地服务器[mariadb]', 'val':'mariadb'})
|
||||
return mw.returnData(True, 'ok', data)
|
||||
|
||||
@singleton
|
||||
@@ -135,11 +172,20 @@ class nosqlMySQLCtr():
|
||||
instance.setSid(sid)
|
||||
return instance
|
||||
|
||||
def _connect(self, sid):
|
||||
obj = self.getInstanceBySid(sid)
|
||||
conn = obj.conn()
|
||||
if conn is False:
|
||||
if obj.get_status() == 'not_installed':
|
||||
return None, mw.returnData(False, '未安装')
|
||||
return None, mw.returnData(False, '未启动')
|
||||
return conn, None
|
||||
|
||||
def getDbList(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
result = {}
|
||||
db_list = my_instance.query('show databases')
|
||||
@@ -154,9 +200,9 @@ class nosqlMySQLCtr():
|
||||
sid = args['sid']
|
||||
db = args['db']
|
||||
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
sql = "select * from information_schema.tables where table_schema = '"+db+"'"
|
||||
table_list = my_instance.query(sql)
|
||||
@@ -208,9 +254,9 @@ class nosqlMySQLCtr():
|
||||
else:
|
||||
where_sql = ' where '+args_where['field'] + " like '%"+args_where['value']+"%' "
|
||||
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
my_instance.setDbName(db)
|
||||
sql = 'select count(*) as num from ' + table + where_sql
|
||||
@@ -250,9 +296,9 @@ class nosqlMySQLCtr():
|
||||
sql = 'show processlist';
|
||||
sid = args['sid']
|
||||
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
result = my_instance.query(sql)
|
||||
rdata = {}
|
||||
@@ -263,9 +309,9 @@ class nosqlMySQLCtr():
|
||||
sql = 'show status';
|
||||
sid = args['sid']
|
||||
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
result = my_instance.query(sql)
|
||||
rdata = {}
|
||||
@@ -276,9 +322,9 @@ class nosqlMySQLCtr():
|
||||
sql = "show status like 'Com_%'";
|
||||
sid = args['sid']
|
||||
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
result = my_instance.query(sql)
|
||||
rdata = {}
|
||||
@@ -313,9 +359,9 @@ class nosqlMySQLCtr():
|
||||
def getNetList(self, args):
|
||||
from datetime import datetime
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
rdata = []
|
||||
row = {}
|
||||
@@ -351,9 +397,9 @@ class nosqlMySQLCtr():
|
||||
|
||||
def getTopnList(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
is_performance_schema = my_instance.find("SELECT @@performance_schema")
|
||||
if is_performance_schema is None:
|
||||
@@ -381,9 +427,9 @@ class nosqlMySQLCtr():
|
||||
# 查看重复或冗余的索引
|
||||
def getRedundantIndexes(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
is_performance_schema = my_instance.find("SELECT @@performance_schema")
|
||||
if is_performance_schema["@@performance_schema"] == 0:
|
||||
@@ -399,9 +445,9 @@ class nosqlMySQLCtr():
|
||||
|
||||
def redundantIndexesCmd(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
is_performance_schema = my_instance.find("SELECT @@performance_schema")
|
||||
if is_performance_schema["@@performance_schema"] == 0:
|
||||
@@ -424,29 +470,53 @@ class nosqlMySQLCtr():
|
||||
from decimal import Decimal
|
||||
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
my_instance.execute("SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''))")
|
||||
|
||||
# ANY_VALUE 是 MySQL 5.7+ 的函数,MariaDB 不支持(报 FUNCTION ANY_VALUE does not exist);
|
||||
# 上面已去掉 ONLY_FULL_GROUP_BY,MariaDB 可直接取非聚合字段
|
||||
ver = my_instance.find("SELECT @@version as db_version")
|
||||
is_mariadb = ver is not None and 'MariaDB' in str(ver.get('db_version', ''))
|
||||
|
||||
def any_val(expr):
|
||||
return expr if is_mariadb else 'ANY_VALUE(' + expr + ')'
|
||||
|
||||
data = my_instance.query(
|
||||
"""
|
||||
SELECT t.TABLE_SCHEMA as TABLE_SCHEMA, t.TABLE_NAME as TABLE_NAME, t.ENGINE as ENGINE,
|
||||
IFNULL(t.DATA_LENGTH/1024/1024/1024, 0) as DATA_LENGTH,
|
||||
IFNULL(t.INDEX_LENGTH/1024/1024/1024, 0) as INDEX_LENGTH,
|
||||
IFNULL((DATA_LENGTH+INDEX_LENGTH)/1024/1024/1024, 0) AS TOTAL_LENGTH,
|
||||
c.column_name AS COLUMN_NAME, c.data_type AS DATA_TYPE, c.COLUMN_TYPE AS COLUMN_TYPE,
|
||||
t.AUTO_INCREMENT AS AUTO_INCREMENT, locate('unsigned', c.COLUMN_TYPE) = 0 AS IS_SIGNED
|
||||
FROM information_schema.TABLES t
|
||||
JOIN information_schema.COLUMNS c ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.table_name=c.table_name
|
||||
WHERE t.TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
|
||||
GROUP BY TABLE_NAME
|
||||
ORDER BY TOTAL_LENGTH DESC, AUTO_INCREMENT DESC;
|
||||
"""
|
||||
SELECT t.TABLE_SCHEMA as TABLE_SCHEMA, t.TABLE_NAME as TABLE_NAME,
|
||||
{engine} as ENGINE,
|
||||
{data_length} as DATA_LENGTH,
|
||||
{index_length} as INDEX_LENGTH,
|
||||
{total_length} AS TOTAL_LENGTH,
|
||||
{column_name} AS COLUMN_NAME, {data_type} AS DATA_TYPE,
|
||||
{column_type} AS COLUMN_TYPE,
|
||||
{auto_increment} AS AUTO_INCREMENT,
|
||||
{is_signed} AS IS_SIGNED
|
||||
FROM information_schema.TABLES t
|
||||
JOIN information_schema.COLUMNS c ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.table_name=c.table_name
|
||||
WHERE t.TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
|
||||
GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME
|
||||
ORDER BY TOTAL_LENGTH DESC, AUTO_INCREMENT DESC
|
||||
""".format(
|
||||
engine=any_val("t.ENGINE"),
|
||||
data_length=any_val("IFNULL(t.DATA_LENGTH/1024/1024/1024, 0)"),
|
||||
index_length=any_val("IFNULL(t.INDEX_LENGTH/1024/1024/1024, 0)"),
|
||||
total_length=any_val("IFNULL((t.DATA_LENGTH+t.INDEX_LENGTH)/1024/1024/1024, 0)"),
|
||||
column_name=any_val("c.column_name"),
|
||||
data_type=any_val("c.data_type"),
|
||||
column_type=any_val("c.COLUMN_TYPE"),
|
||||
auto_increment=any_val("t.AUTO_INCREMENT"),
|
||||
is_signed=any_val("locate('unsigned', c.COLUMN_TYPE) = 0"),
|
||||
)
|
||||
)
|
||||
|
||||
if data is None:
|
||||
return mw.returnData(True, 'ok', [])
|
||||
if isinstance(data, Exception):
|
||||
return mw.returnData(False, '查询失败: ' + str(data))
|
||||
|
||||
for i in range(len(data)):
|
||||
row = data[i]
|
||||
@@ -497,9 +567,9 @@ class nosqlMySQLCtr():
|
||||
# 查看应用端IP连接数总和
|
||||
def getConnCount(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
data = my_instance.query(
|
||||
"SELECT user,db,substring_index(HOST,':',1) AS Client_IP,count(1) AS count FROM information_schema.PROCESSLIST "
|
||||
@@ -521,9 +591,9 @@ class nosqlMySQLCtr():
|
||||
# 快速找出没有主键的表
|
||||
def getFpkInfo(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
data = my_instance.query(
|
||||
"""
|
||||
@@ -543,9 +613,9 @@ class nosqlMySQLCtr():
|
||||
|
||||
def getLockSql(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
data = my_instance.query(
|
||||
"""
|
||||
@@ -574,9 +644,9 @@ class nosqlMySQLCtr():
|
||||
|
||||
def killLockPid(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
pid = args['pid']
|
||||
my_instance.execute('kill %s' % pid)
|
||||
@@ -584,9 +654,9 @@ class nosqlMySQLCtr():
|
||||
|
||||
def killAllLock(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
data = self.getLockSql(args)
|
||||
if data['status']:
|
||||
@@ -601,9 +671,9 @@ class nosqlMySQLCtr():
|
||||
|
||||
def getDeadlockInfo(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
data = my_instance.find("SHOW ENGINE INNODB STATUS")
|
||||
if data is not None:
|
||||
@@ -616,29 +686,45 @@ class nosqlMySQLCtr():
|
||||
|
||||
def getSlaveStatus(self, args):
|
||||
sid = args['sid']
|
||||
my_instance = self.getInstanceBySid(sid).conn()
|
||||
if my_instance is False:
|
||||
return mw.returnData(False,'无法链接')
|
||||
my_instance, err = self._connect(sid)
|
||||
if err:
|
||||
return err
|
||||
|
||||
slave_info = my_instance.find('SHOW SLAVE STATUS')
|
||||
if slave_info is None:
|
||||
# MySQL 8.0.22+ 使用 SHOW REPLICA STATUS,MySQL 9.0 已移除 SHOW SLAVE STATUS
|
||||
slave_data = my_instance.query('SHOW REPLICA STATUS')
|
||||
if isinstance(slave_data, Exception):
|
||||
# 兼容 MySQL 8.0.22 以下及旧版 MariaDB
|
||||
slave_data = my_instance.query('SHOW SLAVE STATUS')
|
||||
if isinstance(slave_data, Exception):
|
||||
return mw.returnData(False, '获取主从状态失败: ' + str(slave_data))
|
||||
if slave_data is None or len(slave_data) == 0:
|
||||
return mw.returnData(True, 'ok', '未开启从库!')
|
||||
|
||||
slave_info = slave_data[0]
|
||||
# 兼容新旧字段名: MySQL 8.0.22+ 将 Slave_*/Seconds_Behind_Master 重命名为 Replica_*/Seconds_Behind_Source
|
||||
io_running = slave_info.get('Replica_IO_Running', slave_info.get('Slave_IO_Running'))
|
||||
sql_running = slave_info.get('Replica_SQL_Running', slave_info.get('Slave_SQL_Running'))
|
||||
seconds_behind = slave_info.get('Seconds_Behind_Source', slave_info.get('Seconds_Behind_Master'))
|
||||
|
||||
msg = ''
|
||||
if slave_info['Auto_Position'] != 1:
|
||||
if slave_info.get('Auto_Position') != 1:
|
||||
msg += '你没有开启基于GTID全局事务ID复制,请确保CHANGE MASTER TO MASTER_AUTO_POSITION = 1.\n'
|
||||
|
||||
|
||||
if slave_info['Slave_IO_Running'] == 'Yes' and slave_info['Slave_SQL_Running'] == 'Yes':
|
||||
if slave_info['Seconds_Behind_Master'] == 0:
|
||||
if io_running == 'Yes' and sql_running == 'Yes':
|
||||
if seconds_behind == 0:
|
||||
msg = "同步正常,无延迟"
|
||||
return mw.returnData(True, 'ok', msg)
|
||||
else:
|
||||
return mw.returnData(True, 'ok', '同步正常,但有延迟,延迟时间为:%s' % slave_info['Seconds_Behind_Master'])
|
||||
return mw.returnData(True, 'ok', '同步正常,但有延迟,延迟时间为:%s' % seconds_behind)
|
||||
else:
|
||||
msg = '主从复制报错,请检查\nSlave_IO_Running状态值是:%s, | Slave_SQL_Running状态值是:%s\nLast_Error错误信息是:%s\nLast_SQL_Error错误信息是:%s\n' \
|
||||
% (slave_info['Slave_IO_Running'], slave_info['Slave_SQL_Running'], slave_info['Last_Error'], slave_info['Last_SQL_Error'])
|
||||
error_dict = my_instance.find('select LAST_ERROR_NUMBER,LAST_ERROR_MESSAGE,LAST_ERROR_TIMESTAMP from performance_schema.replication_applier_status_by_worker ORDER BY LAST_ERROR_TIMESTAMP desc limit 1')
|
||||
% (io_running, sql_running, slave_info.get('Last_Error'), slave_info.get('Last_SQL_Error'))
|
||||
error_data = my_instance.query('select LAST_ERROR_NUMBER,LAST_ERROR_MESSAGE,LAST_ERROR_TIMESTAMP from performance_schema.replication_applier_status_by_worker ORDER BY LAST_ERROR_TIMESTAMP desc limit 1')
|
||||
if isinstance(error_data, Exception) or not error_data:
|
||||
msg += 'MySQL Replication Health is NOT OK!'
|
||||
return mw.returnData(True, 'ok', msg)
|
||||
error_dict = error_data[0]
|
||||
msg += '错误号是:%s \n' % error_dict['LAST_ERROR_NUMBER']
|
||||
msg += '错误信息是:%s \n' % error_dict['LAST_ERROR_MESSAGE']
|
||||
msg += '报错时间是:%s \n' % error_dict['LAST_ERROR_TIMESTAMP']
|
||||
|
||||
825
plugins/data_query/sql_postgresql.py
Executable file
825
plugins/data_query/sql_postgresql.py
Executable file
@@ -0,0 +1,825 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
|
||||
web_dir = os.getcwd() + "/web"
|
||||
if os.path.exists(web_dir):
|
||||
sys.path.append(web_dir)
|
||||
os.chdir(web_dir)
|
||||
|
||||
import core.mw as mw
|
||||
|
||||
PG_SYSTEM_SCHEMAS = ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'postgresql'
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getConf():
|
||||
return getServerDir() + '/data/postgresql.conf'
|
||||
|
||||
|
||||
def isInstalled():
|
||||
return os.path.exists(getConf())
|
||||
|
||||
|
||||
def getPort():
|
||||
content = mw.readFile(getConf())
|
||||
if content:
|
||||
tmp = re.search(r'^[#\s]*port\s*=\s*(\d+)', content, re.M)
|
||||
if tmp:
|
||||
return int(tmp.groups()[0])
|
||||
return 5432
|
||||
|
||||
|
||||
def getSocketDir():
|
||||
sock_name = '.s.PGSQL.' + str(getPort())
|
||||
for sock_dir in ['/tmp', getServerDir()]:
|
||||
if os.path.exists(sock_dir + '/' + sock_name):
|
||||
return sock_dir
|
||||
return ''
|
||||
|
||||
|
||||
def quoteIdent(name):
|
||||
return '"' + str(name).replace('"', '""') + '"'
|
||||
|
||||
|
||||
def quoteTable(name):
|
||||
return '.'.join(quoteIdent(x) for x in str(name).split('.'))
|
||||
|
||||
|
||||
def fmtValue(value):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (dict, list, tuple)):
|
||||
return json.dumps(value, ensure_ascii=False, default=str)
|
||||
if isinstance(value, (bytes, bytearray, memoryview)):
|
||||
return bytes(value).hex()
|
||||
if isinstance(value, (int, float, str, bool)):
|
||||
return value
|
||||
return str(value)
|
||||
|
||||
|
||||
def fmtRows(rows):
|
||||
ret = []
|
||||
for row in rows:
|
||||
item = {}
|
||||
for k in row:
|
||||
item[k] = fmtValue(row[k])
|
||||
ret.append(item)
|
||||
return ret
|
||||
|
||||
|
||||
class PgClient:
|
||||
|
||||
def __init__(self, database='postgres'):
|
||||
self.database = database
|
||||
self.status = None
|
||||
self.error = None
|
||||
self.conn = None
|
||||
|
||||
def connect(self):
|
||||
if not isInstalled():
|
||||
self.status = 'not_installed'
|
||||
return False
|
||||
|
||||
try:
|
||||
host = getSocketDir()
|
||||
if host == '':
|
||||
host = '127.0.0.1'
|
||||
|
||||
self.conn = psycopg2.connect(database=self.database,
|
||||
user='postgres',
|
||||
host=host,
|
||||
port=getPort(),
|
||||
connect_timeout=3)
|
||||
self.conn.autocommit = True
|
||||
return True
|
||||
except psycopg2.OperationalError as e:
|
||||
msg = str(e).strip()
|
||||
if 'does not exist' in msg:
|
||||
self.status = 'db_not_found'
|
||||
elif 'authentication' in msg or 'password' in msg:
|
||||
self.status = 'auth_error'
|
||||
else:
|
||||
self.status = 'not_running'
|
||||
self.error = msg
|
||||
except Exception as e:
|
||||
self.status = 'not_running'
|
||||
self.error = str(e)
|
||||
return False
|
||||
|
||||
def query(self, sql, params=()):
|
||||
cur = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||||
try:
|
||||
# 无参数时不能传空 tuple,否则 SQL 里的 % 会被当作占位符
|
||||
if params:
|
||||
cur.execute(sql, params)
|
||||
else:
|
||||
cur.execute(sql)
|
||||
rows = cur.fetchall()
|
||||
return [dict(x) for x in rows]
|
||||
finally:
|
||||
cur.close()
|
||||
|
||||
def one(self, sql, params=()):
|
||||
rows = self.query(sql, params)
|
||||
if len(rows) == 0:
|
||||
return None
|
||||
return rows[0]
|
||||
|
||||
def execute(self, sql, params=()):
|
||||
cur = self.conn.cursor()
|
||||
try:
|
||||
if params:
|
||||
cur.execute(sql, params)
|
||||
else:
|
||||
cur.execute(sql)
|
||||
return cur.rowcount
|
||||
finally:
|
||||
cur.close()
|
||||
|
||||
def close(self):
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
|
||||
def openClient(args, database='postgres'):
|
||||
client = PgClient(database)
|
||||
if client.connect():
|
||||
return client, None
|
||||
|
||||
if client.status == 'not_installed':
|
||||
msg = '未安装'
|
||||
elif client.status == 'not_running':
|
||||
msg = '未启动'
|
||||
elif client.status == 'db_not_found':
|
||||
msg = '数据库不存在'
|
||||
elif client.status == 'auth_error':
|
||||
msg = '连接失败,请检查数据库认证配置'
|
||||
else:
|
||||
msg = client.error or '连接失败'
|
||||
return None, mw.returnData(False, msg)
|
||||
|
||||
|
||||
# 服务器列表
|
||||
def get_server_list(args):
|
||||
data = []
|
||||
if isInstalled():
|
||||
data.append({'name': '本地服务器', 'val': 'postgresql'})
|
||||
return mw.returnData(True, 'ok', data)
|
||||
|
||||
|
||||
# 数据库列表
|
||||
def get_db_list(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
rows = client.query("select datname from pg_database where datistemplate = false order by datname")
|
||||
result = {'list': [x['datname'] for x in rows]}
|
||||
return mw.returnData(True, 'ok', result)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 数据表列表
|
||||
def get_table_list(args):
|
||||
db = args.get('db', '')
|
||||
client, err = openClient(args, db)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select n.nspname as schemaname, c.relname as tablename
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.relkind in ('r', 'p', 'v', 'm', 'f')
|
||||
and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
and n.nspname not like 'pg_temp%'
|
||||
order by n.nspname, c.relname
|
||||
"""
|
||||
rows = client.query(sql)
|
||||
|
||||
table_list = []
|
||||
for x in rows:
|
||||
if x['schemaname'] == 'public':
|
||||
table_list.append(x['tablename'])
|
||||
else:
|
||||
table_list.append(x['schemaname'] + '.' + x['tablename'])
|
||||
|
||||
result = {'list': table_list}
|
||||
return mw.returnData(True, 'ok', result)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 数据列表
|
||||
def get_data_list(args):
|
||||
db = args.get('db', '')
|
||||
table = args.get('table', '')
|
||||
|
||||
p = 1
|
||||
size = 10
|
||||
if 'p' in args:
|
||||
p = int(args['p'])
|
||||
if 'size' in args:
|
||||
size = int(args['size'])
|
||||
start_index = (p - 1) * size
|
||||
|
||||
if table == '':
|
||||
page_args = {}
|
||||
page_args['count'] = 0
|
||||
page_args['tojs'] = 'pgGetDataList'
|
||||
page_args['p'] = 1
|
||||
page_args['row'] = size
|
||||
|
||||
rdata = {}
|
||||
rdata['page'] = mw.getPage(page_args)
|
||||
rdata['list'] = []
|
||||
rdata['count'] = 0
|
||||
return mw.returnData(True, 'ok', rdata)
|
||||
|
||||
where_sql = ''
|
||||
where_params = []
|
||||
args_where = {}
|
||||
if 'where' in args and isinstance(args['where'], dict):
|
||||
args_where = args['where']
|
||||
|
||||
field = args_where.get('field', '')
|
||||
if field and field != '0':
|
||||
field_sql = quoteIdent(field)
|
||||
value = str(args_where.get('value', ''))
|
||||
if 'id' in field.lower():
|
||||
where_sql = ' where ' + field_sql + ' = %s'
|
||||
where_params.append(value)
|
||||
else:
|
||||
where_sql = ' where ' + field_sql + ' like %s'
|
||||
where_params.append('%' + value + '%')
|
||||
|
||||
client, err = openClient(args, db)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
try:
|
||||
sql = 'select count(*) as num from ' + quoteTable(table) + where_sql
|
||||
count_row = client.one(sql, tuple(where_params))
|
||||
count = int(count_row['num'])
|
||||
|
||||
sql = 'select * from ' + quoteTable(table) + where_sql + ' limit %s offset %s'
|
||||
params = tuple(where_params) + (size, start_index)
|
||||
rows = client.query(sql, params)
|
||||
rows = fmtRows(rows)
|
||||
except psycopg2.errors.UndefinedTable:
|
||||
return mw.returnData(False, '数据表不存在:' + table)
|
||||
except psycopg2.errors.UndefinedColumn:
|
||||
return mw.returnData(False, '字段不存在,请刷新后重试')
|
||||
except psycopg2.Error as e:
|
||||
return mw.returnData(False, '查询失败:' + str(e).strip())
|
||||
|
||||
page_args = {}
|
||||
page_args['count'] = count
|
||||
page_args['tojs'] = 'pgGetDataList'
|
||||
page_args['p'] = p
|
||||
page_args['row'] = size
|
||||
|
||||
rdata = {}
|
||||
rdata['page'] = mw.getPage(page_args)
|
||||
rdata['list'] = rows
|
||||
rdata['count'] = count
|
||||
rdata['soso_field'] = ''
|
||||
if field and field != '0':
|
||||
rdata['soso_field'] = field
|
||||
|
||||
return mw.returnData(True, 'ok', rdata)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 进程列表
|
||||
def get_proccess_list(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select pid, usename as "user", datname as db,
|
||||
coalesce(host(client_addr), '本地') as host,
|
||||
application_name as command, state, query as info,
|
||||
backend_start, xact_start, query_start,
|
||||
wait_event_type, wait_event
|
||||
from pg_stat_activity
|
||||
where pid <> pg_backend_pid()
|
||||
and backend_type = 'client backend'
|
||||
order by pid
|
||||
"""
|
||||
rows = fmtRows(client.query(sql))
|
||||
result = {'list': rows}
|
||||
return mw.returnData(True, 'ok', result)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 状态列表
|
||||
def get_status_list(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select name, setting, coalesce(unit, '') as unit,
|
||||
category, short_desc
|
||||
from pg_settings
|
||||
order by name
|
||||
"""
|
||||
rows = fmtRows(client.query(sql))
|
||||
result = {'list': rows}
|
||||
return mw.returnData(True, 'ok', result)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 统计列表
|
||||
def get_stats_list(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select datname as db,
|
||||
numbackends as conn,
|
||||
xact_commit as commit, xact_rollback as rollback,
|
||||
blks_read, blks_hit,
|
||||
tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted,
|
||||
deadlocks, conflicts, temp_files, temp_bytes,
|
||||
round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) as cache_hit_ratio
|
||||
from pg_stat_database
|
||||
where datname is not null
|
||||
order by datname
|
||||
"""
|
||||
rows = fmtRows(client.query(sql))
|
||||
result = {'list': rows}
|
||||
return mw.returnData(True, 'ok', result)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def _dbStatSnapshot(client):
|
||||
sql = """
|
||||
select coalesce(sum(tup_fetched), 0) as fetched,
|
||||
coalesce(sum(tup_inserted), 0) as inserted,
|
||||
coalesce(sum(tup_updated), 0) as updated,
|
||||
coalesce(sum(tup_deleted), 0) as deleted,
|
||||
coalesce(sum(xact_commit), 0) as commits
|
||||
from pg_stat_database
|
||||
"""
|
||||
row = client.one(sql)
|
||||
return {
|
||||
'fetched': int(row['fetched']),
|
||||
'inserted': int(row['inserted']),
|
||||
'updated': int(row['updated']),
|
||||
'deleted': int(row['deleted']),
|
||||
'commits': int(row['commits']),
|
||||
}
|
||||
|
||||
|
||||
# QPS/TPS 指标
|
||||
def get_net_list(args):
|
||||
from datetime import datetime
|
||||
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
row1 = _dbStatSnapshot(client)
|
||||
time.sleep(1)
|
||||
row2 = _dbStatSnapshot(client)
|
||||
|
||||
max_conn = client.one("select setting from pg_settings where name = 'max_connections'")
|
||||
conn_row = client.one("select count(*) as num from pg_stat_activity where backend_type = 'client backend'")
|
||||
|
||||
row = {}
|
||||
row['current_time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
row['select'] = row2['fetched'] - row1['fetched']
|
||||
row['insert'] = row2['inserted'] - row1['inserted']
|
||||
row['update'] = row2['updated'] - row1['updated']
|
||||
row['delete'] = row2['deleted'] - row1['deleted']
|
||||
row['conn'] = conn_row['num']
|
||||
row['max_conn'] = max_conn['setting']
|
||||
# PostgreSQL 的 pg_stat 不提供网络字节计数器
|
||||
row['recv_mbps'] = '-'
|
||||
row['send_mbps'] = '-'
|
||||
|
||||
rdata = []
|
||||
rdata.append(row)
|
||||
return mw.returnData(True, 'ok', rdata)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 查询执行次数最频繁的前N条SQL语句
|
||||
def get_topn_list(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
avail = client.one("""
|
||||
select default_version, installed_version
|
||||
from pg_available_extensions
|
||||
where name = 'pg_stat_statements'
|
||||
""")
|
||||
if avail is None:
|
||||
return mw.returnData(False, '当前 PostgreSQL 未提供 pg_stat_statements 扩展(缺少 contrib 模块),无法使用该功能。')
|
||||
|
||||
# 先检查 shared_preload_libraries —— pg_stat_statements 必须预加载
|
||||
preload = client.one("select setting from pg_settings where name = 'shared_preload_libraries'")
|
||||
libs = (preload or {}).get('setting', '') or ''
|
||||
if 'pg_stat_statements' not in libs:
|
||||
return mw.returnData(False, "pg_stat_statements 扩展必须在启动时预加载。\n"
|
||||
"请在 postgresql.conf 添加 shared_preload_libraries = 'pg_stat_statements' 后重启 PostgreSQL。")
|
||||
|
||||
# 再检查扩展是否已 CREATE
|
||||
ext = client.one("select to_regclass('pg_stat_statements') as v")
|
||||
if ext is None or ext['v'] is None:
|
||||
return mw.returnData(False, "pg_stat_statements 扩展已预加载,但尚未启用。\n"
|
||||
"请在当前数据库执行 CREATE EXTENSION pg_stat_statements;")
|
||||
|
||||
version_num = int(client.one('show server_version_num')['server_version_num'])
|
||||
if version_num >= 130000:
|
||||
total_col = 'total_exec_time'
|
||||
max_col = 'max_exec_time'
|
||||
mean_col = 'mean_exec_time'
|
||||
else:
|
||||
total_col = 'total_time'
|
||||
max_col = 'max_time'
|
||||
mean_col = 'mean_time'
|
||||
|
||||
sql = """
|
||||
select s.query,
|
||||
coalesce(d.datname, '-') as db,
|
||||
'-' as last_seen,
|
||||
s.calls as exec_count,
|
||||
round(s.{max_col}::numeric, 2) as max_latency,
|
||||
round(s.{mean_col}::numeric, 2) as avg_latency
|
||||
from pg_stat_statements s
|
||||
left join pg_database d on d.oid = s.dbid
|
||||
""".format(max_col=max_col, mean_col=mean_col)
|
||||
|
||||
filter_db = args.get('filter_db', 'no')
|
||||
if filter_db == 'yes':
|
||||
sql += ' where d.datname is not null '
|
||||
|
||||
sql += ' order by s.calls desc limit 20'
|
||||
rows = fmtRows(client.query(sql))
|
||||
return mw.returnData(True, 'ok', rows)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def _redundantIndexes(client):
|
||||
sql = """
|
||||
select n.nspname as table_schema,
|
||||
t.relname as table_name,
|
||||
ic.relname as redundant_index_name,
|
||||
coalesce((
|
||||
select string_agg(a.attname, ',' order by k.ord)
|
||||
from unnest(i.indkey) with ordinality as k(attnum, ord)
|
||||
join pg_attribute a on a.attrelid = i.indrelid and a.attnum = k.attnum
|
||||
), '') as redundant_index_columns,
|
||||
'DROP INDEX ' || quote_ident(n.nspname) || '.' || quote_ident(ic.relname) as sql_drop_index
|
||||
from pg_index i
|
||||
join pg_class t on t.oid = i.indrelid
|
||||
join pg_namespace n on n.oid = t.relnamespace
|
||||
join pg_class ic on ic.oid = i.indexrelid
|
||||
where n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
and not exists (
|
||||
select 1 from pg_constraint con where con.conindid = i.indexrelid
|
||||
)
|
||||
and exists (
|
||||
select 1
|
||||
from pg_index i2
|
||||
join pg_class ic2 on ic2.oid = i2.indexrelid
|
||||
where i2.indrelid = i.indrelid
|
||||
and i2.indkey = i.indkey
|
||||
and i2.indclass = i.indclass
|
||||
and i2.indcollation = i.indcollation
|
||||
and i2.indpred is not distinct from i.indpred
|
||||
and i2.indexprs is not distinct from i.indexprs
|
||||
and i2.indexrelid <> i.indexrelid
|
||||
and ic2.relname < ic.relname
|
||||
)
|
||||
order by 1, 2, 3
|
||||
"""
|
||||
return fmtRows(client.query(sql))
|
||||
|
||||
|
||||
# 查看重复或冗余的索引
|
||||
def get_redundant_indexes(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
rows = _redundantIndexes(client)
|
||||
return mw.returnData(True, 'ok', rows)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 删除重复或冗余的索引
|
||||
def redundant_indexes_cmd(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
rows = _redundantIndexes(client)
|
||||
index = int(args['index'])
|
||||
if index >= len(rows):
|
||||
return mw.returnData(False, '索引不存在,请刷新后重试')
|
||||
|
||||
client.execute(rows[index]['sql_drop_index'])
|
||||
return mw.returnData(True, '执行成功!')
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 统计库里每个表的大小
|
||||
def get_table_info(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select n.nspname as "TABLE_SCHEMA",
|
||||
c.relname as "TABLE_NAME",
|
||||
case c.relkind
|
||||
when 'r' then 'table'
|
||||
when 'p' then 'partitioned table'
|
||||
when 'v' then 'view'
|
||||
when 'm' then 'materialized view'
|
||||
else c.relkind::text
|
||||
end as "ENGINE",
|
||||
c.reltuples as rows_estimate,
|
||||
pg_relation_size(c.oid) as data_bytes,
|
||||
pg_indexes_size(c.oid) as index_bytes,
|
||||
pg_total_relation_size(c.oid) as total_bytes,
|
||||
coalesce((
|
||||
select string_agg(a.attname, ',' order by k.ord)
|
||||
from pg_index i
|
||||
join unnest(i.indkey) with ordinality as k(attnum, ord) on true
|
||||
join pg_attribute a on a.attrelid = i.indrelid and a.attnum = k.attnum
|
||||
where i.indrelid = c.oid and i.indisprimary
|
||||
), '-') as "COLUMN_NAME",
|
||||
coalesce((
|
||||
select string_agg(format_type(a.atttypid, a.atttypmod), ',' order by k.ord)
|
||||
from pg_index i
|
||||
join unnest(i.indkey) with ordinality as k(attnum, ord) on true
|
||||
join pg_attribute a on a.attrelid = i.indrelid and a.attnum = k.attnum
|
||||
where i.indrelid = c.oid and i.indisprimary
|
||||
), '-') as "COLUMN_TYPE"
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.relkind in ('r', 'p', 'v', 'm')
|
||||
and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
and n.nspname not like 'pg_temp%'
|
||||
order by total_bytes desc
|
||||
"""
|
||||
rows = client.query(sql)
|
||||
|
||||
for i in range(len(rows)):
|
||||
row = rows[i]
|
||||
row['DATA_LENGTH'] = round(float(row['data_bytes'] or 0) / 1024 / 1024 / 1024, 2)
|
||||
row['INDEX_LENGTH'] = round(float(row['index_bytes'] or 0) / 1024 / 1024 / 1024, 2)
|
||||
row['TOTAL_LENGTH'] = round(float(row['total_bytes'] or 0) / 1024 / 1024 / 1024, 2)
|
||||
# PostgreSQL 没有 AUTO_INCREMENT,使用 SEQUENCE / IDENTITY
|
||||
row['AUTO_INCREMENT'] = '-'
|
||||
row['RESIDUAL_AUTO_INCREMENT'] = '使用 SEQUENCE/IDENTITY'
|
||||
row['ROWS_ESTIMATE'] = int(row['rows_estimate'] or 0)
|
||||
del row['data_bytes']
|
||||
del row['index_bytes']
|
||||
del row['total_bytes']
|
||||
|
||||
return mw.returnData(True, 'ok', fmtRows(rows))
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 查看应用端IP连接数总和
|
||||
def get_conn_count(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select usename as "user",
|
||||
coalesce(datname, '空') as db,
|
||||
coalesce(host(client_addr), '本地') as "Client_IP",
|
||||
count(*) as count
|
||||
from pg_stat_activity
|
||||
where backend_type = 'client backend'
|
||||
group by usename, datname, client_addr
|
||||
order by count desc
|
||||
"""
|
||||
rows = fmtRows(client.query(sql))
|
||||
return mw.returnData(True, 'ok', rows)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 快速找出没有主键的表
|
||||
def get_fpk_info(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select n.nspname as table_schema, c.relname as table_name
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where c.relkind in ('r', 'p')
|
||||
and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
and not exists (
|
||||
select 1 from pg_index i
|
||||
where i.indrelid = c.oid and i.indisprimary
|
||||
)
|
||||
order by n.nspname, c.relname
|
||||
"""
|
||||
rows = fmtRows(client.query(sql))
|
||||
return mw.returnData(True, 'ok', rows)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 查看当前锁阻塞的SQL
|
||||
def get_lock_sql(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select coalesce(a.backend_xid::text, '-') as trx_id,
|
||||
'等待锁' as trx_state,
|
||||
a.xact_start as trx_started,
|
||||
a.pid as processlist_id,
|
||||
a.query as info,
|
||||
a.usename as "user",
|
||||
coalesce(host(a.client_addr), '本地') as host,
|
||||
a.datname as db,
|
||||
coalesce(a.wait_event_type, '-') as command,
|
||||
a.state,
|
||||
'SELECT pg_cancel_backend(' || a.pid || ')' as sql_kill_blocking_query,
|
||||
pg_blocking_pids(a.pid) as blocking_pids
|
||||
from pg_stat_activity a
|
||||
where cardinality(pg_blocking_pids(a.pid)) > 0
|
||||
and a.backend_type = 'client backend'
|
||||
order by a.xact_start
|
||||
"""
|
||||
rows = fmtRows(client.query(sql))
|
||||
return mw.returnData(True, 'ok', rows)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 终止阻塞会话
|
||||
def kill_lock_pid(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
pid = int(args['pid'])
|
||||
data = client.one('select pg_terminate_backend(%s) as ok', (pid,))
|
||||
if data is None or data['ok'] is not True:
|
||||
return mw.returnData(False, '结束会话失败,请检查会话是否存在')
|
||||
return mw.returnData(True, '执行成功!')
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 终止所有阻塞会话
|
||||
def kill_all_lock(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
sql = """
|
||||
select distinct unnest(pg_blocking_pids(pid)) as pid
|
||||
from pg_stat_activity
|
||||
where cardinality(pg_blocking_pids(pid)) > 0
|
||||
and backend_type = 'client backend'
|
||||
"""
|
||||
rows = client.query(sql)
|
||||
count = 0
|
||||
for x in rows:
|
||||
data = client.one('select pg_terminate_backend(%s) as ok', (x['pid'],))
|
||||
if data is not None and data['ok'] is True:
|
||||
count += 1
|
||||
return mw.returnData(True, '执行成功,已结束 %s 个阻塞会话' % count)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 查看死锁信息
|
||||
def get_deadlock_info(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
deadlocks = client.one('select coalesce(sum(deadlocks), 0) as num from pg_stat_database')
|
||||
locks = client.query("""
|
||||
select pid, usename as "user", datname as db, state,
|
||||
coalesce(wait_event_type, '-') as wait_event_type,
|
||||
query
|
||||
from pg_stat_activity
|
||||
where cardinality(pg_blocking_pids(pid)) > 0
|
||||
and backend_type = 'client backend'
|
||||
""")
|
||||
|
||||
msg = 'PostgreSQL 不保存历史死锁明细,仅提供累计次数与当前锁等待。\n'
|
||||
msg += '数据库累计死锁次数:%s\n' % deadlocks['num']
|
||||
if len(locks) == 0:
|
||||
msg += '当前没有处于锁等待的会话。'
|
||||
else:
|
||||
msg += '当前锁等待会话:\n'
|
||||
for x in locks:
|
||||
msg += 'pid=%s user=%s db=%s state=%s wait=%s\nquery=%s\n' % (
|
||||
x['pid'], x['user'], x['db'], x['state'], x['wait_event_type'], x['query'])
|
||||
return mw.returnData(True, 'ok', msg)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
# 查看主从复制信息
|
||||
def get_slave_status(args):
|
||||
client, err = openClient(args)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
recovery = client.one('select pg_is_in_recovery() as recovery')['recovery']
|
||||
|
||||
if recovery is False:
|
||||
reps = client.query("""
|
||||
select client_addr, application_name, state, sync_state,
|
||||
sent_lsn, write_lsn, flush_lsn, replay_lsn,
|
||||
coalesce(replay_lag::text, '-') as replay_lag
|
||||
from pg_stat_replication
|
||||
""")
|
||||
if len(reps) == 0:
|
||||
return mw.returnData(True, 'ok', '未开启主从复制:当前为主库,且没有复制的从库连接。')
|
||||
|
||||
msg = '当前为主库,从库复制状态:\n'
|
||||
for x in reps:
|
||||
msg += '从库地址:%s 应用:%s\n' % (x['client_addr'], x['application_name'])
|
||||
msg += '状态:%s 同步模式:%s\n' % (x['state'], x['sync_state'])
|
||||
msg += 'sent_lsn=%s write_lsn=%s flush_lsn=%s replay_lsn=%s\n' % (
|
||||
x['sent_lsn'], x['write_lsn'], x['flush_lsn'], x['replay_lsn'])
|
||||
msg += 'replay_lag=%s\n\n' % x['replay_lag']
|
||||
return mw.returnData(True, 'ok', msg)
|
||||
|
||||
# 当前为从库
|
||||
lag = client.one("""
|
||||
select coalesce(extract(epoch from (now() - pg_last_xact_replay_timestamp()))::int, 0) as lag
|
||||
""")
|
||||
lsn = client.one("""
|
||||
select pg_last_wal_receive_lsn() as receive_lsn,
|
||||
pg_last_wal_replay_lsn() as replay_lsn,
|
||||
pg_last_xact_replay_timestamp() as replay_time
|
||||
""")
|
||||
|
||||
msg = '当前为从库,复制状态:\n'
|
||||
msg += 'receive_lsn=%s\n' % lsn['receive_lsn']
|
||||
msg += 'replay_lsn=%s\n' % lsn['replay_lsn']
|
||||
msg += '最近回放时间=%s\n' % lsn['replay_time']
|
||||
msg += '延迟时间:%s 秒' % lag['lag']
|
||||
return mw.returnData(True, 'ok', msg)
|
||||
finally:
|
||||
client.close()
|
||||
@@ -45,4 +45,26 @@
|
||||
right:0; /* 首行永远固定在左侧 */
|
||||
z-index:1;
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
.divtable {
|
||||
overflow: auto;
|
||||
/*border: 1px solid #ddd;*/
|
||||
}
|
||||
|
||||
.divtable table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.divtable table thead th {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.divtable table tbody td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
<div class="tabs-item" data-name="mongodb">Mongodb</div>
|
||||
<div class="tabs-item" data-name="memcached">Memcached</div>
|
||||
<div class="tabs-item" data-name="mysql">MySQL</div>
|
||||
<div class="tabs-item" data-name="postgresql">PostgreSQL</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +17,99 @@
|
||||
<div class="prompt_description">当前未安装本地服务器,<a class="btlink install_server" href="/soft/index">点击安装</a></div>
|
||||
</div>
|
||||
|
||||
<!-- postgresql start -->
|
||||
<div class="tab-con hide" id="postgresql" style="padding: 0px;">
|
||||
|
||||
<div class="col-md-12" style="padding: 0px;overflow: hidden;">
|
||||
<div class="tootls_group tootls_top">
|
||||
<div class="pull-left">
|
||||
<div class="pgserver_list" style="float:left;">
|
||||
<select name="sid" class="bt-input-text c5 mr5">
|
||||
<option value="0">本地服务器</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="postgresql_db_list" style="float:left;">
|
||||
<select name="postgresql_db" class="bt-input-text c5 mr5">
|
||||
<option value="0">数据库空</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="postgresql_table_list" style="float:left;">
|
||||
<select name="postgresql_table" class="bt-input-text c5 mr5">
|
||||
<option value="0">数据表空</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field" style="float:left;">
|
||||
<select name="postgresql_field_key" class="bt-input-text c5 mr5" style="width:80px;">
|
||||
<option value="0">无字段</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="value" style="float:left;">
|
||||
<input name="postgresql_field_value" type="text" class="bt-input-text" placeholder="请输入查找内容">
|
||||
</div>
|
||||
|
||||
|
||||
<div style="float:left;">
|
||||
<button id="postgresql_find" type="button" class="btn btn-success btn-sm mr5 ml5" >查找</button>
|
||||
<button id="postgresql_common" type="button" class="btn btn-success btn-sm mr5 ml5">常用功能</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="position: relative;">
|
||||
<div id="postgresql_table" class="postgresql_table divtable mtb10">
|
||||
<table class="table table-hover">
|
||||
<thead style="position: relative;z-index: 1;">
|
||||
<tr>
|
||||
<th><span>编号</span></th>
|
||||
<th><span>操作类型</span></th>
|
||||
<th><span>详情</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="postgresql_list_page page">
|
||||
<div><span class="Pcurrent">0</span><span class="Pcount">共0条</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr style="border:none;" />
|
||||
|
||||
<div class="col-md-12" style="padding-left: 3px;">
|
||||
<div id="postgresql_list_tab" style="padding-top:0px;">
|
||||
<div class="tab-nav">
|
||||
<span data-name="proccess" class="on">进程</span>
|
||||
<span data-name="status" class="">状态</span>
|
||||
<span data-name="stats" class="">统计</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position: relative;">
|
||||
<div id="postgresql_ot_table" class="divtable">
|
||||
<table class="table table-hover">
|
||||
<thead style="position: relative;z-index: 1;">
|
||||
<tr>
|
||||
<th><span>编号</span></th>
|
||||
<th><span>操作类型</span></th>
|
||||
<th><span>详情</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- postgresql end -->
|
||||
|
||||
<!-- mysql start -->
|
||||
<!-- show w-full -->
|
||||
<div class="tab-con hide" id="mysql" style="padding: 0px;">
|
||||
|
||||
@@ -143,6 +143,60 @@ function myPostCBN(method, args, callback){
|
||||
},'json');
|
||||
}
|
||||
|
||||
function pgPostCB(method, args, callback){
|
||||
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
|
||||
|
||||
var req_data = {};
|
||||
req_data['name'] = 'data_query';
|
||||
req_data['func'] = method;
|
||||
req_data['script']='sql_postgresql';
|
||||
args['version'] = '';
|
||||
|
||||
if (typeof(args) == 'string' && args == ''){
|
||||
req_data['args'] = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
req_data['args'] = JSON.stringify(args);
|
||||
}
|
||||
|
||||
$.post('/plugins/callback', req_data, function(data) {
|
||||
layer.close(loadT);
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
|
||||
function pgPostCBN(method, args, callback){
|
||||
var req_data = {};
|
||||
req_data['name'] = 'data_query';
|
||||
req_data['func'] = method;
|
||||
req_data['script']='sql_postgresql';
|
||||
args['version'] = '';
|
||||
|
||||
if (typeof(args) == 'string' && args == ''){
|
||||
req_data['args'] = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
req_data['args'] = JSON.stringify(args);
|
||||
}
|
||||
|
||||
$.post('/plugins/callback', req_data, function(data) {
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
|
||||
function selectTab(tab = 'redis'){
|
||||
$('.tab-view-box .tab-con').addClass('hide').removeClass('show').removeClass('w-full');
|
||||
@@ -163,6 +217,7 @@ function initTabFunc(tab){
|
||||
case 'mongodb':initTabMongodb();break;
|
||||
case 'memcached':initTabMemcached();break;
|
||||
case 'mysql':initTabMySQL();break;
|
||||
case 'postgresql':initTabPostgresql();break;
|
||||
case 'default:':initTabRedis();break;
|
||||
}
|
||||
}
|
||||
@@ -237,7 +292,7 @@ function initTabMemcached(){
|
||||
$('#memcached_clear_all').unbind('click').click(function(){
|
||||
var sid = memcachedGetSid();
|
||||
memPostCB('clear',{'sid':sid} ,function(rdata){
|
||||
// console.log(rdata);
|
||||
console.log("xxx",rdata);
|
||||
showMsg(rdata.data.msg,function(){
|
||||
if (rdata.data.status){
|
||||
memcachedGetList();
|
||||
@@ -927,6 +982,7 @@ function mysqlInitField(f, data){
|
||||
|
||||
|
||||
function mysqlGetServerList(call_func){
|
||||
closeInstallLayer();
|
||||
myPostCBN('get_server_list', {}, function(rdata){
|
||||
var rdata = rdata.data;
|
||||
if (rdata.data.length != 0){
|
||||
@@ -951,7 +1007,10 @@ function mysqlGetServerList(call_func){
|
||||
}
|
||||
closeInstallLayer();
|
||||
} else {
|
||||
showInstallLayer();
|
||||
layer.msg(rdata.data.msg,{ icon: 7, time: 2000, shade: 0.3 });
|
||||
setTimeout(function(){
|
||||
showInstallLayer();
|
||||
},2050);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1061,7 +1120,7 @@ function mysqlGetDataList(p){
|
||||
for (var j = 0; j < fields.length; j++) {
|
||||
var f = fields[j];
|
||||
if (f in dlist[i]) {
|
||||
tbody += '<td style="word-wrap:break-word;word-break:break-all;">'+dlist[i][f]+'</td>';
|
||||
tbody += '<td title="'+dlist[i][f]+'">'+dlist[i][f]+'</td>';
|
||||
} else {
|
||||
tbody += '<td>undefined</td>';
|
||||
}
|
||||
@@ -1100,7 +1159,7 @@ function mysqlProcessList(){
|
||||
for (var j = 0; j < fields.length; j++) {
|
||||
var f = fields[j];
|
||||
if (f in dlist[i]) {
|
||||
tbody += '<td>'+dlist[i][f]+'</td>';
|
||||
tbody += '<td title="'+dlist[i][f]+'">'+dlist[i][f]+'</td>';
|
||||
} else {
|
||||
tbody += '<td>undefined</td>';
|
||||
}
|
||||
@@ -1137,7 +1196,7 @@ function mysqlStatusList(){
|
||||
for (var j = 0; j < fields.length; j++) {
|
||||
var f = fields[j];
|
||||
if (f in dlist[i]) {
|
||||
tbody += '<td>'+dlist[i][f]+'</td>';
|
||||
tbody += '<td title="'+dlist[i][f]+'">'+dlist[i][f]+'</td>';
|
||||
} else {
|
||||
tbody += '<td>undefined</td>';
|
||||
}
|
||||
@@ -1174,7 +1233,7 @@ function mysqlStatsList(){
|
||||
for (var j = 0; j < fields.length; j++) {
|
||||
var f = fields[j];
|
||||
if (f in dlist[i]) {
|
||||
tbody += '<td>'+dlist[i][f]+'</td>';
|
||||
tbody += '<td title="'+dlist[i][f]+'">'+dlist[i][f]+'</td>';
|
||||
} else {
|
||||
tbody += '<td>undefined</td>';
|
||||
}
|
||||
@@ -1201,9 +1260,9 @@ function memcachedGetItem(){
|
||||
|
||||
function memcachedGetList(){
|
||||
var sid = memcachedGetSid();
|
||||
closeInstallLayer();
|
||||
memPostCB('get_items',{'sid':sid} ,function(rdata){
|
||||
if (rdata.data.status){
|
||||
|
||||
var items = rdata.data.data['items'];
|
||||
var content = '';
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
@@ -1219,10 +1278,14 @@ function memcachedGetList(){
|
||||
memcachedGetKeyList(1);
|
||||
});
|
||||
closeInstallLayer();
|
||||
memcachedGetKeyList(1);
|
||||
} else {
|
||||
showInstallLayer();
|
||||
layer.msg(rdata.data.msg,{ icon: 7, time: 2000, shade: 0.3 });
|
||||
setTimeout(function(){
|
||||
showInstallLayer();
|
||||
},2050);
|
||||
}
|
||||
memcachedGetKeyList(1);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1386,7 +1449,6 @@ function mongodbInitField(f, data){
|
||||
|
||||
var mogodb_db_list;
|
||||
function mongodbCollectionName(){
|
||||
// console.log(mogodb_db_list);
|
||||
var v = mogodb_db_list.getValue('value');
|
||||
if (v.length == 0){
|
||||
// console.log($('#mongodb').data('collection'));
|
||||
@@ -1397,6 +1459,7 @@ function mongodbCollectionName(){
|
||||
|
||||
function mongodbGetList(){
|
||||
var sid = mongodbGetSid();
|
||||
closeInstallLayer();
|
||||
mgdbPostCB('get_db_list',{'sid':sid} ,function(rdata){
|
||||
// console.log(rdata);
|
||||
if (rdata.data.status){
|
||||
@@ -1423,7 +1486,11 @@ function mongodbGetList(){
|
||||
|
||||
closeInstallLayer();
|
||||
} else {
|
||||
showInstallLayer();
|
||||
layer.msg(rdata.data.msg,{ icon: 7, time: 2000, shade: 0.3 });
|
||||
setTimeout(function(){
|
||||
showInstallLayer();
|
||||
},2050);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1606,6 +1673,7 @@ function redisGetIdx(){
|
||||
|
||||
function redisGetList(){
|
||||
var sid = redisGetSid();
|
||||
closeInstallLayer();
|
||||
redisPostCB('get_list',{'sid':sid} ,function(rdata){
|
||||
if (rdata.data.status){
|
||||
var list = rdata.data.data;
|
||||
@@ -1627,7 +1695,10 @@ function redisGetList(){
|
||||
redisGetKeyList(1);
|
||||
closeInstallLayer();
|
||||
} else {
|
||||
showInstallLayer();
|
||||
layer.msg(rdata.data.msg,{ icon: 7, time: 1000, shade: 0.3 });
|
||||
setTimeout(function(){
|
||||
showInstallLayer();
|
||||
},1050);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1936,3 +2007,663 @@ function redisBatchClear(){
|
||||
}
|
||||
// ------------------------- redis end ---------------------------------
|
||||
|
||||
// ------------------------- postgresql start -------------------------------
|
||||
var pg_timer = null;
|
||||
var pg_last_error = '';
|
||||
|
||||
function pgToastError(msg){
|
||||
// 轮询失败时避免每 2 秒重复弹出相同的提示
|
||||
if (!msg || pg_last_error == msg){
|
||||
return;
|
||||
}
|
||||
pg_last_error = msg;
|
||||
layer.msg(msg,{icon:7,time:2500});
|
||||
}
|
||||
|
||||
function pgHtml(v){
|
||||
if (v === null || v === undefined){
|
||||
return '';
|
||||
}
|
||||
return String(v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function pgGetSid(){
|
||||
return $('#postgresql select[name=sid]').val();
|
||||
}
|
||||
|
||||
function pgGetDbName(){
|
||||
return $('#postgresql .postgresql_db_list select[name=postgresql_db]').val();
|
||||
}
|
||||
|
||||
function pgGetTableName(){
|
||||
var table = $('#postgresql .postgresql_table_list select[name=postgresql_table]').val();
|
||||
if (!table || table == '0'){
|
||||
return '';
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
function pgShowNotice(title, msg){
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: title,
|
||||
area: ['560px', '260px'],
|
||||
closeBtn: 1,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20"><pre style="white-space:pre-wrap;word-break:break-all;margin:0;line-height:1.8;">'+pgHtml(msg)+'</pre></div>'
|
||||
});
|
||||
}
|
||||
|
||||
function pgModalTable(title, list, width, height){
|
||||
if (!list || list.length == 0){
|
||||
layer.msg('暂无数据',{icon:7});
|
||||
return;
|
||||
}
|
||||
|
||||
var fields = mongodbGetDataFields(list);
|
||||
var thead = '';
|
||||
for (var i = 0; i < fields.length; i++){
|
||||
thead += '<th>'+pgHtml(fields[i])+'</th>';
|
||||
}
|
||||
|
||||
var tbody = '';
|
||||
for (var i = 0; i < list.length; i++){
|
||||
tbody += '<tr>';
|
||||
for (var j = 0; j < fields.length; j++){
|
||||
var f = fields[j];
|
||||
var val = (f in list[i]) ? list[i][f] : '';
|
||||
tbody += '<td title="'+pgHtml(val)+'">'+pgHtml(val)+'</td>';
|
||||
}
|
||||
tbody += '</tr>';
|
||||
}
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: title,
|
||||
area: [width || '900px', height || '420px'],
|
||||
closeBtn: 1,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 divtable taskdivtable" style="height:'+(parseInt(height || '420') - 80)+'px;overflow:auto;">\
|
||||
<table class="table table-hover">\
|
||||
<thead><tr>'+thead+'</tr></thead>\
|
||||
<tbody>'+tbody+'</tbody>\
|
||||
</table>\
|
||||
</div>'
|
||||
});
|
||||
}
|
||||
|
||||
function pgInitField(f, data){
|
||||
var option_html = '<option value="0">无字段</option>';
|
||||
for (var i = 0; i < f.length; i++){
|
||||
if (data['soso_field'] == f[i]){
|
||||
option_html += '<option value="'+pgHtml(f[i])+'" selected>'+pgHtml(f[i])+'</option>';
|
||||
} else {
|
||||
option_html += '<option value="'+pgHtml(f[i])+'">'+pgHtml(f[i])+'</option>';
|
||||
}
|
||||
}
|
||||
$('#postgresql select[name="postgresql_field_key"]').html(option_html);
|
||||
}
|
||||
|
||||
function initTabPostgresql(){
|
||||
clearInterval(pg_timer);
|
||||
pg_last_error = '';
|
||||
|
||||
pgGetServerList(function(){
|
||||
pgGetDbList();
|
||||
pgProcessList();
|
||||
|
||||
clearInterval(pg_timer);
|
||||
pg_timer = setInterval(function(){
|
||||
var name = $('#postgresql_list_tab .tab-nav span.on').data('name');
|
||||
pgRunPgTab(name);
|
||||
|
||||
var fname = $('#cutTab .tabs-item.active').data('name');
|
||||
if (fname != 'postgresql'){
|
||||
clearInterval(pg_timer);
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
$('#postgresql_list_tab .tab-nav span').unbind('click').click(function(){
|
||||
$('#postgresql_list_tab .tab-nav span').removeClass('on');
|
||||
$(this).addClass('on');
|
||||
var name = $(this).data('name');
|
||||
pgRunPgTab(name);
|
||||
});
|
||||
|
||||
$('#postgresql_find').unbind('click').click(function(){
|
||||
var val = $('#postgresql input[name="postgresql_field_value"]').val();
|
||||
if (val == ''){
|
||||
layer.msg('搜索不能为空!',{icon:7});
|
||||
return;
|
||||
}
|
||||
pgGetDataList(1);
|
||||
});
|
||||
|
||||
pgCommonFunc();
|
||||
}
|
||||
|
||||
function pgRunPgTab(name){
|
||||
switch(name){
|
||||
case 'proccess':pgProcessList();break;
|
||||
case 'status':pgStatusList();break;
|
||||
case 'stats':pgStatsList();break;
|
||||
}
|
||||
}
|
||||
|
||||
function pgGetServerList(call_func){
|
||||
closeInstallLayer();
|
||||
pgPostCBN('get_server_list', {}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (data.data && data.data.length != 0){
|
||||
var items = data.data;
|
||||
var content = '';
|
||||
for (var i = 0; i < items.length; i++){
|
||||
var t = items[i];
|
||||
if (i == 0){
|
||||
content += '<option value="'+t['val']+'" selected>'+t['name']+'</option>';
|
||||
} else {
|
||||
content += '<option value="'+t['val']+'">'+t['name']+'</option>';
|
||||
}
|
||||
}
|
||||
|
||||
$('#postgresql select[name=sid]').html(content);
|
||||
$('#postgresql select[name=sid]').change(function(){
|
||||
pgGetDbList();
|
||||
});
|
||||
|
||||
if (typeof(call_func) == 'function'){
|
||||
call_func();
|
||||
}
|
||||
closeInstallLayer();
|
||||
} else {
|
||||
layer.msg('当前未安装 PostgreSQL',{icon:7,time:2000,shade:0.3});
|
||||
setTimeout(function(){
|
||||
showInstallLayer();
|
||||
},2050);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pgGetDbList(){
|
||||
var sid = pgGetSid();
|
||||
pgPostCBN('get_db_list',{'sid':sid}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
|
||||
var items = data.data['list'];
|
||||
if (items.length == 0){
|
||||
$('#postgresql .postgresql_db_list select[name=postgresql_db]').html('<option value="0">数据库空</option>');
|
||||
$('#postgresql .postgresql_table_list select[name=postgresql_table]').html('<option value="0">数据表空</option>');
|
||||
$('#postgresql_table thead tr').html('');
|
||||
$('#postgresql_table tbody').html('');
|
||||
$('.postgresql_list_page').html('<div><span class="Pcurrent">0</span><span class="Pcount">共0条</span></div>');
|
||||
return;
|
||||
}
|
||||
|
||||
var content = '';
|
||||
for (var i = 0; i < items.length; i++){
|
||||
var name = items[i];
|
||||
if (i == 0){
|
||||
content += '<option value="'+pgHtml(name)+'" selected>database['+pgHtml(name)+']</option>';
|
||||
} else {
|
||||
content += '<option value="'+pgHtml(name)+'">database['+pgHtml(name)+']</option>';
|
||||
}
|
||||
}
|
||||
|
||||
$('#postgresql .postgresql_db_list select[name=postgresql_db]').html(content);
|
||||
$('#postgresql .postgresql_db_list select[name=postgresql_db]').change(function(){
|
||||
pgGetTableList(1);
|
||||
});
|
||||
|
||||
pgGetTableList(1);
|
||||
});
|
||||
}
|
||||
|
||||
function pgGetTableList(p){
|
||||
var sid = pgGetSid();
|
||||
var db = pgGetDbName();
|
||||
if (!db || db == '0'){
|
||||
return;
|
||||
}
|
||||
|
||||
pgPostCBN('get_table_list',{'sid':sid,'db':db}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
|
||||
var items = data.data['list'];
|
||||
if (items.length == 0){
|
||||
$('#postgresql .postgresql_table_list select[name=postgresql_table]').html('<option value="0">数据表空</option>');
|
||||
$('#postgresql_table thead tr').html('');
|
||||
$('#postgresql_table tbody').html('');
|
||||
$('.postgresql_list_page').html('<div><span class="Pcurrent">0</span><span class="Pcount">共0条</span></div>');
|
||||
return;
|
||||
}
|
||||
|
||||
var content = '';
|
||||
for (var i = 0; i < items.length; i++){
|
||||
var name = items[i];
|
||||
if (i == 0){
|
||||
content += '<option value="'+pgHtml(name)+'" selected>table['+pgHtml(name)+']</option>';
|
||||
} else {
|
||||
content += '<option value="'+pgHtml(name)+'">table['+pgHtml(name)+']</option>';
|
||||
}
|
||||
}
|
||||
|
||||
$('#postgresql .postgresql_table_list select[name=postgresql_table]').html(content);
|
||||
$('#postgresql .postgresql_table_list select[name=postgresql_table]').change(function(){
|
||||
pgGetDataList(1);
|
||||
});
|
||||
|
||||
pgGetDataList(1);
|
||||
});
|
||||
}
|
||||
|
||||
function pgGetDataList(p){
|
||||
var sid = pgGetSid();
|
||||
var db = pgGetDbName();
|
||||
var table = pgGetTableName();
|
||||
|
||||
var pg_field = $('#postgresql select[name="postgresql_field_key"]').val();
|
||||
var pg_value = $('#postgresql input[name="postgresql_field_value"]').val();
|
||||
|
||||
var request_data = {
|
||||
'sid': sid,
|
||||
'db': db,
|
||||
'table': table,
|
||||
'p': p,
|
||||
'size': 10
|
||||
};
|
||||
|
||||
if (pg_field != '0' && pg_field){
|
||||
request_data['where'] = {field: pg_field, value: pg_value};
|
||||
} else {
|
||||
request_data['where'] = {};
|
||||
}
|
||||
|
||||
pgPostCB('get_data_list', request_data, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
|
||||
var rdata2 = data.data;
|
||||
var dlist = rdata2['list'];
|
||||
var fields = mongodbGetDataFields(dlist);
|
||||
if (fields.length != 0){
|
||||
pgInitField(fields, rdata2);
|
||||
}
|
||||
|
||||
var header_field = '';
|
||||
for (var i = 0; i < fields.length; i++){
|
||||
header_field += '<th>'+pgHtml(fields[i])+'</th>';
|
||||
}
|
||||
$('#postgresql_table thead tr').html(header_field);
|
||||
|
||||
var tbody = '';
|
||||
for (var i = 0; i < dlist.length; i++){
|
||||
tbody += '<tr>';
|
||||
for (var j = 0; j < fields.length; j++){
|
||||
var f = fields[j];
|
||||
var val = (f in dlist[i]) ? dlist[i][f] : '';
|
||||
tbody += '<td title="'+pgHtml(val)+'">'+pgHtml(val)+'</td>';
|
||||
}
|
||||
tbody += '</tr>';
|
||||
}
|
||||
$('#postgresql_table tbody').html(tbody);
|
||||
$('#postgresql .postgresql_list_page').html(rdata2['page']);
|
||||
});
|
||||
}
|
||||
|
||||
function pgRenderOtTable(dlist){
|
||||
var fields = mongodbGetDataFields(dlist);
|
||||
|
||||
var header_field = '';
|
||||
for (var i = 0; i < fields.length; i++){
|
||||
header_field += '<th>'+pgHtml(fields[i])+'</th>';
|
||||
}
|
||||
$('#postgresql_ot_table thead tr').html(header_field);
|
||||
|
||||
var tbody = '';
|
||||
for (var i = 0; i < dlist.length; i++){
|
||||
tbody += '<tr>';
|
||||
for (var j = 0; j < fields.length; j++){
|
||||
var f = fields[j];
|
||||
var val = (f in dlist[i]) ? dlist[i][f] : '';
|
||||
tbody += '<td title="'+pgHtml(val)+'">'+pgHtml(val)+'</td>';
|
||||
}
|
||||
tbody += '</tr>';
|
||||
}
|
||||
$('#postgresql_ot_table tbody').html(tbody);
|
||||
}
|
||||
|
||||
function pgProcessList(){
|
||||
pgPostCBN('get_proccess_list',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
pgToastError(data.msg);
|
||||
return;
|
||||
}
|
||||
pg_last_error = '';
|
||||
pgRenderOtTable(data.data['list']);
|
||||
});
|
||||
}
|
||||
|
||||
function pgStatusList(){
|
||||
pgPostCBN('get_status_list',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
pgToastError(data.msg);
|
||||
return;
|
||||
}
|
||||
pg_last_error = '';
|
||||
pgRenderOtTable(data.data['list']);
|
||||
});
|
||||
}
|
||||
|
||||
function pgStatsList(){
|
||||
pgPostCBN('get_stats_list',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
pgToastError(data.msg);
|
||||
return;
|
||||
}
|
||||
pg_last_error = '';
|
||||
pgRenderOtTable(data.data['list']);
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFunc(){
|
||||
$('#postgresql_common').unbind('click').click(function(){
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: "PostgreSQL常用功能",
|
||||
area: ['600px', '200px'],
|
||||
closeBtn: 1,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20">\
|
||||
<button style="margin-bottom: 8px;" id="pg_top_nsql" type="button" class="btn btn-default btn-sm">查询执行次数最频繁的前N条SQL语句</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_net_stat" type="button" class="btn btn-default btn-sm">PostgreSQL服务器的QPS/TPS指标</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_redundant_indexes" type="button" class="btn btn-default btn-sm">查看重复或冗余的索引</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_table_info" type="button" class="btn btn-default btn-sm">统计库里每个表的大小</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_conn_count" type="button" class="btn btn-default btn-sm">查看应用端IP连接数总和</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_fpk_info" type="button" class="btn btn-default btn-sm">快速找出没有主键的表</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_lock_sql" type="button" class="btn btn-default btn-sm">查看当前锁阻塞的SQL</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_deadlock_info" type="button" class="btn btn-default btn-sm">查看死锁信息</button>\
|
||||
<button style="margin-bottom: 8px;" id="pg_slave_status" type="button" class="btn btn-default btn-sm">查看主从复制信息</button>\
|
||||
</div>',
|
||||
success:function(i,l){
|
||||
$('#pg_top_nsql').click(function(){ pgCommonFuncTopN(); });
|
||||
$('#pg_net_stat').click(function(){ pgCommonFuncNet(); });
|
||||
$('#pg_redundant_indexes').click(function(){ pgCommonFuncRedundantIndexes(); });
|
||||
$('#pg_table_info').click(function(){ pgCommonFuncTableInfo(); });
|
||||
$('#pg_conn_count').click(function(){ pgCommonFuncConnCount(); });
|
||||
$('#pg_fpk_info').click(function(){ pgCommonFuncFpkInfo(); });
|
||||
$('#pg_lock_sql').click(function(){ pgCommonFuncLockSQL(); });
|
||||
$('#pg_deadlock_info').click(function(){ pgCommonFuncDeadlockInfo(); });
|
||||
$('#pg_slave_status').click(function(){ pgCommonFuncSlaveStatus(); });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncTopN(){
|
||||
pgPostCBN('get_topn_list',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
pgShowNotice('查询执行次数最频繁的前N条SQL语句', data.msg);
|
||||
return;
|
||||
}
|
||||
pgModalTable('查询执行次数最频繁的前N条SQL语句', data.data, '1000px', '450px');
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncNet(){
|
||||
pgPostCBN('get_net_list',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
pgShowNotice('PostgreSQL服务器的QPS/TPS指标', data.msg);
|
||||
return;
|
||||
}
|
||||
pgModalTable('PostgreSQL服务器的QPS/TPS指标(网络带宽需在系统层监控)', data.data, '1000px', '360px');
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncRedundantIndexes(){
|
||||
function renderList(){
|
||||
pgPostCBN('get_redundant_indexes',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
|
||||
var items = data.data;
|
||||
if (items.length == 0){
|
||||
layer.msg('没有发现重复或冗余的索引',{icon:7});
|
||||
return;
|
||||
}
|
||||
|
||||
var tbody = '';
|
||||
for (var i = 0; i < items.length; i++){
|
||||
tbody += '<tr>\
|
||||
<td>'+pgHtml(items[i]['table_schema'])+'</td>\
|
||||
<td>'+pgHtml(items[i]['table_name'])+'</td>\
|
||||
<td>'+pgHtml(items[i]['redundant_index_name'])+'</td>\
|
||||
<td>'+pgHtml(items[i]['redundant_index_columns'])+'</td>\
|
||||
<td><a class="pg_drop_index btlink" index="'+i+'">删除</a></td>\
|
||||
</tr>';
|
||||
}
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: "查看重复或冗余的索引",
|
||||
area: ['1000px', '420px'],
|
||||
closeBtn: 1,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 divtable taskdivtable">\
|
||||
<table class="table table-hover" id="pg_redundant_table">\
|
||||
<thead><tr>\
|
||||
<th>库名</th><th>表名</th><th>冗余索引</th><th>字段</th><th>操作</th>\
|
||||
</tr></thead>\
|
||||
<tbody>'+tbody+'</tbody>\
|
||||
</table>\
|
||||
</div>',
|
||||
success: function(){
|
||||
$('#pg_redundant_table .pg_drop_index').click(function(){
|
||||
var index = $(this).attr('index');
|
||||
layer.confirm('确定删除索引 '+pgHtml(items[index]['redundant_index_name'])+' ?', {btn:['确定','取消']}, function(li){
|
||||
pgPostCB('redundant_indexes_cmd', {'sid':pgGetSid(),'index':index}, function(r2){
|
||||
var d = r2.data;
|
||||
showMsg(d.msg,function(){
|
||||
if (d.status){
|
||||
layer.close(li);
|
||||
renderList();
|
||||
}
|
||||
},{icon: d.status ? 1 : 2}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
renderList();
|
||||
}
|
||||
|
||||
function pgCommonFuncTableInfo(){
|
||||
pgPostCBN('get_table_info',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
pgShowNotice('统计库里每个表的大小(单位 GB)', data.msg);
|
||||
return;
|
||||
}
|
||||
pgModalTable('统计库里每个表的大小(单位 GB)', data.data, '1200px', '460px');
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncConnCount(){
|
||||
pgPostCBN('get_conn_count',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
pgShowNotice('查看应用端IP连接数总和', data.msg);
|
||||
return;
|
||||
}
|
||||
pgModalTable('查看应用端IP连接数总和', data.data, '700px', '420px');
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncFpkInfo(){
|
||||
pgPostCBN('get_fpk_info',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
pgModalTable('快速找出没有主键的表', data.data, '800px', '400px');
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncLockSQL(){
|
||||
function renderSQL(){
|
||||
pgPostCBN('get_lock_sql',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
|
||||
var items = data.data;
|
||||
var tbody = '';
|
||||
for (var i = 0; i < items.length; i++){
|
||||
var t = '<tr>';
|
||||
t += '<td>'+pgHtml(items[i]['trx_id'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['trx_state'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['trx_started'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['processlist_id'])+'</td>';
|
||||
t += '<td title="'+pgHtml(items[i]['info'])+'">'+pgHtml(items[i]['info'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['user'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['host'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['db'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['command'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['state'])+'</td>';
|
||||
t += '<td>'+pgHtml(items[i]['sql_kill_blocking_query'])+'</td>';
|
||||
t += '<td><a class="exec btlink" index="'+i+'">执行</a></td>';
|
||||
t += '</tr>';
|
||||
tbody += t;
|
||||
}
|
||||
$('#pg_lock_table tbody').html(tbody);
|
||||
|
||||
$('#pg_lock_table tbody .exec').click(function(){
|
||||
var index = $(this).attr('index');
|
||||
var pid = items[index]['processlist_id'];
|
||||
pgPostCB('kill_lock_pid', {'sid':pgGetSid(),'pid':pid}, function(r2){
|
||||
var d = r2.data;
|
||||
showMsg(d.msg,function(){
|
||||
if (d.status){
|
||||
renderSQL();
|
||||
}
|
||||
},{icon: d.status ? 1 : 2}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: "查看当前锁阻塞的SQL",
|
||||
area: ['1100px', '420px'],
|
||||
closeBtn: 1,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 divtable taskdivtable">\
|
||||
<div class="mr20 pull-left" style="border-right: 1px solid #ccc; padding-right: 20px;">\
|
||||
<button id="pg_kill_all" type="button" class="btn btn-default btn-sm">关闭所有阻塞</button>\
|
||||
</div>\
|
||||
<hr />\
|
||||
<table class="table table-hover" id="pg_lock_table">\
|
||||
<thead><tr>\
|
||||
<th>事务ID</th><th>事务状态</th><th>执行时间</th><th>线程ID</th>\
|
||||
<th>Info</th><th>user</th><th>host</th><th>db</th>\
|
||||
<th>command</th><th>state</th><th>kill</th><th>操作</th>\
|
||||
</tr></thead>\
|
||||
<tbody></tbody>\
|
||||
</table>\
|
||||
</div>',
|
||||
success:function(){
|
||||
renderSQL();
|
||||
$('#pg_kill_all').unbind('click').click(function(){
|
||||
pgPostCB('kill_all_lock', {'sid':pgGetSid()}, function(r2){
|
||||
var d = r2.data;
|
||||
showMsg(d.msg,function(){
|
||||
if (d.status){
|
||||
renderSQL();
|
||||
}
|
||||
},{icon: d.status ? 1 : 2}, 2000);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncDeadlockInfo(){
|
||||
function renderText(){
|
||||
pgPostCBN('get_deadlock_info',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
$('#pg_info_log').val(data.data);
|
||||
});
|
||||
}
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: "查看死锁信息",
|
||||
area: ['800px', '400px'],
|
||||
closeBtn: 1,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd15">\
|
||||
<textarea readonly="" style="margin: 0px;height: 330px;width: 100%;background-color: #333;color:#fff; padding:0 5px" id="pg_info_log"></textarea>\
|
||||
</div>',
|
||||
success:function(){
|
||||
renderText();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pgCommonFuncSlaveStatus(){
|
||||
function renderText(){
|
||||
pgPostCBN('get_slave_status',{'sid':pgGetSid()}, function(rdata){
|
||||
var data = rdata.data;
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:7,time:2500});
|
||||
return;
|
||||
}
|
||||
$('#pg_info_log').val(data.data);
|
||||
});
|
||||
}
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: "查看主从复制信息",
|
||||
area: ['800px', '400px'],
|
||||
closeBtn: 1,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd15">\
|
||||
<textarea readonly="" style="margin: 0px;height: 330px;width: 100%;background-color: #333;color:#fff; padding:0 5px" id="pg_info_log"></textarea>\
|
||||
</div>',
|
||||
success:function(){
|
||||
renderText();
|
||||
}
|
||||
});
|
||||
}
|
||||
// ------------------------- postgresql end ---------------------------------
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sort": 7,
|
||||
"sort": 1000,
|
||||
"ps": "Docker是一个开源的应用容器引擎",
|
||||
"name": "docker",
|
||||
"title": "Docker",
|
||||
|
||||
@@ -13,7 +13,7 @@ serverPath=$(dirname "$rootPath")
|
||||
VERSION=$2
|
||||
|
||||
if [ -f ${rootPath}/bin/activate ];then
|
||||
source ${rootPath}/bin/activate
|
||||
. ${rootPath}/bin/activate
|
||||
fi
|
||||
|
||||
# {
|
||||
@@ -40,7 +40,7 @@ fi
|
||||
Install_Docker()
|
||||
{
|
||||
# which docker
|
||||
# if [ "$?" == "0" ];then
|
||||
# if [ "$?" = "0" ];then
|
||||
# echo '安装已经完成docker'
|
||||
# exit 0
|
||||
# fi
|
||||
@@ -69,7 +69,7 @@ Uninstall_Docker()
|
||||
{
|
||||
CMD=yum
|
||||
which apt
|
||||
if [ "$?" == "0" ];then
|
||||
if [ "$?" = "0" ];then
|
||||
CMD=apt
|
||||
fi
|
||||
|
||||
@@ -97,7 +97,7 @@ Uninstall_Docker()
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
if [ "${1}" = 'install' ];then
|
||||
Install_Docker
|
||||
else
|
||||
Uninstall_Docker
|
||||
|
||||
48
plugins/doh/config/config.toml
Normal file
48
plugins/doh/config/config.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
[general]
|
||||
# 监听地址和端口
|
||||
listen_addr = "0.0.0.0:8080"
|
||||
|
||||
# DoH 路径
|
||||
path = "/dns-query"
|
||||
|
||||
# TLS 配置(如果需要 HTTPS)
|
||||
# tls_cert_path = "/etc/doh-proxy/cert.pem"
|
||||
# tls_key_path = "/etc/doh-proxy/key.pem"
|
||||
|
||||
# 并发请求限制
|
||||
max_concurrent_requests = 512
|
||||
|
||||
# 日志文件
|
||||
log_file = "{$SERVER_PATH}/doh/doh-proxy.log"
|
||||
|
||||
[upstream]
|
||||
# 上游 DNS 服务器
|
||||
upstream_addr = "1.1.1.1:443"
|
||||
bootstrap_addr = "1.1.1.1:53"
|
||||
|
||||
# 上游 DoH 服务器 URL(如果使用标准的 DoH 端点)
|
||||
# upstream_url = "https://cloudflare-dns.com/dns-query"
|
||||
|
||||
# 超时设置
|
||||
timeout = 10
|
||||
|
||||
# 重试次数
|
||||
tries = 3
|
||||
|
||||
# 启用 TCP 保活
|
||||
tcp_keepalive = 30
|
||||
|
||||
[cache]
|
||||
# 缓存设置
|
||||
max_entries = 65536
|
||||
min_ttl = 60
|
||||
max_ttl = 3600
|
||||
|
||||
[network]
|
||||
# 网络设置
|
||||
tcp_fastopen = true
|
||||
reuse_port = true
|
||||
|
||||
[log]
|
||||
# 日志级别:debug, info, warn, error
|
||||
level = "info"
|
||||
BIN
plugins/doh/ico.png
Normal file
BIN
plugins/doh/ico.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user